Botan  2.1.0
Crypto and TLS for C++11
http_util.cpp
Go to the documentation of this file.
1 /*
2 * Sketchy HTTP client
3 * (C) 2013,2016 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7 
8 #include <botan/http_util.h>
9 #include <botan/parsing.h>
10 #include <botan/hex.h>
11 #include <botan/internal/stl_util.h>
12 #include <sstream>
13 
14 #if defined(BOTAN_HAS_BOOST_ASIO)
15 
16  /*
17  * We don't need serial port support anyway, and asking for it
18  * causes macro conflicts with Darwin's termios.h when this
19  * file is included in the amalgamation. GH #350
20  */
21  #define BOOST_ASIO_DISABLE_SERIAL_PORT
22  #include <boost/asio.hpp>
23 
24 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
25  #include <sys/types.h>
26  #include <sys/socket.h>
27  #include <netdb.h>
28  #include <unistd.h>
29  #include <netinet/in.h>
30 #else
31  //#warning "No network support enabled in http_util"
32 #endif
33 
34 namespace Botan {
35 
36 namespace HTTP {
37 
38 namespace {
39 
40 /*
41 * Connect to a host, write some bytes, then read until the server
42 * closes the socket.
43 */
44 std::string http_transact(const std::string& hostname,
45  const std::string& message)
46  {
47 #if defined(BOTAN_HAS_BOOST_ASIO)
48  using namespace boost::asio::ip;
49 
50  boost::asio::ip::tcp::iostream tcp;
51 
52  tcp.connect(hostname, "http");
53 
54  if(!tcp)
55  throw HTTP_Error("HTTP connection to " + hostname + " failed");
56 
57  tcp << message;
58  tcp.flush();
59 
60  std::ostringstream oss;
61  oss << tcp.rdbuf();
62 
63  return oss.str();
64 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
65 
66  hostent* host_addr = ::gethostbyname(hostname.c_str());
67  uint16_t port = 80;
68 
69  if(!host_addr)
70  throw HTTP_Error("Name resolution failed for " + hostname);
71 
72  if(host_addr->h_addrtype != AF_INET) // FIXME
73  throw HTTP_Error("Hostname " + hostname + " resolved to non-IPv4 address");
74 
75  struct socket_raii {
76  socket_raii(int fd) : m_fd(fd) {}
77  ~socket_raii() { ::close(m_fd); }
78  int m_fd;
79  };
80 
81  int fd = ::socket(PF_INET, SOCK_STREAM, 0);
82  if(fd == -1)
83  throw HTTP_Error("Unable to create TCP socket");
84  socket_raii raii(fd);
85 
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);
90 
91  ::memcpy(&socket_info.sin_addr,
92  host_addr->h_addr,
93  host_addr->h_length);
94 
95  socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME
96 
97  if(::connect(fd, reinterpret_cast<sockaddr*>(&socket_info), sizeof(struct sockaddr)) != 0)
98  throw HTTP_Error("HTTP connection to " + hostname + " failed");
99 
100  size_t sent_so_far = 0;
101  while(sent_so_far != message.size())
102  {
103  size_t left = message.size() - sent_so_far;
104  ssize_t sent = ::write(fd, &message[sent_so_far], left);
105 
106  if(sent < 0)
107  throw HTTP_Error("write to HTTP server failed, error '" + std::string(::strerror(errno)) + "'");
108  else
109  sent_so_far += static_cast<size_t>(sent);
110  }
111 
112  std::ostringstream oss;
113  std::vector<char> buf(1024); // arbitrary size
114  while(true)
115  {
116  ssize_t got = ::read(fd, buf.data(), buf.size());
117 
118  if(got < 0)
119  throw HTTP_Error("read from HTTP server failed, error '" + std::string(::strerror(errno)) + "'");
120  else if(got > 0)
121  oss.write(buf.data(), static_cast<std::streamsize>(got));
122  else
123  break; // EOF
124  }
125  return oss.str();
126 
127 #else
128  throw HTTP_Error("Cannot connect to " + hostname + ": network code disabled in build");
129 #endif
130  }
131 
132 }
133 
134 std::string url_encode(const std::string& in)
135  {
136  std::ostringstream out;
137 
138  for(auto c : in)
139  {
140  if(c >= 'A' && c <= 'Z')
141  out << c;
142  else if(c >= 'a' && c <= 'z')
143  out << c;
144  else if(c >= '0' && c <= '9')
145  out << c;
146  else if(c == '-' || c == '_' || c == '.' || c == '~')
147  out << c;
148  else
149  out << '%' << hex_encode(reinterpret_cast<uint8_t*>(&c), 1);
150  }
151 
152  return out.str();
153  }
154 
155 std::ostream& operator<<(std::ostream& o, const Response& resp)
156  {
157  o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n";
158  for(auto h : resp.headers())
159  o << "Header '" << h.first << "' = '" << h.second << "'\n";
160  o << "Body " << std::to_string(resp.body().size()) << " bytes:\n";
161  o.write(reinterpret_cast<const char*>(&resp.body()[0]), resp.body().size());
162  return o;
163  }
164 
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)
171  {
172  if(url.empty())
173  throw HTTP_Error("URL empty");
174 
175  const auto protocol_host_sep = url.find("://");
176  if(protocol_host_sep == std::string::npos)
177  throw HTTP_Error("Invalid URL '" + url + "'");
178 
179  const auto host_loc_sep = url.find('/', protocol_host_sep + 3);
180 
181  std::string hostname, loc;
182 
183  if(host_loc_sep == std::string::npos)
184  {
185  hostname = url.substr(protocol_host_sep + 3, std::string::npos);
186  loc = "/";
187  }
188  else
189  {
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);
192  }
193 
194  std::ostringstream outbuf;
195 
196  outbuf << verb << " " << loc << " HTTP/1.0\r\n";
197  outbuf << "Host: " << hostname << "\r\n";
198 
199  if(verb == "GET")
200  {
201  outbuf << "Accept: */*\r\n";
202  outbuf << "Cache-Control: no-cache\r\n";
203  }
204  else if(verb == "POST")
205  outbuf << "Content-Length: " << body.size() << "\r\n";
206 
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());
211 
212  std::istringstream io(http_transact(hostname, outbuf.str()));
213 
214  std::string line1;
215  std::getline(io, line1);
216  if(!io || line1.empty())
217  throw HTTP_Error("No response");
218 
219  std::stringstream response_stream(line1);
220  std::string http_version;
221  unsigned int status_code;
222  std::string status_message;
223 
224  response_stream >> http_version >> status_code;
225 
226  std::getline(response_stream, status_message);
227 
228  if(!response_stream || http_version.substr(0,5) != "HTTP/")
229  throw HTTP_Error("Not an HTTP response");
230 
231  std::map<std::string, std::string> headers;
232  std::string header_line;
233  while (std::getline(io, header_line) && header_line != "\r")
234  {
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);
239 
240  if(sep + 2 < header_line.size() - 1)
241  {
242  const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2));
243  headers[key] = val;
244  }
245  }
246 
247  if(status_code == 301 && headers.count("Location"))
248  {
249  if(allowable_redirects == 0)
250  throw HTTP_Error("HTTP redirection count exceeded");
251  return GET_sync(headers["Location"], allowable_redirects - 1);
252  }
253 
254  std::vector<uint8_t> resp_body;
255  std::vector<uint8_t> buf(4096);
256  while(io.good())
257  {
258  io.read(reinterpret_cast<char*>(buf.data()), buf.size());
259  resp_body.insert(resp_body.end(), buf.data(), &buf[io.gcount()]);
260  }
261 
262  const std::string header_size = search_map(headers, std::string("Content-Length"));
263 
264  if(!header_size.empty())
265  {
266  if(resp_body.size() != to_u32bit(header_size))
267  throw HTTP_Error("Content-Length disagreement, header says " +
268  header_size + " got " + std::to_string(resp_body.size()));
269  }
270 
271  return Response(status_code, status_message, resp_body, headers);
272  }
273 
274 Response http_sync(const std::string& verb,
275  const std::string& url,
276  const std::string& content_type,
277  const std::vector<uint8_t>& body,
278  size_t allowable_redirects)
279  {
280  return http_sync(
281  http_transact,
282  verb,
283  url,
284  content_type,
285  body,
286  allowable_redirects);
287  }
288 
289 Response GET_sync(const std::string& url, size_t allowable_redirects)
290  {
291  return http_sync("GET", url, "", std::vector<uint8_t>(), allowable_redirects);
292  }
293 
294 Response POST_sync(const std::string& url,
295  const std::string& content_type,
296  const std::vector<uint8_t>& body,
297  size_t allowable_redirects)
298  {
299  return http_sync("POST", url, content_type, body, allowable_redirects);
300  }
301 
302 }
303 
304 }
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)
Definition: http_util.cpp:165
uint32_t to_u32bit(const std::string &str)
Definition: parsing.cpp:18
V search_map(const std::map< K, V > &mapping, const K &key, const V &null_result=V())
Definition: stl_util.h:52
const std::vector< uint8_t > & body() const
Definition: http_util.h:37
std::string to_string(const BER_Object &obj)
Definition: asn1_obj.cpp:47
const std::map< std::string, std::string > & headers() const
Definition: http_util.h:39
unsigned int status_code() const
Definition: http_util.h:35
std::string url_encode(const std::string &in)
Definition: http_util.cpp:134
std::function< std::string(const std::string &, const std::string &)> http_exch_fn
Definition: http_util.h:68
std::string status_message() const
Definition: http_util.h:41
Response POST_sync(const std::string &url, const std::string &content_type, const std::vector< uint8_t > &body, size_t allowable_redirects)
Definition: http_util.cpp:294
Definition: alg_id.cpp:13
std::ostream & operator<<(std::ostream &o, const Response &resp)
Definition: http_util.cpp:155
Response GET_sync(const std::string &url, size_t allowable_redirects)
Definition: http_util.cpp:289
int m_fd
Definition: system_rng.cpp:55