diff options
author | Jack Lloyd <[email protected]> | 2016-10-31 02:02:03 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-10-31 02:02:03 -0400 |
commit | ce5d725a15063fdcd0de31b3384c1a3575312f65 (patch) | |
tree | 4a24e8b3e21ee4f460004c733297e072fa09f85e | |
parent | f034ff4fb4752d17a2a6b292d2f707224d6eef13 (diff) | |
parent | d6221c1cebb971d3c2369fb121b23eddf9cbfd6d (diff) |
Merge GH #699 Add socket-based HTTP client
-rw-r--r-- | src/lib/utils/http_util/http_util.cpp | 114 | ||||
-rw-r--r-- | src/lib/utils/http_util/http_util.h | 19 |
2 files changed, 101 insertions, 32 deletions
diff --git a/src/lib/utils/http_util/http_util.cpp b/src/lib/utils/http_util/http_util.cpp index 5624f2e4f..85bd1828b 100644 --- a/src/lib/utils/http_util/http_util.cpp +++ b/src/lib/utils/http_util/http_util.cpp @@ -1,6 +1,6 @@ /* * Sketchy HTTP client -* (C) 2013 Jack Lloyd +* (C) 2013,2016 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -21,16 +21,27 @@ #define BOOST_ASIO_DISABLE_SERIAL_PORT #include <boost/asio.hpp> +#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) + #include <sys/types.h> + #include <sys/socket.h> + #include <netdb.h> + #include <unistd.h> #endif namespace Botan { namespace HTTP { -#if defined(BOTAN_HAS_BOOST_ASIO) -std::string http_transact_asio(const std::string& hostname, - const std::string& message) +namespace { + +/* +* Connect to a host, write some bytes, then read until the server +* closes the socket. +*/ +std::string http_transact(const std::string& hostname, + const std::string& message) { +#if defined(BOTAN_HAS_BOOST_ASIO) using namespace boost::asio::ip; boost::asio::ip::tcp::iostream tcp; @@ -38,7 +49,7 @@ std::string http_transact_asio(const std::string& hostname, tcp.connect(hostname, "http"); if(!tcp) - throw Exception("HTTP connection to " + hostname + " failed"); + throw HTTP_Error("HTTP connection to " + hostname + " failed"); tcp << message; tcp.flush(); @@ -47,16 +58,77 @@ std::string http_transact_asio(const std::string& hostname, oss << tcp.rdbuf(); return oss.str(); - } -#endif +#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) -std::string http_transact_fail(const std::string& hostname, - const std::string&) - { - throw Exception("Cannot connect to " + hostname + + hostent* host_addr = ::gethostbyname(hostname.c_str()); + uint16_t port = 80; + + if(!host_addr) + throw HTTP_Error("Name resolution failed for " + hostname); + + if(host_addr->h_addrtype != AF_INET) // FIXME + throw HTTP_Error("Hostname " + hostname + " resolved to non-IPv4 address"); + + struct socket_raii { + socket_raii(int fd) : m_fd(fd) {} + ~socket_raii() { ::close(m_fd); } + int m_fd; + }; + + int fd = ::socket(PF_INET, SOCK_STREAM, 0); + if(fd == -1) + throw HTTP_Error("Unable to create TCP socket"); + socket_raii raii(fd); + + sockaddr_in socket_info; + ::memset(&socket_info, 0, sizeof(socket_info)); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); + + ::memcpy(&socket_info.sin_addr, + host_addr->h_addr, + host_addr->h_length); + + socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME + + if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) + throw HTTP_Error("HTTP connection to " + hostname + " failed"); + + size_t sent_so_far = 0; + while(sent_so_far != message.size()) + { + size_t left = message.size() - sent_so_far; + ssize_t sent = ::write(fd, &message[sent_so_far], left); + + if(sent < 0) + throw HTTP_Error("HTTP server hung up on us"); + else + sent_so_far += static_cast<size_t>(sent); + } + + std::ostringstream oss; + std::vector<char> buf(1024); // arbitrary size + while(true) + { + ssize_t got = ::read(fd, buf.data(), buf.size()); + + if(got < 0) + throw HTTP_Error("HTTP server hung up on us"); + else if(got > 0) + oss.write(buf.data(), static_cast<std::streamsize>(got)); + else + break; // EOF + } + return oss.str(); + +#else + throw HTTP_Error("Cannot connect to " + hostname + ": network code disabled in build"); +#endif } +} + std::string url_encode(const std::string& in) { std::ostringstream out; @@ -97,7 +169,7 @@ Response http_sync(http_exch_fn http_transact, { const auto protocol_host_sep = url.find("://"); if(protocol_host_sep == std::string::npos) - throw Exception("Invalid URL " + url); + throw HTTP_Error("Invalid URL " + url); const auto host_loc_sep = url.find('/', protocol_host_sep + 3); @@ -137,7 +209,7 @@ Response http_sync(http_exch_fn http_transact, std::string line1; std::getline(io, line1); if(!io || line1.empty()) - throw Exception("No response"); + throw HTTP_Error("No response"); std::stringstream response_stream(line1); std::string http_version; @@ -149,7 +221,7 @@ Response http_sync(http_exch_fn http_transact, std::getline(response_stream, status_message); if(!response_stream || http_version.substr(0,5) != "HTTP/") - throw Exception("Not an HTTP response"); + throw HTTP_Error("Not an HTTP response"); std::map<std::string, std::string> headers; std::string header_line; @@ -157,7 +229,7 @@ Response http_sync(http_exch_fn http_transact, { auto sep = header_line.find(": "); if(sep == std::string::npos || sep > header_line.size() - 2) - throw Exception("Invalid HTTP header " + header_line); + throw HTTP_Error("Invalid HTTP header " + header_line); const std::string key = header_line.substr(0, sep); if(sep + 2 < header_line.size() - 1) @@ -170,7 +242,7 @@ Response http_sync(http_exch_fn http_transact, if(status_code == 301 && headers.count("Location")) { if(allowable_redirects == 0) - throw Exception("HTTP redirection count exceeded"); + throw HTTP_Error("HTTP redirection count exceeded"); return GET_sync(headers["Location"], allowable_redirects - 1); } @@ -187,8 +259,8 @@ Response http_sync(http_exch_fn http_transact, if(!header_size.empty()) { if(resp_body.size() != to_u32bit(header_size)) - throw Exception("Content-Length disagreement, header says " + - header_size + " got " + std::to_string(resp_body.size())); + throw HTTP_Error("Content-Length disagreement, header says " + + header_size + " got " + std::to_string(resp_body.size())); } return Response(status_code, status_message, resp_body, headers); @@ -201,11 +273,7 @@ Response http_sync(const std::string& verb, size_t allowable_redirects) { return http_sync( -#if defined(BOTAN_HAS_BOOST_ASIO) - http_transact_asio, -#else - http_transact_fail, -#endif + http_transact, verb, url, content_type, diff --git a/src/lib/utils/http_util/http_util.h b/src/lib/utils/http_util/http_util.h index 918dfc269..1b4da3d2c 100644 --- a/src/lib/utils/http_util/http_util.h +++ b/src/lib/utils/http_util/http_util.h @@ -53,19 +53,20 @@ struct Response std::map<std::string, std::string> m_headers; }; +/** +* HTTP_Error Exception +*/ +struct BOTAN_DLL HTTP_Error : public Exception + { + explicit HTTP_Error(const std::string& msg) : + Exception("HTTP error " + msg) + {} + }; + BOTAN_DLL std::ostream& operator<<(std::ostream& o, const Response& resp); typedef std::function<std::string (const std::string&, const std::string&)> http_exch_fn; -#if defined(BOTAN_HAS_BOOST_ASIO) -std::string BOTAN_DLL http_transact_asio(const std::string& hostname, - const std::string& message); -#endif - -std::string BOTAN_DLL http_transact_fail(const std::string& hostname, - const std::string& message); - - BOTAN_DLL Response http_sync(http_exch_fn fn, const std::string& verb, const std::string& url, |