Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpParser.cpp
Go to the documentation of this file.
1
18#include "http/HttpParser.h"
19#include <sstream>
20#include <algorithm>
21#include <map>
22
23#include "http/ChunkedDecoder.h"
24#include "utility/utility.h"
25#include "framework_config.h"
26#include "DebugTrace.h"
33int HttpParser::parseStatusCode(const std::string &statusLine)
34{
35 std::istringstream stream(statusLine);
36 std::string httpVersion;
37 int code = 0;
38 stream >> httpVersion >> code;
39 return code;
40}
46std::map<std::string, std::string> HttpParser::parseHeaders(const std::string &rawHeaders)
47{
48 std::map<std::string, std::string> headers;
49 std::istringstream stream(rawHeaders);
50 std::string line;
51
52 while (std::getline(stream, line))
53 {
54 // Stop on blank line (end of headers)
55 if (line == "\r" || line.empty())
56 {
57 break;
58 }
59
60 auto colon = line.find(':');
61 if (colon == std::string::npos || colon + 1 >= line.size())
62 {
63 continue;
64 }
65
66 std::string key = line.substr(0, colon);
67 std::string value = line.substr(colon + 1);
68
69 // Remove CR and quotes
70 key.erase(std::remove(key.begin(), key.end(), '\r'), key.end());
71 value.erase(std::remove(value.begin(), value.end(), '\r'), value.end());
72 key.erase(std::remove(key.begin(), key.end(), '"'), key.end());
73 value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
74
75 // Lowercase key
76 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
77
78 // Trim whitespace from value
79 size_t start = value.find_first_not_of(" \t");
80 size_t end = value.find_last_not_of(" \t");
81 if (start != std::string::npos && end != std::string::npos)
82 {
83 value = value.substr(start, end - start + 1);
84 }
85 else
86 {
87 value = "";
88 }
89
90 headers[key] = value;
91 }
92
93 return headers;
94}
95
111std::pair<std::string, std::string> HttpParser::receiveHeaderAndLeftover(Tcp &socket)
112{
113 std::string buffer;
114 char temp[1460];
115
116 while (true)
117 {
118 int n = socket.recv(temp, sizeof(temp), HTTP_RECEIVE_TIMEOUT);
119 if (n <= 0)
120 {
121 return {"", ""}; // signal error (empty header)
122 }
123
124 buffer.append(temp, n);
125 std::size_t headerEnd = buffer.find("\r\n\r\n");
126
127 if (headerEnd != std::string::npos)
128 {
129 std::string headerText = buffer.substr(0, headerEnd + 4);
130 std::string leftover = buffer.substr(headerEnd + 4);
131 return {headerText, leftover};
132 }
133
134 vTaskDelay(pdMS_TO_TICKS(1));
135 }
136}
137
153 const std::map<std::string, std::string> &headers,
154 const std::string &leftoverBody,
155 std::string &outBody,
156 size_t maxLength,
157 bool *wasTruncated)
158{
159 if (wasTruncated)
160 *wasTruncated = false;
161
162 if (isChunkedEncoding(headers))
163 {
164 return receiveChunkedBodyToString(socket, leftoverBody, outBody, maxLength, wasTruncated);
165 }
166
167 if (headers.count("content-length"))
168 {
169 return receiveFixedLengthBodyToString(socket, headers, leftoverBody, outBody, maxLength, wasTruncated);
170 }
171
172 return receiveUnknownLengthBodyToString(socket, leftoverBody, outBody, maxLength, wasTruncated);
173}
174
175bool HttpParser::isChunkedEncoding(const std::map<std::string, std::string>& headers)
176{
177 auto it = headers.find("transfer-encoding");
178 return (it != headers.end() && toLower(it->second) == "chunked");
179}
180
181bool HttpParser::receiveChunkedBodyToString(Tcp& socket, const std::string& leftover, std::string& outBody, size_t maxLength, bool* wasTruncated)
182{
183 ChunkedDecoder decoder;
184 decoder.feed(leftover, maxLength);
185
186 char temp[1460];
187 while (!decoder.isComplete()) {
188 int n = socket.recv(temp, sizeof(temp), HTTP_RECEIVE_TIMEOUT);
189 if (n <= 0) {
190 printf("Chunked: recv() failed or EOF");
191 return false;
192 }
193 decoder.feed(std::string(temp, n), maxLength);
194 if (decoder.wasTruncated()) {
195 outBody = decoder.getDecoded();
196 if (wasTruncated) *wasTruncated = true;
197 return true;
198 }
199 }
200
201 outBody = decoder.getDecoded();
202 return true;
203}
204
205bool HttpParser::receiveFixedLengthBodyToString(Tcp& socket, const std::map<std::string, std::string>& headers, const std::string& leftover, std::string& outBody, size_t maxLength, bool* wasTruncated)
206{
207 int contentLength = std::stoi(headers.at("content-length"));
208 outBody = leftover;
209
210 if ((int)outBody.size() >= contentLength) {
211 outBody = outBody.substr(0, std::min((size_t)contentLength, maxLength));
212 if ((size_t)contentLength > maxLength && wasTruncated) *wasTruncated = true;
213 return true;
214 }
215
216 int attempts = 0;
217 int idleCycles = 0;
218 while ((int)outBody.size() < contentLength && attempts++ < 2000) {
219 char buffer[1460];
220 int toRead = std::min<int>(sizeof(buffer), contentLength - (int)outBody.size());
221 int n = socket.recv(buffer, toRead, HTTP_RECEIVE_TIMEOUT);
222
223 if (n <= 0) {
224 idleCycles++;
225 vTaskDelay(pdMS_TO_TICKS(10));
226 if (idleCycles > 20) return false;
227 continue;
228 }
229
230 idleCycles = 0;
231 if (outBody.size() + n > maxLength) {
232 outBody.append(buffer, maxLength - outBody.size());
233 if (wasTruncated) *wasTruncated = true;
234 return true;
235 }
236
237 outBody.append(buffer, n);
238 }
239
240 return ((int)outBody.size() == contentLength);
241}
242
243bool HttpParser::receiveUnknownLengthBodyToString(Tcp& socket, const std::string& leftover, std::string& outBody, size_t maxLength, bool* wasTruncated)
244{
245 outBody = leftover;
246 char buffer[1460];
247 while (true) {
248 int n = socket.recv(buffer, sizeof(buffer), HTTP_RECEIVE_TIMEOUT);
249 if (n <= 0) break;
250
251 if (outBody.size() + n > maxLength) {
252 outBody.append(buffer, maxLength - outBody.size());
253 if (wasTruncated) *wasTruncated = true;
254 return true;
255 }
256
257 outBody.append(buffer, n);
258 }
259
260 return !outBody.empty();
261}
262
263
265 Tcp& socket,
266 const std::string& leftover,
267 std::function<bool(const char*, size_t)> writeFn,
268 size_t maxLength,
269 bool* wasTruncated)
270{
271 ChunkedDecoder decoder;
272 decoder.feedToFile(leftover, writeFn, maxLength);
273
274 char buffer[1460];
275 while (!decoder.isComplete()) {
276 int n = socket.recv(buffer, sizeof(buffer), HTTP_RECEIVE_TIMEOUT);
277 if (n <= 0) {
278 printf("Chunked: recv() failed or EOF\n");
279 return false;
280 }
281
282 if (!decoder.feedToFile(std::string(buffer, n), writeFn, maxLength)) {
283 printf("Chunked: writeFn failed\n");
284 return false;
285 }
286
287 if (decoder.wasTruncated()) {
288 if (wasTruncated) *wasTruncated = true;
289 return true;
290 }
291 }
292
293 if (wasTruncated) *wasTruncated = decoder.wasTruncated();
294 return true;
295}
296
298 Tcp& socket,
299 const std::map<std::string, std::string>& headers,
300 const std::string& leftover,
301 std::function<bool(const char*, size_t)> writeFn,
302 size_t maxLength,
303 bool* wasTruncated)
304{
305 if (wasTruncated) *wasTruncated = false;
306
307 auto it = headers.find("content-length");
308 if (it == headers.end()) return false;
309
310 int contentLength = std::stoi(it->second);
311 size_t totalWritten = 0;
312
313 // Write leftover first
314 if (!leftover.empty()) {
315 size_t toWrite = std::min(leftover.size(), maxLength);
316 if (!writeFn(leftover.data(), toWrite)) return false;
317 totalWritten += toWrite;
318
319 if (leftover.size() > maxLength) {
320 if (wasTruncated) *wasTruncated = true;
321 return true;
322 }
323 }
324
325 char buffer[1460];
326 int idleCycles = 0;
327 while (totalWritten < (size_t)contentLength) {
328 size_t remaining = contentLength - totalWritten;
329 int toRead = std::min((int)sizeof(buffer), (int)remaining);
330 int n = socket.recv(buffer, toRead, HTTP_RECEIVE_TIMEOUT);
331
332 if (n <= 0) {
333 vTaskDelay(pdMS_TO_TICKS(10));
334 if (++idleCycles > 20) return false;
335 continue;
336 }
337
338 idleCycles = 0;
339
340 size_t available = maxLength - totalWritten;
341 size_t toWrite = std::min((size_t)n, available);
342 if (!writeFn(buffer, toWrite)) return false;
343 totalWritten += toWrite;
344
345 if (toWrite < (size_t)n) {
346 if (wasTruncated) *wasTruncated = true;
347 return true;
348 }
349 }
350
351 return true;
352}
353
354
355
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
void feed(const std::string &data, size_t maxLength=MAX_HTTP_BODY_LENGTH)
std::string getDecoded()
bool isComplete() const
bool wasTruncated() const
Set if the content-length is longer than the maximum value allowed.
bool feedToFile(const std::string &data, std::function< bool(const char *data, size_t len)> writeFn, size_t maxLength=MAX_HTTP_BODY_LENGTH)
Feed data to the decoder, writing it to a file using the provided write function.
static std::pair< std::string, std::string > receiveHeaderAndLeftover(Tcp &socket)
Receive raw headers from the socket until \r \r .
static bool receiveChunkedBodyToString(Tcp &socket, const std::string &leftover, std::string &out, size_t maxLength, bool *wasTruncated)
static bool receiveFixedLengthBodyToString(Tcp &socket, const std::map< std::string, std::string > &headers, const std::string &leftover, std::string &out, size_t maxLength, bool *wasTruncated)
static bool receiveFixedLengthBodyToFile(Tcp &socket, const std::map< std::string, std::string > &headers, const std::string &leftover, std::function< bool(const char *, size_t)> writeFn, size_t maxLength, bool *wasTruncated)
static bool receiveUnknownLengthBodyToString(Tcp &socket, const std::string &leftover, std::string &out, size_t maxLength, bool *wasTruncated)
static bool isChunkedEncoding(const std::map< std::string, std::string > &headers)
static bool receiveBody(Tcp &socket, const std::map< std::string, std::string > &headers, const std::string &leftoverBody, std::string &outBody, size_t maxLength, bool *wasTruncated)
Receive the HTTP body from socket, using Content-Length or connection close.
static std::map< std::string, std::string > parseHeaders(const std::string &rawHeaders)
Parses the HTTP headers from a raw header string.
static bool receiveChunkedBodyToFile(Tcp &socket, const std::string &leftover, std::function< bool(const char *, size_t)> writeFn, size_t maxLength, bool *wasTruncated)
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
int recv(char *buffer, size_t size, uint32_t timeout_ms)
Receive data from the connection.
Definition Tcp.cpp:389
Delegates to user or system configuration.
#define HTTP_RECEIVE_TIMEOUT
Timeout for receiving HTTP data in milliseconds.
std::string toLower(std::string str)
Definition utility.cpp:93
System utilities for diagnostics, memory, stack usage, and tracing.