static void CALLBACK completion_callback()

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;
            }
        }
    }