1 /*
2  * Copyright (c) 2014, Peter Thorson. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *     * Redistributions of source code must retain the above copyright
7  *       notice, this list of conditions and the following disclaimer.
8  *     * Redistributions in binary form must reproduce the above copyright
9  *       notice, this list of conditions and the following disclaimer in the
10  *       documentation and/or other materials provided with the distribution.
11  *     * Neither the name of the WebSocket++ Project nor the
12  *       names of its contributors may be used to endorse or promote products
13  *       derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 
28 #ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
29 #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
30 
31 #include <websocketpp/transport/asio/base.hpp>
32 
33 #include <websocketpp/transport/base/connection.hpp>
34 
35 #include <websocketpp/logger/levels.hpp>
36 #include <websocketpp/http/constants.hpp>
37 
38 #include <websocketpp/base64/base64.hpp>
39 #include <websocketpp/error.hpp>
40 
41 #include <websocketpp/common/cpp11.hpp>
42 #include <websocketpp/common/memory.hpp>
43 #include <websocketpp/common/functional.hpp>
44 #include <websocketpp/common/connection_hdl.hpp>
45 
46 #include <boost/asio.hpp>
47 #include <boost/system/error_code.hpp>
48 
49 #include <istream>
50 #include <sstream>
51 #include <string>
52 #include <vector>
53 
54 namespace websocketpp {
55 namespace transport {
56 namespace asio {
57 
58 typedef lib::function<void(connection_hdl)> tcp_init_handler;
59 
60 /// Boost Asio based connection transport component
61 /**
62  * transport::asio::connection implements a connection transport component using
63  * Boost ASIO that works with the transport::asio::endpoint endpoint transport
64  * component.
65  */
66 template <typename config>
67 class connection : public config::socket_type::socket_con_type {
68 public:
69     /// Type of this connection transport component
70     typedef connection<config> type;
71     /// Type of a shared pointer to this connection transport component
72     typedef lib::shared_ptr<type> ptr;
73 
74     /// Type of the socket connection component
75     typedef typename config::socket_type::socket_con_type socket_con_type;
76     /// Type of a shared pointer to the socket connection component
77     typedef typename socket_con_type::ptr socket_con_ptr;
78     /// Type of this transport's access logging policy
79     typedef typename config::alog_type alog_type;
80     /// Type of this transport's error logging policy
81     typedef typename config::elog_type elog_type;
82 
83     typedef typename config::request_type request_type;
84     typedef typename request_type::ptr request_ptr;
85     typedef typename config::response_type response_type;
86     typedef typename response_type::ptr response_ptr;
87 
88     /// Type of a pointer to the ASIO io_service being used
89     typedef boost::asio::io_service* io_service_ptr;
90     /// Type of a pointer to the ASIO io_service::strand being used
91     typedef lib::shared_ptr<boost::asio::io_service::strand> strand_ptr;
92     /// Type of a pointer to the ASIO timer class
93     typedef lib::shared_ptr<boost::asio::deadline_timer> timer_ptr;
94 
95     // connection is friends with its associated endpoint to allow the endpoint
96     // to call private/protected utility methods that we don't want to expose
97     // to the public api.
98     friend class endpoint<config>;
99 
100     // generate and manage our own io_service
connection(bool is_server,alog_type & alog,elog_type & elog)101     explicit connection(bool is_server, alog_type& alog, elog_type& elog)
102       : m_is_server(is_server)
103       , m_alog(alog)
104       , m_elog(elog)
105     {
106         m_alog.write(log::alevel::devel,"asio con transport constructor");
107     }
108 
109     /// Get a shared pointer to this component
get_shared()110     ptr get_shared() {
111         return lib::static_pointer_cast<type>(socket_con_type::get_shared());
112     }
113 
is_secure() const114     bool is_secure() const {
115         return socket_con_type::is_secure();
116     }
117 
118     /// Sets the tcp pre init handler
119     /**
120      * The tcp pre init handler is called after the raw tcp connection has been
121      * established but before any additional wrappers (proxy connects, TLS
122      * handshakes, etc) have been performed.
123      *
124      * @since 0.3.0
125      *
126      * @param h The handler to call on tcp pre init.
127      */
set_tcp_pre_init_handler(tcp_init_handler h)128     void set_tcp_pre_init_handler(tcp_init_handler h) {
129         m_tcp_pre_init_handler = h;
130     }
131 
132     /// Sets the tcp pre init handler (deprecated)
133     /**
134      * The tcp pre init handler is called after the raw tcp connection has been
135      * established but before any additional wrappers (proxy connects, TLS
136      * handshakes, etc) have been performed.
137      *
138      * @deprecated Use set_tcp_pre_init_handler instead
139      *
140      * @param h The handler to call on tcp pre init.
141      */
set_tcp_init_handler(tcp_init_handler h)142     void set_tcp_init_handler(tcp_init_handler h) {
143         set_tcp_pre_init_handler(h);
144     }
145 
146     /// Sets the tcp post init handler
147     /**
148      * The tcp post init handler is called after the tcp connection has been
149      * established and all additional wrappers (proxy connects, TLS handshakes,
150      * etc have been performed. This is fired before any bytes are read or any
151      * WebSocket specific handshake logic has been performed.
152      *
153      * @since 0.3.0
154      *
155      * @param h The handler to call on tcp post init.
156      */
set_tcp_post_init_handler(tcp_init_handler h)157     void set_tcp_post_init_handler(tcp_init_handler h) {
158         m_tcp_post_init_handler = h;
159     }
160 
161     /// Set the proxy to connect through (exception free)
162     /**
163      * The URI passed should be a complete URI including scheme. For example:
164      * http://proxy.example.com:8080/
165      *
166      * The proxy must be set up as an explicit (CONNECT) proxy allowed to
167      * connect to the port you specify. Traffic to the proxy is not encrypted.
168      *
169      * @param uri The full URI of the proxy to connect to.
170      *
171      * @param ec A status value
172      */
set_proxy(std::string const & uri,lib::error_code & ec)173     void set_proxy(std::string const & uri, lib::error_code & ec) {
174         // TODO: return errors for illegal URIs here?
175         // TODO: should https urls be illegal for the moment?
176         m_proxy = uri;
177         m_proxy_data = lib::make_shared<proxy_data>();
178         ec = lib::error_code();
179     }
180 
181     /// Set the proxy to connect through (exception)
set_proxy(std::string const & uri)182     void set_proxy(std::string const & uri) {
183         lib::error_code ec;
184         set_proxy(uri,ec);
185         if (ec) { throw exception(ec); }
186     }
187 
188     /// Set the basic auth credentials to use (exception free)
189     /**
190      * The URI passed should be a complete URI including scheme. For example:
191      * http://proxy.example.com:8080/
192      *
193      * The proxy must be set up as an explicit proxy
194      *
195      * @param username The username to send
196      *
197      * @param password The password to send
198      *
199      * @param ec A status value
200      */
set_proxy_basic_auth(std::string const & username,std::string const & password,lib::error_code & ec)201     void set_proxy_basic_auth(std::string const & username, std::string const &
202         password, lib::error_code & ec)
203     {
204         if (!m_proxy_data) {
205             ec = make_error_code(websocketpp::error::invalid_state);
206             return;
207         }
208 
209         // TODO: username can't contain ':'
210         std::string val = "Basic "+base64_encode(username + ":" + password);
211         m_proxy_data->req.replace_header("Proxy-Authorization",val);
212         ec = lib::error_code();
213     }
214 
215     /// Set the basic auth credentials to use (exception)
set_proxy_basic_auth(std::string const & username,std::string const & password)216     void set_proxy_basic_auth(std::string const & username, std::string const &
217         password)
218     {
219         lib::error_code ec;
220         set_proxy_basic_auth(username,password,ec);
221         if (ec) { throw exception(ec); }
222     }
223 
224     /// Set the proxy timeout duration (exception free)
225     /**
226      * Duration is in milliseconds. Default value is based on the transport
227      * config
228      *
229      * @param duration The number of milliseconds to wait before aborting the
230      * proxy connection.
231      *
232      * @param ec A status value
233      */
set_proxy_timeout(long duration,lib::error_code & ec)234     void set_proxy_timeout(long duration, lib::error_code & ec) {
235         if (!m_proxy_data) {
236             ec = make_error_code(websocketpp::error::invalid_state);
237             return;
238         }
239 
240         m_proxy_data->timeout_proxy = duration;
241         ec = lib::error_code();
242     }
243 
244     /// Set the proxy timeout duration (exception)
set_proxy_timeout(long duration)245     void set_proxy_timeout(long duration) {
246         lib::error_code ec;
247         set_proxy_timeout(duration,ec);
248         if (ec) { throw exception(ec); }
249     }
250 
get_proxy() const251     std::string const & get_proxy() const {
252         return m_proxy;
253     }
254 
255     /// Get the remote endpoint address
256     /**
257      * The iostream transport has no information about the ultimate remote
258      * endpoint. It will return the string "iostream transport". To indicate
259      * this.
260      *
261      * TODO: allow user settable remote endpoint addresses if this seems useful
262      *
263      * @return A string identifying the address of the remote endpoint
264      */
get_remote_endpoint() const265     std::string get_remote_endpoint() const {
266         lib::error_code ec;
267 
268         std::string ret = socket_con_type::get_remote_endpoint(ec);
269 
270         if (ec) {
271             m_elog.write(log::elevel::info,ret);
272             return "Unknown";
273         } else {
274             return ret;
275         }
276     }
277 
278     /// Get the connection handle
get_handle() const279     connection_hdl get_handle() const {
280         return m_connection_hdl;
281     }
282 
283     /// Call back a function after a period of time.
284     /**
285      * Sets a timer that calls back a function after the specified period of
286      * milliseconds. Returns a handle that can be used to cancel the timer.
287      * A cancelled timer will return the error code error::operation_aborted
288      * A timer that expired will return no error.
289      *
290      * @param duration Length of time to wait in milliseconds
291      *
292      * @param callback The function to call back when the timer has expired
293      *
294      * @return A handle that can be used to cancel the timer if it is no longer
295      * needed.
296      */
set_timer(long duration,timer_handler callback)297     timer_ptr set_timer(long duration, timer_handler callback) {
298         timer_ptr new_timer = lib::make_shared<boost::asio::deadline_timer>(
299             *m_io_service,
300             boost::posix_time::milliseconds(duration)
301         );
302 
303         if (config::enable_multithreading) {
304             new_timer->async_wait(m_strand->wrap(lib::bind(
305                 &type::handle_timer, get_shared(),
306                 new_timer,
307                 callback,
308                 lib::placeholders::_1
309             )));
310         } else {
311             new_timer->async_wait(lib::bind(
312                 &type::handle_timer, get_shared(),
313                 new_timer,
314                 callback,
315                 lib::placeholders::_1
316             ));
317         }
318 
319         return new_timer;
320     }
321 
322     /// Timer callback
323     /**
324      * The timer pointer is included to ensure the timer isn't destroyed until
325      * after it has expired.
326      *
327      * TODO: candidate for protected status
328      *
329      * @param post_timer Pointer to the timer in question
330      * @param callback The function to call back
331      * @param ec The status code
332      */
handle_timer(timer_ptr,timer_handler callback,boost::system::error_code const & ec)333     void handle_timer(timer_ptr, timer_handler callback,
334         boost::system::error_code const & ec)
335     {
336         if (ec) {
337             if (ec == boost::asio::error::operation_aborted) {
338                 callback(make_error_code(transport::error::operation_aborted));
339             } else {
340                 log_err(log::elevel::info,"asio handle_timer",ec);
341                 callback(make_error_code(error::pass_through));
342             }
343         } else {
344             callback(lib::error_code());
345         }
346     }
347 protected:
348     /// Get a pointer to this connection's strand
get_strand()349     strand_ptr get_strand() {
350         return m_strand;
351     }
352 
353     /// Initialize transport for reading
354     /**
355      * init_asio is called once immediately after construction to initialize
356      * boost::asio components to the io_service
357      *
358      * The transport initialization sequence consists of the following steps:
359      * - Pre-init: the underlying socket is initialized to the point where
360      * bytes may be written. No bytes are actually written in this stage
361      * - Proxy negotiation: if a proxy is set, a request is made to it to start
362      * a tunnel to the final destination. This stage ends when the proxy is
363      * ready to forward the
364      * next byte to the remote endpoint.
365      * - Post-init: Perform any i/o with the remote endpoint, such as setting up
366      * tunnels for encryption. This stage ends when the connection is ready to
367      * read or write the WebSocket handshakes. At this point the original
368      * callback function is called.
369      */
init(init_handler callback)370     void init(init_handler callback) {
371         if (m_alog.static_test(log::alevel::devel)) {
372             m_alog.write(log::alevel::devel,"asio connection init");
373         }
374 
375         // TODO: pre-init timeout. Right now no implemented socket policies
376         // actually have an asyncronous pre-init
377 
378         m_init_handler = callback;
379 
380         socket_con_type::pre_init(
381             lib::bind(
382                 &type::handle_pre_init,
383                 get_shared(),
384                 lib::placeholders::_1
385             )
386         );
387     }
388 
389     /// initialize the proxy buffers and http parsers
390     /**
391      *
392      * @param authority The address of the server we want the proxy to tunnel to
393      * in the format of a URI authority (host:port)
394      *
395      * @return Status code indicating what errors occurred, if any
396      */
proxy_init(std::string const & authority)397     lib::error_code proxy_init(std::string const & authority) {
398         if (!m_proxy_data) {
399             return websocketpp::error::make_error_code(
400                 websocketpp::error::invalid_state);
401         }
402         m_proxy_data->req.set_version("HTTP/1.1");
403         m_proxy_data->req.set_method("CONNECT");
404 
405         m_proxy_data->req.set_uri(authority);
406         m_proxy_data->req.replace_header("Host",authority);
407 
408         return lib::error_code();
409     }
410 
411     /// Finish constructing the transport
412     /**
413      * init_asio is called once immediately after construction to initialize
414      * boost::asio components to the io_service.
415      *
416      * @param io_service A pointer to the io_service to register with this
417      * connection
418      *
419      * @return Status code for the success or failure of the initialization
420      */
init_asio(io_service_ptr io_service)421     lib::error_code init_asio (io_service_ptr io_service) {
422         m_io_service = io_service;
423 
424         if (config::enable_multithreading) {
425             m_strand = lib::make_shared<boost::asio::io_service::strand>(
426                 lib::ref(*io_service));
427 
428             m_async_read_handler = m_strand->wrap(lib::bind(
429                 &type::handle_async_read, get_shared(),lib::placeholders::_1,
430                 lib::placeholders::_2));
431 
432             m_async_write_handler = m_strand->wrap(lib::bind(
433                 &type::handle_async_write, get_shared(),lib::placeholders::_1,
434                 lib::placeholders::_2));
435         } else {
436             m_async_read_handler = lib::bind(&type::handle_async_read,
437                 get_shared(), lib::placeholders::_1, lib::placeholders::_2);
438 
439             m_async_write_handler = lib::bind(&type::handle_async_write,
440                 get_shared(), lib::placeholders::_1, lib::placeholders::_2);
441         }
442 
443         lib::error_code ec = socket_con_type::init_asio(io_service, m_strand,
444             m_is_server);
445 
446         if (ec) {
447             // reset the handlers to break the circular reference:
448             // this->handler->this
449             lib::clear_function(m_async_read_handler);
450             lib::clear_function(m_async_write_handler);
451         }
452 
453         return ec;
454     }
455 
handle_pre_init(lib::error_code const & ec)456     void handle_pre_init(lib::error_code const & ec) {
457         if (m_alog.static_test(log::alevel::devel)) {
458             m_alog.write(log::alevel::devel,"asio connection handle pre_init");
459         }
460 
461         if (m_tcp_pre_init_handler) {
462             m_tcp_pre_init_handler(m_connection_hdl);
463         }
464 
465         if (ec) {
466             m_init_handler(ec);
467         }
468 
469         // If we have a proxy set issue a proxy connect, otherwise skip to
470         // post_init
471         if (!m_proxy.empty()) {
472             proxy_write();
473         } else {
474             post_init();
475         }
476     }
477 
post_init()478     void post_init() {
479         if (m_alog.static_test(log::alevel::devel)) {
480             m_alog.write(log::alevel::devel,"asio connection post_init");
481         }
482 
483         timer_ptr post_timer;
484 
485         if (config::timeout_socket_post_init > 0) {
486             post_timer = set_timer(
487                 config::timeout_socket_post_init,
488                 lib::bind(
489                     &type::handle_post_init_timeout,
490                     get_shared(),
491                     post_timer,
492                     m_init_handler,
493                     lib::placeholders::_1
494                 )
495             );
496         }
497 
498         socket_con_type::post_init(
499             lib::bind(
500                 &type::handle_post_init,
501                 get_shared(),
502                 post_timer,
503                 m_init_handler,
504                 lib::placeholders::_1
505             )
506         );
507     }
508 
509     /// Post init timeout callback
510     /**
511      * The timer pointer is included to ensure the timer isn't destroyed until
512      * after it has expired.
513      *
514      * @param post_timer Pointer to the timer in question
515      * @param callback The function to call back
516      * @param ec The status code
517      */
handle_post_init_timeout(timer_ptr,init_handler callback,lib::error_code const & ec)518     void handle_post_init_timeout(timer_ptr, init_handler callback,
519         lib::error_code const & ec)
520     {
521         lib::error_code ret_ec;
522 
523         if (ec) {
524             if (ec == transport::error::operation_aborted) {
525                 m_alog.write(log::alevel::devel,
526                     "asio post init timer cancelled");
527                 return;
528             }
529 
530             log_err(log::elevel::devel,"asio handle_post_init_timeout",ec);
531             ret_ec = ec;
532         } else {
533             if (socket_con_type::get_ec()) {
534                 ret_ec = socket_con_type::get_ec();
535             } else {
536                 ret_ec = make_error_code(transport::error::timeout);
537             }
538         }
539 
540         m_alog.write(log::alevel::devel,"Asio transport post-init timed out");
541         socket_con_type::cancel_socket();
542         callback(ret_ec);
543     }
544 
545     /// Post init timeout callback
546     /**
547      * The timer pointer is included to ensure the timer isn't destroyed until
548      * after it has expired.
549      *
550      * @param post_timer Pointer to the timer in question
551      * @param callback The function to call back
552      * @param ec The status code
553      */
handle_post_init(timer_ptr post_timer,init_handler callback,lib::error_code const & ec)554     void handle_post_init(timer_ptr post_timer, init_handler callback,
555         lib::error_code const & ec)
556     {
557         if (ec == transport::error::operation_aborted ||
558             (post_timer && post_timer->expires_from_now().is_negative()))
559         {
560             m_alog.write(log::alevel::devel,"post_init cancelled");
561             return;
562         }
563 
564         if (post_timer) {
565             post_timer->cancel();
566         }
567 
568         if (m_alog.static_test(log::alevel::devel)) {
569             m_alog.write(log::alevel::devel,"asio connection handle_post_init");
570         }
571 
572         if (m_tcp_post_init_handler) {
573             m_tcp_post_init_handler(m_connection_hdl);
574         }
575 
576         callback(ec);
577     }
578 
proxy_write()579     void proxy_write() {
580         if (m_alog.static_test(log::alevel::devel)) {
581             m_alog.write(log::alevel::devel,"asio connection proxy_write");
582         }
583 
584         if (!m_proxy_data) {
585             m_elog.write(log::elevel::library,
586                 "assertion failed: !m_proxy_data in asio::connection::proxy_write");
587             m_init_handler(make_error_code(error::general));
588             return;
589         }
590 
591         m_proxy_data->write_buf = m_proxy_data->req.raw();
592 
593         m_bufs.push_back(boost::asio::buffer(m_proxy_data->write_buf.data(),
594                                              m_proxy_data->write_buf.size()));
595 
596         m_alog.write(log::alevel::devel,m_proxy_data->write_buf);
597 
598         // Set a timer so we don't wait forever for the proxy to respond
599         m_proxy_data->timer = this->set_timer(
600             m_proxy_data->timeout_proxy,
601             lib::bind(
602                 &type::handle_proxy_timeout,
603                 get_shared(),
604                 m_init_handler,
605                 lib::placeholders::_1
606             )
607         );
608 
609         // Send proxy request
610         if (config::enable_multithreading) {
611             boost::asio::async_write(
612                 socket_con_type::get_next_layer(),
613                 m_bufs,
614                 m_strand->wrap(lib::bind(
615                     &type::handle_proxy_write, get_shared(),
616                     m_init_handler,
617                     lib::placeholders::_1
618                 ))
619             );
620         } else {
621             boost::asio::async_write(
622                 socket_con_type::get_next_layer(),
623                 m_bufs,
624                 lib::bind(
625                     &type::handle_proxy_write, get_shared(),
626                     m_init_handler,
627                     lib::placeholders::_1
628                 )
629             );
630         }
631     }
632 
handle_proxy_timeout(init_handler callback,lib::error_code const & ec)633     void handle_proxy_timeout(init_handler callback, lib::error_code const & ec)
634     {
635         if (ec == transport::error::operation_aborted) {
636             m_alog.write(log::alevel::devel,
637                 "asio handle_proxy_write timer cancelled");
638             return;
639         } else if (ec) {
640             log_err(log::elevel::devel,"asio handle_proxy_write",ec);
641             callback(ec);
642         } else {
643             m_alog.write(log::alevel::devel,
644                 "asio handle_proxy_write timer expired");
645             socket_con_type::cancel_socket();
646             callback(make_error_code(transport::error::timeout));
647         }
648     }
649 
handle_proxy_write(init_handler callback,boost::system::error_code const & ec)650     void handle_proxy_write(init_handler callback,
651         boost::system::error_code const & ec)
652     {
653         if (m_alog.static_test(log::alevel::devel)) {
654             m_alog.write(log::alevel::devel,
655                 "asio connection handle_proxy_write");
656         }
657 
658         m_bufs.clear();
659 
660         // Timer expired or the operation was aborted for some reason.
661         // Whatever aborted it will be issuing the callback so we are safe to
662         // return
663         if (ec == boost::asio::error::operation_aborted ||
664             m_proxy_data->timer->expires_from_now().is_negative())
665         {
666             m_elog.write(log::elevel::devel,"write operation aborted");
667             return;
668         }
669 
670         if (ec) {
671             log_err(log::elevel::info,"asio handle_proxy_write",ec);
672             m_proxy_data->timer->cancel();
673             callback(make_error_code(error::pass_through));
674             return;
675         }
676 
677         proxy_read(callback);
678     }
679 
proxy_read(init_handler callback)680     void proxy_read(init_handler callback) {
681         if (m_alog.static_test(log::alevel::devel)) {
682             m_alog.write(log::alevel::devel,"asio connection proxy_read");
683         }
684 
685         if (!m_proxy_data) {
686             m_elog.write(log::elevel::library,
687                 "assertion failed: !m_proxy_data in asio::connection::proxy_read");
688             m_proxy_data->timer->cancel();
689             callback(make_error_code(error::general));
690             return;
691         }
692 
693         if (config::enable_multithreading) {
694             boost::asio::async_read_until(
695                 socket_con_type::get_next_layer(),
696                 m_proxy_data->read_buf,
697                 "\r\n\r\n",
698                 m_strand->wrap(lib::bind(
699                     &type::handle_proxy_read, get_shared(),
700                     callback,
701                     lib::placeholders::_1, lib::placeholders::_2
702                 ))
703             );
704         } else {
705             boost::asio::async_read_until(
706                 socket_con_type::get_next_layer(),
707                 m_proxy_data->read_buf,
708                 "\r\n\r\n",
709                 lib::bind(
710                     &type::handle_proxy_read, get_shared(),
711                     callback,
712                     lib::placeholders::_1, lib::placeholders::_2
713                 )
714             );
715         }
716     }
717 
718     /// Proxy read callback
719     /**
720      * @param init_handler The function to call back
721      * @param ec The status code
722      * @param bytes_transferred The number of bytes read
723      */
handle_proxy_read(init_handler callback,boost::system::error_code const & ec,size_t)724     void handle_proxy_read(init_handler callback,
725         boost::system::error_code const & ec, size_t)
726     {
727         if (m_alog.static_test(log::alevel::devel)) {
728             m_alog.write(log::alevel::devel,
729                 "asio connection handle_proxy_read");
730         }
731 
732         // Timer expired or the operation was aborted for some reason.
733         // Whatever aborted it will be issuing the callback so we are safe to
734         // return
735         if (ec == boost::asio::error::operation_aborted ||
736             m_proxy_data->timer->expires_from_now().is_negative())
737         {
738             m_elog.write(log::elevel::devel,"read operation aborted");
739             return;
740         }
741 
742         // At this point there is no need to wait for the timer anymore
743         m_proxy_data->timer->cancel();
744 
745         if (ec) {
746             m_elog.write(log::elevel::info,
747                 "asio handle_proxy_read error: "+ec.message());
748             callback(make_error_code(error::pass_through));
749         } else {
750             if (!m_proxy_data) {
751                 m_elog.write(log::elevel::library,
752                     "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
753                 callback(make_error_code(error::general));
754                 return;
755             }
756 
757             std::istream input(&m_proxy_data->read_buf);
758 
759             m_proxy_data->res.consume(input);
760 
761             if (!m_proxy_data->res.headers_ready()) {
762                 // we read until the headers were done in theory but apparently
763                 // they aren't. Internal endpoint error.
764                 callback(make_error_code(error::general));
765                 return;
766             }
767 
768             m_alog.write(log::alevel::devel,m_proxy_data->res.raw());
769 
770             if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
771                 // got an error response back
772                 // TODO: expose this error in a programmatically accessible way?
773                 // if so, see below for an option on how to do this.
774                 std::stringstream s;
775                 s << "Proxy connection error: "
776                   << m_proxy_data->res.get_status_code()
777                   << " ("
778                   << m_proxy_data->res.get_status_msg()
779                   << ")";
780                 m_elog.write(log::elevel::info,s.str());
781                 callback(make_error_code(error::proxy_failed));
782                 return;
783             }
784 
785             // we have successfully established a connection to the proxy, now
786             // we can continue and the proxy will transparently forward the
787             // WebSocket connection.
788 
789             // TODO: decide if we want an on_proxy callback that would allow
790             // access to the proxy response.
791 
792             // free the proxy buffers and req/res objects as they aren't needed
793             // anymore
794             m_proxy_data.reset();
795 
796             // Continue with post proxy initialization
797             post_init();
798         }
799     }
800 
801     /// read at least num_bytes bytes into buf and then call handler.
802     /**
803      *
804      *
805      */
async_read_at_least(size_t num_bytes,char * buf,size_t len,read_handler handler)806     void async_read_at_least(size_t num_bytes, char *buf, size_t len,
807         read_handler handler)
808     {
809         if (m_alog.static_test(log::alevel::devel)) {
810             std::stringstream s;
811             s << "asio async_read_at_least: " << num_bytes;
812             m_alog.write(log::alevel::devel,s.str());
813         }
814 
815         if (!m_async_read_handler) {
816             m_alog.write(log::alevel::devel,
817                 "async_read_at_least called after async_shutdown");
818             handler(make_error_code(transport::error::action_after_shutdown),0);
819             return;
820         }
821 
822         // TODO: safety vs speed ?
823         // maybe move into an if devel block
824         /*if (num_bytes > len) {
825             m_elog.write(log::elevel::devel,
826                 "asio async_read_at_least error::invalid_num_bytes");
827             handler(make_error_code(transport::error::invalid_num_bytes),
828                 size_t(0));
829             return;
830         }*/
831 
832         m_read_handler = handler;
833 
834         if (!m_read_handler) {
835             m_alog.write(log::alevel::devel,
836                 "asio con async_read_at_least called with bad handler");
837         }
838 
839         boost::asio::async_read(
840             socket_con_type::get_socket(),
841             boost::asio::buffer(buf,len),
842             boost::asio::transfer_at_least(num_bytes),
843             make_custom_alloc_handler(
844                 m_read_handler_allocator,
845                 m_async_read_handler
846             )
847         );
848     }
849 
handle_async_read(boost::system::error_code const & ec,size_t bytes_transferred)850     void handle_async_read(boost::system::error_code const & ec,
851         size_t bytes_transferred)
852     {
853         m_alog.write(log::alevel::devel, "asio con handle_async_read");
854 
855         // translate boost error codes into more lib::error_codes
856         lib::error_code tec;
857         if (ec == boost::asio::error::eof) {
858             tec = make_error_code(transport::error::eof);
859         } else if (ec) {
860             // We don't know much more about the error at this point. As our
861             // socket/security policy if it knows more:
862             tec = socket_con_type::translate_ec(ec);
863 
864             if (tec == transport::error::tls_error ||
865                 tec == transport::error::pass_through)
866             {
867                 // These are aggregate/catch all errors. Log some human readable
868                 // information to the info channel to give library users some
869                 // more details about why the upstream method may have failed.
870                 log_err(log::elevel::info,"asio async_read_at_least",ec);
871             }
872         }
873         if (m_read_handler) {
874             m_read_handler(tec,bytes_transferred);
875             // TODO: why does this line break things?
876             //m_read_handler = _WEBSOCKETPP_NULL_FUNCTION_;
877         } else {
878             // This can happen in cases where the connection is terminated while
879             // the transport is waiting on a read.
880             m_alog.write(log::alevel::devel,
881                 "handle_async_read called with null read handler");
882         }
883     }
884 
async_write(const char * buf,size_t len,write_handler handler)885     void async_write(const char* buf, size_t len, write_handler handler) {
886         if (!m_async_write_handler) {
887             m_alog.write(log::alevel::devel,
888                 "async_write (single) called after async_shutdown");
889             handler(make_error_code(transport::error::action_after_shutdown));
890             return;
891         }
892 
893         m_bufs.push_back(boost::asio::buffer(buf,len));
894 
895         m_write_handler = handler;
896 
897         boost::asio::async_write(
898             socket_con_type::get_socket(),
899             m_bufs,
900             make_custom_alloc_handler(
901                 m_write_handler_allocator,
902                 m_async_write_handler
903             )
904         );
905     }
906 
async_write(std::vector<buffer> const & bufs,write_handler handler)907     void async_write(std::vector<buffer> const & bufs, write_handler handler) {
908         if (!m_async_write_handler) {
909             m_alog.write(log::alevel::devel,
910                 "async_write (vector) called after async_shutdown");
911             handler(make_error_code(transport::error::action_after_shutdown));
912             return;
913         }
914         std::vector<buffer>::const_iterator it;
915 
916         for (it = bufs.begin(); it != bufs.end(); ++it) {
917             m_bufs.push_back(boost::asio::buffer((*it).buf,(*it).len));
918         }
919 
920         m_write_handler = handler;
921 
922         boost::asio::async_write(
923             socket_con_type::get_socket(),
924             m_bufs,
925             make_custom_alloc_handler(
926                 m_write_handler_allocator,
927                 m_async_write_handler
928             )
929         );
930     }
931 
932     /// Async write callback
933     /**
934      * @param ec The status code
935      * @param bytes_transferred The number of bytes read
936      */
handle_async_write(boost::system::error_code const & ec,size_t)937     void handle_async_write(boost::system::error_code const & ec, size_t) {
938         m_bufs.clear();
939         lib::error_code tec;
940         if (ec) {
941             log_err(log::elevel::info,"asio async_write",ec);
942             tec = make_error_code(transport::error::pass_through);
943         }
944         if (m_write_handler) {
945             m_write_handler(tec);
946             // TODO: why does this line break things?
947             //m_write_handler = _WEBSOCKETPP_NULL_FUNCTION_;
948         } else {
949             // This can happen in cases where the connection is terminated while
950             // the transport is waiting on a read.
951             m_alog.write(log::alevel::devel,
952                 "handle_async_write called with null write handler");
953         }
954     }
955 
956     /// Set Connection Handle
957     /**
958      * See common/connection_hdl.hpp for information
959      *
960      * @param hdl A connection_hdl that the transport will use to refer
961      * to itself
962      */
set_handle(connection_hdl hdl)963     void set_handle(connection_hdl hdl) {
964         m_connection_hdl = hdl;
965         socket_con_type::set_handle(hdl);
966     }
967 
968     /// Trigger the on_interrupt handler
969     /**
970      * This needs to be thread safe
971      */
interrupt(interrupt_handler handler)972     lib::error_code interrupt(interrupt_handler handler) {
973         if (config::enable_multithreading) {
974             m_io_service->post(m_strand->wrap(handler));
975         } else {
976             m_io_service->post(handler);
977         }
978         return lib::error_code();
979     }
980 
dispatch(dispatch_handler handler)981     lib::error_code dispatch(dispatch_handler handler) {
982         if (config::enable_multithreading) {
983             m_io_service->post(m_strand->wrap(handler));
984         } else {
985             m_io_service->post(handler);
986         }
987         return lib::error_code();
988     }
989 
990     /*void handle_interrupt(interrupt_handler handler) {
991         handler();
992     }*/
993 
994     /// close and clean up the underlying socket
async_shutdown(shutdown_handler callback)995     void async_shutdown(shutdown_handler callback) {
996         if (m_alog.static_test(log::alevel::devel)) {
997             m_alog.write(log::alevel::devel,"asio connection async_shutdown");
998         }
999 
1000         // Reset cached handlers now that we won't be reading or writing anymore
1001         // These cached handlers store shared pointers to this connection and
1002         // will leak the connection if not destroyed.
1003         lib::clear_function(m_async_read_handler);
1004         lib::clear_function(m_async_write_handler);
1005         lib::clear_function(m_init_handler);
1006 
1007         lib::clear_function(m_read_handler);
1008         lib::clear_function(m_write_handler);
1009 
1010         timer_ptr shutdown_timer;
1011         shutdown_timer = set_timer(
1012             config::timeout_socket_shutdown,
1013             lib::bind(
1014                 &type::handle_async_shutdown_timeout,
1015                 get_shared(),
1016                 shutdown_timer,
1017                 callback,
1018                 lib::placeholders::_1
1019             )
1020         );
1021 
1022         socket_con_type::async_shutdown(
1023             lib::bind(
1024                 &type::handle_async_shutdown,
1025                 get_shared(),
1026                 shutdown_timer,
1027                 callback,
1028                 lib::placeholders::_1
1029             )
1030         );
1031     }
1032 
1033     /// Async shutdown timeout handler
1034     /**
1035      * @param shutdown_timer A pointer to the timer to keep it in scope
1036      * @param callback The function to call back
1037      * @param ec The status code
1038      */
handle_async_shutdown_timeout(timer_ptr,init_handler callback,lib::error_code const & ec)1039     void handle_async_shutdown_timeout(timer_ptr, init_handler callback,
1040         lib::error_code const & ec)
1041     {
1042         lib::error_code ret_ec;
1043 
1044         if (ec) {
1045             if (ec == transport::error::operation_aborted) {
1046                 m_alog.write(log::alevel::devel,
1047                     "asio socket shutdown timer cancelled");
1048                 return;
1049             }
1050 
1051             log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec);
1052             ret_ec = ec;
1053         } else {
1054             ret_ec = make_error_code(transport::error::timeout);
1055         }
1056 
1057         m_alog.write(log::alevel::devel,
1058             "Asio transport socket shutdown timed out");
1059         socket_con_type::cancel_socket();
1060         callback(ret_ec);
1061     }
1062 
handle_async_shutdown(timer_ptr shutdown_timer,shutdown_handler callback,boost::system::error_code const & ec)1063     void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler
1064         callback, boost::system::error_code const & ec)
1065     {
1066         if (ec == boost::asio::error::operation_aborted ||
1067             shutdown_timer->expires_from_now().is_negative())
1068         {
1069             m_alog.write(log::alevel::devel,"async_shutdown cancelled");
1070             return;
1071         }
1072 
1073         shutdown_timer->cancel();
1074 
1075         lib::error_code tec;
1076         if (ec) {
1077             if (ec == boost::asio::error::not_connected) {
1078                 // The socket was already closed when we tried to close it. This
1079                 // happens periodically (usually if a read or write fails
1080                 // earlier and if it is a real error will be caught at another
1081                 // level of the stack.
1082             } else {
1083                 // We don't know anything more about this error, give our
1084                 // socket/security policy a crack at it.
1085                 tec = socket_con_type::translate_ec(ec);
1086 
1087                 if (tec == transport::error::tls_short_read) {
1088                     // TLS short read at this point is somewhat expected if both
1089                     // sides try and end the connection at the same time or if
1090                     // SSLv2 is being used. In general there is nothing that can
1091                     // be done here other than a low level development log.
1092                 } else {
1093                     // all other errors are effectively pass through errors of
1094                     // some sort so print some detail on the info channel for
1095                     // library users to look up if needed.
1096                     log_err(log::elevel::info,"asio async_shutdown",ec);
1097                 }
1098             }
1099         } else {
1100             if (m_alog.static_test(log::alevel::devel)) {
1101                 m_alog.write(log::alevel::devel,
1102                     "asio con handle_async_shutdown");
1103             }
1104         }
1105         callback(tec);
1106     }
1107 private:
1108     /// Convenience method for logging the code and message for an error_code
1109     template <typename error_type>
log_err(log::level l,const char * msg,const error_type & ec)1110     void log_err(log::level l, const char * msg, const error_type & ec) {
1111         std::stringstream s;
1112         s << msg << " error: " << ec << " (" << ec.message() << ")";
1113         m_elog.write(l,s.str());
1114     }
1115 
1116     // static settings
1117     const bool m_is_server;
1118     alog_type& m_alog;
1119     elog_type& m_elog;
1120 
1121     struct proxy_data {
proxy_datawebsocketpp::transport::asio::connection::proxy_data1122         proxy_data() : timeout_proxy(config::timeout_proxy) {}
1123 
1124         request_type req;
1125         response_type res;
1126         std::string write_buf;
1127         boost::asio::streambuf read_buf;
1128         long timeout_proxy;
1129         timer_ptr timer;
1130     };
1131 
1132     std::string m_proxy;
1133     lib::shared_ptr<proxy_data> m_proxy_data;
1134 
1135     // transport resources
1136     io_service_ptr  m_io_service;
1137     strand_ptr      m_strand;
1138     connection_hdl  m_connection_hdl;
1139 
1140     std::vector<boost::asio::const_buffer> m_bufs;
1141 
1142     // Handlers
1143     tcp_init_handler    m_tcp_pre_init_handler;
1144     tcp_init_handler    m_tcp_post_init_handler;
1145 
1146     handler_allocator   m_read_handler_allocator;
1147     handler_allocator   m_write_handler_allocator;
1148 
1149     read_handler        m_read_handler;
1150     write_handler       m_write_handler;
1151     init_handler        m_init_handler;
1152 
1153     async_read_handler  m_async_read_handler;
1154     async_write_handler m_async_write_handler;
1155 };
1156 
1157 
1158 } // namespace asio
1159 } // namespace transport
1160 } // namespace websocketpp
1161 
1162 #endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
1163