in Release/src/http/client/http_client_winhttp.cpp [1947:2578]
static void CALLBACK completion_callback(
HINTERNET hRequestHandle, DWORD_PTR context, DWORD statusCode, _In_ void* statusInfo, DWORD statusInfoLength)
{
(void)statusInfoLength;
std::weak_ptr<winhttp_request_context>* p_weak_request_context =
reinterpret_cast<std::weak_ptr<winhttp_request_context>*>(context);
if (p_weak_request_context == nullptr)
{
return;
}
if (statusCode == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING)
{
// This callback is responsible for freeing the type-erased context.
// This particular status code indicates that this is the final callback call, suitable for context
// destruction.
delete p_weak_request_context;
return;
}
auto p_request_context = p_weak_request_context->lock();
if (!p_request_context)
{
// The request context was already released, probably due to cancellation
return;
}
switch (statusCode)
{
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
WINHTTP_ASYNC_RESULT* error_result = reinterpret_cast<WINHTTP_ASYNC_RESULT*>(statusInfo);
const DWORD errorCode = error_result->dwError;
// Some authentication schemes require multiple transactions.
// When ERROR_WINHTTP_RESEND_REQUEST is encountered,
// we should continue to resend the request until a response is received that does not contain a 401 or
// 407 status code.
if (errorCode == ERROR_WINHTTP_RESEND_REQUEST)
{
bool resending = handle_authentication_failure(hRequestHandle, p_request_context, errorCode);
if (resending)
{
// The request is resending. Wait until we get a new response.
return;
}
}
p_request_context->report_error(errorCode, build_error_msg(error_result));
return;
}
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
{
if (!p_request_context->m_request.body())
{
// Report progress finished uploading with no message body.
auto progress = p_request_context->m_request._get_impl()->_progress_handler();
if (progress)
{
try
{
(*progress)(message_direction::upload, 0);
}
catch (...)
{
p_request_context->report_exception(std::current_exception());
return;
}
}
}
if (p_request_context->m_bodyType == transfer_encoding_chunked)
{
_transfer_encoding_chunked_write_data(p_request_context.get());
}
else if (p_request_context->m_bodyType == content_length_chunked)
{
_multiple_segment_write_data(p_request_context.get());
}
else
{
if (!WinHttpReceiveResponse(hRequestHandle, nullptr))
{
auto errorCode = GetLastError();
p_request_context->report_error(errorCode,
build_error_msg(errorCode, "WinHttpReceiveResponse"));
}
}
return;
}
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
{
p_request_context->on_send_request_validate_cn();
return;
}
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
{
p_request_context->report_exception(web::http::http_exception(
generate_security_failure_message(*reinterpret_cast<std::uint32_t*>(statusInfo))));
return;
}
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
{
DWORD bytesWritten = *((DWORD*)statusInfo);
_ASSERTE(statusInfoLength == sizeof(DWORD));
if (bytesWritten > 0)
{
auto progress = p_request_context->m_request._get_impl()->_progress_handler();
if (progress)
{
p_request_context->m_uploaded += bytesWritten;
try
{
(*progress)(message_direction::upload, p_request_context->m_uploaded);
}
catch (...)
{
p_request_context->report_exception(std::current_exception());
return;
}
}
}
if (p_request_context->is_externally_allocated())
{
p_request_context->_get_readbuffer().release(p_request_context->m_body_data.get(), bytesWritten);
}
if (p_request_context->m_bodyType == transfer_encoding_chunked)
{
_transfer_encoding_chunked_write_data(p_request_context.get());
}
else if (p_request_context->m_bodyType == content_length_chunked)
{
_multiple_segment_write_data(p_request_context.get());
}
else
{
if (!WinHttpReceiveResponse(hRequestHandle, nullptr))
{
auto errorCode = GetLastError();
p_request_context->report_error(errorCode,
build_error_msg(errorCode, "WinHttpReceiveResponse"));
}
}
return;
}
case WINHTTP_CALLBACK_STATUS_REDIRECT:
{
// Return and continue unless that's too many automatic redirects.
if (p_request_context->m_remaining_redirects > 0)
{
--p_request_context->m_remaining_redirects;
return;
}
// First need to query to see what the headers size is.
DWORD headerBufferLength = 0;
query_header_length(hRequestHandle, WINHTTP_QUERY_RAW_HEADERS_CRLF, headerBufferLength);
// Now allocate buffer for headers and query for them.
std::vector<unsigned char> header_raw_buffer;
header_raw_buffer.resize(headerBufferLength);
utility::char_t* header_buffer = reinterpret_cast<utility::char_t*>(&header_raw_buffer[0]);
if (!WinHttpQueryHeaders(hRequestHandle,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
header_buffer,
&headerBufferLength,
WINHTTP_NO_HEADER_INDEX))
{
auto errorCode = GetLastError();
p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpQueryHeaders"));
return;
}
http_response& response = p_request_context->m_response;
parse_winhttp_headers(hRequestHandle, header_buffer, response);
// Signal that the headers are available.
p_request_context->complete_headers();
// The body of the message is unavailable in WINHTTP_CALLBACK_STATUS_REDIRECT.
p_request_context->allocate_request_space(nullptr, 0);
p_request_context->complete_request(0);
// Cancel the WinHTTP operation by closing the handle.
p_request_context->cleanup();
return;
}
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
// First need to query to see what the headers size is.
DWORD headerBufferLength = 0;
query_header_length(hRequestHandle, WINHTTP_QUERY_RAW_HEADERS_CRLF, headerBufferLength);
// Now allocate buffer for headers and query for them.
std::vector<unsigned char> header_raw_buffer;
header_raw_buffer.resize(headerBufferLength);
utility::char_t* header_buffer = reinterpret_cast<utility::char_t*>(&header_raw_buffer[0]);
if (!WinHttpQueryHeaders(hRequestHandle,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
header_buffer,
&headerBufferLength,
WINHTTP_NO_HEADER_INDEX))
{
auto errorCode = GetLastError();
p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpQueryHeaders"));
return;
}
http_response& response = p_request_context->m_response;
parse_winhttp_headers(hRequestHandle, header_buffer, response);
if (response.status_code() == status_codes::Unauthorized /*401*/ ||
response.status_code() == status_codes::ProxyAuthRequired /*407*/)
{
bool resending = handle_authentication_failure(hRequestHandle, p_request_context);
if (resending)
{
// The request was not completed but resent with credentials. Wait until we get a new response
return;
}
}
// Check whether the request is compressed, and if so, whether we're handling it.
if (!p_request_context->handle_compression())
{
// false indicates report_exception was called
return;
}
if (p_request_context->m_decompressor &&
!p_request_context->m_http_client->client_config().request_compressed_response())
{
p_request_context->m_compression_state.m_chunk =
::utility::details::make_unique<winhttp_request_context::compression_state::_chunk_helper>();
p_request_context->m_compression_state.m_chunked = true;
}
// Signal that the headers are available.
p_request_context->complete_headers();
// If the method was 'HEAD,' the body of the message is by definition empty. No need to
// read it. Any headers that suggest the presence of a body can safely be ignored.
if (p_request_context->m_request.method() == methods::HEAD)
{
p_request_context->allocate_request_space(nullptr, 0);
p_request_context->complete_request(0);
return;
}
// HTTP Specification states:
// If a message is received with both a Transfer-Encoding header field
// and a Content-Length header field, the latter MUST be ignored.
// If none of them is specified, the message length should be determined by the server closing the
// connection. http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
read_next_response_chunk(p_request_context.get(), 0, true);
return;
}
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
{
// Status information contains pointer to DWORD containing number of bytes available.
const DWORD num_bytes = *(PDWORD)statusInfo;
uint8_t* buffer;
if (num_bytes > 0)
{
if (p_request_context->m_decompressor)
{
// Allocate space for the compressed data; we'll decompress it into the caller stream once it's
// been filled in
if (p_request_context->m_compression_state.m_buffer.capacity() < num_bytes)
{
p_request_context->m_compression_state.m_buffer.reserve(num_bytes);
}
buffer = p_request_context->m_compression_state.m_buffer.data();
}
else
{
auto writebuf = p_request_context->_get_writebuffer();
p_request_context->allocate_reply_space(writebuf.alloc(num_bytes), num_bytes);
buffer = p_request_context->m_body_data.get();
}
// Read in available body data all at once.
if (!WinHttpReadData(hRequestHandle, buffer, num_bytes, nullptr))
{
auto errorCode = GetLastError();
p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpReadData"));
}
}
else
{
if (p_request_context->m_decompressor)
{
if (p_request_context->m_compression_state.m_chunked)
{
// We haven't seen the 0-length chunk and/or trailing delimiter that indicate the end of
// chunked input
p_request_context->report_exception(
http_exception("Chunked response stream ended unexpectedly"));
return;
}
if (p_request_context->m_compression_state.m_started &&
!p_request_context->m_compression_state.m_done)
{
p_request_context->report_exception(
http_exception("Received incomplete compressed stream"));
return;
}
}
// No more data available, complete the request.
auto progress = p_request_context->m_request._get_impl()->_progress_handler();
if (progress)
{
try
{
(*progress)(message_direction::download, p_request_context->m_downloaded);
}
catch (...)
{
p_request_context->report_exception(std::current_exception());
return;
}
}
p_request_context->complete_request(p_request_context->m_downloaded);
}
return;
}
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
{
// Status information length contains the number of bytes read.
DWORD bytesRead = statusInfoLength;
// Report progress about downloaded bytes.
auto progress = p_request_context->m_request._get_impl()->_progress_handler();
p_request_context->m_downloaded += statusInfoLength;
if (progress)
{
try
{
(*progress)(message_direction::download, p_request_context->m_downloaded);
}
catch (...)
{
p_request_context->report_exception(std::current_exception());
return;
}
}
// If no bytes have been read, then this is the end of the response.
if (bytesRead == 0)
{
if (p_request_context->m_decompressor)
{
if (p_request_context->m_compression_state.m_chunked)
{
// We haven't seen the 0-length chunk and/or trailing delimiter that indicate the end of
// chunked input
p_request_context->report_exception(
http_exception("Chunked response stream ended unexpectedly"));
return;
}
if (p_request_context->m_compression_state.m_started &&
!p_request_context->m_compression_state.m_done)
{
p_request_context->report_exception(
http_exception("Received incomplete compressed stream"));
return;
}
}
p_request_context->complete_request(p_request_context->m_downloaded);
return;
}
auto writebuf = p_request_context->_get_writebuffer();
if (p_request_context->m_decompressor)
{
size_t chunk_size = (std::max)(static_cast<size_t>(bytesRead),
p_request_context->m_http_client->client_config().chunksize());
p_request_context->m_compression_state.m_bytes_read = static_cast<size_t>(bytesRead);
p_request_context->m_compression_state.m_chunk_bytes = 0;
// Note, some servers seem to send a first chunk of body data that decompresses to nothing, but
// initializes the decompression state; this produces no decompressed output. Subsequent chunks
// will then begin emitting decompressed body data.
// Oddly enough, WinHttp doesn't de-chunk for us if "chunked" isn't the only
// encoding, so we need to do so on the fly as we process the received data
auto process_buffer =
[chunk_size](winhttp_request_context* c, size_t bytes_produced, bool outer) -> bool {
if (!c->m_compression_state.m_chunk_bytes)
{
if (c->m_compression_state.m_chunked)
{
size_t offset;
bool done;
// Process the next portion of this piece of the transfer-encoded message
done = c->m_compression_state.m_chunk->process_buffer(
c->m_compression_state.m_buffer.data() + c->m_compression_state.m_bytes_processed,
c->m_compression_state.m_bytes_read - c->m_compression_state.m_bytes_processed,
offset,
c->m_compression_state.m_chunk_bytes);
// Skip chunk-related metadata; it isn't relevant to decompression
_ASSERTE(c->m_compression_state.m_bytes_processed + offset <=
c->m_compression_state.m_bytes_read);
c->m_compression_state.m_bytes_processed += offset;
if (!c->m_compression_state.m_chunk_bytes)
{
if (done)
{
// We've processed/validated all bytes in this transfer-encoded message.
// Note that we currently ignore "extra" trailing bytes, i.e.
// c->m_compression_state.m_bytes_processed <
// c->m_compression_state.m_bytes_read
if (c->m_compression_state.m_done)
{
c->complete_request(c->m_downloaded);
return false;
}
else if (!outer && bytes_produced != chunk_size)
{
throw http_exception("Transfer ended before decompression completed");
}
}
else if (!outer && bytes_produced != chunk_size)
{
// There should be more data to receive; look for it
c->m_compression_state.m_bytes_processed = 0;
read_next_response_chunk(
c, static_cast<DWORD>(c->m_compression_state.m_bytes_read));
return false;
}
}
}
else
{
_ASSERTE(!c->m_compression_state.m_bytes_processed ||
c->m_compression_state.m_bytes_processed ==
c->m_compression_state.m_bytes_read);
if (c->m_compression_state.m_done)
{
// Decompression is done; complete the request
c->complete_request(c->m_downloaded);
return false;
}
else if (c->m_compression_state.m_bytes_processed !=
c->m_compression_state.m_bytes_read)
{
// We still have more data to process in the current buffer
c->m_compression_state.m_chunk_bytes =
c->m_compression_state.m_bytes_read - c->m_compression_state.m_bytes_processed;
}
else if (!outer && bytes_produced != chunk_size)
{
// There should be more data to receive; look for it
c->m_compression_state.m_bytes_processed = 0;
read_next_response_chunk(c,
static_cast<DWORD>(c->m_compression_state.m_bytes_read));
return false;
}
// Otherwise, we've processed all bytes in the input buffer, but there's a good chance
// that there are still decompressed bytes to emit; we'll do so before reading the next
// chunk
}
}
// We're still processing the current message chunk
return true;
};
pplx::details::_do_while([p_request_context, chunk_size, process_buffer]() -> pplx::task<bool> {
uint8_t* buffer;
try
{
if (!process_buffer(p_request_context.get(), 0, true))
{
// The chunked request has been completely processed (or contains no data in the first
// place)
return pplx::task_from_result<bool>(false);
}
}
catch (...)
{
// The outer do-while requires an explicit task return to activate the then() clause
return pplx::task_from_exception<bool>(std::current_exception());
}
// If it's possible to know how much post-compression data we're expecting (for instance if we
// can discern how much total data the ostream can support, we could allocate (or at least
// attempt to acquire) based on that
p_request_context->m_compression_state.m_acquired =
p_request_context->_get_writebuffer().alloc(chunk_size);
if (p_request_context->m_compression_state.m_acquired)
{
buffer = p_request_context->m_compression_state.m_acquired;
}
else
{
// The streambuf couldn't accommodate our request; we'll use m_body_data's
// internal vector as temporary storage, then putn() to the caller's stream
p_request_context->allocate_reply_space(nullptr, chunk_size);
buffer = p_request_context->m_body_data.get();
}
uint8_t* in = p_request_context->m_compression_state.m_buffer.data() +
p_request_context->m_compression_state.m_bytes_processed;
size_t inbytes = p_request_context->m_compression_state.m_chunk_bytes;
if (inbytes)
{
p_request_context->m_compression_state.m_started = true;
}
return p_request_context->m_decompressor
->decompress(
in, inbytes, buffer, chunk_size, web::http::compression::operation_hint::has_more)
.then([p_request_context, buffer, chunk_size, process_buffer](
pplx::task<web::http::compression::operation_result> op) {
auto r = op.get();
auto keep_going = [&r, process_buffer](winhttp_request_context* c) -> pplx::task<bool> {
_ASSERTE(r.input_bytes_processed <= c->m_compression_state.m_chunk_bytes);
c->m_compression_state.m_chunk_bytes -= r.input_bytes_processed;
c->m_compression_state.m_bytes_processed += r.input_bytes_processed;
c->m_compression_state.m_done = r.done;
try
{
// See if we still have more work to do for this section and/or for the response
// in general
return pplx::task_from_result<bool>(
process_buffer(c, r.output_bytes_produced, false));
}
catch (...)
{
return pplx::task_from_exception<bool>(std::current_exception());
}
};
_ASSERTE(p_request_context->m_compression_state.m_bytes_processed +
r.input_bytes_processed <=
p_request_context->m_compression_state.m_bytes_read);
if (p_request_context->m_compression_state.m_acquired != nullptr)
{
// We decompressed directly into the output stream
p_request_context->m_compression_state.m_acquired = nullptr;
p_request_context->_get_writebuffer().commit(r.output_bytes_produced);
return keep_going(p_request_context.get());
}
// We decompressed into our own buffer; let the stream copy the data
return p_request_context->_get_writebuffer()
.putn_nocopy(buffer, r.output_bytes_produced)
.then([p_request_context, r, keep_going](pplx::task<size_t> op) {
if (op.get() != r.output_bytes_produced)
{
return pplx::task_from_exception<bool>(
std::runtime_error("Response stream unexpectedly failed to write the "
"requested number of bytes"));
}
return keep_going(p_request_context.get());
});
});
}).then([p_request_context](pplx::task<bool> op) {
try
{
op.get();
}
catch (...)
{
// We're only here to pick up any exception that may have been thrown, and to clean up
// if needed
if (p_request_context->m_compression_state.m_acquired)
{
p_request_context->_get_writebuffer().commit(0);
p_request_context->m_compression_state.m_acquired = nullptr;
}
p_request_context->report_exception(std::current_exception());
}
});
}
else
{
// If the data was allocated directly from the buffer then commit, otherwise we still
// need to write to the response stream buffer.
if (p_request_context->is_externally_allocated())
{
writebuf.commit(bytesRead);
read_next_response_chunk(p_request_context.get(), bytesRead);
}
else
{
writebuf.putn_nocopy(p_request_context->m_body_data.get(), bytesRead)
.then([hRequestHandle, p_request_context, bytesRead](pplx::task<size_t> op) {
size_t written = 0;
try
{
written = op.get();
}
catch (...)
{
p_request_context->report_exception(std::current_exception());
return;
}
// If we couldn't write everything, it's time to exit.
if (written != bytesRead)
{
p_request_context->report_exception(std::runtime_error(
"response stream unexpectedly failed to write the requested number of bytes"));
return;
}
read_next_response_chunk(p_request_context.get(), bytesRead);
});
}
}
return;
}
}
}