8 #include <botan/http_util.h>
9 #include <botan/parsing.h>
10 #include <botan/hex.h>
11 #include <botan/internal/stl_util.h>
14 #if defined(BOTAN_HAS_BOOST_ASIO)
21 #define BOOST_ASIO_DISABLE_SERIAL_PORT
22 #include <boost/asio.hpp>
24 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
25 #include <sys/types.h>
26 #include <sys/socket.h>
29 #include <netinet/in.h>
44 std::string http_transact(
const std::string& hostname,
45 const std::string& message)
47 #if defined(BOTAN_HAS_BOOST_ASIO)
48 using namespace boost::asio::ip;
50 boost::asio::ip::tcp::iostream tcp;
52 tcp.connect(hostname,
"http");
55 throw HTTP_Error(
"HTTP connection to " + hostname +
" failed");
60 std::ostringstream oss;
64 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
66 hostent* host_addr = ::gethostbyname(hostname.c_str());
70 throw HTTP_Error(
"Name resolution failed for " + hostname);
72 if(host_addr->h_addrtype != AF_INET)
73 throw HTTP_Error(
"Hostname " + hostname +
" resolved to non-IPv4 address");
76 socket_raii(
int fd) :
m_fd(fd) {}
77 ~socket_raii() { ::close(
m_fd); }
81 int fd = ::socket(PF_INET, SOCK_STREAM, 0);
83 throw HTTP_Error(
"Unable to create TCP socket");
86 sockaddr_in socket_info;
87 ::memset(&socket_info, 0,
sizeof(socket_info));
88 socket_info.sin_family = AF_INET;
89 socket_info.sin_port = htons(port);
91 ::memcpy(&socket_info.sin_addr,
95 socket_info.sin_addr = *
reinterpret_cast<struct in_addr*
>(host_addr->h_addr);
97 if(::connect(fd, reinterpret_cast<sockaddr*>(&socket_info),
sizeof(
struct sockaddr)) != 0)
98 throw HTTP_Error(
"HTTP connection to " + hostname +
" failed");
100 size_t sent_so_far = 0;
101 while(sent_so_far != message.size())
103 size_t left = message.size() - sent_so_far;
104 ssize_t sent = ::write(fd, &message[sent_so_far], left);
107 throw HTTP_Error(
"write to HTTP server failed, error '" + std::string(::strerror(errno)) +
"'");
109 sent_so_far +=
static_cast<size_t>(sent);
112 std::ostringstream oss;
113 std::vector<char> buf(1024);
116 ssize_t got = ::read(fd, buf.data(), buf.size());
119 throw HTTP_Error(
"read from HTTP server failed, error '" + std::string(::strerror(errno)) +
"'");
121 oss.write(buf.data(),
static_cast<std::streamsize
>(got));
128 throw HTTP_Error(
"Cannot connect to " + hostname +
": network code disabled in build");
136 std::ostringstream out;
140 if(c >=
'A' && c <=
'Z')
142 else if(c >=
'a' && c <=
'z')
144 else if(c >=
'0' && c <=
'9')
146 else if(c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~')
149 out << '%' << hex_encode(reinterpret_cast<uint8_t*>(&c), 1);
159 o <<
"Header '" << h.first <<
"' = '" << h.second <<
"'\n";
161 o.write(reinterpret_cast<const char*>(&resp.
body()[0]), resp.
body().size());
166 const std::string& verb,
167 const std::string& url,
168 const std::string& content_type,
169 const std::vector<uint8_t>& body,
170 size_t allowable_redirects)
175 const auto protocol_host_sep = url.find(
"://");
176 if(protocol_host_sep == std::string::npos)
177 throw HTTP_Error(
"Invalid URL '" + url +
"'");
179 const auto host_loc_sep = url.find(
'/', protocol_host_sep + 3);
181 std::string hostname, loc;
183 if(host_loc_sep == std::string::npos)
185 hostname = url.substr(protocol_host_sep + 3, std::string::npos);
190 hostname = url.substr(protocol_host_sep + 3, host_loc_sep-protocol_host_sep-3);
191 loc = url.substr(host_loc_sep, std::string::npos);
194 std::ostringstream outbuf;
196 outbuf << verb <<
" " << loc <<
" HTTP/1.0\r\n";
197 outbuf <<
"Host: " << hostname <<
"\r\n";
201 outbuf <<
"Accept: */*\r\n";
202 outbuf <<
"Cache-Control: no-cache\r\n";
204 else if(verb ==
"POST")
205 outbuf <<
"Content-Length: " << body.size() <<
"\r\n";
207 if(!content_type.empty())
208 outbuf <<
"Content-Type: " << content_type <<
"\r\n";
209 outbuf <<
"Connection: close\r\n\r\n";
210 outbuf.write(reinterpret_cast<const char*>(body.data()), body.size());
212 std::istringstream io(http_transact(hostname, outbuf.str()));
215 std::getline(io, line1);
216 if(!io || line1.empty())
219 std::stringstream response_stream(line1);
220 std::string http_version;
221 unsigned int status_code;
222 std::string status_message;
224 response_stream >> http_version >> status_code;
226 std::getline(response_stream, status_message);
228 if(!response_stream || http_version.substr(0,5) !=
"HTTP/")
231 std::map<std::string, std::string> headers;
232 std::string header_line;
233 while (std::getline(io, header_line) && header_line !=
"\r")
235 auto sep = header_line.find(
": ");
236 if(sep == std::string::npos || sep > header_line.size() - 2)
237 throw HTTP_Error(
"Invalid HTTP header " + header_line);
238 const std::string key = header_line.substr(0, sep);
240 if(sep + 2 < header_line.size() - 1)
242 const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2));
247 if(status_code == 301 && headers.count(
"Location"))
249 if(allowable_redirects == 0)
250 throw HTTP_Error(
"HTTP redirection count exceeded");
251 return GET_sync(headers[
"Location"], allowable_redirects - 1);
254 std::vector<uint8_t> resp_body;
255 std::vector<uint8_t> buf(4096);
258 io.read(reinterpret_cast<char*>(buf.data()), buf.size());
259 resp_body.insert(resp_body.end(), buf.data(), &buf[io.gcount()]);
262 const std::string header_size =
search_map(headers, std::string(
"Content-Length"));
264 if(!header_size.empty())
266 if(resp_body.size() !=
to_u32bit(header_size))
267 throw HTTP_Error(
"Content-Length disagreement, header says " +
271 return Response(status_code, status_message, resp_body, headers);
275 const std::string& url,
276 const std::string& content_type,
277 const std::vector<uint8_t>& body,
278 size_t allowable_redirects)
286 allowable_redirects);
291 return http_sync(
"GET", url,
"", std::vector<uint8_t>(), allowable_redirects);
295 const std::string& content_type,
296 const std::vector<uint8_t>& body,
297 size_t allowable_redirects)
299 return http_sync(
"POST", url, content_type, body, allowable_redirects);
Response http_sync(http_exch_fn http_transact, const std::string &verb, const std::string &url, const std::string &content_type, const std::vector< uint8_t > &body, size_t allowable_redirects)
uint32_t to_u32bit(const std::string &str)
V search_map(const std::map< K, V > &mapping, const K &key, const V &null_result=V())
const std::vector< uint8_t > & body() const
std::string to_string(const BER_Object &obj)
const std::map< std::string, std::string > & headers() const
unsigned int status_code() const
std::string url_encode(const std::string &in)
std::function< std::string(const std::string &, const std::string &)> http_exch_fn
std::string status_message() const
Response POST_sync(const std::string &url, const std::string &content_type, const std::vector< uint8_t > &body, size_t allowable_redirects)
std::ostream & operator<<(std::ostream &o, const Response &resp)
Response GET_sync(const std::string &url, size_t allowable_redirects)