9 static const char rcsid[] __attribute__((used)) =
"$Id: HTTPConnection.cpp 1785 2016-11-28 01:09:40Z sella $";
11 #include "HTTPConnection.h"
13 #include "../util/Time.h"
14 #include "../util/String.h"
15 #include "../util/CommonMacro.h"
25 using namespace sella::server;
26 using namespace sella::net;
27 using namespace sella::util;
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";
33 HTTPConnection::HTTPConnection(
const HTTP &http, TCPSocket::shared &socket, timeval tv)
throw () :
36 start(Time::clock_monotonic_coarse()),
39 socket->setSoSndTimeo(tv);
40 socket->setSoRcvTimeo(tv);
43 HTTPConnection::~HTTPConnection() throw () { }
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);
49 const HTTPRequest& HTTPConnection::getRequest(
void) throw (ServerException) {
50 if (request.empty()) {
54 request.parse(buffer);
56 if (request.contentLength > http.getRequestBodyLimit()) {
57 THROW_CODE(ServerException, Code::RequestEntityTooLarge,
"Content-Length is larger than %zd", http.getRequestBodyLimit());
60 if (request.isContinue()) {
61 if (request.isChunked()) {
64 THROW_CODE(ServerException, Code::NotImplemented,
"Unsupported transfer-encoding");
73 struct timespec delta = Time::clock_monotonic_coarse();
75 return Time::subtractts(delta, start);
78 bool HTTPConnection::isUsable(
void)
const throw () {
79 return socket->isUsable();
82 bool HTTPConnection::isSecure(
void)
const throw () {
83 return socket->isSecure();
86 void HTTPConnection::response(
const std::string &body,
const std::string &contentType,
bool close)
throw (ServerException) {
87 response(Code::OK, body, contentType, close);
90 void HTTPConnection::response(
const std::vector<char> &content,
const std::string &contentType,
bool close)
throw (ServerException) {
91 response(Code::OK, content, contentType, close);
94 void HTTPConnection::error(
int code,
const std::string &detail,
const std::string &contentType)
throw (ServerException) {
95 std::string body = getErrorBody(code, detail);
100 void HTTPConnection::error(
int code,
const std::vector<char> &content,
const std::string &contentType)
throw (ServerException) {
101 response(code, content, contentType,
true);
104 void HTTPConnection::methodOptions(
void) throw (ServerException) {
107 if (request.getURI().compare(
"*") == 0) {
114 response(Code::OK, buf, ContentTypeDefault);
115 }
catch (SocketException &e) {
116 RETHROW_CODE(ServerException, Code::InternalServerError, e);
120 void HTTPConnection::methodGet(
void) throw (ServerException) {
121 std::vector<char> content;
122 std::string contentType;
124 getFileContent(request.getPath(), content, contentType);
126 response(content, contentType);
129 void HTTPConnection::methodHead(
void) throw (ServerException) {
131 std::vector<char> content;
132 std::string contentType;
135 getFileContent(request.getPath(), content, contentType);
136 }
catch (ServerException &e) {
141 sendStatusLine(code);
142 sendHeaders(content.capacity(), contentType);
143 }
catch (SocketException &e) {
144 RETHROW_CODE(ServerException, Code::InternalServerError, e);
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());
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());
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());
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());
164 for (
auto h = request.getHeaders().begin(); h != request.getHeaders().end(); ++h) {
165 trace += *h +
"\r\n";
170 sendStatusLine(Code::OK);
171 sendHeaders(trace.size() + content.capacity(),
"message/http");
173 sendContent(content);
174 }
catch (SocketException &e) {
175 RETHROW_CODE(ServerException, Code::InternalServerError, e);
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());
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());
187 IPAddressPort HTTPConnection::getLocalIPAddressPort(
void)
const throw (ServerException) {
189 return socket->getLocalIPAddressPort();
190 }
catch (SocketException &e) {
191 RETHROW_CODE(ServerException, Code::InternalServerError, e);
195 IPAddressPort HTTPConnection::getRemoteIPAddressPort(
void)
const throw (ServerException) {
197 return socket->getRemoteIPAddressPort();
198 }
catch (SocketException &e) {
199 RETHROW_CODE(ServerException, Code::InternalServerError, e);
203 const char* HTTPConnection::getSSLLibraryVersion(
void)
const throw () {
204 if (socket->isSecure()) {
211 const char* HTTPConnection::getActiveProtocol(
void)
const throw () {
212 if (socket->isSecure()) {
221 const char* HTTPConnection::getActiveCipher(
void)
const throw () {
222 if (socket->isSecure()) {
231 std::string HTTPConnection::getContentType(HTTPRequest::AcceptType type,
const std::string &fallback)
const throw () {
233 case HTTPRequest::AcceptType::HTML:
236 case HTTPRequest::AcceptType::Plain:
239 case HTTPRequest::AcceptType::JSON:
240 return "application/json";
242 case HTTPRequest::AcceptType::JSONP:
243 return "application/javascript";
245 case HTTPRequest::AcceptType::XML:
246 return "application/xml";
248 case HTTPRequest::AcceptType::XHTML_XML:
249 return "application/xhtml+xml";
251 case HTTPRequest::AcceptType::CSV:
254 case HTTPRequest::AcceptType::PNG:
257 case HTTPRequest::AcceptType::GIF:
260 case HTTPRequest::AcceptType::JPEG:
263 case HTTPRequest::AcceptType::Icon:
264 return "image/x-icon";
273 void HTTPConnection::clear(
void) throw () {
278 void HTTPConnection::response(
int code,
const std::string &body,
const std::string &contentType,
bool close)
throw (ServerException) {
280 Compression compression = getCompressionType(body, contentType);
282 sendStatusLine(code);
284 if (compression == Gzip || compression == Deflate) {
285 std::string buf = String::deflate(body, (compression == Gzip) ? String::Gzip : String::Zlib, http.getCompressionLevel());
287 sendHeaders(buf.size(), contentType, close, compression);
290 sendHeaders(body.size(), contentType, close);
293 }
catch (CompressionException &e) {
294 RETHROW_CODE(ServerException, Code::InternalServerError, e);
295 }
catch (SocketException &e) {
296 RETHROW_CODE(ServerException, Code::InternalServerError, e);
300 void HTTPConnection::response(
int code,
const std::vector<char> &content,
const std::string &contentType,
bool close)
throw (ServerException) {
302 Compression compression = getCompressionType(content, contentType);
304 sendStatusLine(code);
306 if (compression == Gzip || compression == Deflate) {
307 std::vector<char> buf = String::deflate(content, (compression == Gzip) ? String::Gzip : String::Zlib, http.getCompressionLevel(), content.capacity());
309 sendHeaders(buf.capacity(), contentType, close, compression);
312 sendHeaders(content.capacity(), contentType, close);
313 sendContent(content);
315 }
catch (CompressionException &e) {
316 RETHROW_CODE(ServerException, Code::InternalServerError, e);
317 }
catch (SocketException &e) {
318 RETHROW_CODE(ServerException, Code::InternalServerError, e);
322 void HTTPConnection::sendStatusLine(
int code,
const std::string &detail)
throw (SocketException) {
323 std::string buf(
"HTTP/1.1 ");
325 buf += Code::statusCodeDisplayString(code);
327 if (!detail.empty()) {
334 socket->send(buf.c_str(), buf.size());
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);
340 socket->send(buf.c_str(), buf.size());
343 void HTTPConnection::sendBody(
const std::string &body)
throw (SocketException) {
344 socket->send(body.c_str(), body.size());
347 void HTTPConnection::sendContent(
const std::vector<char> &content)
throw (SocketException) {
348 socket->send(&content.front(), content.capacity());
351 void HTTPConnection::read(std::string &buffer)
throw (ServerException) {
357 if ((pktlen = socket->recv(buf,
sizeof(buf))) <= 0) {
358 THROW_CODE(ServerException, Code::InternalServerError,
"socket disconnected");
361 buffer.append(buf, pktlen);
362 }
while (socket->hasData());
363 }
catch (SocketException &e) {
364 RETHROW_CODE(ServerException, Code::InternalServerError, e);
368 void HTTPConnection::chunker(
void) throw (ServerException) {
372 sendStatusLine(Code::Continue);
376 while ((size = buffer.find(
"\r\n")) == std::string::npos) {
382 std::string buf = buffer.substr(0, size);
386 size = std::stoull(buf, NULL, 16);
387 }
catch (std::exception &e) {
388 THROW_CODE(ServerException, Code::InternalServerError,
"Invalid chunk size");
399 buffer.erase(0, buf.size() + 2);
403 while (buffer.size() < size + 2) {
409 request.appendBody(buffer.substr(0, size));
410 buffer.erase(0, size + 2);
413 FIXME(
"Need to imlement support for trailers");
421 HTTPConnection::Compression HTTPConnection::getCompressionType(
const std::string &body,
const std::string &contentType)
const throw () {
422 Compression compression = None;
424 if (http.getCompressionLevel() > 0 && body.size() > http.getCompressionThreshold()) {
425 static const boost::regex pattern(
"^(?:text|application)");
427 if (String::regex_isearch(contentType, pattern)) {
428 if (request.isAcceptGzip()) {
430 }
else if (request.isAcceptDeflate()) {
431 compression = Deflate;
441 HTTPConnection::Compression HTTPConnection::getCompressionType(
const std::vector<char> &content,
const std::string &contentType)
const throw () {
442 Compression compression = None;
444 if (http.getCompressionLevel() > 0 && content.capacity() > http.getCompressionThreshold()) {
445 static const boost::regex pattern(
"^(?:text|application)");
447 if (String::regex_isearch(contentType, pattern)) {
448 if (request.isAcceptGzip()) {
450 }
else if (request.isAcceptDeflate()) {
451 compression = Deflate;
461 const std::string HTTPConnection::buildHeaders(
size_t contentSize,
const std::string &contentType,
bool close,
bool useAllow, Compression compression,
const std::string &transferEncoding)
throw () {
464 buf =
"Date: " + Time::getRFC1123Date() +
"\r\n";
465 buf +=
"Server: " + http.getBanner() +
"\r\n";
468 auto &&allow = http.getAllow();
470 if (!allow.empty()) {
473 for (
auto a = allow.begin(), end = allow.end(); a != end; ++a) {
475 case HTTPRequest::Options:
479 case HTTPRequest::Get:
483 case HTTPRequest::Head:
487 case HTTPRequest::Post:
491 case HTTPRequest::Put:
495 case HTTPRequest::Delete:
499 case HTTPRequest::Trace:
503 case HTTPRequest::Connect:
507 case HTTPRequest::Patch:
516 buf.erase(buf.size() - 1);
521 if (transferEncoding.empty()) {
522 if (request.getMethod() != HTTPRequest::Head) {
523 buf += String::sprintf(
"Content-Length: %zd\r\n", contentSize);
526 buf += String::sprintf(
"Transfer-Encoding: %s\r\n", transferEncoding.c_str());
529 if (compression == Gzip) {
530 if (request.getMethod() != HTTPRequest::Head) {
531 buf +=
"Content-Encoding: gzip\r\n";
533 }
else if (compression == Deflate) {
534 if (request.getMethod() != HTTPRequest::Head) {
535 buf +=
"Content-Encoding: deflate\r\n";
539 const auto &headers = http.getCustomHeaders();
540 for (
auto h = headers.begin(), end = headers.end(); h != end; ++h) {
545 buf +=
"Connection: close\r\n";
548 buf +=
"Content-Type: " + contentType +
"\r\n";
555 void HTTPConnection::getFileContent(
const std::string &file, std::vector<char> &content, std::string &contentType)
throw (ServerException) {
558 std::string fullpath = request.getFullpath(file);
559 std::ifstream ifile(fullpath, std::ifstream::in);
560 size_t size = getFileSize(ifile);
562 content.reserve(size);
563 ifile.read(&content.front(), size);
566 THROW_CODE(ServerException, Code::InternalServerError,
"Read only %zd/%zd bytes from file", ifile.gcount(), size);
569 contentType = request.getMIMEType(fullpath, MIMEFallback);
570 }
catch (std::ifstream::failure &e) {
571 RETHROW_CODE(ServerException, Code::NotFound, e);
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());
580 ifile.seekg(0, std::ios::end);
581 size_t size = ifile.tellg();
582 ifile.seekg(0, std::ios::beg);
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();
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());
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();
603 body =
"<html><head><title>" + displayStatus +
"</title></head><body><h1>" + displayStatus +
"</h1>" + detail +
"<p><hr><address>" + banner +
"</address></body></html>\r\n";
605 body =
"<html><head><title>" + displayStatus +
"</title></head><body><h1>" + displayStatus +
"</h1>" + detail +
"</body></html>\r\n";
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());
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());
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";
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());