Line data Source code
1 : #include "../../util/logger.hpp"
2 : #include "../../util/compression.hpp"
3 : #include <utility>
4 :
5 : #include "request.hpp"
6 : #include "routing/route.hpp"
7 :
8 : namespace thinger::http{
9 :
10 : using nlohmann::json;
11 : using std::string;
12 :
13 930 : request::request(
14 : const std::shared_ptr<server_connection>& http_connection,
15 : const std::shared_ptr<http_stream>& http_stream,
16 930 : std::shared_ptr<http_request> http_request) :
17 930 : http_connection_{http_connection},
18 930 : http_stream_{http_stream},
19 1860 : http_request_{std::move(http_request)}
20 : {
21 :
22 930 : }
23 :
24 930 : request::~request()= default;
25 :
26 234 : const string& request::operator[](const std::string& param) const{
27 234 : return get_uri_parameter(param);
28 : }
29 :
30 12 : bool request::has(const std::string& param) const {
31 12 : return params_.contains(param);
32 : }
33 :
34 2 : bool request::erase(const std::string& param){
35 2 : return params_.erase(param)>0;
36 : }
37 :
38 2063 : std::shared_ptr<http_request> request::get_http_request(){
39 2063 : return http_request_;
40 : }
41 :
42 1041 : std::shared_ptr<server_connection> request::get_http_connection() const {
43 1041 : return http_connection_.lock();
44 : }
45 :
46 929 : std::shared_ptr<http_stream> request::get_http_stream() const {
47 929 : return http_stream_.lock();
48 : }
49 :
50 : /*
51 : void request::add_matched_param(const std::string& param){
52 : uri_params_.emplace_back(param);
53 : }
54 : */
55 :
56 :
57 4 : void request::set_auth_user(const std::string& auth_user){
58 4 : auth_user_ = auth_user;
59 4 : }
60 :
61 4 : const std::string& request::get_auth_user() const{
62 4 : return auth_user_;
63 : }
64 :
65 883 : void request::set_matched_route(const route* route) {
66 883 : matched_route_ = route;
67 883 : }
68 :
69 0 : const route* request::get_matched_route() const {
70 0 : return matched_route_;
71 : }
72 :
73 0 : auth_level request::get_required_auth_level() const {
74 0 : return matched_route_ ? matched_route_->get_auth_level() : auth_level::PUBLIC;
75 : }
76 :
77 233 : void request::set_uri_parameter(const std::string& param, const std::string& value){
78 : // erase all existing entries with the specified key
79 233 : params_.erase(param);
80 :
81 : // insert the new key-value pair
82 233 : params_.insert(std::make_pair(param, value));
83 233 : }
84 :
85 4 : void request::add_uri_parameter(const std::string& param, const std::string& value){
86 4 : params_.insert({param, value});
87 4 : }
88 :
89 234 : const std::string& request::get_uri_parameter(const std::string& param) const{
90 234 : auto it = params_.find(param);
91 234 : if(it!=params_.end()){
92 234 : return it->second;
93 : }
94 0 : LOG_WARNING("cannot find required parameter: {}", param);
95 0 : static std::string empty_string;
96 0 : return empty_string;
97 : }
98 :
99 0 : const std::multimap<std::string, std::string>& request::get_uri_parameters() const{
100 0 : return params_;
101 : }
102 :
103 2 : void request::set_auth_groups(const std::set<std::string> &groups) {
104 2 : groups_ = groups;
105 2 : }
106 :
107 2 : const std::set<std::string> & request::get_auth_groups() const {
108 2 : return groups_;
109 : }
110 :
111 2 : std::string request::debug_parameters() const {
112 2 : std::stringstream str;
113 6 : for(const auto& param: params_){
114 4 : str << "(" << param.first << ":" << param.second << ") ";
115 : }
116 4 : return str.str();
117 2 : }
118 :
119 0 : std::string request::get_request_ip() const{
120 0 : auto http_connection = http_connection_.lock();
121 0 : return http_connection ? http_connection->get_socket()->get_remote_ip() : "";
122 0 : }
123 :
124 : /*
125 : exec_result request::get_request_data() const{
126 : const std::string& content = http_request_->get_body();
127 : const std::string& content_type = http_request_->get_content_type();
128 :
129 : // set content
130 : if(!content.empty()){
131 : if(boost::istarts_with(content_type, mime_types::application_json)){
132 : try{
133 : return {true, nlohmann::json::parse(content)};
134 : }catch(...){
135 : return {false, "invalid json payload", http_response::status::bad_request};
136 : }
137 : }else if(boost::istarts_with(content_type, mime_types::application_octect_stream)){
138 : std::vector<uint8_t> data(content.begin(), content.end());
139 : return {true, nlohmann::json::binary(std::move(data))};
140 : }else if(boost::istarts_with(content_type, mime_types::text_html) || boost::istarts_with(content_type, mime_types::text_plain)){
141 : return {true, content};
142 : }else if(boost::istarts_with(content_type, mime_types::application_form_urlencoded)){
143 : std::multimap<std::string, std::string> parameters;
144 : util::url::parse_url_encoded_data(content, parameters);
145 : return {true, std::move(parameters)};
146 : }else if(boost::istarts_with(content_type, mime_types::application_msgpack)){
147 : try{
148 : return {true, nlohmann::json::from_msgpack(content)};
149 : }catch(...){
150 : return {false, "invalid msgpack payload", http_response::status::bad_request};
151 : }
152 : }
153 : else if(boost::istarts_with(content_type, mime_types::application_cbor)){
154 : try{
155 : return {true, nlohmann::json::from_cbor(content)};
156 : }catch(...){
157 : return {false, "invalid cbor payload", http_response::status::bad_request};
158 : }
159 : }
160 : else if(boost::istarts_with(content_type, mime_types::application_ubjson)){
161 : try{
162 : return {true, nlohmann::json::from_ubjson(content)};
163 : }catch(...){
164 : return {false, "invalid ubjson payload", http_response::status::bad_request};
165 : }
166 : }else{
167 : // unknown content type, return as binary
168 : std::vector<uint8_t> vec(content.begin(), content.end());
169 : return {true, nlohmann::json::binary_t(std::move(vec))};
170 : }
171 : }
172 :
173 : return {true, nullptr};
174 : }*/
175 :
176 2 : bool request::keep_alive() const{
177 2 : return http_request_ && http_request_->keep_alive();
178 : }
179 :
180 : // Convenience methods implementation
181 :
182 8 : std::string request::query(const std::string& key) const {
183 8 : if (http_request_ && http_request_->has_uri_parameter(key)) {
184 4 : return http_request_->get_uri_parameter(key);
185 : }
186 8 : return "";
187 : }
188 :
189 4 : std::string request::query(const std::string& key, const std::string& default_value) const {
190 4 : if (http_request_ && http_request_->has_uri_parameter(key)) {
191 0 : return http_request_->get_uri_parameter(key);
192 : }
193 4 : return default_value;
194 : }
195 :
196 41 : std::string request::body() const {
197 41 : return http_request_ ? http_request_->get_body() : "";
198 : }
199 :
200 15 : nlohmann::json request::json() const {
201 15 : if (!http_request_) {
202 0 : return nlohmann::json{};
203 : }
204 15 : const auto& content = http_request_->get_body();
205 15 : if (content.empty()) {
206 2 : return nlohmann::json{};
207 : }
208 13 : auto j = nlohmann::json::parse(content, nullptr, false);
209 13 : if (j.is_discarded()) {
210 2 : return nlohmann::json{};
211 : }
212 11 : return j;
213 13 : }
214 :
215 15 : std::string request::header(const std::string& key) const {
216 15 : return http_request_ ? http_request_->get_header(key) : "";
217 : }
218 :
219 : // --- Deferred body reading support ---
220 :
221 120 : void request::set_read_ahead(const uint8_t* data, size_t size) {
222 120 : if (data && size > 0) {
223 120 : read_ahead_.assign(data, data + size);
224 120 : read_ahead_offset_ = 0;
225 : }
226 120 : }
227 :
228 108 : size_t request::content_length() const {
229 108 : return http_request_ ? http_request_->get_content_length() : 0;
230 : }
231 :
232 498 : bool request::is_chunked() const {
233 498 : return http_request_ && http_request_->is_chunked_transfer();
234 : }
235 :
236 260 : std::shared_ptr<asio::socket> request::get_socket() const {
237 260 : auto conn = http_connection_.lock();
238 520 : return conn ? conn->get_socket() : nullptr;
239 260 : }
240 :
241 1306 : size_t request::read_ahead_available() const {
242 1306 : return read_ahead_.size() > read_ahead_offset_ ? read_ahead_.size() - read_ahead_offset_ : 0;
243 : }
244 :
245 : // --- Raw I/O (bypasses chunked decoding) ---
246 :
247 44 : thinger::awaitable<size_t> request::raw_read_some(uint8_t* buffer, size_t max_size) {
248 : // Consume from read-ahead first (using offset, O(1) per call)
249 : size_t avail = read_ahead_available();
250 : if (avail > 0) {
251 : size_t from_ahead = std::min(avail, max_size);
252 : std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
253 : read_ahead_offset_ += from_ahead;
254 : if (read_ahead_offset_ >= read_ahead_.size()) {
255 : read_ahead_.clear();
256 : read_ahead_offset_ = 0;
257 : }
258 : co_return from_ahead;
259 : }
260 :
261 : // Read from socket
262 : auto sock = get_socket();
263 : if (sock) {
264 : co_return co_await sock->read_some(buffer, max_size);
265 : }
266 :
267 : co_return 0;
268 88 : }
269 :
270 : // --- Chunked transfer encoding decoder ---
271 :
272 60 : static bool is_hex_char(uint8_t c) {
273 60 : return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
274 : }
275 :
276 34 : static size_t hex_value(uint8_t c) {
277 34 : if (c >= '0' && c <= '9') return c - '0';
278 2 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
279 0 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
280 0 : return 0;
281 : }
282 :
283 18 : thinger::awaitable<size_t> request::read_some_chunked(uint8_t* buffer, size_t max_size) {
284 : size_t output = 0;
285 :
286 : while (output == 0 && chunk_state_ != chunk_state::done) {
287 : if (chunk_state_ == chunk_state::data && chunk_remaining_ > 0) {
288 : // Fast path: read data directly into user buffer (no copy overhead)
289 : size_t to_read = std::min(chunk_remaining_, max_size - output);
290 : size_t bytes = co_await raw_read_some(buffer + output, to_read);
291 : if (bytes == 0) co_return output;
292 : chunk_remaining_ -= bytes;
293 : output += bytes;
294 : if (chunk_remaining_ == 0) {
295 : chunk_state_ = chunk_state::data_cr;
296 : }
297 : } else {
298 : // Slow path: read a batch of raw bytes for framing
299 : uint8_t raw[512];
300 : size_t raw_bytes = co_await raw_read_some(raw, sizeof(raw));
301 : if (raw_bytes == 0) co_return output;
302 :
303 : size_t i = 0;
304 : while (i < raw_bytes && chunk_state_ != chunk_state::done && output < max_size) {
305 : uint8_t byte = raw[i];
306 :
307 : switch (chunk_state_) {
308 : case chunk_state::size:
309 : if (is_hex_char(byte)) {
310 : chunk_size_accum_ = chunk_size_accum_ * 16 + hex_value(byte);
311 : i++;
312 : } else if (byte == '\r') {
313 : chunk_state_ = chunk_state::size_lf;
314 : i++;
315 : } else {
316 : // Skip chunk extensions (e.g. ";ext=val")
317 : i++;
318 : }
319 : break;
320 :
321 : case chunk_state::size_lf:
322 : if (byte == '\n') {
323 : if (chunk_size_accum_ == 0) {
324 : // Last chunk — expect trailing CRLF
325 : chunk_state_ = chunk_state::trailer_lf;
326 : } else {
327 : chunk_remaining_ = chunk_size_accum_;
328 : chunk_size_accum_ = 0;
329 : chunk_state_ = chunk_state::data;
330 : }
331 : i++;
332 :
333 : // If entering data state and raw buffer has more bytes,
334 : // copy data directly from raw buffer (avoid another read)
335 : if (chunk_state_ == chunk_state::data && i < raw_bytes) {
336 : size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
337 : size_t to_copy = std::min(data_avail, max_size - output);
338 : std::memcpy(buffer + output, raw + i, to_copy);
339 : output += to_copy;
340 : i += to_copy;
341 : chunk_remaining_ -= to_copy;
342 : if (chunk_remaining_ == 0) {
343 : chunk_state_ = chunk_state::data_cr;
344 : }
345 : }
346 : } else {
347 : i++; // malformed, skip
348 : }
349 : break;
350 :
351 : case chunk_state::data:
352 : // We shouldn't reach here (handled above), but safety
353 : {
354 : size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
355 : size_t to_copy = std::min(data_avail, max_size - output);
356 : std::memcpy(buffer + output, raw + i, to_copy);
357 : output += to_copy;
358 : i += to_copy;
359 : chunk_remaining_ -= to_copy;
360 : if (chunk_remaining_ == 0) {
361 : chunk_state_ = chunk_state::data_cr;
362 : }
363 : }
364 : break;
365 :
366 : case chunk_state::data_cr:
367 : if (byte == '\r') chunk_state_ = chunk_state::data_lf;
368 : i++;
369 : break;
370 :
371 : case chunk_state::data_lf:
372 : if (byte == '\n') {
373 : chunk_state_ = chunk_state::size;
374 : chunk_size_accum_ = 0;
375 : }
376 : i++;
377 : break;
378 :
379 : case chunk_state::trailer_lf:
380 : if (byte == '\r') {
381 : i++;
382 : // Expect final \n
383 : if (i < raw_bytes && raw[i] == '\n') {
384 : i++;
385 : }
386 : chunk_state_ = chunk_state::done;
387 : } else {
388 : // Trailer header line — skip until we find empty CRLF
389 : i++;
390 : }
391 : break;
392 :
393 : case chunk_state::done:
394 : break;
395 : }
396 : }
397 :
398 : // Push unconsumed raw bytes back to read-ahead for next call
399 : if (i < raw_bytes) {
400 : read_ahead_.assign(raw + i, raw + raw_bytes);
401 : read_ahead_offset_ = 0;
402 : }
403 : }
404 : }
405 :
406 : co_return output;
407 36 : }
408 :
409 : // --- Public read API (dispatches to raw or chunked) ---
410 :
411 356 : thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
412 : if (is_chunked()) {
413 : // For chunked, read decoded data until we have `size` bytes or EOF
414 : size_t total = 0;
415 : while (total < size) {
416 : size_t bytes = co_await read_some_chunked(buffer + total, size - total);
417 : if (bytes == 0) break;
418 : total += bytes;
419 : }
420 : co_return total;
421 : }
422 :
423 : // Non-chunked: read exact size
424 : size_t total = 0;
425 :
426 : // Consume from read-ahead first
427 : size_t avail = read_ahead_available();
428 : if (avail > 0) {
429 : size_t from_ahead = std::min(avail, size);
430 : std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
431 : read_ahead_offset_ += from_ahead;
432 : total += from_ahead;
433 : if (read_ahead_offset_ >= read_ahead_.size()) {
434 : read_ahead_.clear();
435 : read_ahead_offset_ = 0;
436 : }
437 : }
438 :
439 : // Read remaining from socket
440 : if (total < size) {
441 : auto sock = get_socket();
442 : if (sock) {
443 : size_t remaining = size - total;
444 : size_t bytes = co_await sock->read(buffer + total, remaining);
445 : total += bytes;
446 : }
447 : }
448 :
449 : co_return total;
450 712 : }
451 :
452 38 : thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
453 : if (is_chunked()) {
454 : co_return co_await read_some_chunked(buffer, max_size);
455 : }
456 : co_return co_await raw_read_some(buffer, max_size);
457 76 : }
458 :
459 104 : thinger::awaitable<bool> request::read_body() {
460 : if (!http_request_) co_return false;
461 :
462 : if (is_chunked()) {
463 : // Chunked: read decoded chunks until EOF, respecting max_body_size
464 : auto& body = http_request_->get_body();
465 : uint8_t buf[8192];
466 : while (true) {
467 : size_t bytes = co_await read_some_chunked(buf, sizeof(buf));
468 : if (bytes == 0) break;
469 : if (body.size() + bytes > max_body_size_) co_return false;
470 : body.append(reinterpret_cast<char*>(buf), bytes);
471 : }
472 :
473 : // Decompress chunked body if Content-Encoding is set
474 : if (http_request_->has_header("Content-Encoding")) {
475 : std::string encoding = http_request_->get_header("Content-Encoding");
476 : if (encoding == "gzip") {
477 : auto decompressed = ::thinger::util::gzip::decompress(body);
478 : if (decompressed) {
479 : body = std::move(*decompressed);
480 : http_request_->remove_header("Content-Encoding");
481 : } else {
482 : LOG_ERROR("Failed to decompress gzip request body");
483 : co_return false;
484 : }
485 : } else if (encoding == "deflate") {
486 : auto decompressed = ::thinger::util::deflate::decompress(body);
487 : if (decompressed) {
488 : body = std::move(*decompressed);
489 : http_request_->remove_header("Content-Encoding");
490 : } else {
491 : LOG_ERROR("Failed to decompress deflate request body");
492 : co_return false;
493 : }
494 : }
495 : }
496 :
497 : co_return true;
498 : }
499 :
500 : // Content-Length based
501 : size_t cl = http_request_->get_content_length();
502 : if (cl == 0) co_return true;
503 :
504 : auto& body = http_request_->get_body();
505 : body.resize(cl);
506 :
507 : size_t bytes_read = co_await read(reinterpret_cast<uint8_t*>(body.data()), cl);
508 : if (bytes_read != cl) co_return false;
509 :
510 : // Decompress body if Content-Encoding is set
511 : if (http_request_->has_header("Content-Encoding")) {
512 : std::string encoding = http_request_->get_header("Content-Encoding");
513 : if (encoding == "gzip") {
514 : auto decompressed = ::thinger::util::gzip::decompress(body);
515 : if (decompressed) {
516 : body = std::move(*decompressed);
517 : http_request_->remove_header("Content-Encoding");
518 : } else {
519 : LOG_ERROR("Failed to decompress gzip request body");
520 : co_return false;
521 : }
522 : } else if (encoding == "deflate") {
523 : auto decompressed = ::thinger::util::deflate::decompress(body);
524 : if (decompressed) {
525 : body = std::move(*decompressed);
526 : http_request_->remove_header("Content-Encoding");
527 : } else {
528 : LOG_ERROR("Failed to decompress deflate request body");
529 : co_return false;
530 : }
531 : }
532 : }
533 :
534 : co_return true;
535 208 : }
536 :
537 : }
|