libutil++  1.9.3
 All Classes Functions Variables
HTTPConnection.cpp
1 /*
2 ** libutil++
3 ** $Id: HTTPConnection.cpp 1785 2016-11-28 01:09:40Z 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: HTTPConnection.cpp 1785 2016-11-28 01:09:40Z sella $";
10 
11 #include "HTTPConnection.h"
12 #include "HTTP.h"
13 #include "../util/Time.h"
14 #include "../util/String.h"
15 #include "../util/CommonMacro.h"
16 
17 #include <string.h>
18 #include <unistd.h>
19 #include <zlib.h>
20 
21 #include <iostream>
22 #include <fstream>
23 #include <string>
24 
25 using namespace sella::server;
26 using namespace sella::net;
27 using namespace sella::util;
28 
29 const timeval HTTPConnection::ReceiveTimeoutDefault = { 30, 0 };
30 const std::string HTTPConnection::MIMEFallback = "text/plain; charset=us-ascii";
31 const std::string HTTPConnection::ContentTypeDefault = "text/html; charset=iso-8859-1";
32 
33 HTTPConnection::HTTPConnection(const HTTP &http, TCPSocket::shared &socket, timeval tv) throw () :
34  http(http),
35  socket(socket),
36  start(Time::clock_monotonic_coarse()),
37  request(http)
38 {
39  socket->setSoSndTimeo(tv);
40  socket->setSoRcvTimeo(tv);
41 }
42 
43 HTTPConnection::~HTTPConnection() throw () { }
44 
45 const HTTPConnection::shared HTTPConnection::shared_ptr(const HTTP &http, sella::net::TCPSocket::shared &socket, timeval tv) throw () {
46  return std::make_shared<HTTPConnection>(http, socket, tv);
47 }
48 
49 const HTTPRequest& HTTPConnection::getRequest(void) throw (ServerException) {
50  if (request.empty()) {
51  std::string buffer;
52 
53  read(buffer);
54  request.parse(buffer);
55 
56  if (request.contentLength > http.getRequestBodyLimit()) {
57  THROW_CODE(ServerException, Code::RequestEntityTooLarge, "Content-Length is larger than %zd", http.getRequestBodyLimit());
58  }
59 
60  if (request.isContinue()) {
61  if (request.isChunked()) {
62  chunker();
63  } else {
64  THROW_CODE(ServerException, Code::NotImplemented, "Unsupported transfer-encoding"); /* Not implemented */
65  }
66  }
67  }
68 
69  return request;
70 }
71 
72 struct timespec HTTPConnection::getRuntime(void) const throw () {
73  struct timespec delta = Time::clock_monotonic_coarse();
74 
75  return Time::subtractts(delta, start);
76 }
77 
78 bool HTTPConnection::isUsable(void) const throw () {
79  return socket->isUsable();
80 }
81 
82 bool HTTPConnection::isSecure(void) const throw () {
83  return socket->isSecure();
84 }
85 
86 void HTTPConnection::response(const std::string &body, const std::string &contentType, bool close) throw (ServerException) {
87  response(Code::OK, body, contentType, close);
88 }
89 
90 void HTTPConnection::response(const std::vector<char> &content, const std::string &contentType, bool close) throw (ServerException) {
91  response(Code::OK, content, contentType, close);
92 }
93 
94 void HTTPConnection::error(int code, const std::string &detail, const std::string &contentType) throw (ServerException) {
95  std::string body = getErrorBody(code, detail);
96 
97  response(code, body);
98 }
99 
100 void HTTPConnection::error(int code, const std::vector<char> &content, const std::string &contentType) throw (ServerException) {
101  response(code, content, contentType, true);
102 }
103 
104 void HTTPConnection::methodOptions(void) throw (ServerException) {
105  std::string buf;
106 
107  if (request.getURI().compare("*") == 0) {
108  /* Server specific */
109  } else {
110  /* Resource specific */
111  }
112 
113  try {
114  response(Code::OK, buf, ContentTypeDefault);
115  } catch (SocketException &e) {
116  RETHROW_CODE(ServerException, Code::InternalServerError, e);
117  }
118 }
119 
120 void HTTPConnection::methodGet(void) throw (ServerException) {
121  std::vector<char> content;
122  std::string contentType;
123 
124  getFileContent(request.getPath(), content, contentType);
125 
126  response(content, contentType);
127 }
128 
129 void HTTPConnection::methodHead(void) throw (ServerException) {
130  int code = Code::OK;
131  std::vector<char> content;
132  std::string contentType;
133 
134  try {
135  getFileContent(request.getPath(), content, contentType);
136  } catch (ServerException &e) {
137  code = e.getCode();
138  }
139 
140  try {
141  sendStatusLine(code);
142  sendHeaders(content.capacity(), contentType);
143  } catch (SocketException &e) {
144  RETHROW_CODE(ServerException, Code::InternalServerError, e);
145  }
146 }
147 
148 void HTTPConnection::methodPost(void) throw (ServerException) {
149  THROW_CODE(ServerException, Code::MethodNotAllowed, "The method %s is not allowed for the URL %s.", request.getMethodRaw().c_str(), request.getPath().c_str());
150 }
151 
152 void HTTPConnection::methodPut(void) throw (ServerException) {
153  THROW_CODE(ServerException, Code::MethodNotAllowed, "The method %s is not allowed for the URL %s.", request.getMethodRaw().c_str(), request.getPath().c_str());
154 }
155 
156 void HTTPConnection::methodDelete(void) throw (ServerException) {
157  THROW_CODE(ServerException, Code::MethodNotAllowed, "The method %s is not allowed for the URL %s.", request.getMethodRaw().c_str(), request.getPath().c_str());
158 }
159 
160 void HTTPConnection::methodTrace(void) throw (ServerException) {
161  const std::vector<char> &content = request.getContent();
162  std::string trace = String::sprintf("%s %s %s\r\n", request.getMethodRaw().c_str(), request.getURI().c_str(), request.getVersion().c_str());
163 
164  for (auto h = request.getHeaders().begin(); h != request.getHeaders().end(); ++h) {
165  trace += *h + "\r\n";
166  }
167  trace += "\r\n";
168 
169  try {
170  sendStatusLine(Code::OK);
171  sendHeaders(trace.size() + content.capacity(), "message/http");
172  sendBody(trace);
173  sendContent(content);
174  } catch (SocketException &e) {
175  RETHROW_CODE(ServerException, Code::InternalServerError, e);
176  }
177 }
178 
179 void HTTPConnection::methodConnect(void) throw (ServerException) {
180  THROW_CODE(ServerException, Code::MethodNotAllowed, "The method %s is not allowed for the URL %s.", request.getMethodRaw().c_str(), request.getPath().c_str());
181 }
182 
183 void HTTPConnection::methodPatch(void) throw (ServerException) {
184  THROW_CODE(ServerException, Code::MethodNotAllowed, "The method %s is not allowed for the URL %s.", request.getMethodRaw().c_str(), request.getPath().c_str());
185 }
186 
187 IPAddressPort HTTPConnection::getLocalIPAddressPort(void) const throw (ServerException) {
188  try {
189  return socket->getLocalIPAddressPort();
190  } catch (SocketException &e) {
191  RETHROW_CODE(ServerException, Code::InternalServerError, e);
192  }
193 }
194 
195 IPAddressPort HTTPConnection::getRemoteIPAddressPort(void) const throw (ServerException) {
196  try {
197  return socket->getRemoteIPAddressPort();
198  } catch (SocketException &e) {
199  RETHROW_CODE(ServerException, Code::InternalServerError, e);
200  }
201 }
202 
203 const char* HTTPConnection::getSSLLibraryVersion(void) const throw () {
204  if (socket->isSecure()) {
205  return std::static_pointer_cast<sella::net::SecureTCPSocket>(socket)->getSSLLibraryVersion();
206  }
207 
208  return "none";
209 }
210 
211 const char* HTTPConnection::getActiveProtocol(void) const throw () {
212  if (socket->isSecure()) {
213  try {
214  return std::static_pointer_cast<sella::net::SecureTCPSocket>(socket)->getActiveProtocol();
215  } catch (...) { }
216  }
217 
218  return "none";
219 }
220 
221 const char* HTTPConnection::getActiveCipher(void) const throw () {
222  if (socket->isSecure()) {
223  try {
224  return std::static_pointer_cast<sella::net::SecureTCPSocket>(socket)->getActiveCipher();
225  } catch (...) { }
226  }
227 
228  return "none";
229 }
230 
231 std::string HTTPConnection::getContentType(HTTPRequest::AcceptType type, const std::string &fallback) const throw () {
232  switch (type) {
233  case HTTPRequest::AcceptType::HTML:
234  return "text/html";
235 
236  case HTTPRequest::AcceptType::Plain:
237  return "text/plain";
238 
239  case HTTPRequest::AcceptType::JSON:
240  return "application/json";
241 
242  case HTTPRequest::AcceptType::JSONP:
243  return "application/javascript";
244 
245  case HTTPRequest::AcceptType::XML:
246  return "application/xml";
247 
248  case HTTPRequest::AcceptType::XHTML_XML:
249  return "application/xhtml+xml";
250 
251  case HTTPRequest::AcceptType::CSV:
252  return "text/csv";
253 
254  case HTTPRequest::AcceptType::PNG:
255  return "image/png";
256 
257  case HTTPRequest::AcceptType::GIF:
258  return "image/gif";
259 
260  case HTTPRequest::AcceptType::JPEG:
261  return "image/jpeg";
262 
263  case HTTPRequest::AcceptType::Icon:
264  return "image/x-icon";
265 
266  default:
267  break;
268  }
269 
270  return fallback;
271 }
272 
273 void HTTPConnection::clear(void) throw () {
274  socket.reset();
275  request.clear();
276 }
277 
278 void HTTPConnection::response(int code, const std::string &body, const std::string &contentType, bool close) throw (ServerException) {
279  try {
280  Compression compression = getCompressionType(body, contentType);
281 
282  sendStatusLine(code);
283 
284  if (compression == Gzip || compression == Deflate) {
285  std::string buf = String::deflate(body, (compression == Gzip) ? String::Gzip : String::Zlib, http.getCompressionLevel());
286 
287  sendHeaders(buf.size(), contentType, close, compression);
288  sendBody(buf);
289  } else {
290  sendHeaders(body.size(), contentType, close);
291  sendBody(body);
292  }
293  } catch (CompressionException &e) {
294  RETHROW_CODE(ServerException, Code::InternalServerError, e);
295  } catch (SocketException &e) {
296  RETHROW_CODE(ServerException, Code::InternalServerError, e);
297  }
298 }
299 
300 void HTTPConnection::response(int code, const std::vector<char> &content, const std::string &contentType, bool close) throw (ServerException) {
301  try {
302  Compression compression = getCompressionType(content, contentType);
303 
304  sendStatusLine(code);
305 
306  if (compression == Gzip || compression == Deflate) {
307  std::vector<char> buf = String::deflate(content, (compression == Gzip) ? String::Gzip : String::Zlib, http.getCompressionLevel(), content.capacity());
308 
309  sendHeaders(buf.capacity(), contentType, close, compression);
310  sendContent(buf);
311  } else {
312  sendHeaders(content.capacity(), contentType, close);
313  sendContent(content);
314  }
315  } catch (CompressionException &e) {
316  RETHROW_CODE(ServerException, Code::InternalServerError, e);
317  } catch (SocketException &e) {
318  RETHROW_CODE(ServerException, Code::InternalServerError, e);
319  }
320 }
321 
322 void HTTPConnection::sendStatusLine(int code, const std::string &detail) throw (SocketException) {
323  std::string buf("HTTP/1.1 ");
324 
325  buf += Code::statusCodeDisplayString(code);
326 
327  if (!detail.empty()) {
328  buf += " - ";
329  buf += detail;
330  }
331 
332  buf += "\r\n";
333 
334  socket->send(buf.c_str(), buf.size());
335 }
336 
337 void HTTPConnection::sendHeaders(size_t size, const std::string &contentType, bool close, Compression compression) throw (SocketException) {
338  std::string buf = buildHeaders(size, contentType, close, false, compression);
339 
340  socket->send(buf.c_str(), buf.size());
341 }
342 
343 void HTTPConnection::sendBody(const std::string &body) throw (SocketException) {
344  socket->send(body.c_str(), body.size());
345 }
346 
347 void HTTPConnection::sendContent(const std::vector<char> &content) throw (SocketException) {
348  socket->send(&content.front(), content.capacity());
349 }
350 
351 void HTTPConnection::read(std::string &buffer) throw (ServerException) {
352  char buf[262144]; /* 256K */
353  ssize_t pktlen;
354 
355  try {
356  do {
357  if ((pktlen = socket->recv(buf, sizeof(buf))) <= 0) {
358  THROW_CODE(ServerException, Code::InternalServerError, "socket disconnected");
359  }
360 
361  buffer.append(buf, pktlen);
362  } while (socket->hasData());
363  } catch (SocketException &e) {
364  RETHROW_CODE(ServerException, Code::InternalServerError, e);
365  }
366 }
367 
368 void HTTPConnection::chunker(void) throw (ServerException) {
369  std::string buffer;
370  size_t size;
371 
372  sendStatusLine(Code::Continue); /* Request next chunk */
373 
374  do {
375  /* Read until chunk size is available */
376  while ((size = buffer.find("\r\n")) == std::string::npos) {
377 // printf("READING FOR CHUNK SIZE\n");
378  read(buffer);
379  }
380 
381  { /* Chunk Size */
382  std::string buf = buffer.substr(0, size);
383 // printf("Chunk Size Buffer: %s (oct)\n", buf.c_str());
384 
385  try {
386  size = std::stoull(buf, NULL, 16);
387  } catch (std::exception &e) {
388  THROW_CODE(ServerException, Code::InternalServerError, "Invalid chunk size");
389  }
390 
391 // printf("Chunk Size: %zd\n", size);
392 
393  if (size == 0) {
394 // printf("GOT LAST CHUNK\n");
395 
396  return;
397  }
398 
399  buffer.erase(0, buf.size() + 2);
400  }
401 
402  /* Read additional data, until next chunk is full. */
403  while (buffer.size() < size + 2) {
404 // printf("READING FOR CHUNK DATA: buffer.size(): %zd, size + 2: %zd\n", buffer.size(), size + 2);
405  read(buffer);
406  }
407 
408  { /* Chunk Data */
409  request.appendBody(buffer.substr(0, size));
410  buffer.erase(0, size + 2);
411  }
412 
413  FIXME("Need to imlement support for trailers");
414  /* Read any trailers, until chunk size is reached */
415 // while ((size = buffer.find("\r\n")) == std::string::npos) {
416 // read(buffer);
417 // }
418  } while (true);
419 }
420 
421 HTTPConnection::Compression HTTPConnection::getCompressionType(const std::string &body, const std::string &contentType) const throw () {
422  Compression compression = None;
423 
424  if (http.getCompressionLevel() > 0 && body.size() > http.getCompressionThreshold()) {
425  static const boost::regex pattern("^(?:text|application)");
426 
427  if (String::regex_isearch(contentType, pattern)) {
428  if (request.isAcceptGzip()) { /* Prefer gzip over deflate - deflate support is poor on some browsers */
429  compression = Gzip;
430  } else if (request.isAcceptDeflate()) {
431  compression = Deflate;
432  } else {
433  compression = None;
434  }
435  }
436  }
437 
438  return compression;
439 }
440 
441 HTTPConnection::Compression HTTPConnection::getCompressionType(const std::vector<char> &content, const std::string &contentType) const throw () {
442  Compression compression = None;
443 
444  if (http.getCompressionLevel() > 0 && content.capacity() > http.getCompressionThreshold()) {
445  static const boost::regex pattern("^(?:text|application)");
446 
447  if (String::regex_isearch(contentType, pattern)) {
448  if (request.isAcceptGzip()) { /* Prefer gzip over deflate - deflate support is poor on some browsers */
449  compression = Gzip;
450  } else if (request.isAcceptDeflate()) {
451  compression = Deflate;
452  } else {
453  compression = None;
454  }
455  }
456  }
457 
458  return compression;
459 }
460 
461 const std::string HTTPConnection::buildHeaders(size_t contentSize, const std::string &contentType, bool close, bool useAllow, Compression compression, const std::string &transferEncoding) throw () {
462  std::string buf;
463 
464  buf = "Date: " + Time::getRFC1123Date() + "\r\n";
465  buf += "Server: " + http.getBanner() + "\r\n";
466 
467  if (useAllow) {
468  auto &&allow = http.getAllow();
469 
470  if (!allow.empty()) {
471  buf += "Allow: ";
472 
473  for (auto a = allow.begin(), end = allow.end(); a != end; ++a) {
474  switch (*a) {
475  case HTTPRequest::Options:
476  buf += "OPTIONS,";
477  break;
478 
479  case HTTPRequest::Get:
480  buf += "GET,";
481  break;
482 
483  case HTTPRequest::Head:
484  buf += "HEAD,";
485  break;
486 
487  case HTTPRequest::Post:
488  buf += "POST,";
489  break;
490 
491  case HTTPRequest::Put:
492  buf += "PUT,";
493  break;
494 
495  case HTTPRequest::Delete:
496  buf += "DELETE,";
497  break;
498 
499  case HTTPRequest::Trace:
500  buf += "TRACE,";
501  break;
502 
503  case HTTPRequest::Connect:
504  buf += "CONNECT,";
505  break;
506 
507  case HTTPRequest::Patch:
508  buf += "PATCH,";
509  break;
510 
511  default:
512  break;
513  }
514  }
515 
516  buf.erase(buf.size() - 1); /* buf.pop_back() not implemented yet. */
517  buf += "\r\n";
518  }
519  }
520 
521  if (transferEncoding.empty()) {
522  if (request.getMethod() != HTTPRequest::Head) {
523  buf += String::sprintf("Content-Length: %zd\r\n", contentSize);
524  }
525  } else {
526  buf += String::sprintf("Transfer-Encoding: %s\r\n", transferEncoding.c_str());
527  }
528 
529  if (compression == Gzip) {
530  if (request.getMethod() != HTTPRequest::Head) {
531  buf += "Content-Encoding: gzip\r\n";
532  }
533  } else if (compression == Deflate) {
534  if (request.getMethod() != HTTPRequest::Head) {
535  buf += "Content-Encoding: deflate\r\n";
536  }
537  }
538 
539  const auto &headers = http.getCustomHeaders();
540  for (auto h = headers.begin(), end = headers.end(); h != end; ++h) {
541  buf += *h + "\r\n";
542  }
543 
544  if (close) {
545  buf += "Connection: close\r\n";
546  }
547 
548  buf += "Content-Type: " + contentType + "\r\n";
549 
550  buf += "\r\n";
551 
552  return buf;
553 }
554 
555 void HTTPConnection::getFileContent(const std::string &file, std::vector<char> &content, std::string &contentType) throw (ServerException) {
556  try {
557  std::string buf;
558  std::string fullpath = request.getFullpath(file);
559  std::ifstream ifile(fullpath, std::ifstream::in);
560  size_t size = getFileSize(ifile);
561 
562  content.reserve(size); /* Changes capacity, but not size. */
563  ifile.read(&content.front(), size);
564 
565  if (!ifile) {
566  THROW_CODE(ServerException, Code::InternalServerError, "Read only %zd/%zd bytes from file", ifile.gcount(), size);
567  }
568 
569  contentType = request.getMIMEType(fullpath, MIMEFallback);
570  } catch (std::ifstream::failure &e) {
571  RETHROW_CODE(ServerException, Code::NotFound, e);
572  }
573 }
574 
575 size_t HTTPConnection::getFileSize(std::ifstream &ifile) throw (ServerException) {
576  if (!ifile.is_open()) {
577  THROW_CODE(ServerException, Code::Forbidden, "You don't have permission to access %s on this server.", request.getPath().c_str());
578  }
579 
580  ifile.seekg(0, std::ios::end);
581  size_t size = ifile.tellg();
582  ifile.seekg(0, std::ios::beg);
583 
584  if (size > http.getRequestBodyLimit()) {
585  THROW_CODE(ServerException, Code::RequestEntityTooLarge, "The requested URI %s is larger than the configured limit of %zd bytes.", request.getPath().c_str(), http.getRequestBodyLimit()).c_str();
586  }
587 
588  return size;
589 }
590 
591 std::string HTTPConnection::getErrorBody(int code, const std::string &detail, const std::string &callback) throw () {
592  std::string body, status(Code::statusCodeString(code));
593  std::string displayStatus(Code::statusCodeDisplayString(code));
594  HTTPRequest::AcceptType type = request.getAcceptType(http.getFallbackAcceptType());
595 
596  switch (type) {
597  case HTTPRequest::AcceptType::Any:
598  case HTTPRequest::AcceptType::Text_Any:
599  case HTTPRequest::AcceptType::HTML:
600  if (!http.getBanner().empty()) {
601  std::string banner = http.getBanner();
602 
603  body = "<html><head><title>" + displayStatus + "</title></head><body><h1>" + displayStatus + "</h1>" + detail + "<p><hr><address>" + banner + "</address></body></html>\r\n";
604  } else {
605  body = "<html><head><title>" + displayStatus + "</title></head><body><h1>" + displayStatus + "</h1>" + detail + "</body></html>\r\n";
606  }
607 
608  break;
609 
610  case HTTPRequest::AcceptType::JSON:
611  body = String::sprintf("{ \"error\": { \"code\": %d, \"status\": \"%s\", \"message\": \"%s\" }}", code, status.c_str(), String::escape(detail).c_str());
612 
613  break;
614 
615  case HTTPRequest::AcceptType::JSONP:
616  body = String::sprintf("%s({ \"error\": { \"code\": %d, \"status\": \"%s\", \"message\": \"%s\" } })", callback.c_str(), code, status.c_str(), String::escape(detail).c_str());
617 
618  break;
619 
620  case HTTPRequest::AcceptType::XHTML_XML:
621  case HTTPRequest::AcceptType::XML:
622  body = "<error><code>" + String::sprintf("%d", code) + "</code><status>" + status + "</status><message>" + detail + "</message></error>\r\n";
623 
624  break;
625 
626  case HTTPRequest::AcceptType::Text:
627  case HTTPRequest::AcceptType::Plain:
628  body = String::sprintf("code: %d, status: %s, message: %s", code, status.c_str(), detail.c_str());
629 
630  break;
631 
632  default:
633  break;
634  }
635 
636  return body;
637 }
638 
639 /*
640 ** vim: noet ts=3 sw=3
641 */