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 a cross platform implementation based on Boost.ASIO.
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 "../common/connection_pool_helpers.h"
19 #include "../common/internal_http_helpers.h"
20 #include "cpprest/asyncrt_utils.h"
21 #include <sstream>
22 
23 #if defined(__clang__)
24 #pragma clang diagnostic push
25 #pragma clang diagnostic ignored "-Wunused-local-typedef"
26 #pragma clang diagnostic ignored "-Winfinite-recursion"
27 #endif
28 #include <boost/algorithm/string.hpp>
29 #include <boost/asio.hpp>
30 #include <boost/asio/ssl.hpp>
31 #include <boost/asio/ssl/error.hpp>
32 #include <boost/asio/steady_timer.hpp>
33 #include <boost/bind.hpp>
34 #if defined(__clang__)
35 #pragma clang diagnostic pop
36 #endif
37 
38 #if defined(BOOST_NO_CXX11_SMART_PTR)
39 #error "Cpp rest SDK requires c++11 smart pointer support from boost"
40 #endif
41 
42 #include "../common/x509_cert_utilities.h"
43 #include "cpprest/base_uri.h"
44 #include "cpprest/details/http_helpers.h"
45 #include "http_client_impl.h"
46 #include "pplx/threadpool.h"
47 #include <memory>
48 #include <unordered_set>
49 
50 #if defined(__GNUC__) && !defined(__clang__)
51 
52 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
53 #define AND_CAPTURE_MEMBER_FUNCTION_POINTERS
54 #else
55 // GCC Bug 56222 - Pointer to member in lambda should not require this to be captured
56 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56222
57 // GCC Bug 51494 - Legal program rejection - capturing "this" when using static method inside lambda
58 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51494
59 #define AND_CAPTURE_MEMBER_FUNCTION_POINTERS , this
60 #endif
61 
62 #elif defined(_MSC_VER)
63 
64 #if _MSC_VER >= 1900
65 #define AND_CAPTURE_MEMBER_FUNCTION_POINTERS
66 #else
67 // This bug also afflicts VS2013 which incorrectly reports "warning C4573: the usage of 'symbol' requires the compiler
68 // to capture 'this' but the current default capture mode does not allow it"
69 #define AND_CAPTURE_MEMBER_FUNCTION_POINTERS , this
70 #endif
71 
72 #else
73 
74 #define AND_CAPTURE_MEMBER_FUNCTION_POINTERS
75 
76 #endif
77 
78 using boost::asio::ip::tcp;
79 
80 #ifdef __ANDROID__
81 using utility::conversions::details::to_string;
82 #else
83 using std::to_string;
84 #endif
85 
86 namespace
87 {
88 const std::string CRLF("\r\n");
89 
calc_cn_host(const web::http::uri & baseUri,const web::http::http_headers & requestHeaders)90 std::string calc_cn_host(const web::http::uri& baseUri, const web::http::http_headers& requestHeaders)
91 {
92     std::string result;
93     if (baseUri.scheme() == U("https"))
94     {
95         const utility::string_t* encResult;
96         const auto hostHeader = requestHeaders.find(_XPLATSTR("Host"));
97         if (hostHeader == requestHeaders.end())
98         {
99             encResult = &baseUri.host();
100         }
101         else
102         {
103             encResult = &hostHeader->second;
104         }
105 
106         result = utility::conversions::to_utf8string(*encResult);
107         utility::details::inplace_tolower(result);
108     }
109 
110     return result;
111 }
112 } // namespace
113 
114 namespace web
115 {
116 namespace http
117 {
118 namespace client
119 {
120 namespace details
121 {
122 enum class httpclient_errorcode_context
123 {
124     none = 0,
125     connect,
126     handshake,
127     writeheader,
128     writebody,
129     readheader,
130     readbody,
131     close
132 };
133 
generate_base64_userpass(const::web::credentials & creds)134 static std::string generate_base64_userpass(const ::web::credentials& creds)
135 {
136     auto userpass = creds.username() + U(":") + *creds._internal_decrypt();
137     auto&& u8_userpass = utility::conversions::to_utf8string(userpass);
138     std::vector<unsigned char> credentials_buffer(u8_userpass.begin(), u8_userpass.end());
139     return utility::conversions::to_utf8string(utility::conversions::to_base64(credentials_buffer));
140 }
141 
142 class asio_connection_pool;
143 
144 class asio_connection
145 {
146     friend class asio_client;
147 
148 public:
asio_connection(boost::asio::io_service & io_service)149     asio_connection(boost::asio::io_service& io_service)
150         : m_socket_lock()
151         , m_socket(io_service)
152         , m_ssl_stream()
153         , m_cn_hostname()
154         , m_is_reused(false)
155         , m_keep_alive(true)
156         , m_closed(false)
157     {
158     }
159 
~asio_connection()160     ~asio_connection() { close(); }
161 
162     // This simply instantiates the internal state to support ssl. It does not perform the handshake.
upgrade_to_ssl(std::string && cn_hostname,const std::function<void (boost::asio::ssl::context &)> & ssl_context_callback)163     void upgrade_to_ssl(std::string&& cn_hostname,
164                         const std::function<void(boost::asio::ssl::context&)>& ssl_context_callback)
165     {
166         std::lock_guard<std::mutex> lock(m_socket_lock);
167         assert(!is_ssl());
168         boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23);
169         ssl_context.set_default_verify_paths();
170         ssl_context.set_options(boost::asio::ssl::context::default_workarounds);
171         if (ssl_context_callback)
172         {
173             ssl_context_callback(ssl_context);
174         }
175         m_ssl_stream = utility::details::make_unique<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>(
176             m_socket, ssl_context);
177         m_cn_hostname = std::move(cn_hostname);
178     }
179 
close()180     void close()
181     {
182         std::lock_guard<std::mutex> lock(m_socket_lock);
183 
184         // Ensures closed connections owned by request_context will not be put to pool when they are released.
185         m_keep_alive = false;
186         m_closed = true;
187 
188         boost::system::error_code error;
189         m_socket.shutdown(tcp::socket::shutdown_both, error);
190         m_socket.close(error);
191     }
192 
cancel()193     boost::system::error_code cancel()
194     {
195         std::lock_guard<std::mutex> lock(m_socket_lock);
196         boost::system::error_code error;
197         m_socket.cancel(error);
198         return error;
199     }
200 
is_reused() const201     bool is_reused() const { return m_is_reused; }
set_keep_alive(bool keep_alive)202     void set_keep_alive(bool keep_alive) { m_keep_alive = keep_alive; }
keep_alive() const203     bool keep_alive() const { return m_keep_alive; }
is_ssl() const204     bool is_ssl() const { return m_ssl_stream ? true : false; }
cn_hostname() const205     const std::string& cn_hostname() const { return m_cn_hostname; }
206 
207     // Check if the error code indicates that the connection was closed by the
208     // server: this is used to detect if a connection in the pool was closed during
209     // its period of inactivity and we should reopen it.
was_reused_and_closed_by_server(const boost::system::error_code & ec) const210     bool was_reused_and_closed_by_server(const boost::system::error_code& ec) const
211     {
212         if (!is_reused())
213         {
214             // Don't bother reopening the connection if it's a new one: in this
215             // case, even if the connection was really lost, it's still a real
216             // error and we shouldn't try to reopen it.
217             return false;
218         }
219 
220         // These errors tell if connection was closed.
221         if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec) ||
222             (boost::asio::error::connection_aborted == ec))
223         {
224             return true;
225         }
226 
227         if (is_ssl())
228         {
229             // For SSL connections, we can also get a different error due to
230             // incorrect secure connection shutdown if it was closed by the
231             // server due to inactivity. Unfortunately, the exact error we get
232             // in this case depends on the Boost.Asio version used.
233 #if BOOST_ASIO_VERSION >= 101008
234             if (boost::asio::ssl::error::stream_truncated == ec) return true;
235 #else // Asio < 1.10.8 didn't have ssl::error::stream_truncated
236             if (boost::system::error_code(ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ),
237                                           boost::asio::error::get_ssl_category()) == ec)
238                 return true;
239 #endif
240         }
241 
242         return false;
243     }
244 
245     template<typename Iterator, typename Handler>
async_connect(const Iterator & begin,const Handler & handler)246     void async_connect(const Iterator& begin, const Handler& handler)
247     {
248         {
249             std::lock_guard<std::mutex> lock(m_socket_lock);
250             if (!m_closed)
251             {
252                 m_socket.async_connect(begin, handler);
253                 return;
254             }
255         } // unlock
256 
257         handler(boost::asio::error::operation_aborted);
258     }
259 
260     template<typename HandshakeHandler, typename CertificateHandler>
async_handshake(boost::asio::ssl::stream_base::handshake_type type,const http_client_config & config,const HandshakeHandler & handshake_handler,const CertificateHandler & cert_handler)261     void async_handshake(boost::asio::ssl::stream_base::handshake_type type,
262                          const http_client_config& config,
263                          const HandshakeHandler& handshake_handler,
264                          const CertificateHandler& cert_handler)
265     {
266         std::lock_guard<std::mutex> lock(m_socket_lock);
267         assert(is_ssl());
268 
269         // Check to turn on/off server certificate verification.
270         if (config.validate_certificates())
271         {
272             m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_peer);
273             m_ssl_stream->set_verify_callback(cert_handler);
274         }
275         else
276         {
277             m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_none);
278         }
279 
280         // Check to set host name for Server Name Indication (SNI)
281         if (config.is_tlsext_sni_enabled())
282         {
283             SSL_set_tlsext_host_name(m_ssl_stream->native_handle(), &m_cn_hostname[0]);
284         }
285 
286         m_ssl_stream->async_handshake(type, handshake_handler);
287     }
288 
289     template<typename ConstBufferSequence, typename Handler>
async_write(ConstBufferSequence & buffer,const Handler & writeHandler)290     void async_write(ConstBufferSequence& buffer, const Handler& writeHandler)
291     {
292         std::lock_guard<std::mutex> lock(m_socket_lock);
293         if (m_ssl_stream)
294         {
295             boost::asio::async_write(*m_ssl_stream, buffer, writeHandler);
296         }
297         else
298         {
299             boost::asio::async_write(m_socket, buffer, writeHandler);
300         }
301     }
302 
303     template<typename MutableBufferSequence, typename CompletionCondition, typename Handler>
async_read(MutableBufferSequence & buffer,const CompletionCondition & condition,const Handler & readHandler)304     void async_read(MutableBufferSequence& buffer, const CompletionCondition& condition, const Handler& readHandler)
305     {
306         std::lock_guard<std::mutex> lock(m_socket_lock);
307         if (m_ssl_stream)
308         {
309             boost::asio::async_read(*m_ssl_stream, buffer, condition, readHandler);
310         }
311         else
312         {
313             boost::asio::async_read(m_socket, buffer, condition, readHandler);
314         }
315     }
316 
317     template<typename Handler>
async_read_until(boost::asio::streambuf & buffer,const std::string & delim,const Handler & readHandler)318     void async_read_until(boost::asio::streambuf& buffer, const std::string& delim, const Handler& readHandler)
319     {
320         std::lock_guard<std::mutex> lock(m_socket_lock);
321         if (m_ssl_stream)
322         {
323             boost::asio::async_read_until(*m_ssl_stream, buffer, delim, readHandler);
324         }
325         else
326         {
327             boost::asio::async_read_until(m_socket, buffer, delim, readHandler);
328         }
329     }
330 
start_reuse()331     void start_reuse() { m_is_reused = true; }
332 
enable_no_delay()333     void enable_no_delay()
334     {
335         boost::asio::ip::tcp::no_delay option(true);
336         boost::system::error_code error_ignored;
337         m_socket.set_option(option, error_ignored);
338     }
339 
340 private:
341     // Guards concurrent access to socket/ssl::stream. This is necessary
342     // because timeouts and cancellation can touch the socket at the same time
343     // as normal message processing.
344     std::mutex m_socket_lock;
345     tcp::socket m_socket;
346     std::unique_ptr<boost::asio::ssl::stream<tcp::socket&>> m_ssl_stream;
347     std::string m_cn_hostname;
348 
349     bool m_is_reused;
350     bool m_keep_alive;
351     bool m_closed;
352 };
353 
354 /// <summary>Implements a connection pool with adaptive connection removal</summary>
355 /// <remarks>
356 /// Every 30 seconds, the lambda in `start_epoch_interval` fires, triggering the
357 /// cleanup of any connections that have resided in the pool since the last
358 /// cleanup phase.
359 ///
360 /// During the cleanup phase, connections are removed starting with the oldest. This
361 /// ensures that if a high intensity workload is followed by a low intensity workload,
362 /// the connection pool will correctly adapt to the low intensity workload.
363 ///
364 /// Specifically, the following code will eventually result in a maximum of one pooled
365 /// connection regardless of the initial number of pooled connections:
366 /// <code>
367 ///   while(1)
368 ///   {
369 ///     auto conn = pool.try_acquire();
370 ///     if (!conn) conn = new_conn();
371 ///     pool.release(std::move(conn));
372 ///   }
373 /// </code>
374 /// </remarks>
375 class asio_connection_pool final : public std::enable_shared_from_this<asio_connection_pool>
376 {
377 public:
asio_connection_pool()378     asio_connection_pool()
379         : m_lock()
380         , m_connections()
381         , m_is_timer_running(false)
382         , m_pool_epoch_timer(crossplat::threadpool::shared_instance().service())
383     {
384     }
385 
386     asio_connection_pool(const asio_connection_pool&) = delete;
387     asio_connection_pool& operator=(const asio_connection_pool&) = delete;
388 
try_acquire(const std::string & cn_hostname)389     std::shared_ptr<asio_connection> try_acquire(const std::string& cn_hostname)
390     {
391         std::lock_guard<std::mutex> lock(m_lock);
392         if (m_connections.empty())
393         {
394             return nullptr;
395         }
396 
397         auto conn = m_connections[cn_hostname].try_acquire();
398         if (conn)
399         {
400             conn->start_reuse();
401         }
402 
403         return conn;
404     }
405 
release(std::shared_ptr<asio_connection> && connection)406     void release(std::shared_ptr<asio_connection>&& connection)
407     {
408         connection->cancel();
409         if (!connection->keep_alive())
410         {
411             connection.reset();
412             return;
413         }
414 
415         std::lock_guard<std::mutex> lock(m_lock);
416         if (!m_is_timer_running)
417         {
418             start_epoch_interval(shared_from_this());
419             m_is_timer_running = true;
420         }
421 
422         m_connections[connection->cn_hostname()].release(std::move(connection));
423     }
424 
425 private:
426     // Note: must be called under m_lock
start_epoch_interval(const std::shared_ptr<asio_connection_pool> & pool)427     static void start_epoch_interval(const std::shared_ptr<asio_connection_pool>& pool)
428     {
429         auto& self = *pool;
430         std::weak_ptr<asio_connection_pool> weak_pool = pool;
431 
432         self.m_pool_epoch_timer.expires_from_now(boost::posix_time::seconds(30));
433         self.m_pool_epoch_timer.async_wait([weak_pool](const boost::system::error_code& ec) {
434             if (ec)
435             {
436                 return;
437             }
438 
439             auto pool = weak_pool.lock();
440             if (!pool)
441             {
442                 return;
443             }
444 
445             auto& self = *pool;
446             std::lock_guard<std::mutex> lock(self.m_lock);
447             bool restartTimer = false;
448             for (auto& entry : self.m_connections)
449             {
450                 if (entry.second.free_stale_connections())
451                 {
452                     restartTimer = true;
453                 }
454             }
455 
456             if (restartTimer)
457             {
458                 start_epoch_interval(pool);
459             }
460             else
461             {
462                 self.m_is_timer_running = false;
463             }
464         });
465     }
466 
467     std::mutex m_lock;
468     std::map<std::string, connection_pool_stack<asio_connection>> m_connections;
469     bool m_is_timer_running;
470     boost::asio::deadline_timer m_pool_epoch_timer;
471 };
472 
473 class asio_client final : public _http_client_communicator
474 {
475 public:
asio_client(http::uri && address,http_client_config && client_config)476     asio_client(http::uri&& address, http_client_config&& client_config)
477         : _http_client_communicator(std::move(address), std::move(client_config))
478         , m_pool(std::make_shared<asio_connection_pool>())
479     {
480     }
481 
482     virtual void send_request(const std::shared_ptr<request_context>& request_ctx) override;
483 
release_connection(std::shared_ptr<asio_connection> && conn)484     void release_connection(std::shared_ptr<asio_connection>&& conn) { m_pool->release(std::move(conn)); }
485 
obtain_connection(const http_request & req)486     std::shared_ptr<asio_connection> obtain_connection(const http_request& req)
487     {
488         std::string cn_host = calc_cn_host(base_uri(), req.headers());
489         std::shared_ptr<asio_connection> conn = m_pool->try_acquire(cn_host);
490         if (conn == nullptr)
491         {
492             // Pool was empty. Create a new connection
493             conn = std::make_shared<asio_connection>(crossplat::threadpool::shared_instance().service());
494             if (base_uri().scheme() == U("https") && !this->client_config().proxy().is_specified())
495             {
496                 conn->upgrade_to_ssl(std::move(cn_host), this->client_config().get_ssl_context_callback());
497             }
498         }
499 
500         return conn;
501     }
502 
503     virtual pplx::task<http_response> propagate(http_request request) override;
504 
505 private:
506     const std::shared_ptr<asio_connection_pool> m_pool;
507 };
508 
509 class asio_context final : public request_context, public std::enable_shared_from_this<asio_context>
510 {
511     friend class asio_client;
512 
513 public:
asio_context(const std::shared_ptr<_http_client_communicator> & client,http_request & request,const std::shared_ptr<asio_connection> & connection)514     asio_context(const std::shared_ptr<_http_client_communicator>& client,
515                  http_request& request,
516                  const std::shared_ptr<asio_connection>& connection)
517         : request_context(client, request)
518         , m_content_length(0)
519         , m_needChunked(false)
520         , m_timer(client->client_config().timeout<std::chrono::microseconds>())
521         , m_resolver(crossplat::threadpool::shared_instance().service())
522         , m_connection(connection)
523 #ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
524         , m_openssl_failed(false)
525 #endif // CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
526     {
527     }
528 
~asio_context()529     virtual ~asio_context()
530     {
531         m_timer.stop();
532         // Release connection back to the pool. If connection was not closed, it will be put to the pool for reuse.
533         std::static_pointer_cast<asio_client>(m_http_client)->release_connection(std::move(m_connection));
534     }
535 
create_request_context(std::shared_ptr<_http_client_communicator> & client,http_request & request)536     static std::shared_ptr<request_context> create_request_context(std::shared_ptr<_http_client_communicator>& client,
537                                                                    http_request& request)
538     {
539         auto client_cast(std::static_pointer_cast<asio_client>(client));
540         auto connection(client_cast->obtain_connection(request));
541         auto ctx = std::make_shared<asio_context>(client, request, connection);
542         ctx->m_timer.set_ctx(std::weak_ptr<asio_context>(ctx));
543         return ctx;
544     }
545 
546     class ssl_proxy_tunnel final : public std::enable_shared_from_this<ssl_proxy_tunnel>
547     {
548     public:
ssl_proxy_tunnel(std::shared_ptr<asio_context> context,std::function<void (std::shared_ptr<asio_context>)> ssl_tunnel_established)549         ssl_proxy_tunnel(std::shared_ptr<asio_context> context,
550                          std::function<void(std::shared_ptr<asio_context>)> ssl_tunnel_established)
551             : m_ssl_tunnel_established(ssl_tunnel_established), m_context(context)
552         {
553         }
554 
start_proxy_connect()555         void start_proxy_connect()
556         {
557             auto proxy = m_context->m_http_client->client_config().proxy();
558             auto proxy_uri = proxy.address();
559 
560             utility::string_t proxy_host = proxy_uri.host();
561             int proxy_port = proxy_uri.port() == -1 ? 8080 : proxy_uri.port();
562 
563             const auto& base_uri = m_context->m_http_client->base_uri();
564             const auto& host = utility::conversions::to_utf8string(base_uri.host());
565             const int portRaw = base_uri.port();
566             const int port = (portRaw != 0) ? portRaw : 443;
567 
568             std::ostream request_stream(&m_request);
569             request_stream.imbue(std::locale::classic());
570 
571             request_stream << "CONNECT " << host << ":" << port << " HTTP/1.1\r\n";
572             request_stream << "Host: " << host << ":" << port << CRLF;
573             request_stream << "Proxy-Connection: Keep-Alive\r\n";
574 
575             if (m_context->m_http_client->client_config().proxy().credentials().is_set())
576             {
577                 request_stream << m_context->generate_basic_proxy_auth_header();
578             }
579 
580             request_stream << CRLF;
581 
582             m_context->m_timer.start();
583 
584             tcp::resolver::query query(utility::conversions::to_utf8string(proxy_host), to_string(proxy_port));
585 
586             auto client = std::static_pointer_cast<asio_client>(m_context->m_http_client);
587             m_context->m_resolver.async_resolve(query,
588                                                 boost::bind(&ssl_proxy_tunnel::handle_resolve,
589                                                             shared_from_this(),
590                                                             boost::asio::placeholders::error,
591                                                             boost::asio::placeholders::iterator));
592         }
593 
594     private:
handle_resolve(const boost::system::error_code & ec,tcp::resolver::iterator endpoints)595         void handle_resolve(const boost::system::error_code& ec, tcp::resolver::iterator endpoints)
596         {
597             if (ec)
598             {
599                 m_context->report_error("Error resolving proxy address", ec, httpclient_errorcode_context::connect);
600             }
601             else
602             {
603                 m_context->m_timer.reset();
604                 auto endpoint = *endpoints;
605                 m_context->m_connection->async_connect(endpoint,
606                                                        boost::bind(&ssl_proxy_tunnel::handle_tcp_connect,
607                                                                    shared_from_this(),
608                                                                    boost::asio::placeholders::error,
609                                                                    ++endpoints));
610             }
611         }
612 
handle_tcp_connect(const boost::system::error_code & ec,tcp::resolver::iterator endpoints)613         void handle_tcp_connect(const boost::system::error_code& ec, tcp::resolver::iterator endpoints)
614         {
615             if (!ec)
616             {
617                 m_context->m_timer.reset();
618                 m_context->m_connection->enable_no_delay();
619                 m_context->m_connection->async_write(m_request,
620                                                      boost::bind(&ssl_proxy_tunnel::handle_write_request,
621                                                                  shared_from_this(),
622                                                                  boost::asio::placeholders::error));
623             }
624             else if (endpoints == tcp::resolver::iterator())
625             {
626                 m_context->report_error(
627                     "Failed to connect to any resolved proxy endpoint", ec, httpclient_errorcode_context::connect);
628             }
629             else
630             {
631                 m_context->m_timer.reset();
632                 //// Replace the connection. This causes old connection object to go out of scope.
633                 auto client = std::static_pointer_cast<asio_client>(m_context->m_http_client);
634                 try
635                 {
636                     m_context->m_connection = client->obtain_connection(m_context->m_request);
637                 }
638                 catch (...)
639                 {
640                     m_context->report_exception(std::current_exception());
641                     return;
642                 }
643 
644                 auto endpoint = *endpoints;
645                 m_context->m_connection->async_connect(endpoint,
646                                                        boost::bind(&ssl_proxy_tunnel::handle_tcp_connect,
647                                                                    shared_from_this(),
648                                                                    boost::asio::placeholders::error,
649                                                                    ++endpoints));
650             }
651         }
652 
handle_write_request(const boost::system::error_code & err)653         void handle_write_request(const boost::system::error_code& err)
654         {
655             if (!err)
656             {
657                 m_context->m_timer.reset();
658                 m_context->m_connection->async_read_until(m_response,
659                                                           CRLF + CRLF,
660                                                           boost::bind(&ssl_proxy_tunnel::handle_status_line,
661                                                                       shared_from_this(),
662                                                                       boost::asio::placeholders::error));
663             }
664             else
665             {
666                 m_context->report_error(
667                     "Failed to send connect request to proxy.", err, httpclient_errorcode_context::writebody);
668             }
669         }
670 
handle_status_line(const boost::system::error_code & ec)671         void handle_status_line(const boost::system::error_code& ec)
672         {
673             if (!ec)
674             {
675                 m_context->m_timer.reset();
676                 std::istream response_stream(&m_response);
677                 response_stream.imbue(std::locale::classic());
678                 std::string http_version;
679                 response_stream >> http_version;
680                 status_code status_code;
681                 response_stream >> status_code;
682 
683                 if (!response_stream || http_version.substr(0, 5) != "HTTP/")
684                 {
685                     m_context->report_error("Invalid HTTP status line during proxy connection",
686                                             ec,
687                                             httpclient_errorcode_context::readheader);
688                     return;
689                 }
690 
691                 if (status_code != 200)
692                 {
693                     m_context->report_error("Expected a 200 response from proxy, received: " + to_string(status_code),
694                                             ec,
695                                             httpclient_errorcode_context::readheader);
696                     return;
697                 }
698 
699                 try
700                 {
701                     m_context->upgrade_to_ssl();
702                 }
703                 catch (...)
704                 {
705                     m_context->report_exception(std::current_exception());
706                     return;
707                 }
708 
709                 m_ssl_tunnel_established(m_context);
710             }
711             else
712             {
713                 m_context->handle_failed_read_status_line(ec, "Failed to read HTTP status line from proxy");
714             }
715         }
716 
717         std::function<void(std::shared_ptr<asio_context>)> m_ssl_tunnel_established;
718         std::shared_ptr<asio_context> m_context;
719 
720         boost::asio::streambuf m_request;
721         boost::asio::streambuf m_response;
722     };
723 
724     enum class http_proxy_type
725     {
726         none,
727         http,
728         ssl_tunnel
729     };
730 
start_request()731     void start_request()
732     {
733         if (m_request._cancellation_token().is_canceled())
734         {
735             request_context::report_error(make_error_code(std::errc::operation_canceled).value(),
736                                           "Request canceled by user.");
737             return;
738         }
739 
740         http_proxy_type proxy_type = http_proxy_type::none;
741         std::string proxy_host;
742         int proxy_port = -1;
743 
744         // There is no support for auto-detection of proxies on non-windows platforms, it must be specified explicitly
745         // from the client code.
746         if (m_http_client->client_config().proxy().is_specified())
747         {
748             proxy_type =
749                 m_http_client->base_uri().scheme() == U("https") ? http_proxy_type::ssl_tunnel : http_proxy_type::http;
750             auto proxy = m_http_client->client_config().proxy();
751             auto proxy_uri = proxy.address();
752             proxy_port = proxy_uri.port() == -1 ? 8080 : proxy_uri.port();
753             proxy_host = utility::conversions::to_utf8string(proxy_uri.host());
754         }
755 
756         auto start_http_request_flow = [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS](
757                                            std::shared_ptr<asio_context> ctx) {
758             if (ctx->m_request._cancellation_token().is_canceled())
759             {
760                 ctx->request_context::report_error(make_error_code(std::errc::operation_canceled).value(),
761                                                    "Request canceled by user.");
762                 return;
763             }
764 
765             const auto& base_uri = ctx->m_http_client->base_uri();
766             const auto full_uri = uri_builder(base_uri).append(ctx->m_request.relative_uri()).to_uri();
767 
768             // For a normal http proxy, we need to specify the full request uri, otherwise just specify the resource
769             auto encoded_resource =
770                 proxy_type == http_proxy_type::http ? full_uri.to_string() : full_uri.resource().to_string();
771 
772             if (encoded_resource.empty())
773             {
774                 encoded_resource = U("/");
775             }
776 
777             const auto& method = ctx->m_request.method();
778 
779             // stop injection of headers via method
780             // resource should be ok, since it's been encoded
781             // and host won't resolve
782             if (!::web::http::details::validate_method(method))
783             {
784                 ctx->report_exception(http_exception("The method string is invalid."));
785                 return;
786             }
787 
788             std::ostream request_stream(&ctx->m_body_buf);
789             request_stream.imbue(std::locale::classic());
790             const auto& host = utility::conversions::to_utf8string(base_uri.host());
791 
792             request_stream << utility::conversions::to_utf8string(method) << " "
793                            << utility::conversions::to_utf8string(encoded_resource) << " "
794                            << "HTTP/1.1\r\n";
795 
796             int port = base_uri.port();
797 
798             if (base_uri.is_port_default())
799             {
800                 port = (ctx->m_connection->is_ssl() ? 443 : 80);
801             }
802 
803             // Add the Host header if user has not specified it explicitly
804             if (!ctx->m_request.headers().has(header_names::host))
805             {
806                 request_stream << "Host: " << host;
807                 if (!base_uri.is_port_default())
808                 {
809                     request_stream << ":" << port;
810                 }
811                 request_stream << CRLF;
812             }
813 
814             // Extra request headers are constructed here.
815             std::string extra_headers;
816 
817             // Add header for basic proxy authentication
818             if (proxy_type == http_proxy_type::http &&
819                 ctx->m_http_client->client_config().proxy().credentials().is_set())
820             {
821                 extra_headers.append(ctx->generate_basic_proxy_auth_header());
822             }
823 
824             if (ctx->m_http_client->client_config().credentials().is_set())
825             {
826                 extra_headers.append(ctx->generate_basic_auth_header());
827             }
828 
829             extra_headers += utility::conversions::to_utf8string(ctx->get_compression_header());
830 
831             // Check user specified transfer-encoding.
832             std::string transferencoding;
833             if (ctx->m_request.headers().match(header_names::transfer_encoding, transferencoding) &&
834                 boost::icontains(transferencoding, U("chunked")))
835             {
836                 ctx->m_needChunked = true;
837             }
838             else if (!ctx->m_request.headers().match(header_names::content_length, ctx->m_content_length))
839             {
840                 // Stream without content length is the signal of requiring transfer encoding chunked.
841                 if (ctx->m_request.body())
842                 {
843                     ctx->m_needChunked = true;
844                     extra_headers.append("Transfer-Encoding:chunked\r\n");
845                 }
846                 else if (ctx->m_request.method() == methods::POST || ctx->m_request.method() == methods::PUT)
847                 {
848                     // Some servers do not accept POST/PUT requests with a content length of 0, such as
849                     // lighttpd - http://serverfault.com/questions/315849/curl-post-411-length-required
850                     // old apache versions - https://issues.apache.org/jira/browse/TS-2902
851                     extra_headers.append("Content-Length: 0\r\n");
852                 }
853             }
854 
855             if (proxy_type == http_proxy_type::http)
856             {
857                 extra_headers.append("Cache-Control: no-store, no-cache\r\n"
858                                      "Pragma: no-cache\r\n");
859             }
860 
861             request_stream << utility::conversions::to_utf8string(
862                 ::web::http::details::flatten_http_headers(ctx->m_request.headers()));
863             request_stream << extra_headers;
864             // Enforce HTTP connection keep alive (even for the old HTTP/1.0 protocol).
865             request_stream << "Connection: Keep-Alive\r\n\r\n";
866 
867             // Start connection timeout timer.
868             if (!ctx->m_timer.has_started())
869             {
870                 ctx->m_timer.start();
871             }
872 
873             if (ctx->m_connection->is_reused() || proxy_type == http_proxy_type::ssl_tunnel)
874             {
875                 // If socket is a reused connection or we're connected via an ssl-tunneling proxy, try to write the
876                 // request directly. In both cases we have already established a tcp connection.
877                 ctx->write_request();
878             }
879             else
880             {
881                 // If the connection is new (unresolved and unconnected socket), then start async
882                 // call to resolve first, leading eventually to request write.
883 
884                 // For normal http proxies, we want to connect directly to the proxy server. It will relay our request.
885                 auto tcp_host = proxy_type == http_proxy_type::http ? proxy_host : host;
886                 auto tcp_port = proxy_type == http_proxy_type::http ? proxy_port : port;
887 
888                 tcp::resolver::query query(tcp_host, to_string(tcp_port));
889                 ctx->m_resolver.async_resolve(query,
890                                               boost::bind(&asio_context::handle_resolve,
891                                                           ctx,
892                                                           boost::asio::placeholders::error,
893                                                           boost::asio::placeholders::iterator));
894             }
895 
896             // Register for notification on cancellation to abort this request.
897             if (ctx->m_request._cancellation_token() != pplx::cancellation_token::none())
898             {
899                 // weak_ptr prevents lambda from taking shared ownership of the context.
900                 // Otherwise context replacement in the handle_status_line() would leak the objects.
901                 std::weak_ptr<asio_context> ctx_weak(ctx);
902                 ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback([ctx_weak]() {
903                     if (auto ctx_lock = ctx_weak.lock())
904                     {
905                         // Shut down transmissions, close the socket and prevent connection from being pooled.
906                         ctx_lock->m_connection->close();
907                     }
908                 });
909             }
910         };
911 
912         // Note that we must not try to CONNECT using an already established connection via proxy -- this would send
913         // CONNECT to the end server which is definitely not what we want.
914         if (proxy_type == http_proxy_type::ssl_tunnel && !m_connection->is_reused())
915         {
916             // The ssl_tunnel_proxy keeps the context alive and then calls back once the ssl tunnel is established via
917             // 'start_http_request_flow'
918             std::shared_ptr<ssl_proxy_tunnel> ssl_tunnel =
919                 std::make_shared<ssl_proxy_tunnel>(shared_from_this(), start_http_request_flow);
920             ssl_tunnel->start_proxy_connect();
921         }
922         else
923         {
924             start_http_request_flow(shared_from_this());
925         }
926     }
927 
928     template<typename _ExceptionType>
report_exception(const _ExceptionType & e)929     void report_exception(const _ExceptionType& e)
930     {
931         report_exception(std::make_exception_ptr(e));
932     }
933 
report_exception(std::exception_ptr exceptionPtr)934     void report_exception(std::exception_ptr exceptionPtr) override
935     {
936         // Don't recycle connections that had an error into the connection pool.
937         m_connection->close();
938         request_context::report_exception(exceptionPtr);
939     }
940 
941 private:
upgrade_to_ssl()942     void upgrade_to_ssl()
943     {
944         auto& client = static_cast<asio_client&>(*m_http_client);
945         m_connection->upgrade_to_ssl(calc_cn_host(client.base_uri(), m_request.headers()),
946                                      client.client_config().get_ssl_context_callback());
947     }
948 
generate_basic_auth_header()949     std::string generate_basic_auth_header()
950     {
951         std::string header;
952         header.append("Authorization: Basic ");
953         header.append(generate_base64_userpass(m_http_client->client_config().credentials()));
954         header.append(CRLF);
955         return header;
956     }
957 
generate_basic_proxy_auth_header()958     std::string generate_basic_proxy_auth_header()
959     {
960         std::string header;
961         header.append("Proxy-Authorization: Basic ");
962         header.append(generate_base64_userpass(m_http_client->client_config().proxy().credentials()));
963         header.append(CRLF);
964         return header;
965     }
966 
report_error(const std::string & message,const boost::system::error_code & ec,httpclient_errorcode_context context=httpclient_errorcode_context::none)967     void report_error(const std::string& message,
968                       const boost::system::error_code& ec,
969                       httpclient_errorcode_context context = httpclient_errorcode_context::none)
970     {
971         // By default, errorcodeValue don't need to converted
972         long errorcodeValue = ec.value();
973 
974         // map timer cancellation to time_out
975         if (m_timer.has_timedout())
976         {
977             errorcodeValue = make_error_code(std::errc::timed_out).value();
978         }
979         else
980         {
981             // We need to correct inaccurate ASIO error code base on context information
982             switch (context)
983             {
984                 case httpclient_errorcode_context::writeheader:
985                     if (ec == boost::system::errc::broken_pipe)
986                     {
987                         errorcodeValue = make_error_code(std::errc::host_unreachable).value();
988                     }
989                     break;
990                 case httpclient_errorcode_context::connect:
991                     if (ec == boost::system::errc::connection_refused)
992                     {
993                         errorcodeValue = make_error_code(std::errc::host_unreachable).value();
994                     }
995                     break;
996                 case httpclient_errorcode_context::readheader:
997                     if (ec.default_error_condition().value() ==
998                         boost::system::errc::no_such_file_or_directory) // bug in boost error_code mapping
999                     {
1000                         errorcodeValue = make_error_code(std::errc::connection_aborted).value();
1001                     }
1002                     break;
1003                 default: break;
1004             }
1005         }
1006         request_context::report_error(errorcodeValue, message);
1007     }
1008 
handle_connect(const boost::system::error_code & ec,tcp::resolver::iterator endpoints)1009     void handle_connect(const boost::system::error_code& ec, tcp::resolver::iterator endpoints)
1010     {
1011         m_timer.reset();
1012         if (!ec)
1013         {
1014             m_connection->enable_no_delay();
1015             write_request();
1016         }
1017         else if (ec.value() == boost::system::errc::operation_canceled ||
1018                  ec.value() == boost::asio::error::operation_aborted)
1019         {
1020             report_error("Request canceled by user.", ec, httpclient_errorcode_context::connect);
1021         }
1022         else if (endpoints == tcp::resolver::iterator())
1023         {
1024             report_error("Failed to connect to any resolved endpoint", ec, httpclient_errorcode_context::connect);
1025         }
1026         else
1027         {
1028             // Replace the connection. This causes old connection object to go out of scope.
1029             auto client = std::static_pointer_cast<asio_client>(m_http_client);
1030             try
1031             {
1032                 m_connection = client->obtain_connection(m_request);
1033             }
1034             catch (...)
1035             {
1036                 request_context::report_exception(std::current_exception());
1037                 return;
1038             }
1039 
1040             auto endpoint = *endpoints;
1041             m_connection->async_connect(
1042                 endpoint,
1043                 boost::bind(
1044                     &asio_context::handle_connect, shared_from_this(), boost::asio::placeholders::error, ++endpoints));
1045         }
1046     }
1047 
handle_resolve(const boost::system::error_code & ec,tcp::resolver::iterator endpoints)1048     void handle_resolve(const boost::system::error_code& ec, tcp::resolver::iterator endpoints)
1049     {
1050         if (ec)
1051         {
1052             report_error("Error resolving address", ec, httpclient_errorcode_context::connect);
1053         }
1054         else if (endpoints == tcp::resolver::iterator())
1055         {
1056             report_error("Failed to resolve address", ec, httpclient_errorcode_context::connect);
1057         }
1058         else
1059         {
1060             m_timer.reset();
1061             auto endpoint = *endpoints;
1062             m_connection->async_connect(
1063                 endpoint,
1064                 boost::bind(
1065                     &asio_context::handle_connect, shared_from_this(), boost::asio::placeholders::error, ++endpoints));
1066         }
1067     }
1068 
write_request()1069     void write_request()
1070     {
1071         // Only perform handshake if a TLS connection and not being reused.
1072         if (m_connection->is_ssl() && !m_connection->is_reused())
1073         {
1074             const auto weakCtx = std::weak_ptr<asio_context>(shared_from_this());
1075             m_connection->async_handshake(
1076                 boost::asio::ssl::stream_base::client,
1077                 m_http_client->client_config(),
1078                 boost::bind(&asio_context::handle_handshake, shared_from_this(), boost::asio::placeholders::error),
1079 
1080                 // Use a weak_ptr since the verify_callback is stored until the connection is
1081                 // destroyed. This avoids creating a circular reference since we pool connection
1082                 // objects.
1083                 [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) {
1084                     auto this_request = weakCtx.lock();
1085                     if (this_request)
1086                     {
1087                         return this_request->handle_cert_verification(preverified, verify_context);
1088                     }
1089                     return false;
1090                 });
1091         }
1092         else
1093         {
1094             m_connection->async_write(
1095                 m_body_buf,
1096                 boost::bind(&asio_context::handle_write_headers, shared_from_this(), boost::asio::placeholders::error));
1097         }
1098     }
1099 
handle_handshake(const boost::system::error_code & ec)1100     void handle_handshake(const boost::system::error_code& ec)
1101     {
1102         if (!ec)
1103         {
1104             m_connection->async_write(
1105                 m_body_buf,
1106                 boost::bind(&asio_context::handle_write_headers, shared_from_this(), boost::asio::placeholders::error));
1107         }
1108         else
1109         {
1110             report_error("Error in SSL handshake", ec, httpclient_errorcode_context::handshake);
1111         }
1112     }
1113 
handle_cert_verification(bool preverified,boost::asio::ssl::verify_context & verifyCtx)1114     bool handle_cert_verification(bool preverified, boost::asio::ssl::verify_context& verifyCtx)
1115     {
1116         // OpenSSL calls the verification callback once per certificate in the chain,
1117         // starting with the root CA certificate. The 'leaf', non-Certificate Authority (CA)
1118         // certificate, i.e. actual server certificate is at the '0' position in the
1119         // certificate chain, the rest are optional intermediate certificates, followed
1120         // finally by the root CA self signed certificate.
1121 
1122 #ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
1123         // If OpenSSL fails we will doing verification at the end using the whole certificate
1124         // chain so wait until the 'leaf' cert. For now return true so OpenSSL continues down
1125         // the certificate chain.
1126         if (!preverified)
1127         {
1128             m_openssl_failed = true;
1129         }
1130 
1131         if (m_openssl_failed)
1132         {
1133             return verify_cert_chain_platform_specific(verifyCtx, m_connection->cn_hostname());
1134         }
1135 #endif // CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
1136 
1137         boost::asio::ssl::rfc2818_verification rfc2818(m_connection->cn_hostname());
1138         return rfc2818(preverified, verifyCtx);
1139     }
1140 
handle_write_headers(const boost::system::error_code & ec)1141     void handle_write_headers(const boost::system::error_code& ec)
1142     {
1143         if (ec)
1144         {
1145             report_error("Failed to write request headers", ec, httpclient_errorcode_context::writeheader);
1146         }
1147         else
1148         {
1149             if (m_needChunked)
1150             {
1151                 handle_write_chunked_body(ec);
1152             }
1153             else
1154             {
1155                 handle_write_large_body(ec);
1156             }
1157         }
1158     }
1159 
handle_write_chunked_body(const boost::system::error_code & ec)1160     void handle_write_chunked_body(const boost::system::error_code& ec)
1161     {
1162         if (ec)
1163         {
1164             // Reuse error handling.
1165             return handle_write_body(ec);
1166         }
1167 
1168         m_timer.reset();
1169         const auto& progress = m_request._get_impl()->_progress_handler();
1170         if (progress)
1171         {
1172             try
1173             {
1174                 (*progress)(message_direction::upload, m_uploaded);
1175             }
1176             catch (...)
1177             {
1178                 report_exception(std::current_exception());
1179                 return;
1180             }
1181         }
1182 
1183         const auto& chunkSize = m_http_client->client_config().chunksize();
1184         auto readbuf = _get_readbuffer();
1185         uint8_t* buf = boost::asio::buffer_cast<uint8_t*>(
1186             m_body_buf.prepare(chunkSize + http::details::chunked_encoding::additional_encoding_space));
1187         const auto this_request = shared_from_this();
1188         readbuf.getn(buf + http::details::chunked_encoding::data_offset, chunkSize)
1189             .then([this_request, buf, chunkSize AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task<size_t> op) {
1190                 size_t readSize = 0;
1191                 try
1192                 {
1193                     readSize = op.get();
1194                 }
1195                 catch (...)
1196                 {
1197                     this_request->report_exception(std::current_exception());
1198                     return;
1199                 }
1200 
1201                 const size_t offset = http::details::chunked_encoding::add_chunked_delimiters(
1202                     buf, chunkSize + http::details::chunked_encoding::additional_encoding_space, readSize);
1203                 this_request->m_body_buf.commit(readSize + http::details::chunked_encoding::additional_encoding_space);
1204                 this_request->m_body_buf.consume(offset);
1205                 this_request->m_uploaded += static_cast<uint64_t>(readSize);
1206 
1207                 if (readSize != 0)
1208                 {
1209                     this_request->m_connection->async_write(this_request->m_body_buf,
1210                                                             boost::bind(&asio_context::handle_write_chunked_body,
1211                                                                         this_request,
1212                                                                         boost::asio::placeholders::error));
1213                 }
1214                 else
1215                 {
1216                     this_request->m_connection->async_write(
1217                         this_request->m_body_buf,
1218                         boost::bind(&asio_context::handle_write_body, this_request, boost::asio::placeholders::error));
1219                 }
1220             });
1221     }
1222 
handle_write_large_body(const boost::system::error_code & ec)1223     void handle_write_large_body(const boost::system::error_code& ec)
1224     {
1225         if (ec || m_uploaded >= m_content_length)
1226         {
1227             // Reuse error handling.
1228             return handle_write_body(ec);
1229         }
1230 
1231         m_timer.reset();
1232         const auto& progress = m_request._get_impl()->_progress_handler();
1233         if (progress)
1234         {
1235             try
1236             {
1237                 (*progress)(message_direction::upload, m_uploaded);
1238             }
1239             catch (...)
1240             {
1241                 report_exception(std::current_exception());
1242                 return;
1243             }
1244         }
1245 
1246         const auto this_request = shared_from_this();
1247         const auto readSize = static_cast<size_t>((std::min)(
1248             static_cast<uint64_t>(m_http_client->client_config().chunksize()), m_content_length - m_uploaded));
1249         auto readbuf = _get_readbuffer();
1250         readbuf.getn(boost::asio::buffer_cast<uint8_t*>(m_body_buf.prepare(readSize)), readSize)
1251             .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task<size_t> op) {
1252                 try
1253                 {
1254                     const auto actualReadSize = op.get();
1255                     if (actualReadSize == 0)
1256                     {
1257                         this_request->report_exception(http_exception(
1258                             "Unexpected end of request body stream encountered before Content-Length satisfied."));
1259                         return;
1260                     }
1261                     this_request->m_uploaded += static_cast<uint64_t>(actualReadSize);
1262                     this_request->m_body_buf.commit(actualReadSize);
1263                     this_request->m_connection->async_write(this_request->m_body_buf,
1264                                                             boost::bind(&asio_context::handle_write_large_body,
1265                                                                         this_request,
1266                                                                         boost::asio::placeholders::error));
1267                 }
1268                 catch (...)
1269                 {
1270                     this_request->report_exception(std::current_exception());
1271                     return;
1272                 }
1273             });
1274     }
1275 
handle_write_body(const boost::system::error_code & ec)1276     void handle_write_body(const boost::system::error_code& ec)
1277     {
1278         if (!ec)
1279         {
1280             m_timer.reset();
1281             const auto& progress = m_request._get_impl()->_progress_handler();
1282             if (progress)
1283             {
1284                 try
1285                 {
1286                     (*progress)(message_direction::upload, m_uploaded);
1287                 }
1288                 catch (...)
1289                 {
1290                     report_exception(std::current_exception());
1291                     return;
1292                 }
1293             }
1294 
1295             // Read until the end of entire headers
1296             m_connection->async_read_until(
1297                 m_body_buf,
1298                 CRLF + CRLF,
1299                 boost::bind(&asio_context::handle_status_line, shared_from_this(), boost::asio::placeholders::error));
1300         }
1301         else
1302         {
1303             report_error("Failed to write request body", ec, httpclient_errorcode_context::writebody);
1304         }
1305     }
1306 
handle_status_line(const boost::system::error_code & ec)1307     void handle_status_line(const boost::system::error_code& ec)
1308     {
1309         if (!ec)
1310         {
1311             m_timer.reset();
1312 
1313             std::istream response_stream(&m_body_buf);
1314             response_stream.imbue(std::locale::classic());
1315             std::string http_version;
1316             response_stream >> http_version;
1317             status_code status_code;
1318             response_stream >> status_code;
1319 
1320             std::string status_message;
1321             std::getline(response_stream, status_message);
1322 
1323             m_response.set_status_code(status_code);
1324 
1325             ::web::http::details::trim_whitespace(status_message);
1326             m_response.set_reason_phrase(utility::conversions::to_string_t(std::move(status_message)));
1327 
1328             if (!response_stream || http_version.substr(0, 5) != "HTTP/")
1329             {
1330                 report_error("Invalid HTTP status line", ec, httpclient_errorcode_context::readheader);
1331                 return;
1332             }
1333 
1334             web::http::http_version parsed_version = web::http::http_version::from_string(http_version);
1335             m_response._get_impl()->_set_http_version(parsed_version);
1336 
1337             // if HTTP version is 1.0 then disable 'Keep-Alive' by default
1338             if (parsed_version == web::http::http_versions::HTTP_1_0)
1339             {
1340                 m_connection->set_keep_alive(false);
1341             }
1342 
1343             read_headers();
1344         }
1345         else
1346         {
1347             handle_failed_read_status_line(ec, "Failed to read HTTP status line");
1348         }
1349     }
1350 
handle_failed_read_status_line(const boost::system::error_code & ec,const char * generic_error_message)1351     void handle_failed_read_status_line(const boost::system::error_code& ec, const char* generic_error_message)
1352     {
1353         if (m_connection->was_reused_and_closed_by_server(ec))
1354         {
1355             // Failed to write to socket because connection was already closed while it was in the pool.
1356             // close() here ensures socket is closed in a robust way and prevents the connection from being put to the
1357             // pool again.
1358             m_connection->close();
1359 
1360             // Create a new context and copy the request object, completion event and
1361             // cancellation registration to maintain the old state.
1362             // This also obtains a new connection from pool.
1363             std::shared_ptr<request_context> new_ctx;
1364             try
1365             {
1366                 new_ctx = create_request_context(m_http_client, m_request);
1367             }
1368             catch (...)
1369             {
1370                 report_exception(std::current_exception());
1371                 return;
1372             }
1373 
1374             // If the request contains a valid instream, we try to rewind it to
1375             // replay the just-failed request. Otherwise we assume that no data
1376             // was sent in the first place.
1377             const auto& instream = new_ctx->m_request._get_impl()->instream();
1378             if (instream)
1379             {
1380                 // As stated in the commit message of f4f2348, we might encounter
1381                 // streams that are not capable of rewinding and hence resending the
1382                 // request is not possible. We cannot recover from this condition and
1383                 // need to escalate it to the using code.
1384                 if (!instream.can_seek())
1385                 {
1386                     report_error("cannot rewind input stream for connection re-establishment",
1387                                  ec,
1388                                  httpclient_errorcode_context::readheader);
1389                     return;
1390                 }
1391 
1392                 try
1393                 {
1394                     // Rewinding the stream might throw, in which case we cannot do the
1395                     // connection re-establishment transparently. I.e. report the exception
1396                     // to the calling code.
1397                     instream.seek(0);
1398                 }
1399                 catch (...)
1400                 {
1401                     report_exception(std::current_exception());
1402                     return;
1403                 }
1404             }
1405 
1406             new_ctx->m_request_completion = m_request_completion;
1407             new_ctx->m_cancellationRegistration = m_cancellationRegistration;
1408 
1409             auto client = std::static_pointer_cast<asio_client>(m_http_client);
1410             // Resend the request using the new context.
1411             client->send_request(new_ctx);
1412         }
1413         else
1414         {
1415             report_error(generic_error_message, ec, httpclient_errorcode_context::readheader);
1416         }
1417     }
1418 
read_headers()1419     void read_headers()
1420     {
1421         auto needChunked = false;
1422         std::istream response_stream(&m_body_buf);
1423         response_stream.imbue(std::locale::classic());
1424         std::string header;
1425         while (std::getline(response_stream, header) && header != "\r")
1426         {
1427             const auto colon = header.find(':');
1428             if (colon != std::string::npos)
1429             {
1430                 auto name = header.substr(0, colon);
1431                 auto value = header.substr(colon + 1, header.size() - colon - 2);
1432                 boost::algorithm::trim(name);
1433                 boost::algorithm::trim(value);
1434 
1435                 if (boost::iequals(name, header_names::transfer_encoding))
1436                 {
1437                     needChunked = boost::icontains(value, U("chunked"));
1438                 }
1439 
1440                 if (boost::iequals(name, header_names::connection))
1441                 {
1442                     // If the server uses HTTP/1.1, then 'Keep-Alive' is the default,
1443                     // so connection is explicitly closed only if we get "Connection: close".
1444                     // If the server uses HTTP/1.0, it would need to respond using
1445                     // 'Connection: Keep-Alive' every time.
1446                     if (m_response._get_impl()->http_version() != web::http::http_versions::HTTP_1_0)
1447                         m_connection->set_keep_alive(!boost::iequals(value, U("close")));
1448                     else
1449                         m_connection->set_keep_alive(boost::iequals(value, U("Keep-Alive")));
1450                 }
1451 
1452                 m_response.headers().add(utility::conversions::to_string_t(std::move(name)),
1453                                          utility::conversions::to_string_t(std::move(value)));
1454             }
1455         }
1456 
1457         m_content_length = (std::numeric_limits<size_t>::max)(); // Without Content-Length header, size should be same
1458                                                                  // as TCP stream - set it size_t max.
1459         m_response.headers().match(header_names::content_length, m_content_length);
1460 
1461         if (!this->handle_compression())
1462         {
1463             // false indicates report_exception was called
1464             return;
1465         }
1466 
1467         complete_headers();
1468 
1469         // Check for HEAD requests and status codes which cannot contain a
1470         // message body in HTTP/1.1 (see 3.3.3/1 of the RFC 7230).
1471         //
1472         // note: need to check for 'chunked' here as well, azure storage sends both
1473         // transfer-encoding:chunked and content-length:0 (although HTTP says not to)
1474         const auto status = m_response.status_code();
1475         if (m_request.method() == U("HEAD") || (status >= 100 && status < 200) || status == status_codes::NoContent ||
1476             status == status_codes::NotModified || (!needChunked && m_content_length == 0))
1477         {
1478             // we can stop early - no body
1479             const auto& progress = m_request._get_impl()->_progress_handler();
1480             if (progress)
1481             {
1482                 try
1483                 {
1484                     (*progress)(message_direction::download, 0);
1485                 }
1486                 catch (...)
1487                 {
1488                     report_exception(std::current_exception());
1489                     return;
1490                 }
1491             }
1492 
1493             complete_request(0);
1494         }
1495         else
1496         {
1497             if (!needChunked)
1498             {
1499                 async_read_until_buffersize(
1500                     static_cast<size_t>((std::min)(m_content_length,
1501                                                    static_cast<uint64_t>(m_http_client->client_config().chunksize()))),
1502                     boost::bind(
1503                         &asio_context::handle_read_content, shared_from_this(), boost::asio::placeholders::error));
1504             }
1505             else
1506             {
1507                 m_connection->async_read_until(m_body_buf,
1508                                                CRLF,
1509                                                boost::bind(&asio_context::handle_chunk_header,
1510                                                            shared_from_this(),
1511                                                            boost::asio::placeholders::error));
1512             }
1513         }
1514     }
1515 
1516     template<typename ReadHandler>
async_read_until_buffersize(size_t size,const ReadHandler & handler)1517     void async_read_until_buffersize(size_t size, const ReadHandler& handler)
1518     {
1519         size_t size_to_read = 0;
1520         if (m_body_buf.size() < size)
1521         {
1522             size_to_read = size - m_body_buf.size();
1523         }
1524 
1525         m_connection->async_read(m_body_buf, boost::asio::transfer_exactly(size_to_read), handler);
1526     }
1527 
handle_chunk_header(const boost::system::error_code & ec)1528     void handle_chunk_header(const boost::system::error_code& ec)
1529     {
1530         if (!ec)
1531         {
1532             m_timer.reset();
1533 
1534             std::istream response_stream(&m_body_buf);
1535             response_stream.imbue(std::locale::classic());
1536             std::string line;
1537             std::getline(response_stream, line);
1538 
1539             std::istringstream octetLine(std::move(line));
1540             octetLine.imbue(std::locale::classic());
1541             int octets = 0;
1542             octetLine >> std::hex >> octets;
1543 
1544             if (octetLine.fail())
1545             {
1546                 report_error("Invalid chunked response header",
1547                              boost::system::error_code(),
1548                              httpclient_errorcode_context::readbody);
1549             }
1550             else
1551             {
1552                 async_read_until_buffersize(
1553                     octets + CRLF.size(),
1554                     boost::bind(
1555                         &asio_context::handle_chunk, shared_from_this(), boost::asio::placeholders::error, octets));
1556             }
1557         }
1558         else
1559         {
1560             report_error("Retrieving message chunk header", ec, httpclient_errorcode_context::readbody);
1561         }
1562     }
1563 
decompress(const uint8_t * input,size_t input_size,std::vector<uint8_t> & output)1564     bool decompress(const uint8_t* input, size_t input_size, std::vector<uint8_t>& output)
1565     {
1566         // Need to guard against attempting to decompress when we're already finished or encountered an error!
1567         if (input == nullptr || input_size == 0)
1568         {
1569             return false;
1570         }
1571 
1572         size_t processed;
1573         size_t got;
1574         size_t inbytes = 0;
1575         size_t outbytes = 0;
1576         bool done;
1577 
1578         try
1579         {
1580             output.resize(input_size * 3);
1581             do
1582             {
1583                 if (inbytes)
1584                 {
1585                     output.resize(output.size() + (std::max)(input_size, static_cast<size_t>(1024)));
1586                 }
1587                 got = m_decompressor->decompress(input + inbytes,
1588                                                  input_size - inbytes,
1589                                                  output.data() + outbytes,
1590                                                  output.size() - outbytes,
1591                                                  web::http::compression::operation_hint::has_more,
1592                                                  processed,
1593                                                  done);
1594                 inbytes += processed;
1595                 outbytes += got;
1596             } while (got && !done);
1597             output.resize(outbytes);
1598         }
1599         catch (...)
1600         {
1601             return false;
1602         }
1603 
1604         return true;
1605     }
1606 
handle_chunk(const boost::system::error_code & ec,int to_read)1607     void handle_chunk(const boost::system::error_code& ec, int to_read)
1608     {
1609         if (!ec)
1610         {
1611             m_timer.reset();
1612 
1613             m_downloaded += static_cast<uint64_t>(to_read);
1614             const auto& progress = m_request._get_impl()->_progress_handler();
1615             if (progress)
1616             {
1617                 try
1618                 {
1619                     (*progress)(message_direction::download, m_downloaded);
1620                 }
1621                 catch (...)
1622                 {
1623                     report_exception(std::current_exception());
1624                     return;
1625                 }
1626             }
1627 
1628             if (to_read == 0)
1629             {
1630                 m_body_buf.consume(CRLF.size());
1631                 complete_request(m_downloaded);
1632             }
1633             else
1634             {
1635                 auto writeBuffer = _get_writebuffer();
1636                 const auto this_request = shared_from_this();
1637                 if (m_decompressor)
1638                 {
1639                     std::vector<uint8_t> decompressed;
1640 
1641                     bool boo =
1642                         decompress(boost::asio::buffer_cast<const uint8_t*>(m_body_buf.data()), to_read, decompressed);
1643                     if (!boo)
1644                     {
1645                         report_exception(std::runtime_error("Failed to decompress the response body"));
1646                         return;
1647                     }
1648 
1649                     // It is valid for the decompressor to sometimes return an empty output for a given chunk, the data
1650                     // will be flushed when the next chunk is received
1651                     if (decompressed.empty())
1652                     {
1653                         m_body_buf.consume(to_read + CRLF.size()); // consume crlf
1654                         m_connection->async_read_until(m_body_buf,
1655                                                        CRLF,
1656                                                        boost::bind(&asio_context::handle_chunk_header,
1657                                                                    this_request,
1658                                                                    boost::asio::placeholders::error));
1659                     }
1660                     else
1661                     {
1662                         // Move the decompressed buffer into a shared_ptr to keep it alive until putn_nocopy completes.
1663                         // When VS 2013 support is dropped, this should be changed to a unique_ptr plus a move capture.
1664                         auto shared_decompressed = std::make_shared<std::vector<uint8_t>>(std::move(decompressed));
1665 
1666                         writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size())
1667                             .then([this_request, to_read, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS](
1668                                       pplx::task<size_t> op) {
1669                                 try
1670                                 {
1671                                     op.get();
1672                                     this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf
1673                                     this_request->m_connection->async_read_until(
1674                                         this_request->m_body_buf,
1675                                         CRLF,
1676                                         boost::bind(&asio_context::handle_chunk_header,
1677                                                     this_request,
1678                                                     boost::asio::placeholders::error));
1679                                 }
1680                                 catch (...)
1681                                 {
1682                                     this_request->report_exception(std::current_exception());
1683                                     return;
1684                                 }
1685                             });
1686                     }
1687                 }
1688                 else
1689                 {
1690                     writeBuffer.putn_nocopy(boost::asio::buffer_cast<const uint8_t*>(m_body_buf.data()), to_read)
1691                         .then([this_request, to_read AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task<size_t> op) {
1692                             try
1693                             {
1694                                 op.wait();
1695                             }
1696                             catch (...)
1697                             {
1698                                 this_request->report_exception(std::current_exception());
1699                                 return;
1700                             }
1701                             this_request->m_body_buf.consume(to_read + CRLF.size()); // consume crlf
1702                             this_request->m_connection->async_read_until(this_request->m_body_buf,
1703                                                                          CRLF,
1704                                                                          boost::bind(&asio_context::handle_chunk_header,
1705                                                                                      this_request,
1706                                                                                      boost::asio::placeholders::error));
1707                         });
1708                 }
1709             }
1710         }
1711         else
1712         {
1713             report_error("Failed to read chunked response part", ec, httpclient_errorcode_context::readbody);
1714         }
1715     }
1716 
handle_read_content(const boost::system::error_code & ec)1717     void handle_read_content(const boost::system::error_code& ec)
1718     {
1719         auto writeBuffer = _get_writebuffer();
1720 
1721         if (ec)
1722         {
1723             if (ec == boost::asio::error::eof && m_content_length == (std::numeric_limits<size_t>::max)())
1724             {
1725                 m_content_length = m_downloaded + m_body_buf.size();
1726             }
1727             else
1728             {
1729                 report_error("Failed to read response body", ec, httpclient_errorcode_context::readbody);
1730                 return;
1731             }
1732         }
1733 
1734         m_timer.reset();
1735         const auto& progress = m_request._get_impl()->_progress_handler();
1736         if (progress)
1737         {
1738             try
1739             {
1740                 (*progress)(message_direction::download, m_downloaded);
1741             }
1742             catch (...)
1743             {
1744                 report_exception(std::current_exception());
1745                 return;
1746             }
1747         }
1748 
1749         if (m_downloaded < m_content_length)
1750         {
1751             // more data need to be read
1752             const auto this_request = shared_from_this();
1753 
1754             auto read_size = static_cast<size_t>(
1755                 (std::min)(static_cast<uint64_t>(m_body_buf.size()), m_content_length - m_downloaded));
1756 
1757             if (m_decompressor)
1758             {
1759                 std::vector<uint8_t> decompressed;
1760 
1761                 bool boo =
1762                     decompress(boost::asio::buffer_cast<const uint8_t*>(m_body_buf.data()), read_size, decompressed);
1763                 if (!boo)
1764                 {
1765                     this_request->report_exception(std::runtime_error("Failed to decompress the response body"));
1766                     return;
1767                 }
1768 
1769                 // It is valid for the decompressor to sometimes return an empty output for a given chunk, the data will
1770                 // be flushed when the next chunk is received
1771                 if (decompressed.empty())
1772                 {
1773                     try
1774                     {
1775                         this_request->m_downloaded += static_cast<uint64_t>(read_size);
1776 
1777                         this_request->async_read_until_buffersize(
1778                             static_cast<size_t>((std::min)(
1779                                 static_cast<uint64_t>(this_request->m_http_client->client_config().chunksize()),
1780                                 this_request->m_content_length - this_request->m_downloaded)),
1781                             boost::bind(
1782                                 &asio_context::handle_read_content, this_request, boost::asio::placeholders::error));
1783                     }
1784                     catch (...)
1785                     {
1786                         this_request->report_exception(std::current_exception());
1787                         return;
1788                     }
1789                 }
1790                 else
1791                 {
1792                     // Move the decompressed buffer into a shared_ptr to keep it alive until putn_nocopy completes.
1793                     // When VS 2013 support is dropped, this should be changed to a unique_ptr plus a move capture.
1794                     auto shared_decompressed = std::make_shared<std::vector<uint8_t>>(std::move(decompressed));
1795 
1796                     writeBuffer.putn_nocopy(shared_decompressed->data(), shared_decompressed->size())
1797                         .then([this_request, read_size, shared_decompressed AND_CAPTURE_MEMBER_FUNCTION_POINTERS](
1798                                   pplx::task<size_t> op) {
1799                             size_t writtenSize = 0;
1800                             (void)writtenSize;
1801                             try
1802                             {
1803                                 writtenSize = op.get();
1804                                 this_request->m_downloaded += static_cast<uint64_t>(read_size);
1805                                 this_request->m_body_buf.consume(read_size);
1806                                 this_request->async_read_until_buffersize(
1807                                     static_cast<size_t>((std::min)(
1808                                         static_cast<uint64_t>(this_request->m_http_client->client_config().chunksize()),
1809                                         this_request->m_content_length - this_request->m_downloaded)),
1810                                     boost::bind(&asio_context::handle_read_content,
1811                                                 this_request,
1812                                                 boost::asio::placeholders::error));
1813                             }
1814                             catch (...)
1815                             {
1816                                 this_request->report_exception(std::current_exception());
1817                                 return;
1818                             }
1819                         });
1820                 }
1821             }
1822             else
1823             {
1824                 writeBuffer.putn_nocopy(boost::asio::buffer_cast<const uint8_t*>(m_body_buf.data()), read_size)
1825                     .then([this_request AND_CAPTURE_MEMBER_FUNCTION_POINTERS](pplx::task<size_t> op) {
1826                         size_t writtenSize = 0;
1827                         try
1828                         {
1829                             writtenSize = op.get();
1830                             this_request->m_downloaded += static_cast<uint64_t>(writtenSize);
1831                             this_request->m_body_buf.consume(writtenSize);
1832                             this_request->async_read_until_buffersize(
1833                                 static_cast<size_t>((std::min)(
1834                                     static_cast<uint64_t>(this_request->m_http_client->client_config().chunksize()),
1835                                     this_request->m_content_length - this_request->m_downloaded)),
1836                                 boost::bind(&asio_context::handle_read_content,
1837                                             this_request,
1838                                             boost::asio::placeholders::error));
1839                         }
1840                         catch (...)
1841                         {
1842                             this_request->report_exception(std::current_exception());
1843                             return;
1844                         }
1845                     });
1846             }
1847         }
1848         else
1849         {
1850             // Request is complete no more data to read.
1851             complete_request(m_downloaded);
1852         }
1853     }
1854 
1855     // Simple timer class wrapping Boost deadline timer.
1856     // Closes the connection when timer fires.
1857     class timeout_timer
1858     {
1859     public:
timeout_timer(const std::chrono::microseconds & timeout)1860         timeout_timer(const std::chrono::microseconds& timeout)
1861             : m_duration(timeout.count()), m_state(created), m_timer(crossplat::threadpool::shared_instance().service())
1862         {
1863         }
1864 
set_ctx(const std::weak_ptr<asio_context> & ctx)1865         void set_ctx(const std::weak_ptr<asio_context>& ctx) { m_ctx = ctx; }
1866 
start()1867         void start()
1868         {
1869             assert(m_state == created);
1870             assert(!m_ctx.expired());
1871             m_state = started;
1872 
1873             m_timer.expires_from_now(m_duration);
1874             auto ctx = m_ctx;
1875             m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) {
1876                 handle_timeout(ec, ctx);
1877             });
1878         }
1879 
reset()1880         void reset()
1881         {
1882             assert(m_state == started || m_state == timedout);
1883             assert(!m_ctx.expired());
1884             if (m_timer.expires_from_now(m_duration) > 0)
1885             {
1886                 // The existing handler was canceled so schedule a new one.
1887                 assert(m_state == started);
1888                 auto ctx = m_ctx;
1889                 m_timer.async_wait([ctx AND_CAPTURE_MEMBER_FUNCTION_POINTERS](const boost::system::error_code& ec) {
1890                     handle_timeout(ec, ctx);
1891                 });
1892             }
1893         }
1894 
has_timedout() const1895         bool has_timedout() const { return m_state == timedout; }
1896 
has_started() const1897         bool has_started() const { return m_state == started; }
1898 
stop()1899         void stop()
1900         {
1901             m_state = stopped;
1902             m_timer.cancel();
1903         }
1904 
handle_timeout(const boost::system::error_code & ec,const std::weak_ptr<asio_context> & ctx)1905         static void handle_timeout(const boost::system::error_code& ec, const std::weak_ptr<asio_context>& ctx)
1906         {
1907             if (!ec)
1908             {
1909                 auto shared_ctx = ctx.lock();
1910                 if (shared_ctx)
1911                 {
1912                     assert(shared_ctx->m_timer.m_state != timedout);
1913                     shared_ctx->m_timer.m_state = timedout;
1914                     shared_ctx->m_connection->close();
1915                 }
1916             }
1917         }
1918 
1919     private:
1920         enum timer_state
1921         {
1922             created,
1923             started,
1924             stopped,
1925             timedout
1926         };
1927 
1928 #if (defined(ANDROID) || defined(__ANDROID__)) && !defined(_LIBCPP_VERSION)
1929         boost::chrono::microseconds m_duration;
1930 #else
1931         std::chrono::microseconds m_duration;
1932 #endif
1933         std::atomic<timer_state> m_state;
1934         std::weak_ptr<asio_context> m_ctx;
1935         boost::asio::steady_timer m_timer;
1936     };
1937 
1938     uint64_t m_content_length;
1939     bool m_needChunked;
1940     timeout_timer m_timer;
1941     tcp::resolver m_resolver;
1942     boost::asio::streambuf m_body_buf;
1943     std::shared_ptr<asio_connection> m_connection;
1944 
1945 #ifdef CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
1946     bool m_openssl_failed;
1947 #endif // CPPREST_PLATFORM_ASIO_CERT_VERIFICATION_AVAILABLE
1948 };
1949 
create_platform_final_pipeline_stage(uri && base_uri,http_client_config && client_config)1950 std::shared_ptr<_http_client_communicator> create_platform_final_pipeline_stage(uri&& base_uri,
1951                                                                                 http_client_config&& client_config)
1952 {
1953     return std::make_shared<asio_client>(std::move(base_uri), std::move(client_config));
1954 }
1955 
send_request(const std::shared_ptr<request_context> & request_ctx)1956 void asio_client::send_request(const std::shared_ptr<request_context>& request_ctx)
1957 {
1958     auto ctx = std::static_pointer_cast<asio_context>(request_ctx);
1959 
1960     try
1961     {
1962         if (ctx->m_connection->is_ssl())
1963         {
1964             client_config().invoke_nativehandle_options(ctx->m_connection->m_ssl_stream.get());
1965         }
1966         else
1967         {
1968             client_config().invoke_nativehandle_options(&(ctx->m_connection->m_socket));
1969         }
1970     }
1971     catch (...)
1972     {
1973         request_ctx->report_exception(std::current_exception());
1974         return;
1975     }
1976 
1977     ctx->start_request();
1978 }
1979 
is_retrieval_redirection(status_code code)1980 static bool is_retrieval_redirection(status_code code)
1981 {
1982     // See https://tools.ietf.org/html/rfc7231#section-6.4
1983 
1984     switch (code)
1985     {
1986     case status_codes::MovedPermanently:
1987         // "For historical reasons, a user agent MAY change the request method
1988         // from POST to GET for the subsequent request."
1989         return true;
1990     case status_codes::Found:
1991         // "For historical reasons, a user agent MAY change the request method
1992         // from POST to GET for the subsequent request."
1993         return true;
1994     case status_codes::SeeOther:
1995         // "A user agent can perform a [GET or HEAD] request. It is primarily
1996         // used to allow the output of a POST action to redirect the user agent
1997         // to a selected resource."
1998         return true;
1999     default:
2000         return false;
2001     }
2002 }
2003 
is_unchanged_redirection(status_code code)2004 static bool is_unchanged_redirection(status_code code)
2005 {
2006     // See https://tools.ietf.org/html/rfc7231#section-6.4
2007     // and https://tools.ietf.org/html/rfc7538#section-3
2008 
2009     switch (code)
2010     {
2011     case status_codes::TemporaryRedirect:
2012         // "The user agent MUST NOT change the request method if it performs an
2013         // automatic redirection to that URI."
2014         return true;
2015     case status_codes::PermanentRedirect:
2016         // This status code "does not allow changing the request method from POST
2017         // to GET."
2018         return true;
2019     default:
2020         return false;
2021     }
2022 }
2023 
is_recognized_redirection(status_code code)2024 static bool is_recognized_redirection(status_code code)
2025 {
2026     // other 3xx status codes, e.g. 300 Multiple Choices, are not handled
2027     // and should be handled externally
2028     return is_retrieval_redirection(code) || is_unchanged_redirection(code);
2029 }
2030 
is_retrieval_request(method method)2031 static bool is_retrieval_request(method method)
2032 {
2033     return methods::GET == method || methods::HEAD == method;
2034 }
2035 
2036 static const std::vector<utility::string_t> request_body_header_names =
2037 {
2038     header_names::content_encoding,
2039     header_names::content_language,
2040     header_names::content_length,
2041     header_names::content_location,
2042     header_names::content_type
2043 };
2044 
2045 // A request continuation that follows redirects according to the specified configuration.
2046 // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request
2047 // using the same method since the request body may have been consumed.
2048 struct http_redirect_follower
2049 {
2050     http_client_config config;
2051     std::vector<uri> followed_urls;
2052     http_request redirect;
2053 
2054     http_redirect_follower(http_client_config config, const http_request& request);
2055 
2056     uri url_to_follow(const http_response& response) const;
2057 
2058     pplx::task<http_response> operator()(http_response response);
2059 };
2060 
http_redirect_follower(http_client_config config,const http_request & request)2061 http_redirect_follower::http_redirect_follower(http_client_config config, const http_request& request)
2062     : config(std::move(config))
2063     , followed_urls(1, request.absolute_uri())
2064     , redirect(request.method())
2065 {
2066     // Stash the original request URL, etc. to be prepared for an automatic redirect
2067 
2068     // Basically, it makes sense to send the redirects with the same headers as the original request
2069     redirect.headers() = request.headers();
2070     // However, this implementation only supports retrieval redirects, with no body, so Content-* headers
2071     // should be removed
2072     for (const auto& content_header : request_body_header_names)
2073     {
2074         redirect.headers().remove(content_header);
2075     }
2076 
2077     redirect._set_cancellation_token(request._cancellation_token());
2078 }
2079 
url_to_follow(const http_response & response) const2080 uri http_redirect_follower::url_to_follow(const http_response& response) const
2081 {
2082     // Return immediately if the response is not a supported redirection
2083     if (!is_recognized_redirection(response.status_code()))
2084         return{};
2085 
2086     // Although not required by RFC 7231, config may limit the number of automatic redirects
2087     // (followed_urls includes the initial request URL, hence '<' here)
2088     if (config.max_redirects() < followed_urls.size())
2089         return{};
2090 
2091     // Can't very well automatically redirect if the server hasn't provided a Location
2092     const auto location = response.headers().find(header_names::location);
2093     if (response.headers().end() == location)
2094         return{};
2095 
2096     uri to_follow(followed_urls.back().resolve_uri(location->second));
2097 
2098     // Config may prohibit automatic redirects from HTTPS to HTTP
2099     if (!config.https_to_http_redirects() && followed_urls.back().scheme() == _XPLATSTR("https")
2100         && to_follow.scheme() != _XPLATSTR("https"))
2101         return{};
2102 
2103     // "A client SHOULD detect and intervene in cyclical redirections."
2104     if (followed_urls.end() != std::find(followed_urls.begin(), followed_urls.end(), to_follow))
2105         return{};
2106 
2107     return to_follow;
2108 }
2109 
operator ()(http_response response)2110 pplx::task<http_response> http_redirect_follower::operator()(http_response response)
2111 {
2112     // Return immediately if the response doesn't indicate a valid automatic redirect
2113     uri to_follow = url_to_follow(response);
2114     if (to_follow.is_empty())
2115         return pplx::task_from_result(response);
2116 
2117     // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request
2118     // using the same method since the request body may have been consumed.
2119     if (!is_retrieval_request(redirect.method()) && !is_retrieval_redirection(response.status_code()))
2120         return pplx::task_from_result(response);
2121 
2122     if (!is_retrieval_request(redirect.method()))
2123         redirect.set_method(methods::GET);
2124 
2125     // If the reply to this request is also a redirect, we want visibility of that
2126     auto config_no_redirects = config;
2127     config_no_redirects.set_max_redirects(0);
2128     http_client client(to_follow, config_no_redirects);
2129 
2130     // Stash the redirect request URL and make the request with the same continuation
2131     auto request_task = client.request(redirect, redirect._cancellation_token());
2132     followed_urls.push_back(std::move(to_follow));
2133     return request_task.then(std::move(*this));
2134 }
2135 
propagate(http_request request)2136 pplx::task<http_response> asio_client::propagate(http_request request)
2137 {
2138     auto self = std::static_pointer_cast<_http_client_communicator>(shared_from_this());
2139     std::shared_ptr<request_context> context;
2140     try
2141     {
2142         context = details::asio_context::create_request_context(self, request);
2143     }
2144     catch (...)
2145     {
2146         return pplx::task_from_exception<http_response>(std::current_exception());
2147     }
2148 
2149     // Use a task to externally signal the final result and completion of the task.
2150     auto result_task = pplx::create_task(context->m_request_completion);
2151 
2152     // Asynchronously send the response with the HTTP client implementation.
2153     this->async_send_request(context);
2154 
2155     return client_config().max_redirects() > 0
2156         ? result_task.then(http_redirect_follower(client_config(), request))
2157         : result_task;
2158 }
2159 } // namespace details
2160 } // namespace client
2161 } // namespace http
2162 } // namespace web
2163