diff options
author | Jack Lloyd <[email protected]> | 2017-09-03 07:57:41 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-09-03 07:57:41 -0400 |
commit | 20667782f65a9f368c6536c98e22dd49d18175ef (patch) | |
tree | 60e9d38554d07f0013d0c59f7aef5a02d5d9d74f | |
parent | 87f19427dbc3662636a84e56b7c7a8a49f1246df (diff) |
Refactor network code used in http_util
Move the actual socket stuff to os_utils.cpp
-rw-r--r-- | src/lib/utils/http_util/http_util.cpp | 156 | ||||
-rw-r--r-- | src/lib/utils/os_utils.cpp | 254 | ||||
-rw-r--r-- | src/lib/utils/os_utils.h | 33 |
3 files changed, 303 insertions, 140 deletions
diff --git a/src/lib/utils/http_util/http_util.cpp b/src/lib/utils/http_util/http_util.cpp index 4b0db03a5..73efb7adc 100644 --- a/src/lib/utils/http_util/http_util.cpp +++ b/src/lib/utils/http_util/http_util.cpp @@ -9,55 +9,10 @@ #include <botan/http_util.h> #include <botan/parsing.h> #include <botan/hex.h> +#include <botan/internal/os_utils.h> #include <botan/internal/stl_util.h> #include <sstream> -#if defined(BOTAN_HAS_BOOST_ASIO) - - /* - * We don't need serial port support anyway, and asking for it - * causes macro conflicts with Darwin's termios.h when this - * file is included in the amalgamation. GH #350 - */ - #define BOOST_ASIO_DISABLE_SERIAL_PORT - #include <boost/asio.hpp> - -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) -#if defined(BOTAN_TARGET_OS_IS_WINDOWS) - #include <winsock2.h> - #include <WS2tcpip.h> - -namespace { - -int close(int fd) - { - return ::closesocket(fd); - } - -int read(int s, void* buf, size_t len) - { - return ::recv(s, reinterpret_cast<char*>(buf), static_cast<int>(len), 0); - } - -int write(int s, const char* buf, size_t len) - { - return ::send(s, reinterpret_cast<const char*>(buf), static_cast<int>(len), 0); - } - -} - -typedef size_t ssize_t; -#else - #include <sys/types.h> - #include <sys/socket.h> - #include <netdb.h> - #include <unistd.h> - #include <netinet/in.h> -#endif -#else - //#warning "No network support enabled in http_util" -#endif - namespace Botan { namespace HTTP { @@ -71,111 +26,36 @@ namespace { 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; - - tcp.connect(hostname, "http"); - - if(!tcp) - throw HTTP_Error("HTTP connection to " + hostname + " failed"); - - tcp << message; - tcp.flush(); - - std::ostringstream oss; - oss << tcp.rdbuf(); + std::unique_ptr<OS::Socket> socket; - return oss.str(); -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) - -#if defined(BOTAN_TARGET_OS_IS_WINDOWS) - WSAData wsa_data; - WORD wsa_version = MAKEWORD(2, 2); - - if (::WSAStartup(wsa_version, &wsa_data) != 0) + try { - throw HTTP_Error("WSAStartup() failed: " + std::to_string(WSAGetLastError())); + socket = OS::open_socket(hostname, "http"); + if(!socket) + throw Exception("No socket support enabled in build"); } - - if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) + catch(std::exception& e) { - ::WSACleanup(); - throw HTTP_Error("Could not find a usable version of Winsock.dll"); + throw HTTP_Error("HTTP connection to " + hostname + " failed: " + e.what()); } -#endif - - 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); -#if defined(BOTAN_TARGET_OS_IS_WINDOWS) - ::WSACleanup(); -#endif - } - 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, reinterpret_cast<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("write to HTTP server failed, error '" + std::string(::strerror(errno)) + "'"); - else - sent_so_far += static_cast<size_t>(sent); - } + // Blocks until entire message has been written + socket->write(reinterpret_cast<const uint8_t*>(message.data()), + message.size()); std::ostringstream oss; - std::vector<char> buf(1024); // arbitrary size + std::vector<uint8_t> buf(BOTAN_DEFAULT_BUFFER_SIZE); while(true) { - ssize_t got = ::read(fd, buf.data(), buf.size()); + const size_t got = socket->read(buf.data(), buf.size()); + if(got == 0) // EOF + break; - if(got < 0) - throw HTTP_Error("read from HTTP server failed, error '" + std::string(::strerror(errno)) + "'"); - else if(got > 0) - oss.write(buf.data(), static_cast<std::streamsize>(got)); - else - break; // EOF + oss.write(reinterpret_cast<const char*>(buf.data()), + static_cast<std::streamsize>(got)); } - return oss.str(); -#else - throw HTTP_Error("Cannot connect to " + hostname + ": network code disabled in build"); -#endif + return oss.str(); } } diff --git a/src/lib/utils/os_utils.cpp b/src/lib/utils/os_utils.cpp index d08e7e040..b71568328 100644 --- a/src/lib/utils/os_utils.cpp +++ b/src/lib/utils/os_utils.cpp @@ -12,6 +12,17 @@ #include <botan/mem_ops.h> #include <chrono> +#if defined(BOTAN_HAS_BOOST_ASIO) + + /* + * We don't need serial port support anyway, and asking for it + * causes macro conflicts with Darwin's termios.h when this + * file is included in the amalgamation. GH #350 + */ + #define BOOST_ASIO_DISABLE_SERIAL_PORT + #include <boost/asio.hpp> +#endif + #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX) #include <sys/types.h> #include <sys/mman.h> @@ -19,15 +30,254 @@ #include <unistd.h> #include <signal.h> #include <setjmp.h> -#endif + #include <sys/socket.h> + #include <netinet/in.h> + #include <netdb.h> -#if defined(BOTAN_TARGET_OS_IS_WINDOWS) || defined(BOTAN_TARGET_OS_IS_MINGW) +#elif defined(BOTAN_TARGET_OS_IS_WINDOWS) || defined(BOTAN_TARGET_OS_IS_MINGW) #define NOMINMAX 1 + #include <winsock2.h> + #include <WS2tcpip.h> #include <windows.h> #endif namespace Botan { +std::unique_ptr<OS::Socket> +OS::open_socket(const std::string& hostname, + const std::string& service) + { +#if defined(BOTAN_HAS_BOOST_ASIO) + class Asio_Socket : public OS::Socket + { + public: + Asio_Socket(const std::string& hostname, const std::string& service) : + m_tcp(m_io) + { + boost::asio::ip::tcp::resolver resolver(m_io); + boost::asio::ip::tcp::resolver::query query(hostname, service); + boost::asio::connect(m_tcp, resolver.resolve(query)); + } + + void write(const uint8_t buf[], size_t len) + { + boost::asio::write(m_tcp, boost::asio::buffer(buf, len)); + } + + size_t read(uint8_t buf[], size_t len) + { + boost::system::error_code error; + size_t got = m_tcp.read_some(boost::asio::buffer(buf, len), error); + + if(error) + { + if(error == boost::asio::error::eof) + return 0; + throw boost::system::system_error(error); // Some other error. + } + + return got; + } + + private: + boost::asio::io_service m_io; + boost::asio::ip::tcp::socket m_tcp; + }; + + return std::unique_ptr<OS::Socket>(new Asio_Socket(hostname, service)); + +#elif defined(BOTAN_TARGET_OS_IS_WINDOWS) + + class Winsock_Socket : public OS::Socket + { + public: + Winsock_Socket(const std::string& hostname, const std::string& service) + { + WSAData wsa_data; + WORD wsa_version = MAKEWORD(2, 2); + + if (::WSAStartup(wsa_version, &wsa_data) != 0) + { + throw Exception("WSAStartup() failed: " + std::to_string(WSAGetLastError())); + } + + if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) + { + ::WSACleanup(); + throw Exception("Could not find a usable version of Winsock.dll"); + } + + addrinfo hints; + ::memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + addrinfo* res; + + if(::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &res) != 0) + { + throw Exception("Name resolution failed for " + hostname); + } + + for(addrinfo* rp = res; (m_socket < 0) && (rp != nullptr); rp = rp->ai_next) + { + m_socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + // unsupported socket type? + if(m_socket == INVALID_SOCKET) + continue; + + if(::connect(m_socket, rp->ai_addr, rp->ai_addrlen) != 0) + { + ::closesocket(m_socket); + m_socket = INVALID_SOCKET; + continue; + } + } + + ::freeaddrinfo(res); + + if(m_socket == INVALID_SOCKET) + { + throw Exception("Connecting to " + hostname + + " for service " + service + " failed"); + } + } + + ~Winsock_Socket() + { + ::closesocket(m_socket); + m_socket = INVALID_SOCKET; + ::WSACleanup(); + } + + void write(const uint8_t buf[], size_t len) + { + size_t sent_so_far = 0; + while(sent_so_far != len) + { + const size_t left = len - sent_so_far; + int sent = ::send(m_socket, + reinterpret_cast<const char*>(buf + sent_so_far), + static_cast<int>(left), + 0); + + if(sent == SOCKET_ERROR) + throw Exception("Socket write failed with error " + + std::to_string(::WSAGetLastError())); + else + sent_so_far += static_cast<size_t>(sent); + } + } + + size_t read(uint8_t buf[], size_t len) + { + int got = ::recv(m_socket,, + reinterpret_cast<char*>(buf), + static_cast<int>(len), 0); + + if(got == SOCKET_ERROR) + throw Exception("Socket read failed with error " + + std::to_string(::WSAGetLastError())); + return static_cast<size_t>(got); + } + + private: + SOCKET m_socket = INVALID_SOCKET; + }; + + return std::unique_ptr<OS::Socket>(new Winsock_Socket(hostname, service)); + +#elif defined(BOTAN_TARGET_OS_TYPE_IS_UNIX) + + class BSD_Socket : public OS::Socket + { + public: + BSD_Socket(const std::string& hostname, const std::string& service) + { + addrinfo hints; + ::memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + addrinfo* res; + + if(::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &res) != 0) + { + throw Exception("Name resolution failed for " + hostname); + } + + m_fd = -1; + + for(addrinfo* rp = res; (m_fd < 0) && (rp != nullptr); rp = rp->ai_next) + { + m_fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if(m_fd < 0) + { + // unsupported socket type? + continue; + } + + if(::connect(m_fd, rp->ai_addr, rp->ai_addrlen) != 0) + { + ::close(m_fd); + m_fd = -1; + continue; + } + } + + ::freeaddrinfo(res); + + if(m_fd < 0) + { + throw Exception("Connecting to " + hostname + + " for service " + service + " failed"); + } + } + + ~BSD_Socket() + { + ::close(m_fd); + m_fd = -1; + } + + void write(const uint8_t buf[], size_t len) + { + size_t sent_so_far = 0; + while(sent_so_far != len) + { + const size_t left = len - sent_so_far; + ssize_t sent = ::write(m_fd, &buf[sent_so_far], left); + if(sent < 0) + throw Exception("Socket write failed with error '" + + std::string(::strerror(errno)) + "'"); + else + sent_so_far += static_cast<size_t>(sent); + } + } + + size_t read(uint8_t buf[], size_t len) + { + ssize_t got = ::read(m_fd, buf, len); + + if(got < 0) + throw Exception("Socket read failed with error '" + + std::string(::strerror(errno)) + "'"); + return static_cast<size_t>(got); + } + + private: + int m_fd; + }; + + return std::unique_ptr<OS::Socket>(new BSD_Socket(hostname, service)); + +#else + // No sockets for you + return std::unique_ptr<Socket>(); +#endif + } + + uint32_t OS::get_process_id() { #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX) diff --git a/src/lib/utils/os_utils.h b/src/lib/utils/os_utils.h index cae1192f1..2146e11b3 100644 --- a/src/lib/utils/os_utils.h +++ b/src/lib/utils/os_utils.h @@ -23,6 +23,39 @@ namespace OS { * this hasn't been tested. */ + +/** +* A wrapper around a simple blocking TCP socket +*/ +class BOTAN_DLL Socket + { + public: + /** + * The socket will be closed upon destruction + */ + virtual ~Socket() {}; + + /** + * Write to the socket. Blocks until all bytes sent. + * Throws on error. + */ + virtual void write(const uint8_t buf[], size_t len) = 0; + + /** + * Reads up to len bytes, returns bytes written to buf. + * Returns 0 on EOF. Throws on error. + */ + virtual size_t read(uint8_t buf[], size_t len) = 0; + }; + +/** +* Open up a socket. Will throw on error. Returns null if sockets are +* not available on this platform. +*/ +std::unique_ptr<Socket> +BOTAN_DLL open_socket(const std::string& hostname, + const std::string& service); + /** * @return process ID assigned by the operating system. * On Unix and Windows systems, this always returns a result |