LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
Request.cpp
Go to the documentation of this file.
1/**
2 * @file Request.cpp
3 * @brief Implementation of the Request class HTTP parser state machine.
4 */
5
6#include "../includes/Request.hpp"
7#include <sstream>
8#include <arpa/inet.h>
9#include <sys/socket.h>
10namespace req_utils
11{//sorry, this is ugly but first time creating a namespace, bear with me ;p
12
13 std::string trim(const std::string& s)
14 {
15 int start = 0;
16 int end = static_cast<int>(s.size()) - 1;
17 while (start <= end && std::isspace(static_cast<unsigned char>(s[start]))) {
18 ++start;
19 }
20 while (end >= start && std::isspace(static_cast<unsigned char>(s[end]))) {
21 --end;
22 }
23 if (start > end) {
24 return "";
25 }
26 return s.substr(start, end - start + 1);
27 }
28
29 std::string ipv4ToString(const struct ::sockaddr_in& addr)
30 {
31 unsigned long hostOrder = ntohl(addr.sin_addr.s_addr);
32 std::ostringstream oss;
33 oss << ((hostOrder >> 24) & 0xFF) << "."
34 << ((hostOrder >> 16) & 0xFF) << "."
35 << ((hostOrder >> 8) & 0xFF) << "."
36 << (hostOrder & 0xFF);
37 return oss.str();
38 }
39}
40// Canonical Form
41
42Request::Request(): _methodName(UNKNOWN_METHOD), _contentLength(-1), _reqState(REQ_HEADERS), _statusCode("200"), _maxBodySize(0), _totalBytesRead(0), _chunkSize(0), _chunkDecodeOffset(0), _isBodyProcessed(false), _ramParsePos(0)
43{}
44
45Request::Request(long long maxBodySize): _methodName(UNKNOWN_METHOD), _contentLength(-1), _reqState(REQ_HEADERS), _statusCode("200"), _maxBodySize(static_cast<size_t>(maxBodySize)), _totalBytesRead(0), _chunkSize(0), _chunkDecodeOffset(0), _isBodyProcessed(false), _ramParsePos(0)
46{}
47
48Request::Request(const Request& other): _methodName(other._methodName), _URL(other._URL), _protocol(other._protocol), _query(other._query), _contentLength(other._contentLength), _body(other._body), _decodedBody(other._decodedBody), _headers(other._headers), _cookies(other._cookies), _reqState(other._reqState), _statusCode(other._statusCode), _maxBodySize(other._maxBodySize), _totalBytesRead(other._totalBytesRead), _chunkSize(other._chunkSize), _chunkDecodeOffset(other._chunkDecodeOffset), _isBodyProcessed(other._isBodyProcessed), _chunkBuffer(other._chunkBuffer), _ramParsePos(other._ramParsePos)
49{
50}
51
53{
54 if (this != &other)
55 {
57 _URL = other._URL;
58 _protocol = other._protocol;
59 _query = other._query;
61 _body = other._body;
63 _headers = other._headers;
64 _cookies = other._cookies;
65 _reqState = other._reqState;
69 _chunkSize = other._chunkSize;
74 }
75 return *this;
76}
77
79
80// Getters
81
83 return _methodName;
84}
85
86const std::string& Request::getURL() const {
87 return _URL;
88}
89
90const std::string& Request::getProtocol() const {
91 return _protocol;
92}
93
94const std::string& Request::getQuery() const {
95 return _query;
96}
97
98const std::map<std::string, std::string>& Request::getHeaders() const {
99 return _headers;
100}
101
102const std::map<std::string, std::string>& Request::getCookies() const {
103 return _cookies;
104}
105
106// Core Parsing Behavior
107void Request::_parseRequestLine(const std::string& line)
108{
109 size_t firstSpace = line.find(' ');
110 if (firstSpace == std::string::npos)
111 {
113 _statusCode = "400"; // Bad Request
114 return;
115 }
116 std::string methodStr = line.substr(0, firstSpace);
119 {
121 _statusCode = "501"; // Not Implemented
122 return;
123 }
124 size_t secondSpace = line.find(' ', firstSpace + 1);
125 if (secondSpace == std::string::npos)
126 {
127 // HTTP/0.9: "GET /path"
128 _URL = line.substr(firstSpace + 1);
129 if (_URL.empty() || _URL[0] != '/')
130 {
132 _statusCode = "400"; // Bad Request
133 return;
134 }
135 _protocol = "HTTP/0.9";
138 return;
139 }
140 _URL = line.substr(firstSpace + 1, secondSpace - firstSpace - 1);
141 _protocol = line.substr(secondSpace + 1);
142 if (_protocol != "HTTP/1.0" && _protocol != "HTTP/1.1")
143 {
145 _statusCode = "505"; // HTTP Version Not Supported
146 return;
147 }
148 if (_URL.empty() || _URL[0] != '/')
149 {
151 _statusCode = "400"; // Bad Request
152 return;
153 }
155}
156
158{
159 size_t queryPos = _URL.find('?');
160
161 if (queryPos != std::string::npos)
162 {
163 _query = _URL.substr(queryPos + 1);
164 _URL = _URL.substr(0, queryPos);
165 }
166}
167
168size_t Request::parseHeaders(const std::string& rawBuffer)
169{
170 size_t headerEnd = rawBuffer.find("\r\n\r\n");
171 if (headerEnd == std::string::npos)
172 return 0;
173 _cookies.clear();
174
175 std::string headerSection = rawBuffer.substr(0, headerEnd);
176 size_t lineStart = 0;
177 bool firstLine = true;
178
179 while (lineStart < headerSection.size())
180 {
181 size_t lineEnd = headerSection.find("\r\n", lineStart);
182 if (lineEnd == std::string::npos)
183 lineEnd = headerSection.size();
184
185 std::string line = headerSection.substr(lineStart, lineEnd - lineStart);
186 if (!line.empty())
187 {
188 if (firstLine)
189 {
190 _parseRequestLine(line);
191 firstLine = false;
192 }
193 else
194 _parseHeaderLine(line);
195 }
196 lineStart = lineEnd + 2;
197 }
198 if (_reqState != REQ_ERROR)
199 _typeOfReq();
200 if (_headers.count("cookie"))
201 _parseCookies(_headers["cookie"]);
202 return headerEnd + 4;
203}
204
205void Request::_parseCookies(const std::string& cookieHeader)
206{
207 size_t pos = 0;
208 while (pos < cookieHeader.size())
209 {
210 size_t semicolonPos = cookieHeader.find(';', pos);
211 if (semicolonPos == std::string::npos)
212 semicolonPos = cookieHeader.size();
213
214 std::string token = req_utils::trim(cookieHeader.substr(pos, semicolonPos - pos));
215 size_t eqPos = token.find('=');
216 if (eqPos != std::string::npos)
217 {
218 std::string key = req_utils::trim(token.substr(0, eqPos));
219 std::string value = req_utils::trim(token.substr(eqPos + 1));
220 if (!key.empty())
221 _cookies[key] = value;
222 }
223 pos = semicolonPos + 1;
224 }
225}
226
228{
229 // determine body transfer mode from the parsed headers
230 std::string transferEncoding = getHeader("Transfer-Encoding");
231 std::string contentLengthStr = getHeader("Content-Length");
232
233 std::string toLower = transferEncoding;
234 for (size_t i = 0; i < toLower.size(); ++i)
235 toLower[i] = std::tolower(static_cast<unsigned char>(toLower[i]));
236 if (toLower.find("chunked") != std::string::npos)
237 {
238 _contentLength = -1;
240 }
241 else if (!contentLengthStr.empty())
242 {
243 char* endPtr = NULL;
244 long long cl = strtoll(contentLengthStr.c_str(), &endPtr, 10);
245 if (*endPtr != '\0' || cl < 0)
246 {
248 _statusCode = "400";
249 return ;
250 }
251 _contentLength = cl;
252 if (_maxBodySize > 0 && static_cast<size_t>(_contentLength) > _maxBodySize)
253 {
255 _statusCode = "413";
256 return ;
257 }
259 }
260 else
261 {
262 _contentLength = 0;
264 }
265}
266
267std::string Request::getHeader(const std::string& key) const
268{
269 std::string keyCopy = key;
270 for (size_t i = 0; i < key.size(); ++i)
271 {
272 keyCopy[i] = std::tolower(static_cast<unsigned char>(keyCopy[i]));
273 }
274 if (_headers.count(keyCopy))
275 return _headers.find(keyCopy)->second;
276 return "";
277}
278
279std::string Request::getCookie(const std::string& key) const
280{
281 std::map<std::string, std::string>::const_iterator it = _cookies.find(key);
282 if (it == _cookies.end())
283 return "";
284 return it->second;
285}
286
287void Request::_parseHeaderLine(const std::string& line)
288{
289 size_t colonPos = line.find(':');
290 if (colonPos == std::string::npos)
291 return;
292
293 std::string key = req_utils::trim(line.substr(0, colonPos));
294 std::string value = req_utils::trim(line.substr(colonPos + 1));
295
296 for (size_t i = 0; i < key.size(); ++i)
297 key[i] = std::tolower(static_cast<unsigned char>(key[i]));
298 if (!key.empty())
299 _headers[key] = value;
300}
301
303{
305 return true;
306
307 std::vector<char> tempBuffer(PARSE_BYTE_SLICE);
308 size_t ReadBaytes = _body.read(&tempBuffer[0], tempBuffer.size());
309 _totalBytesRead += ReadBaytes;
310 _chunkBuffer += std::string(&tempBuffer[0], ReadBaytes);
311 while (true)
312 {
313 size_t crlfPos = _chunkBuffer.find("\r\n");
314 if (crlfPos == std::string::npos)
315 break;
316 std::string hexStr = _chunkBuffer.substr(0, crlfPos);
317 size_t semi = hexStr.find(';');
318 if (semi != std::string::npos)
319 hexStr = hexStr.substr(0, semi);
320
321 unsigned long chunkSize = strtoul(hexStr.c_str(), NULL, 16);
322 size_t totalBytesNeeded = crlfPos + 2 + chunkSize + 2;
323
324 if (_chunkBuffer.size() < totalBytesNeeded)
325 break;
326 if (_chunkBuffer.substr(crlfPos + 2 + chunkSize, 2) != "\r\n") {
328 _statusCode = "400"; // Malformed chunk formatting
329 return true;
330 }
331 if (chunkSize == 0) {
332 _chunkBuffer.erase(0, totalBytesNeeded);
333 _isBodyProcessed = true;
334 break;
335 }
336
337 _decodedBody.append(_chunkBuffer.substr(crlfPos + 2, chunkSize));
340 _statusCode = "413"; // Payload Too Large
341 return true;
342 }
343 _chunkBuffer.erase(0, totalBytesNeeded);
344 }
345
346 if (_isBodyProcessed) {
350 std::stringstream ss;
351 ss << _body.getSize();
352 std::string str = ss.str();
353 _headers["content-length"] = str;
354 return true;
355 }
356 return false;
357}
358
359bool Request::isChunkedDone(const std::string& newData) const
360{
361 return newData.find("0\r\n\r\n") != std::string::npos;
362}
363
364
365// State Management Getters
366
368 return _reqState;
369}
370std::string Request::getStatusCode() const{
371 return _statusCode;
372}
373
374// I don't know what to return here
376 return _maxBodySize;
377}
378
380 return _contentLength;
381}
382
384 return _totalBytesRead;
385}
387 return _reqState == REQ_DONE;
388}
389
HTTPMethod
Represents the HTTP methods supported by the server as a bitmask.
@ UNKNOWN_METHOD
ReqState
Represents the current network-reading phase of the HTTP request.
Definition Request.hpp:27
@ REQ_BODY
Definition Request.hpp:29
@ REQ_ERROR
Definition Request.hpp:32
@ REQ_CHUNKED
Definition Request.hpp:30
@ REQ_DONE
Definition Request.hpp:31
@ REQ_HEADERS
Definition Request.hpp:28
#define PARSE_BYTE_SLICE
Definition Request.hpp:16
static HTTPMethod stringToMethod(const std::string &methodStr)
Converts a string representation of an HTTP method to its enum value.
size_t getSize() const
Gets total bytes currently stored (whether in RAM or File).
void append(const char *data, size_t length)
Appends raw byte data to the store. Automatically transitions from RAM to FILE_MODE if _bufferLimit i...
void resetReadPosition()
Resets the internal read position to the beginning of the data.
size_t read(char *buffer, size_t length)
Reads data from the store starting at the current read position. Advances the internal read position ...
void clear()
Resets the store, clears the vector, and closes the temp file descriptor.
size_t _chunkSize
Definition Request.hpp:123
size_t _chunkDecodeOffset
Definition Request.hpp:124
size_t _maxBodySize
Definition Request.hpp:119
long long _contentLength
Definition Request.hpp:109
void _typeOfReq()
Definition Request.cpp:227
bool isChunkedDone(const std::string &newData) const
Checks if the incoming chunked data contains the terminal "\r\n\r\n". Used to transition from REQ_CHU...
Definition Request.cpp:359
size_t _totalBytesRead
Definition Request.hpp:120
DataStore & getBodyStore()
Returns a reference to the DataStore to allow direct writing from recv().
Definition Request.cpp:390
const std::string & getQuery() const
Definition Request.cpp:94
void _parseRequestLine(const std::string &line)
Definition Request.cpp:107
bool _isBodyProcessed
Definition Request.hpp:125
void _parseHeaderLine(const std::string &line)
Definition Request.cpp:287
HTTPMethod getMethod() const
Definition Request.cpp:82
std::string getHeader(const std::string &key) const
Gets a specific header value.
Definition Request.cpp:267
ReqState getReqState() const
Definition Request.cpp:367
~Request()
Definition Request.cpp:78
std::string getStatusCode() const
Definition Request.cpp:370
size_t parseHeaders(const std::string &rawBuffer)
Parses the raw buffer containing the HTTP Request-Line and Headers. Transitions state to REQ_BODY,...
Definition Request.cpp:168
void _extractQueryFromURL()
Definition Request.cpp:157
std::string _chunkBuffer
Definition Request.hpp:126
long long getContentLength() const
Definition Request.cpp:379
void _parseCookies(const std::string &cookieHeader)
Definition Request.cpp:205
size_t getMaxBytesToRead() const
Definition Request.cpp:375
const std::string & getURL() const
Definition Request.cpp:86
std::string _URL
Definition Request.hpp:106
DataStore _body
Definition Request.hpp:111
std::map< std::string, std::string > _headers
Definition Request.hpp:113
std::string _statusCode
Definition Request.hpp:118
const std::map< std::string, std::string > & getHeaders() const
Definition Request.cpp:98
Request & operator=(const Request &other)
Definition Request.cpp:52
std::string _protocol
Definition Request.hpp:107
const std::map< std::string, std::string > & getCookies() const
Definition Request.cpp:102
size_t _ramParsePos
Definition Request.hpp:127
std::string getCookie(const std::string &key) const
Gets a specific cookie value.
Definition Request.cpp:279
size_t getTotalBytesRead() const
Definition Request.cpp:383
DataStore _decodedBody
Definition Request.hpp:112
HTTPMethod _methodName
Definition Request.hpp:105
std::string _query
Definition Request.hpp:108
bool processBodySlice()
Unified method to prepare the body for the Response/CGI phase. For Content-Length bodies,...
Definition Request.cpp:302
std::map< std::string, std::string > _cookies
Definition Request.hpp:114
ReqState _reqState
Definition Request.hpp:117
const std::string & getProtocol() const
Definition Request.cpp:90
bool isComplete() const
Definition Request.cpp:386
Request()
Definition Request.cpp:42
std::string ipv4ToString(const struct ::sockaddr_in &addr)
Definition Request.cpp:29
std::string trim(const std::string &s)
Definition Request.cpp:13