FastLED 3.9.15
Loading...
Searching...
No Matches
http_parser.cpp.hpp
Go to the documentation of this file.
1#pragma once
2
4// IWYU pragma: begin_keep
5#include "fl/net/http/chunked_encoding.h" // Full type for shared_ptr<ChunkedReader>
6// IWYU pragma: end_keep
7#include "fl/stl/algorithm.h"
8#include "fl/stl/cstring.h"
9#include "fl/stl/cctype.h"
10#include "fl/stl/noexcept.h"
11namespace fl {
12namespace {
13
14// Helper: Convert string to lowercase
16 fl::string result = str;
17 for (size_t i = 0; i < result.size(); i++) {
18 result[i] = fl::tolower(result[i]);
19 }
20 return result;
21}
22
23// Helper: Trim whitespace from string
25 if (str.empty()) return str;
26
27 size_t start = 0;
28 while (start < str.size() && fl::isspace(str[start])) {
29 start++;
30 }
31
32 size_t end = str.size();
33 while (end > start && fl::isspace(str[end - 1])) {
34 end--;
35 }
36
37 return str.substr(start, end - start);
38}
39
40// Helper: Parse integer from string
41bool parseInt(const fl::string& str, int& out) {
42 if (str.empty()) return false;
43
44 int value = 0;
45 for (size_t i = 0; i < str.size(); i++) {
46 if (!fl::isdigit(str[i])) {
47 return false;
48 }
49 value = value * 10 + (str[i] - '0');
50 }
51
52 out = value;
53 return true;
54}
55} // anonymous namespace
56
57//==============================================================================
58// HttpRequestParser
59//==============================================================================
60
69
71
72void HttpRequestParser::feed(fl::span<const u8> data) {
73 mBuffer.insert(mBuffer.end(), data.begin(), data.end());
74
75 // State machine: parse incrementally
76 bool progress = true;
77 while (progress && mState != COMPLETE) {
78 progress = false;
79
80 switch (mState) {
82
83 if (parseRequestLine()) {
84
86 progress = true;
87 }
88 break;
89
90 case READ_HEADERS:
91
92 if (parseHeaders()) {
93
94 // Check if body is expected
95 auto contentLength = getHeader("Content-Length");
96 auto transferEncoding = getHeader("Transfer-Encoding");
97
98 if (transferEncoding.has_value() &&
99 toLower(transferEncoding.value()).find("chunked") != fl::string::npos) {
100 mIsChunked = true;
102 } else if (contentLength.has_value()) {
103 int contentLengthInt = 0;
104 if (parseInt(contentLength.value(), contentLengthInt)) {
105 mContentLength = static_cast<size_t>(contentLengthInt);
107 } else {
108 // Invalid Content-Length, skip to complete
110 }
111 } else {
112 // No body
114 }
115 progress = true;
116 }
117 break;
118
119 case READ_BODY:
120
121 parseBody();
122 // parseBody() will set mState to COMPLETE when done
123 // Continue processing if state changed to COMPLETE
124 if (mState == COMPLETE) {
125
126 progress = true;
127 }
128 break;
129
130 case COMPLETE:
131 // Nothing to do
132 break;
133 }
134 }
135}
136
138 return mState == COMPLETE;
139}
140
142 if (mState != COMPLETE) {
143 return HttpRequestPtr();
144 }
145
146 // Hand off the shared_ptr (zero-copy) and allocate a fresh one for next parse
148 reset();
149 return result;
150}
151
154 mBuffer.clear();
156 mChunkedReader->reset();
157 mContentLength = 0;
158 mIsChunked = false;
159}
160
162 auto crlfPos = findCRLF();
163 if (!crlfPos.has_value()) {
164 return false; // Need more data
165 }
166
167 // Extract request line
168 fl::string line(reinterpret_cast<const char*>(mBuffer.data()), crlfPos.value()); // ok reinterpret cast
169
170 consume(crlfPos.value() + 2); // +2 for CRLF
171
172 // Parse: "METHOD URI VERSION"
173 size_t methodEnd = line.find(' ');
174 if (methodEnd == fl::string::npos) {
175 return false; // Invalid format
176 }
177
178 size_t uriEnd = line.find(' ', methodEnd + 1);
179 if (uriEnd == fl::string::npos) {
180 return false; // Invalid format
181 }
182
183 req().method = line.substr(0, methodEnd);
184 req().uri = line.substr(methodEnd + 1, uriEnd - methodEnd - 1);
185 req().version = line.substr(uriEnd + 1);
186
187 return true;
188}
189
191 while (true) {
192 auto crlfPos = findCRLF();
193 if (!crlfPos.has_value()) {
194 return false; // Need more data
195 }
196
197 // Check for empty line (end of headers)
198 if (crlfPos.value() == 0) {
199 consume(2); // Consume final CRLF
200 return true;
201 }
202
203 // Extract header line
204 fl::string line(reinterpret_cast<const char*>(mBuffer.data()), crlfPos.value()); // ok reinterpret cast
205 consume(crlfPos.value() + 2); // +2 for CRLF
206
207 // Parse: "Name: Value"
208 size_t colonPos = line.find(':');
209 if (colonPos == fl::string::npos) {
210 continue; // Skip invalid header
211 }
212
213 fl::string name = http_parser_trim(line.substr(0, colonPos));
214 fl::string value = http_parser_trim(line.substr(colonPos + 1));
215
216 req().headers[name] = value;
217 }
218}
219
221 if (mIsChunked) {
222 // Feed buffer to chunked reader
223 if (!mBuffer.empty()) {
224 mChunkedReader->feed(mBuffer);
225 mBuffer.clear();
226 }
227
228 // Read all available chunks
229 while (mChunkedReader->hasChunk()) {
230 size_t chunkSz = mChunkedReader->nextChunkSize();
231 size_t offset = req().body.size();
232 req().body.resize(offset + chunkSz);
233 auto result = mChunkedReader->readChunk(
234 fl::span<u8>(req().body.data() + offset, chunkSz));
235 (void)result;
236 }
237
238 // Check if final chunk received
239 if (mChunkedReader->isFinal()) {
241 }
242 } else {
243 // Read Content-Length bytes
244 if (mBuffer.size() >= mContentLength) {
245 req().body.insert(req().body.end(),
246 mBuffer.begin(),
247 mBuffer.begin() + mContentLength);
250 }
251 }
252}
253
255 for (size_t i = 0; i + 1 < mBuffer.size(); i++) {
256 if (mBuffer[i] == '\r' && mBuffer[i + 1] == '\n') {
257 return i;
258 }
259 }
260 return fl::nullopt;
261}
262
264 if (n >= mBuffer.size()) {
265 mBuffer.clear();
266 } else {
267 // Work around fl::vector::erase() issue by copying remaining data
268 fl::vector<u8> remaining(mBuffer.begin() + n, mBuffer.end());
269 mBuffer = remaining;
270 }
271}
272
274 fl::string lowerName = toLower(name);
275
276 for (const auto& pair : req().headers) {
277 if (toLower(pair.first) == lowerName) {
278 return pair.second;
279 }
280 }
281
282 return fl::nullopt;
283}
284
285//==============================================================================
286// HttpResponseParser
287//==============================================================================
288
297
299
300void HttpResponseParser::feed(fl::span<const u8> data) {
301 mBuffer.insert(mBuffer.end(), data.begin(), data.end());
302
303 // State machine: parse incrementally
304 bool progress = true;
305 while (progress && mState != COMPLETE) {
306 progress = false;
307
308 switch (mState) {
309 case READ_STATUS_LINE:
310
311 if (parseStatusLine()) {
312
314 progress = true;
315 }
316 break;
317
318 case READ_HEADERS:
319
320 if (parseHeaders()) {
321
322 // Check if body is expected
323 auto contentLength = getHeader("Content-Length");
324 auto transferEncoding = getHeader("Transfer-Encoding");
325
326 if (transferEncoding.has_value() &&
327 toLower(transferEncoding.value()).find("chunked") != fl::string::npos) {
328 mIsChunked = true;
330 } else if (contentLength.has_value()) {
331 int contentLengthInt = 0;
332 if (parseInt(contentLength.value(), contentLengthInt)) {
333 mContentLength = static_cast<size_t>(contentLengthInt);
335 } else {
336 // Invalid Content-Length, skip to complete
338 }
339 } else {
340 // No body
342 }
343 progress = true;
344 }
345 break;
346
347 case READ_BODY:
348
349 parseBody();
350 // parseBody() will set mState to COMPLETE when done
351 // Continue processing if state changed to COMPLETE
352 if (mState == COMPLETE) {
353
354 progress = true;
355 }
356 break;
357
358 case COMPLETE:
359 // Nothing to do
360 break;
361 }
362 }
363}
364
366 return mState == COMPLETE;
367}
368
370 if (mState != COMPLETE) {
371 return HttpResponsePtr();
372 }
373
374 // Hand off the shared_ptr (zero-copy) and allocate a fresh one for next parse
376 reset();
377 return result;
378}
379
388
390 auto crlfPos = findCRLF();
391 if (!crlfPos.has_value()) {
392 return false; // Need more data
393 }
394
395 // Extract status line
396 fl::string line(reinterpret_cast<const char*>(mBuffer.data()), crlfPos.value()); // ok reinterpret cast
397 consume(crlfPos.value() + 2); // +2 for CRLF
398
399 // Parse: "VERSION STATUS_CODE REASON_PHRASE"
400 size_t versionEnd = line.find(' ');
401 if (versionEnd == fl::string::npos) {
402 return false; // Invalid format
403 }
404
405 size_t statusEnd = line.find(' ', versionEnd + 1);
406 if (statusEnd == fl::string::npos) {
407 // No reason phrase (optional in HTTP/1.1)
408 statusEnd = line.size();
409 }
410
411 resp().version = line.substr(0, versionEnd);
412 fl::string statusStr = line.substr(versionEnd + 1, statusEnd - versionEnd - 1);
413
414 if (!parseInt(statusStr, resp().statusCode)) {
415 return false; // Invalid status code
416 }
417
418 if (statusEnd < line.size()) {
419 resp().reasonPhrase = line.substr(statusEnd + 1);
420 }
421
422 return true;
423}
424
426 while (true) {
427 auto crlfPos = findCRLF();
428 if (!crlfPos.has_value()) {
429 return false; // Need more data
430 }
431
432 // Check for empty line (end of headers)
433 if (crlfPos.value() == 0) {
434 consume(2); // Consume final CRLF
435 return true;
436 }
437
438 // Extract header line
439 fl::string line(reinterpret_cast<const char*>(mBuffer.data()), crlfPos.value()); // ok reinterpret cast
440 consume(crlfPos.value() + 2); // +2 for CRLF
441
442 // Parse: "Name: Value"
443 size_t colonPos = line.find(':');
444 if (colonPos == fl::string::npos) {
445 continue; // Skip invalid header
446 }
447
448 fl::string name = http_parser_trim(line.substr(0, colonPos));
449 fl::string value = http_parser_trim(line.substr(colonPos + 1));
450
451 resp().headers[name] = value;
452 }
453}
454
456 if (mIsChunked) {
457 // Feed buffer to chunked reader
458 if (!mBuffer.empty()) {
459 mChunkedReader->feed(mBuffer);
460 mBuffer.clear();
461 }
462
463 // Read all available chunks
464 while (mChunkedReader->hasChunk()) {
465 size_t chunkSz = mChunkedReader->nextChunkSize();
466 size_t offset = resp().body.size();
467 resp().body.resize(offset + chunkSz);
468 auto result = mChunkedReader->readChunk(
469 fl::span<u8>(resp().body.data() + offset, chunkSz));
470 (void)result;
471 }
472
473 // Check if final chunk received
474 if (mChunkedReader->isFinal()) {
476 }
477 } else {
478 // Read Content-Length bytes
479 if (mBuffer.size() >= mContentLength) {
480 resp().body.insert(resp().body.end(),
481 mBuffer.begin(),
482 mBuffer.begin() + mContentLength);
485 }
486 }
487}
488
490 for (size_t i = 0; i + 1 < mBuffer.size(); i++) {
491 if (mBuffer[i] == '\r' && mBuffer[i + 1] == '\n') {
492 return i;
493 }
494 }
495 return fl::nullopt;
496}
497
499 if (n >= mBuffer.size()) {
500 mBuffer.clear();
501 } else {
502 // Work around fl::vector::erase() issue by copying remaining data
503 fl::vector<u8> remaining(mBuffer.begin() + n, mBuffer.end());
504 mBuffer = remaining;
505 }
506}
507
509 fl::string lowerName = toLower(name);
510
511 for (const auto& pair : resp().headers) {
512 if (toLower(pair.first) == lowerName) {
513 return pair.second;
514 }
515 }
516
517 return fl::nullopt;
518}
519
520} // namespace fl
fl::optional< fl::string > getHeader(const char *name) const
~HttpRequestParser() FL_NOEXCEPT
HttpRequest & req()
Definition http_parser.h:81
fl::shared_ptr< net::http::ChunkedReader > mChunkedReader
Definition http_parser.h:77
fl::optional< size_t > findCRLF() const
fl::shared_ptr< HttpRequest > mRequest
Definition http_parser.h:76
fl::vector< u8 > mBuffer
Definition http_parser.h:75
HttpRequestParser() FL_NOEXCEPT
HttpRequestPtrConst getRequest()
void feed(fl::span< const u8 > data)
fl::optional< size_t > findCRLF() const
~HttpResponseParser() FL_NOEXCEPT
HttpResponse & resp()
fl::vector< u8 > mBuffer
HttpResponsePtrConst getResponse()
void feed(fl::span< const u8 > data)
fl::shared_ptr< HttpResponse > mResponse
fl::shared_ptr< net::http::ChunkedReader > mChunkedReader
fl::optional< fl::string > getHeader(const char *name) const
bool empty() const FL_NOEXCEPT
fl::size find(const char &value) const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
string substr(fl::size start, fl::size length) const FL_NOEXCEPT
static constexpr fl::size npos
Definition string.h:195
fl::size size() const FL_NOEXCEPT
bool insert(iterator pos, const T &value) FL_NOEXCEPT
Definition vector.h:742
void resize(fl::size n) FL_NOEXCEPT
Definition vector.h:593
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
fl::string toLower(const fl::string &str)
fl::string http_parser_trim(const fl::string &str)
fl::shared_ptr< HttpResponse > HttpResponsePtr
Definition http_parser.h:36
unsigned char u8
Definition stdint.h:131
char tolower(char c) FL_NOEXCEPT
Convert character to lowercase.
Definition cctype.h:32
constexpr int type_rank< T >::value
bool isdigit(char c) FL_NOEXCEPT
Check if character is a decimal digit (0-9)
Definition cctype.h:25
int parseInt(const char *str, fl::size len)
Parse an integer from a character buffer.
constexpr T * end(T(&array)[N]) FL_NOEXCEPT
Optional< T > optional
Definition optional.h:16
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
fl::shared_ptr< const HttpResponse > HttpResponsePtrConst
Definition http_parser.h:38
fl::shared_ptr< const HttpRequest > HttpRequestPtrConst
Definition http_parser.h:37
constexpr nullopt_t nullopt
Definition optional.h:13
fl::shared_ptr< HttpRequest > HttpRequestPtr
Definition http_parser.h:35
bool isspace(char c) FL_NOEXCEPT
Check if character is whitespace (space, tab, newline, carriage return)
Definition cctype.h:18
Base definition for an LED controller.
Definition crgb.hpp:179
fl::string method
Definition http_parser.h:19
fl::string version
Definition http_parser.h:28
fl::vector< u8 > body
Definition http_parser.h:32
fl::string uri
Definition http_parser.h:20
fl::vector< u8 > body
Definition http_parser.h:23
fl::flat_map< fl::string, fl::string, fl::StringFastLess > headers
Definition http_parser.h:22
fl::string version
Definition http_parser.h:21
fl::string reasonPhrase
Definition http_parser.h:30
fl::flat_map< fl::string, fl::string, fl::StringFastLess > headers
Definition http_parser.h:31
#define FL_NOEXCEPT
T1 first
Definition pair.h:16
T2 second
Definition pair.h:17