1/*
2 * IXHttp.cpp
3 * Author: Benjamin Sergeant
4 * Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
5 */
6
7#include "IXHttp.h"
8
9#include "IXCancellationRequest.h"
10#include "IXGzipCodec.h"
11#include "IXSocket.h"
12#include <sstream>
13#include <vector>
14
15namespace ix
16{
17 std::string Http::trim(const std::string& str)
18 {
19 std::string out;
20 for (auto c : str)
21 {
22 if (c != ' ' && c != '\n' && c != '\r')
23 {
24 out += c;
25 }
26 }
27
28 return out;
29 }
30
31 std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
32 {
33 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
34 std::string token;
35 std::stringstream tokenStream(line);
36 std::vector<std::string> tokens;
37
38 // Split by ' '
39 while (std::getline(tokenStream, token, ' '))
40 {
41 tokens.push_back(token);
42 }
43
44 std::string httpVersion;
45 if (tokens.size() >= 1)
46 {
47 httpVersion = trim(tokens[0]);
48 }
49
50 int statusCode = -1;
51 if (tokens.size() >= 2)
52 {
53 std::stringstream ss;
54 ss << trim(tokens[1]);
55 ss >> statusCode;
56 }
57
58 return std::make_pair(httpVersion, statusCode);
59 }
60
61 std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
62 const std::string& line)
63 {
64 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
65 std::string token;
66 std::stringstream tokenStream(line);
67 std::vector<std::string> tokens;
68
69 // Split by ' '
70 while (std::getline(tokenStream, token, ' '))
71 {
72 tokens.push_back(token);
73 }
74
75 std::string method;
76 if (tokens.size() >= 1)
77 {
78 method = trim(tokens[0]);
79 }
80
81 std::string requestUri;
82 if (tokens.size() >= 2)
83 {
84 requestUri = trim(tokens[1]);
85 }
86
87 std::string httpVersion;
88 if (tokens.size() >= 3)
89 {
90 httpVersion = trim(tokens[2]);
91 }
92
93 return std::make_tuple(method, requestUri, httpVersion);
94 }
95
96 std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
97 std::unique_ptr<Socket>& socket, int timeoutSecs)
98 {
99 HttpRequestPtr httpRequest;
100
101 std::atomic<bool> requestInitCancellation(false);
102
103 auto isCancellationRequested =
104 makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
105
106 // Read first line
107 auto lineResult = socket->readLine(isCancellationRequested);
108 auto lineValid = lineResult.first;
109 auto line = lineResult.second;
110
111 if (!lineValid)
112 {
113 return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
114 }
115
116 // Parse request line (GET /foo HTTP/1.1\r\n)
117 auto requestLine = Http::parseRequestLine(line);
118 auto method = std::get<0>(requestLine);
119 auto uri = std::get<1>(requestLine);
120 auto httpVersion = std::get<2>(requestLine);
121
122 // Retrieve and validate HTTP headers
123 auto result = parseHttpHeaders(socket, isCancellationRequested);
124 auto headersValid = result.first;
125 auto headers = result.second;
126
127 if (!headersValid)
128 {
129 return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
130 }
131
132 std::string body;
133 if (headers.find("Content-Length") != headers.end())
134 {
135 int contentLength = 0;
136 try
137 {
138 contentLength = std::stoi(headers["Content-Length"]);
139 }
140 catch (const std::exception&)
141 {
142 return std::make_tuple(
143 false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
144 }
145
146 if (contentLength < 0)
147 {
148 return std::make_tuple(
149 false, "Error: 'Content-Length' should be a positive integer", httpRequest);
150 }
151
152 auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested);
153 if (!res.first)
154 {
155 return std::make_tuple(
156 false, std::string("Error reading request: ") + res.second, httpRequest);
157 }
158 body = res.second;
159 }
160
161 // If the content was compressed with gzip, decode it
162 if (headers["Content-Encoding"] == "gzip")
163 {
164#ifdef IXWEBSOCKET_USE_ZLIB
165 std::string decompressedPayload;
166 if (!gzipDecompress(body, decompressedPayload))
167 {
168 return std::make_tuple(
169 false, std::string("Error during gzip decompression of the body"), httpRequest);
170 }
171 body = decompressedPayload;
172#else
173 std::string errorMsg("ixwebsocket was not compiled with gzip support on");
174 return std::make_tuple(false, errorMsg, httpRequest);
175#endif
176 }
177
178 httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
179 return std::make_tuple(true, "", httpRequest);
180 }
181
182 bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
183 {
184 // Write the response to the socket
185 std::stringstream ss;
186 ss << "HTTP/1.1 ";
187 ss << response->statusCode;
188 ss << " ";
189 ss << response->description;
190 ss << "\r\n";
191
192 if (!socket->writeBytes(ss.str(), nullptr))
193 {
194 return false;
195 }
196
197 // Write headers
198 ss.str("");
199 ss << "Content-Length: " << response->body.size() << "\r\n";
200 for (auto&& it : response->headers)
201 {
202 ss << it.first << ": " << it.second << "\r\n";
203 }
204 ss << "\r\n";
205
206 if (!socket->writeBytes(ss.str(), nullptr))
207 {
208 return false;
209 }
210
211 return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
212 }
213} // namespace ix
214