1 /***
2  * Copyright (C) Microsoft. All rights reserved.
3  * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4  *
5  * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6  *
7  * HTTP Library: Client-side APIs.
8  *
9  * This file contains shared code across all http_client implementations.
10  *
11  * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
12  *
13  * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
14  ****/
15 
16 #include "stdafx.h"
17 
18 #include "http_client_impl.h"
19 
20 namespace web
21 {
22 namespace http
23 {
24 namespace client
25 {
26 // Helper function to check to make sure the uri is valid.
verify_uri(const uri & uri)27 static void verify_uri(const uri& uri)
28 {
29     // Some things like proper URI schema are verified by the URI class.
30     // We only need to check certain things specific to HTTP.
31     if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https"))
32     {
33         throw std::invalid_argument("URI scheme must be 'http' or 'https'");
34     }
35 
36     if (uri.host().empty())
37     {
38         throw std::invalid_argument("URI must contain a hostname.");
39     }
40 }
41 
42 namespace details
43 {
44 #if defined(_WIN32) || defined(CPPREST_FORCE_HTTP_CLIENT_WINHTTPPAL)
45 const utility::char_t* get_with_body_err_msg =
46     _XPLATSTR("A GET or HEAD request should not have an entity body.");
47 #endif
48 
complete_headers()49 void request_context::complete_headers()
50 {
51     // We have already read (and transmitted) the request body. Should we explicitly close the stream?
52     // Well, there are test cases that assumes that the istream is valid when t receives the response!
53     // For now, we will drop our reference which will close the stream if the user doesn't have one.
54     m_request.set_body(Concurrency::streams::istream());
55     m_request_completion.set(m_response);
56 }
57 
complete_request(utility::size64_t body_size)58 void request_context::complete_request(utility::size64_t body_size)
59 {
60     m_response._get_impl()->_complete(body_size);
61 
62     finish();
63 }
64 
report_error(unsigned long error_code,const std::string & errorMessage)65 void request_context::report_error(unsigned long error_code, const std::string& errorMessage)
66 {
67     report_exception(http_exception(static_cast<int>(error_code), errorMessage));
68 }
69 
70 #if defined(_WIN32)
report_error(unsigned long error_code,const std::wstring & errorMessage)71 void request_context::report_error(unsigned long error_code, const std::wstring& errorMessage)
72 {
73     report_exception(http_exception(static_cast<int>(error_code), errorMessage));
74 }
75 #endif
76 
report_exception(std::exception_ptr exceptionPtr)77 void request_context::report_exception(std::exception_ptr exceptionPtr)
78 {
79     auto response_impl = m_response._get_impl();
80 
81     // If cancellation has been triggered then ignore any errors.
82     if (m_request._cancellation_token().is_canceled())
83     {
84         exceptionPtr =
85             std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category()));
86     }
87 
88     // First try to complete the headers with an exception.
89     if (m_request_completion.set_exception(exceptionPtr))
90     {
91         // Complete the request with no msg body. The exception
92         // should only be propagated to one of the tce.
93         response_impl->_complete(0);
94     }
95     else
96     {
97         // Complete the request with an exception
98         response_impl->_complete(0, exceptionPtr);
99     }
100 
101     finish();
102 }
103 
handle_compression()104 bool request_context::handle_compression()
105 {
106     // If the response body is compressed we will read the encoding header and create a decompressor object which will
107     // later decompress the body
108     try
109     {
110         utility::string_t encoding;
111         http_headers& headers = m_response.headers();
112 
113         // Note that some headers, for example "Transfer-Encoding: chunked", may legitimately not produce a decompressor
114         if (m_http_client->client_config().request_compressed_response() &&
115             headers.match(web::http::header_names::content_encoding, encoding))
116         {
117             // Note that, while Transfer-Encoding (chunked only) is valid with Content-Encoding,
118             // we don't need to look for it here because winhttp de-chunks for us in that case
119             m_decompressor = compression::details::get_decompressor_from_header(
120                 encoding, compression::details::header_types::content_encoding, m_request.decompress_factories());
121         }
122         else if (!m_request.decompress_factories().empty() &&
123                  headers.match(web::http::header_names::transfer_encoding, encoding))
124         {
125             m_decompressor = compression::details::get_decompressor_from_header(
126                 encoding, compression::details::header_types::transfer_encoding, m_request.decompress_factories());
127         }
128     }
129     catch (...)
130     {
131         report_exception(std::current_exception());
132         return false;
133     }
134 
135     return true;
136 }
137 
get_compression_header() const138 utility::string_t request_context::get_compression_header() const
139 {
140     utility::string_t headers;
141 
142     // Add the correct header needed to request a compressed response if supported
143     // on this platform and it has been specified in the config and/or request
144     if (m_http_client->client_config().request_compressed_response())
145     {
146         if (!m_request.decompress_factories().empty() || web::http::compression::builtin::supported())
147         {
148             // Accept-Encoding -- request Content-Encoding from the server
149             headers.append(header_names::accept_encoding + U(": "));
150             headers.append(compression::details::build_supported_header(
151                 compression::details::header_types::accept_encoding, m_request.decompress_factories()));
152             headers.append(U("\r\n"));
153         }
154     }
155     else if (!m_request.decompress_factories().empty())
156     {
157         // TE -- request Transfer-Encoding from the server
158         headers.append(header_names::connection + U(": TE\r\n") + // Required by Section 4.3 of RFC-7230
159                        header_names::te + U(": "));
160         headers.append(compression::details::build_supported_header(compression::details::header_types::te,
161                                                                     m_request.decompress_factories()));
162         headers.append(U("\r\n"));
163     }
164 
165     return headers;
166 }
167 
_get_readbuffer()168 concurrency::streams::streambuf<uint8_t> request_context::_get_readbuffer()
169 {
170     auto instream = m_request.body();
171 
172     _ASSERTE((bool)instream);
173     return instream.streambuf();
174 }
175 
_get_writebuffer()176 concurrency::streams::streambuf<uint8_t> request_context::_get_writebuffer()
177 {
178     auto outstream = m_response._get_impl()->outstream();
179 
180     _ASSERTE((bool)outstream);
181     return outstream.streambuf();
182 }
183 
request_context(const std::shared_ptr<_http_client_communicator> & client,const http_request & request)184 request_context::request_context(const std::shared_ptr<_http_client_communicator>& client, const http_request& request)
185     : m_http_client(client), m_request(request), m_uploaded(0), m_downloaded(0)
186 {
187     auto responseImpl = m_response._get_impl();
188 
189     // Copy the user specified output stream over to the response
190     responseImpl->set_outstream(request._get_impl()->_response_stream(), false);
191 
192     // Prepare for receiving data from the network. Ideally, this should be done after
193     // we receive the headers and determine that there is a response body. We will do it here
194     // since it is not immediately apparent where that would be in the callback handler
195     responseImpl->_prepare_to_receive_data();
196 }
197 
async_send_request_impl(const std::shared_ptr<request_context> & request)198 void _http_client_communicator::async_send_request_impl(const std::shared_ptr<request_context>& request)
199 {
200     auto self = std::static_pointer_cast<_http_client_communicator>(this->shared_from_this());
201     // Schedule a task to start sending.
202     pplx::create_task([self, request] {
203         try
204         {
205             self->send_request(request);
206         }
207         catch (...)
208         {
209             request->report_exception(std::current_exception());
210         }
211     });
212 }
213 
async_send_request(const std::shared_ptr<request_context> & request)214 void _http_client_communicator::async_send_request(const std::shared_ptr<request_context>& request)
215 {
216     if (m_client_config.guarantee_order())
217     {
218         pplx::extensibility::scoped_critical_section_t l(m_client_lock);
219 
220         if (m_outstanding)
221         {
222             m_requests_queue.push(request);
223         }
224         else
225         {
226             async_send_request_impl(request);
227             m_outstanding = true;
228         }
229     }
230     else
231     {
232         async_send_request_impl(request);
233     }
234 }
235 
finish_request()236 void _http_client_communicator::finish_request()
237 {
238     // If guarantee order is specified we don't need to do anything.
239     if (m_client_config.guarantee_order())
240     {
241         pplx::extensibility::scoped_critical_section_t l(m_client_lock);
242 
243         if (m_requests_queue.empty())
244         {
245             m_outstanding = false;
246         }
247         else
248         {
249             auto request = m_requests_queue.front();
250             m_requests_queue.pop();
251 
252             async_send_request_impl(request);
253         }
254     }
255 }
256 
client_config() const257 const http_client_config& _http_client_communicator::client_config() const { return m_client_config; }
258 
base_uri() const259 const uri& _http_client_communicator::base_uri() const { return m_uri; }
260 
_http_client_communicator(http::uri && address,http_client_config && client_config)261 _http_client_communicator::_http_client_communicator(http::uri&& address, http_client_config&& client_config)
262     : m_uri(std::move(address)), m_client_config(std::move(client_config)), m_outstanding(false)
263 {
264 }
265 
finish()266 inline void request_context::finish()
267 {
268     // If cancellation is enabled and registration was performed, unregister.
269     if (m_cancellationRegistration != pplx::cancellation_token_registration())
270     {
271         _ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none());
272         m_request._cancellation_token().deregister_callback(m_cancellationRegistration);
273     }
274 
275     m_http_client->finish_request();
276 }
277 
278 } // namespace details
279 
280 /// <summary>
281 /// Private implementation of http_client. Manages the http request processing pipeline.
282 /// </summary>
283 class http_pipeline
284 {
285 public:
http_pipeline(std::shared_ptr<details::_http_client_communicator> last)286     http_pipeline(std::shared_ptr<details::_http_client_communicator> last) : m_last_stage(std::move(last)) {}
287 
288     // pplx::extensibility::recursive_lock_t does not support move/copy, but does not delete the functions either.
289     http_pipeline(const http_pipeline&) = delete;
290     http_pipeline(http_pipeline&&) = delete;
291     http_pipeline& operator=(const http_pipeline&) = delete;
292     http_pipeline& operator=(http_pipeline&&) = delete;
293 
294     /// <summary>
295     /// Initiate an http request into the pipeline
296     /// </summary>
297     /// <param name="request">Http request</param>
propagate(http_request request)298     pplx::task<http_response> propagate(http_request request)
299     {
300         std::shared_ptr<http_pipeline_stage> first;
301         {
302             pplx::extensibility::scoped_recursive_lock_t l(m_lock);
303             first = (m_stages.size() > 0) ? m_stages[0] : m_last_stage;
304         }
305         return first->propagate(request);
306     }
307 
308     /// <summary>
309     /// Adds an HTTP pipeline stage to the pipeline.
310     /// </summary>
311     /// <param name="stage">A pipeline stage.</param>
append(const std::shared_ptr<http_pipeline_stage> & stage)312     void append(const std::shared_ptr<http_pipeline_stage>& stage)
313     {
314         pplx::extensibility::scoped_recursive_lock_t l(m_lock);
315 
316         if (m_stages.size() > 0)
317         {
318             std::shared_ptr<http_pipeline_stage> penultimate = m_stages[m_stages.size() - 1];
319             penultimate->set_next_stage(stage);
320         }
321         stage->set_next_stage(m_last_stage);
322 
323         m_stages.push_back(stage);
324     }
325 
326     // The last stage is always set up by the client or listener and cannot
327     // be changed. All application-defined stages are executed before the
328     // last stage, which is typically a send or dispatch.
329     const std::shared_ptr<details::_http_client_communicator> m_last_stage;
330 
331 private:
332     // The vector of pipeline stages.
333     std::vector<std::shared_ptr<http_pipeline_stage>> m_stages;
334 
335     pplx::extensibility::recursive_lock_t m_lock;
336 };
337 
add_handler(const std::function<pplx::task<http_response> __cdecl (http_request,std::shared_ptr<http::http_pipeline_stage>)> & handler)338 void http_client::add_handler(
339     const std::function<pplx::task<http_response> __cdecl(http_request, std::shared_ptr<http::http_pipeline_stage>)>&
340         handler)
341 {
342     class function_pipeline_wrapper : public http::http_pipeline_stage
343     {
344     public:
345         function_pipeline_wrapper(const std::function<pplx::task<http_response> __cdecl(
346                                       http_request, std::shared_ptr<http::http_pipeline_stage>)>& handler)
347             : m_handler(handler)
348         {
349         }
350 
351         virtual pplx::task<http_response> propagate(http_request request) override
352         {
353             return m_handler(std::move(request), next_stage());
354         }
355 
356     private:
357         std::function<pplx::task<http_response>(http_request, std::shared_ptr<http::http_pipeline_stage>)> m_handler;
358     };
359 
360     m_pipeline->append(std::make_shared<function_pipeline_wrapper>(handler));
361 }
362 
add_handler(const std::shared_ptr<http::http_pipeline_stage> & stage)363 void http_client::add_handler(const std::shared_ptr<http::http_pipeline_stage>& stage) { m_pipeline->append(stage); }
364 
http_client(const uri & base_uri)365 http_client::http_client(const uri& base_uri) : http_client(base_uri, http_client_config()) {}
366 
http_client(const uri & base_uri,const http_client_config & client_config)367 http_client::http_client(const uri& base_uri, const http_client_config& client_config)
368 {
369     std::shared_ptr<details::_http_client_communicator> final_pipeline_stage;
370 
371     if (base_uri.scheme().empty())
372     {
373         auto uribuilder = uri_builder(base_uri);
374         uribuilder.set_scheme(_XPLATSTR("http"));
375         uri uriWithScheme = uribuilder.to_uri();
376         verify_uri(uriWithScheme);
377         final_pipeline_stage =
378             details::create_platform_final_pipeline_stage(std::move(uriWithScheme), http_client_config(client_config));
379     }
380     else
381     {
382         verify_uri(base_uri);
383         final_pipeline_stage =
384             details::create_platform_final_pipeline_stage(uri(base_uri), http_client_config(client_config));
385     }
386 
387     m_pipeline = std::make_shared<http_pipeline>(std::move(final_pipeline_stage));
388 
389 #if _WIN32_WINNT >= _WIN32_WINNT_VISTA
390     add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
391         std::make_shared<oauth1::details::oauth1_handler>(client_config.oauth1())));
392 #endif
393 
394     add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
395         std::make_shared<oauth2::details::oauth2_handler>(client_config.oauth2())));
396 }
397 
~http_client()398 http_client::~http_client() CPPREST_NOEXCEPT {}
399 
client_config() const400 const http_client_config& http_client::client_config() const { return m_pipeline->m_last_stage->client_config(); }
401 
base_uri() const402 const uri& http_client::base_uri() const { return m_pipeline->m_last_stage->base_uri(); }
403 
404 // Macros to help build string at compile time and avoid overhead.
405 #define STRINGIFY(x) _XPLATSTR(#x)
406 #define TOSTRING(x) STRINGIFY(x)
407 #define USERAGENT                                                                                                      \
408     _XPLATSTR("cpprestsdk/")                                                                                           \
409     TOSTRING(CPPREST_VERSION_MAJOR)                                                                                    \
410     _XPLATSTR(".") TOSTRING(CPPREST_VERSION_MINOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_REVISION)
411 
request(http_request request,const pplx::cancellation_token & token)412 pplx::task<http_response> http_client::request(http_request request, const pplx::cancellation_token& token)
413 {
414     if (!request.headers().has(header_names::user_agent))
415     {
416         request.headers().add(header_names::user_agent, USERAGENT);
417     }
418 
419     request._set_base_uri(base_uri());
420     request._set_cancellation_token(token);
421     return m_pipeline->propagate(request);
422 }
423 
424 } // namespace client
425 } // namespace http
426 } // namespace web
427