/src/pistache/src/common/http.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * SPDX-FileCopyrightText: 2015 Mathieu Stefani |
3 | | * |
4 | | * SPDX-License-Identifier: Apache-2.0 |
5 | | */ |
6 | | |
7 | | /* http.cc |
8 | | Mathieu Stefani, 13 August 2015 |
9 | | |
10 | | Http layer implementation |
11 | | */ |
12 | | |
13 | | #include <pistache/winornix.h> |
14 | | |
15 | | #include <pistache/config.h> |
16 | | #include <pistache/eventmeth.h> |
17 | | #include <pistache/http.h> |
18 | | #include <pistache/http_header.h> |
19 | | #include <pistache/net.h> |
20 | | #include <pistache/peer.h> |
21 | | #include <pistache/transport.h> |
22 | | |
23 | | #include PST_STRERROR_R_HDR |
24 | | |
25 | | #include <charconv> |
26 | | #include <cstring> |
27 | | #include <ctime> |
28 | | #include <iomanip> |
29 | | #include <iostream> |
30 | | #include <memory> |
31 | | #include <stdexcept> |
32 | | #include <string> |
33 | | #include <unordered_map> |
34 | | |
35 | | #include <fcntl.h> // for file-constants (_O_RDONLY etc.) in Windows |
36 | | #include PST_FCNTL_HDR // for function fcntl() |
37 | | |
38 | | #include PST_MISC_IO_HDR // for _close (io.h / unistd.h) |
39 | | #include PIST_FILEFNS_HDR // for "open" |
40 | | |
41 | | #include <sys/stat.h> |
42 | | #include <sys/types.h> |
43 | | |
44 | | namespace Pistache::Http |
45 | | { |
46 | | |
47 | | template <typename H, typename Stream, typename... Args> |
48 | | typename std::enable_if<Header::IsHeader<H>::value, Stream&>::type |
49 | | writeHeader(Stream& stream, Args&&... args) |
50 | 0 | { |
51 | 0 | H header(std::forward<Args>(args)...); |
52 | |
|
53 | 0 | stream << H::Name << ": "; |
54 | 0 | header.write(stream); |
55 | |
|
56 | 0 | stream << crlf; |
57 | |
|
58 | 0 | return stream; |
59 | 0 | } Unexecuted instantiation: _ZN8Pistache4Http11writeHeaderINS0_6Header16TransferEncodingENSt3__113basic_ostreamIcNS4_11char_traitsIcEEEEJNS2_8EncodingEEEENS4_9enable_ifIXsr6Header8IsHeaderIT_EE5valueERT0_E4typeESD_DpOT1_ Unexecuted instantiation: _ZN8Pistache4Http11writeHeaderINS0_6Header13ContentLengthENSt3__113basic_ostreamIcNS4_11char_traitsIcEEEEJRmEEENS4_9enable_ifIXsr6Header8IsHeaderIT_EE5valueERT0_E4typeESD_DpOT1_ Unexecuted instantiation: _ZN8Pistache4Http11writeHeaderINS0_6Header13ContentLengthENSt3__113basic_ostreamIcNS4_11char_traitsIcEEEEJRKmEEENS4_9enable_ifIXsr6Header8IsHeaderIT_EE5valueERT0_E4typeESE_DpOT1_ |
60 | | |
61 | | namespace |
62 | | { |
63 | | bool writeStatusLine(Version version, Code code, DynamicStreamBuf& buf) |
64 | 0 | { |
65 | 0 | #define PST_OUT(...) \ |
66 | 0 | do \ |
67 | 0 | { \ |
68 | 0 | __VA_ARGS__; \ |
69 | 0 | if (!os) \ |
70 | 0 | return false; \ |
71 | 0 | } while (0) |
72 | |
|
73 | 0 | std::ostream os(&buf); |
74 | |
|
75 | 0 | PST_OUT(os << version << " "); |
76 | 0 | PST_OUT(os << static_cast<int>(code)); |
77 | 0 | PST_OUT(os << ' '); |
78 | 0 | PST_OUT(os << code); |
79 | 0 | PST_OUT(os << crlf); |
80 | | |
81 | 0 | return true; |
82 | |
|
83 | 0 | #undef PST_OUT |
84 | 0 | } |
85 | | |
86 | | bool writeHeaders(const Header::Collection& headers, DynamicStreamBuf& buf) |
87 | 0 | { |
88 | 0 | #define PST_OUT(...) \ |
89 | 0 | do \ |
90 | 0 | { \ |
91 | 0 | __VA_ARGS__; \ |
92 | 0 | if (!os) \ |
93 | 0 | return false; \ |
94 | 0 | } while (0) |
95 | |
|
96 | 0 | std::ostream os(&buf); |
97 | |
|
98 | 0 | for (const auto& header : headers.list()) |
99 | 0 | { |
100 | 0 | PST_OUT(os << header->name() << ": "); |
101 | 0 | PST_OUT(header->write(os)); |
102 | 0 | PST_OUT(os << crlf); |
103 | 0 | } |
104 | | |
105 | 0 | return true; |
106 | |
|
107 | 0 | #undef PST_OUT |
108 | 0 | } |
109 | | |
110 | | bool writeCookies(const CookieJar& cookies, DynamicStreamBuf& buf) |
111 | 0 | { |
112 | 0 | #define PST_OUT(...) \ |
113 | 0 | do \ |
114 | 0 | { \ |
115 | 0 | __VA_ARGS__; \ |
116 | 0 | if (!os) \ |
117 | 0 | return false; \ |
118 | 0 | } while (0) |
119 | |
|
120 | 0 | std::ostream os(&buf); |
121 | 0 | for (const auto& cookie : cookies) |
122 | 0 | { |
123 | 0 | PST_OUT(os << "Set-Cookie: "); |
124 | 0 | PST_OUT(os << cookie); |
125 | 0 | PST_OUT(os << crlf); |
126 | 0 | } |
127 | | |
128 | 0 | return true; |
129 | |
|
130 | 0 | #undef PST_OUT |
131 | 0 | } |
132 | | |
133 | | using HttpMethods = std::unordered_map<std::string, Method>; |
134 | | |
135 | | const HttpMethods httpMethods = { |
136 | | #define METHOD(repr, str) { str, Method::repr }, |
137 | | HTTP_METHODS |
138 | | #undef METHOD |
139 | | }; |
140 | | |
141 | | } // namespace |
142 | | |
143 | | namespace Private |
144 | | { |
145 | | |
146 | | Step::Step(Message* request) |
147 | 7.66k | : message(request) |
148 | 7.66k | { } |
149 | | |
150 | | void Step::raise(const char* msg, Code code /* = Code::Bad_Request */) |
151 | 611 | { |
152 | 611 | throw HttpError(code, msg); |
153 | 611 | } |
154 | | |
155 | | State RequestLineStep::apply(StreamCursor& cursor) |
156 | 2.57k | { |
157 | 2.57k | StreamCursor::Revert revert(cursor); |
158 | | |
159 | 2.57k | auto* request = static_cast<Request*>(message); |
160 | | |
161 | 2.57k | StreamCursor::Token methodToken(cursor); |
162 | 2.57k | if (!match_until(' ', cursor)) |
163 | 26 | return State::Again; |
164 | | |
165 | 2.54k | auto it = httpMethods.find(methodToken.text()); |
166 | 2.54k | if (it != httpMethods.end()) |
167 | 2.42k | { |
168 | 2.42k | request->method_ = it->second; |
169 | 2.42k | } |
170 | 126 | else |
171 | 126 | { |
172 | 126 | raise("Unknown HTTP request method"); |
173 | 126 | } |
174 | | |
175 | 2.54k | int n; |
176 | | |
177 | 2.54k | if (cursor.eof()) |
178 | 0 | return State::Again; |
179 | 2.54k | else if ((n = cursor.current()) != ' ') |
180 | 0 | raise("Malformed HTTP request after Method, expected SP"); |
181 | | |
182 | 2.54k | if (!cursor.advance(1)) |
183 | 0 | return State::Again; |
184 | | |
185 | 2.54k | StreamCursor::Token resToken(cursor); |
186 | 24.0k | while ((n = cursor.current()) != '?' && n != ' ') |
187 | 21.5k | if (!cursor.advance(1)) |
188 | 40 | return State::Again; |
189 | | |
190 | 2.50k | request->resource_ = resToken.text(); |
191 | | |
192 | | // Query parameters of the Uri |
193 | 2.50k | if (n == '?') |
194 | 545 | { |
195 | 545 | if (!cursor.advance(1)) |
196 | 0 | return State::Again; |
197 | | |
198 | 8.25k | while ((n = cursor.current()) != ' ') |
199 | 8.08k | { |
200 | 8.08k | StreamCursor::Token keyToken(cursor); |
201 | 8.08k | if (!match_until({ '=', ' ', '&' }, cursor)) |
202 | 312 | return State::Again; |
203 | | |
204 | 7.76k | std::string key = keyToken.text(); |
205 | | |
206 | 7.76k | auto c = cursor.current(); |
207 | 7.76k | if (c == ' ') |
208 | 87 | { |
209 | 87 | request->query_.add(std::move(key), ""); |
210 | 87 | } |
211 | 7.68k | else if (c == '&') |
212 | 5.00k | { |
213 | 5.00k | request->query_.add(std::move(key), ""); |
214 | 5.00k | if (!cursor.advance(1)) |
215 | 0 | return State::Again; |
216 | 5.00k | } |
217 | 2.68k | else if (c == '=') |
218 | 2.68k | { |
219 | 2.68k | if (!cursor.advance(1)) |
220 | 0 | return State::Again; |
221 | | |
222 | 2.68k | StreamCursor::Token valueToken(cursor); |
223 | 2.68k | if (!match_until({ ' ', '&' }, cursor)) |
224 | 62 | return State::Again; |
225 | | |
226 | 2.61k | std::string value = valueToken.text(); |
227 | 2.61k | request->query_.add(std::move(key), std::move(value)); |
228 | 2.61k | if (cursor.current() == '&') |
229 | 2.54k | { |
230 | 2.54k | if (!cursor.advance(1)) |
231 | 0 | return State::Again; |
232 | 2.54k | } |
233 | 2.61k | } |
234 | 7.76k | } |
235 | 545 | } |
236 | | |
237 | | // @Todo: Fragment |
238 | | |
239 | | // SP |
240 | 2.13k | if (!cursor.advance(1)) |
241 | 0 | return State::Again; |
242 | | |
243 | | // HTTP-Version |
244 | 2.13k | StreamCursor::Token versionToken(cursor); |
245 | | |
246 | 9.70k | while (!cursor.eol()) |
247 | 7.75k | if (!cursor.advance(1)) |
248 | 186 | return State::Again; |
249 | | |
250 | 1.94k | const char* ver = versionToken.rawText(); |
251 | 1.94k | const size_t size = versionToken.size(); |
252 | 1.94k | if (strncmp(ver, "HTTP/1.0", size) == 0) |
253 | 1.24k | { |
254 | 1.24k | request->version_ = Version::Http10; |
255 | 1.24k | } |
256 | 703 | else if (strncmp(ver, "HTTP/1.1", size) == 0) |
257 | 476 | { |
258 | 476 | request->version_ = Version::Http11; |
259 | 476 | } |
260 | 227 | else |
261 | 227 | { |
262 | 227 | raise("Encountered invalid HTTP version"); |
263 | 227 | } |
264 | | |
265 | 1.94k | if (!cursor.advance(2)) |
266 | 0 | return State::Again; |
267 | | |
268 | 1.94k | revert.ignore(); |
269 | 1.94k | return State::Next; |
270 | 1.94k | } |
271 | | |
272 | | State ResponseLineStep::apply(StreamCursor& cursor) |
273 | 348 | { |
274 | 348 | StreamCursor::Revert revert(cursor); |
275 | | |
276 | 348 | auto* response = static_cast<Response*>(message); |
277 | | |
278 | 348 | if (match_raw("HTTP/1.1", strlen("HTTP/1.1"), cursor)) |
279 | 175 | { |
280 | | // response->version = Version::Http11; |
281 | 175 | } |
282 | 173 | else if (match_raw("HTTP/1.0", strlen("HTTP/1.0"), cursor)) |
283 | 166 | { |
284 | 166 | } |
285 | 7 | else |
286 | 7 | { |
287 | 7 | raise("Encountered invalid HTTP version"); |
288 | 7 | } |
289 | | |
290 | 348 | int n; |
291 | | // SP |
292 | 348 | if ((n = cursor.current()) != StreamCursor::Eof && n != ' ') |
293 | 9 | raise("Expected SPACE after http version"); |
294 | 348 | if (!cursor.advance(1)) |
295 | 4 | return State::Again; |
296 | | |
297 | 344 | StreamCursor::Token codeToken(cursor); |
298 | 344 | if (!match_until(' ', cursor)) |
299 | 2 | return State::Again; |
300 | | |
301 | 342 | int code = 0; |
302 | 342 | const char* beg = codeToken.rawText(); |
303 | 342 | const char* end = codeToken.rawText() + codeToken.size(); |
304 | 342 | const auto parseResult = std::from_chars(beg, end, code); |
305 | | |
306 | 342 | if (parseResult.ec != std::errc {} || *parseResult.ptr != ' ') |
307 | 127 | raise("Failed to parse return code"); |
308 | 342 | response->code_ = static_cast<Http::Code>(code); |
309 | | |
310 | 342 | if (!cursor.advance(1)) |
311 | 0 | return State::Again; |
312 | | |
313 | 1.92k | while (!cursor.eol() && !cursor.eof()) |
314 | 1.58k | { |
315 | 1.58k | cursor.advance(1); |
316 | 1.58k | } |
317 | | |
318 | 342 | if (!cursor.advance(2)) |
319 | 138 | return State::Again; |
320 | | |
321 | 204 | revert.ignore(); |
322 | 204 | return State::Next; |
323 | 342 | } |
324 | | |
325 | | State HeadersStep::apply(StreamCursor& cursor) |
326 | 2.91k | { |
327 | 2.91k | StreamCursor::Revert revert(cursor); |
328 | | |
329 | 35.4k | while (!cursor.eol()) |
330 | 34.8k | { |
331 | 34.8k | StreamCursor::Revert headerRevert(cursor); |
332 | | |
333 | | // Read the header name |
334 | 34.8k | size_t start = cursor; |
335 | | |
336 | 439k | while (cursor.current() != ':') |
337 | 406k | if (!cursor.advance(1)) |
338 | 1.67k | return State::Again; |
339 | | |
340 | | // Skip the ':' |
341 | 33.1k | if (!cursor.advance(1)) |
342 | 0 | return State::Again; |
343 | | |
344 | 33.1k | std::string name = std::string(cursor.offset(start), cursor.diff(start) - 1); |
345 | | |
346 | | // Ignore spaces |
347 | 43.5k | while (cursor.current() == ' ') |
348 | 10.4k | if (!cursor.advance(1)) |
349 | 0 | return State::Again; |
350 | | |
351 | | // Read the header value |
352 | 33.1k | start = cursor; |
353 | 751k | while (!cursor.eol()) |
354 | 719k | { |
355 | 719k | if (!cursor.advance(1)) |
356 | 584 | return State::Again; |
357 | 719k | } |
358 | | |
359 | 32.5k | if (Header::LowercaseEqualStatic(name, "cookie")) |
360 | 2.98k | { |
361 | 2.98k | message->cookies_.removeAllCookies(); // removing existing cookies before |
362 | | // re-adding them. |
363 | 2.98k | message->cookies_.addFromRaw(cursor.offset(start), cursor.diff(start)); |
364 | 2.98k | } |
365 | 29.5k | else if (Header::LowercaseEqualStatic(name, "set-cookie")) |
366 | 0 | { |
367 | 0 | message->cookies_.add( |
368 | 0 | Cookie::fromRaw(cursor.offset(start), cursor.diff(start))); |
369 | 0 | } |
370 | | |
371 | | // If the header is registered with the Registry, add its strongly |
372 | | // typed form to the headers list... |
373 | 29.5k | else if (Header::Registry::instance().isRegistered(name)) |
374 | 16.5k | { |
375 | 16.5k | std::shared_ptr<Header::Header> header = Header::Registry::instance().makeHeader(name); |
376 | 16.5k | header->parseRaw(cursor.offset(start), cursor.diff(start)); |
377 | 16.5k | message->headers_.add(header); |
378 | 16.5k | } |
379 | | |
380 | | // But also preserve a raw header version too, regardless of whether |
381 | | // its type was known to the Registry... |
382 | 32.5k | std::string value(cursor.offset(start), cursor.diff(start)); |
383 | 32.5k | message->headers_.addRaw(Header::Raw(std::move(name), std::move(value))); |
384 | | |
385 | | // CRLF |
386 | 32.5k | if (!cursor.advance(2)) |
387 | 0 | return State::Again; |
388 | | |
389 | 32.5k | headerRevert.ignore(); |
390 | 32.5k | } |
391 | | |
392 | 652 | if (!cursor.advance(2)) |
393 | 0 | return State::Again; |
394 | | |
395 | 652 | revert.ignore(); |
396 | 652 | return State::Next; |
397 | 652 | } |
398 | | |
399 | | State BodyStep::apply(StreamCursor& cursor) |
400 | 687 | { |
401 | 687 | auto cl = message->headers_.tryGet<Header::ContentLength>(); |
402 | 687 | auto te = message->headers_.tryGet<Header::TransferEncoding>(); |
403 | | |
404 | 687 | if (cl && te) |
405 | 2 | raise("Got mutually exclusive ContentLength and TransferEncoding header"); |
406 | | |
407 | 687 | if (cl) |
408 | 181 | return parseContentLength(cursor, cl); |
409 | | |
410 | 506 | if (te) |
411 | 423 | return parseTransferEncoding(cursor, te); |
412 | | |
413 | 83 | return State::Done; |
414 | 506 | } |
415 | | |
416 | | State BodyStep::parseContentLength( |
417 | | StreamCursor& cursor, const std::shared_ptr<Header::ContentLength>& cl) |
418 | 181 | { |
419 | 181 | auto contentLength = cl->value(); |
420 | | |
421 | 181 | auto readBody = [&](size_t size) { |
422 | 181 | StreamCursor::Token token(cursor); |
423 | 181 | const size_t available = cursor.remaining(); |
424 | | |
425 | | // We have an incomplete body, read what we can |
426 | 181 | if (available < size) |
427 | 164 | { |
428 | 164 | cursor.advance(available); |
429 | 164 | message->body_.append(token.rawText(), token.size()); |
430 | | |
431 | 164 | bytesRead += available; |
432 | | |
433 | 164 | return false; |
434 | 164 | } |
435 | | |
436 | 17 | cursor.advance(size); |
437 | 17 | message->body_.append(token.rawText(), token.size()); |
438 | 17 | return true; |
439 | 181 | }; |
440 | | |
441 | | // We already started to read some bytes but we got an incomplete payload |
442 | 181 | if (bytesRead > 0) |
443 | 74 | { |
444 | | // How many bytes do we still need to read ? |
445 | 74 | const size_t remaining = static_cast<size_t>( |
446 | 74 | contentLength - bytesRead); |
447 | 74 | if (!readBody(remaining)) |
448 | 74 | return State::Again; |
449 | 74 | } |
450 | | // This is the first time we are reading the payload |
451 | 107 | else |
452 | 107 | { |
453 | 107 | message->body_.reserve( |
454 | 107 | static_cast<unsigned int>(contentLength)); |
455 | 107 | if (!readBody(static_cast<size_t>(contentLength))) |
456 | 90 | return State::Again; |
457 | 107 | } |
458 | | |
459 | 17 | bytesRead = 0; |
460 | 17 | return State::Done; |
461 | 181 | } |
462 | | |
463 | | BodyStep::Chunk::Result BodyStep::Chunk::parse(StreamCursor& cursor) |
464 | 1.30k | { |
465 | 1.30k | if (size == -1) |
466 | 1.23k | { |
467 | 1.23k | StreamCursor::Revert revert(cursor); |
468 | 1.23k | StreamCursor::Token chunkSize(cursor); |
469 | | |
470 | 18.5k | while (!cursor.eol()) |
471 | 17.3k | if (!cursor.advance(1)) |
472 | 41 | return Incomplete; |
473 | | |
474 | 1.19k | const char* raw { chunkSize.rawText() }; |
475 | 1.19k | const auto* end { chunkSize.rawText() + chunkSize.size() }; |
476 | | |
477 | 1.19k | size_t sz = 0; |
478 | 1.19k | const auto parseResult = std::from_chars(raw, end, sz, 16); |
479 | | |
480 | 1.19k | if (parseResult.ec != std::errc {} || *parseResult.ptr != '\r') |
481 | 229 | throw std::runtime_error("Invalid chunk size"); |
482 | | |
483 | | // CRLF |
484 | 965 | if (!cursor.advance(2)) |
485 | 0 | return Incomplete; |
486 | | |
487 | 965 | revert.ignore(); |
488 | | |
489 | 965 | size = sz; |
490 | 965 | alreadyAppendedChunkBytes = 0; |
491 | 965 | } |
492 | | |
493 | 1.03k | if (size == 0) |
494 | 2 | return Final; |
495 | | |
496 | 1.03k | message->body_.reserve(size); |
497 | 1.03k | StreamCursor::Token chunkData(cursor); |
498 | 1.03k | const PST_SSIZE_T available = cursor.remaining(); |
499 | | |
500 | 1.03k | if (available + alreadyAppendedChunkBytes < size + 2) |
501 | 136 | { |
502 | 136 | cursor.advance(available); |
503 | 136 | message->body_.append(chunkData.rawText(), available); |
504 | 136 | alreadyAppendedChunkBytes += available; |
505 | 136 | return Incomplete; |
506 | 136 | } |
507 | 895 | cursor.advance(size - alreadyAppendedChunkBytes); |
508 | | |
509 | | // trailing EOL |
510 | 895 | cursor.advance(2); |
511 | | |
512 | 895 | message->body_.append(chunkData.rawText(), size - alreadyAppendedChunkBytes); |
513 | | |
514 | 895 | return Complete; |
515 | 1.03k | } |
516 | | |
517 | | State BodyStep::parseTransferEncoding( |
518 | | StreamCursor& cursor, const std::shared_ptr<Header::TransferEncoding>& te) |
519 | 423 | { |
520 | 423 | auto encoding = te->encoding(); |
521 | 423 | if (encoding == Http::Header::Encoding::Chunked) |
522 | 415 | { |
523 | 415 | Chunk::Result result; |
524 | 415 | try |
525 | 415 | { |
526 | 1.30k | while ((result = chunk.parse(cursor)) != Chunk::Final) |
527 | 1.07k | { |
528 | 1.07k | if (result == Chunk::Incomplete) |
529 | 177 | return State::Again; |
530 | | |
531 | 893 | chunk.reset(); |
532 | 893 | if (cursor.eof()) |
533 | 5 | return State::Again; |
534 | 893 | } |
535 | 233 | chunk.reset(); |
536 | 233 | } |
537 | 415 | catch (const std::exception& e) |
538 | 415 | { |
539 | | // reset chunk incase signal handled & chunk eventually reused |
540 | 231 | chunk.reset(); |
541 | 231 | raise(e.what()); |
542 | 231 | } |
543 | | |
544 | 2 | return State::Done; |
545 | 415 | } |
546 | 8 | else |
547 | 8 | { |
548 | 8 | raise("Unsupported Transfer-Encoding", Code::Not_Implemented); |
549 | 8 | } |
550 | | // raise defined with [[noreturn]], so compiler knows we cannot |
551 | | // reach here |
552 | 423 | } |
553 | | |
554 | | ParserBase::ParserBase(size_t maxDataSize) |
555 | 2.55k | : buffer(maxDataSize) |
556 | 2.55k | , cursor(&buffer) |
557 | 2.55k | { } |
558 | | |
559 | | State ParserBase::parse() |
560 | 4.22k | { |
561 | 4.22k | State state; |
562 | 4.22k | do |
563 | 6.51k | { |
564 | 6.51k | Step* step = allSteps[currentStep].get(); |
565 | 6.51k | state = step->apply(cursor); |
566 | 6.51k | if (state == State::Next) |
567 | 2.29k | { |
568 | 2.29k | ++currentStep; |
569 | 2.29k | } |
570 | 6.51k | } while (state == State::Next); |
571 | | |
572 | | // Should be either Again or Done |
573 | 4.22k | return state; |
574 | 4.22k | } |
575 | | |
576 | | bool ParserBase::feed(const char* data, size_t len) |
577 | 2.55k | { |
578 | 2.55k | return buffer.feed(data, len); |
579 | 2.55k | } |
580 | | |
581 | | void ParserBase::reset() |
582 | 0 | { |
583 | 0 | buffer.reset(); |
584 | 0 | cursor.reset(); |
585 | |
|
586 | 0 | currentStep = 0; |
587 | 0 | } |
588 | | |
589 | | Step* ParserBase::step() |
590 | 0 | { |
591 | 0 | return allSteps[currentStep].get(); |
592 | 0 | } |
593 | | |
594 | | } // namespace Private |
595 | | |
596 | | namespace Uri |
597 | | { |
598 | | |
599 | | Query::Query() |
600 | 2.29k | : params() |
601 | 2.29k | { } |
602 | | |
603 | | Query::Query( |
604 | | std::initializer_list<std::pair<const std::string, std::string>> params) |
605 | 0 | : params(params) |
606 | 0 | { } |
607 | | |
608 | | void Query::add(std::string name, std::string value) |
609 | 7.72k | { |
610 | 7.72k | params.insert(std::make_pair(std::move(name), std::move(value))); |
611 | 7.72k | } |
612 | | |
613 | | std::optional<std::string> Query::get(const std::string& name) const |
614 | 0 | { |
615 | 0 | auto it = params.find(name); |
616 | 0 | if (it == std::end(params)) |
617 | 0 | return std::nullopt; |
618 | | |
619 | 0 | return std::optional<std::string>(it->second); |
620 | 0 | } |
621 | | |
622 | | std::string Query::as_str() const |
623 | 0 | { |
624 | 0 | std::string query_url; |
625 | 0 | for (const auto& e : params) |
626 | 0 | { |
627 | 0 | query_url += "&" + e.first + "=" + e.second; |
628 | 0 | } |
629 | 0 | if (!query_url.empty()) |
630 | 0 | { |
631 | 0 | query_url[0] = '?'; // replace first `&` with `?` |
632 | 0 | } |
633 | 0 | return query_url; |
634 | 0 | } |
635 | | |
636 | | bool Query::has(const std::string& name) const |
637 | 0 | { |
638 | 0 | return params.find(name) != std::end(params); |
639 | 0 | } |
640 | | |
641 | | } // namespace Uri |
642 | | |
643 | | Message::Message(Version version) |
644 | 0 | : version_(version) |
645 | 0 | { } |
646 | | |
647 | 0 | Version Message::version() const { return version_; } |
648 | | |
649 | 0 | Code Message::code() const { return code_; } |
650 | | |
651 | 0 | const std::string& Message::body() const { return body_; } |
652 | | |
653 | 0 | std::string Message::body() { return body_; } |
654 | | |
655 | 0 | const Header::Collection& Message::headers() const { return headers_; } |
656 | | |
657 | 0 | Header::Collection& Message::headers() { return headers_; } |
658 | | |
659 | 0 | const CookieJar& Message::cookies() const { return cookies_; } |
660 | | |
661 | 0 | CookieJar& Message::cookies() { return cookies_; } |
662 | | |
663 | 0 | Method Request::method() const { return method_; } |
664 | | |
665 | 0 | const std::string& Request::resource() const { return resource_; } |
666 | | |
667 | 0 | const Uri::Query& Request::query() const { return query_; } |
668 | | |
669 | 0 | const Address& Request::address() const { return address_; } |
670 | | |
671 | 0 | std::chrono::milliseconds Request::timeout() const { return timeout_; } |
672 | | |
673 | | Header::Encoding Request::getBestAcceptEncoding() const |
674 | 0 | { |
675 | 0 | const auto& maybe_header = headers().tryGet<Header::AcceptEncoding>(); |
676 | 0 | if (maybe_header == nullptr) |
677 | 0 | { |
678 | 0 | return Header::Encoding::Identity; |
679 | 0 | } |
680 | | |
681 | 0 | const auto& header = *maybe_header; |
682 | |
|
683 | 0 | for (const auto& encoding : header.encodings()) |
684 | 0 | { |
685 | | // If the qvalue is 0, the encoding is not supported by the client |
686 | 0 | if (encodingSupported(encoding.first) && encoding.second != 0) |
687 | 0 | { |
688 | 0 | return encoding.first; |
689 | 0 | } |
690 | 0 | } |
691 | | |
692 | 0 | return Header::Encoding::Identity; |
693 | 0 | } |
694 | | |
695 | | Response::Response(Version version) |
696 | 0 | : Message(version) |
697 | 0 | { } |
698 | | |
699 | | #ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME |
700 | | std::shared_ptr<Tcp::Peer> Request::peer() const |
701 | | { |
702 | | auto p = peer_.lock(); |
703 | | |
704 | | if (!p) |
705 | | throw std::runtime_error("Failed to retrieve peer: Broken pipe"); |
706 | | |
707 | | return p; |
708 | | } |
709 | | #endif |
710 | | |
711 | | ResponseStream::ResponseStream(ResponseStream&& other) |
712 | 0 | : response_(std::move(other.response_)) |
713 | 0 | , peer_(std::move(other.peer_)) |
714 | 0 | , buf_(std::move(other.buf_)) |
715 | 0 | , transport_(other.transport_) |
716 | 0 | , timeout_(std::move(other.timeout_)) |
717 | 0 | { } |
718 | | |
719 | | ResponseStream::ResponseStream(Message&& other, std::weak_ptr<Tcp::Peer> peer, |
720 | | Tcp::Transport* transport, Timeout timeout, |
721 | | size_t streamSize, size_t maxResponseSize) |
722 | 0 | : response_(std::move(other)) |
723 | 0 | , peer_(std::move(peer)) |
724 | 0 | , buf_(streamSize, maxResponseSize) |
725 | 0 | , transport_(transport) |
726 | 0 | , timeout_(std::move(timeout)) |
727 | 0 | { |
728 | 0 | if (!writeStatusLine(response_.version(), response_.code(), buf_)) |
729 | 0 | throw Error("Response exceeded buffer size"); |
730 | | |
731 | 0 | if (!writeCookies(response_.cookies(), buf_)) |
732 | 0 | { |
733 | 0 | throw Error("Response exceeded buffer size"); |
734 | 0 | } |
735 | | |
736 | 0 | if (writeHeaders(response_.headers(), buf_)) |
737 | 0 | { |
738 | 0 | std::ostream os(&buf_); |
739 | | /* @Todo @Major: |
740 | | * Correctly handle non-keep alive requests |
741 | | * Do not put Keep-Alive if version == Http::11 and request.keepAlive == |
742 | | * true |
743 | | */ |
744 | | // writeHeader<Header::Connection>(os, ConnectionControl::KeepAlive); |
745 | | // if (!os) throw Error("Response exceeded buffer size"); |
746 | 0 | writeHeader<Header::TransferEncoding>(os, Header::Encoding::Chunked); |
747 | 0 | if (!os) |
748 | 0 | throw Error("Response exceeded buffer size"); |
749 | 0 | os << crlf; |
750 | 0 | } |
751 | 0 | } |
752 | | |
753 | | ResponseStream& ResponseStream::operator=(ResponseStream&& other) |
754 | 0 | { |
755 | 0 | response_ = std::move(other.response_); |
756 | 0 | peer_ = std::move(other.peer_); |
757 | 0 | buf_ = std::move(other.buf_); |
758 | 0 | transport_ = other.transport_; |
759 | 0 | timeout_ = std::move(other.timeout_); |
760 | |
|
761 | 0 | return *this; |
762 | 0 | } |
763 | | |
764 | | std::streamsize ResponseStream::write(const char* data, std::streamsize sz) |
765 | 0 | { |
766 | 0 | std::ostream os(&buf_); |
767 | 0 | os << std::hex << sz << crlf; |
768 | 0 | os.write(data, sz); |
769 | 0 | os << crlf; |
770 | 0 | return sz; |
771 | 0 | } |
772 | | |
773 | | std::shared_ptr<Tcp::Peer> ResponseStream::peer() const |
774 | 0 | { |
775 | 0 | if (peer_.expired()) |
776 | 0 | { |
777 | 0 | throw std::runtime_error("Write failed: Broken pipe"); |
778 | 0 | } |
779 | | |
780 | 0 | return peer_.lock(); |
781 | 0 | } |
782 | | |
783 | | void ResponseStream::flush() |
784 | 0 | { |
785 | 0 | timeout_.disarm(); |
786 | 0 | auto buf = buf_.buffer(); |
787 | |
|
788 | 0 | auto fd = peer()->fd(); |
789 | 0 | transport_->asyncWrite(fd, buf); |
790 | | |
791 | | // Calling transport_->flush from here is unnecessary - we already |
792 | | // placed the write on the transport's writesQueue with the call to |
793 | | // asyncWrite directly above; the transport will send just as soon as |
794 | | // the fd becomes writable. |
795 | | // |
796 | | // Calling transport_->flush is also dangerous - writesQueue is |
797 | | // supposed to be single consumer queue, but calling flush here |
798 | | // initiates a pop from a different thread (i.e. from our thread |
799 | | // here). This can cause queue corruption if both our thread here and |
800 | | // the Pistache consumer thread are popping at the same moment; see |
801 | | // Issue #1290. |
802 | | // |
803 | | // transport_->flush(); |
804 | |
|
805 | 0 | buf_.clear(); |
806 | 0 | } |
807 | | |
808 | | void ResponseStream::ends() |
809 | 0 | { |
810 | 0 | std::ostream os(&buf_); |
811 | 0 | os << "0" << crlf; |
812 | 0 | os << crlf; |
813 | |
|
814 | 0 | if (!os) |
815 | 0 | { |
816 | 0 | throw Error("Response exceeded buffer size"); |
817 | 0 | } |
818 | | |
819 | 0 | flush(); |
820 | 0 | } |
821 | | |
822 | | ResponseWriter::ResponseWriter(ResponseWriter&& other) |
823 | 0 | : response_(std::move(other.response_)) |
824 | 0 | , peer_(other.peer_) |
825 | 0 | , buf_(std::move(other.buf_)) |
826 | 0 | , transport_(other.transport_) |
827 | 0 | , timeout_(std::move(other.timeout_)) |
828 | 0 | { } |
829 | | |
830 | | ResponseWriter::ResponseWriter(Http::Version version, Tcp::Transport* transport, |
831 | | Handler* handler, std::weak_ptr<Tcp::Peer> peer) |
832 | 0 | : response_(version) |
833 | 0 | , peer_(peer) |
834 | 0 | , buf_(DefaultStreamSize, handler->getMaxResponseSize()) |
835 | 0 | , transport_(transport) |
836 | 0 | , timeout_(transport, version, handler, peer) |
837 | 0 | { } |
838 | | |
839 | | ResponseWriter::ResponseWriter(const ResponseWriter& other) |
840 | 0 | : response_(other.response_) |
841 | 0 | , peer_(other.peer_) |
842 | 0 | , buf_(DefaultStreamSize, other.buf_.maxSize()) |
843 | 0 | , transport_(other.transport_) |
844 | 0 | , timeout_(other.timeout_) |
845 | 0 | { } |
846 | | |
847 | | void ResponseWriter::setMime(const Mime::MediaType& mime) |
848 | 0 | { |
849 | 0 | auto ct = response_.headers().tryGet<Header::ContentType>(); |
850 | 0 | if (ct) |
851 | 0 | { |
852 | 0 | ct->setMime(mime); |
853 | 0 | } |
854 | 0 | else |
855 | 0 | { |
856 | 0 | response_.headers().add(std::make_shared<Header::ContentType>(mime)); |
857 | 0 | } |
858 | 0 | } |
859 | | |
860 | | Async::Promise<PST_SSIZE_T> ResponseWriter::sendMethodNotAllowed( |
861 | | const std::vector<Http::Method>& supportedMethods) |
862 | 0 | { |
863 | 0 | response_.code_ = Http::Code::Method_Not_Allowed; |
864 | 0 | response_.headers().add( |
865 | 0 | std::make_shared<Http::Header::Allow>(supportedMethods)); |
866 | 0 | const std::string& body = codeString(Pistache::Http::Code::Method_Not_Allowed); |
867 | 0 | return putOnWire(body.c_str(), body.size()); |
868 | 0 | } |
869 | | |
870 | | Async::Promise<PST_SSIZE_T> ResponseWriter::send(Code code, const std::string& body, |
871 | | const Mime::MediaType& mime) |
872 | 0 | { |
873 | 0 | return sendImpl(code, body.c_str(), body.size(), mime); |
874 | 0 | } |
875 | | |
876 | | Async::Promise<PST_SSIZE_T> ResponseWriter::send(Code code, const char* data, |
877 | | const size_t size, |
878 | | const Mime::MediaType& mime) |
879 | 0 | { |
880 | 0 | return sendImpl(code, data, size, mime); |
881 | 0 | } |
882 | | |
883 | | Async::Promise<PST_SSIZE_T> ResponseWriter::sendImpl(Code code, const char* data, |
884 | | const size_t size, |
885 | | const Mime::MediaType& mime) |
886 | 0 | { |
887 | 0 | if (!peer_.expired()) |
888 | 0 | { |
889 | 0 | auto curPeer = peer_.lock(); |
890 | 0 | curPeer->setIdle(true); // change peer state to idle |
891 | | |
892 | | // It will result in double free |
893 | | // Http::Handler::getParser(curPeer)->reset(); // reset the timeout time |
894 | 0 | } |
895 | |
|
896 | 0 | response_.code_ = code; |
897 | |
|
898 | 0 | if (mime.isValid()) |
899 | 0 | { |
900 | 0 | auto contentType = headers().tryGet<Header::ContentType>(); |
901 | 0 | if (contentType) |
902 | 0 | { |
903 | 0 | contentType->setMime(mime); |
904 | 0 | } |
905 | 0 | else |
906 | 0 | { |
907 | 0 | headers().add(std::make_shared<Header::ContentType>(mime)); |
908 | 0 | } |
909 | 0 | } |
910 | | |
911 | | // Compress data, if necessary, before sending over wire to user... |
912 | 0 | switch (contentEncoding_) |
913 | 0 | { |
914 | | |
915 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_BROTLI |
916 | | // User requested Brotli compression... |
917 | | case Http::Header::Encoding::Br: { |
918 | | |
919 | | // Location for size of compressed buffer, initially set to upper |
920 | | // bound on the data after its been compressed... |
921 | | size_t compressedSize = ::BrotliEncoderMaxCompressedSize(size); |
922 | | |
923 | | // Failed... |
924 | | if (compressedSize == 0) |
925 | | throw std::runtime_error("BrotliEncoderMaxCompressedSize() failed"); |
926 | | |
927 | | // Allocate a smart buffer to contain compressed data... |
928 | | std::unique_ptr compressedData = std::make_unique<std::byte[]>(compressedSize); |
929 | | |
930 | | // Compress data. The encoder expects compressedSize to initially be |
931 | | // the size of the output buffer. After it completes writing it |
932 | | // will update its value to reflect actual size used... |
933 | | const auto compressionStatus = ::BrotliEncoderCompress( |
934 | | contentEncodingBrotliLevel_, |
935 | | BROTLI_DEFAULT_WINDOW, |
936 | | BROTLI_DEFAULT_MODE, |
937 | | size, |
938 | | reinterpret_cast<const uint8_t*>(data), |
939 | | &compressedSize, |
940 | | reinterpret_cast<uint8_t*>(compressedData.get())); |
941 | | |
942 | | // Failed... |
943 | | if (compressionStatus != BROTLI_TRUE) |
944 | | throw std::runtime_error("BrotliEncoderCompress() failed"); |
945 | | |
946 | | // Notify client to expect Brotli compressed response... |
947 | | headers().add<Http::Header::ContentEncoding>( |
948 | | Http::Header::Encoding::Br); |
949 | | |
950 | | // Send compressed data back to client... |
951 | | return putOnWire( |
952 | | reinterpret_cast<const char*>(compressedData.get()), |
953 | | compressedSize); |
954 | | } |
955 | | #endif |
956 | | |
957 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_ZSTD |
958 | | |
959 | | case Http::Header::Encoding::Zstd: { |
960 | | size_t estimated_size = ZSTD_compressBound(size); |
961 | | // Allocate a smart buffer to contain compressed data... |
962 | | std::unique_ptr compressedData = std::make_unique<std::byte[]>(estimated_size); |
963 | | |
964 | | auto compress_size = ZSTD_compress(reinterpret_cast<void*>(compressedData.get()), estimated_size, |
965 | | data, size, contentEncodingZstdLevel_); |
966 | | if (ZSTD_isError(compress_size)) |
967 | | { |
968 | | throw std::runtime_error( |
969 | | std::string("failed to compress data to ZSTD on ZSTD_compress(), returning: ") + std::to_string(compress_size)); |
970 | | } |
971 | | headers().add<Http::Header::ContentEncoding>( |
972 | | Http::Header::Encoding::Zstd); |
973 | | |
974 | | // Send compressed data back to client... |
975 | | return putOnWire( |
976 | | reinterpret_cast<const char*>(compressedData.get()), |
977 | | compress_size); |
978 | | } |
979 | | |
980 | | #endif |
981 | | |
982 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_DEFLATE |
983 | | // User requested deflate compression... |
984 | | case Http::Header::Encoding::Deflate: { |
985 | | |
986 | | // Compute upper bound on size of expected compressed data. This |
987 | | // will be updated by compress2()... |
988 | | uLongf compressedSize = static_cast<uLongf>(::compressBound(static_cast<uLong>(size))); |
989 | | |
990 | | // Allocate a smart buffer to contain compressed data... |
991 | | std::unique_ptr compressedData = std::make_unique<std::byte[]>(compressedSize); |
992 | | |
993 | | // Compress user data at requested level... |
994 | | const auto compressionStatus = ::compress2( |
995 | | reinterpret_cast<unsigned char*>(compressedData.get()), |
996 | | &compressedSize, |
997 | | reinterpret_cast<const unsigned char*>(data), |
998 | | static_cast<uLong>(size), |
999 | | contentEncodingDeflateLevel_); |
1000 | | |
1001 | | // Failed... |
1002 | | if (compressionStatus != Z_OK) |
1003 | | throw std::runtime_error( |
1004 | | std::string("compress2() failed, returning: ") + std::to_string(compressionStatus)); |
1005 | | |
1006 | | // Notify client to expect deflate compressed response... |
1007 | | headers().add<Http::Header::ContentEncoding>( |
1008 | | Http::Header::Encoding::Deflate); |
1009 | | |
1010 | | // Send compressed data back to client... |
1011 | | return putOnWire( |
1012 | | reinterpret_cast<const char*>(compressedData.get()), |
1013 | | compressedSize); |
1014 | | } |
1015 | | #endif |
1016 | | // No compression requested. Send uncompressed data to client... |
1017 | 0 | case Http::Header::Encoding::Identity: |
1018 | 0 | return putOnWire(data, size); |
1019 | | |
1020 | | // Unknown... |
1021 | 0 | default: |
1022 | 0 | throw std::runtime_error("User requested unknown content encoding."); |
1023 | 0 | } |
1024 | 0 | } |
1025 | | |
1026 | | ResponseStream ResponseWriter::stream(Code code, size_t streamSize) |
1027 | 0 | { |
1028 | 0 | response_.code_ = code; |
1029 | |
|
1030 | 0 | return ResponseStream(std::move(response_), peer_, transport_, |
1031 | 0 | std::move(timeout_), streamSize, buf_.maxSize()); |
1032 | 0 | } |
1033 | | |
1034 | 0 | const CookieJar& ResponseWriter::cookies() const { return response_.cookies(); } |
1035 | | |
1036 | 0 | CookieJar& ResponseWriter::cookies() { return response_.cookies(); } |
1037 | | |
1038 | | const Header::Collection& ResponseWriter::headers() const |
1039 | 0 | { |
1040 | 0 | return response_.headers(); |
1041 | 0 | } |
1042 | | |
1043 | 0 | Header::Collection& ResponseWriter::headers() { return response_.headers(); } |
1044 | | |
1045 | 0 | Timeout& ResponseWriter::timeout() { return timeout_; } |
1046 | | |
1047 | | std::shared_ptr<Tcp::Peer> ResponseWriter::peer() const |
1048 | 0 | { |
1049 | 0 | if (peer_.expired()) |
1050 | 0 | { |
1051 | 0 | throw std::runtime_error("Write failed: Broken pipe"); |
1052 | 0 | } |
1053 | | |
1054 | 0 | return peer_.lock(); |
1055 | 0 | } |
1056 | | |
1057 | 0 | DynamicStreamBuf* ResponseWriter::rdbuf() { return &buf_; } |
1058 | | |
1059 | | DynamicStreamBuf* ResponseWriter::rdbuf(DynamicStreamBuf* /*other*/) |
1060 | 0 | { |
1061 | 0 | throw std::domain_error("Unimplemented"); |
1062 | 0 | } |
1063 | | |
1064 | 0 | ResponseWriter ResponseWriter::clone() const { return ResponseWriter(*this); } |
1065 | | |
1066 | | Async::Promise<PST_SSIZE_T> ResponseWriter::putOnWire(const char* data, |
1067 | | size_t len) |
1068 | 0 | { |
1069 | 0 | try |
1070 | 0 | { |
1071 | 0 | std::ostream os(&buf_); |
1072 | |
|
1073 | 0 | #define PST_OUT(...) \ |
1074 | 0 | do \ |
1075 | 0 | { \ |
1076 | 0 | __VA_ARGS__; \ |
1077 | 0 | if (!os) \ |
1078 | 0 | { \ |
1079 | 0 | return Async::Promise<PST_SSIZE_T>::rejected( \ |
1080 | 0 | Error("Response exceeded buffer size")); \ |
1081 | 0 | } \ |
1082 | 0 | } while (0); |
1083 | |
|
1084 | 0 | PST_OUT(writeStatusLine(response_.version(), response_.code(), buf_)); |
1085 | 0 | PST_OUT(writeHeaders(response_.headers(), buf_)); |
1086 | 0 | PST_OUT(writeCookies(response_.cookies(), buf_)); |
1087 | | |
1088 | | /* @Todo @Major: |
1089 | | * Correctly handle non-keep alive requests |
1090 | | * Do not put Keep-Alive if version == Http::11 and request.keepAlive == |
1091 | | * true |
1092 | | */ |
1093 | | // PST_OUT(writeHeader<Header::Connection>(os, ConnectionControl::KeepAlive)); |
1094 | 0 | PST_OUT(writeHeader<Header::ContentLength>(os, len)); |
1095 | |
|
1096 | 0 | PST_OUT(os << crlf); |
1097 | |
|
1098 | 0 | if (len > 0) |
1099 | 0 | { |
1100 | 0 | PST_OUT(os.write(data, len)); |
1101 | 0 | } |
1102 | | |
1103 | 0 | auto buffer = buf_.buffer(); |
1104 | 0 | sent_bytes_ += buffer.size(); |
1105 | |
|
1106 | 0 | timeout_.disarm(); |
1107 | |
|
1108 | 0 | #undef PST_OUT |
1109 | |
|
1110 | 0 | auto fd = peer()->fd(); |
1111 | |
|
1112 | 0 | return transport_->asyncWrite(fd, buffer) |
1113 | 0 | .then<std::function<Async::Promise<PST_SSIZE_T>(PST_SSIZE_T)>, |
1114 | 0 | std::function<void(std::exception_ptr&)>>( |
1115 | 0 | [](PST_SSIZE_T data) { |
1116 | 0 | return Async::Promise<PST_SSIZE_T>::resolved(data); |
1117 | 0 | }, |
1118 | |
|
1119 | 0 | [](std::exception_ptr& eptr) { |
1120 | 0 | return Async::Promise<PST_SSIZE_T>::rejected(eptr); |
1121 | 0 | }); |
1122 | 0 | } |
1123 | 0 | catch (const std::runtime_error& e) |
1124 | 0 | { |
1125 | 0 | return Async::Promise<PST_SSIZE_T>::rejected(e); |
1126 | 0 | } |
1127 | 0 | } |
1128 | | |
1129 | | // Compress using the requested content encoding, if supported, before |
1130 | | // sending bits to client. User responsible for setting Content-Encoding |
1131 | | // header... |
1132 | | void ResponseWriter::setCompression(const Pistache::Http::Header::Encoding _contentEncoding) |
1133 | 0 | { |
1134 | 0 | switch (_contentEncoding) |
1135 | 0 | { |
1136 | | |
1137 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_BROTLI |
1138 | | // Application requested Brotli compression... |
1139 | | case Http::Header::Encoding::Br: |
1140 | | contentEncoding_ = Http::Header::Encoding::Br; |
1141 | | break; |
1142 | | #endif |
1143 | | |
1144 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_ZSTD |
1145 | | case Http::Header::Encoding::Zstd: |
1146 | | contentEncoding_ = Http::Header::Encoding::Zstd; |
1147 | | break; |
1148 | | #endif |
1149 | | |
1150 | | #ifdef PISTACHE_USE_CONTENT_ENCODING_DEFLATE |
1151 | | // Application requested deflate compression... |
1152 | | case Http::Header::Encoding::Deflate: |
1153 | | contentEncoding_ = Http::Header::Encoding::Deflate; |
1154 | | break; |
1155 | | #endif |
1156 | | |
1157 | | // Application requested identity encoding which means no compression... |
1158 | 0 | case Http::Header::Encoding::Identity: |
1159 | 0 | contentEncoding_ = Http::Header::Encoding::Identity; |
1160 | 0 | break; |
1161 | | |
1162 | | // Any other type is not supported... |
1163 | 0 | default: |
1164 | 0 | throw std::runtime_error("Unsupported content encoding compression requested."); |
1165 | 0 | } |
1166 | 0 | } |
1167 | | |
1168 | | Async::Promise<PST_SSIZE_T> serveFile(ResponseWriter& writer, |
1169 | | const std::string& fileName, |
1170 | | const Mime::MediaType& contentType) |
1171 | 0 | { |
1172 | 0 | struct stat sb; |
1173 | |
|
1174 | 0 | int fd = PST_FILE_OPEN(fileName.c_str(), PST_O_RDONLY); |
1175 | 0 | if (fd == -1) |
1176 | 0 | { |
1177 | 0 | PST_DECL_SE_ERR_P_EXTRA; |
1178 | 0 | std::string str_error(PST_STRERROR_R_ERRNO); |
1179 | 0 | if (errno == ENOENT) |
1180 | 0 | { |
1181 | 0 | throw HttpError(Http::Code::Not_Found, std::move(str_error)); |
1182 | 0 | } |
1183 | | // eles if TODO |
1184 | | /* @Improvement: maybe could we check for errno here and emit a different |
1185 | | error message |
1186 | | */ |
1187 | 0 | else |
1188 | 0 | { |
1189 | 0 | throw HttpError(Http::Code::Internal_Server_Error, std::move(str_error)); |
1190 | 0 | } |
1191 | 0 | } |
1192 | | |
1193 | 0 | int res = ::fstat(fd, &sb); |
1194 | |
|
1195 | 0 | PST_FILE_CLOSE(fd); // Done with fd, close before error can be thrown |
1196 | 0 | if (res == -1) |
1197 | 0 | { |
1198 | 0 | throw HttpError(Code::Internal_Server_Error, ""); |
1199 | 0 | } |
1200 | | |
1201 | 0 | auto* buf = writer.rdbuf(); |
1202 | |
|
1203 | 0 | std::ostream os(buf); |
1204 | |
|
1205 | 0 | #define PST_OUT(...) \ |
1206 | 0 | do \ |
1207 | 0 | { \ |
1208 | 0 | __VA_ARGS__; \ |
1209 | 0 | if (!os) \ |
1210 | 0 | { \ |
1211 | 0 | return Async::Promise<PST_SSIZE_T>::rejected( \ |
1212 | 0 | Error("Response exceeded buffer size")); \ |
1213 | 0 | } \ |
1214 | 0 | } while (0); |
1215 | |
|
1216 | 0 | auto setContentType = [&](const Mime::MediaType& contentType) { |
1217 | 0 | auto& headers = writer.headers(); |
1218 | 0 | auto ct = headers.tryGet<Header::ContentType>(); |
1219 | 0 | if (ct) |
1220 | 0 | ct->setMime(contentType); |
1221 | 0 | else |
1222 | 0 | headers.add<Header::ContentType>(contentType); |
1223 | 0 | }; |
1224 | |
|
1225 | 0 | PST_OUT(writeStatusLine(writer.response_.version(), Http::Code::Ok, *buf)); |
1226 | 0 | if (contentType.isValid()) |
1227 | 0 | { |
1228 | 0 | setContentType(contentType); |
1229 | 0 | } |
1230 | 0 | else |
1231 | 0 | { |
1232 | 0 | auto mime = Mime::MediaType::fromFile(fileName.c_str()); |
1233 | 0 | if (mime.isValid()) |
1234 | 0 | setContentType(mime); |
1235 | 0 | } |
1236 | |
|
1237 | 0 | PST_OUT(writeHeaders(writer.headers(), *buf)); |
1238 | |
|
1239 | 0 | const size_t len = static_cast<size_t>(sb.st_size); |
1240 | |
|
1241 | 0 | PST_OUT(writeHeader<Header::ContentLength>(os, len)); |
1242 | |
|
1243 | 0 | PST_OUT(os << crlf); |
1244 | |
|
1245 | 0 | auto* transport = writer.transport_; |
1246 | 0 | auto peer = writer.peer(); |
1247 | 0 | auto sockFd = peer->fd(); // may be PS_FD_EMPTY |
1248 | |
|
1249 | 0 | auto buffer = buf->buffer(); |
1250 | 0 | return transport->asyncWrite(sockFd, buffer, |
1251 | | #ifdef _USE_LIBEVENT_LIKE_APPLE |
1252 | | 0, // MSG_MORE unsupported in macos sendmsg |
1253 | | // Instead, we set TCP_NOPUSH via |
1254 | | // setsockopt (see "man tcp"). |
1255 | | true // use msg_more_style |
1256 | | #else |
1257 | 0 | MSG_MORE |
1258 | 0 | #endif |
1259 | 0 | ) |
1260 | 0 | .then( |
1261 | 0 | [=](PST_SSIZE_T) { |
1262 | 0 | return transport->asyncWrite(sockFd, FileBuffer(fileName)); |
1263 | 0 | }, |
1264 | 0 | Async::Throw); |
1265 | |
|
1266 | 0 | #undef PST_OUT |
1267 | 0 | } |
1268 | | |
1269 | | Private::ParserImpl<Http::Request>::ParserImpl(size_t maxDataSize) |
1270 | 2.27k | : ParserBase(maxDataSize) |
1271 | 2.27k | , request() |
1272 | 2.27k | , time_(std::chrono::steady_clock::now()) |
1273 | 2.27k | { |
1274 | 2.27k | allSteps[0] = std::make_unique<RequestLineStep>(&request); |
1275 | 2.27k | allSteps[1] = std::make_unique<HeadersStep>(&request); |
1276 | 2.27k | allSteps[2] = std::make_unique<BodyStep>(&request); |
1277 | 2.27k | } |
1278 | | |
1279 | | void Private::ParserImpl<Http::Request>::reset() |
1280 | 0 | { |
1281 | 0 | ParserBase::reset(); |
1282 | |
|
1283 | 0 | request = Request(); |
1284 | 0 | time_ = std::chrono::steady_clock::now(); |
1285 | 0 | } |
1286 | | |
1287 | | Private::ParserImpl<Http::Response>::ParserImpl(size_t maxDataSize) |
1288 | 279 | : ParserBase(maxDataSize) |
1289 | 279 | , response() |
1290 | 279 | { |
1291 | 279 | allSteps[0] = std::make_unique<ResponseLineStep>(&response); |
1292 | 279 | allSteps[1] = std::make_unique<HeadersStep>(&response); |
1293 | 279 | allSteps[2] = std::make_unique<BodyStep>(&response); |
1294 | 279 | } |
1295 | | |
1296 | | void Handler::onInput(const char* buffer, size_t len, |
1297 | | const std::shared_ptr<Tcp::Peer>& peer) |
1298 | 0 | { |
1299 | 0 | PS_TIMEDBG_START_ARGS("input len %u", len); |
1300 | |
|
1301 | 0 | auto parser = getParser(peer); |
1302 | 0 | auto& request = parser->request; |
1303 | 0 | try |
1304 | 0 | { |
1305 | 0 | if (!parser->feed(buffer, len)) |
1306 | 0 | { |
1307 | 0 | PS_LOG_DEBUG("parser returned false"); |
1308 | |
|
1309 | 0 | parser->reset(); |
1310 | 0 | throw HttpError(Code::Request_Entity_Too_Large, |
1311 | 0 | "Request exceeded maximum buffer size"); |
1312 | 0 | } |
1313 | | |
1314 | 0 | auto state = parser->parse(); |
1315 | |
|
1316 | 0 | if (state == Private::State::Done) |
1317 | 0 | { |
1318 | 0 | PS_LOG_DEBUG("Creating response"); |
1319 | |
|
1320 | 0 | ResponseWriter response(request.version(), transport(), this, peer); |
1321 | |
|
1322 | | #ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME |
1323 | | request.associatePeer(peer); |
1324 | | #endif |
1325 | |
|
1326 | 0 | request.copyAddress(peer->address()); |
1327 | |
|
1328 | 0 | auto connection = request.headers().tryGet<Header::Connection>(); |
1329 | |
|
1330 | 0 | if (connection) |
1331 | 0 | { |
1332 | 0 | PS_LOG_DEBUG("Response connection control"); |
1333 | |
|
1334 | 0 | response.headers().add<Header::Connection>(connection->control()); |
1335 | 0 | } |
1336 | 0 | else |
1337 | 0 | { |
1338 | 0 | PS_LOG_DEBUG("Response connection close"); |
1339 | |
|
1340 | 0 | response.headers().add<Header::Connection>(ConnectionControl::Close); |
1341 | 0 | } |
1342 | |
|
1343 | 0 | PS_LOG_DEBUG("Calling peer->setIdle"); |
1344 | 0 | peer->setIdle(false); // change peer state to not idle |
1345 | |
|
1346 | 0 | PS_LOG_DEBUG("Calling onRequest"); |
1347 | 0 | onRequest(request, std::move(response)); |
1348 | |
|
1349 | 0 | PS_LOG_DEBUG("Calling parser->reset"); |
1350 | 0 | parser->reset(); |
1351 | 0 | } |
1352 | 0 | } |
1353 | 0 | catch (const HttpError& err) |
1354 | 0 | { |
1355 | 0 | PS_LOG_DEBUG("HTTP Error"); |
1356 | |
|
1357 | 0 | ResponseWriter response(request.version(), transport(), this, peer); |
1358 | 0 | response.send(static_cast<Code>(err.code()), err.reason()); |
1359 | 0 | parser->reset(); |
1360 | 0 | } |
1361 | |
|
1362 | 0 | catch (const std::exception& e) |
1363 | 0 | { |
1364 | 0 | PS_LOG_DEBUG("HTTP exception"); |
1365 | |
|
1366 | 0 | ResponseWriter response(request.version(), transport(), this, peer); |
1367 | 0 | response.send(Code::Internal_Server_Error, e.what()); |
1368 | 0 | parser->reset(); |
1369 | 0 | } |
1370 | 0 | } |
1371 | | |
1372 | | void Handler::onConnection(const std::shared_ptr<Tcp::Peer>& peer) |
1373 | 0 | { |
1374 | 0 | peer->putData(ParserData, std::make_shared<RequestParser>(maxRequestSize_)); |
1375 | 0 | } |
1376 | | |
1377 | | void Handler::onTimeout(const Request& /*request*/, |
1378 | | ResponseWriter response) |
1379 | 0 | { |
1380 | 0 | response.send(Code::Request_Timeout); |
1381 | 0 | } |
1382 | | |
1383 | 0 | Timeout::~Timeout() { disarm(); } |
1384 | | |
1385 | | void Timeout::disarm() |
1386 | 0 | { |
1387 | 0 | if (transport && armed) |
1388 | 0 | { |
1389 | 0 | transport->disarmTimer(timerFd); |
1390 | 0 | } |
1391 | 0 | } |
1392 | | |
1393 | 0 | bool Timeout::isArmed() const { return armed; } |
1394 | | |
1395 | | Timeout::Timeout(Tcp::Transport* transport_, Http::Version version, Handler* handler_, |
1396 | | std::weak_ptr<Tcp::Peer> peer_) |
1397 | 0 | : handler(handler_) |
1398 | 0 | , version(version) |
1399 | 0 | , transport(transport_) |
1400 | 0 | , armed(false) |
1401 | 0 | , timerFd(PS_FD_EMPTY) |
1402 | 0 | , peer(peer_) |
1403 | 0 | { } |
1404 | | |
1405 | | void Timeout::onTimeout(uint64_t /*numWakeup*/) |
1406 | 0 | { |
1407 | 0 | auto sp = peer.lock(); |
1408 | 0 | if (!sp) |
1409 | 0 | return; |
1410 | | |
1411 | 0 | ResponseWriter response(version, transport, handler, peer); |
1412 | 0 | auto parser = Handler::getParser(sp); |
1413 | 0 | const auto& request = parser->request; |
1414 | 0 | handler->onTimeout(request, std::move(response)); |
1415 | 0 | } |
1416 | | |
1417 | 0 | void Handler::setMaxRequestSize(size_t value) { maxRequestSize_ = value; } |
1418 | | |
1419 | 0 | size_t Handler::getMaxRequestSize() const { return maxRequestSize_; } |
1420 | | |
1421 | 0 | void Handler::setMaxResponseSize(size_t value) { maxResponseSize_ = value; } |
1422 | | |
1423 | 0 | size_t Handler::getMaxResponseSize() const { return maxResponseSize_; } |
1424 | | |
1425 | | std::shared_ptr<RequestParser> |
1426 | | Handler::getParser(const std::shared_ptr<Tcp::Peer>& peer) |
1427 | 0 | { |
1428 | 0 | return std::static_pointer_cast<RequestParser>(peer->getData(ParserData)); |
1429 | 0 | } |
1430 | | |
1431 | | } // namespace Pistache::Http |