/* * IXHttp.cpp * Author: Benjamin Sergeant * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. */ #include "IXHttp.h" #include "IXCancellationRequest.h" #include "IXGzipCodec.h" #include "IXSocket.h" #include #include namespace ix { std::string Http::trim(const std::string& str) { std::string out; for (auto c : str) { if (c != ' ' && c != '\n' && c != '\r') { out += c; } } return out; } std::pair Http::parseStatusLine(const std::string& line) { // Request-Line = Method SP Request-URI SP HTTP-Version CRLF std::string token; std::stringstream tokenStream(line); std::vector tokens; // Split by ' ' while (std::getline(tokenStream, token, ' ')) { tokens.push_back(token); } std::string httpVersion; if (tokens.size() >= 1) { httpVersion = trim(tokens[0]); } int statusCode = -1; if (tokens.size() >= 2) { std::stringstream ss; ss << trim(tokens[1]); ss >> statusCode; } return std::make_pair(httpVersion, statusCode); } std::tuple Http::parseRequestLine( const std::string& line) { // Request-Line = Method SP Request-URI SP HTTP-Version CRLF std::string token; std::stringstream tokenStream(line); std::vector tokens; // Split by ' ' while (std::getline(tokenStream, token, ' ')) { tokens.push_back(token); } std::string method; if (tokens.size() >= 1) { method = trim(tokens[0]); } std::string requestUri; if (tokens.size() >= 2) { requestUri = trim(tokens[1]); } std::string httpVersion; if (tokens.size() >= 3) { httpVersion = trim(tokens[2]); } return std::make_tuple(method, requestUri, httpVersion); } std::tuple Http::parseRequest( std::unique_ptr& socket, int timeoutSecs) { HttpRequestPtr httpRequest; std::atomic requestInitCancellation(false); auto isCancellationRequested = makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation); // Read first line auto lineResult = socket->readLine(isCancellationRequested); auto lineValid = lineResult.first; auto line = lineResult.second; if (!lineValid) { return std::make_tuple(false, "Error reading HTTP request line", httpRequest); } // Parse request line (GET /foo HTTP/1.1\r\n) auto requestLine = Http::parseRequestLine(line); auto method = std::get<0>(requestLine); auto uri = std::get<1>(requestLine); auto httpVersion = std::get<2>(requestLine); // Retrieve and validate HTTP headers auto result = parseHttpHeaders(socket, isCancellationRequested); auto headersValid = result.first; auto headers = result.second; if (!headersValid) { return std::make_tuple(false, "Error parsing HTTP headers", httpRequest); } std::string body; if (headers.find("Content-Length") != headers.end()) { int contentLength = 0; try { contentLength = std::stoi(headers["Content-Length"]); } catch (const std::exception&) { return std::make_tuple( false, "Error parsing HTTP Header 'Content-Length'", httpRequest); } if (contentLength < 0) { return std::make_tuple( false, "Error: 'Content-Length' should be a positive integer", httpRequest); } auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested); if (!res.first) { return std::make_tuple( false, std::string("Error reading request: ") + res.second, httpRequest); } body = res.second; } // If the content was compressed with gzip, decode it if (headers["Content-Encoding"] == "gzip") { #ifdef IXWEBSOCKET_USE_ZLIB std::string decompressedPayload; if (!gzipDecompress(body, decompressedPayload)) { return std::make_tuple( false, std::string("Error during gzip decompression of the body"), httpRequest); } body = decompressedPayload; #else std::string errorMsg("ixwebsocket was not compiled with gzip support on"); return std::make_tuple(false, errorMsg, httpRequest); #endif } httpRequest = std::make_shared(uri, method, httpVersion, body, headers); return std::make_tuple(true, "", httpRequest); } bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr& socket) { // Write the response to the socket std::stringstream ss; ss << "HTTP/1.1 "; ss << response->statusCode; ss << " "; ss << response->description; ss << "\r\n"; if (!socket->writeBytes(ss.str(), nullptr)) { return false; } // Write headers ss.str(""); ss << "Content-Length: " << response->body.size() << "\r\n"; for (auto&& it : response->headers) { ss << it.first << ": " << it.second << "\r\n"; } ss << "\r\n"; if (!socket->writeBytes(ss.str(), nullptr)) { return false; } return response->body.empty() ? true : socket->writeBytes(response->body, nullptr); } } // namespace ix