in src/transports/winhttp.c [959:1226]
static int winhttp_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
size_t buf_size,
size_t *bytes_read)
{
winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD dw_bytes_read;
char replay_count = 0;
int error;
replay:
/* Enforce a reasonable cap on the number of replays */
if (replay_count++ >= GIT_HTTP_REPLAY_MAX) {
git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays");
return -1;
}
/* Connect if necessary */
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
if (!s->received_response) {
DWORD status_code, status_code_length, content_type_length, bytes_written;
char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
if (!s->sent_request) {
if ((error = send_request(s, s->post_body_len, false)) < 0)
return error;
s->sent_request = 1;
}
if (s->chunked) {
assert(s->verb == post_verb);
/* Flush, if necessary */
if (s->chunk_buffer_len > 0 &&
write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
/* Write the final chunk. */
if (!WinHttpWriteData(s->request,
"0\r\n\r\n", 5,
&bytes_written)) {
git_error_set(GIT_ERROR_OS, "failed to write final chunk");
return -1;
}
}
else if (s->post_body) {
char *buffer;
DWORD len = s->post_body_len, bytes_read;
if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
0, 0, FILE_BEGIN) &&
NO_ERROR != GetLastError()) {
git_error_set(GIT_ERROR_OS, "failed to reset file pointer");
return -1;
}
buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
GIT_ERROR_CHECK_ALLOC(buffer);
while (len > 0) {
DWORD bytes_written;
if (!ReadFile(s->post_body, buffer,
min(CACHED_POST_BODY_BUF_SIZE, len),
&bytes_read, NULL) ||
!bytes_read) {
git__free(buffer);
git_error_set(GIT_ERROR_OS, "failed to read from temp file");
return -1;
}
if (!WinHttpWriteData(s->request, buffer,
bytes_read, &bytes_written)) {
git__free(buffer);
git_error_set(GIT_ERROR_OS, "failed to write data");
return -1;
}
len -= bytes_read;
assert(bytes_read == bytes_written);
}
git__free(buffer);
/* Eagerly close the temp file */
CloseHandle(s->post_body);
s->post_body = NULL;
}
if (!WinHttpReceiveResponse(s->request, 0)) {
git_error_set(GIT_ERROR_OS, "failed to receive response");
return -1;
}
/* Verify that we got a 200 back */
status_code_length = sizeof(status_code);
if (!WinHttpQueryHeaders(s->request,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&status_code, &status_code_length,
WINHTTP_NO_HEADER_INDEX)) {
git_error_set(GIT_ERROR_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 ||
HTTP_STATUS_PERMANENT_REDIRECT == status_code)) {
/* Check for Windows 7. This workaround is only necessary on
* Windows Vista and earlier. Windows 7 is version 6.1. */
wchar_t *location;
DWORD location_length;
char *location8;
/* 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) {
git_error_set(GIT_ERROR_OS, "failed to read Location header");
return -1;
}
location = git__malloc(location_length);
GIT_ERROR_CHECK_ALLOC(location);
if (!WinHttpQueryHeaders(s->request,
WINHTTP_QUERY_LOCATION,
WINHTTP_HEADER_NAME_BY_INDEX,
location,
&location_length,
WINHTTP_NO_HEADER_INDEX)) {
git_error_set(GIT_ERROR_OS, "failed to read Location header");
git__free(location);
return -1;
}
/* Convert the Location header to UTF-8 */
if (git__utf16_to_8_alloc(&location8, location) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8");
git__free(location);
return -1;
}
git__free(location);
/* Replay the request */
winhttp_stream_close(s);
if (!git__prefixcmp_icase(location8, prefix_https)) {
/* Upgrade to secure connection; disconnect and start over */
if (git_net_url_apply_redirect(&t->server.url, location8, s->service_url) < 0) {
git__free(location8);
return -1;
}
winhttp_close_connection(t);
if (winhttp_connect(t) < 0)
return -1;
}
git__free(location8);
goto replay;
}
/* Handle authentication failures */
if (status_code == HTTP_STATUS_DENIED) {
int error = acquire_credentials(s->request,
&t->server,
t->owner->url,
t->owner->cred_acquire_cb,
t->owner->cred_acquire_payload);
if (error < 0) {
return error;
} else if (!error) {
assert(t->server.cred);
winhttp_stream_close(s);
goto replay;
}
} else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
int error = acquire_credentials(s->request,
&t->proxy,
t->owner->proxy.url,
t->owner->proxy.credentials,
t->owner->proxy.payload);
if (error < 0) {
return error;
} else if (!error) {
assert(t->proxy.cred);
winhttp_stream_close(s);
goto replay;
}
}
if (HTTP_STATUS_OK != status_code) {
git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code);
return -1;
}
/* Verify that we got the correct content-type back */
if (post_verb == s->verb)
p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
else
p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters");
return -1;
}
content_type_length = sizeof(content_type);
if (!WinHttpQueryHeaders(s->request,
WINHTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
&content_type, &content_type_length,
WINHTTP_NO_HEADER_INDEX)) {
git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type");
return -1;
}
if (wcscmp(expected_content_type, content_type)) {
git_error_set(GIT_ERROR_HTTP, "received unexpected content-type");
return -1;
}
s->received_response = 1;
}
if (!WinHttpReadData(s->request,
(LPVOID)buffer,
(DWORD)buf_size,
&dw_bytes_read))
{
git_error_set(GIT_ERROR_OS, "failed to read data");
return -1;
}
*bytes_read = dw_bytes_read;
return 0;
}