libutil++  1.9.3
 All Classes Functions Variables
HTTPRequest.cpp
1 /*
2 ** libutil++
3 ** $Id: HTTPRequest.cpp 1783 2016-11-27 19:17:46Z sella $
4 ** Copyright (c) 2011-2016 Digital Genesis, LLC. All Rights Reserved.
5 ** Released under the LGPL Version 2.1 License.
6 ** http://www.digitalgenesis.com
7 */
8 
9 static const char rcsid[] __attribute__((used)) = "$Id: HTTPRequest.cpp 1783 2016-11-27 19:17:46Z sella $";
10 
11 #include "HTTPRequest.h"
12 #include "HTTP.h"
13 
14 #include "../util/String.h"
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 
19 #include <magic.h>
20 
21 using namespace sella::server;
22 using namespace sella::util;
23 
24 const std::string HTTPRequest::ContentTypeDefault = "application/octet-stream"; /* RFC 2616 7.2.1 */
25 
26 HTTPRequest::HTTPRequest(const HTTP &http) throw () :
27  http(http),
28  method(None),
29  methodRaw(),
30  methodOverride(None),
31  methodOverrideRaw(),
32  uri(),
33  path(),
34  query(),
35  version(),
36  headers(),
37  host(),
38  userAgent(),
39  accept(),
40  acceptCharset(),
41  acceptEncoding(),
42  acceptLanguage(),
43  transferEncoding(),
44  authorization(),
45  connection(),
46  cacheControl(),
47  expect(),
48  contentType(),
49  contentLength(0),
50  content()
51 { }
52 
53 HTTPRequest::~HTTPRequest() throw () { }
54 
55 void HTTPRequest::parse(const std::string &buffer) throw (ServerException) {
56  char buf[buffer.size() + 1];
57  char *line, *nline, *token, *rest, *ptr = buf;
58 
59  strncpy(buf, buffer.c_str(), sizeof(buf));
60 
61  if ((line = strtok_r(ptr, "\r\n", &rest)) == NULL) {
62  THROW_CODE(ServerException, Code::BadRequest, "Request was empty");
63  }
64 
65  TRACE(PACKAGE, "Request-Line: %s", line);
66 
67  nline = rest;
68 
69  /* Tokenize Request-Line - RFC 2616 5.1.2 */
70  if ((token = strtok_r(ptr, " ", &rest)) != NULL) {
71  if (strncmp("GET", token, 3) == 0) {
72  method = Get;
73  } else if (strncmp("POST", token, 4) == 0) {
74  method = Post;
75  } else if (strncmp("PUT", token, 3) == 0) {
76  method = Put;
77  } else if (strncmp("DELETE", token, 6) == 0) {
78  method = Delete;
79  } else if (strncmp("PATCH", token, 5) == 0) {
80  method = Patch;
81  } else if (strncmp("HEAD", token, 4) == 0) {
82  method = Head;
83  } else if (strncmp("OPTIONS", token, 7) == 0) {
84  method = Options;
85  } else if (strncmp("TRACE", token, 5) == 0) {
86  method = Trace;
87  } else if (strncmp("CONNECT", token, 7) == 0) {
88  method = Connect;
89  } else {
90  method = Extension;
91  }
92 
93  methodRaw = token;
94  } else {
95  THROW_CODE(ServerException, Code::BadRequest, "Request syntax is malformed: %s", line);
96  }
97 
98  if ((token = strrchr(rest, ' ')) != NULL) {
99  token[0] = 0;
100  uri = rest;
101  } else {
102  THROW_CODE(ServerException, Code::BadRequest, "Request syntax is malformed: %s", line);
103  }
104 
105  if (strncmp("HTTP/", ++token, 5) == 0) {
106  version = token;
107  } else {
108  THROW_CODE(ServerException, Code::BadRequest, "Request syntax is malformed: %s", line);
109  }
110 
111  if ((token = strchr(rest, '?')) != NULL) {
112  token[0] = 0;
113  query = token + 1;
114  path = rest;
115  } else {
116  path = rest;
117  }
118 
119  if ((token = strrchr(rest, '.')) != NULL) {
120  extension = token + 1;
121  }
122 
123  ptr = nline;
124 
125  /* Tokenize Headers */
126  while ((line = strtok_r(ptr, "\r\n", &rest)) != NULL) {
127  /* Standard Request Header Fields - RFC 2616 5.3 */
128  if (strncasecmp("Host: ", line, 6) == 0) {
129  host = line + 6;
130  } else if (strncasecmp("User-Agent: ", line, 12) == 0) {
131  userAgent = line + 12;
132  } else if (strncasecmp("Accept: ", line, 8) == 0) {
133  accept = String::split(line + 8, ",", false, false); // RFC 2616 14.1 - FIXME: Must sort accept by q values for later processing.
134  } else if (strncasecmp("Accept-Charset: ", line, 16) == 0) {
135  acceptCharset = String::split(line + 16, ",", false, false);
136  } else if (strncasecmp("Accept-Language: ", line, 17) == 0) {
137  acceptLanguage = String::split(line + 17, ",", false, false);
138  } else if (strncasecmp("Accept-Encoding: ", line, 17) == 0) {
139  acceptEncoding = String::split(line + 17, ",", false, false);
140  } else if (strncasecmp("Authorization: ", line, 15) == 0) {
141  authorization = line + 15;
142  } else if (strncasecmp("Transfer-Encoding: ", line, 19) == 0) {
143  transferEncoding = line + 19;
144  } else if (strncasecmp("Connection: ", line, 12) == 0) {
145  connection = line + 12;
146  } else if (strncasecmp("Cache-Control: ", line, 15) == 0) {
147  cacheControl = line + 15;
148  } else if (strncasecmp("Expect: ", line, 8) == 0) {
149  expect = line + 8;
150  } else if (strncasecmp("Content-Type: ", line, 14) == 0) {
151  contentType = line + 14;
152  } else if (strncasecmp("Content-Length: ", line, 16) == 0) {
153  try {
154  contentLength = std::stoull(line + 16);
155  } catch (std::exception &e) {
156  THROW_CODE(ServerException, Code::BadRequest, "Content-Length is invalid: %s", line + 15);
157  }
158  } else if (strncasecmp("X-HTTP-Method-Override: ", line, 24) == 0) { /* Common Non-standard (Google) */
159  methodOverrideRaw = line + 24;
160  } else if (strncasecmp("X-HTTP-Method: ", line, 15) == 0) { /* Common Non-standard (Microsoft) */
161  methodOverrideRaw = line + 15;
162  } else if (strncasecmp("X-METHOD-OVERRIDE: ", line, 19) == 0) { /* Common Non-standard (IBM) */
163  methodOverrideRaw = line + 19;
164  }
165 
166  headers.push_back(line);
167 
168  ptr = rest;
169 
170  if (rest != NULL && strncmp(rest, "\n\r\n", 3) == 0) {
171  rest += 3;
172 
173  break;
174  }
175  }
176 
177  if (!methodOverrideRaw.empty()) { /* Request-Line Method - RFC 2616 5.1.2 */
178  if (methodOverrideRaw.compare("GET")) {
179  methodOverride = Get;
180  } else if (methodOverrideRaw.compare("POST")) {
181  methodOverride = Post;
182  } else if (methodOverrideRaw.compare("PUT")) {
183  methodOverride = Put;
184  } else if (methodOverrideRaw.compare("DELETE")) {
185  methodOverride = Delete;
186  } else if (methodOverrideRaw.compare("PATCH")) {
187  methodOverride = Patch;
188  } else if (methodOverrideRaw.compare("HEAD")) {
189  methodOverride = Head;
190  } else if (methodOverrideRaw.compare("OPTIONS")) {
191  methodOverride = Options;
192  } else if (methodOverrideRaw.compare("TRACE")) {
193  methodOverride = Trace;
194  } else if (methodOverrideRaw.compare("CONNECT")) {
195  methodOverride = Connect;
196  } else {
197  methodOverride = Extension;
198  }
199  }
200 
201  if (host.empty()) { /* RFC 2616 14.23 */
202  THROW_CODE(ServerException, Code::BadRequest, "Host header field is missing");
203  }
204 
205  if (rest != NULL) {
206  std::string body(rest);
207 
208  appendBody(body);
209 
210  if (contentType.empty()) { /* Guess content-type from content - RFC 2616 7.2.1 */
211  contentType = getMIMEType(body, body.size(), ContentTypeDefault);
212  }
213  }
214 }
215 
216 void HTTPRequest::appendBody(const std::string &data) throw (ServerException) {
217  if (content.size() + data.size() > http.getRequestBodyLimit()) {
218  THROW_CODE(ServerException, Code::RequestEntityTooLarge, "The requested URI %s is larger than the configured limit of %zd bytes.", uri.c_str(), http.getRequestBodyLimit());
219  }
220 
221  content.reserve(content.size() + data.size());
222  content.insert(content.end(), data.begin(), data.end());
223 }
224 
225 void HTTPRequest::appendBody(const std::vector<char> &data) throw (ServerException) {
226  if (content.size() + data.size() > http.getRequestBodyLimit()) {
227  THROW_CODE(ServerException, Code::RequestEntityTooLarge, "The requested URI %s is larger than the configured limit of %zd bytes.", uri.c_str(), http.getRequestBodyLimit());
228  }
229 
230  content.reserve(content.size() + data.size());
231  content.insert(content.end(), data.begin(), data.end());
232 }
233 
234 HTTPRequest::Method HTTPRequest::getMethod(bool useOverride) const throw () {
235  if (useOverride && methodOverride != None) {
236  return methodOverride;
237  }
238 
239  return method;
240 }
241 
242 const std::string& HTTPRequest::getMethodRaw(bool useOverride) const throw () {
243  if (useOverride && methodOverride != None) {
244  return methodOverrideRaw;
245  }
246 
247  return methodRaw;
248 }
249 
250 HTTPRequest::Method HTTPRequest::getMethodOverride(void) const throw () {
251  return methodOverride;
252 }
253 
254 const std::string& HTTPRequest::getMethodOverrideRaw(void) const throw () {
255  return methodOverrideRaw;
256 }
257 
258 const std::string& HTTPRequest::getURI(void) const throw () {
259  return uri;
260 }
261 
262 const std::string& HTTPRequest::getPath(void) const throw () {
263  return path;
264 }
265 
266 const std::string& HTTPRequest::getExtension(void) const throw () {
267  return extension;
268 }
269 
270 const std::string& HTTPRequest::getQuery(void) const throw () {
271  return query;
272 }
273 
274 const std::string& HTTPRequest::getVersion(void) const throw () {
275  return version;
276 }
277 
278 const std::string& HTTPRequest::getHost(void) const throw () {
279  return host;
280 }
281 
282 const std::string& HTTPRequest::getUserAgent(void) const throw () {
283  return userAgent;
284 }
285 
286 const std::string& HTTPRequest::getAuthorization(void) const throw () {
287  return authorization;
288 }
289 
290 bool HTTPRequest::getAuthorizationCredentials(std::string &username, std::string &password) const throw () {
291  if (isBasicAuthorization()) {
292  std::string tmp = util::String::base64_decode(authorization);
293  size_t delim = tmp.find_first_of(':');
294 
295  if (delim != std::string::npos) {
296  username = tmp.substr(0, delim - 1);
297  password = tmp.substr(delim + 1);
298 
299  tmp.assign(tmp.size(), '\0'); /* Scrub sensitive data */
300 
301  return true;
302  }
303  }
304 
305  return false;
306 }
307 
308 const std::vector<std::string>& HTTPRequest::getAccept(void) const throw () {
309  return accept;
310 }
311 
312 HTTPRequest::AcceptType HTTPRequest::getAcceptType(AcceptType fallback) const throw () {
313  static const boost::regex pattern_json("^application/(?:x-)?json");
314  static const boost::regex pattern_jsonp("^application/(?:x-)?javascript");
315  static const boost::regex pattern_icon("^image/(?:x-icon|vnd.microsoft.icon)");
316 
317  if (!extension.empty()) {
318  if (extension.compare("json") == 0) {
319  return AcceptType::JSON;
320  } else if (extension.compare("xml") == 0) {
321  return AcceptType::XML;
322  } else if (extension.compare("jsonp") == 0) {
323  return AcceptType::JSONP;
324  } else if (extension.compare("csv") == 0) {
325  return AcceptType::CSV;
326  } else if (extension.compare("html") == 0) {
327  return AcceptType::HTML;
328  } else if (extension.compare("plain") == 0) {
329  return AcceptType::Plain;
330  }
331  }
332 
333  if (accept.empty()) {
334  return fallback;
335  }
336 
337  for (auto a = accept.begin(); a != accept.end(); ++a) { /* Ordered by most likely to match */
338  if (a->compare(0, 9, "text/html") == 0) {
339  return AcceptType::HTML;
340  } else if (a->compare(0, 10, "text/plain") == 0) {
341  return AcceptType::Plain;
342  } else if (String::regex_isearch(*a, pattern_json)) {
343  return AcceptType::JSON;
344  } else if (a->compare(0, 15, "application/xml") == 0) {
345  return AcceptType::XML;
346  } else if (String::regex_isearch(*a, pattern_jsonp)) { // JSONP
347  return AcceptType::JSONP;
348  } else if (a->compare(0, 21, "application/xhtml+xml") == 0) {
349  return AcceptType::XHTML_XML;
350  } else if (a->compare(0, 9, "image/png") == 0) {
351  return AcceptType::PNG;
352  } else if (a->compare(0, 9, "image/gif") == 0) {
353  return AcceptType::GIF;
354  } else if (a->compare(0, 10, "image/jpeg") == 0) {
355  return AcceptType::JPEG;
356  } else if (a->compare(0, 8, "text/csv") == 0) {
357  return AcceptType::CSV;
358  } else if (String::regex_isearch(*a, pattern_icon)) {
359  return AcceptType::Icon;
360  } else if (a->compare(0, 3, "*/*") == 0) {
361  return AcceptType::Any;
362  } else if (a->compare(0, 6, "text/*") == 0) {
363  return AcceptType::Text_Any;
364  } else if (a->compare(0, 5, "text/") == 0) {
365  return AcceptType::Text;
366  } else if (a->compare(0, 13, "application/*") == 0) {
367  return AcceptType::Application_Any;
368  } else if (a->compare(0, 12, "application/") == 0) {
369  return AcceptType::Application;
370  } else if (a->compare(0, 7, "image/*") == 0) {
371  return AcceptType::Image_Any;
372  } else if (a->compare(0, 6, "image/") == 0) {
373  return AcceptType::Image;
374  } else if (a->compare(0, 7, "audio/*") == 0) {
375  return AcceptType::Audio_Any;
376  } else if (a->compare(0, 7, "audio/") == 0) {
377  return AcceptType::Audio;
378  }
379  }
380 
381  return fallback;
382 }
383 
384 const std::vector<std::string>& HTTPRequest::getHeaders() const throw () {
385  return headers;
386 }
387 
388 std::string HTTPRequest::getHeader(const std::string &header) const throw (ServerException) {
389  std::vector<std::string> matches;
390  const boost::regex pattern(header + std::string(": *(\\S+) *"));
391 
392  for (auto h = headers.begin(); h != headers.end(); ++h) {
393  try {
394  if (String::regex_imatch(*h, pattern, matches)) {
395  return matches[1];
396  }
397  } catch (RegexException &e) {
398  RETHROW_CODE(ServerException, Code::InternalServerError, e);
399  }
400  }
401 
402  THROW_CODE(ServerException, Code::InternalServerError, "The requested header '%s' was not found.", header.c_str());
403 }
404 
405 const std::string HTTPRequest::getBody(void) const throw () {
406  std::string buf(content.begin(), content.end());
407 
408  return buf;
409 }
410 
411 const std::vector<char>& HTTPRequest::getContent(void) const throw () {
412  return content;
413 }
414 
415 bool HTTPRequest::isContinue(void) const throw () {
416  return !expect.compare("100-continue");
417 }
418 
419 bool HTTPRequest::isChunked(void) const throw () {
420  return !transferEncoding.compare("chunked");
421 }
422 
423 bool HTTPRequest::isAcceptGzip(void) const throw () {
424  auto end = acceptEncoding.end();
425 
426  return (std::find(acceptEncoding.begin(), end, "gzip") != end);
427 }
428 
429 bool HTTPRequest::isAcceptDeflate(void) const throw () {
430  auto end = acceptEncoding.end();
431 
432  return (std::find(acceptEncoding.begin(), end, "delfate") != end);
433 }
434 
435 bool HTTPRequest::isBasicAuthorization(void) const throw () {
436  return (authorization.compare(0, 5, "Basic") == 0);
437 }
438 
439 bool HTTPRequest::empty(void) const throw () {
440  return (method == None);
441 }
442 
443 bool HTTPRequest::hasHeader(const std::string &header) const throw (ServerException) {
444 #if 0
445  const boost::regex pattern(std::string("^") + header + std::string(":"));
446 
447  for (auto h = headers.begin(); h != headers.end(); ++h) {
448  try {
449  if (String::regex_isearch(*h, pattern)) {
450  return true;
451  }
452  } catch (RegexException &e) {
453  RETHROW_CODE(ServerException, Code::InternalServerError, e);
454  }
455  }
456 #else /* More efficient */
457  std::string pattern = String::lower(header) + ":";
458  size_t len = pattern.size();
459 
460  for (auto h = headers.begin(), end = headers.end(); h != end; ++h) {
461  if (pattern.compare(0, len, String::lower(*h))) {
462  return true;
463  }
464  }
465 #endif
466 
467  return false;
468 }
469 
470 void HTTPRequest::clear(void) throw () {
471  method = None;
472  methodRaw.clear();
473  methodOverride = None;
474  methodOverrideRaw.clear();
475  uri.clear();
476  path.clear();
477  extension.clear();
478  query.clear();
479  version.clear();
480  headers.clear();
481  host.clear();
482  userAgent.clear();
483  accept.clear();
484  acceptCharset.clear();
485  acceptEncoding.clear();
486  acceptLanguage.clear();
487  transferEncoding.clear();
488  authorization.clear();
489  connection.clear();
490  cacheControl.clear();
491  expect.clear();
492  contentType.clear();
493  contentLength = 0;
494  std::vector<char>().swap(content);
495 }
496 
497 std::string HTTPRequest::str(void) const throw () {
498  return String::sprintf("%s %s %s\n%s\n%s", methodRaw.c_str(), uri.c_str(), version.c_str(), String::join(headers, '\n').c_str(), getBody().c_str());
499 }
500 
501 void HTTPRequest::print(void) const throw () {
502  printf("[request]\n");
503  printf("%s %s %s\n", methodRaw.c_str(), uri.c_str(), version.c_str());
504 
505  for (auto h = headers.begin(); h != headers.end(); ++h) {
506  printf("%s\n", h->c_str());
507  }
508 
509  printf("uri: %s\n", uri.c_str());
510  printf("path: %s\n", path.c_str());
511  printf("extension: %s\n", extension.c_str());
512  printf("query: %s\n", query.c_str());
513 
514  printf("%s\n", getBody().c_str());
515 }
516 
517 std::string HTTPRequest::getFullpath(const std::string &file) throw (ServerException) {
518  std::string fullpath = http.getRoot();
519 
520  if (file.empty()) {
521  fullpath += getPath();
522  } else {
523  fullpath += file;
524  }
525 
526  if (isDirectory(fullpath)) {
527  const auto &indexes = http.getDirectoryIndexes();
528 
529  for (auto i = indexes.begin(); i != indexes.end(); ++i) {
530  std::string indexpath = fullpath + *i;
531 
532  if (isFile(indexpath)) {
533  return std::move(indexpath);
534  }
535  }
536  } else if (isFile(fullpath)) {
537  return std::move(fullpath);
538  }
539 
540  THROW_CODE(ServerException, Code::NotFound, "The requested URI %s was not found on this server.", getPath().c_str());
541 }
542 
543 std::string HTTPRequest::getMIMEType(const std::string &file, const std::string &fallback) const throw (ServerException) {
544  const char *result = NULL;
545 
546  try {
547  magic_t magic;
548 
549  magic_open(magic);
550 
551  if ((result = ::magic_file(magic, file.c_str())) == NULL) {
552  THROW(ServerException, "magic_file: failed");
553  }
554 
555  magic_close(magic);
556  } catch (ServerException &e) {
557  if (fallback.empty()) {
558  throw e;
559  }
560 
561  return fallback;
562  }
563 
564  return result;
565 }
566 
567 std::string HTTPRequest::getMIMEType(const std::string &buffer, size_t size, const std::string &fallback) const throw (ServerException) {
568  const char *result = NULL;
569 
570  try {
571  magic_t magic;
572 
573  magic_open(magic);
574 
575  if ((result = ::magic_buffer(magic, buffer.c_str(), (size < 0) ? buffer.size() : size)) == NULL) {
576  THROW(ServerException, "magic_buffer: failed");
577  }
578 
579  magic_close(magic);
580  } catch (ServerException &e) {
581  if (fallback.empty()) {
582  throw e;
583  }
584 
585  return fallback;
586  }
587 
588  return result;
589 }
590 
591 bool HTTPRequest::isDirectory(const std::string &path) const throw () {
592  struct stat s;
593 
594  return (stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode));
595 }
596 
597 bool HTTPRequest::isFile(const std::string &file) const throw () {
598  struct stat s;
599 
600  return (stat(file.c_str(), &s) == 0 && S_ISREG(s.st_mode));
601 }
602 
603 void HTTPRequest::magic_open(magic_t &magic) const throw (ServerException) {
604  if ((magic = ::magic_open(MAGIC_MIME)) == NULL) {
605  THROW_CODE(ServerException, Code::InternalServerError, "magic_open: failed to initialize");
606  }
607 
608  if (::magic_load(magic, NULL) != 0) {
609  std::string err = String::sprintf("magic_open: %s", magic_error(magic));
610  ::magic_close(magic);
611 
612  THROW_CODE(ServerException, Code::InternalServerError, err.c_str());
613  }
614 }
615 
616 void HTTPRequest::magic_close(magic_t &magic) const throw () {
617  ::magic_close(magic);
618 }
619 
620 /*
621 ** vim: noet ts=3 sw=3
622 */
Definition: String.cpp:34