1 #ifndef SIMPLE_WEB_CLIENT_HTTPS_HPP 2 #define SIMPLE_WEB_CLIENT_HTTPS_HPP 3 4 #include "client_http.hpp" 5 6 #ifdef ASIO_STANDALONE 7 #include <asio/ssl.hpp> 8 #else 9 #include <boost/asio/ssl.hpp> 10 #endif 11 12 namespace SimpleWeb { 13 using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>; 14 15 template <> 16 class Client<HTTPS> : public ClientBase<HTTPS> { 17 public: 18 /** 19 * Constructs a client object. 20 * 21 * @param server_port_path Server resource given by host[:port][/path] 22 * @param verify_certificate Set to true (default) to verify the server's certificate and hostname according to RFC 2818. 23 * @param certification_file If non-empty, sends the given certification file to server. Requires private_key_file. 24 * @param private_key_file If non-empty, specifies the file containing the private key for certification_file. Requires certification_file. 25 * @param verify_file If non-empty, use this certificate authority file to perform verification. 26 */ Client(const std::string & server_port_path,bool verify_certificate=true,const std::string & certification_file=std::string (),const std::string & private_key_file=std::string (),const std::string & verify_file=std::string ())27 Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &certification_file = std::string(), 28 const std::string &private_key_file = std::string(), const std::string &verify_file = std::string()) 29 : ClientBase<HTTPS>::ClientBase(server_port_path, 443), 30 #if(ASIO_STANDALONE && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 31 context(asio::ssl::context::tls_client) { 32 // Disabling TLS 1.0 and 1.1 (see RFC 8996) 33 context.set_options(asio::ssl::context::no_tlsv1); 34 context.set_options(asio::ssl::context::no_tlsv1_1); 35 #else 36 context(asio::ssl::context::tlsv12) { 37 #endif 38 if(certification_file.size() > 0 && private_key_file.size() > 0) { 39 context.use_certificate_chain_file(certification_file); 40 context.use_private_key_file(private_key_file, asio::ssl::context::pem); 41 } 42 43 if(verify_certificate) 44 context.set_verify_callback(asio::ssl::rfc2818_verification(host)); 45 46 if(verify_file.size() > 0) 47 context.load_verify_file(verify_file); 48 else 49 context.set_default_verify_paths(); 50 51 if(verify_certificate) 52 context.set_verify_mode(asio::ssl::verify_peer); 53 else 54 context.set_verify_mode(asio::ssl::verify_none); 55 } 56 57 protected: 58 asio::ssl::context context; 59 60 /// Ignore for end of file and SSL_R_SHORT_READ error codes 61 error_code clean_error_code(const error_code &ec) override { 62 return ec == error::eof || ec == asio::ssl::error::stream_truncated ? error_code() : ec; 63 } 64 65 std::shared_ptr<Connection> create_connection() noexcept override { 66 return std::make_shared<Connection>(handler_runner, *io_service, context); 67 } 68 69 void connect(const std::shared_ptr<Session> &session) override { 70 if(!session->connection->socket->lowest_layer().is_open()) { 71 auto resolver = std::make_shared<asio::ip::tcp::resolver>(*io_service); 72 session->connection->set_timeout(this->config.timeout_connect); 73 async_resolve(*resolver, *host_port, [this, session, resolver](const error_code &ec, resolver_results results) { 74 session->connection->cancel_timeout(); 75 auto lock = session->connection->handler_runner->continue_lock(); 76 if(!lock) 77 return; 78 if(!ec) { 79 session->connection->set_timeout(this->config.timeout_connect); 80 asio::async_connect(session->connection->socket->lowest_layer(), results, [this, session, resolver](const error_code &ec, async_connect_endpoint /*endpoint*/) { 81 session->connection->cancel_timeout(); 82 auto lock = session->connection->handler_runner->continue_lock(); 83 if(!lock) 84 return; 85 if(!ec) { 86 asio::ip::tcp::no_delay option(true); 87 error_code ec; 88 session->connection->socket->lowest_layer().set_option(option, ec); 89 90 if(!this->config.proxy_server.empty()) { 91 auto write_buffer = std::make_shared<asio::streambuf>(); 92 std::ostream write_stream(write_buffer.get()); 93 auto host_port = this->host + ':' + std::to_string(this->port); 94 write_stream << "CONNECT " + host_port + " HTTP/1.1\r\n" 95 << "Host: " << host_port << "\r\n\r\n"; 96 session->connection->set_timeout(this->config.timeout_connect); 97 asio::async_write(session->connection->socket->next_layer(), *write_buffer, [this, session, write_buffer](const error_code &ec, std::size_t /*bytes_transferred*/) { 98 session->connection->cancel_timeout(); 99 auto lock = session->connection->handler_runner->continue_lock(); 100 if(!lock) 101 return; 102 if(!ec) { 103 std::shared_ptr<Response> response(new Response(this->config.max_response_streambuf_size, session->connection)); 104 session->connection->set_timeout(this->config.timeout_connect); 105 asio::async_read_until(session->connection->socket->next_layer(), response->streambuf, "\r\n\r\n", [this, session, response](const error_code &ec, std::size_t /*bytes_transferred*/) { 106 session->connection->cancel_timeout(); 107 auto lock = session->connection->handler_runner->continue_lock(); 108 if(!lock) 109 return; 110 if(response->streambuf.size() == response->streambuf.max_size()) { 111 session->callback(make_error_code::make_error_code(errc::message_size)); 112 return; 113 } 114 115 if(!ec) { 116 if(!ResponseMessage::parse(response->content, response->http_version, response->status_code, response->header)) 117 session->callback(make_error_code::make_error_code(errc::protocol_error)); 118 else { 119 if(response->status_code.compare(0, 3, "200") != 0) 120 session->callback(make_error_code::make_error_code(errc::permission_denied)); 121 else 122 this->handshake(session); 123 } 124 } 125 else 126 session->callback(ec); 127 }); 128 } 129 else 130 session->callback(ec); 131 }); 132 } 133 else 134 this->handshake(session); 135 } 136 else 137 session->callback(ec); 138 }); 139 } 140 else 141 session->callback(ec); 142 }); 143 } 144 else 145 write(session); 146 } 147 148 void handshake(const std::shared_ptr<Session> &session) { 149 SSL_set_tlsext_host_name(session->connection->socket->native_handle(), this->host.c_str()); 150 151 session->connection->set_timeout(this->config.timeout_connect); 152 session->connection->socket->async_handshake(asio::ssl::stream_base::client, [this, session](const error_code &ec) { 153 session->connection->cancel_timeout(); 154 auto lock = session->connection->handler_runner->continue_lock(); 155 if(!lock) 156 return; 157 if(!ec) 158 this->write(session); 159 else 160 session->callback(ec); 161 }); 162 } 163 }; 164 } // namespace SimpleWeb 165 166 #endif /* SIMPLE_WEB_CLIENT_HTTPS_HPP */ 167