~ubuntu-branches/debian/sid/botan/sid

« back to all changes in this revision

Viewing changes to src/lib/utils/http_util/http_util.cpp

  • Committer: Package Import Robot
  • Author(s): Laszlo Boszormenyi (GCS)
  • Date: 2018-03-01 22:23:25 UTC
  • mfrom: (1.2.2)
  • Revision ID: package-import@ubuntu.com-20180301222325-7p7vc45gu3hta34d
Tags: 2.4.0-2
* Don't remove .doctrees from the manual if it doesn't exist.
* Don't specify parallel to debhelper.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
* Sketchy HTTP client
 
3
* (C) 2013,2016 Jack Lloyd
 
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
 
5
*
 
6
* Botan is released under the Simplified BSD License (see license.txt)
 
7
*/
 
8
 
 
9
#include <botan/http_util.h>
 
10
#include <botan/parsing.h>
 
11
#include <botan/hex.h>
 
12
#include <botan/internal/os_utils.h>
 
13
#include <botan/internal/socket.h>
 
14
#include <botan/internal/stl_util.h>
 
15
#include <sstream>
 
16
 
 
17
namespace Botan {
 
18
 
 
19
namespace HTTP {
 
20
 
 
21
namespace {
 
22
 
 
23
/*
 
24
* Connect to a host, write some bytes, then read until the server
 
25
* closes the socket.
 
26
*/
 
27
std::string http_transact(const std::string& hostname,
 
28
                          const std::string& message,
 
29
                          std::chrono::milliseconds timeout)
 
30
   {
 
31
   std::unique_ptr<OS::Socket> socket;
 
32
 
 
33
   const std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
 
34
 
 
35
   try
 
36
      {
 
37
      socket = OS::open_socket(hostname, "http", timeout);
 
38
      if(!socket)
 
39
         throw Exception("No socket support enabled in build");
 
40
      }
 
41
   catch(std::exception& e)
 
42
      {
 
43
      throw HTTP_Error("HTTP connection to " + hostname + " failed: " + e.what());
 
44
      }
 
45
 
 
46
   // Blocks until entire message has been written
 
47
   socket->write(cast_char_ptr_to_uint8(message.data()),
 
48
                 message.size());
 
49
 
 
50
   if(std::chrono::system_clock::now() - start_time > timeout)
 
51
      throw HTTP_Error("Timeout during writing message body");
 
52
 
 
53
   std::ostringstream oss;
 
54
   std::vector<uint8_t> buf(BOTAN_DEFAULT_BUFFER_SIZE);
 
55
   while(true)
 
56
      {
 
57
      const size_t got = socket->read(buf.data(), buf.size());
 
58
      if(got == 0) // EOF
 
59
         break;
 
60
 
 
61
      if(std::chrono::system_clock::now() - start_time > timeout)
 
62
         throw HTTP_Error("Timeout while reading message body");
 
63
 
 
64
      oss.write(cast_uint8_ptr_to_char(buf.data()),
 
65
                static_cast<std::streamsize>(got));
 
66
      }
 
67
 
 
68
   return oss.str();
 
69
   }
 
70
 
 
71
}
 
72
 
 
73
std::string url_encode(const std::string& in)
 
74
   {
 
75
   std::ostringstream out;
 
76
 
 
77
   for(auto c : in)
 
78
      {
 
79
      if(c >= 'A' && c <= 'Z')
 
80
         out << c;
 
81
      else if(c >= 'a' && c <= 'z')
 
82
         out << c;
 
83
      else if(c >= '0' && c <= '9')
 
84
         out << c;
 
85
      else if(c == '-' || c == '_' || c == '.' || c == '~')
 
86
         out << c;
 
87
      else
 
88
         out << '%' << hex_encode(cast_char_ptr_to_uint8(&c), 1);
 
89
      }
 
90
 
 
91
   return out.str();
 
92
   }
 
93
 
 
94
std::ostream& operator<<(std::ostream& o, const Response& resp)
 
95
   {
 
96
   o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n";
 
97
   for(auto h : resp.headers())
 
98
      o << "Header '" << h.first << "' = '" << h.second << "'\n";
 
99
   o << "Body " << std::to_string(resp.body().size()) << " bytes:\n";
 
100
   o.write(cast_uint8_ptr_to_char(resp.body().data()), resp.body().size());
 
101
   return o;
 
102
   }
 
103
 
 
104
Response http_sync(http_exch_fn http_transact,
 
105
                   const std::string& verb,
 
106
                   const std::string& url,
 
107
                   const std::string& content_type,
 
108
                   const std::vector<uint8_t>& body,
 
109
                   size_t allowable_redirects)
 
110
   {
 
111
   if(url.empty())
 
112
      throw HTTP_Error("URL empty");
 
113
 
 
114
   const auto protocol_host_sep = url.find("://");
 
115
   if(protocol_host_sep == std::string::npos)
 
116
      throw HTTP_Error("Invalid URL '" + url + "'");
 
117
 
 
118
   const auto host_loc_sep = url.find('/', protocol_host_sep + 3);
 
119
 
 
120
   std::string hostname, loc;
 
121
 
 
122
   if(host_loc_sep == std::string::npos)
 
123
      {
 
124
      hostname = url.substr(protocol_host_sep + 3, std::string::npos);
 
125
      loc = "/";
 
126
      }
 
127
   else
 
128
      {
 
129
      hostname = url.substr(protocol_host_sep + 3, host_loc_sep-protocol_host_sep-3);
 
130
      loc = url.substr(host_loc_sep, std::string::npos);
 
131
      }
 
132
 
 
133
   std::ostringstream outbuf;
 
134
 
 
135
   outbuf << verb << " " << loc << " HTTP/1.0\r\n";
 
136
   outbuf << "Host: " << hostname << "\r\n";
 
137
 
 
138
   if(verb == "GET")
 
139
      {
 
140
      outbuf << "Accept: */*\r\n";
 
141
      outbuf << "Cache-Control: no-cache\r\n";
 
142
      }
 
143
   else if(verb == "POST")
 
144
      outbuf << "Content-Length: " << body.size() << "\r\n";
 
145
 
 
146
   if(!content_type.empty())
 
147
      outbuf << "Content-Type: " << content_type << "\r\n";
 
148
   outbuf << "Connection: close\r\n\r\n";
 
149
   outbuf.write(cast_uint8_ptr_to_char(body.data()), body.size());
 
150
 
 
151
   std::istringstream io(http_transact(hostname, outbuf.str()));
 
152
 
 
153
   std::string line1;
 
154
   std::getline(io, line1);
 
155
   if(!io || line1.empty())
 
156
      throw HTTP_Error("No response");
 
157
 
 
158
   std::stringstream response_stream(line1);
 
159
   std::string http_version;
 
160
   unsigned int status_code;
 
161
   std::string status_message;
 
162
 
 
163
   response_stream >> http_version >> status_code;
 
164
 
 
165
   std::getline(response_stream, status_message);
 
166
 
 
167
   if(!response_stream || http_version.substr(0,5) != "HTTP/")
 
168
      throw HTTP_Error("Not an HTTP response");
 
169
 
 
170
   std::map<std::string, std::string> headers;
 
171
   std::string header_line;
 
172
   while (std::getline(io, header_line) && header_line != "\r")
 
173
      {
 
174
      auto sep = header_line.find(": ");
 
175
      if(sep == std::string::npos || sep > header_line.size() - 2)
 
176
         throw HTTP_Error("Invalid HTTP header " + header_line);
 
177
      const std::string key = header_line.substr(0, sep);
 
178
 
 
179
      if(sep + 2 < header_line.size() - 1)
 
180
         {
 
181
         const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2));
 
182
         headers[key] = val;
 
183
         }
 
184
      }
 
185
 
 
186
   if(status_code == 301 && headers.count("Location"))
 
187
      {
 
188
      if(allowable_redirects == 0)
 
189
         throw HTTP_Error("HTTP redirection count exceeded");
 
190
      return GET_sync(headers["Location"], allowable_redirects - 1);
 
191
      }
 
192
 
 
193
   std::vector<uint8_t> resp_body;
 
194
   std::vector<uint8_t> buf(4096);
 
195
   while(io.good())
 
196
      {
 
197
      io.read(cast_uint8_ptr_to_char(buf.data()), buf.size());
 
198
      resp_body.insert(resp_body.end(), buf.data(), &buf[io.gcount()]);
 
199
      }
 
200
 
 
201
   const std::string header_size = search_map(headers, std::string("Content-Length"));
 
202
 
 
203
   if(!header_size.empty())
 
204
      {
 
205
      if(resp_body.size() != to_u32bit(header_size))
 
206
         throw HTTP_Error("Content-Length disagreement, header says " +
 
207
                          header_size + " got " + std::to_string(resp_body.size()));
 
208
      }
 
209
 
 
210
   return Response(status_code, status_message, resp_body, headers);
 
211
   }
 
212
 
 
213
Response http_sync(const std::string& verb,
 
214
                   const std::string& url,
 
215
                   const std::string& content_type,
 
216
                   const std::vector<uint8_t>& body,
 
217
                   size_t allowable_redirects,
 
218
                   std::chrono::milliseconds timeout)
 
219
   {
 
220
   auto transact_with_timeout =
 
221
      [timeout](const std::string& hostname, const std::string& service)
 
222
      {
 
223
      return http_transact(hostname, service, timeout);
 
224
      };
 
225
 
 
226
   return http_sync(
 
227
      transact_with_timeout,
 
228
      verb,
 
229
      url,
 
230
      content_type,
 
231
      body,
 
232
      allowable_redirects);
 
233
   }
 
234
 
 
235
Response GET_sync(const std::string& url,
 
236
                  size_t allowable_redirects,
 
237
                  std::chrono::milliseconds timeout)
 
238
   {
 
239
   return http_sync("GET", url, "", std::vector<uint8_t>(), allowable_redirects, timeout);
 
240
   }
 
241
 
 
242
Response POST_sync(const std::string& url,
 
243
                   const std::string& content_type,
 
244
                   const std::vector<uint8_t>& body,
 
245
                   size_t allowable_redirects,
 
246
                   std::chrono::milliseconds timeout)
 
247
   {
 
248
   return http_sync("POST", url, content_type, body, allowable_redirects, timeout);
 
249
   }
 
250
 
 
251
}
 
252
 
 
253
}