Commit 08283cbd by Vicent Martí

Merge pull request #1448 from phkelley/development

Avoid pre-Win7 WinHTTP self-redirect quirk
parents c45d9c46 b39f9697
...@@ -29,9 +29,10 @@ ...@@ -29,9 +29,10 @@
# include "win32/msvc-compat.h" # include "win32/msvc-compat.h"
# include "win32/mingw-compat.h" # include "win32/mingw-compat.h"
# include "win32/error.h" # include "win32/error.h"
# include "win32/version.h"
# ifdef GIT_THREADS # ifdef GIT_THREADS
# include "win32/pthread.h" # include "win32/pthread.h"
#endif # endif
#else #else
......
...@@ -58,6 +58,7 @@ typedef struct { ...@@ -58,6 +58,7 @@ typedef struct {
const char *service_url; const char *service_url;
const wchar_t *verb; const wchar_t *verb;
HINTERNET request; HINTERNET request;
wchar_t *request_uri;
char *chunk_buffer; char *chunk_buffer;
unsigned chunk_buffer_len; unsigned chunk_buffer_len;
HANDLE post_body; HANDLE post_body;
...@@ -145,10 +146,10 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -145,10 +146,10 @@ static int winhttp_stream_connect(winhttp_stream *s)
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
char *proxy_url = NULL; char *proxy_url = NULL;
wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; wchar_t ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL }; wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE; BOOL peerdist = FALSE;
int error = -1; int error = -1, wide_len;
/* Prepare URL */ /* Prepare URL */
git_buf_printf(&buf, "%s%s", t->path, s->service_url); git_buf_printf(&buf, "%s%s", t->path, s->service_url);
...@@ -156,13 +157,31 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -156,13 +157,31 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (git_buf_oom(&buf)) if (git_buf_oom(&buf))
return -1; return -1;
git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); /* Convert URL to wide characters */
wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
git_buf_cstr(&buf), -1, NULL, 0);
if (!wide_len) {
giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
goto on_error;
}
s->request_uri = git__malloc(wide_len * sizeof(wchar_t));
if (!s->request_uri)
goto on_error;
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
git_buf_cstr(&buf), -1, s->request_uri, wide_len)) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
goto on_error;
}
/* Establish request */ /* Establish request */
s->request = WinHttpOpenRequest( s->request = WinHttpOpenRequest(
t->connection, t->connection,
s->verb, s->verb,
url, s->request_uri,
NULL, NULL,
WINHTTP_NO_REFERER, WINHTTP_NO_REFERER,
types, types,
...@@ -179,19 +198,36 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -179,19 +198,36 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (proxy_url) { if (proxy_url) {
WINHTTP_PROXY_INFO proxy_info; WINHTTP_PROXY_INFO proxy_info;
size_t wide_len; wchar_t *proxy_wide;
/* Convert URL to wide characters */
wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
proxy_url, -1, NULL, 0);
if (!wide_len) {
giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
goto on_error;
}
proxy_wide = git__malloc(wide_len * sizeof(wchar_t));
git__utf8_to_16(url, GIT_WIN_PATH, proxy_url); if (!proxy_wide)
goto on_error;
wide_len = wcslen(url); if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
proxy_url, -1, proxy_wide, wide_len)) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
git__free(proxy_wide);
goto on_error;
}
/* Strip any trailing forward slash on the proxy URL; /* Strip any trailing forward slash on the proxy URL;
* WinHTTP doesn't like it if one is present */ * WinHTTP doesn't like it if one is present */
if (L'/' == url[wide_len - 1]) if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2])
url[wide_len - 1] = L'\0'; proxy_wide[wide_len - 2] = L'\0';
proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy_info.lpszProxy = url; proxy_info.lpszProxy = proxy_wide;
proxy_info.lpszProxyBypass = NULL; proxy_info.lpszProxyBypass = NULL;
if (!WinHttpSetOption(s->request, if (!WinHttpSetOption(s->request,
...@@ -199,8 +235,11 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -199,8 +235,11 @@ static int winhttp_stream_connect(winhttp_stream *s)
&proxy_info, &proxy_info,
sizeof(WINHTTP_PROXY_INFO))) { sizeof(WINHTTP_PROXY_INFO))) {
giterr_set(GITERR_OS, "Failed to set proxy"); giterr_set(GITERR_OS, "Failed to set proxy");
git__free(proxy_wide);
goto on_error; goto on_error;
} }
git__free(proxy_wide);
} }
/* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
...@@ -348,8 +387,15 @@ static int winhttp_stream_read( ...@@ -348,8 +387,15 @@ static int winhttp_stream_read(
winhttp_stream *s = (winhttp_stream *)stream; winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD dw_bytes_read; DWORD dw_bytes_read;
char replay_count = 0;
replay: replay:
/* Enforce a reasonable cap on the number of replays */
if (++replay_count >= 7) {
giterr_set(GITERR_NET, "Too many redirects or authentication replays");
return -1;
}
/* Connect if necessary */ /* Connect if necessary */
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
...@@ -445,10 +491,70 @@ replay: ...@@ -445,10 +491,70 @@ replay:
WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_HEADER_NAME_BY_INDEX,
&status_code, &status_code_length, &status_code, &status_code_length,
WINHTTP_NO_HEADER_INDEX)) { WINHTTP_NO_HEADER_INDEX)) {
giterr_set(GITERR_OS, "Failed to retreive status code"); giterr_set(GITERR_OS, "Failed to retrieve status code");
return -1;
}
/* The implementation of WinHTTP prior to Windows 7 will not
* redirect to an identical URI. Some Git hosters use self-redirects
* as part of their DoS mitigation strategy. Check first to see if we
* have a redirect status code, and that we haven't already streamed
* a post body. (We can't replay a streamed POST.) */
if (!s->chunked &&
(HTTP_STATUS_MOVED == status_code ||
HTTP_STATUS_REDIRECT == status_code ||
(HTTP_STATUS_REDIRECT_METHOD == status_code &&
get_verb == s->verb) ||
HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
/* Check for Windows 7. This workaround is only necessary on
* Windows Vista and earlier. Windows 7 is version 6.1. */
if (!git_has_win32_version(6, 1)) {
wchar_t *location;
DWORD location_length;
int redirect_cmp;
/* OK, fetch the Location header from the redirect. */
if (WinHttpQueryHeaders(s->request,
WINHTTP_QUERY_LOCATION,
WINHTTP_HEADER_NAME_BY_INDEX,
WINHTTP_NO_OUTPUT_BUFFER,
&location_length,
WINHTTP_NO_HEADER_INDEX) ||
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
giterr_set(GITERR_OS, "Failed to read Location header");
return -1; return -1;
} }
location = git__malloc(location_length);
GITERR_CHECK_ALLOC(location);
if (!WinHttpQueryHeaders(s->request,
WINHTTP_QUERY_LOCATION,
WINHTTP_HEADER_NAME_BY_INDEX,
location,
&location_length,
WINHTTP_NO_HEADER_INDEX)) {
giterr_set(GITERR_OS, "Failed to read Location header");
git__free(location);
return -1;
}
/* Compare the Location header with the request URI */
redirect_cmp = wcscmp(location, s->request_uri);
git__free(location);
if (!redirect_cmp) {
/* Replay the request */
WinHttpCloseHandle(s->request);
s->request = NULL;
s->sent_request = 0;
goto replay;
}
}
}
/* Handle authentication failures */ /* Handle authentication failures */
if (HTTP_STATUS_DENIED == status_code && if (HTTP_STATUS_DENIED == status_code &&
get_verb == s->verb && t->owner->cred_acquire_cb) { get_verb == s->verb && t->owner->cred_acquire_cb) {
...@@ -747,6 +853,11 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream) ...@@ -747,6 +853,11 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
s->post_body = NULL; s->post_body = NULL;
} }
if (s->request_uri) {
git__free(s->request_uri);
s->request_uri = NULL;
}
if (s->request) { if (s->request) {
WinHttpCloseHandle(s->request); WinHttpCloseHandle(s->request);
s->request = NULL; s->request = NULL;
...@@ -876,7 +987,7 @@ static int winhttp_receivepack( ...@@ -876,7 +987,7 @@ static int winhttp_receivepack(
{ {
/* WinHTTP only supports Transfer-Encoding: chunked /* WinHTTP only supports Transfer-Encoding: chunked
* on Windows Vista (NT 6.0) and higher. */ * on Windows Vista (NT 6.0) and higher. */
s->chunked = LOBYTE(LOWORD(GetVersion())) >= 6; s->chunked = git_has_win32_version(6, 0);
if (s->chunked) if (s->chunked)
s->parent.write = winhttp_stream_write_chunked; s->parent.write = winhttp_stream_write_chunked;
......
...@@ -8,34 +8,70 @@ ...@@ -8,34 +8,70 @@
#include "common.h" #include "common.h"
#include "error.h" #include "error.h"
#ifdef GIT_WINHTTP
# include <winhttp.h>
#endif
#define WC_ERR_INVALID_CHARS 0x80
char *git_win32_get_error_message(DWORD error_code) char *git_win32_get_error_message(DWORD error_code)
{ {
LPWSTR lpMsgBuf = NULL; LPWSTR lpMsgBuf = NULL;
HMODULE hModule = NULL;
char *utf8_msg = NULL;
int utf8_size;
DWORD dwFlags =
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
if (!error_code) if (!error_code)
return NULL; return NULL;
if (FormatMessageW( #ifdef GIT_WINHTTP
FORMAT_MESSAGE_ALLOCATE_BUFFER | /* Errors raised by WinHTTP are not in the system resource table */
FORMAT_MESSAGE_FROM_SYSTEM | if (error_code >= WINHTTP_ERROR_BASE &&
FORMAT_MESSAGE_IGNORE_INSERTS, error_code <= WINHTTP_ERROR_LAST)
NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), hModule = GetModuleHandleW(L"winhttp");
#endif
GIT_UNUSED(hModule);
if (hModule)
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
else
dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
if (FormatMessageW(dwFlags, hModule, error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&lpMsgBuf, 0, NULL)) { (LPWSTR)&lpMsgBuf, 0, NULL)) {
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, NULL, 0, NULL, NULL);
char *lpMsgBuf_utf8 = git__malloc(utf8_size * sizeof(char)); /* Invalid code point check supported on Vista+ only */
if (lpMsgBuf_utf8 == NULL) { if (git_has_win32_version(6, 0))
LocalFree(lpMsgBuf); dwFlags = WC_ERR_INVALID_CHARS;
return NULL; else
dwFlags = 0;
utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags,
lpMsgBuf, -1, NULL, 0, NULL, NULL);
if (!utf8_size) {
assert(0);
goto on_error;
} }
if (!WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, lpMsgBuf_utf8, utf8_size, NULL, NULL)) {
LocalFree(lpMsgBuf); utf8_msg = git__malloc(utf8_size);
git__free(lpMsgBuf_utf8);
return NULL; if (!utf8_msg)
goto on_error;
if (!WideCharToMultiByte(CP_UTF8, dwFlags,
lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) {
git__free(utf8_msg);
goto on_error;
} }
on_error:
LocalFree(lpMsgBuf); LocalFree(lpMsgBuf);
return lpMsgBuf_utf8;
} }
return NULL;
return utf8_msg;
} }
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_win32_version_h__
#define INCLUDE_win32_version_h__
#include <windows.h>
GIT_INLINE(int) git_has_win32_version(int major, int minor)
{
WORD wVersion = LOWORD(GetVersion());
return LOBYTE(wVersion) > major ||
(LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor);
}
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment