diff --git a/Release/CMakeLists.txt b/Release/CMakeLists.txt index e229106951..3467977980 100644 --- a/Release/CMakeLists.txt +++ b/Release/CMakeLists.txt @@ -160,6 +160,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") message("-- Setting msvc options") set(WARNINGS) + add_compile_options(/bigobj) else() message("-- Unknown compiler, success is doubtful.") message("CMAKE_CXX_COMPILER_ID=${CMAKE_CXX_COMPILER_ID}") diff --git a/Release/include/cpprest/details/http_client_impl.h b/Release/include/cpprest/details/http_client_impl.h deleted file mode 100644 index 2f88472a50..0000000000 --- a/Release/include/cpprest/details/http_client_impl.h +++ /dev/null @@ -1,466 +0,0 @@ -/*** -* ==++== -* -* 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. -* -* ==--== -* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -* -* HTTP Library: Client-side APIs, non-public declarations used in the implementation. -* -* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk -* -* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -****/ -#pragma once - -#include "cpprest/details/basic_types.h" -#include "cpprest/details/http_helpers.h" - -#ifdef _WIN32 -# define CRLF _XPLATSTR("\r\n") -#else -# define CRLF std::string("\r\n") -#endif - -namespace web { namespace http { namespace client { namespace details -{ - -#ifdef _WIN32 -static const utility::char_t * get_with_body = _XPLATSTR("A GET or HEAD request should not have an entity body."); - -// Helper function to trim leading and trailing null characters from a string. -static void trim_nulls(utility::string_t &str) -{ - size_t index; - for(index = 0; index < str.size() && str[index] == 0; ++index); - str.erase(0, index); - for(index = str.size(); index > 0 && str[index - 1] == 0; --index); - str.erase(index); -} - -#endif - -// Flatten the http_headers into a name:value pairs separated by a carriage return and line feed. -static utility::string_t flatten_http_headers(const http_headers &headers) -{ - utility::string_t flattened_headers; - for(auto iter = headers.begin(); iter != headers.end(); ++iter) - { - flattened_headers.append(iter->first); - flattened_headers.push_back(':'); - flattened_headers.append(iter->second); - flattened_headers.append(CRLF); - } - return flattened_headers; -} - -#ifdef _WIN32 -/// -/// Parses a string containing Http headers. -/// -static void parse_headers_string(_Inout_z_ utf16char *headersStr, http_headers &headers) -{ - utf16char *context = nullptr; - utf16char *line = wcstok_s(headersStr, CRLF, &context); - while(line != nullptr) - { - const utility::string_t header_line(line); - const size_t colonIndex = header_line.find_first_of(_XPLATSTR(":")); - if(colonIndex != utility::string_t::npos) - { - utility::string_t key = header_line.substr(0, colonIndex); - utility::string_t value = header_line.substr(colonIndex + 1, header_line.length() - colonIndex - 1); - http::details::trim_whitespace(key); - http::details::trim_whitespace(value); - headers.add(key, value); - } - line = wcstok_s(nullptr, CRLF, &context); - } -} -#endif - -class _http_client_communicator; - -// Request context encapsulating everything necessary for creating and responding to a request. -class request_context -{ -public: - - // Destructor to clean up any held resources. - virtual ~request_context() - { - } - - void complete_headers() - { - // We have already read (and transmitted) the request body. Should we explicitly close the stream? - // Well, there are test cases that assumes that the istream is valid when t receives the response! - // For now, we will drop our reference which will close the stream if the user doesn't have one. - m_request.set_body(Concurrency::streams::istream()); - m_request_completion.set(m_response); - } - - /// - /// Completes this request, setting the underlying task completion event, and cleaning up the handles - /// - void complete_request(utility::size64_t body_size) - { - m_response._get_impl()->_complete(body_size); - - finish(); - } - - void report_error(unsigned long error_code, const std::string &errorMessage) - { - report_exception(http_exception(static_cast(error_code), errorMessage)); - } - -#ifdef _WIN32 - void report_error(unsigned long error_code, const std::wstring &errorMessage) - { - report_exception(http_exception(static_cast(error_code), errorMessage)); - } -#endif - - template - void report_exception(const _ExceptionType &e) - { - report_exception(std::make_exception_ptr(e)); - } - - virtual void report_exception(std::exception_ptr exceptionPtr) - { - auto response_impl = m_response._get_impl(); - - // If cancellation has been triggered then ignore any errors. - if(m_request._cancellation_token().is_canceled()) - { - exceptionPtr = std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category())); - } - - // First try to complete the headers with an exception. - if(m_request_completion.set_exception(exceptionPtr)) - { - // Complete the request with no msg body. The exception - // should only be propagated to one of the tce. - response_impl->_complete(0); - } - else - { - // Complete the request with an exception - response_impl->_complete(0, exceptionPtr); - } - - finish(); - } - - virtual concurrency::streams::streambuf _get_readbuffer() - { - auto instream = m_request.body(); - - _ASSERTE((bool)instream); - return instream.streambuf(); - } - - concurrency::streams::streambuf _get_writebuffer() - { - auto outstream = m_response._get_impl()->outstream(); - - _ASSERTE((bool)outstream); - return outstream.streambuf(); - } - - // Reference to the http_client implementation. - std::shared_ptr<_http_client_communicator> m_http_client; - - // request/response pair. - http_request m_request; - http_response m_response; - - utility::size64_t m_uploaded; - utility::size64_t m_downloaded; - - // task completion event to signal request is completed. - pplx::task_completion_event m_request_completion; - - // Registration for cancellation notification if enabled. - pplx::cancellation_token_registration m_cancellationRegistration; - -protected: - - request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request) - : m_http_client(client), - m_request(request), - m_uploaded(0), - m_downloaded(0) - { - auto responseImpl = m_response._get_impl(); - - // Copy the user specified output stream over to the response - responseImpl->set_outstream(request._get_impl()->_response_stream(), false); - - // Prepare for receiving data from the network. Ideally, this should be done after - // we receive the headers and determine that there is a response body. We will do it here - // since it is not immediately apparent where that would be in the callback handler - responseImpl->_prepare_to_receive_data(); - } - - virtual void finish(); -}; - -// -// Interface used by client implementations. Concrete implementations are responsible for -// sending HTTP requests and receiving the responses. -// -class _http_client_communicator -{ -public: - - // Destructor to clean up any held resources. - virtual ~_http_client_communicator() {} - - // Asynchronously send a HTTP request and process the response. - void async_send_request(const std::shared_ptr &request) - { - if(m_client_config.guarantee_order()) - { - // Send to call block to be processed. - push_request(request); - } - else - { - // Schedule a task to start sending. - pplx::create_task([this, request] - { - open_and_send_request(request); - }); - } - } - - void finish_request() - { - // If guarantee order is specified we don't need to do anything. - if(m_client_config.guarantee_order()) - { - pplx::extensibility::scoped_critical_section_t l(m_open_lock); - - --m_scheduled; - - if( !m_requests_queue.empty()) - { - auto request = m_requests_queue.front(); - m_requests_queue.pop(); - - // Schedule a task to start sending. - pplx::create_task([this, request] - { - open_and_send_request(request); - }); - } - } - } - - const http_client_config& client_config() const - { - return m_client_config; - } - - const uri & base_uri() const - { - return m_uri; - } - -protected: - _http_client_communicator(http::uri address, http_client_config client_config) - : m_uri(std::move(address)), m_client_config(std::move(client_config)), m_opened(false), m_scheduled(0) - { - } - - // Method to open client. - virtual unsigned long open() = 0; - - // HTTP client implementations must implement send_request. - virtual void send_request(_In_ const std::shared_ptr &request) = 0; - - // URI to connect to. - const http::uri m_uri; - -private: - - http_client_config m_client_config; - - bool m_opened; - - pplx::extensibility::critical_section_t m_open_lock; - - // Wraps opening the client around sending a request. - void open_and_send_request(const std::shared_ptr &request) - { - // First see if client needs to be opened. - auto error = open_if_required(); - - if (error != 0) - { - // Failed to open - request->report_error(error, _XPLATSTR("Open failed")); - - // DO NOT TOUCH the this pointer after completing the request - // This object could be freed along with the request as it could - // be the last reference to this object - return; - } - - send_request(request); - } - - unsigned long open_if_required() - { - unsigned long error = 0; - - if(!m_opened) - { - pplx::extensibility::scoped_critical_section_t l(m_open_lock); - - // Check again with the lock held - if (!m_opened) - { - error = open(); - - if (error == 0) - { - m_opened = true; - } - } - } - - return error; - } - - void push_request(const std::shared_ptr &request) - { - pplx::extensibility::scoped_critical_section_t l(m_open_lock); - - if(++m_scheduled == 1) - { - // Schedule a task to start sending. - pplx::create_task([this, request]() - { - open_and_send_request(request); - }); - } - else - { - m_requests_queue.push(request); - } - } - - // Queue used to guarantee ordering of requests, when applicable. - std::queue> m_requests_queue; - int m_scheduled; -}; - -inline void request_context::finish() -{ - // If cancellation is enabled and registration was performed, unregister. - if(m_cancellationRegistration != pplx::cancellation_token_registration()) - { - _ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none()); - m_request._cancellation_token().deregister_callback(m_cancellationRegistration); - } - - m_http_client->finish_request(); -} - -class http_network_handler : public http_pipeline_stage -{ -public: - http_network_handler(const uri &base_uri, const http_client_config &client_config); - - virtual pplx::task propagate(http_request request); - - const std::shared_ptr& http_client_impl() const - { - return m_http_client_impl; - } - -private: - std::shared_ptr<_http_client_communicator> m_http_client_impl; -}; - -// Helper function to check to make sure the uri is valid. -void verify_uri(const uri &uri) -{ - // Some things like proper URI schema are verified by the URI class. - // We only need to check certain things specific to HTTP. - if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https")) - { - throw std::invalid_argument("URI scheme must be 'http' or 'https'"); - } - - if(uri.host().empty()) - { - throw std::invalid_argument("URI must contain a hostname."); - } -} - -} // namespace details - -http_client::http_client(const uri &base_uri) -{ - build_pipeline(base_uri, http_client_config()); -} - -http_client::http_client(const uri &base_uri, const http_client_config &client_config) -{ - build_pipeline(base_uri, client_config); -} - -void http_client::build_pipeline(const uri &base_uri, const http_client_config &client_config) -{ - if (base_uri.scheme().empty()) - { - auto uribuilder = uri_builder(base_uri); - uribuilder.set_scheme(_XPLATSTR("http")); - uri uriWithScheme = uribuilder.to_uri(); - details::verify_uri(uriWithScheme); - m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared(uriWithScheme, client_config)); - } - else - { - details::verify_uri(base_uri); - m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared(base_uri, client_config)); - } - -#if !defined(CPPREST_TARGET_XP) - add_handler(std::static_pointer_cast( - std::make_shared(client_config.oauth1()))); -#endif - - add_handler(std::static_pointer_cast( - std::make_shared(client_config.oauth2()))); -} - -const http_client_config & http_client::client_config() const -{ - auto ph = std::static_pointer_cast(m_pipeline->last_stage()); - return ph->http_client_impl()->client_config(); -} - -const uri & http_client::base_uri() const -{ - auto ph = std::static_pointer_cast(m_pipeline->last_stage()); - return ph->http_client_impl()->base_uri(); -} - -}}} // namespaces diff --git a/Release/include/cpprest/details/http_helpers.h b/Release/include/cpprest/details/http_helpers.h index 3e231f5871..0ef034b606 100644 --- a/Release/include/cpprest/details/http_helpers.h +++ b/Release/include/cpprest/details/http_helpers.h @@ -27,86 +27,23 @@ ****/ #pragma once -#include "cpprest/http_msg.h" +#include "cpprest/details/basic_types.h" namespace web { namespace http { namespace details { - /// - /// Constants for MIME types. - /// - class mime_types - { - public: - #define _MIME_TYPES - #define DAT(a,b) _ASYNCRTIMP const static utility::string_t a; - #include "cpprest/details/http_constants.dat" - #undef _MIME_TYPES - #undef DAT - }; - - /// - /// Constants for charset types. - /// - class charset_types - { - public: - #define _CHARSET_TYPES - #define DAT(a,b) _ASYNCRTIMP const static utility::string_t a; - #include "cpprest/details/http_constants.dat" - #undef _CHARSET_TYPES - #undef DAT - }; - - /// - /// Determines whether or not the given content type is 'textual' according the feature specifications. - /// - bool is_content_type_textual(const utility::string_t &content_type); - - /// - /// Determines whether or not the given content type is JSON according the feature specifications. - /// - bool is_content_type_json(const utility::string_t &content_type); - - /// - /// Parses the given Content-Type header value to get out actual content type and charset. - /// If the charset isn't specified the default charset for the content type will be set. - /// - void parse_content_type_and_charset(const utility::string_t &content_type, utility::string_t &content, utility::string_t &charset); - - /// - /// Gets the default charset for given content type. If the MIME type is not textual or recognized Latin1 will be returned. - /// - utility::string_t get_default_charset(const utility::string_t &content_type); - /// /// Helper function to get the default HTTP reason phrase for a status code. /// utility::string_t get_default_reason_phrase(status_code code); - /// - /// Helper functions to convert a series of bytes from a charset to utf-8 or utf-16. - /// These APIs deal with checking for and handling byte order marker (BOM). - /// - utility::string_t convert_utf16_to_string_t(utf16string src); - utf16string convert_utf16_to_utf16(utf16string src); - std::string convert_utf16_to_utf8(utf16string src); - utility::string_t convert_utf16le_to_string_t(utf16string src, bool erase_bom); - std::string convert_utf16le_to_utf8(utf16string src, bool erase_bom); - utility::string_t convert_utf16be_to_string_t(utf16string src, bool erase_bom); - std::string convert_utf16be_to_utf8(utf16string src, bool erase_bom); - utf16string convert_utf16be_to_utf16le(utf16string src, bool erase_bom); - // simple helper functions to trim whitespace. - _ASYNCRTIMP void __cdecl ltrim_whitespace(utility::string_t &str); - _ASYNCRTIMP void __cdecl rtrim_whitespace(utility::string_t &str); _ASYNCRTIMP void __cdecl trim_whitespace(utility::string_t &str); bool validate_method(const utility::string_t& method); - namespace chunked_encoding { // Transfer-Encoding: chunked support diff --git a/Release/include/cpprest/details/x509_cert_utilities.h b/Release/include/cpprest/details/x509_cert_utilities.h index b9e60df880..5cfa561be5 100644 --- a/Release/include/cpprest/details/x509_cert_utilities.h +++ b/Release/include/cpprest/details/x509_cert_utilities.h @@ -25,7 +25,6 @@ #pragma once -#include #include #if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) @@ -52,13 +51,11 @@ namespace web { namespace http { namespace client { namespace details { /// Using platform specific APIs verifies server certificate. /// Currently implemented to work on iOS, Android, and OS X. /// -/// Boost.ASIO context get certificate chain from. +/// 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_X509_cert_chain(const std::vector &certChain, const std::string &hostName); - }}}} #endif \ No newline at end of file diff --git a/Release/include/cpprest/http_client.h b/Release/include/cpprest/http_client.h index 1e0ce8b73f..0c8d7e9f8c 100644 --- a/Release/include/cpprest/http_client.h +++ b/Release/include/cpprest/http_client.h @@ -280,8 +280,7 @@ class http_client_config } #endif -#ifdef _WIN32 -#if !defined(__cplusplus_winrt) +#if defined(_WIN32) && !defined(__cplusplus_winrt) /// /// Checks if request data buffering is turned on, the default is off. /// @@ -302,7 +301,6 @@ class http_client_config { m_buffer_request = buffer_request; } -#endif #endif /// @@ -399,6 +397,8 @@ class http_client_config #endif }; +class http_pipeline; + /// /// HTTP client class, used to maintain a connection to an HTTP service for an extended session. /// @@ -422,7 +422,7 @@ class http_client /// Note the destructor doesn't necessarily close the connection and release resources. /// The connection is reference counted with the http_responses. /// - ~http_client() CPPREST_NOEXCEPT {} + _ASYNCRTIMP ~http_client() CPPREST_NOEXCEPT; /// /// Gets the base URI. @@ -442,19 +442,14 @@ class http_client /// Adds an HTTP pipeline stage to the client. /// /// A function object representing the pipeline stage. - void add_handler(const std::function(http_request, std::shared_ptr)> &handler) - { - m_pipeline->append(std::make_shared<::web::http::details::function_pipeline_wrapper>(handler)); - } + _ASYNCRTIMP void add_handler(const std::function(http_request, std::shared_ptr)> &handler); + /// /// Adds an HTTP pipeline stage to the client. /// /// A shared pointer to a pipeline stage. - void add_handler(const std::shared_ptr &stage) - { - m_pipeline->append(stage); - } + _ASYNCRTIMP void add_handler(const std::shared_ptr &stage); /// /// Asynchronously sends an HTTP request. @@ -727,11 +722,16 @@ class http_client private: - void build_pipeline(const uri &base_uri, const http_client_config &client_config); - std::shared_ptr<::web::http::http_pipeline> m_pipeline; }; +namespace details { +#if defined(_WIN32) +extern const utility::char_t * get_with_body_err_msg; +#endif + +} + }}} #endif diff --git a/Release/include/cpprest/http_headers.h b/Release/include/cpprest/http_headers.h index 8228d7cccb..388fb4bd1c 100644 --- a/Release/include/cpprest/http_headers.h +++ b/Release/include/cpprest/http_headers.h @@ -328,4 +328,18 @@ class http_headers std::map m_headers; }; +namespace details { + + /// + /// Serialize the http_headers into name:value pairs separated by a carriage return and line feed. + /// + utility::string_t flatten_http_headers(const http_headers &headers); +#if defined(_WIN32) + /// + /// Parses a string containing Http headers. + /// + void parse_headers_string(_Inout_z_ utf16char *headersStr, http_headers &headers); +#endif +} + }} \ No newline at end of file diff --git a/Release/include/cpprest/http_msg.h b/Release/include/cpprest/http_msg.h index 9ad32d937b..b2721f9ebc 100644 --- a/Release/include/cpprest/http_msg.h +++ b/Release/include/cpprest/http_msg.h @@ -89,6 +89,36 @@ class status_codes #undef DAT }; +namespace details { + +/// +/// Constants for MIME types. +/// +class mime_types +{ +public: +#define _MIME_TYPES +#define DAT(a,b) _ASYNCRTIMP const static utility::string_t a; +#include "cpprest/details/http_constants.dat" +#undef _MIME_TYPES +#undef DAT +}; + +/// +/// Constants for charset types. +/// +class charset_types +{ +public: +#define _CHARSET_TYPES +#define DAT(a,b) _ASYNCRTIMP const static utility::string_t a; +#include "cpprest/details/http_constants.dat" +#undef _CHARSET_TYPES +#undef DAT +}; + +} + /// Message direction namespace message_direction { @@ -346,7 +376,7 @@ class _http_server_context /// /// Internal representation of an HTTP response. /// -class _http_response : public http::details::http_msg_base +class _http_response final : public http::details::http_msg_base { public: _http_response() : m_status_code((std::numeric_limits::max)()) { } @@ -458,7 +488,7 @@ class http_response /// Extracts the body of the response message as a string value, checking that the content type is a MIME text type. /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. /// - /// If true, ignores the Content-Type header and assumes UTF-8. + /// If true, ignores the Content-Type header and assumes text. /// String containing body of the message. pplx::task extract_string(bool ignore_content_type = false) const { @@ -470,7 +500,7 @@ class http_response /// Extracts the body of the response message as a UTF-8 string value, checking that the content type is a MIME text type. /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. /// - /// If true, ignores the Content-Type header and assumes UTF-8. + /// If true, ignores the Content-Type header and assumes text. /// String containing body of the message. pplx::task extract_utf8string(bool ignore_content_type = false) const { @@ -482,7 +512,7 @@ class http_response /// Extracts the body of the response message as a UTF-16 string value, checking that the content type is a MIME text type. /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. /// - /// If true, ignores the Content-Type header and assumes UTF-16. + /// If true, ignores the Content-Type header and assumes text. /// String containing body of the message. pplx::task extract_utf16string(bool ignore_content_type = false) const { @@ -494,7 +524,7 @@ class http_response /// Extracts the body of the response message into a json value, checking that the content type is application/json. /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. /// - /// If true, ignores the Content-Type header and assumes UTF-8. + /// If true, ignores the Content-Type header and assumes json. /// JSON value from the body of this message. pplx::task extract_json(bool ignore_content_type = false) const { @@ -677,7 +707,7 @@ namespace details { /// /// Internal representation of an HTTP request message. /// -class _http_request : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request> +class _http_request final : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request> { public: @@ -1288,6 +1318,10 @@ class http_request std::shared_ptr _m_impl; }; +namespace client { +class http_pipeline; +} + /// /// HTTP client handler class, used to represent an HTTP pipeline stage. /// @@ -1303,9 +1337,12 @@ class http_pipeline_stage : public std::enable_shared_from_this /// Runs this stage against the given request and passes onto the next stage. @@ -1316,10 +1353,6 @@ class http_pipeline_stage : public std::enable_shared_from_this /// Gets the next stage in the pipeline. /// @@ -1333,13 +1366,14 @@ class http_pipeline_stage : public std::enable_shared_from_this /// A shared pointer to a pipeline stage. + CASABLANCA_DEPRECATED("This api is redundant. Use 'shared_from_this()' directly instead.") std::shared_ptr current_stage() { return this->shared_from_this(); } private: - friend class http_pipeline; + friend class ::web::http::client::http_pipeline; void set_next_stage(const std::shared_ptr &next) { @@ -1348,116 +1382,6 @@ class http_pipeline_stage : public std::enable_shared_from_this m_next_stage; - // No copy or assignment. - http_pipeline_stage & operator=(const http_pipeline_stage &); - http_pipeline_stage(const http_pipeline_stage &); -}; - -namespace details { - -class function_pipeline_wrapper : public http::http_pipeline_stage -{ -public: - function_pipeline_wrapper(std::function(http_request, std::shared_ptr)> handler) : m_handler(handler) - { - } - - virtual pplx::task propagate(http_request request) override - { - return m_handler(request, next_stage()); - } -private: - - std::function(http_request, std::shared_ptr)> m_handler; -}; - -} // namespace details - -/// -/// -/// -class http_pipeline -{ -public: - - /// - /// Create an http pipeline that consists of a linear chain of stages - /// - /// The final stage - static std::shared_ptr create_pipeline(const std::shared_ptr &last) - { - return std::shared_ptr(new http_pipeline(last)); - } - - /// - /// Initiate an http request into the pipeline - /// - /// Http request - pplx::task propagate(http_request request) - { - std::shared_ptr first; - { - pplx::extensibility::scoped_recursive_lock_t l(m_lock); - first = (m_stages.size() > 0) ? m_stages[0] : m_last_stage; - } - return first->propagate(request); - } - - /// - /// Adds an HTTP pipeline stage to the pipeline. - /// - /// A pipeline stage. - void append(const std::shared_ptr &stage) - { - pplx::extensibility::scoped_recursive_lock_t l(m_lock); - - if (m_stages.size() > 0) - { - std::shared_ptr penultimate = m_stages[m_stages.size()-1]; - penultimate->set_next_stage(stage); - } - stage->set_next_stage(m_last_stage); - - m_stages.push_back(stage); - } - - /// - /// Sets the last stage of the pipeline. - /// - /// Shared pointer to pipeline stage to set as the last. - void set_last_stage(const std::shared_ptr &last) - { - m_last_stage = last; - } - - /// - /// Retrieves the last stage in this pipeline. - /// - /// A shared pointer to last stage. - const std::shared_ptr& last_stage() const - { - return m_last_stage; - } - -private: - - http_pipeline(const std::shared_ptr &last) : m_last_stage(last) - { - } - - // The vector of pipeline stages. - std::vector> m_stages; - - // The last stage is always set up by the client or listener and cannot - // be changed. All application-defined stages are executed before the - // last stage, which is typically a send or dispatch. - std::shared_ptr m_last_stage; - - pplx::extensibility::recursive_lock_t m_lock; - - // No copy or assignment. - http_pipeline & operator=(const http_pipeline &); - http_pipeline(const http_pipeline &); }; }} diff --git a/Release/src/CMakeLists.txt b/Release/src/CMakeLists.txt index ade7c93b5b..eba83b2cc3 100644 --- a/Release/src/CMakeLists.txt +++ b/Release/src/CMakeLists.txt @@ -4,6 +4,7 @@ if (NOT CPPREST_EXCLUDE_WEBSOCKETS) endif() set(SOURCES_COMMON + http/client/http_client.cpp http/client/http_client_msg.cpp http/client/x509_cert_utilities.cpp http/common/http_helpers.cpp @@ -40,14 +41,12 @@ if(UNIX) if(APPLE) list(APPEND SOURCES pplx/pplxapple.cpp - http/client/x509_cert_utilities_apple.cpp ) find_library(COREFOUNDATION CoreFoundation "/") find_library(SECURITY Security "/") set(EXTRALINKS ${COREFOUNDATION} ${SECURITY}) elseif(ANDROID) list(APPEND SOURCES - http/client/x509_cert_utilities_android.cpp pplx/pplxlinux.cpp ) else() @@ -62,7 +61,6 @@ elseif(WIN32) set(SOURCES ${SOURCES_COMMON} http/client/http_client_winhttp.cpp - http/client/x509_cert_utilities_win32.cpp http/listener/http_server_httpsys.cpp pplx/pplxwin.cpp streams/fileio_win32.cpp diff --git a/Release/src/build/android.vcxitems b/Release/src/build/android.vcxitems index e6febf2f9e..b838706244 100644 --- a/Release/src/build/android.vcxitems +++ b/Release/src/build/android.vcxitems @@ -15,7 +15,6 @@ - diff --git a/Release/src/build/android.vcxitems.filters b/Release/src/build/android.vcxitems.filters index 28951ec63a..a79b9de949 100644 --- a/Release/src/build/android.vcxitems.filters +++ b/Release/src/build/android.vcxitems.filters @@ -19,9 +19,6 @@ Source Files - - Source Files - diff --git a/Release/src/build/common.vcxitems b/Release/src/build/common.vcxitems index 28702632db..eec185f2ee 100644 --- a/Release/src/build/common.vcxitems +++ b/Release/src/build/common.vcxitems @@ -15,6 +15,7 @@ + @@ -51,12 +52,11 @@ - - + @@ -83,6 +83,7 @@ + diff --git a/Release/src/build/common.vcxitems.filters b/Release/src/build/common.vcxitems.filters index 4133dbbe93..35cf8139d2 100644 --- a/Release/src/build/common.vcxitems.filters +++ b/Release/src/build/common.vcxitems.filters @@ -67,6 +67,9 @@ Source Files + + Source Files + @@ -87,6 +90,9 @@ {1c12997c-5bf5-4b60-853e-a5f9c8303760} + + {97da7aee-41c8-4948-bb0e-c31cec1bfb16} + @@ -152,9 +158,6 @@ Header Files\cpprest\details - - Header Files\cpprest\details - Header Files\cpprest\details @@ -209,6 +212,9 @@ Header Files\cpprest\details + + Header Files\private + diff --git a/Release/src/build/other.vcxitems b/Release/src/build/other.vcxitems index ff29aaf2bb..2199600b11 100644 --- a/Release/src/build/other.vcxitems +++ b/Release/src/build/other.vcxitems @@ -14,7 +14,6 @@ - diff --git a/Release/src/build/other.vcxitems.filters b/Release/src/build/other.vcxitems.filters index 486bba9f26..caa96f0b2a 100644 --- a/Release/src/build/other.vcxitems.filters +++ b/Release/src/build/other.vcxitems.filters @@ -12,8 +12,5 @@ Source Files - - Source Files - \ No newline at end of file diff --git a/Release/src/build/win32.vcxitems b/Release/src/build/win32.vcxitems index 5de0ce00ad..0d9e515317 100644 --- a/Release/src/build/win32.vcxitems +++ b/Release/src/build/win32.vcxitems @@ -15,7 +15,6 @@ - diff --git a/Release/src/build/win32.vcxitems.filters b/Release/src/build/win32.vcxitems.filters index 10063c1013..f32d09f1d2 100644 --- a/Release/src/build/win32.vcxitems.filters +++ b/Release/src/build/win32.vcxitems.filters @@ -13,9 +13,6 @@ Source Files - - Source Files - Source Files diff --git a/Release/src/http/client/http_client.cpp b/Release/src/http/client/http_client.cpp new file mode 100644 index 0000000000..d9676024ee --- /dev/null +++ b/Release/src/http/client/http_client.cpp @@ -0,0 +1,422 @@ +/*** +* ==++== +* +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: Client-side APIs. +* +* This file contains shared code across all http_client implementations. +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "stdafx.h" + +#include "http_client_impl.h" + +namespace web { namespace http { namespace client { + +// Helper function to check to make sure the uri is valid. +static void verify_uri(const uri &uri) +{ + // Some things like proper URI schema are verified by the URI class. + // We only need to check certain things specific to HTTP. + if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https")) + { + throw std::invalid_argument("URI scheme must be 'http' or 'https'"); + } + + if (uri.host().empty()) + { + throw std::invalid_argument("URI must contain a hostname."); + } +} + +namespace details +{ + +#if defined(_WIN32) + extern const utility::char_t * get_with_body_err_msg = _XPLATSTR("A GET or HEAD request should not have an entity body."); +#endif + +void request_context::complete_headers() +{ + // We have already read (and transmitted) the request body. Should we explicitly close the stream? + // Well, there are test cases that assumes that the istream is valid when t receives the response! + // For now, we will drop our reference which will close the stream if the user doesn't have one. + m_request.set_body(Concurrency::streams::istream()); + m_request_completion.set(m_response); +} + +void request_context::complete_request(utility::size64_t body_size) +{ + m_response._get_impl()->_complete(body_size); + + finish(); +} + +void request_context::report_error(unsigned long error_code, const std::string &errorMessage) +{ + report_exception(http_exception(static_cast(error_code), errorMessage)); +} + +#if defined(_WIN32) +void request_context::report_error(unsigned long error_code, const std::wstring &errorMessage) +{ + report_exception(http_exception(static_cast(error_code), errorMessage)); +} +#endif + +void request_context::report_exception(std::exception_ptr exceptionPtr) +{ + auto response_impl = m_response._get_impl(); + + // If cancellation has been triggered then ignore any errors. + if (m_request._cancellation_token().is_canceled()) + { + exceptionPtr = std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category())); + } + + // First try to complete the headers with an exception. + if (m_request_completion.set_exception(exceptionPtr)) + { + // Complete the request with no msg body. The exception + // should only be propagated to one of the tce. + response_impl->_complete(0); + } + else + { + // Complete the request with an exception + response_impl->_complete(0, exceptionPtr); + } + + finish(); +} + +concurrency::streams::streambuf request_context::_get_readbuffer() +{ + auto instream = m_request.body(); + + _ASSERTE((bool)instream); + return instream.streambuf(); +} + +concurrency::streams::streambuf request_context::_get_writebuffer() +{ + auto outstream = m_response._get_impl()->outstream(); + + _ASSERTE((bool)outstream); + return outstream.streambuf(); +} + +request_context::request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request) + : m_http_client(client), + m_request(request), + m_uploaded(0), + m_downloaded(0) +{ + auto responseImpl = m_response._get_impl(); + + // Copy the user specified output stream over to the response + responseImpl->set_outstream(request._get_impl()->_response_stream(), false); + + // Prepare for receiving data from the network. Ideally, this should be done after + // we receive the headers and determine that there is a response body. We will do it here + // since it is not immediately apparent where that would be in the callback handler + responseImpl->_prepare_to_receive_data(); +} + +void _http_client_communicator::async_send_request(const std::shared_ptr &request) +{ + if (m_client_config.guarantee_order()) + { + // Send to call block to be processed. + push_request(request); + } + else + { + // Schedule a task to start sending. + pplx::create_task([this, request] + { + open_and_send_request(request); + }); + } +} + +void _http_client_communicator::finish_request() +{ + // If guarantee order is specified we don't need to do anything. + if (m_client_config.guarantee_order()) + { + pplx::extensibility::scoped_critical_section_t l(m_open_lock); + + --m_scheduled; + + if (!m_requests_queue.empty()) + { + auto request = m_requests_queue.front(); + m_requests_queue.pop(); + + // Schedule a task to start sending. + pplx::create_task([this, request] + { + open_and_send_request(request); + }); + } + } +} + +const http_client_config& _http_client_communicator::client_config() const +{ + return m_client_config; +} + +const uri & _http_client_communicator::base_uri() const +{ + return m_uri; +} + +_http_client_communicator::_http_client_communicator(http::uri address, http_client_config client_config) + : m_uri(std::move(address)), m_client_config(std::move(client_config)), m_opened(false), m_scheduled(0) +{ +} + +// Wraps opening the client around sending a request. +void _http_client_communicator::open_and_send_request(const std::shared_ptr &request) +{ + // First see if client needs to be opened. + auto error = open_if_required(); + + if (error != 0) + { + // Failed to open + request->report_error(error, _XPLATSTR("Open failed")); + + // DO NOT TOUCH the this pointer after completing the request + // This object could be freed along with the request as it could + // be the last reference to this object + return; + } + + send_request(request); +} + +unsigned long _http_client_communicator::open_if_required() +{ + unsigned long error = 0; + + if (!m_opened) + { + pplx::extensibility::scoped_critical_section_t l(m_open_lock); + + // Check again with the lock held + if (!m_opened) + { + error = open(); + + if (error == 0) + { + m_opened = true; + } + } + } + + return error; +} + +void _http_client_communicator::push_request(const std::shared_ptr &request) +{ + pplx::extensibility::scoped_critical_section_t l(m_open_lock); + + if (++m_scheduled == 1) + { + // Schedule a task to start sending. + pplx::create_task([this, request]() + { + open_and_send_request(request); + }); + } + else + { + m_requests_queue.push(request); + } +} + +inline void request_context::finish() +{ + // If cancellation is enabled and registration was performed, unregister. + if (m_cancellationRegistration != pplx::cancellation_token_registration()) + { + _ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none()); + m_request._cancellation_token().deregister_callback(m_cancellationRegistration); + } + + m_http_client->finish_request(); +} + +} // namespace details + +/// +/// Private implementation of http_client. Manages the http request processing pipeline. +/// +class http_pipeline +{ +public: + http_pipeline(std::shared_ptr last) : m_last_stage(std::move(last)) + {} + + // pplx::extensibility::recursive_lock_t does not support move/copy, but does not delete the functions either. + http_pipeline(const http_pipeline &) = delete; + http_pipeline(http_pipeline &&) = delete; + http_pipeline & operator=(const http_pipeline &) = delete; + http_pipeline & operator=(http_pipeline &&) = delete; + + /// + /// Initiate an http request into the pipeline + /// + /// Http request + pplx::task propagate(http_request request) + { + std::shared_ptr first; + { + pplx::extensibility::scoped_recursive_lock_t l(m_lock); + first = (m_stages.size() > 0) ? m_stages[0] : m_last_stage; + } + return first->propagate(request); + } + + /// + /// Adds an HTTP pipeline stage to the pipeline. + /// + /// A pipeline stage. + void append(const std::shared_ptr &stage) + { + pplx::extensibility::scoped_recursive_lock_t l(m_lock); + + if (m_stages.size() > 0) + { + std::shared_ptr penultimate = m_stages[m_stages.size() - 1]; + penultimate->set_next_stage(stage); + } + stage->set_next_stage(m_last_stage); + + m_stages.push_back(stage); + } + + // The last stage is always set up by the client or listener and cannot + // be changed. All application-defined stages are executed before the + // last stage, which is typically a send or dispatch. + const std::shared_ptr m_last_stage; + +private: + + // The vector of pipeline stages. + std::vector> m_stages; + + pplx::extensibility::recursive_lock_t m_lock; +}; + +void http_client::add_handler(const std::function(http_request, std::shared_ptr)> &handler) +{ + class function_pipeline_wrapper : public http::http_pipeline_stage + { + public: + function_pipeline_wrapper(const std::function(http_request, std::shared_ptr)> &handler) : m_handler(handler) + { + } + + virtual pplx::task propagate(http_request request) override + { + return m_handler(std::move(request), next_stage()); + } + private: + + std::function(http_request, std::shared_ptr)> m_handler; + }; + + m_pipeline->append(std::make_shared(handler)); +} + +void http_client::add_handler(const std::shared_ptr &stage) +{ + m_pipeline->append(stage); +} + +http_client::http_client(const uri &base_uri) : http_client(base_uri, http_client_config()) +{} + +http_client::http_client(const uri &base_uri, const http_client_config &client_config) +{ + std::shared_ptr final_pipeline_stage; + + if (base_uri.scheme().empty()) + { + auto uribuilder = uri_builder(base_uri); + uribuilder.set_scheme(_XPLATSTR("http")); + uri uriWithScheme = uribuilder.to_uri(); + verify_uri(uriWithScheme); + final_pipeline_stage = details::create_platform_final_pipeline_stage(uriWithScheme, client_config); + } + else + { + verify_uri(base_uri); + final_pipeline_stage = details::create_platform_final_pipeline_stage(base_uri, client_config); + } + + m_pipeline = std::make_shared(std::move(final_pipeline_stage)); + +#if !defined(CPPREST_TARGET_XP) + add_handler(std::static_pointer_cast( + std::make_shared(client_config.oauth1()))); +#endif + + add_handler(std::static_pointer_cast( + std::make_shared(client_config.oauth2()))); +} + +http_client::~http_client() CPPREST_NOEXCEPT {} + +const http_client_config & http_client::client_config() const +{ + return m_pipeline->m_last_stage->client_config(); +} + +const uri & http_client::base_uri() const +{ + return m_pipeline->m_last_stage->base_uri(); +} + +// Macros to help build string at compile time and avoid overhead. +#define STRINGIFY(x) _XPLATSTR(#x) +#define TOSTRING(x) STRINGIFY(x) +#define USERAGENT _XPLATSTR("cpprestsdk/") TOSTRING(CPPREST_VERSION_MAJOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_MINOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_REVISION) + +pplx::task http_client::request(http_request request, const pplx::cancellation_token &token) +{ + if (!request.headers().has(header_names::user_agent)) + { + request.headers().add(header_names::user_agent, USERAGENT); + } + + request._set_base_uri(base_uri()); + request._set_cancellation_token(token); + return m_pipeline->propagate(request); +} + + +}}} \ No newline at end of file diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index 44296040e0..72eeb0d8df 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -43,12 +43,15 @@ #error "Cpp rest SDK requires c++11 smart pointer support from boost" #endif -#include "cpprest/details/http_client_impl.h" +#include "http_client_impl.h" +#include "cpprest/base_uri.h" #include "cpprest/details/x509_cert_utilities.h" #include using boost::asio::ip::tcp; +#define CRLF std::string("\r\n") + namespace web { namespace http { namespace client @@ -227,8 +230,6 @@ class asio_connection m_is_reused = true; } - void handle_pool_timer(const boost::system::error_code& ec); - // Guards concurrent access to socket/ssl::stream. This is necessary // because timeouts and cancellation can touch the socket at the same time // as normal message processing. @@ -331,7 +332,7 @@ class asio_connection_pool -class asio_client : public _http_client_communicator, public std::enable_shared_from_this +class asio_client : public _http_client_communicator { public: asio_client(http::uri address, http_client_config client_config) @@ -347,6 +348,8 @@ class asio_client : public _http_client_communicator, public std::enable_shared_ unsigned long open() override { return 0; } + virtual pplx::task propagate(http_request request) override; + asio_connection_pool m_pool; tcp::resolver m_resolver; }; @@ -656,7 +659,7 @@ class asio_context : public request_context, public std::enable_shared_from_this extra_headers.append(": no-cache" + CRLF); } - request_stream << flatten_http_headers(ctx->m_request.headers()); + request_stream << ::web::http::details::flatten_http_headers(ctx->m_request.headers()); request_stream << extra_headers; // Enforce HTTP connection keep alive (even for the old HTTP/1.0 protocol). request_stream << "Connection: Keep-Alive" << CRLF << CRLF; @@ -1446,22 +1449,9 @@ class asio_context : public request_context, public std::enable_shared_from_this }; - -http_network_handler::http_network_handler(const uri &base_uri, const http_client_config &client_config) : - m_http_client_impl(std::make_shared(base_uri, client_config)) -{} - -pplx::task http_network_handler::propagate(http_request request) +std::shared_ptr<_http_client_communicator> create_platform_final_pipeline_stage(uri base_uri, const http_client_config& client_config) { - auto context = details::asio_context::create_request_context(m_http_client_impl, request); - - // Use a task to externally signal the final result and completion of the task. - auto result_task = pplx::create_task(context->m_request_completion); - - // Asynchronously send the response with the HTTP client implementation. - m_http_client_impl->async_send_request(context); - - return result_task; + return std::make_shared(base_uri, client_config); } void asio_client::send_request(const std::shared_ptr &request_ctx) @@ -1488,4 +1478,18 @@ void asio_client::send_request(const std::shared_ptr &request_c ctx->start_request(); } +pplx::task asio_client::propagate(http_request request) +{ + auto self = std::static_pointer_cast<_http_client_communicator>(shared_from_this()); + auto context = details::asio_context::create_request_context(self, request); + + // Use a task to externally signal the final result and completion of the task. + auto result_task = pplx::create_task(context->m_request_completion); + + // Asynchronously send the response with the HTTP client implementation. + this->async_send_request(context); + + return result_task; +} + }}}} // namespaces diff --git a/Release/src/http/client/http_client_impl.h b/Release/src/http/client/http_client_impl.h new file mode 100644 index 0000000000..ac0ae81ce1 --- /dev/null +++ b/Release/src/http/client/http_client_impl.h @@ -0,0 +1,151 @@ +/*** +* ==++== +* +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: Client-side APIs. +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ +#pragma once + +#include "cpprest/details/basic_types.h" +#include "cpprest/astreambuf.h" +#include "cpprest/http_client.h" +#include "cpprest/http_msg.h" +#include +#include +#include + +namespace web { namespace http { namespace client { namespace details { + +class _http_client_communicator; + +// Request context encapsulating everything necessary for creating and responding to a request. +class request_context +{ +public: + + // Destructor to clean up any held resources. + virtual ~request_context() {} + + virtual void report_exception(std::exception_ptr exceptionPtr); + + virtual concurrency::streams::streambuf _get_readbuffer(); + + void complete_headers(); + + /// + /// Completes this request, setting the underlying task completion event, and cleaning up the handles + /// + void complete_request(utility::size64_t body_size); + + void report_error(unsigned long error_code, const std::string &errorMessage); + +#ifdef _WIN32 + void report_error(unsigned long error_code, const std::wstring &errorMessage); +#endif + + template + void report_exception(const _ExceptionType &e) + { + report_exception(std::make_exception_ptr(e)); + } + + concurrency::streams::streambuf _get_writebuffer(); + + // Reference to the http_client implementation. + std::shared_ptr<_http_client_communicator> m_http_client; + + // request/response pair. + http_request m_request; + http_response m_response; + + utility::size64_t m_uploaded; + utility::size64_t m_downloaded; + + // task completion event to signal request is completed. + pplx::task_completion_event m_request_completion; + + // Registration for cancellation notification if enabled. + pplx::cancellation_token_registration m_cancellationRegistration; + +protected: + + request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request); + + virtual void finish(); +}; + +// +// Interface used by client implementations. Concrete implementations are responsible for +// sending HTTP requests and receiving the responses. +// +class _http_client_communicator : public http_pipeline_stage +{ +public: + + virtual ~_http_client_communicator() {} + + // Asynchronously send a HTTP request and process the response. + void async_send_request(const std::shared_ptr &request); + + void finish_request(); + + const http_client_config& client_config() const; + + const uri & base_uri() const; + +protected: + _http_client_communicator(http::uri address, http_client_config client_config); + + // Method to open client. + virtual unsigned long open() = 0; + + // HTTP client implementations must implement send_request. + virtual void send_request(_In_ const std::shared_ptr &request) = 0; + + // URI to connect to. + const http::uri m_uri; + +private: + + http_client_config m_client_config; + + bool m_opened; + + pplx::extensibility::critical_section_t m_open_lock; + + // Wraps opening the client around sending a request. + void open_and_send_request(const std::shared_ptr &request); + + unsigned long open_if_required(); + + void push_request(const std::shared_ptr &request); + + // Queue used to guarantee ordering of requests, when applicable. + std::queue> m_requests_queue; + int m_scheduled; +}; + +/// +/// Factory function implemented by the separate platforms to construct their subclasses of _http_client_communicator +/// +std::shared_ptr<_http_client_communicator> create_platform_final_pipeline_stage(uri base_uri, const http_client_config& client_config); + +}}}} \ No newline at end of file diff --git a/Release/src/http/client/http_client_msg.cpp b/Release/src/http/client/http_client_msg.cpp index 0a73670a47..dd89d8fffa 100644 --- a/Release/src/http/client/http_client_msg.cpp +++ b/Release/src/http/client/http_client_msg.cpp @@ -97,21 +97,4 @@ utility::string_t details::_http_response::to_string() const return buffer.str(); } -// Macros to help build string at compile time and avoid overhead. -#define STRINGIFY(x) _XPLATSTR(#x) -#define TOSTRING(x) STRINGIFY(x) -#define USERAGENT _XPLATSTR("cpprestsdk/") TOSTRING(CPPREST_VERSION_MAJOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_MINOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_REVISION) - -pplx::task client::http_client::request(http_request request, const pplx::cancellation_token &token) -{ - if(!request.headers().has(header_names::user_agent)) - { - request.headers().add(header_names::user_agent, USERAGENT); - } - - request._set_base_uri(base_uri()); - request._set_cancellation_token(token); - return m_pipeline->propagate(request); -} - }} // namespace web::http diff --git a/Release/src/http/client/http_client_winhttp.cpp b/Release/src/http/client/http_client_winhttp.cpp index 36f596b937..e4d19042b2 100644 --- a/Release/src/http/client/http_client_winhttp.cpp +++ b/Release/src/http/client/http_client_winhttp.cpp @@ -26,7 +26,8 @@ ****/ #include "stdafx.h" -#include "cpprest/details/http_client_impl.h" +#include "cpprest/http_headers.h" +#include "http_client_impl.h" namespace web { @@ -66,6 +67,16 @@ static http::status_code parse_status_code(HINTERNET request_handle) return (unsigned short)_wtoi(buffer.c_str()); } +// Helper function to trim leading and trailing null characters from a string. +static void trim_nulls(utility::string_t &str) +{ + size_t index; + for (index = 0; index < str.size() && str[index] == 0; ++index); + str.erase(0, index); + for (index = str.size(); index > 0 && str[index - 1] == 0; --index); + str.erase(index); +} + // Helper function to get the reason phrase from a WinHTTP response. static utility::string_t parse_reason_phrase(HINTERNET request_handle) { @@ -98,7 +109,7 @@ static void parse_winhttp_headers(HINTERNET request_handle, _In_z_ utf16char *he response.set_status_code(parse_status_code(request_handle)); response.set_reason_phrase(parse_reason_phrase(request_handle)); - parse_headers_string(headersStr, response.headers()); + web::http::details::parse_headers_string(headersStr, response.headers()); } // Helper function to build error messages. @@ -301,7 +312,13 @@ class winhttp_client : public _http_client_communicator { public: winhttp_client(http::uri address, http_client_config client_config) - : _http_client_communicator(std::move(address), std::move(client_config)), m_secure(m_uri.scheme() == _XPLATSTR("https")), m_hSession(nullptr), m_hConnection(nullptr) { } + : _http_client_communicator(std::move(address), std::move(client_config)) + , m_secure(m_uri.scheme() == _XPLATSTR("https")) + , m_hSession(nullptr) + , m_hConnection(nullptr) { } + + winhttp_client(const winhttp_client&) = delete; + winhttp_client &operator=(const winhttp_client&) = delete; // Closes session. ~winhttp_client() @@ -324,6 +341,20 @@ class winhttp_client : public _http_client_communicator } } + virtual pplx::task propagate(http_request request) override + { + auto self = std::static_pointer_cast<_http_client_communicator>(shared_from_this()); + auto context = details::winhttp_request_context::create_request_context(self, request); + + // Use a task to externally signal the final result and completion of the task. + auto result_task = pplx::create_task(context->m_request_completion); + + // Asynchronously send the response with the HTTP client implementation. + this->async_send_request(context); + + return result_task; + } + protected: unsigned long report_failure(const utility::string_t& errorMessage) @@ -568,7 +599,7 @@ class winhttp_client : public _http_client_communicator { if ( msg.method() == http::methods::GET || msg.method() == http::methods::HEAD ) { - request->report_exception(http_exception(get_with_body)); + request->report_exception(http_exception(get_with_body_err_msg)); return; } @@ -590,7 +621,7 @@ class winhttp_client : public _http_client_communicator // Add headers. if(!msg.headers().empty()) { - const utility::string_t flattened_headers = flatten_http_headers(msg.headers()); + const utility::string_t flattened_headers = web::http::details::flatten_http_headers(msg.headers()); if(!WinHttpAddRequestHeaders( winhttp_context->m_request_handle, flattened_headers.c_str(), @@ -1261,28 +1292,11 @@ class winhttp_client : public _http_client_communicator HINTERNET m_hSession; HINTERNET m_hConnection; bool m_secure; - - // No copy or assignment. - winhttp_client(const winhttp_client&); - winhttp_client &operator=(const winhttp_client&); }; -http_network_handler::http_network_handler(const uri &base_uri, const http_client_config &client_config) : - m_http_client_impl(std::make_shared(base_uri, client_config)) -{ -} - -pplx::task http_network_handler::propagate(http_request request) +std::shared_ptr<_http_client_communicator> create_platform_final_pipeline_stage(uri base_uri, const http_client_config& client_config) { - auto context = details::winhttp_request_context::create_request_context(m_http_client_impl, request); - - // Use a task to externally signal the final result and completion of the task. - auto result_task = pplx::create_task(context->m_request_completion); - - // Asynchronously send the response with the HTTP client implementation. - m_http_client_impl->async_send_request(context); - - return result_task; + return std::make_shared(std::move(base_uri), client_config); } }}}} diff --git a/Release/src/http/client/http_client_winrt.cpp b/Release/src/http/client/http_client_winrt.cpp index d2b5a58cc4..5ae4a8ec52 100644 --- a/Release/src/http/client/http_client_winrt.cpp +++ b/Release/src/http/client/http_client_winrt.cpp @@ -26,7 +26,7 @@ ****/ #include "stdafx.h" -#include "cpprest/details/http_client_impl.h" +#include "http_client_impl.h" #include // Important for WP8 @@ -109,7 +109,7 @@ class HttpRequestCallback : } } - parse_headers_string(hdrStr, response.headers()); + web::http::details::parse_headers_string(hdrStr, response.headers()); m_request->complete_headers(); return S_OK; @@ -359,6 +359,23 @@ class winrt_client : public _http_client_communicator winrt_client(http::uri address, http_client_config client_config) : _http_client_communicator(std::move(address), std::move(client_config)) { } + winrt_client(const winrt_client&) = delete; + winrt_client &operator=(const winrt_client&) = delete; + + virtual pplx::task propagate(http_request request) override + { + auto self = std::static_pointer_cast<_http_client_communicator>(shared_from_this()); + auto context = details::winrt_request_context::create_request_context(self, request); + + // Use a task to externally signal the final result and completion of the task. + auto result_task = pplx::create_task(context->m_request_completion); + + // Asynchronously send the response with the HTTP client implementation. + this->async_send_request(context); + + return result_task; + } + protected: // Method to open client. @@ -516,7 +533,7 @@ class winrt_client : public _http_client_communicator { if ( msg.method() == http::methods::GET || msg.method() == http::methods::HEAD ) { - request->report_exception(http_exception(get_with_body)); + request->report_exception(http_exception(get_with_body_err_msg)); return; } @@ -541,30 +558,11 @@ class winrt_client : public _http_client_communicator }); } } - -private: - - // No copy or assignment. - winrt_client(const winrt_client&); - winrt_client &operator=(const winrt_client&); }; -http_network_handler::http_network_handler(const uri &base_uri, const http_client_config &client_config) : - m_http_client_impl(std::make_shared(base_uri, client_config)) -{ -} - -pplx::task http_network_handler::propagate(http_request request) +std::shared_ptr<_http_client_communicator> create_platform_final_pipeline_stage(uri base_uri, const http_client_config& client_config) { - auto context = details::winrt_request_context::create_request_context(m_http_client_impl, request); - - // Use a task to externally signal the final result and completion of the task. - auto result_task = pplx::create_task(context->m_request_completion); - - // Asynchronously send the response with the HTTP client implementation. - m_http_client_impl->async_send_request(context); - - return result_task; + return std::make_shared(std::move(base_uri), client_config); } }}}} diff --git a/Release/src/http/client/x509_cert_utilities.cpp b/Release/src/http/client/x509_cert_utilities.cpp index fed486dcc2..20eef9812f 100644 --- a/Release/src/http/client/x509_cert_utilities.cpp +++ b/Release/src/http/client/x509_cert_utilities.cpp @@ -25,11 +25,32 @@ #include "stdafx.h" +#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) + #include "cpprest/details/x509_cert_utilities.h" +#include + +#if defined(ANDROID) || defined(__ANDROID__) +#include +#endif + +#if defined(__APPLE__) +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) +#include +#include +#endif namespace web { namespace http { namespace client { namespace details { -#if defined(__APPLE__) || (defined(ANDROID) || defined(__ANDROID__)) || (defined(_WIN32) && !defined(__cplusplus_winrt) && !defined(_M_ARM) && !defined(CPPREST_EXCLUDE_WEBSOCKETS)) +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) { X509_STORE_CTX *storeContext = verifyCtx.native_handle(); @@ -83,6 +104,379 @@ bool verify_cert_chain_platform_specific(boost::asio::ssl::verify_context &verif #endif return verify_result; } + +#if defined(ANDROID) || defined(__ANDROID__) +using namespace crossplat; + +/// +/// Helper function to check return value and see if any exceptions +/// occurred when calling a JNI function. +/// +/// true if JNI call failed, false otherwise. +static bool jni_failed(JNIEnv *env) +{ + if(env->ExceptionOccurred()) + { + // Clear exception otherwise no other JNI functions can be called. + // In the future if we improve error reporting the exception message + // can be retrieved from here. + env->ExceptionClear(); + return true; + } + return false; +} +template +static bool jni_failed(JNIEnv *env, const java_local_ref &result) +{ + if(jni_failed(env) || !result) + { + return true; + } + return false; +} +static bool jni_failed(JNIEnv *env, const jmethodID &result) +{ + if(jni_failed(env) || result == nullptr) + { + return true; + } + return false; +} +#define CHECK_JREF(env, obj) if(jni_failed(env, obj)) return false; +#define CHECK_JMID(env, mid) if(jni_failed(env, mid)) return false; +#define CHECK_JNI(env) if(jni_failed(env)) return false; + +bool verify_X509_cert_chain(const std::vector &certChain, const std::string &hostName) +{ + JNIEnv* env = get_jvm_env(); + + // Possible performance improvement: + // In the future we could gain performance by turning all the jclass local + // references into global references. Then we could lazy initialize and + // save them globally. If this is done I'm not exactly sure where the release + // should be. + + // ByteArrayInputStream + java_local_ref byteArrayInputStreamClass(env->FindClass("java/io/ByteArrayInputStream")); + CHECK_JREF(env, byteArrayInputStreamClass); + jmethodID byteArrayInputStreamConstructorMethod = env->GetMethodID( + byteArrayInputStreamClass.get(), + "", + "([B)V"); + CHECK_JMID(env, byteArrayInputStreamConstructorMethod); + + // CertificateFactory + java_local_ref certificateFactoryClass(env->FindClass("java/security/cert/CertificateFactory")); + CHECK_JREF(env, certificateFactoryClass); + jmethodID certificateFactoryGetInstanceMethod = env->GetStaticMethodID( + certificateFactoryClass.get(), + "getInstance", + "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;"); + CHECK_JMID(env, certificateFactoryGetInstanceMethod); + jmethodID generateCertificateMethod = env->GetMethodID( + certificateFactoryClass.get(), + "generateCertificate", + "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"); + CHECK_JMID(env, generateCertificateMethod); + + // X509Certificate + java_local_ref X509CertificateClass(env->FindClass("java/security/cert/X509Certificate")); + CHECK_JREF(env, X509CertificateClass); + + // TrustManagerFactory + java_local_ref trustManagerFactoryClass(env->FindClass("javax/net/ssl/TrustManagerFactory")); + CHECK_JREF(env, trustManagerFactoryClass); + jmethodID trustManagerFactoryGetInstanceMethod = env->GetStaticMethodID( + trustManagerFactoryClass.get(), + "getInstance", + "(Ljava/lang/String;)Ljavax/net/ssl/TrustManagerFactory;"); + CHECK_JMID(env, trustManagerFactoryGetInstanceMethod); + jmethodID trustManagerFactoryInitMethod = env->GetMethodID( + trustManagerFactoryClass.get(), + "init", + "(Ljava/security/KeyStore;)V"); + CHECK_JMID(env, trustManagerFactoryInitMethod); + jmethodID trustManagerFactoryGetTrustManagersMethod = env->GetMethodID( + trustManagerFactoryClass.get(), + "getTrustManagers", + "()[Ljavax/net/ssl/TrustManager;"); + CHECK_JMID(env, trustManagerFactoryGetTrustManagersMethod); + + // X509TrustManager + java_local_ref X509TrustManagerClass(env->FindClass("javax/net/ssl/X509TrustManager")); + CHECK_JREF(env, X509TrustManagerClass); + jmethodID X509TrustManagerCheckServerTrustedMethod = env->GetMethodID( + X509TrustManagerClass.get(), + "checkServerTrusted", + "([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V"); + CHECK_JMID(env, X509TrustManagerCheckServerTrustedMethod); + + // StrictHostnameVerifier + java_local_ref strictHostnameVerifierClass(env->FindClass("org/apache/http/conn/ssl/StrictHostnameVerifier")); + CHECK_JREF(env, strictHostnameVerifierClass); + jmethodID strictHostnameVerifierConstructorMethod = env->GetMethodID(strictHostnameVerifierClass.get(), "", "()V"); + CHECK_JMID(env, strictHostnameVerifierConstructorMethod); + jmethodID strictHostnameVerifierVerifyMethod = env->GetMethodID( + strictHostnameVerifierClass.get(), + "verify", + "(Ljava/lang/String;Ljava/security/cert/X509Certificate;)V"); + CHECK_JMID(env, strictHostnameVerifierVerifyMethod); + + // Create CertificateFactory + java_local_ref XDot509String(env->NewStringUTF("X.509")); + CHECK_JREF(env, XDot509String); + java_local_ref certificateFactory(env->CallStaticObjectMethod( + certificateFactoryClass.get(), + certificateFactoryGetInstanceMethod, + XDot509String.get())); + CHECK_JREF(env, certificateFactory); + + // Create Java array to store all the certs in. + java_local_ref certsArray(env->NewObjectArray(certChain.size(), X509CertificateClass.get(), nullptr)); + CHECK_JREF(env, certsArray); + + // For each certificate perform the following steps: + // 1. Create ByteArrayInputStream backed by DER certificate bytes + // 2. Create Certificate using CertificateFactory.generateCertificate + // 3. Add Certificate to array + int i = 0; + for(const auto &certData : certChain) + { + java_local_ref byteArray(env->NewByteArray(certData.size())); + CHECK_JREF(env, byteArray); + env->SetByteArrayRegion(byteArray.get(), 0, certData.size(), reinterpret_cast(certData.c_str())); + CHECK_JNI(env); + java_local_ref byteArrayInputStream(env->NewObject( + byteArrayInputStreamClass.get(), + byteArrayInputStreamConstructorMethod, + byteArray.get())); + CHECK_JREF(env, byteArrayInputStream); + + java_local_ref cert(env->CallObjectMethod( + certificateFactory.get(), + generateCertificateMethod, + byteArrayInputStream.get())); + CHECK_JREF(env, cert); + + env->SetObjectArrayElement(certsArray.get(), i, cert.get()); + CHECK_JNI(env); + ++i; + } + + // Create TrustManagerFactory, init with Android system certs + java_local_ref X509String(env->NewStringUTF("X509")); + CHECK_JREF(env, X509String); + java_local_ref trustFactoryManager(env->CallStaticObjectMethod( + trustManagerFactoryClass.get(), + trustManagerFactoryGetInstanceMethod, + X509String.get())); + CHECK_JREF(env, trustFactoryManager); + env->CallVoidMethod(trustFactoryManager.get(), trustManagerFactoryInitMethod, nullptr); + CHECK_JNI(env); + + // Get TrustManager + java_local_ref trustManagerArray(static_cast( + env->CallObjectMethod(trustFactoryManager.get(), trustManagerFactoryGetTrustManagersMethod))); + CHECK_JREF(env, trustManagerArray); + java_local_ref trustManager(env->GetObjectArrayElement(trustManagerArray.get(), 0)); + CHECK_JREF(env, trustManager); + + // Validate certificate chain. + java_local_ref RSAString(env->NewStringUTF("RSA")); + CHECK_JREF(env, RSAString); + env->CallVoidMethod( + trustManager.get(), + X509TrustManagerCheckServerTrustedMethod, + certsArray.get(), + RSAString.get()); + CHECK_JNI(env); + + // Verify hostname on certificate according to RFC 2818. + java_local_ref hostnameVerifier(env->NewObject( + strictHostnameVerifierClass.get(), strictHostnameVerifierConstructorMethod)); + CHECK_JREF(env, hostnameVerifier); + java_local_ref hostNameString(env->NewStringUTF(hostName.c_str())); + CHECK_JREF(env, hostNameString); + java_local_ref cert(env->GetObjectArrayElement(certsArray.get(), 0)); + CHECK_JREF(env, cert); + env->CallVoidMethod( + hostnameVerifier.get(), + strictHostnameVerifierVerifyMethod, + hostNameString.get(), + cert.get()); + CHECK_JNI(env); + + return true; +} +#endif + +#if defined(__APPLE__) +namespace { + // Simple RAII pattern wrapper to perform CFRelease on objects. + template + class cf_ref + { + public: + cf_ref(T v) : value(v) + { + static_assert(sizeof(cf_ref) == sizeof(T), "Code assumes just a wrapper, see usage in CFArrayCreate below."); + } + cf_ref() : value(nullptr) {} + cf_ref(cf_ref &&other) : value(other.value) { other.value = nullptr; } + + ~cf_ref() + { + if(value != nullptr) + { + CFRelease(value); + } + } + + T & get() + { + return value; + } + private: + cf_ref(const cf_ref &); + cf_ref & operator=(const cf_ref &); + T value; + }; +} + +bool verify_X509_cert_chain(const std::vector &certChain, const std::string &hostName) +{ + // Build up CFArrayRef with all the certificates. + // All this code is basically just to get into the correct structures for the Apple APIs. + // Copies are avoided whenever possible. + std::vector> certs; + for(const auto & certBuf : certChain) + { + cf_ref certDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + reinterpret_cast(certBuf.c_str()), + certBuf.size(), + kCFAllocatorNull); + if(certDataRef.get() == nullptr) + { + return false; + } + + cf_ref certObj = SecCertificateCreateWithData(nullptr, certDataRef.get()); + if(certObj.get() == nullptr) + { + return false; + } + certs.push_back(std::move(certObj)); + } + cf_ref certsArray = CFArrayCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&certs[0])), certs.size(), nullptr); + if(certsArray.get() == nullptr) + { + return false; + } + + // Create trust management object with certificates and SSL policy. + // Note: SecTrustCreateWithCertificates expects the certificate to be + // verified is the first element. + cf_ref cfHostName = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, + hostName.c_str(), + kCFStringEncodingASCII, + kCFAllocatorNull); + if(cfHostName.get() == nullptr) + { + return false; + } + cf_ref policy = SecPolicyCreateSSL(true /* client side */, cfHostName.get()); + cf_ref trust; + OSStatus status = SecTrustCreateWithCertificates(certsArray.get(), policy.get(), &trust.get()); + if(status == noErr) + { + // Perform actual certificate verification. + SecTrustResultType trustResult; + status = SecTrustEvaluate(trust.get(), &trustResult); + if(status == noErr && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) + { + return true; + } + } + + return false; +} +#endif + +#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; +} + +bool verify_X509_cert_chain(const std::vector &certChain, const std::string &) +{ + // Create certificate context from server certificate. + cert_context cert(CertCreateCertificateContext( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + reinterpret_cast(certChain[0].c_str()), + static_cast(certChain[0].size()))); + if (cert == nullptr) + { + return false; + } + + // Let the OS build a certificate chain from the server certificate. + CERT_CHAIN_PARA params; + ZeroMemory(¶ms, sizeof(params)); + params.cbSize = sizeof(CERT_CHAIN_PARA); + params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + LPSTR usages [] = + { + szOID_PKIX_KP_SERVER_AUTH, + + // For older servers and to match IE. + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + params.RequestedUsage.Usage.cUsageIdentifier = std::extent::value; + params.RequestedUsage.Usage.rgpszUsageIdentifier = usages; + PCCERT_CHAIN_CONTEXT chainContext; + chain_context chain; + if (!CertGetCertificateChain( + nullptr, + cert.get(), + nullptr, + nullptr, + ¶ms, + CERT_CHAIN_REVOCATION_CHECK_CHAIN, + nullptr, + &chainContext)) + { + return false; + } + chain.reset(chainContext); + + // Check to see if the certificate chain is actually trusted. + if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + { + return false; + } + + return true; +} #endif + }}}} + +#endif diff --git a/Release/src/http/client/x509_cert_utilities_android.cpp b/Release/src/http/client/x509_cert_utilities_android.cpp deleted file mode 100644 index 916582b222..0000000000 --- a/Release/src/http/client/x509_cert_utilities_android.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/*** -* ==++== -* -* 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. -* -* ==--== -* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -* -* Contains utility functions for helping to verify server certificates on Android. -* -* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk -* -* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -****/ - -#include "stdafx.h" - -#include "cpprest/details/x509_cert_utilities.h" - -#include -using namespace crossplat; - -namespace web { namespace http { namespace client { namespace details { - -/// -/// Helper function to check return value and see if any exceptions -/// occurred when calling a JNI function. -/// -/// true if JNI call failed, false otherwise. -bool jni_failed(JNIEnv *env) -{ - if(env->ExceptionOccurred()) - { - // Clear exception otherwise no other JNI functions can be called. - // In the future if we improve error reporting the exception message - // can be retrieved from here. - env->ExceptionClear(); - return true; - } - return false; -} -template -bool jni_failed(JNIEnv *env, const java_local_ref &result) -{ - if(jni_failed(env) || !result) - { - return true; - } - return false; -} -bool jni_failed(JNIEnv *env, const jmethodID &result) -{ - if(jni_failed(env) || result == nullptr) - { - return true; - } - return false; -} -#define CHECK_JREF(env, obj) if(jni_failed(env, obj)) return false; -#define CHECK_JMID(env, mid) if(jni_failed(env, mid)) return false; -#define CHECK_JNI(env) if(jni_failed(env)) return false; - -bool verify_X509_cert_chain(const std::vector &certChain, const std::string &hostName) -{ - JNIEnv* env = get_jvm_env(); - - // Possible performance improvement: - // In the future we could gain performance by turning all the jclass local - // references into global references. Then we could lazy initialize and - // save them globally. If this is done I'm not exactly sure where the release - // should be. - - // ByteArrayInputStream - java_local_ref byteArrayInputStreamClass(env->FindClass("java/io/ByteArrayInputStream")); - CHECK_JREF(env, byteArrayInputStreamClass); - jmethodID byteArrayInputStreamConstructorMethod = env->GetMethodID( - byteArrayInputStreamClass.get(), - "", - "([B)V"); - CHECK_JMID(env, byteArrayInputStreamConstructorMethod); - - // CertificateFactory - java_local_ref certificateFactoryClass(env->FindClass("java/security/cert/CertificateFactory")); - CHECK_JREF(env, certificateFactoryClass); - jmethodID certificateFactoryGetInstanceMethod = env->GetStaticMethodID( - certificateFactoryClass.get(), - "getInstance", - "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;"); - CHECK_JMID(env, certificateFactoryGetInstanceMethod); - jmethodID generateCertificateMethod = env->GetMethodID( - certificateFactoryClass.get(), - "generateCertificate", - "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"); - CHECK_JMID(env, generateCertificateMethod); - - // X509Certificate - java_local_ref X509CertificateClass(env->FindClass("java/security/cert/X509Certificate")); - CHECK_JREF(env, X509CertificateClass); - - // TrustManagerFactory - java_local_ref trustManagerFactoryClass(env->FindClass("javax/net/ssl/TrustManagerFactory")); - CHECK_JREF(env, trustManagerFactoryClass); - jmethodID trustManagerFactoryGetInstanceMethod = env->GetStaticMethodID( - trustManagerFactoryClass.get(), - "getInstance", - "(Ljava/lang/String;)Ljavax/net/ssl/TrustManagerFactory;"); - CHECK_JMID(env, trustManagerFactoryGetInstanceMethod); - jmethodID trustManagerFactoryInitMethod = env->GetMethodID( - trustManagerFactoryClass.get(), - "init", - "(Ljava/security/KeyStore;)V"); - CHECK_JMID(env, trustManagerFactoryInitMethod); - jmethodID trustManagerFactoryGetTrustManagersMethod = env->GetMethodID( - trustManagerFactoryClass.get(), - "getTrustManagers", - "()[Ljavax/net/ssl/TrustManager;"); - CHECK_JMID(env, trustManagerFactoryGetTrustManagersMethod); - - // X509TrustManager - java_local_ref X509TrustManagerClass(env->FindClass("javax/net/ssl/X509TrustManager")); - CHECK_JREF(env, X509TrustManagerClass); - jmethodID X509TrustManagerCheckServerTrustedMethod = env->GetMethodID( - X509TrustManagerClass.get(), - "checkServerTrusted", - "([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V"); - CHECK_JMID(env, X509TrustManagerCheckServerTrustedMethod); - - // StrictHostnameVerifier - java_local_ref strictHostnameVerifierClass(env->FindClass("org/apache/http/conn/ssl/StrictHostnameVerifier")); - CHECK_JREF(env, strictHostnameVerifierClass); - jmethodID strictHostnameVerifierConstructorMethod = env->GetMethodID(strictHostnameVerifierClass.get(), "", "()V"); - CHECK_JMID(env, strictHostnameVerifierConstructorMethod); - jmethodID strictHostnameVerifierVerifyMethod = env->GetMethodID( - strictHostnameVerifierClass.get(), - "verify", - "(Ljava/lang/String;Ljava/security/cert/X509Certificate;)V"); - CHECK_JMID(env, strictHostnameVerifierVerifyMethod); - - // Create CertificateFactory - java_local_ref XDot509String(env->NewStringUTF("X.509")); - CHECK_JREF(env, XDot509String); - java_local_ref certificateFactory(env->CallStaticObjectMethod( - certificateFactoryClass.get(), - certificateFactoryGetInstanceMethod, - XDot509String.get())); - CHECK_JREF(env, certificateFactory); - - // Create Java array to store all the certs in. - java_local_ref certsArray(env->NewObjectArray(certChain.size(), X509CertificateClass.get(), nullptr)); - CHECK_JREF(env, certsArray); - - // For each certificate perform the following steps: - // 1. Create ByteArrayInputStream backed by DER certificate bytes - // 2. Create Certificate using CertificateFactory.generateCertificate - // 3. Add Certificate to array - int i = 0; - for(const auto &certData : certChain) - { - java_local_ref byteArray(env->NewByteArray(certData.size())); - CHECK_JREF(env, byteArray); - env->SetByteArrayRegion(byteArray.get(), 0, certData.size(), reinterpret_cast(certData.c_str())); - CHECK_JNI(env); - java_local_ref byteArrayInputStream(env->NewObject( - byteArrayInputStreamClass.get(), - byteArrayInputStreamConstructorMethod, - byteArray.get())); - CHECK_JREF(env, byteArrayInputStream); - - java_local_ref cert(env->CallObjectMethod( - certificateFactory.get(), - generateCertificateMethod, - byteArrayInputStream.get())); - CHECK_JREF(env, cert); - - env->SetObjectArrayElement(certsArray.get(), i, cert.get()); - CHECK_JNI(env); - ++i; - } - - // Create TrustManagerFactory, init with Android system certs - java_local_ref X509String(env->NewStringUTF("X509")); - CHECK_JREF(env, X509String); - java_local_ref trustFactoryManager(env->CallStaticObjectMethod( - trustManagerFactoryClass.get(), - trustManagerFactoryGetInstanceMethod, - X509String.get())); - CHECK_JREF(env, trustFactoryManager); - env->CallVoidMethod(trustFactoryManager.get(), trustManagerFactoryInitMethod, nullptr); - CHECK_JNI(env); - - // Get TrustManager - java_local_ref trustManagerArray(static_cast( - env->CallObjectMethod(trustFactoryManager.get(), trustManagerFactoryGetTrustManagersMethod))); - CHECK_JREF(env, trustManagerArray); - java_local_ref trustManager(env->GetObjectArrayElement(trustManagerArray.get(), 0)); - CHECK_JREF(env, trustManager); - - // Validate certificate chain. - java_local_ref RSAString(env->NewStringUTF("RSA")); - CHECK_JREF(env, RSAString); - env->CallVoidMethod( - trustManager.get(), - X509TrustManagerCheckServerTrustedMethod, - certsArray.get(), - RSAString.get()); - CHECK_JNI(env); - - // Verify hostname on certificate according to RFC 2818. - java_local_ref hostnameVerifier(env->NewObject( - strictHostnameVerifierClass.get(), strictHostnameVerifierConstructorMethod)); - CHECK_JREF(env, hostnameVerifier); - java_local_ref hostNameString(env->NewStringUTF(hostName.c_str())); - CHECK_JREF(env, hostNameString); - java_local_ref cert(env->GetObjectArrayElement(certsArray.get(), 0)); - CHECK_JREF(env, cert); - env->CallVoidMethod( - hostnameVerifier.get(), - strictHostnameVerifierVerifyMethod, - hostNameString.get(), - cert.get()); - CHECK_JNI(env); - - return true; -} - -}}}} \ No newline at end of file diff --git a/Release/src/http/client/x509_cert_utilities_apple.cpp b/Release/src/http/client/x509_cert_utilities_apple.cpp deleted file mode 100644 index c311144d56..0000000000 --- a/Release/src/http/client/x509_cert_utilities_apple.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/*** -* ==++== -* -* 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. -* -* ==--== -* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -* -* Contains utility functions for helping to verify server certificates on OSX/iOS. -* -* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk -* -* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -****/ - -#include "stdafx.h" - -#include "cpprest/details/x509_cert_utilities.h" - -#include -#include -#include -#include -#include - -namespace web { namespace http { namespace client { namespace details { - -// Simple RAII pattern wrapper to perform CFRelease on objects. -template -class cf_ref -{ -public: - cf_ref(T v) : value(v) - { - static_assert(sizeof(cf_ref) == sizeof(T), "Code assumes just a wrapper, see usage in CFArrayCreate below."); - } - cf_ref() : value(nullptr) {} - cf_ref(cf_ref &&other) : value(other.value) { other.value = nullptr; } - - ~cf_ref() - { - if(value != nullptr) - { - CFRelease(value); - } - } - - T & get() - { - return value; - } -private: - cf_ref(const cf_ref &); - cf_ref & operator=(const cf_ref &); - T value; -}; - -bool verify_X509_cert_chain(const std::vector &certChain, const std::string &hostName) -{ - // Build up CFArrayRef with all the certificates. - // All this code is basically just to get into the correct structures for the Apple APIs. - // Copies are avoided whenever possible. - std::vector> certs; - for(const auto & certBuf : certChain) - { - cf_ref certDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, - reinterpret_cast(certBuf.c_str()), - certBuf.size(), - kCFAllocatorNull); - if(certDataRef.get() == nullptr) - { - return false; - } - - cf_ref certObj = SecCertificateCreateWithData(nullptr, certDataRef.get()); - if(certObj.get() == nullptr) - { - return false; - } - certs.push_back(std::move(certObj)); - } - cf_ref certsArray = CFArrayCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&certs[0])), certs.size(), nullptr); - if(certsArray.get() == nullptr) - { - return false; - } - - // Create trust management object with certificates and SSL policy. - // Note: SecTrustCreateWithCertificates expects the certificate to be - // verified is the first element. - cf_ref cfHostName = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, - hostName.c_str(), - kCFStringEncodingASCII, - kCFAllocatorNull); - if(cfHostName.get() == nullptr) - { - return false; - } - cf_ref policy = SecPolicyCreateSSL(true /* client side */, cfHostName.get()); - cf_ref trust; - OSStatus status = SecTrustCreateWithCertificates(certsArray.get(), policy.get(), &trust.get()); - if(status == noErr) - { - // Perform actual certificate verification. - SecTrustResultType trustResult; - status = SecTrustEvaluate(trust.get(), &trustResult); - if(status == noErr && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) - { - return true; - } - } - - return false; -} - -}}}} \ No newline at end of file diff --git a/Release/src/http/client/x509_cert_utilities_win32.cpp b/Release/src/http/client/x509_cert_utilities_win32.cpp deleted file mode 100644 index cd2b671424..0000000000 --- a/Release/src/http/client/x509_cert_utilities_win32.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/*** -* ==++== -* -* 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. -* -* ==--== -* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -* -* Contains utility functions for helping to verify server certificates on Windows desktop. -* -* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk -* -* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -****/ - -#include "stdafx.h" - -#include "cpprest/details/x509_cert_utilities.h" - -#include -#include - -namespace web { namespace http { namespace client { namespace details { - -// 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; - -bool verify_X509_cert_chain(const std::vector &certChain, const std::string &) -{ - // Create certificate context from server certificate. - cert_context cert(CertCreateCertificateContext( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - reinterpret_cast(certChain[0].c_str()), - static_cast(certChain[0].size()))); - if (cert == nullptr) - { - return false; - } - - // Let the OS build a certificate chain from the server certificate. - CERT_CHAIN_PARA params; - ZeroMemory(¶ms, sizeof(params)); - params.cbSize = sizeof(CERT_CHAIN_PARA); - params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - LPSTR usages [] = - { - szOID_PKIX_KP_SERVER_AUTH, - - // For older servers and to match IE. - szOID_SERVER_GATED_CRYPTO, - szOID_SGC_NETSCAPE - }; - params.RequestedUsage.Usage.cUsageIdentifier = std::extent::value; - params.RequestedUsage.Usage.rgpszUsageIdentifier = usages; - PCCERT_CHAIN_CONTEXT chainContext; - chain_context chain; - if (!CertGetCertificateChain( - nullptr, - cert.get(), - nullptr, - nullptr, - ¶ms, - CERT_CHAIN_REVOCATION_CHECK_CHAIN, - nullptr, - &chainContext)) - { - return false; - } - chain.reset(chainContext); - - // Check to see if the certificate chain is actually trusted. - if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) - { - return false; - } - - return true; -} - -}}}} \ No newline at end of file diff --git a/Release/src/http/common/http_helpers.cpp b/Release/src/http/common/http_helpers.cpp index 1424dbe382..0d60c04ea9 100644 --- a/Release/src/http/common/http_helpers.cpp +++ b/Release/src/http/common/http_helpers.cpp @@ -34,145 +34,6 @@ namespace web { namespace http namespace details { -bool is_content_type_one_of(const utility::string_t *first, const utility::string_t *last, const utility::string_t &value) -{ - while (first != last) - { - if (utility::details::str_icmp(*first, value)) - { - return true; - } - ++first; - } - return false; -} - -// Remove once VS 2013 is no longer supported. -#if defined(_WIN32) && _MSC_VER < 1900 -// Not referring to mime_types to avoid static initialization order fiasco. -static const utility::string_t textual_types [] = { - U("message/http"), - U("application/json"), - U("application/xml"), - U("application/atom+xml"), - U("application/http"), - U("application/x-www-form-urlencoded") -}; -#endif -bool is_content_type_textual(const utility::string_t &content_type) -{ -#if !defined(_WIN32) || _MSC_VER >= 1900 - static const utility::string_t textual_types [] = { - mime_types::message_http, - mime_types::application_json, - mime_types::application_xml, - mime_types::application_atom_xml, - mime_types::application_http, - mime_types::application_x_www_form_urlencoded - }; -#endif - - if (content_type.size() >= 4 && utility::details::str_icmp(content_type.substr(0, 4), _XPLATSTR("text"))) - { - return true; - } - return (is_content_type_one_of(std::begin(textual_types), std::end(textual_types), content_type)); -} - -// Remove once VS 2013 is no longer supported. -#if defined(_WIN32) && _MSC_VER < 1900 -// Not referring to mime_types to avoid static initialization order fiasco. -static const utility::string_t json_types [] = { - U("application/json"), - U("application/x-json"), - U("text/json"), - U("text/x-json"), - U("text/javascript"), - U("text/x-javascript"), - U("application/javascript"), - U("application/x-javascript") -}; -#endif -bool is_content_type_json(const utility::string_t &content_type) -{ -#if !defined(_WIN32) || _MSC_VER >= 1900 - static const utility::string_t json_types [] = { - mime_types::application_json, - mime_types::application_xjson, - mime_types::text_json, - mime_types::text_xjson, - mime_types::text_javascript, - mime_types::text_xjavascript, - mime_types::application_javascript, - mime_types::application_xjavascript - }; -#endif - - return (is_content_type_one_of(std::begin(json_types), std::end(json_types), content_type)); -} - -void parse_content_type_and_charset(const utility::string_t &content_type, utility::string_t &content, utility::string_t &charset) -{ - const size_t semi_colon_index = content_type.find_first_of(_XPLATSTR(";")); - - // No charset specified. - if (semi_colon_index == utility::string_t::npos) - { - content = content_type; - trim_whitespace(content); - charset = get_default_charset(content); - return; - } - - // Split into content type and second part which could be charset. - content = content_type.substr(0, semi_colon_index); - trim_whitespace(content); - utility::string_t possible_charset = content_type.substr(semi_colon_index + 1); - trim_whitespace(possible_charset); - const size_t equals_index = possible_charset.find_first_of(_XPLATSTR("=")); - - // No charset specified. - if (equals_index == utility::string_t::npos) - { - charset = get_default_charset(content); - return; - } - - // Split and make sure 'charset' - utility::string_t charset_key = possible_charset.substr(0, equals_index); - trim_whitespace(charset_key); - if (!utility::details::str_icmp(charset_key, _XPLATSTR("charset"))) - { - charset = get_default_charset(content); - return; - } - charset = possible_charset.substr(equals_index + 1); - // Remove the redundant ';' at the end of charset. - while (charset.back() == ';') - { - charset.pop_back(); - } - trim_whitespace(charset); - if (charset.front() == _XPLATSTR('"') && charset.back() == _XPLATSTR('"')) - { - charset = charset.substr(1, charset.size() - 2); - trim_whitespace(charset); - } -} - -utility::string_t get_default_charset(const utility::string_t &content_type) -{ - // We are defaulting everything to Latin1 except JSON which is utf-8. - if (is_content_type_json(content_type)) - { - return charset_types::utf8; - } - else - { - return charset_types::latin1; - } -} - // Remove once VS 2013 is no longer supported. #if defined(_WIN32) && _MSC_VER < 1900 static const http_status_to_phrase idToPhraseMap [] = { @@ -210,149 +71,13 @@ utility::string_t get_default_reason_phrase(status_code code) return phrase; } -// Helper function to determine byte order mark. -enum endian_ness -{ - little_endian, - big_endian, - unknown -}; -static endian_ness check_byte_order_mark(const utf16string &str) -{ - if (str.empty()) - { - return unknown; - } - const unsigned char *src = (const unsigned char *) &str[0]; - - // little endian - if (src[0] == 0xFF && src[1] == 0xFE) - { - return little_endian; - } - - // big endian - else if (src[0] == 0xFE && src[1] == 0xFF) - { - return big_endian; - } - - return unknown; -} - -utility::string_t convert_utf16_to_string_t(utf16string src) -{ -#ifdef _UTF16_STRINGS - return convert_utf16_to_utf16(std::move(src)); -#else - return convert_utf16_to_utf8(std::move(src)); -#endif -} - -std::string convert_utf16_to_utf8(utf16string src) -{ - const endian_ness endian = check_byte_order_mark(src); - switch (endian) - { - case little_endian: - return convert_utf16le_to_utf8(std::move(src), true); - case big_endian: - return convert_utf16be_to_utf8(std::move(src), true); - case unknown: - // unknown defaults to big endian. - return convert_utf16be_to_utf8(std::move(src), false); - } - __assume(0); -} - -utf16string convert_utf16_to_utf16(utf16string src) -{ - const endian_ness endian = check_byte_order_mark(src); - switch (endian) - { - case little_endian: - src.erase(0, 1); - return src; - case big_endian: - return convert_utf16be_to_utf16le(std::move(src), true); - case unknown: - // unknown defaults to big endian. - return convert_utf16be_to_utf16le(std::move(src), false); - } - __assume(0); -} - -std::string convert_utf16le_to_utf8(utf16string src, bool erase_bom) -{ - if (erase_bom && !src.empty()) - { - src.erase(0, 1); - } - return utf16_to_utf8(std::move(src)); -} - -utility::string_t convert_utf16le_to_string_t(utf16string src, bool erase_bom) -{ - if (erase_bom && !src.empty()) - { - src.erase(0, 1); - } -#ifdef _UTF16_STRINGS - return std::move(src); -#else - return utf16_to_utf8(std::move(src)); -#endif -} - -// Helper function to change endian ness from big endian to little endian -static utf16string big_endian_to_little_endian(utf16string src, bool erase_bom) -{ - if (erase_bom && !src.empty()) - { - src.erase(0, 1); - } - if (src.empty()) - { - return src; - } - - const size_t size = src.size(); - for (size_t i = 0; i < size; ++i) - { - utf16char ch = src[i]; - src[i] = static_cast(ch << 8); - src[i] = static_cast(src[i] | ch >> 8); - } - - return src; -} - -utility::string_t convert_utf16be_to_string_t(utf16string src, bool erase_bom) -{ -#ifdef _UTF16_STRINGS - return convert_utf16be_to_utf16le(std::move(src), erase_bom); -#else - return convert_utf16be_to_utf8(std::move(src), erase_bom); -#endif -} - -std::string convert_utf16be_to_utf8(utf16string src, bool erase_bom) -{ - return utf16_to_utf8(big_endian_to_little_endian(std::move(src), erase_bom)); -} - -utf16string convert_utf16be_to_utf16le(utf16string src, bool erase_bom) -{ - return big_endian_to_little_endian(std::move(src), erase_bom); -} - -void ltrim_whitespace(utility::string_t &str) +static void ltrim_whitespace(utility::string_t &str) { size_t index; for (index = 0; index < str.size() && isspace(str[index]); ++index); str.erase(0, index); } -void rtrim_whitespace(utility::string_t &str) +static void rtrim_whitespace(utility::string_t &str) { size_t index; for (index = str.size(); index > 0 && isspace(str[index - 1]); --index); diff --git a/Release/src/http/common/http_msg.cpp b/Release/src/http/common/http_msg.cpp index c655f7a989..ba236ce8d3 100644 --- a/Release/src/http/common/http_msg.cpp +++ b/Release/src/http/common/http_msg.cpp @@ -42,6 +42,146 @@ utility::string_t http_headers::content_type() const return result; } + + +/// Helper functions to convert a series of bytes from a charset to utf-8 or utf-16. +/// These APIs deal with checking for and handling byte order marker (BOM). +namespace { + enum endianness + { + little_endian, + big_endian, + unknown + }; + endianness check_byte_order_mark(const utf16string &str) + { + if (str.empty()) + { + return unknown; + } + const unsigned char *src = reinterpret_cast(str.data()); + + // little endian + if (src[0] == 0xFF && src[1] == 0xFE) + { + return little_endian; + } + + // big endian + else if (src[0] == 0xFE && src[1] == 0xFF) + { + return big_endian; + } + + return unknown; + } + + std::string convert_utf16le_to_utf8(utf16string src, bool erase_bom) + { + if (erase_bom && !src.empty()) + { + src.erase(0, 1); + } + return utf16_to_utf8(std::move(src)); + } + + utility::string_t convert_utf16le_to_string_t(utf16string src, bool erase_bom) + { + if (erase_bom && !src.empty()) + { + src.erase(0, 1); + } + #ifdef _UTF16_STRINGS + return src; + #else + return utf16_to_utf8(std::move(src)); + #endif + } + + // Helper function to change endian ness from big endian to little endian + utf16string big_endian_to_little_endian(utf16string src, bool erase_bom) + { + if (erase_bom && !src.empty()) + { + src.erase(0, 1); + } + if (src.empty()) + { + return src; + } + + const size_t size = src.size(); + for (size_t i = 0; i < size; ++i) + { + utf16char ch = src[i]; + src[i] = static_cast(ch << 8); + src[i] = static_cast(src[i] | ch >> 8); + } + + return src; + } + + std::string convert_utf16be_to_utf8(utf16string src, bool erase_bom) + { + return utf16_to_utf8(big_endian_to_little_endian(std::move(src), erase_bom)); + } + + utf16string convert_utf16be_to_utf16le(utf16string src, bool erase_bom) + { + return big_endian_to_little_endian(std::move(src), erase_bom); + } + + utility::string_t convert_utf16be_to_string_t(utf16string src, bool erase_bom) + { + #ifdef _UTF16_STRINGS + return convert_utf16be_to_utf16le(std::move(src), erase_bom); + #else + return convert_utf16be_to_utf8(std::move(src), erase_bom); + #endif + } + + std::string convert_utf16_to_utf8(utf16string src) + { + const endianness endian = check_byte_order_mark(src); + switch (endian) + { + case little_endian: + return convert_utf16le_to_utf8(std::move(src), true); + case big_endian: + return convert_utf16be_to_utf8(std::move(src), true); + case unknown: + // unknown defaults to big endian. + return convert_utf16be_to_utf8(std::move(src), false); + } + __assume(0); + } + + utf16string convert_utf16_to_utf16(utf16string src) + { + const endianness endian = check_byte_order_mark(src); + switch (endian) + { + case little_endian: + src.erase(0, 1); + return src; + case big_endian: + return convert_utf16be_to_utf16le(std::move(src), true); + case unknown: + // unknown defaults to big endian. + return convert_utf16be_to_utf16le(std::move(src), false); + } + __assume(0); + } + utility::string_t convert_utf16_to_string_t(utf16string src) + { + #ifdef _UTF16_STRINGS + return convert_utf16_to_utf16(std::move(src)); + #else + return convert_utf16_to_utf8(std::move(src)); + #endif + } +} + void http_headers::set_content_type(utility::string_t type) { m_headers[http::header_names::content_type] = std::move(type); @@ -83,6 +223,45 @@ void http_headers::set_content_length(utility::size64_t length) m_headers[http::header_names::content_length] = utility::conversions::print_string(length, std::locale::classic()); } +namespace details { + +utility::string_t flatten_http_headers(const http_headers &headers) +{ + utility::string_t flattened_headers; + for (auto iter = headers.begin(); iter != headers.end(); ++iter) + { + flattened_headers.append(iter->first); + flattened_headers.push_back(':'); + flattened_headers.append(iter->second); + flattened_headers.append(CRLF); + } + return flattened_headers; +} + +#if defined(_WIN32) +void parse_headers_string(_Inout_z_ utf16char *headersStr, http_headers &headers) +{ + utf16char *context = nullptr; + utf16char *line = wcstok_s(headersStr, CRLF, &context); + while (line != nullptr) + { + const utility::string_t header_line(line); + const size_t colonIndex = header_line.find_first_of(_XPLATSTR(":")); + if (colonIndex != utility::string_t::npos) + { + utility::string_t key = header_line.substr(0, colonIndex); + utility::string_t value = header_line.substr(colonIndex + 1, header_line.length() - colonIndex - 1); + http::details::trim_whitespace(key); + http::details::trim_whitespace(value); + headers.add(key, value); + } + line = wcstok_s(nullptr, CRLF, &context); + } +} +#endif + +} + static const utility::char_t * stream_was_set_explicitly = _XPLATSTR("A stream was set on the message and extraction is not possible"); static const utility::char_t * unsupported_charset = _XPLATSTR("Charset must be iso-8859-1, utf-8, utf-16, utf-16le, or utf-16be to be extracted."); @@ -218,13 +397,168 @@ void http_msg_base::_complete(utility::size64_t body_size, const std::exception_ } } +static bool is_content_type_one_of(const utility::string_t *first, const utility::string_t *last, const utility::string_t &value) +{ + while (first != last) + { + if (utility::details::str_icmp(*first, value)) + { + return true; + } + ++first; + } + return false; +} + +// Remove once VS 2013 is no longer supported. +#if defined(_WIN32) && _MSC_VER < 1900 +// Not referring to mime_types to avoid static initialization order fiasco. +static const utility::string_t textual_types [] = { + U("message/http"), + U("application/json"), + U("application/xml"), + U("application/atom+xml"), + U("application/http"), + U("application/x-www-form-urlencoded") +}; +#endif + +/// +/// Determines whether or not the given content type is 'textual' according the feature specifications. +/// +static bool is_content_type_textual(const utility::string_t &content_type) +{ +#if !defined(_WIN32) || _MSC_VER >= 1900 + static const utility::string_t textual_types [] = { + mime_types::message_http, + mime_types::application_json, + mime_types::application_xml, + mime_types::application_atom_xml, + mime_types::application_http, + mime_types::application_x_www_form_urlencoded + }; +#endif + + if (content_type.size() >= 4 && utility::details::str_icmp(content_type.substr(0, 4), _XPLATSTR("text"))) + { + return true; + } + return (is_content_type_one_of(std::begin(textual_types), std::end(textual_types), content_type)); +} + +// Remove once VS 2013 is no longer supported. +#if defined(_WIN32) && _MSC_VER < 1900 +// Not referring to mime_types to avoid static initialization order fiasco. +static const utility::string_t json_types [] = { + U("application/json"), + U("application/x-json"), + U("text/json"), + U("text/x-json"), + U("text/javascript"), + U("text/x-javascript"), + U("application/javascript"), + U("application/x-javascript") +}; +#endif + +/// +/// Determines whether or not the given content type is JSON according the feature specifications. +/// +static bool is_content_type_json(const utility::string_t &content_type) +{ +#if !defined(_WIN32) || _MSC_VER >= 1900 + static const utility::string_t json_types [] = { + mime_types::application_json, + mime_types::application_xjson, + mime_types::text_json, + mime_types::text_xjson, + mime_types::text_javascript, + mime_types::text_xjavascript, + mime_types::application_javascript, + mime_types::application_xjavascript + }; +#endif + + return (is_content_type_one_of(std::begin(json_types), std::end(json_types), content_type)); +} + +/// +/// Gets the default charset for given content type. If the MIME type is not textual or recognized Latin1 will be returned. +/// +static utility::string_t get_default_charset(const utility::string_t &content_type) +{ + // We are defaulting everything to Latin1 except JSON which is utf-8. + if (is_content_type_json(content_type)) + { + return charset_types::utf8; + } + else + { + return charset_types::latin1; + } +} + + +/// +/// Parses the given Content-Type header value to get out actual content type and charset. +/// If the charset isn't specified the default charset for the content type will be set. +/// +static void parse_content_type_and_charset(const utility::string_t &content_type, utility::string_t &content, utility::string_t &charset) +{ + const size_t semi_colon_index = content_type.find_first_of(_XPLATSTR(";")); + + // No charset specified. + if (semi_colon_index == utility::string_t::npos) + { + content = content_type; + trim_whitespace(content); + charset = get_default_charset(content); + return; + } + + // Split into content type and second part which could be charset. + content = content_type.substr(0, semi_colon_index); + trim_whitespace(content); + utility::string_t possible_charset = content_type.substr(semi_colon_index + 1); + trim_whitespace(possible_charset); + const size_t equals_index = possible_charset.find_first_of(_XPLATSTR("=")); + + // No charset specified. + if (equals_index == utility::string_t::npos) + { + charset = get_default_charset(content); + return; + } + + // Split and make sure 'charset' + utility::string_t charset_key = possible_charset.substr(0, equals_index); + trim_whitespace(charset_key); + if (!utility::details::str_icmp(charset_key, _XPLATSTR("charset"))) + { + charset = get_default_charset(content); + return; + } + charset = possible_charset.substr(equals_index + 1); + // Remove the redundant ';' at the end of charset. + while (charset.back() == ';') + { + charset.pop_back(); + } + trim_whitespace(charset); + if (charset.front() == _XPLATSTR('"') && charset.back() == _XPLATSTR('"')) + { + charset = charset.substr(1, charset.size() - 2); + trim_whitespace(charset); + } +} + utility::string_t details::http_msg_base::parse_and_check_content_type(bool ignore_content_type, const std::function &check_content_type) { if (!instream()) { throw http_exception(stream_was_set_explicitly); } - + utility::string_t content, charset = charset_types::utf8; if (!ignore_content_type) { diff --git a/Release/src/websockets/client/ws_client_wspp.cpp b/Release/src/websockets/client/ws_client_wspp.cpp index e8f5d2edbe..81512bc89f 100644 --- a/Release/src/websockets/client/ws_client_wspp.cpp +++ b/Release/src/websockets/client/ws_client_wspp.cpp @@ -24,10 +24,11 @@ ****/ #include "stdafx.h" -#include "cpprest/details/x509_cert_utilities.h" #if !defined(CPPREST_EXCLUDE_WEBSOCKETS) +#include "cpprest/details/x509_cert_utilities.h" + // Force websocketpp to use C++ std::error_code instead of Boost. #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ #if defined(__GNUC__) diff --git a/Release/tests/functional/http/client/pipeline_stage_tests.cpp b/Release/tests/functional/http/client/pipeline_stage_tests.cpp index 4e166e26f7..e276a013b2 100644 --- a/Release/tests/functional/http/client/pipeline_stage_tests.cpp +++ b/Release/tests/functional/http/client/pipeline_stage_tests.cpp @@ -244,7 +244,7 @@ class modify_count_responses_stage : public http_pipeline_stage { request.headers().set_content_type(U("modified content type")); - auto currentStage = current_stage(); + auto currentStage = this->shared_from_this(); return next_stage()->propagate(request).then([currentStage](http_response response) -> http_response {