From b5e294e377a602931482d9d0bf58c673a2d81370 Mon Sep 17 00:00:00 2001 From: Chris O'Gorman Date: Mon, 19 Feb 2018 18:57:55 +0000 Subject: [PATCH 1/8] Add support for cert-pinning on Windows and Mac. --- CONTRIBUTORS.txt | 1 + Release/include/cpprest/certificate_info.h | 48 ++ Release/include/cpprest/http_client.h | 24 + Release/include/cpprest/oauth2.h | 33 +- Release/include/cpprest/ws_client.h | 68 ++- Release/src/http/client/http_client_asio.cpp | 473 ++++++++------- Release/src/http/client/http_client_impl.h | 3 +- .../src/http/client/http_client_winhttp.cpp | 568 +++++++++++------- .../src/http/client/x509_cert_utilities.cpp | 234 +++++++- Release/src/http/common/x509_cert_utilities.h | 19 +- Release/src/http/oauth/oauth2.cpp | 1 + .../src/websockets/client/ws_client_wspp.cpp | 449 ++++++++------ 12 files changed, 1249 insertions(+), 672 deletions(-) create mode 100644 Release/include/cpprest/certificate_info.h diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 36348fa544..118e368b92 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -36,6 +36,7 @@ Gery Vessere (gery@vessere.com) Cisco Systems Gergely Lukacsy (glukacsy) Chris Deering (deeringc) +Chris O'Gorman (chogorma) Ocedo GmbH Henning Pfeiffer (megaposer) diff --git a/Release/include/cpprest/certificate_info.h b/Release/include/cpprest/certificate_info.h new file mode 100644 index 0000000000..282ff7e969 --- /dev/null +++ b/Release/include/cpprest/certificate_info.h @@ -0,0 +1,48 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Certificate info +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#include +#include + +namespace web { namespace http { namespace client { + + using CertificateChain = std::vector>; + + struct certificate_info + { + CertificateChain certificate_chain; + std::string host_name; + long certificate_error{ 0 }; + bool verified{ false }; + + certificate_info(const std::string host) : host_name(host) {}; + certificate_info(const std::string host, CertificateChain chain, long error = 0) : host_name(host), certificate_chain(chain), certificate_error(error) {}; + }; + + using CertificateChainFunction = std::function certificate_Info)>; + +}}} \ No newline at end of file diff --git a/Release/include/cpprest/http_client.h b/Release/include/cpprest/http_client.h index fb7c6067ab..3f899db9c1 100644 --- a/Release/include/cpprest/http_client.h +++ b/Release/include/cpprest/http_client.h @@ -45,6 +45,7 @@ typedef void* native_handle; #endif // __cplusplus_winrt #include "cpprest/asyncrt_utils.h" +#include "cpprest/certificate_info.h" #include "cpprest/details/basic_types.h" #include "cpprest/details/web_utilities.h" #include "cpprest/http_msg.h" @@ -101,6 +102,7 @@ class http_client_config #if !defined(__cplusplus_winrt) , m_validate_certificates(true) #endif + , m_certificate_chain_callback([](const std::shared_ptr&) -> bool { return true; }) #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) , m_tlsext_sni_enabled(true) #endif @@ -362,6 +364,27 @@ class http_client_config if (m_set_user_nativehandle_options) m_set_user_nativehandle_options(handle); } + + /// + /// Set the certificate chain callback. If set, HTTP client will call this callback in a blocking manner during HTTP + /// connection. + /// + void set_user_certificate_chain_callback(const CertificateChainFunction& callback) + { + m_certificate_chain_callback = callback; + } + + /// + /// Invokes the certificate chain callback. + /// + /// Pointer to the certificate_info struct that has the certificate + /// information. True if the consumer code allows the connection, false otherwise. False will + /// terminate the HTTP connection. + bool invoke_certificate_chain_callback(const std::shared_ptr& certificate_Info) const + { + return m_certificate_chain_callback(certificate_Info); + } + #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) /// /// Sets a callback to enable custom setting of the ssl context, at construction time. @@ -416,6 +439,7 @@ class http_client_config bool m_validate_certificates; #endif + CertificateChainFunction m_certificate_chain_callback; std::function m_set_user_nativehandle_options; std::function m_set_user_nativesessionhandle_options; diff --git a/Release/include/cpprest/oauth2.h b/Release/include/cpprest/oauth2.h index b1ec324996..8ef685183c 100644 --- a/Release/include/cpprest/oauth2.h +++ b/Release/include/cpprest/oauth2.h @@ -15,6 +15,7 @@ #ifndef CASA_OAUTH2_H #define CASA_OAUTH2_H +#include "cpprest/certificate_info.h" #include "cpprest/details/web_utilities.h" #include "cpprest/http_msg.h" @@ -58,8 +59,8 @@ namespace experimental class oauth2_exception : public std::exception { public: - oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) {} - ~oauth2_exception() CPPREST_NOEXCEPT {} + oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) { } + ~oauth2_exception() CPPREST_NOEXCEPT { } const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); } private: @@ -219,6 +220,8 @@ class oauth2_config , m_bearer_auth(true) , m_http_basic_auth(true) , m_access_token_key(details::oauth2_strings::access_token) + , m_certificate_chain_callback([](const std::shared_ptr&) -> bool + { return true; }) { } @@ -294,8 +297,7 @@ class oauth2_config pplx::task token_from_client_credentials() { uri_builder ub; - ub.append_query( - details::oauth2_strings::grant_type, details::oauth2_strings::client_credentials, false); + ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::client_credentials, false); return _request_token(ub); } @@ -480,11 +482,28 @@ class oauth2_config /// void set_user_agent(utility::string_t user_agent) { m_user_agent = std::move(user_agent); } + /// + /// Set the certificate chain callback to be used by the http client. + /// + void set_user_certificate_chain_callback(const web::http::client::CertificateChainFunction& callback) + { + m_certificate_chain_callback = callback; + } + + /// + /// Get the cert chain callback. + /// + /// A reference to cert chain callback user by the client. + const web::http::client::CertificateChainFunction& user_certificate_chain_callback() + { + return m_certificate_chain_callback; + } + private: friend class web::http::client::http_client_config; friend class web::http::oauth2::details::oauth2_handler; - oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) {} + oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) { } _ASYNCRTIMP pplx::task _request_token(uri_builder& request_body); @@ -523,6 +542,8 @@ class oauth2_config oauth2_token m_token; utility::nonce_generator m_state_generator; + + web::http::client::CertificateChainFunction m_certificate_chain_callback; }; } // namespace experimental @@ -532,7 +553,7 @@ namespace details class oauth2_handler : public http_pipeline_stage { public: - oauth2_handler(std::shared_ptr cfg) : m_config(std::move(cfg)) {} + oauth2_handler(std::shared_ptr cfg) : m_config(std::move(cfg)) { } virtual pplx::task propagate(http_request request) override { diff --git a/Release/include/cpprest/ws_client.h b/Release/include/cpprest/ws_client.h index af17bd6060..09f53f6dca 100644 --- a/Release/include/cpprest/ws_client.h +++ b/Release/include/cpprest/ws_client.h @@ -18,8 +18,10 @@ #if !defined(CPPREST_EXCLUDE_WEBSOCKETS) #include "cpprest/asyncrt_utils.h" +#include "cpprest/certificate_info.h" #include "cpprest/details/web_utilities.h" #include "cpprest/http_headers.h" +#include "cpprest/json.h" #include "cpprest/uri.h" #include "cpprest/ws_msg.h" #include "pplx/pplxtasks.h" @@ -79,7 +81,13 @@ class websocket_client_config /// /// Creates a websocket client configuration with default settings. /// - websocket_client_config() : m_sni_enabled(true), m_validate_certificates(true) {} + websocket_client_config() + : m_certificate_chain_callback([](const std::shared_ptr&) -> bool + { return true; }) + , m_sni_enabled(true) + , m_validate_certificates(true) + { + } /// /// Get the web proxy object @@ -199,6 +207,26 @@ class websocket_client_config } #endif + /// Set the certificate chain callback. If set, HTTP client will call this callback in a blocking manner during HTTP + /// connection. + /// + void set_user_certificate_chain_callback(const http::client::CertificateChainFunction& callback) + { + m_certificate_chain_callback = callback; + } + + /// + /// Invokes the certificate chain callback. + /// + /// Pointer to the certificate_info struct that has the certificate + /// information. True if the consumer code allows the connection, false otherwise. False will + /// terminate the HTTP connection. + bool invoke_certificate_chain_callback( + const std::shared_ptr& certificate_Info) const + { + return m_certificate_chain_callback(certificate_Info); + } + private: web::web_proxy m_proxy; web::credentials m_credentials; @@ -206,8 +234,10 @@ class websocket_client_config bool m_sni_enabled; utf8string m_sni_hostname; bool m_validate_certificates; -#if !defined(_WIN32) || !defined(__cplusplus_winrt) - std::function m_ssl_context_callback; + http::client::CertificateChainFunction m_certificate_chain_callback; + +#if !defined(_WIN32) || + !defined(__cplusplus_winrt) std::function m_ssl_context_callback; #endif }; @@ -221,14 +251,14 @@ class websocket_exception : public std::exception /// Creates an websocket_exception with just a string message and no error code. /// /// Error message string. - websocket_exception(const utility::string_t& whatArg) : m_msg(utility::conversions::to_utf8string(whatArg)) {} + websocket_exception(const utility::string_t& whatArg) : m_msg(utility::conversions::to_utf8string(whatArg)) { } #ifdef _WIN32 /// /// Creates an websocket_exception with just a string message and no error code. /// /// Error message string. - websocket_exception(std::string whatArg) : m_msg(std::move(whatArg)) {} + websocket_exception(std::string whatArg) : m_msg(std::move(whatArg)) { } #endif /// @@ -318,9 +348,9 @@ namespace details class websocket_client_callback_impl { public: - websocket_client_callback_impl(websocket_client_config config) : m_config(std::move(config)) {} + websocket_client_callback_impl(websocket_client_config config) : m_config(std::move(config)) { } - virtual ~websocket_client_callback_impl() CPPREST_NOEXCEPT {} + virtual ~websocket_client_callback_impl() CPPREST_NOEXCEPT { } virtual pplx::task connect() = 0; @@ -412,7 +442,7 @@ class websocket_client /// /// Creates a new websocket_client. /// - websocket_client() : m_client(std::make_shared(websocket_client_config())) {} + websocket_client() : m_client(std::make_shared(websocket_client_config())) { } /// /// Creates a new websocket_client. @@ -436,17 +466,19 @@ class websocket_client m_client->callback_client()->verify_uri(uri); m_client->callback_client()->set_uri(uri); auto client = m_client; - return m_client->callback_client()->connect().then([client](pplx::task result) { - try - { - result.get(); - } - catch (const websocket_exception& ex) + return m_client->callback_client()->connect().then( + [client](pplx::task result) { - client->close_pending_tasks_with_error(ex); - throw; - } - }); + try + { + result.get(); + } + catch (const websocket_exception& ex) + { + client->close_pending_tasks_with_error(ex); + throw; + } + }); } /// diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index 07bb4885bf..db67a3c558 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -1,3 +1,4 @@ + /*** * Copyright (C) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. @@ -41,6 +42,7 @@ #include "../common/x509_cert_utilities.h" #include "cpprest/base_uri.h" +#include "cpprest/certificate_info.h" #include "cpprest/details/http_helpers.h" #include "http_client_impl.h" #include "pplx/threadpool.h" @@ -430,38 +432,40 @@ class asio_connection_pool final : public std::enable_shared_from_this weak_pool = pool; self.m_pool_epoch_timer.expires_from_now(boost::posix_time::seconds(30)); - self.m_pool_epoch_timer.async_wait([weak_pool](const boost::system::error_code& ec) { - if (ec) + self.m_pool_epoch_timer.async_wait( + [weak_pool](const boost::system::error_code& ec) { - return; - } + if (ec) + { + return; + } - auto pool = weak_pool.lock(); - if (!pool) - { - return; - } + auto pool = weak_pool.lock(); + if (!pool) + { + return; + } - auto& self = *pool; - std::lock_guard lock(self.m_lock); - bool restartTimer = false; - for (auto& entry : self.m_connections) - { - if (entry.second.free_stale_connections()) + auto& self = *pool; + std::lock_guard lock(self.m_lock); + bool restartTimer = false; + for (auto& entry : self.m_connections) { - restartTimer = true; + if (entry.second.free_stale_connections()) + { + restartTimer = true; + } } - } - if (restartTimer) - { - start_epoch_interval(pool); - } - else - { - self.m_is_timer_running = false; - } - }); + if (restartTimer) + { + start_epoch_interval(pool); + } + else + { + self.m_is_timer_running = false; + } + }); } std::mutex m_lock; @@ -753,8 +757,9 @@ class asio_context final : public request_context, public std::enable_shared_fro proxy_host = utility::conversions::to_utf8string(proxy_uri.host()); } - auto start_http_request_flow = [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( - std::shared_ptr ctx) { + auto start_http_request_flow = + [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS](std::shared_ptr ctx) + { if (ctx->m_request._cancellation_token().is_canceled()) { ctx->request_context::report_error(make_error_code(std::errc::operation_canceled).value(), @@ -899,13 +904,15 @@ class asio_context final : public request_context, public std::enable_shared_fro // weak_ptr prevents lambda from taking shared ownership of the context. // Otherwise context replacement in the handle_status_line() would leak the objects. std::weak_ptr ctx_weak(ctx); - ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback([ctx_weak]() { - if (auto ctx_lock = ctx_weak.lock()) + ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback( + [ctx_weak]() { - // Shut down transmissions, close the socket and prevent connection from being pooled. - ctx_lock->m_connection->close(); - } - }); + if (auto ctx_lock = ctx_weak.lock()) + { + // Shut down transmissions, close the socket and prevent connection from being pooled. + ctx_lock->m_connection->close(); + } + }); } }; @@ -1080,7 +1087,8 @@ class asio_context final : public request_context, public std::enable_shared_fro // Use a weak_ptr since the verify_callback is stored until the connection is // destroyed. This avoids creating a circular reference since we pool connection // objects. - [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) { + [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) + { auto this_request = weakCtx.lock(); if (this_request) { @@ -1123,6 +1131,9 @@ class asio_context final : public request_context, public std::enable_shared_fro // If OpenSSL fails we will doing verification at the end using the whole certificate // chain so wait until the 'leaf' cert. For now return true so OpenSSL continues down // the certificate chain. + const auto& host = utility::conversions::to_utf8string(m_http_client->base_uri().host()); + using namespace web::http::client::details; + if (!preverified) { m_openssl_failed = true; @@ -1130,12 +1141,30 @@ class asio_context final : public request_context, public std::enable_shared_fro if (m_openssl_failed) { - return verify_cert_chain_platform_specific(verifyCtx, m_connection->cn_hostname()); + if (!is_end_certificate_in_chain(verifyCtx)) + { + // Continue until we get the end certificate. + return true; + } + + auto chainFunc = [this](const std::shared_ptr& cert_info) + { return m_http_client->client_config().invoke_certificate_chain_callback(cert_info); }; + + return http::client::details::verify_cert_chain_platform_specific( + verifyCtx, utility::conversions::to_utf8string(host), chainFunc); + } +#endif + + boost::asio::ssl::rfc2818_verification rfc2818(host); + if (!rfc2818(preverified, verifyCtx)) + { + return false; } -#endif // CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE - boost::asio::ssl::rfc2818_verification rfc2818(m_connection->cn_hostname()); - return rfc2818(preverified, verifyCtx); + auto info = std::make_shared(host, get_X509_cert_chain_encoded_data(verifyCtx)); + info->verified = true; + + return m_http_client->client_config().invoke_certificate_chain_callback(info); } void handle_write_headers(const boost::system::error_code& ec) @@ -1186,38 +1215,42 @@ class asio_context final : public request_context, public std::enable_shared_fro m_body_buf.prepare(chunkSize + http::details::chunked_encoding::additional_encoding_space)); const auto this_request = shared_from_this(); readbuf.getn(buf + http::details::chunked_encoding::data_offset, chunkSize) - .then([this_request, buf, chunkSize AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - size_t readSize = 0; - try - { - readSize = op.get(); - } - catch (...) + .then( + [this_request, buf, chunkSize AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - this_request->report_exception(std::current_exception()); - return; - } + size_t readSize = 0; + try + { + readSize = op.get(); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } - const size_t offset = http::details::chunked_encoding::add_chunked_delimiters( - buf, chunkSize + http::details::chunked_encoding::additional_encoding_space, readSize); - this_request->m_body_buf.commit(readSize + http::details::chunked_encoding::additional_encoding_space); - this_request->m_body_buf.consume(offset); - this_request->m_uploaded += static_cast(readSize); + const size_t offset = http::details::chunked_encoding::add_chunked_delimiters( + buf, chunkSize + http::details::chunked_encoding::additional_encoding_space, readSize); + this_request->m_body_buf.commit(readSize + + http::details::chunked_encoding::additional_encoding_space); + this_request->m_body_buf.consume(offset); + this_request->m_uploaded += static_cast(readSize); - if (readSize != 0) - { - this_request->m_connection->async_write(this_request->m_body_buf, - boost::bind(&asio_context::handle_write_chunked_body, - this_request, - boost::asio::placeholders::error)); - } - else - { - this_request->m_connection->async_write( - this_request->m_body_buf, - boost::bind(&asio_context::handle_write_body, this_request, boost::asio::placeholders::error)); - } - }); + if (readSize != 0) + { + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_chunked_body, + this_request, + boost::asio::placeholders::error)); + } + else + { + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_body, + this_request, + boost::asio::placeholders::error)); + } + }); } void handle_write_large_body(const boost::system::error_code& ec) @@ -1244,33 +1277,36 @@ class asio_context final : public request_context, public std::enable_shared_fro } const auto this_request = shared_from_this(); - const auto readSize = static_cast((std::min)( - static_cast(m_http_client->client_config().chunksize()), m_content_length - m_uploaded)); + const auto readSize = + static_cast((std::min)(static_cast(m_http_client->client_config().chunksize()), + m_content_length - m_uploaded)); auto readbuf = _get_readbuffer(); readbuf.getn(boost::asio::buffer_cast(m_body_buf.prepare(readSize)), readSize) - .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - try + .then( + [this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - const auto actualReadSize = op.get(); - if (actualReadSize == 0) + try + { + const auto actualReadSize = op.get(); + if (actualReadSize == 0) + { + this_request->report_exception(http_exception( + "Unexpected end of request body stream encountered before Content-Length satisfied.")); + return; + } + this_request->m_uploaded += static_cast(actualReadSize); + this_request->m_body_buf.commit(actualReadSize); + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_large_body, + this_request, + boost::asio::placeholders::error)); + } + catch (...) { - this_request->report_exception(http_exception( - "Unexpected end of request body stream encountered before Content-Length satisfied.")); + this_request->report_exception(std::current_exception()); return; } - this_request->m_uploaded += static_cast(actualReadSize); - this_request->m_body_buf.commit(actualReadSize); - this_request->m_connection->async_write(this_request->m_body_buf, - boost::bind(&asio_context::handle_write_large_body, - this_request, - boost::asio::placeholders::error)); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } - }); + }); } void handle_write_body(const boost::system::error_code& ec) @@ -1664,47 +1700,52 @@ class asio_context final : public request_context, public std::enable_shared_fro auto shared_decompressed = std::make_shared>(std::move(decompressed)); writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size()) - .then([this_request, to_read, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( - pplx::task op) { + .then( + [this_request, to_read, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( + pplx::task op) + { + try + { + op.get(); + this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf + this_request->m_connection->async_read_until( + this_request->m_body_buf, + CRLF, + boost::bind(&asio_context::handle_chunk_header, + this_request, + boost::asio::placeholders::error)); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } + }); + } + } + else + { + writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), to_read) + .then( + [this_request, to_read AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) + { try { - op.get(); - this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf - this_request->m_connection->async_read_until( - this_request->m_body_buf, - CRLF, - boost::bind(&asio_context::handle_chunk_header, - this_request, - boost::asio::placeholders::error)); + op.wait(); } catch (...) { this_request->report_exception(std::current_exception()); return; } + this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf + this_request->m_connection->async_read_until( + this_request->m_body_buf, + CRLF, + boost::bind(&asio_context::handle_chunk_header, + this_request, + boost::asio::placeholders::error)); }); - } - } - else - { - writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), to_read) - .then([this_request, to_read AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - try - { - op.wait(); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } - this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf - this_request->m_connection->async_read_until(this_request->m_body_buf, - CRLF, - boost::bind(&asio_context::handle_chunk_header, - this_request, - boost::asio::placeholders::error)); - }); } } } @@ -1775,9 +1816,10 @@ class asio_context final : public request_context, public std::enable_shared_fro this_request->m_downloaded += static_cast(read_size); this_request->async_read_until_buffersize( - static_cast((std::min)( - static_cast(this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), + static_cast( + (std::min)(static_cast( + this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), boost::bind( &asio_context::handle_read_content, this_request, boost::asio::placeholders::error)); } @@ -1794,19 +1836,51 @@ class asio_context final : public request_context, public std::enable_shared_fro auto shared_decompressed = std::make_shared>(std::move(decompressed)); writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size()) - .then([this_request, read_size, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( - pplx::task op) { + .then( + [this_request, read_size, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( + pplx::task op) + { + size_t writtenSize = 0; + (void)writtenSize; + try + { + writtenSize = op.get(); + this_request->m_downloaded += static_cast(read_size); + this_request->m_body_buf.consume(read_size); + this_request->async_read_until_buffersize( + static_cast( + (std::min)(static_cast( + this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), + boost::bind(&asio_context::handle_read_content, + this_request, + boost::asio::placeholders::error)); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } + }); + } + } + else + { + writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), read_size) + .then( + [this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) + { size_t writtenSize = 0; - (void)writtenSize; try { writtenSize = op.get(); - this_request->m_downloaded += static_cast(read_size); - this_request->m_body_buf.consume(read_size); + this_request->m_downloaded += static_cast(writtenSize); + this_request->m_body_buf.consume(writtenSize); this_request->async_read_until_buffersize( - static_cast((std::min)( - static_cast(this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), + static_cast( + (std::min)(static_cast( + this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), boost::bind(&asio_context::handle_read_content, this_request, boost::asio::placeholders::error)); @@ -1817,32 +1891,6 @@ class asio_context final : public request_context, public std::enable_shared_fro return; } }); - } - } - else - { - writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), read_size) - .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { - size_t writtenSize = 0; - try - { - writtenSize = op.get(); - this_request->m_downloaded += static_cast(writtenSize); - this_request->m_body_buf.consume(writtenSize); - this_request->async_read_until_buffersize( - static_cast((std::min)( - static_cast(this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), - boost::bind(&asio_context::handle_read_content, - this_request, - boost::asio::placeholders::error)); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } - }); } } else @@ -1872,9 +1920,8 @@ class asio_context final : public request_context, public std::enable_shared_fro m_timer.expires_from_now(m_duration); auto ctx = m_ctx; - m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) { - handle_timeout(ec, ctx); - }); + m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) + { handle_timeout(ec, ctx); }); } void reset() @@ -1886,9 +1933,8 @@ class asio_context final : public request_context, public std::enable_shared_fro // The existing handler was canceled so schedule a new one. assert(m_state == started); auto ctx = m_ctx; - m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) { - handle_timeout(ec, ctx); - }); + m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) + { handle_timeout(ec, ctx); }); } } @@ -1983,21 +2029,20 @@ static bool is_retrieval_redirection(status_code code) switch (code) { - case status_codes::MovedPermanently: - // "For historical reasons, a user agent MAY change the request method - // from POST to GET for the subsequent request." - return true; - case status_codes::Found: - // "For historical reasons, a user agent MAY change the request method - // from POST to GET for the subsequent request." - return true; - case status_codes::SeeOther: - // "A user agent can perform a [GET or HEAD] request. It is primarily - // used to allow the output of a POST action to redirect the user agent - // to a selected resource." - return true; - default: - return false; + case status_codes::MovedPermanently: + // "For historical reasons, a user agent MAY change the request method + // from POST to GET for the subsequent request." + return true; + case status_codes::Found: + // "For historical reasons, a user agent MAY change the request method + // from POST to GET for the subsequent request." + return true; + case status_codes::SeeOther: + // "A user agent can perform a [GET or HEAD] request. It is primarily + // used to allow the output of a POST action to redirect the user agent + // to a selected resource." + return true; + default: return false; } } @@ -2008,16 +2053,15 @@ static bool is_unchanged_redirection(status_code code) switch (code) { - case status_codes::TemporaryRedirect: - // "The user agent MUST NOT change the request method if it performs an - // automatic redirection to that URI." - return true; - case status_codes::PermanentRedirect: - // This status code "does not allow changing the request method from POST - // to GET." - return true; - default: - return false; + case status_codes::TemporaryRedirect: + // "The user agent MUST NOT change the request method if it performs an + // automatic redirection to that URI." + return true; + case status_codes::PermanentRedirect: + // This status code "does not allow changing the request method from POST + // to GET." + return true; + default: return false; } } @@ -2028,19 +2072,13 @@ static bool is_recognized_redirection(status_code code) return is_retrieval_redirection(code) || is_unchanged_redirection(code); } -static bool is_retrieval_request(method method) -{ - return methods::GET == method || methods::HEAD == method; -} +static bool is_retrieval_request(method method) { return methods::GET == method || methods::HEAD == method; } -static const std::vector request_body_header_names = -{ - header_names::content_encoding, - header_names::content_language, - header_names::content_length, - header_names::content_location, - header_names::content_type -}; +static const std::vector request_body_header_names = {header_names::content_encoding, + header_names::content_language, + header_names::content_length, + header_names::content_location, + header_names::content_type}; // A request continuation that follows redirects according to the specified configuration. // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request @@ -2059,9 +2097,7 @@ struct http_redirect_follower }; http_redirect_follower::http_redirect_follower(http_client_config config, const http_request& request) - : config(std::move(config)) - , followed_urls(1, request.absolute_uri()) - , redirect(request.method()) + : config(std::move(config)), followed_urls(1, request.absolute_uri()), redirect(request.method()) { // Stash the original request URL, etc. to be prepared for an automatic redirect @@ -2080,29 +2116,25 @@ http_redirect_follower::http_redirect_follower(http_client_config config, const uri http_redirect_follower::url_to_follow(const http_response& response) const { // Return immediately if the response is not a supported redirection - if (!is_recognized_redirection(response.status_code())) - return{}; + if (!is_recognized_redirection(response.status_code())) return {}; // Although not required by RFC 7231, config may limit the number of automatic redirects // (followed_urls includes the initial request URL, hence '<' here) - if (config.max_redirects() < followed_urls.size()) - return{}; + if (config.max_redirects() < followed_urls.size()) return {}; // Can't very well automatically redirect if the server hasn't provided a Location const auto location = response.headers().find(header_names::location); - if (response.headers().end() == location) - return{}; + if (response.headers().end() == location) return {}; uri to_follow(followed_urls.back().resolve_uri(location->second)); // Config may prohibit automatic redirects from HTTPS to HTTP - if (!config.https_to_http_redirects() && followed_urls.back().scheme() == _XPLATSTR("https") - && to_follow.scheme() != _XPLATSTR("https")) - return{}; + if (!config.https_to_http_redirects() && followed_urls.back().scheme() == _XPLATSTR("https") && + to_follow.scheme() != _XPLATSTR("https")) + return {}; // "A client SHOULD detect and intervene in cyclical redirections." - if (followed_urls.end() != std::find(followed_urls.begin(), followed_urls.end(), to_follow)) - return{}; + if (followed_urls.end() != std::find(followed_urls.begin(), followed_urls.end(), to_follow)) return {}; return to_follow; } @@ -2111,16 +2143,14 @@ pplx::task http_redirect_follower::operator()(http_response respo { // Return immediately if the response doesn't indicate a valid automatic redirect uri to_follow = url_to_follow(response); - if (to_follow.is_empty()) - return pplx::task_from_result(response); + if (to_follow.is_empty()) return pplx::task_from_result(response); // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request // using the same method since the request body may have been consumed. if (!is_retrieval_request(redirect.method()) && !is_retrieval_redirection(response.status_code())) return pplx::task_from_result(response); - if (!is_retrieval_request(redirect.method())) - redirect.set_method(methods::GET); + if (!is_retrieval_request(redirect.method())) redirect.set_method(methods::GET); // If the reply to this request is also a redirect, we want visibility of that auto config_no_redirects = config; @@ -2152,9 +2182,8 @@ pplx::task asio_client::propagate(http_request request) // Asynchronously send the response with the HTTP client implementation. this->async_send_request(context); - return client_config().max_redirects() > 0 - ? result_task.then(http_redirect_follower(client_config(), request)) - : result_task; + return client_config().max_redirects() > 0 ? result_task.then(http_redirect_follower(client_config(), request)) + : result_task; } } // namespace details } // namespace client diff --git a/Release/src/http/client/http_client_impl.h b/Release/src/http/client/http_client_impl.h index d9e7d4829e..853fb305a9 100644 --- a/Release/src/http/client/http_client_impl.h +++ b/Release/src/http/client/http_client_impl.h @@ -53,7 +53,7 @@ class request_context { public: // Destructor to clean up any held resources. - virtual ~request_context() {} + virtual ~request_context() { } virtual void report_exception(std::exception_ptr exceptionPtr); @@ -104,6 +104,7 @@ class request_context pplx::cancellation_token_registration m_cancellationRegistration; std::unique_ptr m_decompressor; + bool m_certificate_chain_verification_failed {false}; protected: request_context(const std::shared_ptr<_http_client_communicator>& client, const http_request& request); diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index d6cdb5384a..766fc8c797 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -14,8 +14,8 @@ ****/ #include "stdafx.h" -#include "../common/x509_cert_utilities.h" #include "../common/internal_http_helpers.h" +#include "../common/x509_cert_utilities.h" #include "cpprest/http_headers.h" #include "http_client_impl.h" #ifdef WIN32 @@ -25,6 +25,8 @@ #include "winhttppal.h" #endif #include +#include // for certificate pinning logic +#include // for certificate pinning logic #if _WIN32_WINNT && (_WIN32_WINNT >= _WIN32_WINNT_VISTA) #include @@ -172,7 +174,7 @@ class memory_holder size_t m_size; public: - memory_holder() : m_externalData(nullptr), m_size(0) {} + memory_holder() : m_externalData(nullptr), m_size(0) { } void allocate_space(size_t length) { @@ -922,7 +924,7 @@ class winhttp_client final : public _http_client_communicator DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2); if (!WinHttpSetOption( - m_hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols))) + m_hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols))) { return GetLastError(); } @@ -1159,8 +1161,8 @@ class winhttp_client final : public _http_client_communicator // And another 1 to enable the response (headers) of the rejected automatic redirect to be returned // rather than reporting an error "WinHttpReceiveResponse: 12156: The HTTP redirect request failed". DWORD maxRedirects = client_config().max_redirects() < MAXDWORD - 2 - ? static_cast(client_config().max_redirects() + 2) - : MAXDWORD; + ? static_cast(client_config().max_redirects() + 2) + : MAXDWORD; // Therefore, effective max redirects winhttp_context->m_remaining_redirects = maxRedirects - 2; @@ -1176,8 +1178,8 @@ class winhttp_client final : public _http_client_communicator // (Dis)allow HTTPS to HTTP redirects. DWORD redirectPolicy = client_config().https_to_http_redirects() - ? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS - : WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; + ? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS + : WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; if (!WinHttpSetOption(winhttp_context->m_request_handle, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, @@ -1250,8 +1252,9 @@ class winhttp_client final : public _http_client_communicator if (msg._cancellation_token() != pplx::cancellation_token::none()) { // cancellation callback is unregistered when request is completed. - winhttp_context->m_cancellationRegistration = - msg._cancellation_token().register_callback([weak_winhttp_context]() { + winhttp_context->m_cancellationRegistration = msg._cancellation_token().register_callback( + [weak_winhttp_context]() + { // Call the WinHttpSendRequest API after WinHttpCloseHandle will give invalid handle error and we // throw this exception. Call the cleanup to make the m_request_handle as nullptr, otherwise, // Application Verifier will give AV exception on m_request_handle. @@ -1412,7 +1415,8 @@ class winhttp_client final : public _http_client_communicator p_request_context->allocate_request_space( nullptr, chunk_size + http::details::chunked_encoding::additional_encoding_space); - auto after_read = [p_request_context, chunk_size, &compressor](pplx::task op) { + auto after_read = [p_request_context, chunk_size, &compressor](pplx::task op) + { size_t bytes_read; try { @@ -1488,8 +1492,8 @@ class winhttp_client final : public _http_client_communicator if (compressor) { - auto do_compress = - [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task { + auto do_compress = [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task + { size_t bytes_read; try @@ -1565,52 +1569,54 @@ class winhttp_client final : public _http_client_communicator &p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], chunk_size, hint) - .then([p_request_context, bytes_read, hint, chunk_size]( - pplx::task op) -> pplx::task { - http::compression::operation_result r; - - try + .then( + [p_request_context, bytes_read, hint, chunk_size]( + pplx::task op) -> pplx::task { - r = op.get(); - } - catch (...) - { - return pplx::task_from_exception(std::current_exception()); - } + http::compression::operation_result r; - if (hint == web::http::compression::operation_hint::is_last) - { - // We're done reading all chunks, but the compressor may still have compressed bytes to - // drain from previous reads - _ASSERTE(r.done || r.output_bytes_produced == chunk_size); - p_request_context->m_compression_state.m_needs_flush = !r.done; - p_request_context->m_compression_state.m_done = r.done; - } + try + { + r = op.get(); + } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } - // Update the number of bytes compressed in this read chunk; if it's been fully compressed, - // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk - p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= - p_request_context->m_compression_state.m_bytes_read); - if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) - { - _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); - p_request_context->m_remaining_to_write -= r.input_bytes_processed; - } + if (hint == web::http::compression::operation_hint::is_last) + { + // We're done reading all chunks, but the compressor may still have compressed bytes to + // drain from previous reads + _ASSERTE(r.done || r.output_bytes_produced == chunk_size); + p_request_context->m_compression_state.m_needs_flush = !r.done; + p_request_context->m_compression_state.m_done = r.done; + } - if (p_request_context->m_compression_state.m_acquired != nullptr && - p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read) - { - // Release the acquired buffer back to the streambuf at the earliest possible point - p_request_context->_get_readbuffer().release( - p_request_context->m_compression_state.m_acquired, - p_request_context->m_compression_state.m_bytes_processed); - p_request_context->m_compression_state.m_acquired = nullptr; - } + // Update the number of bytes compressed in this read chunk; if it's been fully compressed, + // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk + p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= + p_request_context->m_compression_state.m_bytes_read); + if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) + { + _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); + p_request_context->m_remaining_to_write -= r.input_bytes_processed; + } - return pplx::task_from_result(r.output_bytes_produced); - }); + if (p_request_context->m_compression_state.m_acquired != nullptr && + p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read) + { + // Release the acquired buffer back to the streambuf at the earliest possible point + p_request_context->_get_readbuffer().release( + p_request_context->m_compression_state.m_acquired, + p_request_context->m_compression_state.m_bytes_processed); + p_request_context->m_compression_state.m_acquired = nullptr; + } + + return pplx::task_from_result(r.output_bytes_produced); + }); }; if (p_request_context->m_compression_state.m_bytes_processed < @@ -1732,43 +1738,45 @@ class winhttp_client final : public _http_client_communicator p_request_context->allocate_request_space(nullptr, safeCount); rbuf.getn(p_request_context->m_body_data.get(), safeCount) - .then([p_request_context, rbuf](pplx::task op) { - size_t read; - try + .then( + [p_request_context, rbuf](pplx::task op) { - read = op.get(); - } - catch (...) - { - p_request_context->report_exception(std::current_exception()); - return; - } - _ASSERTE(read != static_cast(-1)); + size_t read; + try + { + read = op.get(); + } + catch (...) + { + p_request_context->report_exception(std::current_exception()); + return; + } + _ASSERTE(read != static_cast(-1)); - if (read == 0) - { - p_request_context->report_exception(http_exception( - U("Unexpected end of request body stream encountered before Content-Length met."))); - return; - } + if (read == 0) + { + p_request_context->report_exception(http_exception( + U("Unexpected end of request body stream encountered before Content-Length met."))); + return; + } - p_request_context->m_remaining_to_write -= read; + p_request_context->m_remaining_to_write -= read; - // Stop writing chunks after this one if no more data. - if (p_request_context->m_remaining_to_write == 0) - { - p_request_context->m_bodyType = no_body; - } + // Stop writing chunks after this one if no more data. + if (p_request_context->m_remaining_to_write == 0) + { + p_request_context->m_bodyType = no_body; + } - if (!WinHttpWriteData(p_request_context->m_request_handle, - p_request_context->m_body_data.get(), - static_cast(read), - nullptr)) - { - auto errorCode = GetLastError(); - p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpWriteData")); - } - }); + if (!WinHttpWriteData(p_request_context->m_request_handle, + p_request_context->m_body_data.get(), + static_cast(read), + nullptr)) + { + auto errorCode = GetLastError(); + p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpWriteData")); + } + }); } } @@ -1975,6 +1983,7 @@ class winhttp_client final : public _http_client_communicator switch (statusCode) { + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { WINHTTP_ASYNC_RESULT* error_result = reinterpret_cast(statusInfo); @@ -1993,10 +2002,133 @@ class winhttp_client final : public _http_client_communicator return; } } - + // Check if connection rejected the certificate. + if (p_request_context->m_certificate_chain_verification_failed) + { + p_request_context->report_error( + ERROR_WINHTTP_SECURE_FAILURE, + build_error_msg(ERROR_WINHTTP_SECURE_FAILURE, "WinHttpVerificationFailed")); + break; + } p_request_context->report_error(errorCode, build_error_msg(error_result)); return; } + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + { + // get actual URL which might be different from the original one due to redirection etc. + DWORD urlSize {0}; + WinHttpQueryOption(hRequestHandle, WINHTTP_OPTION_URL, NULL, &urlSize); + auto urlwchar = new WCHAR[urlSize / sizeof(WCHAR)]; + + WinHttpQueryOption(hRequestHandle, WINHTTP_OPTION_URL, (void*)urlwchar, &urlSize); + utility::string_t url(urlwchar); + + delete[] urlwchar; + + // obtain leaf cert based on which we will be able to build the certificate chain + PCCERT_CONTEXT pCert {nullptr}; + DWORD dwSize = sizeof(pCert); + + WinHttpQueryOption(hRequestHandle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &pCert, &dwSize); + + std::vector> cert_chain; + DWORD dwErrorStatus = 0; + + if (pCert) + { + CERT_ENHKEY_USAGE keyUsage = {}; + keyUsage.cUsageIdentifier = 0; + keyUsage.rgpszUsageIdentifier = NULL; + + CERT_USAGE_MATCH certUsage = {}; + certUsage.dwType = USAGE_MATCH_TYPE_AND; + certUsage.Usage = keyUsage; + + CERT_CHAIN_PARA chainPara = {}; + chainPara.cbSize = sizeof(CERT_CHAIN_PARA); + chainPara.RequestedUsage = certUsage; + + PCCERT_CHAIN_CONTEXT pChainContext = {}; + + // build the certificate chain relying on the actual intermediate certs returned as part of the TLS + // session disable any network operations to fetch certificates + auto validChain = CertGetCertificateChain( + NULL, + pCert, + NULL, + pCert->hCertStore, + &chainPara, + CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY | + CERT_CHAIN_REVOCATION_CHECK_CHAIN | CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE, + NULL, + &pChainContext); + + if (validChain && pChainContext) + { + dwErrorStatus = pChainContext->TrustStatus.dwErrorStatus; + cert_chain.reserve((int)pChainContext->cChain); + for (size_t i = 0; i < pChainContext->cChain; ++i) + { + auto chain = pChainContext->rgpChain[i]; + for (size_t j = 0; j < chain->cElement; ++j) + { + auto chainElement = chain->rgpElement[j]; + auto cert = chainElement->pCertContext; + if (cert) + { + cert_chain.emplace_back(std::vector( + cert->pbCertEncoded, cert->pbCertEncoded + (int)cert->cbCertEncoded)); + } + } + } + CertFreeCertificateChain(pChainContext); + } + CertFreeCertificateContext(pCert); + } + + utility::string_t host; + + try + { + host = web::uri(url).host(); + } + catch (std::exception e) + { + host = url; + } + + if (host.empty()) + { + host = url; + } + + auto info = std::make_shared( + utility::conversions::to_utf8string(host), cert_chain, dwErrorStatus); + + if (dwErrorStatus == CERT_TRUST_NO_ERROR || dwErrorStatus == CERT_TRUST_REVOCATION_STATUS_UNKNOWN || + dwErrorStatus == (CERT_TRUST_IS_OFFLINE_REVOCATION | CERT_TRUST_REVOCATION_STATUS_UNKNOWN)) + { + info->verified = true; + } + + if (p_request_context->m_http_client->client_config().invoke_certificate_chain_callback(info)) + { + if (info->verified) + { + p_request_context->m_certificate_chain_verification_failed = false; + } + else + { + p_request_context->m_certificate_chain_verification_failed = true; + } + } + else + { + p_request_context->m_certificate_chain_verification_failed = true; + } + break; + } case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: { if (!p_request_context->m_request.body()) @@ -2172,6 +2304,17 @@ class winhttp_client final : public _http_client_communicator return; } } + else + { + // The connection is allowed, but did the client allow the connection. + if (p_request_context->m_certificate_chain_verification_failed) + { + p_request_context->report_error( + ERROR_WINHTTP_SECURE_FAILURE, + build_error_msg(ERROR_WINHTTP_SECURE_FAILURE, "WinHttpVerificationFailed")); + break; + } + } // Check whether the request is compressed, and if so, whether we're handling it. if (!p_request_context->handle_compression()) @@ -2342,7 +2485,8 @@ class winhttp_client final : public _http_client_communicator // 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 { + [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) @@ -2426,115 +2570,126 @@ class winhttp_client final : public _http_client_communicator return true; }; - pplx::details::_do_while([p_request_context, chunk_size, process_buffer]() -> pplx::task { - uint8_t* buffer; - - try + pplx::details::_do_while( + [p_request_context, chunk_size, process_buffer]() -> pplx::task { - if (!process_buffer(p_request_context.get(), 0, true)) + uint8_t* buffer; + + try { - // The chunked request has been completely processed (or contains no data in the first - // place) - return pplx::task_from_result(false); + 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(false); + } + } + catch (...) + { + // The outer do-while requires an explicit task return to activate the then() clause + return pplx::task_from_exception(std::current_exception()); } - } - catch (...) - { - // The outer do-while requires an explicit task return to activate the then() clause - return pplx::task_from_exception(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 op) { - auto r = op.get(); - auto keep_going = [&r, process_buffer](winhttp_request_context* c) -> pplx::task { - _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; + // 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(); + } - 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( - process_buffer(c, r.output_bytes_produced, false)); - } - catch (...) + 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 op) { - return pplx::task_from_exception(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 op) { - if (op.get() != r.output_bytes_produced) + auto r = op.get(); + auto keep_going = + [&r, process_buffer](winhttp_request_context* c) -> pplx::task + { + _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( + process_buffer(c, r.output_bytes_produced, false)); + } + catch (...) + { + return pplx::task_from_exception(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) { - return pplx::task_from_exception( - std::runtime_error("Response stream unexpectedly failed to write the " - "requested number of bytes")); + // 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()); } - 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 op) + { + if (op.get() != r.output_bytes_produced) + { + return pplx::task_from_exception(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 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) + }) + .then( + [p_request_context](pplx::task op) { - p_request_context->_get_writebuffer().commit(0); - p_request_context->m_compression_state.m_acquired = nullptr; - } - p_request_context->report_exception(std::current_exception()); - } - }); + 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 { @@ -2548,28 +2703,31 @@ class winhttp_client final : public _http_client_communicator else { writebuf.putn_nocopy(p_request_context->m_body_data.get(), bytesRead) - .then([hRequestHandle, p_request_context, bytesRead](pplx::task op) { - size_t written = 0; - try - { - written = op.get(); - } - catch (...) + .then( + [hRequestHandle, p_request_context, bytesRead](pplx::task op) { - p_request_context->report_exception(std::current_exception()); - return; - } + 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; - } + // 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); - }); + read_next_response_chunk(p_request_context.get(), bytesRead); + }); } } return; diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index 67fc5ac47b..952a53254a 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -44,14 +44,34 @@ namespace details static bool verify_X509_cert_chain(const std::vector& certChain, const std::string& hostName); bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verifyCtx, const std::string& hostName) +#if defined(_WIN32) +#include +#include +#endif + +#include + + bool is_end_certificate_in_chain(boost::asio::ssl::verify_context& verifyCtx) { X509_STORE_CTX* storeContext = verifyCtx.native_handle(); int currentDepth = X509_STORE_CTX_get_error_depth(storeContext); if (currentDepth != 0) + { + return false; + } + return true; +} + +bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verifyCtx, + const std::string& hostName, + const CertificateChainFunction& func) +{ + if (!is_end_certificate_in_chain(verifyCtx)) { return true; } + X509_STORE_CTX* storeContext = verifyCtx.native_handle(); #if (OPENSSL_VERSION_NUMBER < 0x10100000L) STACK_OF(X509)* certStack = X509_STORE_CTX_get_chain(storeContext); #else @@ -89,7 +109,7 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verif certChain.push_back(std::move(certData)); } - auto verify_result = verify_X509_cert_chain(certChain, hostName); + auto verify_result = verify_X509_cert_chain(certChain, hostName, func); // The Windows Crypto APIs don't do host name checks, use Boost's implementation. #if defined(_WIN32) @@ -296,7 +316,7 @@ class cf_ref { static_assert(sizeof(cf_ref) == sizeof(T), "Code assumes just a wrapper, see usage in CFArrayCreate below."); } - cf_ref() : value(nullptr) {} + cf_ref() : value(nullptr) { } cf_ref(cf_ref&& other) : value(other.value) { other.value = nullptr; } ~cf_ref() @@ -316,7 +336,42 @@ class cf_ref }; } // namespace -bool verify_X509_cert_chain(const std::vector& certChain, const std::string& hostName) +static std::shared_ptr build_certificate_info_ptr(cf_ref& trust, + const std::string& hostName, + long trustResult, + bool isVerified) +{ + auto info = std::make_shared(hostName); + info->certificate_error = trustResult; + info->verified = isVerified; + + CFIndex cnt = SecTrustGetCertificateCount(trust.get()); + if (cnt > 0) + { + info->certificate_chain.reserve(cnt); + for (int i = 0; i < cnt; i++) + { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust.get(), i); + if (!cert) + { + break; + } + + cf_ref cdata = SecCertificateCopyData(cert); + if (cdata.get()) + { + const unsigned char* buffer = CFDataGetBytePtr(cdata.get()); + info->certificate_chain.emplace_back( + std::vector(buffer, buffer + CFDataGetLength(cdata.get()))); + } + } + } + return info; +} + +bool verify_X509_cert_chain(const std::vector& certChain, + const std::string& hostName, + const CertificateChainFunction& certInfoFunc /* = nullptr */) { // Build up CFArrayRef with all the certificates. // All this code is basically just to get into the correct structures for the Apple APIs. @@ -360,6 +415,9 @@ bool verify_X509_cert_chain(const std::vector& certChain, const std cf_ref policy = SecPolicyCreateSSL(true /* client side */, cfHostName.get()); cf_ref trust; OSStatus status = SecTrustCreateWithCertificates(certsArray.get(), policy.get(), &trust.get()); + + bool isVerified = false; + if (status == noErr) { // Perform actual certificate verification. @@ -367,27 +425,133 @@ bool verify_X509_cert_chain(const std::vector& certChain, const std status = SecTrustEvaluate(trust.get(), &trustResult); if (status == noErr && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) { - return true; + isVerified = true; } - } - return false; + if (certInfoFunc) + { + auto info = build_certificate_info_ptr(trust, hostName, (long)trustResult, isVerified); + + if (!certInfoFunc(info)) + { + isVerified = false; + } + } + } + return isVerified; } + #endif +std::vector> get_X509_cert_chain_encoded_data(boost::asio::ssl::verify_context& verifyCtx) +{ + std::vector> cert_chain; + + X509_STORE_CTX* storeContext = verifyCtx.native_handle(); + + STACK_OF(X509)* certStack = X509_STORE_CTX_get_chain(storeContext); + + const int numCerts = sk_X509_num(certStack); + if (numCerts < 0) + { + return cert_chain; + } + + cert_chain.reserve(numCerts); + for (int index = 0; index < numCerts; ++index) + { + X509* cert = sk_X509_value(certStack, index); + if (cert) + { + unsigned char* certKeyOut = nullptr; + int resCertificateLength = i2d_X509(cert, &certKeyOut); + if (resCertificateLength < 0 || !certKeyOut) + { + continue; + } + + std::vector certOut(certKeyOut, certKeyOut + resCertificateLength); + cert_chain.emplace_back(certOut); + } + } + return cert_chain; +} + #if defined(_WIN32) -bool verify_X509_cert_chain(const std::vector& certChain, const std::string& hostname) +namespace +{ +// Helper RAII unique_ptrs to free Windows structures. +struct cert_free_certificate_context +{ + void operator()(const CERT_CONTEXT* ctx) const { CertFreeCertificateContext(ctx); } +}; +typedef std::unique_ptr cert_context; +struct cert_free_certificate_chain +{ + void operator()(const CERT_CHAIN_CONTEXT* chain) const { CertFreeCertificateChain(chain); } +}; +typedef std::unique_ptr chain_context; +} // namespace + +static std::shared_ptr build_certificate_info_ptr(const chain_context& chain, + const std::string& hostName, + bool isVerified) +{ + auto info = std::make_shared(hostName); + + info->verified = isVerified; + info->certificate_error = chain->TrustStatus.dwErrorStatus; + info->certificate_chain.reserve((int)chain->cChain); + + for (size_t i = 0; i < chain->cChain; ++i) + { + auto pChain = chain->rgpChain[i]; + for (size_t j = 0; j < pChain->cElement; ++j) + { + auto chainElement = pChain->rgpElement[j]; + auto cert = chainElement->pCertContext; + if (cert) + { + info->certificate_chain.emplace_back( + std::vector(cert->pbCertEncoded, cert->pbCertEncoded + (int)cert->cbCertEncoded)); + } + } + } + + return info; +} + +bool verify_X509_cert_chain(const std::vector& certChain, + const std::string& hostName, + const CertificateChainFunction& certInfoFunc /* = nullptr */) { // Create certificate context from server certificate. - winhttp_cert_context cert; - cert.raw = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - reinterpret_cast(certChain[0].c_str()), - static_cast(certChain[0].size())); - if (cert.raw == nullptr) + cert_context pCert(CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + reinterpret_cast(certChain[0].c_str()), + static_cast(certChain[0].size()))); + if (pCert == nullptr) { return false; } + // Add all SSL intermediate certs into a store to be used by the OS building the full certificate chain. + HCERTSTORE caMemStore = NULL; + caMemStore = CertOpenStore(CERT_STORE_PROV_MEMORY, (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING), NULL, 0, NULL); + if (caMemStore) + { + for (const auto& certData : certChain) + { + cert_context certContext( + CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + reinterpret_cast(certData.c_str()), + static_cast(certData.size()))); + if (certContext) + { + CertAddCertificateContextToStore(caMemStore, certContext.get(), CERT_STORE_ADD_ALWAYS, NULL); + } + } + } + // Let the OS build a certificate chain from the server certificate. char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; char oidServerGatedCrypto[] = szOID_SERVER_GATED_CRYPTO; @@ -428,20 +592,46 @@ bool verify_X509_cert_chain(const std::vector& certChain, const std 0, &u16HostName[0], }; - CERT_CHAIN_POLICY_PARA policyPara = {sizeof(policyPara)}; - policyPara.pvExtraPolicyPara = &policyData; - CERT_CHAIN_POLICY_STATUS policyStatus = {sizeof(policyStatus)}; - if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext.raw, &policyPara, &policyStatus)) + params.RequestedUsage.Usage.cUsageIdentifier = std::extent::value; + params.RequestedUsage.Usage.rgpszUsageIdentifier = usages; + + PCCERT_CHAIN_CONTEXT pChainContext = {}; + chain_context chain; + + bool isVerified = false; + + auto cSuccess = CertGetCertificateChain( + nullptr, pCert.get(), nullptr, caMemStore, ¶ms, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &pChainContext); + + chain.reset(pChainContext); + + if (caMemStore) { - return false; + CertCloseStore(caMemStore, 0); } - if (policyStatus.dwError) + if (cSuccess && chain) { - return false; - } + // Only do revocation checking if it's known. + if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR || + chain->TrustStatus.dwErrorStatus == CERT_TRUST_REVOCATION_STATUS_UNKNOWN || + chain->TrustStatus.dwErrorStatus == + (CERT_TRUST_IS_OFFLINE_REVOCATION | CERT_TRUST_REVOCATION_STATUS_UNKNOWN)) + { + isVerified = true; + } - return true; + if (certInfoFunc) + { + auto info = build_certificate_info_ptr(chain, hostName, isVerified); + + if (!certInfoFunc(info)) + { + isVerified = false; + } + } + } + return isVerified; } #endif } // namespace details @@ -449,4 +639,4 @@ bool verify_X509_cert_chain(const std::vector& certChain, const std } // namespace http } // namespace web -#endif +#endif \ No newline at end of file diff --git a/Release/src/http/common/x509_cert_utilities.h b/Release/src/http/common/x509_cert_utilities.h index 854e30534d..ff3af5b420 100644 --- a/Release/src/http/common/x509_cert_utilities.h +++ b/Release/src/http/common/x509_cert_utilities.h @@ -13,6 +13,9 @@ #pragma once +#include "cpprest/certificate_info.h" +#include + #if defined(_WIN32) #include @@ -27,7 +30,7 @@ namespace details struct winhttp_cert_context { PCCERT_CONTEXT raw; - winhttp_cert_context() CPPREST_NOEXCEPT : raw(nullptr) {} + winhttp_cert_context() CPPREST_NOEXCEPT : raw(nullptr) { } winhttp_cert_context(const winhttp_cert_context&) = delete; winhttp_cert_context& operator=(const winhttp_cert_context&) = delete; ~winhttp_cert_context() @@ -44,7 +47,7 @@ struct winhttp_cert_context struct winhttp_cert_chain_context { PCCERT_CHAIN_CONTEXT raw; - winhttp_cert_chain_context() CPPREST_NOEXCEPT : raw(nullptr) {} + winhttp_cert_chain_context() CPPREST_NOEXCEPT : raw(nullptr) { } winhttp_cert_chain_context(const winhttp_cert_chain_context&) = delete; winhttp_cert_chain_context& operator=(const winhttp_cert_chain_context&) = delete; ~winhttp_cert_chain_context() @@ -94,6 +97,8 @@ namespace client { namespace details { +bool is_end_certificate_in_chain(boost::asio::ssl::verify_context& verifyCtx); + /// /// Using platform specific APIs verifies server certificate. /// Currently implemented to work on Windows, iOS, Android, and OS X. @@ -101,7 +106,15 @@ namespace details /// Boost.ASIO context to get certificate chain from. /// Host name from the URI. /// True if verification passed and server can be trusted, false otherwise. -bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verifyCtx, const std::string& hostName); +bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verifyCtx, + const std::string& hostName, + const CertificateChainFunction& func = nullptr); + +bool verify_X509_cert_chain(const std::vector& certChain, + const std::string& hostName, + const CertificateChainFunction& func = nullptr); + +std::vector> get_X509_cert_chain_encoded_data(boost::asio::ssl::verify_context& verifyCtx); } // namespace details } // namespace client } // namespace http diff --git a/Release/src/http/oauth/oauth2.cpp b/Release/src/http/oauth/oauth2.cpp index 3e54a6e07c..07bffa3242 100644 --- a/Release/src/http/oauth/oauth2.cpp +++ b/Release/src/http/oauth/oauth2.cpp @@ -137,6 +137,7 @@ pplx::task oauth2_config::_request_token(uri_builder& request_body_ub) // configure proxy http_client_config config; config.set_proxy(m_proxy); + config.set_user_certificate_chain_callback(m_certificate_chain_callback); http_client token_client(token_endpoint(), config); diff --git a/Release/src/websockets/client/ws_client_wspp.cpp b/Release/src/websockets/client/ws_client_wspp.cpp index d7c31c4095..d61fb50c12 100644 --- a/Release/src/websockets/client/ws_client_wspp.cpp +++ b/Release/src/websockets/client/ws_client_wspp.cpp @@ -188,81 +188,109 @@ class wspp_callback_client : public websocket_client_callback_impl, // Options specific to TLS client. auto& client = m_client->client(); - client.set_tls_init_handler([this](websocketpp::connection_hdl) { - auto sslContext = websocketpp::lib::shared_ptr( - new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); - sslContext->set_default_verify_paths(); - sslContext->set_options(boost::asio::ssl::context::default_workarounds); - if (m_config.get_ssl_context_callback()) + client.set_tls_init_handler( + [this](websocketpp::connection_hdl) { - m_config.get_ssl_context_callback()(*sslContext); - } - if (m_config.validate_certificates()) - { - sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer); - } - else - { - sslContext->set_verify_mode(boost::asio::ssl::context::verify_none); - } - -#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE - m_openssl_failed = false; -#endif - sslContext->set_verify_callback([this](bool preverified, boost::asio::ssl::verify_context& verifyCtx) { -#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE - // Attempt to use platform certificate validation when it is available: - // If OpenSSL fails we will doing verification at the end using the whole certificate chain, - // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the certificate - // chain. - if (!preverified) + auto sslContext = websocketpp::lib::shared_ptr( + new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); + sslContext->set_default_verify_paths(); + sslContext->set_options(boost::asio::ssl::context::default_workarounds); + if (m_config.get_ssl_context_callback()) { - m_openssl_failed = true; + m_config.get_ssl_context_callback()(*sslContext); } - if (m_openssl_failed) + if (m_config.validate_certificates()) { - return http::client::details::verify_cert_chain_platform_specific( - verifyCtx, utility::conversions::to_utf8string(m_uri.host())); + sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer); } + else + { + sslContext->set_verify_mode(boost::asio::ssl::context::verify_none); + } + +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE + m_openssl_failed = false; #endif - boost::asio::ssl::rfc2818_verification rfc2818(utility::conversions::to_utf8string(m_uri.host())); - return rfc2818(preverified, verifyCtx); - }); + sslContext->set_verify_callback( + [this](bool preverified, boost::asio::ssl::verify_context& verifyCtx) + { +#ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE + // Attempt to use platform certificate validation when it is available: + // If OpenSSL fails we will doing verification at the end using the whole certificate chain, + // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the + // certificate chain. + using namespace web::http::client::details; + if (!preverified) + { + m_openssl_failed = true; + } + if (m_openssl_failed) + { + if (!http::client::details::is_end_certificate_in_chain(verifyCtx)) + { + // Continue until we get the end certificate. + return true; + } + + auto chainFunc = + [this](const std::shared_ptr& cert_info) + { return m_config.invoke_certificate_chain_callback(cert_info); }; + + return http::client::details::verify_cert_chain_platform_specific( + verifyCtx, utility::conversions::to_utf8string(m_uri.host()), chainFunc); + } +#endif + boost::asio::ssl::rfc2818_verification rfc2818( + utility::conversions::to_utf8string(m_uri.host())); + if (!rfc2818(preverified, verifyCtx)) + { + return false; + } + + auto info = std::make_shared( + utility::conversions::to_utf8string(m_uri.host()), + get_X509_cert_chain_encoded_data(verifyCtx)); + info->verified = true; + + return m_config.invoke_certificate_chain_callback(info); + }); #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - // OpenSSL stores some per thread state that never will be cleaned up until - // the dll is unloaded. If static linking, like we do, the state isn't cleaned up - // at all and will be reported as leaks. - // See http://www.openssl.org/support/faq.html#PROG13 - // This is necessary here because it is called on the user's thread calling connect(...) - // eventually through websocketpp::client::get_connection(...) - ERR_remove_thread_state(nullptr); + // OpenSSL stores some per thread state that never will be cleaned up until + // the dll is unloaded. If static linking, like we do, the state isn't cleaned up + // at all and will be reported as leaks. + // See http://www.openssl.org/support/faq.html#PROG13 + // This is necessary here because it is called on the user's thread calling connect(...) + // eventually through websocketpp::client::get_connection(...) + ERR_remove_thread_state(nullptr); #endif - return sslContext; - }); + return sslContext; + }); // Options specific to underlying socket. - client.set_socket_init_handler([this](websocketpp::connection_hdl, - boost::asio::ssl::stream& ssl_stream) { - // Support for SNI. - if (m_config.is_sni_enabled()) + client.set_socket_init_handler( + [this](websocketpp::connection_hdl, boost::asio::ssl::stream& ssl_stream) { - // If user specified server name is empty default to use URI host name. - if (!m_config.server_name().empty()) + // Support for SNI. + if (m_config.is_sni_enabled()) { - // OpenSSL runs the string parameter through a macro casting away const with a C style cast. - // Do a C++ cast ourselves to avoid warnings. - SSL_set_tlsext_host_name(ssl_stream.native_handle(), - const_cast(m_config.server_name().c_str())); + // If user specified server name is empty default to use URI host name. + if (!m_config.server_name().empty()) + { + // OpenSSL runs the string parameter through a macro casting away const with a C style cast. + // Do a C++ cast ourselves to avoid warnings. + SSL_set_tlsext_host_name(ssl_stream.native_handle(), + const_cast(m_config.server_name().c_str())); + } + else + { + const auto& server_name = utility::conversions::to_utf8string(m_uri.host()); + SSL_set_tlsext_host_name(ssl_stream.native_handle(), + const_cast(server_name.c_str())); + } } - else - { - const auto& server_name = utility::conversions::to_utf8string(m_uri.host()); - SSL_set_tlsext_host_name(ssl_stream.native_handle(), const_cast(server_name.c_str())); - } - } - }); + }); return connect_impl(); } @@ -284,19 +312,24 @@ class wspp_callback_client : public websocket_client_callback_impl, client.start_perpetual(); _ASSERTE(m_state == CREATED); - client.set_open_handler([this](websocketpp::connection_hdl) { - _ASSERTE(m_state == CONNECTING); - m_state = CONNECTED; - m_connect_tce.set(); - }); + client.set_open_handler( + [this](websocketpp::connection_hdl) + { + _ASSERTE(m_state == CONNECTING); + m_state = CONNECTED; + m_connect_tce.set(); + }); - client.set_fail_handler([this](websocketpp::connection_hdl con_hdl) { - _ASSERTE(m_state == CONNECTING); - this->shutdown_wspp_impl(con_hdl, true); - }); + client.set_fail_handler( + [this](websocketpp::connection_hdl con_hdl) + { + _ASSERTE(m_state == CONNECTING); + this->shutdown_wspp_impl(con_hdl, true); + }); client.set_message_handler( - [this](websocketpp::connection_hdl, const websocketpp::config::asio_client::message_type::ptr& msg) { + [this](websocketpp::connection_hdl, const websocketpp::config::asio_client::message_type::ptr& msg) + { if (m_external_message_handler) { _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); @@ -325,37 +358,43 @@ class wspp_callback_client : public websocket_client_callback_impl, } }); - client.set_ping_handler([this](websocketpp::connection_hdl, const std::string& msg) { - if (m_external_message_handler) + client.set_ping_handler( + [this](websocketpp::connection_hdl, const std::string& msg) { - _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); - websocket_incoming_message incoming_msg; + if (m_external_message_handler) + { + _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); + websocket_incoming_message incoming_msg; - incoming_msg.m_msg_type = websocket_message_type::ping; - incoming_msg.m_body = concurrency::streams::container_buffer(msg); + incoming_msg.m_msg_type = websocket_message_type::ping; + incoming_msg.m_body = concurrency::streams::container_buffer(msg); - m_external_message_handler(incoming_msg); - } - return true; - }); + m_external_message_handler(incoming_msg); + } + return true; + }); - client.set_pong_handler([this](websocketpp::connection_hdl, const std::string& msg) { - if (m_external_message_handler) + client.set_pong_handler( + [this](websocketpp::connection_hdl, const std::string& msg) { - _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); - websocket_incoming_message incoming_msg; + if (m_external_message_handler) + { + _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); + websocket_incoming_message incoming_msg; - incoming_msg.m_msg_type = websocket_message_type::pong; - incoming_msg.m_body = concurrency::streams::container_buffer(msg); + incoming_msg.m_msg_type = websocket_message_type::pong; + incoming_msg.m_body = concurrency::streams::container_buffer(msg); - m_external_message_handler(incoming_msg); - } - }); + m_external_message_handler(incoming_msg); + } + }); - client.set_close_handler([this](websocketpp::connection_hdl con_hdl) { - _ASSERTE(m_state != CLOSED); - this->shutdown_wspp_impl(con_hdl, false); - }); + client.set_close_handler( + [this](websocketpp::connection_hdl con_hdl) + { + _ASSERTE(m_state != CLOSED); + this->shutdown_wspp_impl(con_hdl, false); + }); // Set User Agent specified by the user. This needs to happen before any connection is created const auto& headers = m_config.headers(); @@ -429,23 +468,25 @@ class wspp_callback_client : public websocket_client_callback_impl, client.connect(con); { std::lock_guard lock(m_wspp_client_lock); - m_thread = std::thread([&client]() { + m_thread = std::thread( + [&client]() + { #if defined(__ANDROID__) - crossplat::get_jvm_env(); + crossplat::get_jvm_env(); #endif - client.run(); + client.run(); #if defined(__ANDROID__) - crossplat::JVM.load()->DetachCurrentThread(); + crossplat::JVM.load()->DetachCurrentThread(); #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - // OpenSSL stores some per thread state that never will be cleaned up until - // the dll is unloaded. If static linking, like we do, the state isn't cleaned up - // at all and will be reported as leaks. - // See http://www.openssl.org/support/faq.html#PROG13 - ERR_remove_thread_state(nullptr); + // OpenSSL stores some per thread state that never will be cleaned up until + // the dll is unloaded. If static linking, like we do, the state isn't cleaned up + // at all and will be reported as leaks. + // See http://www.openssl.org/support/faq.html#PROG13 + ERR_remove_thread_state(nullptr); #endif - }); + }); } // unlock return pplx::create_task(m_connect_tce); } @@ -517,17 +558,20 @@ class wspp_callback_client : public websocket_client_callback_impl, // The stream needs to be buffered. auto is_buf_istream = is_buf.create_istream(); msg.m_body = concurrency::streams::container_buffer>(); - is_buf_istream.read_to_end(msg.m_body).then([this_client, msg](pplx::task t) mutable { - try - { - msg.m_length = t.get(); - this_client->send_msg(msg); - } - catch (...) - { - msg.signal_body_sent(std::current_exception()); - } - }); + is_buf_istream.read_to_end(msg.m_body) + .then( + [this_client, msg](pplx::task t) mutable + { + try + { + msg.m_length = t.get(); + this_client->send_msg(msg); + } + catch (...) + { + msg.signal_body_sent(std::current_exception()); + } + }); // We have postponed the call to send_msg() until after the data is buffered. return; } @@ -556,12 +600,16 @@ class wspp_callback_client : public websocket_client_callback_impl, // Allocate buffer to hold the data to be read from the stream. sp_allocated.reset(new uint8_t[length], [=](uint8_t* p) { delete[] p; }); - read_task = is_buf.getn(sp_allocated.get(), length).then([length](size_t bytes_read) { - if (bytes_read != length) - { - throw websocket_exception("Failed to read required length of data from the stream."); - } - }); + read_task = + is_buf.getn(sp_allocated.get(), length) + .then( + [length](size_t bytes_read) + { + if (bytes_read != length) + { + throw websocket_exception("Failed to read required length of data from the stream."); + } + }); } else { @@ -572,67 +620,72 @@ class wspp_callback_client : public websocket_client_callback_impl, } read_task - .then([this_client, msg, sp_allocated, length]() { - std::lock_guard lock(this_client->m_wspp_client_lock); - if (this_client->m_state > CONNECTED) + .then( + [this_client, msg, sp_allocated, length]() { - // The client has already been closed. - throw websocket_exception("Websocket connection is closed."); - } + std::lock_guard lock(this_client->m_wspp_client_lock); + if (this_client->m_state > CONNECTED) + { + // The client has already been closed. + throw websocket_exception("Websocket connection is closed."); + } - websocketpp::lib::error_code ec; - if (this_client->m_client->is_tls_client()) - { - this_client->send_msg_impl( - this_client, msg, sp_allocated, length, ec); - } - else - { - this_client->send_msg_impl( - this_client, msg, sp_allocated, length, ec); - } - return ec; - }) - .then([this_client, msg, is_buf, acquired, sp_allocated, length]( - pplx::task previousTask) mutable { - std::exception_ptr eptr; - try - { - // Catch exceptions from previous tasks, if any and convert it to websocket exception. - const auto& ec = previousTask.get(); - if (ec.value() != 0) + websocketpp::lib::error_code ec; + if (this_client->m_client->is_tls_client()) { - eptr = std::make_exception_ptr(websocket_exception(ec, build_error_msg(ec, "sending message"))); + this_client->send_msg_impl( + this_client, msg, sp_allocated, length, ec); } - } - catch (...) + else + { + this_client->send_msg_impl( + this_client, msg, sp_allocated, length, ec); + } + return ec; + }) + .then( + [this_client, msg, is_buf, acquired, sp_allocated, length]( + pplx::task previousTask) mutable { - eptr = std::current_exception(); - } + std::exception_ptr eptr; + try + { + // Catch exceptions from previous tasks, if any and convert it to websocket exception. + const auto& ec = previousTask.get(); + if (ec.value() != 0) + { + eptr = std::make_exception_ptr( + websocket_exception(ec, build_error_msg(ec, "sending message"))); + } + } + catch (...) + { + eptr = std::current_exception(); + } - if (acquired) - { - is_buf.release(sp_allocated.get(), length); - } + if (acquired) + { + is_buf.release(sp_allocated.get(), length); + } - // Set the send_task_completion_event after calling release. - if (eptr) - { - msg.signal_body_sent(eptr); - } - else - { - msg.signal_body_sent(); - } + // Set the send_task_completion_event after calling release. + if (eptr) + { + msg.signal_body_sent(eptr); + } + else + { + msg.signal_body_sent(); + } - websocket_outgoing_message next_msg; - bool msg_pending = this_client->m_out_queue.pop_and_peek(next_msg); + websocket_outgoing_message next_msg; + bool msg_pending = this_client->m_out_queue.pop_and_peek(next_msg); - if (msg_pending) - { - this_client->send_msg(next_msg); - } - }); + if (msg_pending) + { + this_client->send_msg(next_msg); + } + }); } pplx::task close() @@ -679,29 +732,31 @@ class wspp_callback_client : public websocket_client_callback_impl, client.stop_perpetual(); // Can't join thread directly since it is the current thread. - pplx::create_task([] {}).then([this, connecting, ec, closeCode, reason]() mutable { + pplx::create_task([] {}).then( + [this, connecting, ec, closeCode, reason]() mutable { - std::lock_guard lock(m_wspp_client_lock); - if (m_thread.joinable()) { - m_thread.join(); - } - } // unlock + std::lock_guard lock(m_wspp_client_lock); + if (m_thread.joinable()) + { + m_thread.join(); + } + } // unlock - if (connecting) - { - websocket_exception exc(ec, build_error_msg(ec, "set_fail_handler")); - m_connect_tce.set_exception(exc); - } - if (m_external_close_handler) - { - m_external_close_handler( - static_cast(closeCode), utility::conversions::to_string_t(reason), ec); - } - // Making a local copy of the TCE prevents it from being destroyed along with "this" - auto tceref = m_close_tce; - tceref.set(); - }); + if (connecting) + { + websocket_exception exc(ec, build_error_msg(ec, "set_fail_handler")); + m_connect_tce.set_exception(exc); + } + if (m_external_close_handler) + { + m_external_close_handler( + static_cast(closeCode), utility::conversions::to_string_t(reason), ec); + } + // Making a local copy of the TCE prevents it from being destroyed along with "this" + auto tceref = m_close_tce; + tceref.set(); + }); } template @@ -765,7 +820,7 @@ class wspp_callback_client : public websocket_client_callback_impl, // after construction based on the URI. struct websocketpp_client_base { - virtual ~websocketpp_client_base() CPPREST_NOEXCEPT {} + virtual ~websocketpp_client_base() CPPREST_NOEXCEPT { } template websocketpp::client& client() { @@ -784,14 +839,14 @@ class wspp_callback_client : public websocket_client_callback_impl, }; struct websocketpp_client : websocketpp_client_base { - ~websocketpp_client() CPPREST_NOEXCEPT {} + ~websocketpp_client() CPPREST_NOEXCEPT { } websocketpp::client& non_tls_client() override { return m_client; } bool is_tls_client() const override { return false; } websocketpp::client m_client; }; struct websocketpp_tls_client : websocketpp_client_base { - ~websocketpp_tls_client() CPPREST_NOEXCEPT {} + ~websocketpp_tls_client() CPPREST_NOEXCEPT { } websocketpp::client& tls_client() override { return m_client; } bool is_tls_client() const override { return true; } websocketpp::client m_client; @@ -844,3 +899,7 @@ websocket_callback_client::websocket_callback_client(websocket_client_config con } // namespace web #endif +<<<<<<< HEAD +======= + +>>>>>>> 917ee0eb (Add support for cert-pinning on Windows and Mac.) From 240979c9b64f13e68c73a4935d8dfa0d96c592c6 Mon Sep 17 00:00:00 2001 From: Chris O'Gorman Date: Wed, 28 Feb 2018 13:18:50 +0000 Subject: [PATCH 2/8] Pass WINHTTP_CALLBACK_FLAG_SEND_REQUEST when setting WinHttpSetStatusCallback and add two tests for cert-pinning windows. --- .../src/http/client/http_client_winhttp.cpp | 6 +- .../http/client/connections_and_errors.cpp | 155 +++++++++++++----- 2 files changed, 117 insertions(+), 44 deletions(-) diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index 766fc8c797..ae1ee90c37 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -2114,13 +2114,13 @@ class winhttp_client final : public _http_client_communicator if (p_request_context->m_http_client->client_config().invoke_certificate_chain_callback(info)) { - if (info->verified) + if (!info->verified && p_request_context->m_http_client->client_config().validate_certificates()) { - p_request_context->m_certificate_chain_verification_failed = false; + p_request_context->m_certificate_chain_verification_failed = true; } else { - p_request_context->m_certificate_chain_verification_failed = true; + p_request_context->m_certificate_chain_verification_failed = false; } } else diff --git a/Release/tests/functional/http/client/connections_and_errors.cpp b/Release/tests/functional/http/client/connections_and_errors.cpp index 847755d80a..df85d5ac75 100644 --- a/Release/tests/functional/http/client/connections_and_errors.cpp +++ b/Release/tests/functional/http/client/connections_and_errors.cpp @@ -60,10 +60,12 @@ static void pending_requests_after_client_impl(const uri& address) // send responses. for (size_t i = 0; i < num_requests; ++i) { - completed_requests.push_back(requests[i].then([&](test_request* request) { - http_asserts::assert_test_request_equals(request, mtd, U("/")); - VERIFY_ARE_EQUAL(0u, request->reply(status_codes::OK)); - })); + completed_requests.push_back(requests[i].then( + [&](test_request* request) + { + http_asserts::assert_test_request_equals(request, mtd, U("/")); + VERIFY_ARE_EQUAL(0u, request->reply(status_codes::OK)); + })); } // verify responses. @@ -106,6 +108,69 @@ SUITE(connections_and_errors) VERIFY_THROWS(t.wait(), web::http::http_exception); } + TEST_FIXTURE(uri_address, cert_pinning_succeed) + { + test_http_server::scoped_server scoped(m_uri); + + http_client_config client_config; + web::credentials cred(U("some_user"), U("some_password")); + client_config.set_credentials(cred); + pplx::cancellation_token_source source; + + client_config.set_user_certificate_chain_callback( + [](const std::shared_ptr&) -> bool + { + // accept any certificate. + return true; + }); + + http_client client(m_uri, client_config); + + scoped.server()->next_request().then( + [&](test_request* p_request) + { + http_asserts::assert_test_request_equals(p_request, methods::GET, U("/")); + p_request->reply(200); + }); + + auto response = client.request(methods::GET, source.get_token()).get(); + + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); + } + +#ifdef _WIN32 + + TEST_FIXTURE(uri_address, cert_pinning_failed) + { + test_http_server::scoped_server scoped(m_uri); + + http_client_config client_config; + web::credentials cred(U("some_user"), U("some_password")); + client_config.set_credentials(cred); + pplx::cancellation_token_source source; + + client_config.set_user_certificate_chain_callback( + [](const std::shared_ptr&) -> bool + { + // don't accept any certificate. + return false; + }); + + http_client client(m_uri, client_config); + + scoped.server()->next_request().then( + [&](test_request* p_request) + { + http_asserts::assert_test_request_equals(p_request, methods::GET, U("/")); + p_request->reply(200); + }); + + auto request = client.request(methods::GET, source.get_token()); + + VERIFY_THROWS_HTTP_ERROR_CODE(request.wait(), ERROR_WINHTTP_SECURE_FAILURE); + } +#endif + TEST_FIXTURE(uri_address, server_close_without_responding) { http_client_config config; @@ -211,12 +276,14 @@ SUITE(connections_and_errors) streams::producer_consumer_buffer buf; - listener.support([buf](http_request request) { - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - response.headers().add(header_names::connection, U("close")); - request.reply(response); - }); + listener.support( + [buf](http_request request) + { + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + response.headers().add(header_names::connection, U("close")); + request.reply(response); + }); { http_client_config config; @@ -240,12 +307,14 @@ SUITE(connections_and_errors) streams::producer_consumer_buffer buf; - listener.support([buf](http_request request) { - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - response.headers().add(header_names::connection, U("close")); - request.reply(response); - }); + listener.support( + [buf](http_request request) + { + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + response.headers().add(header_names::connection, U("close")); + request.reply(response); + }); { http_client_config config; @@ -285,18 +354,20 @@ SUITE(connections_and_errors) pplx::cancellation_token_source source; pplx::extensibility::event_t ev; - listener.support([&](http_request request) { - streams::producer_consumer_buffer buf; - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - request.reply(response); - ev.wait(); - buf.putc('a').wait(); - buf.putc('b').wait(); - buf.putc('c').wait(); - buf.putc('d').wait(); - buf.close(std::ios::out).wait(); - }); + listener.support( + [&](http_request request) + { + streams::producer_consumer_buffer buf; + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + request.reply(response); + ev.wait(); + buf.putc('a').wait(); + buf.putc('b').wait(); + buf.putc('c').wait(); + buf.putc('d').wait(); + buf.close(std::ios::out).wait(); + }); auto responseTask = c.request(methods::GET, source.get_token()); http_response response = responseTask.get(); @@ -378,19 +449,21 @@ SUITE(connections_and_errors) pplx::extensibility::event_t ev; pplx::extensibility::event_t ev2; - listener.support([&](http_request request) { - streams::producer_consumer_buffer buf; - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - request.reply(response); - buf.putc('a').wait(); - buf.putc('b').wait(); - ev.set(); - ev2.wait(); - buf.putc('c').wait(); - buf.putc('d').wait(); - buf.close(std::ios::out).wait(); - }); + listener.support( + [&](http_request request) + { + streams::producer_consumer_buffer buf; + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + request.reply(response); + buf.putc('a').wait(); + buf.putc('b').wait(); + ev.set(); + ev2.wait(); + buf.putc('c').wait(); + buf.putc('d').wait(); + buf.close(std::ios::out).wait(); + }); auto response = c.request(methods::GET, source.get_token()).get(); ev.wait(); From 67b5cdd0a07a61ff5c3b8d9400aa7a7cae1ca87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 28 Feb 2018 14:27:25 +0100 Subject: [PATCH 3/8] FIX: compile on linux --- Release/src/http/client/x509_cert_utilities.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index 952a53254a..3ff94a70eb 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -122,6 +122,8 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verif return verify_result; } +#endif + #if defined(ANDROID) || defined(__ANDROID__) using namespace crossplat; From a694f501ec2ca99969e9902e1555e5a4a79d9605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 28 Feb 2018 15:19:12 +0100 Subject: [PATCH 4/8] amend contributers file --- CONTRIBUTORS.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 118e368b92..2db1003aa7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -41,6 +41,10 @@ Chris O'Gorman (chogorma) Ocedo GmbH Henning Pfeiffer (megaposer) +neXenio GmbH +Patrik Fiedler (xqp) +René Meusel (reneme) + thomasschaub Trimble From 68c40a752218f38a36f93c6d18d0034253b8503a Mon Sep 17 00:00:00 2001 From: Patrik Fiedler Date: Thu, 5 Apr 2018 15:34:24 +0200 Subject: [PATCH 5/8] linux: invoke the certificate chain callback only once --- Release/include/cpprest/certificate_info.h | 8 ++++---- Release/include/cpprest/ws_client.h | 1 + Release/src/http/client/http_client_asio.cpp | 6 ++++++ Release/src/http/client/x509_cert_utilities.cpp | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Release/include/cpprest/certificate_info.h b/Release/include/cpprest/certificate_info.h index 282ff7e969..6939386542 100644 --- a/Release/include/cpprest/certificate_info.h +++ b/Release/include/cpprest/certificate_info.h @@ -34,15 +34,15 @@ namespace web { namespace http { namespace client { struct certificate_info { - CertificateChain certificate_chain; std::string host_name; + CertificateChain certificate_chain; long certificate_error{ 0 }; bool verified{ false }; - certificate_info(const std::string host) : host_name(host) {}; - certificate_info(const std::string host, CertificateChain chain, long error = 0) : host_name(host), certificate_chain(chain), certificate_error(error) {}; + certificate_info(const std::string host) : host_name(host) {} + certificate_info(const std::string host, CertificateChain chain, long error = 0) : host_name(host), certificate_chain(chain), certificate_error(error) {} }; using CertificateChainFunction = std::function certificate_Info)>; -}}} \ No newline at end of file +}}} diff --git a/Release/include/cpprest/ws_client.h b/Release/include/cpprest/ws_client.h index 09f53f6dca..e90d19bced 100644 --- a/Release/include/cpprest/ws_client.h +++ b/Release/include/cpprest/ws_client.h @@ -228,6 +228,7 @@ class websocket_client_config } private: + http::client::CertificateChainFunction m_certificate_chain_callback; web::web_proxy m_proxy; web::credentials m_credentials; web::http::http_headers m_headers; diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index db67a3c558..2f27ec10f6 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -1164,6 +1164,12 @@ class asio_context final : public request_context, public std::enable_shared_fro auto info = std::make_shared(host, get_X509_cert_chain_encoded_data(verifyCtx)); info->verified = true; + if (!is_end_certificate_in_chain(verifyCtx)) + { + // Continue until we get the end certificate. + return true; + } + return m_http_client->client_config().invoke_certificate_chain_callback(info); } diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index 3ff94a70eb..ef71e0c7a9 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -641,4 +641,4 @@ bool verify_X509_cert_chain(const std::vector& certChain, } // namespace http } // namespace web -#endif \ No newline at end of file +#endif From 32cb396083f966364bc9c3fd224bff5c8ca82ec7 Mon Sep 17 00:00:00 2001 From: Atul Bagga Date: Wed, 27 Oct 2021 16:42:03 +0530 Subject: [PATCH 6/8] Remove unnecessary formatting changes to reduce diff - introduced during merge due to auto format extension --- CONTRIBUTORS.txt | 1 + Release/include/cpprest/certificate_info.h | 75 ++- Release/include/cpprest/oauth2.h | 8 +- Release/include/cpprest/ws_client.h | 37 +- Release/src/http/client/http_client_impl.h | 2 +- .../src/http/client/http_client_winhttp.cpp | 558 +++++++++--------- 6 files changed, 332 insertions(+), 349 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2db1003aa7..193e7b3511 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -23,6 +23,7 @@ LeonidCSIT kreuzerkrieg evanc Jesse Towner (jwtowner) +Atul Bagga (atbagga) Abinsula s.r.l. Gianfranco Costamagna (LocutusOfBorg) diff --git a/Release/include/cpprest/certificate_info.h b/Release/include/cpprest/certificate_info.h index 6939386542..09dd21553e 100644 --- a/Release/include/cpprest/certificate_info.h +++ b/Release/include/cpprest/certificate_info.h @@ -1,48 +1,45 @@ /*** -* ==++== -* -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* ==--== -* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -* -* Certificate info -* -* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk -* -* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -****/ + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Certificate info + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ #pragma once -#include #include +#include -namespace web { namespace http { namespace client { - - using CertificateChain = std::vector>; - - struct certificate_info +namespace web +{ +namespace http +{ +namespace client +{ +using CertificateChain = std::vector>; + +struct certificate_info +{ + std::string host_name; + CertificateChain certificate_chain; + long certificate_error {0}; + bool verified {false}; + + certificate_info(const std::string host) : host_name(host) { } + certificate_info(const std::string host, CertificateChain chain, long error = 0) + : host_name(host), certificate_chain(chain), certificate_error(error) { - std::string host_name; - CertificateChain certificate_chain; - long certificate_error{ 0 }; - bool verified{ false }; - - certificate_info(const std::string host) : host_name(host) {} - certificate_info(const std::string host, CertificateChain chain, long error = 0) : host_name(host), certificate_chain(chain), certificate_error(error) {} - }; + } +}; - using CertificateChainFunction = std::function certificate_Info)>; +using CertificateChainFunction = std::function certificate_Info)>; -}}} +} // namespace client +} // namespace http +} // namespace web diff --git a/Release/include/cpprest/oauth2.h b/Release/include/cpprest/oauth2.h index 8ef685183c..733adc7a2f 100644 --- a/Release/include/cpprest/oauth2.h +++ b/Release/include/cpprest/oauth2.h @@ -59,8 +59,8 @@ namespace experimental class oauth2_exception : public std::exception { public: - oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) { } - ~oauth2_exception() CPPREST_NOEXCEPT { } + oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) {} + ~oauth2_exception() CPPREST_NOEXCEPT {} const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); } private: @@ -503,7 +503,7 @@ class oauth2_config friend class web::http::client::http_client_config; friend class web::http::oauth2::details::oauth2_handler; - oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) { } + oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) {} _ASYNCRTIMP pplx::task _request_token(uri_builder& request_body); @@ -553,7 +553,7 @@ namespace details class oauth2_handler : public http_pipeline_stage { public: - oauth2_handler(std::shared_ptr cfg) : m_config(std::move(cfg)) { } + oauth2_handler(std::shared_ptr cfg) : m_config(std::move(cfg)) {} virtual pplx::task propagate(http_request request) override { diff --git a/Release/include/cpprest/ws_client.h b/Release/include/cpprest/ws_client.h index e90d19bced..bd52cbc8ba 100644 --- a/Release/include/cpprest/ws_client.h +++ b/Release/include/cpprest/ws_client.h @@ -228,7 +228,6 @@ class websocket_client_config } private: - http::client::CertificateChainFunction m_certificate_chain_callback; web::web_proxy m_proxy; web::credentials m_credentials; web::http::http_headers m_headers; @@ -237,8 +236,8 @@ class websocket_client_config bool m_validate_certificates; http::client::CertificateChainFunction m_certificate_chain_callback; -#if !defined(_WIN32) || - !defined(__cplusplus_winrt) std::function m_ssl_context_callback; +#if !defined(_WIN32) || !defined(__cplusplus_winrt) + std::function m_ssl_context_callback; #endif }; @@ -252,14 +251,14 @@ class websocket_exception : public std::exception /// Creates an websocket_exception with just a string message and no error code. /// /// Error message string. - websocket_exception(const utility::string_t& whatArg) : m_msg(utility::conversions::to_utf8string(whatArg)) { } + websocket_exception(const utility::string_t& whatArg) : m_msg(utility::conversions::to_utf8string(whatArg)) {} #ifdef _WIN32 /// /// Creates an websocket_exception with just a string message and no error code. /// /// Error message string. - websocket_exception(std::string whatArg) : m_msg(std::move(whatArg)) { } + websocket_exception(std::string whatArg) : m_msg(std::move(whatArg)) {} #endif /// @@ -349,9 +348,9 @@ namespace details class websocket_client_callback_impl { public: - websocket_client_callback_impl(websocket_client_config config) : m_config(std::move(config)) { } + websocket_client_callback_impl(websocket_client_config config) : m_config(std::move(config)) {} - virtual ~websocket_client_callback_impl() CPPREST_NOEXCEPT { } + virtual ~websocket_client_callback_impl() CPPREST_NOEXCEPT {} virtual pplx::task connect() = 0; @@ -443,7 +442,7 @@ class websocket_client /// /// Creates a new websocket_client. /// - websocket_client() : m_client(std::make_shared(websocket_client_config())) { } + websocket_client() : m_client(std::make_shared(websocket_client_config())) {} /// /// Creates a new websocket_client. @@ -467,19 +466,17 @@ class websocket_client m_client->callback_client()->verify_uri(uri); m_client->callback_client()->set_uri(uri); auto client = m_client; - return m_client->callback_client()->connect().then( - [client](pplx::task result) + return m_client->callback_client()->connect().then([client](pplx::task result) { + try + { + result.get(); + } + catch (const websocket_exception& ex) { - try - { - result.get(); - } - catch (const websocket_exception& ex) - { - client->close_pending_tasks_with_error(ex); - throw; - } - }); + client->close_pending_tasks_with_error(ex); + throw; + } + }); } /// diff --git a/Release/src/http/client/http_client_impl.h b/Release/src/http/client/http_client_impl.h index 853fb305a9..074e1f301f 100644 --- a/Release/src/http/client/http_client_impl.h +++ b/Release/src/http/client/http_client_impl.h @@ -53,7 +53,7 @@ class request_context { public: // Destructor to clean up any held resources. - virtual ~request_context() { } + virtual ~request_context() {} virtual void report_exception(std::exception_ptr exceptionPtr); diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index ae1ee90c37..8bdd252f25 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -14,8 +14,8 @@ ****/ #include "stdafx.h" -#include "../common/internal_http_helpers.h" #include "../common/x509_cert_utilities.h" +#include "../common/internal_http_helpers.h" #include "cpprest/http_headers.h" #include "http_client_impl.h" #ifdef WIN32 @@ -174,7 +174,7 @@ class memory_holder size_t m_size; public: - memory_holder() : m_externalData(nullptr), m_size(0) { } + memory_holder() : m_externalData(nullptr), m_size(0) {} void allocate_space(size_t length) { @@ -924,7 +924,7 @@ class winhttp_client final : public _http_client_communicator DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2); if (!WinHttpSetOption( - m_hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols))) + m_hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols))) { return GetLastError(); } @@ -1161,8 +1161,8 @@ class winhttp_client final : public _http_client_communicator // And another 1 to enable the response (headers) of the rejected automatic redirect to be returned // rather than reporting an error "WinHttpReceiveResponse: 12156: The HTTP redirect request failed". DWORD maxRedirects = client_config().max_redirects() < MAXDWORD - 2 - ? static_cast(client_config().max_redirects() + 2) - : MAXDWORD; + ? static_cast(client_config().max_redirects() + 2) + : MAXDWORD; // Therefore, effective max redirects winhttp_context->m_remaining_redirects = maxRedirects - 2; @@ -1178,8 +1178,8 @@ class winhttp_client final : public _http_client_communicator // (Dis)allow HTTPS to HTTP redirects. DWORD redirectPolicy = client_config().https_to_http_redirects() - ? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS - : WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; + ? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS + : WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; if (!WinHttpSetOption(winhttp_context->m_request_handle, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, @@ -1252,9 +1252,8 @@ class winhttp_client final : public _http_client_communicator if (msg._cancellation_token() != pplx::cancellation_token::none()) { // cancellation callback is unregistered when request is completed. - winhttp_context->m_cancellationRegistration = msg._cancellation_token().register_callback( - [weak_winhttp_context]() - { + winhttp_context->m_cancellationRegistration = + msg._cancellation_token().register_callback([weak_winhttp_context]() { // Call the WinHttpSendRequest API after WinHttpCloseHandle will give invalid handle error and we // throw this exception. Call the cleanup to make the m_request_handle as nullptr, otherwise, // Application Verifier will give AV exception on m_request_handle. @@ -1415,8 +1414,7 @@ class winhttp_client final : public _http_client_communicator p_request_context->allocate_request_space( nullptr, chunk_size + http::details::chunked_encoding::additional_encoding_space); - auto after_read = [p_request_context, chunk_size, &compressor](pplx::task op) - { + auto after_read = [p_request_context, chunk_size, &compressor](pplx::task op) { size_t bytes_read; try { @@ -1492,132 +1490,133 @@ class winhttp_client final : public _http_client_communicator if (compressor) { - auto do_compress = [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task - { - size_t bytes_read; - - try - { - bytes_read = op.get(); - } - catch (...) + auto do_compress = + [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task { - return pplx::task_from_exception(std::current_exception()); - } + size_t bytes_read; - uint8_t* buffer = p_request_context->m_compression_state.m_acquired; - if (buffer == nullptr) - { - buffer = p_request_context->m_compression_state.m_buffer.data(); - } + try + { + bytes_read = op.get(); + } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } - web::http::compression::operation_hint hint = web::http::compression::operation_hint::has_more; + uint8_t* buffer = p_request_context->m_compression_state.m_acquired; + if (buffer == nullptr) + { + buffer = p_request_context->m_compression_state.m_buffer.data(); + } - if (bytes_read) - { - // An actual read always resets compression state for the next chunk - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read); - _ASSERTE(!p_request_context->m_compression_state.m_needs_flush); - p_request_context->m_compression_state.m_bytes_read = bytes_read; - p_request_context->m_compression_state.m_bytes_processed = 0; - if (p_request_context->m_readBufferCopy) + web::http::compression::operation_hint hint = web::http::compression::operation_hint::has_more; + + if (bytes_read) { - // If we've been asked to keep a copy of the raw data for restarts, do so here, pre-compression - p_request_context->m_readBufferCopy->putn_nocopy(buffer, bytes_read).wait(); + // An actual read always resets compression state for the next chunk + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read); + _ASSERTE(!p_request_context->m_compression_state.m_needs_flush); + p_request_context->m_compression_state.m_bytes_read = bytes_read; + p_request_context->m_compression_state.m_bytes_processed = 0; + if (p_request_context->m_readBufferCopy) + { + // If we've been asked to keep a copy of the raw data for restarts, do so here, pre-compression + p_request_context->m_readBufferCopy->putn_nocopy(buffer, bytes_read).wait(); + } + if (p_request_context->m_remaining_to_write == bytes_read) + { + // We've read to the end of the stream; finalize here if possible. We'll + // decrement the remaining count as we actually process the read buffer. + hint = web::http::compression::operation_hint::is_last; + } } - if (p_request_context->m_remaining_to_write == bytes_read) + else if (p_request_context->m_compression_state.m_needs_flush) { - // We've read to the end of the stream; finalize here if possible. We'll - // decrement the remaining count as we actually process the read buffer. + // All input has been consumed, but we still need to collect additional compressed output; + // this is done (in theory it can be multiple times) as a finalizing operation hint = web::http::compression::operation_hint::is_last; } - } - else if (p_request_context->m_compression_state.m_needs_flush) - { - // All input has been consumed, but we still need to collect additional compressed output; - // this is done (in theory it can be multiple times) as a finalizing operation - hint = web::http::compression::operation_hint::is_last; - } - else if (p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read) - { - if (p_request_context->m_remaining_to_write && - p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) + else if (p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read) { - // The stream ended earlier than we detected it should - return pplx::task_from_exception(http_exception( - U("Unexpected end of request body stream encountered before expected length met."))); + if (p_request_context->m_remaining_to_write && + p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) + { + // The stream ended earlier than we detected it should + return pplx::task_from_exception(http_exception( + U("Unexpected end of request body stream encountered before expected length met."))); + } + + // We think we're done; inform the compression library so it can finalize and/or give us any pending + // compressed bytes. Note that we may end up here multiple times if m_needs_flush is set, until all + // compressed bytes are drained. + hint = web::http::compression::operation_hint::is_last; } + // else we're still compressing bytes from the previous read - // We think we're done; inform the compression library so it can finalize and/or give us any pending - // compressed bytes. Note that we may end up here multiple times if m_needs_flush is set, until all - // compressed bytes are drained. - hint = web::http::compression::operation_hint::is_last; - } - // else we're still compressing bytes from the previous read - - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= - p_request_context->m_compression_state.m_bytes_read); - - uint8_t* in = buffer + p_request_context->m_compression_state.m_bytes_processed; - size_t inbytes = p_request_context->m_compression_state.m_bytes_read - - p_request_context->m_compression_state.m_bytes_processed; - return compressor - ->compress(in, - inbytes, - &p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], - chunk_size, - hint) - .then( - [p_request_context, bytes_read, hint, chunk_size]( - pplx::task op) -> pplx::task - { - http::compression::operation_result r; + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= + p_request_context->m_compression_state.m_bytes_read); - try - { - r = op.get(); - } - catch (...) + uint8_t* in = buffer + p_request_context->m_compression_state.m_bytes_processed; + size_t inbytes = p_request_context->m_compression_state.m_bytes_read - + p_request_context->m_compression_state.m_bytes_processed; + return compressor + ->compress(in, + inbytes, + &p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], + chunk_size, + hint) + .then( + [p_request_context, bytes_read, hint, chunk_size]( + pplx::task op) -> pplx::task { - return pplx::task_from_exception(std::current_exception()); - } + http::compression::operation_result r; - if (hint == web::http::compression::operation_hint::is_last) - { - // We're done reading all chunks, but the compressor may still have compressed bytes to - // drain from previous reads - _ASSERTE(r.done || r.output_bytes_produced == chunk_size); - p_request_context->m_compression_state.m_needs_flush = !r.done; - p_request_context->m_compression_state.m_done = r.done; - } + try + { + r = op.get(); + } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } - // Update the number of bytes compressed in this read chunk; if it's been fully compressed, - // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk - p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= - p_request_context->m_compression_state.m_bytes_read); - if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) - { - _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); - p_request_context->m_remaining_to_write -= r.input_bytes_processed; - } + if (hint == web::http::compression::operation_hint::is_last) + { + // We're done reading all chunks, but the compressor may still have compressed bytes to + // drain from previous reads + _ASSERTE(r.done || r.output_bytes_produced == chunk_size); + p_request_context->m_compression_state.m_needs_flush = !r.done; + p_request_context->m_compression_state.m_done = r.done; + } - if (p_request_context->m_compression_state.m_acquired != nullptr && - p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read) - { - // Release the acquired buffer back to the streambuf at the earliest possible point - p_request_context->_get_readbuffer().release( - p_request_context->m_compression_state.m_acquired, - p_request_context->m_compression_state.m_bytes_processed); - p_request_context->m_compression_state.m_acquired = nullptr; - } + // Update the number of bytes compressed in this read chunk; if it's been fully compressed, + // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk + p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= + p_request_context->m_compression_state.m_bytes_read); + if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) + { + _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); + p_request_context->m_remaining_to_write -= r.input_bytes_processed; + } - return pplx::task_from_result(r.output_bytes_produced); - }); - }; + if (p_request_context->m_compression_state.m_acquired != nullptr && + p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read) + { + // Release the acquired buffer back to the streambuf at the earliest possible point + p_request_context->_get_readbuffer().release( + p_request_context->m_compression_state.m_acquired, + p_request_context->m_compression_state.m_bytes_processed); + p_request_context->m_compression_state.m_acquired = nullptr; + } + + return pplx::task_from_result(r.output_bytes_produced); + }); + }; if (p_request_context->m_compression_state.m_bytes_processed < p_request_context->m_compression_state.m_bytes_read || @@ -1738,45 +1737,43 @@ class winhttp_client final : public _http_client_communicator p_request_context->allocate_request_space(nullptr, safeCount); rbuf.getn(p_request_context->m_body_data.get(), safeCount) - .then( - [p_request_context, rbuf](pplx::task op) + .then([p_request_context, rbuf](pplx::task op) { + size_t read; + try { - size_t read; - try - { - read = op.get(); - } - catch (...) - { - p_request_context->report_exception(std::current_exception()); - return; - } - _ASSERTE(read != static_cast(-1)); + read = op.get(); + } + catch (...) + { + p_request_context->report_exception(std::current_exception()); + return; + } + _ASSERTE(read != static_cast(-1)); - if (read == 0) - { - p_request_context->report_exception(http_exception( - U("Unexpected end of request body stream encountered before Content-Length met."))); - return; - } + if (read == 0) + { + p_request_context->report_exception(http_exception( + U("Unexpected end of request body stream encountered before Content-Length met."))); + return; + } - p_request_context->m_remaining_to_write -= read; + p_request_context->m_remaining_to_write -= read; - // Stop writing chunks after this one if no more data. - if (p_request_context->m_remaining_to_write == 0) - { - p_request_context->m_bodyType = no_body; - } + // Stop writing chunks after this one if no more data. + if (p_request_context->m_remaining_to_write == 0) + { + p_request_context->m_bodyType = no_body; + } - if (!WinHttpWriteData(p_request_context->m_request_handle, - p_request_context->m_body_data.get(), - static_cast(read), - nullptr)) - { - auto errorCode = GetLastError(); - p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpWriteData")); - } - }); + if (!WinHttpWriteData(p_request_context->m_request_handle, + p_request_context->m_body_data.get(), + static_cast(read), + nullptr)) + { + auto errorCode = GetLastError(); + p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpWriteData")); + } + }); } } @@ -1983,7 +1980,6 @@ class winhttp_client final : public _http_client_communicator switch (statusCode) { - case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { WINHTTP_ASYNC_RESULT* error_result = reinterpret_cast(statusInfo); @@ -2485,8 +2481,7 @@ class winhttp_client final : public _http_client_communicator // 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 - { + [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) @@ -2570,126 +2565,121 @@ class winhttp_client final : public _http_client_communicator return true; }; - pplx::details::_do_while( - [p_request_context, chunk_size, process_buffer]() -> pplx::task - { - uint8_t* buffer; + pplx::details::_do_while([p_request_context, chunk_size, process_buffer]() -> pplx::task { + 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(false); - } - } - catch (...) + try + { + if (!process_buffer(p_request_context.get(), 0, true)) { - // The outer do-while requires an explicit task return to activate the then() clause - return pplx::task_from_exception(std::current_exception()); + // The chunked request has been completely processed (or contains no data in the + // first place) + return pplx::task_from_result(false); } + } + catch (...) + { + // The outer do-while requires an explicit task return to activate the then() clause + return pplx::task_from_exception(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(); - } + // 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 op) + 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 op) + { + auto r = op.get(); + auto keep_going = + [&r, process_buffer](winhttp_request_context* c) -> pplx::task { - auto r = op.get(); - auto keep_going = - [&r, process_buffer](winhttp_request_context* c) -> pplx::task - { - _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( - process_buffer(c, r.output_bytes_produced, false)); - } - catch (...) - { - return pplx::task_from_exception(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); + _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; - if (p_request_context->m_compression_state.m_acquired != nullptr) + try { - // 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()); + // See if we still have more work to do for this section and/or for the + // response in general + return pplx::task_from_result( + process_buffer(c, r.output_bytes_produced, false)); } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } + }; - // 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 op) - { - if (op.get() != r.output_bytes_produced) - { - return pplx::task_from_exception(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 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) + _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) { - p_request_context->_get_writebuffer().commit(0); + // 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()); } - p_request_context->report_exception(std::current_exception()); + + // 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 op) + { + if (op.get() != r.output_bytes_produced) + { + return pplx::task_from_exception(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 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 { @@ -2703,31 +2693,29 @@ class winhttp_client final : public _http_client_communicator else { writebuf.putn_nocopy(p_request_context->m_body_data.get(), bytesRead) - .then( - [hRequestHandle, p_request_context, bytesRead](pplx::task op) + .then([hRequestHandle, p_request_context, bytesRead](pplx::task op) { + size_t written = 0; + try { - size_t written = 0; - try - { - written = op.get(); - } - catch (...) - { - p_request_context->report_exception(std::current_exception()); - return; - } + 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; - } + // 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); - }); + read_next_response_chunk(p_request_context.get(), bytesRead); + }); } } return; From 27ca4a3adf338ba156baac4315823b789b7b3a49 Mon Sep 17 00:00:00 2001 From: Atul Bagga Date: Wed, 27 Oct 2021 17:37:31 +0530 Subject: [PATCH 7/8] Remove unnecessary formatting changes to reduce diff - introduced during merge due to auto format extension --- Release/include/cpprest/oauth2.h | 3 +- Release/src/http/client/http_client_asio.cpp | 408 +++++++------- .../src/http/client/http_client_winhttp.cpp | 356 ++++++------ .../src/http/client/x509_cert_utilities.cpp | 2 +- Release/src/http/common/x509_cert_utilities.h | 4 +- .../src/websockets/client/ws_client_wspp.cpp | 514 ++++++++---------- .../http/client/connections_and_errors.cpp | 82 ++- 7 files changed, 660 insertions(+), 709 deletions(-) diff --git a/Release/include/cpprest/oauth2.h b/Release/include/cpprest/oauth2.h index 733adc7a2f..1364345fc5 100644 --- a/Release/include/cpprest/oauth2.h +++ b/Release/include/cpprest/oauth2.h @@ -297,7 +297,8 @@ class oauth2_config pplx::task token_from_client_credentials() { uri_builder ub; - ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::client_credentials, false); + ub.append_query( + details::oauth2_strings::grant_type, details::oauth2_strings::client_credentials, false); return _request_token(ub); } diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index 2f27ec10f6..195d7ec958 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -1,4 +1,3 @@ - /*** * Copyright (C) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. @@ -432,40 +431,38 @@ class asio_connection_pool final : public std::enable_shared_from_this weak_pool = pool; self.m_pool_epoch_timer.expires_from_now(boost::posix_time::seconds(30)); - self.m_pool_epoch_timer.async_wait( - [weak_pool](const boost::system::error_code& ec) + self.m_pool_epoch_timer.async_wait([weak_pool](const boost::system::error_code& ec) { + if (ec) { - if (ec) - { - return; - } + return; + } - auto pool = weak_pool.lock(); - if (!pool) - { - return; - } + auto pool = weak_pool.lock(); + if (!pool) + { + return; + } - auto& self = *pool; - std::lock_guard lock(self.m_lock); - bool restartTimer = false; - for (auto& entry : self.m_connections) + auto& self = *pool; + std::lock_guard lock(self.m_lock); + bool restartTimer = false; + for (auto& entry : self.m_connections) + { + if (entry.second.free_stale_connections()) { - if (entry.second.free_stale_connections()) - { - restartTimer = true; - } + restartTimer = true; } + } - if (restartTimer) - { - start_epoch_interval(pool); - } - else - { - self.m_is_timer_running = false; - } - }); + if (restartTimer) + { + start_epoch_interval(pool); + } + else + { + self.m_is_timer_running = false; + } + }); } std::mutex m_lock; @@ -757,9 +754,8 @@ class asio_context final : public request_context, public std::enable_shared_fro proxy_host = utility::conversions::to_utf8string(proxy_uri.host()); } - auto start_http_request_flow = - [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS](std::shared_ptr ctx) - { + auto start_http_request_flow = [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( + std::shared_ptr ctx) { if (ctx->m_request._cancellation_token().is_canceled()) { ctx->request_context::report_error(make_error_code(std::errc::operation_canceled).value(), @@ -904,15 +900,13 @@ class asio_context final : public request_context, public std::enable_shared_fro // weak_ptr prevents lambda from taking shared ownership of the context. // Otherwise context replacement in the handle_status_line() would leak the objects. std::weak_ptr ctx_weak(ctx); - ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback( - [ctx_weak]() + ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback([ctx_weak]() { + if (auto ctx_lock = ctx_weak.lock()) { - if (auto ctx_lock = ctx_weak.lock()) - { - // Shut down transmissions, close the socket and prevent connection from being pooled. - ctx_lock->m_connection->close(); - } - }); + // Shut down transmissions, close the socket and prevent connection from being pooled. + ctx_lock->m_connection->close(); + } + }); } }; @@ -1087,8 +1081,7 @@ class asio_context final : public request_context, public std::enable_shared_fro // Use a weak_ptr since the verify_callback is stored until the connection is // destroyed. This avoids creating a circular reference since we pool connection // objects. - [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) - { + [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) { auto this_request = weakCtx.lock(); if (this_request) { @@ -1221,42 +1214,40 @@ class asio_context final : public request_context, public std::enable_shared_fro m_body_buf.prepare(chunkSize + http::details::chunked_encoding::additional_encoding_space)); const auto this_request = shared_from_this(); readbuf.getn(buf + http::details::chunked_encoding::data_offset, chunkSize) - .then( - [this_request, buf, chunkSize AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) + .then([this_request, buf, chunkSize AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { + size_t readSize = 0; + try { - size_t readSize = 0; - try - { - readSize = op.get(); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } + readSize = op.get(); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } - const size_t offset = http::details::chunked_encoding::add_chunked_delimiters( - buf, chunkSize + http::details::chunked_encoding::additional_encoding_space, readSize); - this_request->m_body_buf.commit(readSize + - http::details::chunked_encoding::additional_encoding_space); - this_request->m_body_buf.consume(offset); - this_request->m_uploaded += static_cast(readSize); + const size_t offset = http::details::chunked_encoding::add_chunked_delimiters( + buf, chunkSize + http::details::chunked_encoding::additional_encoding_space, readSize); + this_request->m_body_buf.commit(readSize + + http::details::chunked_encoding::additional_encoding_space); + this_request->m_body_buf.consume(offset); + this_request->m_uploaded += static_cast(readSize); - if (readSize != 0) - { - this_request->m_connection->async_write(this_request->m_body_buf, - boost::bind(&asio_context::handle_write_chunked_body, - this_request, - boost::asio::placeholders::error)); - } - else - { - this_request->m_connection->async_write(this_request->m_body_buf, - boost::bind(&asio_context::handle_write_body, - this_request, - boost::asio::placeholders::error)); - } - }); + if (readSize != 0) + { + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_chunked_body, + this_request, + boost::asio::placeholders::error)); + } + else + { + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_body, + this_request, + boost::asio::placeholders::error)); + } + }); } void handle_write_large_body(const boost::system::error_code& ec) @@ -1283,36 +1274,33 @@ class asio_context final : public request_context, public std::enable_shared_fro } const auto this_request = shared_from_this(); - const auto readSize = - static_cast((std::min)(static_cast(m_http_client->client_config().chunksize()), - m_content_length - m_uploaded)); + const auto readSize = static_cast((std::min)( + static_cast(m_http_client->client_config().chunksize()), m_content_length - m_uploaded)); auto readbuf = _get_readbuffer(); readbuf.getn(boost::asio::buffer_cast(m_body_buf.prepare(readSize)), readSize) - .then( - [this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) + .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { + try { - try - { - const auto actualReadSize = op.get(); - if (actualReadSize == 0) - { - this_request->report_exception(http_exception( - "Unexpected end of request body stream encountered before Content-Length satisfied.")); - return; - } - this_request->m_uploaded += static_cast(actualReadSize); - this_request->m_body_buf.commit(actualReadSize); - this_request->m_connection->async_write(this_request->m_body_buf, - boost::bind(&asio_context::handle_write_large_body, - this_request, - boost::asio::placeholders::error)); - } - catch (...) + const auto actualReadSize = op.get(); + if (actualReadSize == 0) { - this_request->report_exception(std::current_exception()); + this_request->report_exception(http_exception( + "Unexpected end of request body stream encountered before Content-Length satisfied.")); return; } - }); + this_request->m_uploaded += static_cast(actualReadSize); + this_request->m_body_buf.commit(actualReadSize); + this_request->m_connection->async_write(this_request->m_body_buf, + boost::bind(&asio_context::handle_write_large_body, + this_request, + boost::asio::placeholders::error)); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } + }); } void handle_write_body(const boost::system::error_code& ec) @@ -1706,27 +1694,25 @@ class asio_context final : public request_context, public std::enable_shared_fro auto shared_decompressed = std::make_shared>(std::move(decompressed)); writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size()) - .then( - [this_request, to_read, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( - pplx::task op) + .then([this_request, to_read, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( + pplx::task op) { + try { - try - { - op.get(); - this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf - this_request->m_connection->async_read_until( - this_request->m_body_buf, - CRLF, - boost::bind(&asio_context::handle_chunk_header, - this_request, - boost::asio::placeholders::error)); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } - }); + op.get(); + this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf + this_request->m_connection->async_read_until( + this_request->m_body_buf, + CRLF, + boost::bind(&asio_context::handle_chunk_header, + this_request, + boost::asio::placeholders::error)); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } + }); } } else @@ -1822,10 +1808,9 @@ class asio_context final : public request_context, public std::enable_shared_fro this_request->m_downloaded += static_cast(read_size); this_request->async_read_until_buffersize( - static_cast( - (std::min)(static_cast( - this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), + static_cast((std::min)( + static_cast(this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), boost::bind( &asio_context::handle_read_content, this_request, boost::asio::placeholders::error)); } @@ -1842,51 +1827,20 @@ class asio_context final : public request_context, public std::enable_shared_fro auto shared_decompressed = std::make_shared>(std::move(decompressed)); writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size()) - .then( - [this_request, read_size, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( - pplx::task op) - { - size_t writtenSize = 0; - (void)writtenSize; - try - { - writtenSize = op.get(); - this_request->m_downloaded += static_cast(read_size); - this_request->m_body_buf.consume(read_size); - this_request->async_read_until_buffersize( - static_cast( - (std::min)(static_cast( - this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), - boost::bind(&asio_context::handle_read_content, - this_request, - boost::asio::placeholders::error)); - } - catch (...) - { - this_request->report_exception(std::current_exception()); - return; - } - }); - } - } - else - { - writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), read_size) - .then( - [this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) - { + .then([this_request, read_size, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS]( + pplx::task op) { size_t writtenSize = 0; + (void)writtenSize; try { writtenSize = op.get(); - this_request->m_downloaded += static_cast(writtenSize); - this_request->m_body_buf.consume(writtenSize); + this_request->m_downloaded += static_cast(read_size); + this_request->m_body_buf.consume(read_size); this_request->async_read_until_buffersize( static_cast( (std::min)(static_cast( - this_request->m_http_client->client_config().chunksize()), - this_request->m_content_length - this_request->m_downloaded)), + this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), boost::bind(&asio_context::handle_read_content, this_request, boost::asio::placeholders::error)); @@ -1897,6 +1851,33 @@ class asio_context final : public request_context, public std::enable_shared_fro return; } }); + } + } + else + { + writeBuffer.putn_nocopy(boost::asio::buffer_cast(m_body_buf.data()), read_size) + .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task op) { + size_t writtenSize = 0; + try + { + writtenSize = op.get(); + this_request->m_downloaded += static_cast(writtenSize); + this_request->m_body_buf.consume(writtenSize); + this_request->async_read_until_buffersize( + static_cast( + (std::min)(static_cast( + this_request->m_http_client->client_config().chunksize()), + this_request->m_content_length - this_request->m_downloaded)), + boost::bind(&asio_context::handle_read_content, + this_request, + boost::asio::placeholders::error)); + } + catch (...) + { + this_request->report_exception(std::current_exception()); + return; + } + }); } } else @@ -1926,8 +1907,8 @@ class asio_context final : public request_context, public std::enable_shared_fro m_timer.expires_from_now(m_duration); auto ctx = m_ctx; - m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) - { handle_timeout(ec, ctx); }); + m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) { + handle_timeout(ec, ctx); }); } void reset() @@ -1939,8 +1920,8 @@ class asio_context final : public request_context, public std::enable_shared_fro // The existing handler was canceled so schedule a new one. assert(m_state == started); auto ctx = m_ctx; - m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) - { handle_timeout(ec, ctx); }); + m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) { + handle_timeout(ec, ctx); }); } } @@ -2035,20 +2016,21 @@ static bool is_retrieval_redirection(status_code code) switch (code) { - case status_codes::MovedPermanently: - // "For historical reasons, a user agent MAY change the request method - // from POST to GET for the subsequent request." - return true; - case status_codes::Found: - // "For historical reasons, a user agent MAY change the request method - // from POST to GET for the subsequent request." - return true; - case status_codes::SeeOther: - // "A user agent can perform a [GET or HEAD] request. It is primarily - // used to allow the output of a POST action to redirect the user agent - // to a selected resource." - return true; - default: return false; + case status_codes::MovedPermanently: + // "For historical reasons, a user agent MAY change the request method + // from POST to GET for the subsequent request." + return true; + case status_codes::Found: + // "For historical reasons, a user agent MAY change the request method + // from POST to GET for the subsequent request." + return true; + case status_codes::SeeOther: + // "A user agent can perform a [GET or HEAD] request. It is primarily + // used to allow the output of a POST action to redirect the user agent + // to a selected resource." + return true; + default: + return false; } } @@ -2059,15 +2041,16 @@ static bool is_unchanged_redirection(status_code code) switch (code) { - case status_codes::TemporaryRedirect: - // "The user agent MUST NOT change the request method if it performs an - // automatic redirection to that URI." - return true; - case status_codes::PermanentRedirect: - // This status code "does not allow changing the request method from POST - // to GET." - return true; - default: return false; + case status_codes::TemporaryRedirect: + // "The user agent MUST NOT change the request method if it performs an + // automatic redirection to that URI." + return true; + case status_codes::PermanentRedirect: + // This status code "does not allow changing the request method from POST + // to GET." + return true; + default: + return false; } } @@ -2078,13 +2061,19 @@ static bool is_recognized_redirection(status_code code) return is_retrieval_redirection(code) || is_unchanged_redirection(code); } -static bool is_retrieval_request(method method) { return methods::GET == method || methods::HEAD == method; } +static bool is_retrieval_request(method method) +{ + return methods::GET == method || methods::HEAD == method; +} -static const std::vector request_body_header_names = {header_names::content_encoding, - header_names::content_language, - header_names::content_length, - header_names::content_location, - header_names::content_type}; +static const std::vector request_body_header_names = +{ + header_names::content_encoding, + header_names::content_language, + header_names::content_length, + header_names::content_location, + header_names::content_type +}; // A request continuation that follows redirects according to the specified configuration. // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request @@ -2103,7 +2092,9 @@ struct http_redirect_follower }; http_redirect_follower::http_redirect_follower(http_client_config config, const http_request& request) - : config(std::move(config)), followed_urls(1, request.absolute_uri()), redirect(request.method()) + : config(std::move(config)) + , followed_urls(1, request.absolute_uri()) + , redirect(request.method()) { // Stash the original request URL, etc. to be prepared for an automatic redirect @@ -2122,25 +2113,29 @@ http_redirect_follower::http_redirect_follower(http_client_config config, const uri http_redirect_follower::url_to_follow(const http_response& response) const { // Return immediately if the response is not a supported redirection - if (!is_recognized_redirection(response.status_code())) return {}; + if (!is_recognized_redirection(response.status_code())) + return {}; // Although not required by RFC 7231, config may limit the number of automatic redirects // (followed_urls includes the initial request URL, hence '<' here) - if (config.max_redirects() < followed_urls.size()) return {}; + if (config.max_redirects() < followed_urls.size()) + return {}; // Can't very well automatically redirect if the server hasn't provided a Location const auto location = response.headers().find(header_names::location); - if (response.headers().end() == location) return {}; + if (response.headers().end() == location) + return {}; uri to_follow(followed_urls.back().resolve_uri(location->second)); // Config may prohibit automatic redirects from HTTPS to HTTP - if (!config.https_to_http_redirects() && followed_urls.back().scheme() == _XPLATSTR("https") && - to_follow.scheme() != _XPLATSTR("https")) + if (!config.https_to_http_redirects() && followed_urls.back().scheme() == _XPLATSTR("https") + && to_follow.scheme() != _XPLATSTR("https")) return {}; // "A client SHOULD detect and intervene in cyclical redirections." - if (followed_urls.end() != std::find(followed_urls.begin(), followed_urls.end(), to_follow)) return {}; + if (followed_urls.end() != std::find(followed_urls.begin(), followed_urls.end(), to_follow)) + return {}; return to_follow; } @@ -2149,14 +2144,16 @@ pplx::task http_redirect_follower::operator()(http_response respo { // Return immediately if the response doesn't indicate a valid automatic redirect uri to_follow = url_to_follow(response); - if (to_follow.is_empty()) return pplx::task_from_result(response); + if (to_follow.is_empty()) + return pplx::task_from_result(response); // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request // using the same method since the request body may have been consumed. if (!is_retrieval_request(redirect.method()) && !is_retrieval_redirection(response.status_code())) return pplx::task_from_result(response); - if (!is_retrieval_request(redirect.method())) redirect.set_method(methods::GET); + if (!is_retrieval_request(redirect.method())) + redirect.set_method(methods::GET); // If the reply to this request is also a redirect, we want visibility of that auto config_no_redirects = config; @@ -2188,8 +2185,9 @@ pplx::task asio_client::propagate(http_request request) // Asynchronously send the response with the HTTP client implementation. this->async_send_request(context); - return client_config().max_redirects() > 0 ? result_task.then(http_redirect_follower(client_config(), request)) - : result_task; + return client_config().max_redirects() > 0 + ? result_task.then(http_redirect_follower(client_config(), request)) + : result_task; } } // namespace details } // namespace client diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index 8bdd252f25..f2e449d1ed 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -1491,132 +1491,131 @@ class winhttp_client final : public _http_client_communicator if (compressor) { auto do_compress = - [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task - { - size_t bytes_read; + [p_request_context, chunk_size, &compressor](pplx::task op) -> pplx::task { + size_t bytes_read; - try - { - bytes_read = op.get(); - } - catch (...) - { - return pplx::task_from_exception(std::current_exception()); - } + try + { + bytes_read = op.get(); + } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } - uint8_t* buffer = p_request_context->m_compression_state.m_acquired; - if (buffer == nullptr) - { - buffer = p_request_context->m_compression_state.m_buffer.data(); - } + uint8_t* buffer = p_request_context->m_compression_state.m_acquired; + if (buffer == nullptr) + { + buffer = p_request_context->m_compression_state.m_buffer.data(); + } - web::http::compression::operation_hint hint = web::http::compression::operation_hint::has_more; + web::http::compression::operation_hint hint = web::http::compression::operation_hint::has_more; - if (bytes_read) + if (bytes_read) + { + // An actual read always resets compression state for the next chunk + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read); + _ASSERTE(!p_request_context->m_compression_state.m_needs_flush); + p_request_context->m_compression_state.m_bytes_read = bytes_read; + p_request_context->m_compression_state.m_bytes_processed = 0; + if (p_request_context->m_readBufferCopy) { - // An actual read always resets compression state for the next chunk - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read); - _ASSERTE(!p_request_context->m_compression_state.m_needs_flush); - p_request_context->m_compression_state.m_bytes_read = bytes_read; - p_request_context->m_compression_state.m_bytes_processed = 0; - if (p_request_context->m_readBufferCopy) - { - // If we've been asked to keep a copy of the raw data for restarts, do so here, pre-compression - p_request_context->m_readBufferCopy->putn_nocopy(buffer, bytes_read).wait(); - } - if (p_request_context->m_remaining_to_write == bytes_read) - { - // We've read to the end of the stream; finalize here if possible. We'll - // decrement the remaining count as we actually process the read buffer. - hint = web::http::compression::operation_hint::is_last; - } + // If we've been asked to keep a copy of the raw data for restarts, do so here, pre-compression + p_request_context->m_readBufferCopy->putn_nocopy(buffer, bytes_read).wait(); } - else if (p_request_context->m_compression_state.m_needs_flush) + if (p_request_context->m_remaining_to_write == bytes_read) { - // All input has been consumed, but we still need to collect additional compressed output; - // this is done (in theory it can be multiple times) as a finalizing operation + // We've read to the end of the stream; finalize here if possible. We'll + // decrement the remaining count as we actually process the read buffer. hint = web::http::compression::operation_hint::is_last; } - else if (p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read) + } + else if (p_request_context->m_compression_state.m_needs_flush) + { + // All input has been consumed, but we still need to collect additional compressed output; + // this is done (in theory it can be multiple times) as a finalizing operation + hint = web::http::compression::operation_hint::is_last; + } + else if (p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read) + { + if (p_request_context->m_remaining_to_write && + p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) { - if (p_request_context->m_remaining_to_write && - p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) - { - // The stream ended earlier than we detected it should - return pplx::task_from_exception(http_exception( - U("Unexpected end of request body stream encountered before expected length met."))); - } - - // We think we're done; inform the compression library so it can finalize and/or give us any pending - // compressed bytes. Note that we may end up here multiple times if m_needs_flush is set, until all - // compressed bytes are drained. - hint = web::http::compression::operation_hint::is_last; + // The stream ended earlier than we detected it should + return pplx::task_from_exception(http_exception( + U("Unexpected end of request body stream encountered before expected length met."))); } - // else we're still compressing bytes from the previous read - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= - p_request_context->m_compression_state.m_bytes_read); + // We think we're done; inform the compression library so it can finalize and/or give us any pending + // compressed bytes. Note that we may end up here multiple times if m_needs_flush is set, until all + // compressed bytes are drained. + hint = web::http::compression::operation_hint::is_last; + } + // else we're still compressing bytes from the previous read + + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= + p_request_context->m_compression_state.m_bytes_read); + + uint8_t* in = buffer + p_request_context->m_compression_state.m_bytes_processed; + size_t inbytes = p_request_context->m_compression_state.m_bytes_read - + p_request_context->m_compression_state.m_bytes_processed; + return compressor + ->compress(in, + inbytes, + &p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], + chunk_size, + hint) + .then( + [p_request_context, bytes_read, hint, chunk_size]( + pplx::task op) -> pplx::task + { + http::compression::operation_result r; - uint8_t* in = buffer + p_request_context->m_compression_state.m_bytes_processed; - size_t inbytes = p_request_context->m_compression_state.m_bytes_read - - p_request_context->m_compression_state.m_bytes_processed; - return compressor - ->compress(in, - inbytes, - &p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], - chunk_size, - hint) - .then( - [p_request_context, bytes_read, hint, chunk_size]( - pplx::task op) -> pplx::task + try { - http::compression::operation_result r; - - try - { - r = op.get(); - } - catch (...) - { - return pplx::task_from_exception(std::current_exception()); - } + r = op.get(); + } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } - if (hint == web::http::compression::operation_hint::is_last) - { - // We're done reading all chunks, but the compressor may still have compressed bytes to - // drain from previous reads - _ASSERTE(r.done || r.output_bytes_produced == chunk_size); - p_request_context->m_compression_state.m_needs_flush = !r.done; - p_request_context->m_compression_state.m_done = r.done; - } + if (hint == web::http::compression::operation_hint::is_last) + { + // We're done reading all chunks, but the compressor may still have compressed bytes to + // drain from previous reads + _ASSERTE(r.done || r.output_bytes_produced == chunk_size); + p_request_context->m_compression_state.m_needs_flush = !r.done; + p_request_context->m_compression_state.m_done = r.done; + } - // Update the number of bytes compressed in this read chunk; if it's been fully compressed, - // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk - p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; - _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= - p_request_context->m_compression_state.m_bytes_read); - if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) - { - _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); - p_request_context->m_remaining_to_write -= r.input_bytes_processed; - } + // Update the number of bytes compressed in this read chunk; if it's been fully compressed, + // we'll reset m_bytes_processed and m_bytes_read after reading the next chunk + p_request_context->m_compression_state.m_bytes_processed += r.input_bytes_processed; + _ASSERTE(p_request_context->m_compression_state.m_bytes_processed <= + p_request_context->m_compression_state.m_bytes_read); + if (p_request_context->m_remaining_to_write != (std::numeric_limits::max)()) + { + _ASSERTE(p_request_context->m_remaining_to_write >= r.input_bytes_processed); + p_request_context->m_remaining_to_write -= r.input_bytes_processed; + } - if (p_request_context->m_compression_state.m_acquired != nullptr && - p_request_context->m_compression_state.m_bytes_processed == - p_request_context->m_compression_state.m_bytes_read) - { - // Release the acquired buffer back to the streambuf at the earliest possible point - p_request_context->_get_readbuffer().release( - p_request_context->m_compression_state.m_acquired, - p_request_context->m_compression_state.m_bytes_processed); - p_request_context->m_compression_state.m_acquired = nullptr; - } + if (p_request_context->m_compression_state.m_acquired != nullptr && + p_request_context->m_compression_state.m_bytes_processed == + p_request_context->m_compression_state.m_bytes_read) + { + // Release the acquired buffer back to the streambuf at the earliest possible point + p_request_context->_get_readbuffer().release( + p_request_context->m_compression_state.m_acquired, + p_request_context->m_compression_state.m_bytes_processed); + p_request_context->m_compression_state.m_acquired = nullptr; + } - return pplx::task_from_result(r.output_bytes_produced); - }); - }; + return pplx::task_from_result(r.output_bytes_produced); + }); + }; if (p_request_context->m_compression_state.m_bytes_processed < p_request_context->m_compression_state.m_bytes_read || @@ -1766,9 +1765,9 @@ class winhttp_client final : public _http_client_communicator } if (!WinHttpWriteData(p_request_context->m_request_handle, - p_request_context->m_body_data.get(), - static_cast(read), - nullptr)) + p_request_context->m_body_data.get(), + static_cast(read), + nullptr)) { auto errorCode = GetLastError(); p_request_context->report_error(errorCode, build_error_msg(errorCode, "WinHttpWriteData")); @@ -2572,8 +2571,8 @@ class winhttp_client final : public _http_client_communicator { if (!process_buffer(p_request_context.get(), 0, true)) { - // The chunked request has been completely processed (or contains no data in the - // first place) + // The chunked request has been completely processed (or contains no data in the first + // place) return pplx::task_from_result(false); } } @@ -2583,9 +2582,9 @@ class winhttp_client final : public _http_client_communicator return pplx::task_from_exception(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 + // 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) @@ -2601,7 +2600,7 @@ class winhttp_client final : public _http_client_communicator } uint8_t* in = p_request_context->m_compression_state.m_buffer.data() + - p_request_context->m_compression_state.m_bytes_processed; + p_request_context->m_compression_state.m_bytes_processed; size_t inbytes = p_request_context->m_compression_state.m_chunk_bytes; if (inbytes) { @@ -2610,75 +2609,73 @@ class winhttp_client final : public _http_client_communicator 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 op) + .then([p_request_context, buffer, chunk_size, process_buffer]( + pplx::task op) { + auto r = op.get(); + auto keep_going = + [&r, process_buffer](winhttp_request_context* c) -> pplx::task { - auto r = op.get(); - auto keep_going = - [&r, process_buffer](winhttp_request_context* c) -> pplx::task - { - _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( - process_buffer(c, r.output_bytes_produced, false)); - } - catch (...) - { - return pplx::task_from_exception(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); + _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; - if (p_request_context->m_compression_state.m_acquired != nullptr) + try { - // 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()); + // See if we still have more work to do for this section and/or for the + // response in general + return pplx::task_from_result( + process_buffer(c, r.output_bytes_produced, false)); } + catch (...) + { + return pplx::task_from_exception(std::current_exception()); + } + }; - // 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 op) - { - if (op.get() != r.output_bytes_produced) - { - return pplx::task_from_exception(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 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) + _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) { - p_request_context->_get_writebuffer().commit(0); + // 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()); } - p_request_context->report_exception(std::current_exception()); + + // 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 op) + { + if (op.get() != r.output_bytes_produced) + { + return pplx::task_from_exception(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 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 @@ -2708,9 +2705,8 @@ class winhttp_client final : public _http_client_communicator // 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")); + p_request_context->report_exception(std::runtime_error( + "response stream unexpectedly failed to write the requested number of bytes")); return; } diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index ef71e0c7a9..4e9ae6bb0d 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -318,7 +318,7 @@ class cf_ref { static_assert(sizeof(cf_ref) == sizeof(T), "Code assumes just a wrapper, see usage in CFArrayCreate below."); } - cf_ref() : value(nullptr) { } + cf_ref() : value(nullptr) {} cf_ref(cf_ref&& other) : value(other.value) { other.value = nullptr; } ~cf_ref() diff --git a/Release/src/http/common/x509_cert_utilities.h b/Release/src/http/common/x509_cert_utilities.h index ff3af5b420..a179037a37 100644 --- a/Release/src/http/common/x509_cert_utilities.h +++ b/Release/src/http/common/x509_cert_utilities.h @@ -30,7 +30,7 @@ namespace details struct winhttp_cert_context { PCCERT_CONTEXT raw; - winhttp_cert_context() CPPREST_NOEXCEPT : raw(nullptr) { } + winhttp_cert_context() CPPREST_NOEXCEPT : raw(nullptr) {} winhttp_cert_context(const winhttp_cert_context&) = delete; winhttp_cert_context& operator=(const winhttp_cert_context&) = delete; ~winhttp_cert_context() @@ -47,7 +47,7 @@ struct winhttp_cert_context struct winhttp_cert_chain_context { PCCERT_CHAIN_CONTEXT raw; - winhttp_cert_chain_context() CPPREST_NOEXCEPT : raw(nullptr) { } + winhttp_cert_chain_context() CPPREST_NOEXCEPT : raw(nullptr) {} winhttp_cert_chain_context(const winhttp_cert_chain_context&) = delete; winhttp_cert_chain_context& operator=(const winhttp_cert_chain_context&) = delete; ~winhttp_cert_chain_context() diff --git a/Release/src/websockets/client/ws_client_wspp.cpp b/Release/src/websockets/client/ws_client_wspp.cpp index d61fb50c12..e7e747adb9 100644 --- a/Release/src/websockets/client/ws_client_wspp.cpp +++ b/Release/src/websockets/client/ws_client_wspp.cpp @@ -188,109 +188,105 @@ class wspp_callback_client : public websocket_client_callback_impl, // Options specific to TLS client. auto& client = m_client->client(); - client.set_tls_init_handler( - [this](websocketpp::connection_hdl) + client.set_tls_init_handler([this](websocketpp::connection_hdl) { + auto sslContext = websocketpp::lib::shared_ptr( + new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); + sslContext->set_default_verify_paths(); + sslContext->set_options(boost::asio::ssl::context::default_workarounds); + if (m_config.get_ssl_context_callback()) { - auto sslContext = websocketpp::lib::shared_ptr( - new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); - sslContext->set_default_verify_paths(); - sslContext->set_options(boost::asio::ssl::context::default_workarounds); - if (m_config.get_ssl_context_callback()) - { - m_config.get_ssl_context_callback()(*sslContext); - } - if (m_config.validate_certificates()) - { - sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer); - } - else - { - sslContext->set_verify_mode(boost::asio::ssl::context::verify_none); - } + m_config.get_ssl_context_callback()(*sslContext); + } + if (m_config.validate_certificates()) + { + sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer); + } + else + { + sslContext->set_verify_mode(boost::asio::ssl::context::verify_none); + } #ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE - m_openssl_failed = false; + m_openssl_failed = false; #endif - sslContext->set_verify_callback( - [this](bool preverified, boost::asio::ssl::verify_context& verifyCtx) - { + sslContext->set_verify_callback( + [this](bool preverified, boost::asio::ssl::verify_context& verifyCtx) + { #ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE - // Attempt to use platform certificate validation when it is available: - // If OpenSSL fails we will doing verification at the end using the whole certificate chain, - // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the - // certificate chain. - using namespace web::http::client::details; - if (!preverified) - { - m_openssl_failed = true; - } - if (m_openssl_failed) + // Attempt to use platform certificate validation when it is available: + // If OpenSSL fails we will doing verification at the end using the whole certificate chain, + // so wait until the 'leaf' cert. For now return true so OpenSSL continues down the + // certificate chain. + using namespace web::http::client::details; + if (!preverified) + { + m_openssl_failed = true; + } + if (m_openssl_failed) + { + if (!http::client::details::is_end_certificate_in_chain(verifyCtx)) { - if (!http::client::details::is_end_certificate_in_chain(verifyCtx)) - { - // Continue until we get the end certificate. - return true; - } - - auto chainFunc = - [this](const std::shared_ptr& cert_info) - { return m_config.invoke_certificate_chain_callback(cert_info); }; - - return http::client::details::verify_cert_chain_platform_specific( - verifyCtx, utility::conversions::to_utf8string(m_uri.host()), chainFunc); + // Continue until we get the end certificate. + return true; } + + auto chainFunc = + [this](const std::shared_ptr& cert_info) + { return m_config.invoke_certificate_chain_callback(cert_info); }; + + return http::client::details::verify_cert_chain_platform_specific( + verifyCtx, utility::conversions::to_utf8string(m_uri.host()), chainFunc); + } #endif - boost::asio::ssl::rfc2818_verification rfc2818( - utility::conversions::to_utf8string(m_uri.host())); - if (!rfc2818(preverified, verifyCtx)) - { - return false; - } + boost::asio::ssl::rfc2818_verification rfc2818( + utility::conversions::to_utf8string(m_uri.host())); + if (!rfc2818(preverified, verifyCtx)) + { + return false; + } - auto info = std::make_shared( - utility::conversions::to_utf8string(m_uri.host()), - get_X509_cert_chain_encoded_data(verifyCtx)); - info->verified = true; + auto info = std::make_shared( + utility::conversions::to_utf8string(m_uri.host()), + get_X509_cert_chain_encoded_data(verifyCtx)); + info->verified = true; - return m_config.invoke_certificate_chain_callback(info); - }); + return m_config.invoke_certificate_chain_callback(info); + }); #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - // OpenSSL stores some per thread state that never will be cleaned up until - // the dll is unloaded. If static linking, like we do, the state isn't cleaned up - // at all and will be reported as leaks. - // See http://www.openssl.org/support/faq.html#PROG13 - // This is necessary here because it is called on the user's thread calling connect(...) - // eventually through websocketpp::client::get_connection(...) - ERR_remove_thread_state(nullptr); + // OpenSSL stores some per thread state that never will be cleaned up until + // the dll is unloaded. If static linking, like we do, the state isn't cleaned up + // at all and will be reported as leaks. + // See http://www.openssl.org/support/faq.html#PROG13 + // This is necessary here because it is called on the user's thread calling connect(...) + // eventually through websocketpp::client::get_connection(...) + ERR_remove_thread_state(nullptr); #endif - return sslContext; - }); + return sslContext; + }); // Options specific to underlying socket. - client.set_socket_init_handler( - [this](websocketpp::connection_hdl, boost::asio::ssl::stream& ssl_stream) + client.set_socket_init_handler([this](websocketpp::connection_hdl, boost::asio::ssl::stream& ssl_stream) { + // Support for SNI. + if (m_config.is_sni_enabled()) { - // Support for SNI. - if (m_config.is_sni_enabled()) + // If user specified server name is empty default to use URI host name. + if (!m_config.server_name().empty()) { - // If user specified server name is empty default to use URI host name. - if (!m_config.server_name().empty()) - { - // OpenSSL runs the string parameter through a macro casting away const with a C style cast. - // Do a C++ cast ourselves to avoid warnings. - SSL_set_tlsext_host_name(ssl_stream.native_handle(), - const_cast(m_config.server_name().c_str())); - } - else - { - const auto& server_name = utility::conversions::to_utf8string(m_uri.host()); - SSL_set_tlsext_host_name(ssl_stream.native_handle(), - const_cast(server_name.c_str())); - } + // OpenSSL runs the string parameter through a macro casting away const with a C style cast. + // Do a C++ cast ourselves to avoid warnings. + SSL_set_tlsext_host_name(ssl_stream.native_handle(), + const_cast(m_config.server_name().c_str())); } - }); + else + { + const auto& server_name = utility::conversions::to_utf8string(m_uri.host()); + SSL_set_tlsext_host_name(ssl_stream.native_handle(), + const_cast(server_name.c_str())); + } + } + }); return connect_impl(); } @@ -312,89 +308,77 @@ class wspp_callback_client : public websocket_client_callback_impl, client.start_perpetual(); _ASSERTE(m_state == CREATED); - client.set_open_handler( - [this](websocketpp::connection_hdl) + client.set_open_handler([this](websocketpp::connection_hdl) { + _ASSERTE(m_state == CONNECTING); + m_state = CONNECTED; + m_connect_tce.set(); + }); + + client.set_fail_handler([this](websocketpp::connection_hdl con_hdl) { + _ASSERTE(m_state == CONNECTING); + this->shutdown_wspp_impl(con_hdl, true); + }); + + client.set_message_handler([this](websocketpp::connection_hdl, const websocketpp::config::asio_client::message_type::ptr& msg) { + if (m_external_message_handler) { - _ASSERTE(m_state == CONNECTING); - m_state = CONNECTED; - m_connect_tce.set(); - }); + _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); + websocket_incoming_message incoming_msg; - client.set_fail_handler( - [this](websocketpp::connection_hdl con_hdl) - { - _ASSERTE(m_state == CONNECTING); - this->shutdown_wspp_impl(con_hdl, true); - }); - - client.set_message_handler( - [this](websocketpp::connection_hdl, const websocketpp::config::asio_client::message_type::ptr& msg) - { - if (m_external_message_handler) + switch (msg->get_opcode()) { - _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); - websocket_incoming_message incoming_msg; - - switch (msg->get_opcode()) - { - case websocketpp::frame::opcode::binary: - incoming_msg.m_msg_type = websocket_message_type::binary_message; - break; - case websocketpp::frame::opcode::text: - incoming_msg.m_msg_type = websocket_message_type::text_message; - break; - default: - // Unknown message type. Since both websocketpp and our code use the RFC codes, we'll just - // pass it on to the user. - incoming_msg.m_msg_type = static_cast(msg->get_opcode()); - break; - } + case websocketpp::frame::opcode::binary: + incoming_msg.m_msg_type = websocket_message_type::binary_message; + break; + case websocketpp::frame::opcode::text: + incoming_msg.m_msg_type = websocket_message_type::text_message; + break; + default: + // Unknown message type. Since both websocketpp and our code use the RFC codes, we'll just + // pass it on to the user. + incoming_msg.m_msg_type = static_cast(msg->get_opcode()); + break; + } - // 'move' the payload into a container buffer to avoid any copies. - auto& payload = msg->get_raw_payload(); - incoming_msg.m_body = concurrency::streams::container_buffer(std::move(payload)); + // 'move' the payload into a container buffer to avoid any copies. + auto& payload = msg->get_raw_payload(); + incoming_msg.m_body = concurrency::streams::container_buffer(std::move(payload)); - m_external_message_handler(incoming_msg); - } - }); + m_external_message_handler(incoming_msg); + } + }); - client.set_ping_handler( - [this](websocketpp::connection_hdl, const std::string& msg) + client.set_ping_handler([this](websocketpp::connection_hdl, const std::string& msg) { + if (m_external_message_handler) { - if (m_external_message_handler) - { - _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); - websocket_incoming_message incoming_msg; + _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); + websocket_incoming_message incoming_msg; - incoming_msg.m_msg_type = websocket_message_type::ping; - incoming_msg.m_body = concurrency::streams::container_buffer(msg); + incoming_msg.m_msg_type = websocket_message_type::ping; + incoming_msg.m_body = concurrency::streams::container_buffer(msg); - m_external_message_handler(incoming_msg); - } - return true; - }); + m_external_message_handler(incoming_msg); + } + return true; + }); - client.set_pong_handler( - [this](websocketpp::connection_hdl, const std::string& msg) + client.set_pong_handler([this](websocketpp::connection_hdl, const std::string& msg) { + if (m_external_message_handler) { - if (m_external_message_handler) - { - _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); - websocket_incoming_message incoming_msg; + _ASSERTE(m_state >= CONNECTED && m_state < CLOSED); + websocket_incoming_message incoming_msg; - incoming_msg.m_msg_type = websocket_message_type::pong; - incoming_msg.m_body = concurrency::streams::container_buffer(msg); + incoming_msg.m_msg_type = websocket_message_type::pong; + incoming_msg.m_body = concurrency::streams::container_buffer(msg); - m_external_message_handler(incoming_msg); - } - }); + m_external_message_handler(incoming_msg); + } + }); - client.set_close_handler( - [this](websocketpp::connection_hdl con_hdl) - { - _ASSERTE(m_state != CLOSED); - this->shutdown_wspp_impl(con_hdl, false); - }); + client.set_close_handler([this](websocketpp::connection_hdl con_hdl) { + _ASSERTE(m_state != CLOSED); + this->shutdown_wspp_impl(con_hdl, false); + }); // Set User Agent specified by the user. This needs to happen before any connection is created const auto& headers = m_config.headers(); @@ -468,25 +452,23 @@ class wspp_callback_client : public websocket_client_callback_impl, client.connect(con); { std::lock_guard lock(m_wspp_client_lock); - m_thread = std::thread( - [&client]() - { + m_thread = std::thread([&client]() { #if defined(__ANDROID__) - crossplat::get_jvm_env(); + crossplat::get_jvm_env(); #endif - client.run(); + client.run(); #if defined(__ANDROID__) - crossplat::JVM.load()->DetachCurrentThread(); + crossplat::JVM.load()->DetachCurrentThread(); #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - // OpenSSL stores some per thread state that never will be cleaned up until - // the dll is unloaded. If static linking, like we do, the state isn't cleaned up - // at all and will be reported as leaks. - // See http://www.openssl.org/support/faq.html#PROG13 - ERR_remove_thread_state(nullptr); + // OpenSSL stores some per thread state that never will be cleaned up until + // the dll is unloaded. If static linking, like we do, the state isn't cleaned up + // at all and will be reported as leaks. + // See http://www.openssl.org/support/faq.html#PROG13 + ERR_remove_thread_state(nullptr); #endif - }); + }); } // unlock return pplx::create_task(m_connect_tce); } @@ -558,20 +540,17 @@ class wspp_callback_client : public websocket_client_callback_impl, // The stream needs to be buffered. auto is_buf_istream = is_buf.create_istream(); msg.m_body = concurrency::streams::container_buffer>(); - is_buf_istream.read_to_end(msg.m_body) - .then( - [this_client, msg](pplx::task t) mutable - { - try - { - msg.m_length = t.get(); - this_client->send_msg(msg); - } - catch (...) - { - msg.signal_body_sent(std::current_exception()); - } - }); + is_buf_istream.read_to_end(msg.m_body).then([this_client, msg](pplx::task t) mutable { + try + { + msg.m_length = t.get(); + this_client->send_msg(msg); + } + catch (...) + { + msg.signal_body_sent(std::current_exception()); + } + }); // We have postponed the call to send_msg() until after the data is buffered. return; } @@ -600,16 +579,12 @@ class wspp_callback_client : public websocket_client_callback_impl, // Allocate buffer to hold the data to be read from the stream. sp_allocated.reset(new uint8_t[length], [=](uint8_t* p) { delete[] p; }); - read_task = - is_buf.getn(sp_allocated.get(), length) - .then( - [length](size_t bytes_read) - { - if (bytes_read != length) - { - throw websocket_exception("Failed to read required length of data from the stream."); - } - }); + read_task = is_buf.getn(sp_allocated.get(), length).then([length](size_t bytes_read) { + if (bytes_read != length) + { + throw websocket_exception("Failed to read required length of data from the stream."); + } + }); } else { @@ -620,72 +595,67 @@ class wspp_callback_client : public websocket_client_callback_impl, } read_task - .then( - [this_client, msg, sp_allocated, length]() + .then([this_client, msg, sp_allocated, length]() { + std::lock_guard lock(this_client->m_wspp_client_lock); + if (this_client->m_state > CONNECTED) { - std::lock_guard lock(this_client->m_wspp_client_lock); - if (this_client->m_state > CONNECTED) - { - // The client has already been closed. - throw websocket_exception("Websocket connection is closed."); - } + // The client has already been closed. + throw websocket_exception("Websocket connection is closed."); + } - websocketpp::lib::error_code ec; - if (this_client->m_client->is_tls_client()) - { - this_client->send_msg_impl( - this_client, msg, sp_allocated, length, ec); - } - else - { - this_client->send_msg_impl( - this_client, msg, sp_allocated, length, ec); - } - return ec; - }) - .then( - [this_client, msg, is_buf, acquired, sp_allocated, length]( - pplx::task previousTask) mutable + websocketpp::lib::error_code ec; + if (this_client->m_client->is_tls_client()) { - std::exception_ptr eptr; - try - { - // Catch exceptions from previous tasks, if any and convert it to websocket exception. - const auto& ec = previousTask.get(); - if (ec.value() != 0) - { - eptr = std::make_exception_ptr( - websocket_exception(ec, build_error_msg(ec, "sending message"))); - } - } - catch (...) + this_client->send_msg_impl( + this_client, msg, sp_allocated, length, ec); + } + else + { + this_client->send_msg_impl( + this_client, msg, sp_allocated, length, ec); + } + return ec; + }) + .then([this_client, msg, is_buf, acquired, sp_allocated, length](pplx::task previousTask) mutable { + std::exception_ptr eptr; + try + { + // Catch exceptions from previous tasks, if any and convert it to websocket exception. + const auto& ec = previousTask.get(); + if (ec.value() != 0) { - eptr = std::current_exception(); + eptr = std::make_exception_ptr( + websocket_exception(ec, build_error_msg(ec, "sending message"))); } + } + catch (...) + { + eptr = std::current_exception(); + } - if (acquired) - { - is_buf.release(sp_allocated.get(), length); - } + if (acquired) + { + is_buf.release(sp_allocated.get(), length); + } - // Set the send_task_completion_event after calling release. - if (eptr) - { - msg.signal_body_sent(eptr); - } - else - { - msg.signal_body_sent(); - } + // Set the send_task_completion_event after calling release. + if (eptr) + { + msg.signal_body_sent(eptr); + } + else + { + msg.signal_body_sent(); + } - websocket_outgoing_message next_msg; - bool msg_pending = this_client->m_out_queue.pop_and_peek(next_msg); + websocket_outgoing_message next_msg; + bool msg_pending = this_client->m_out_queue.pop_and_peek(next_msg); - if (msg_pending) - { - this_client->send_msg(next_msg); - } - }); + if (msg_pending) + { + this_client->send_msg(next_msg); + } + }); } pplx::task close() @@ -732,31 +702,29 @@ class wspp_callback_client : public websocket_client_callback_impl, client.stop_perpetual(); // Can't join thread directly since it is the current thread. - pplx::create_task([] {}).then( - [this, connecting, ec, closeCode, reason]() mutable + pplx::create_task([] {}).then([this, connecting, ec, closeCode, reason]() mutable { { + std::lock_guard lock(m_wspp_client_lock); + if (m_thread.joinable()) { - std::lock_guard lock(m_wspp_client_lock); - if (m_thread.joinable()) - { - m_thread.join(); - } - } // unlock - - if (connecting) - { - websocket_exception exc(ec, build_error_msg(ec, "set_fail_handler")); - m_connect_tce.set_exception(exc); + m_thread.join(); } - if (m_external_close_handler) - { - m_external_close_handler( - static_cast(closeCode), utility::conversions::to_string_t(reason), ec); - } - // Making a local copy of the TCE prevents it from being destroyed along with "this" - auto tceref = m_close_tce; - tceref.set(); - }); + } // unlock + + if (connecting) + { + websocket_exception exc(ec, build_error_msg(ec, "set_fail_handler")); + m_connect_tce.set_exception(exc); + } + if (m_external_close_handler) + { + m_external_close_handler( + static_cast(closeCode), utility::conversions::to_string_t(reason), ec); + } + // Making a local copy of the TCE prevents it from being destroyed along with "this" + auto tceref = m_close_tce; + tceref.set(); + }); } template @@ -820,7 +788,7 @@ class wspp_callback_client : public websocket_client_callback_impl, // after construction based on the URI. struct websocketpp_client_base { - virtual ~websocketpp_client_base() CPPREST_NOEXCEPT { } + virtual ~websocketpp_client_base() CPPREST_NOEXCEPT {} template websocketpp::client& client() { @@ -839,14 +807,14 @@ class wspp_callback_client : public websocket_client_callback_impl, }; struct websocketpp_client : websocketpp_client_base { - ~websocketpp_client() CPPREST_NOEXCEPT { } + ~websocketpp_client() CPPREST_NOEXCEPT {} websocketpp::client& non_tls_client() override { return m_client; } bool is_tls_client() const override { return false; } websocketpp::client m_client; }; struct websocketpp_tls_client : websocketpp_client_base { - ~websocketpp_tls_client() CPPREST_NOEXCEPT { } + ~websocketpp_tls_client() CPPREST_NOEXCEPT {} websocketpp::client& tls_client() override { return m_client; } bool is_tls_client() const override { return true; } websocketpp::client m_client; @@ -899,7 +867,3 @@ websocket_callback_client::websocket_callback_client(websocket_client_config con } // namespace web #endif -<<<<<<< HEAD -======= - ->>>>>>> 917ee0eb (Add support for cert-pinning on Windows and Mac.) diff --git a/Release/tests/functional/http/client/connections_and_errors.cpp b/Release/tests/functional/http/client/connections_and_errors.cpp index df85d5ac75..4e860b9e70 100644 --- a/Release/tests/functional/http/client/connections_and_errors.cpp +++ b/Release/tests/functional/http/client/connections_and_errors.cpp @@ -276,14 +276,12 @@ SUITE(connections_and_errors) streams::producer_consumer_buffer buf; - listener.support( - [buf](http_request request) - { - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - response.headers().add(header_names::connection, U("close")); - request.reply(response); - }); + listener.support([buf](http_request request) { + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + response.headers().add(header_names::connection, U("close")); + request.reply(response); + }); { http_client_config config; @@ -307,14 +305,12 @@ SUITE(connections_and_errors) streams::producer_consumer_buffer buf; - listener.support( - [buf](http_request request) - { - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - response.headers().add(header_names::connection, U("close")); - request.reply(response); - }); + listener.support([buf](http_request request) { + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + response.headers().add(header_names::connection, U("close")); + request.reply(response); + }); { http_client_config config; @@ -354,20 +350,18 @@ SUITE(connections_and_errors) pplx::cancellation_token_source source; pplx::extensibility::event_t ev; - listener.support( - [&](http_request request) - { - streams::producer_consumer_buffer buf; - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - request.reply(response); - ev.wait(); - buf.putc('a').wait(); - buf.putc('b').wait(); - buf.putc('c').wait(); - buf.putc('d').wait(); - buf.close(std::ios::out).wait(); - }); + listener.support([&](http_request request) { + streams::producer_consumer_buffer buf; + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + request.reply(response); + ev.wait(); + buf.putc('a').wait(); + buf.putc('b').wait(); + buf.putc('c').wait(); + buf.putc('d').wait(); + buf.close(std::ios::out).wait(); + }); auto responseTask = c.request(methods::GET, source.get_token()); http_response response = responseTask.get(); @@ -449,21 +443,19 @@ SUITE(connections_and_errors) pplx::extensibility::event_t ev; pplx::extensibility::event_t ev2; - listener.support( - [&](http_request request) - { - streams::producer_consumer_buffer buf; - http_response response(200); - response.set_body(streams::istream(buf), U("text/plain")); - request.reply(response); - buf.putc('a').wait(); - buf.putc('b').wait(); - ev.set(); - ev2.wait(); - buf.putc('c').wait(); - buf.putc('d').wait(); - buf.close(std::ios::out).wait(); - }); + listener.support([&](http_request request) { + streams::producer_consumer_buffer buf; + http_response response(200); + response.set_body(streams::istream(buf), U("text/plain")); + request.reply(response); + buf.putc('a').wait(); + buf.putc('b').wait(); + ev.set(); + ev2.wait(); + buf.putc('c').wait(); + buf.putc('d').wait(); + buf.close(std::ios::out).wait(); + }); auto response = c.request(methods::GET, source.get_token()).get(); ev.wait(); From e6f6e45d16499cb6ee0d93ce31674525d11c5729 Mon Sep 17 00:00:00 2001 From: Atul Bagga Date: Thu, 28 Oct 2021 17:27:18 +0530 Subject: [PATCH 8/8] Commit changes for build --- .../src/http/client/http_client_winhttp.cpp | 8 +++---- .../src/http/client/x509_cert_utilities.cpp | 23 ++++--------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index f2e449d1ed..28e51ea3b2 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -2010,6 +2010,9 @@ class winhttp_client final : public _http_client_communicator } case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: { + // Todo Check where to put this. atbagga + p_request_context->on_send_request_validate_cn(); + // get actual URL which might be different from the original one due to redirection etc. DWORD urlSize {0}; WinHttpQueryOption(hRequestHandle, WINHTTP_OPTION_URL, NULL, &urlSize); @@ -2163,11 +2166,6 @@ class winhttp_client final : public _http_client_communicator } 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( diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index 4e9ae6bb0d..d1af9284bd 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -41,9 +41,11 @@ namespace client { namespace details { -static bool verify_X509_cert_chain(const std::vector& certChain, const std::string& hostName); +static bool verify_X509_cert_chain( + const std::vector& certChain, + const std::string& hostName, + const CertificateChainFunction& certInfoFunc); -bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verifyCtx, const std::string& hostName) #if defined(_WIN32) #include #include @@ -51,7 +53,7 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context& verif #include - bool is_end_certificate_in_chain(boost::asio::ssl::verify_context& verifyCtx) +bool is_end_certificate_in_chain(boost::asio::ssl::verify_context& verifyCtx) { X509_STORE_CTX* storeContext = verifyCtx.native_handle(); int currentDepth = X509_STORE_CTX_get_error_depth(storeContext); @@ -480,21 +482,6 @@ std::vector> get_X509_cert_chain_encoded_data(boost:: } #if defined(_WIN32) -namespace -{ -// Helper RAII unique_ptrs to free Windows structures. -struct cert_free_certificate_context -{ - void operator()(const CERT_CONTEXT* ctx) const { CertFreeCertificateContext(ctx); } -}; -typedef std::unique_ptr cert_context; -struct cert_free_certificate_chain -{ - void operator()(const CERT_CHAIN_CONTEXT* chain) const { CertFreeCertificateChain(chain); } -}; -typedef std::unique_ptr chain_context; -} // namespace - static std::shared_ptr build_certificate_info_ptr(const chain_context& chain, const std::string& hostName, bool isVerified)