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 1076 : request::request(
14 : const std::shared_ptr<server_connection>& http_connection,
15 : const std::shared_ptr<http_stream>& http_stream,
16 1076 : std::shared_ptr<http_request> http_request) :
17 1076 : http_connection_{http_connection},
18 1076 : http_stream_{http_stream},
19 2152 : http_request_{std::move(http_request)}
20 : {
21 :
22 1076 : }
23 :
24 1076 : request::~request()= default;
25 :
26 242 : const string& request::operator[](const std::string& param) const{
27 242 : 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 2459 : std::shared_ptr<http_request> request::get_http_request(){
39 2459 : return http_request_;
40 : }
41 :
42 1215 : std::shared_ptr<server_connection> request::get_http_connection() const {
43 1215 : return http_connection_.lock();
44 : }
45 :
46 1079 : std::shared_ptr<http_stream> request::get_http_stream() const {
47 1079 : 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 16 : void request::set_auth_user(const std::string& auth_user){
58 16 : auth_user_ = auth_user;
59 16 : }
60 :
61 16 : const std::string& request::get_auth_user() const{
62 16 : return auth_user_;
63 : }
64 :
65 1025 : void request::set_matched_route(const route* route) {
66 1025 : matched_route_ = route;
67 1025 : }
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 253 : void request::set_uri_parameter(const std::string& param, const std::string& value){
78 : // erase all existing entries with the specified key
79 253 : params_.erase(param);
80 :
81 : // insert the new key-value pair
82 253 : params_.insert(std::make_pair(param, value));
83 253 : }
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 242 : const std::string& request::get_uri_parameter(const std::string& param) const{
90 242 : auto it = params_.find(param);
91 242 : if(it!=params_.end()){
92 242 : 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 44 : std::string request::body() const {
197 44 : 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 174 : void request::set_read_ahead(const uint8_t* data, size_t size) {
222 174 : if (data && size > 0) {
223 174 : read_ahead_.assign(data, data + size);
224 174 : read_ahead_offset_ = 0;
225 : }
226 174 : }
227 :
228 162 : size_t request::content_length() const {
229 162 : return http_request_ ? http_request_->get_content_length() : 0;
230 : }
231 :
232 606 : bool request::is_chunked() const {
233 606 : 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 1506 : size_t request::read_ahead_available() const {
242 1506 : 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 : auto [ec, bytes] = co_await sock->read_some(buffer, max_size);
265 : co_return bytes;
266 : }
267 :
268 : co_return 0;
269 88 : }
270 :
271 : // --- Chunked transfer encoding decoder ---
272 :
273 60 : static bool is_hex_char(uint8_t c) {
274 60 : return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
275 : }
276 :
277 34 : static size_t hex_value(uint8_t c) {
278 34 : if (c >= '0' && c <= '9') return c - '0';
279 2 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
280 0 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
281 0 : return 0;
282 : }
283 :
284 18 : thinger::awaitable<size_t> request::read_some_chunked(uint8_t* buffer, size_t max_size) {
285 : size_t output = 0;
286 :
287 : while (output == 0 && chunk_state_ != chunk_state::done) {
288 : if (chunk_state_ == chunk_state::data && chunk_remaining_ > 0) {
289 : // Fast path: read data directly into user buffer (no copy overhead)
290 : size_t to_read = std::min(chunk_remaining_, max_size - output);
291 : size_t bytes = co_await raw_read_some(buffer + output, to_read);
292 : if (bytes == 0) co_return output;
293 : chunk_remaining_ -= bytes;
294 : output += bytes;
295 : if (chunk_remaining_ == 0) {
296 : chunk_state_ = chunk_state::data_cr;
297 : }
298 : } else {
299 : // Slow path: read a batch of raw bytes for framing
300 : uint8_t raw[512];
301 : size_t raw_bytes = co_await raw_read_some(raw, sizeof(raw));
302 : if (raw_bytes == 0) co_return output;
303 :
304 : size_t i = 0;
305 : while (i < raw_bytes && chunk_state_ != chunk_state::done && output < max_size) {
306 : uint8_t byte = raw[i];
307 :
308 : switch (chunk_state_) {
309 : case chunk_state::size:
310 : if (is_hex_char(byte)) {
311 : chunk_size_accum_ = chunk_size_accum_ * 16 + hex_value(byte);
312 : i++;
313 : } else if (byte == '\r') {
314 : chunk_state_ = chunk_state::size_lf;
315 : i++;
316 : } else {
317 : // Skip chunk extensions (e.g. ";ext=val")
318 : i++;
319 : }
320 : break;
321 :
322 : case chunk_state::size_lf:
323 : if (byte == '\n') {
324 : if (chunk_size_accum_ == 0) {
325 : // Last chunk — expect trailing CRLF
326 : chunk_state_ = chunk_state::trailer_lf;
327 : } else {
328 : chunk_remaining_ = chunk_size_accum_;
329 : chunk_size_accum_ = 0;
330 : chunk_state_ = chunk_state::data;
331 : }
332 : i++;
333 :
334 : // If entering data state and raw buffer has more bytes,
335 : // copy data directly from raw buffer (avoid another read)
336 : if (chunk_state_ == chunk_state::data && i < raw_bytes) {
337 : size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
338 : size_t to_copy = std::min(data_avail, max_size - output);
339 : std::memcpy(buffer + output, raw + i, to_copy);
340 : output += to_copy;
341 : i += to_copy;
342 : chunk_remaining_ -= to_copy;
343 : if (chunk_remaining_ == 0) {
344 : chunk_state_ = chunk_state::data_cr;
345 : }
346 : }
347 : } else {
348 : i++; // malformed, skip
349 : }
350 : break;
351 :
352 : case chunk_state::data:
353 : // We shouldn't reach here (handled above), but safety
354 : {
355 : size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
356 : size_t to_copy = std::min(data_avail, max_size - output);
357 : std::memcpy(buffer + output, raw + i, to_copy);
358 : output += to_copy;
359 : i += to_copy;
360 : chunk_remaining_ -= to_copy;
361 : if (chunk_remaining_ == 0) {
362 : chunk_state_ = chunk_state::data_cr;
363 : }
364 : }
365 : break;
366 :
367 : case chunk_state::data_cr:
368 : if (byte == '\r') chunk_state_ = chunk_state::data_lf;
369 : i++;
370 : break;
371 :
372 : case chunk_state::data_lf:
373 : if (byte == '\n') {
374 : chunk_state_ = chunk_state::size;
375 : chunk_size_accum_ = 0;
376 : }
377 : i++;
378 : break;
379 :
380 : case chunk_state::trailer_lf:
381 : if (byte == '\r') {
382 : i++;
383 : // Expect final \n
384 : if (i < raw_bytes && raw[i] == '\n') {
385 : i++;
386 : }
387 : chunk_state_ = chunk_state::done;
388 : } else {
389 : // Trailer header line — skip until we find empty CRLF
390 : i++;
391 : }
392 : break;
393 :
394 : case chunk_state::done:
395 : break;
396 : }
397 : }
398 :
399 : // Push unconsumed raw bytes back to read-ahead for next call
400 : if (i < raw_bytes) {
401 : read_ahead_.assign(raw + i, raw + raw_bytes);
402 : read_ahead_offset_ = 0;
403 : }
404 : }
405 : }
406 :
407 : co_return output;
408 36 : }
409 :
410 : // --- Public read API (dispatches to raw or chunked) ---
411 :
412 410 : thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
413 : if (is_chunked()) {
414 : // For chunked, read decoded data until we have `size` bytes or EOF
415 : size_t total = 0;
416 : while (total < size) {
417 : size_t bytes = co_await read_some_chunked(buffer + total, size - total);
418 : if (bytes == 0) break;
419 : total += bytes;
420 : }
421 : co_return total;
422 : }
423 :
424 : // Non-chunked: read exact size
425 : size_t total = 0;
426 :
427 : // Consume from read-ahead first
428 : size_t avail = read_ahead_available();
429 : if (avail > 0) {
430 : size_t from_ahead = std::min(avail, size);
431 : std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
432 : read_ahead_offset_ += from_ahead;
433 : total += from_ahead;
434 : if (read_ahead_offset_ >= read_ahead_.size()) {
435 : read_ahead_.clear();
436 : read_ahead_offset_ = 0;
437 : }
438 : }
439 :
440 : // Read remaining from socket
441 : if (total < size) {
442 : auto sock = get_socket();
443 : if (sock) {
444 : size_t remaining = size - total;
445 : auto [ec, bytes] = co_await sock->read(buffer + total, remaining);
446 : total += bytes;
447 : }
448 : }
449 :
450 : co_return total;
451 820 : }
452 :
453 38 : thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
454 : if (is_chunked()) {
455 : co_return co_await read_some_chunked(buffer, max_size);
456 : }
457 : co_return co_await raw_read_some(buffer, max_size);
458 76 : }
459 :
460 158 : thinger::awaitable<bool> request::read_body() {
461 : if (!http_request_) co_return false;
462 :
463 : if (is_chunked()) {
464 : // Chunked: read decoded chunks until EOF, respecting max_body_size
465 : auto& body = http_request_->get_body();
466 : uint8_t buf[8192];
467 : while (true) {
468 : size_t bytes = co_await read_some_chunked(buf, sizeof(buf));
469 : if (bytes == 0) break;
470 : if (body.size() + bytes > max_body_size_) co_return false;
471 : body.append(reinterpret_cast<char*>(buf), bytes);
472 : }
473 :
474 : // Decompress chunked body if Content-Encoding is set
475 : if (http_request_->has_header("Content-Encoding")) {
476 : std::string encoding = http_request_->get_header("Content-Encoding");
477 : if (encoding == "gzip") {
478 : auto decompressed = ::thinger::util::gzip::decompress(body);
479 : if (decompressed) {
480 : body = std::move(*decompressed);
481 : http_request_->remove_header("Content-Encoding");
482 : } else {
483 : LOG_ERROR("Failed to decompress gzip request body");
484 : co_return false;
485 : }
486 : } else if (encoding == "deflate") {
487 : auto decompressed = ::thinger::util::deflate::decompress(body);
488 : if (decompressed) {
489 : body = std::move(*decompressed);
490 : http_request_->remove_header("Content-Encoding");
491 : } else {
492 : LOG_ERROR("Failed to decompress deflate request body");
493 : co_return false;
494 : }
495 : }
496 : }
497 :
498 : co_return true;
499 : }
500 :
501 : // Content-Length based
502 : size_t cl = http_request_->get_content_length();
503 : if (cl == 0) co_return true;
504 :
505 : auto& body = http_request_->get_body();
506 : body.resize(cl);
507 :
508 : size_t bytes_read = co_await read(reinterpret_cast<uint8_t*>(body.data()), cl);
509 : if (bytes_read != cl) co_return false;
510 :
511 : // Decompress body if Content-Encoding is set
512 : if (http_request_->has_header("Content-Encoding")) {
513 : std::string encoding = http_request_->get_header("Content-Encoding");
514 : if (encoding == "gzip") {
515 : auto decompressed = ::thinger::util::gzip::decompress(body);
516 : if (decompressed) {
517 : body = std::move(*decompressed);
518 : http_request_->remove_header("Content-Encoding");
519 : } else {
520 : LOG_ERROR("Failed to decompress gzip request body");
521 : co_return false;
522 : }
523 : } else if (encoding == "deflate") {
524 : auto decompressed = ::thinger::util::deflate::decompress(body);
525 : if (decompressed) {
526 : body = std::move(*decompressed);
527 : http_request_->remove_header("Content-Encoding");
528 : } else {
529 : LOG_ERROR("Failed to decompress deflate request body");
530 : co_return false;
531 : }
532 : }
533 : }
534 :
535 : co_return true;
536 316 : }
537 :
538 : }
|