paivana-httpd_reverse.c (45530B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2012-2014 GNUnet e.V. 4 Copyright (C) 2018, 2025, 2026 Taler Systems SA 5 6 GNU Taler is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License 8 as published by the Free Software Foundation; either version 9 3, or (at your option) any later version. 10 11 GNU Taler is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public 17 License along with GNU Taler; see the file COPYING. If not, 18 write to the Free Software Foundation, Inc., 51 Franklin 19 Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @author Martin Schanzenbach 24 * @author Christian Grothoff 25 * @author Marcello Stanisci 26 * @file src/backend/paivana-httpd_reverse.c 27 * @brief Reverse proxy logic that just forwards the request 28 */ 29 #include "platform.h" 30 #include <curl/curl.h> 31 #include <gnunet/gnunet_util_lib.h> 32 #include <gnunet/gnunet_curl_lib.h> 33 #include <taler/taler_mhd_lib.h> 34 #include "paivana-httpd.h" 35 #include "paivana-httpd_reverse.h" 36 37 38 /** 39 * Log curl error. 40 * 41 * @param level log level 42 * @param fun name of curl_easy-function that gave the error 43 * @param rc return code from curl 44 */ 45 #define LOG_CURL_EASY(level,fun,rc) \ 46 GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \ 47 __LINE__, \ 48 curl_easy_strerror (rc)) 49 50 51 /** 52 * State machine for HTTP requests (per request). MHD invokes the 53 * access handler multiple times per request — we use this enum to 54 * know what each invocation is expected to do, and cascade between 55 * states in a single invocation when no new data from MHD is 56 * required. 57 */ 58 enum RequestState 59 { 60 /** 61 * Initial state. MHD's first access-handler call (immediately 62 * after parsing the request headers, `upload_data_size == 0`) 63 * has not yet been observed. In this state we can still queue 64 * a final response before MHD auto-generates a 100 Continue and 65 * the client invariably starts with the upload (if any). 66 */ 67 REQUEST_STATE_HEADERS_PENDING, 68 69 /** 70 * We have accepted the request and are receiving the 71 * body from the client. Each body chunk is buffered into 72 * `io_buf`; when MHD signals end-of-body (size == 0) we 73 * advance to `CLIENT_UPLOAD_DONE`. 74 */ 75 REQUEST_STATE_CLIENT_UPLOAD_STARTED, 76 77 /** 78 * We have decided to reject the upload (Content-Length exceeded 79 * the buffer cap, or the actual body did) but MHD is still 80 * delivering body chunks and refuses to let us queue a response 81 * during `BODY_RECEIVING`. Silently drop the bytes here until 82 * MHD gets back to a state that allows `MHD_queue_response`. 83 */ 84 REQUEST_STATE_REJECT_UPLOAD_DRAIN, 85 86 /** 87 * Ready to queue the 413 Content Too Large response. 88 */ 89 REQUEST_STATE_REJECT_UPLOAD, 90 91 /** 92 * Client body fully received; next step is to initialize the 93 * curl handle and start forwarding to the upstream. 94 */ 95 REQUEST_STATE_CLIENT_UPLOAD_DONE, 96 97 /** 98 * We have started uploading data to the proxied service. 99 * MHD handling will be suspended. 100 */ 101 REQUEST_STATE_PROXY_UPLOAD_STARTED, 102 103 /** 104 * We're done uploading data to the proxied service. 105 * MHD handling should remain suspended. 106 */ 107 REQUEST_STATE_PROXY_UPLOAD_DONE, 108 109 /** 110 * We've finished uploading data via CURL and can now download. 111 * MHD handling should remain suspended. 112 */ 113 REQUEST_STATE_PROXY_DOWNLOAD_STARTED, 114 115 /** 116 * We've finished receiving download data from cURL; ready to 117 * build and queue the final response to the client. 118 */ 119 REQUEST_STATE_PROXY_DOWNLOAD_DONE, 120 121 /** 122 * We've failed to download data from cURL; ready to 123 * build and queue the final response to the client. 124 */ 125 REQUEST_STATE_PROXY_DOWNLOAD_FAILED 126 }; 127 128 129 /** 130 * A header list 131 */ 132 struct HttpResponseHeader 133 { 134 /** 135 * DLL 136 */ 137 struct HttpResponseHeader *next; 138 139 /** 140 * DLL 141 */ 142 struct HttpResponseHeader *prev; 143 144 /** 145 * Header type 146 */ 147 char *type; 148 149 /** 150 * Header value 151 */ 152 char *value; 153 }; 154 155 156 /** 157 * A structure for socks requests 158 */ 159 struct HttpRequest 160 { 161 162 /** 163 * Kept in DLL. 164 */ 165 struct HttpRequest *prev; 166 167 /** 168 * Kept in DLL. 169 */ 170 struct HttpRequest *next; 171 172 /** 173 * MHD request that triggered us. 174 */ 175 struct MHD_Connection *con; 176 177 /** 178 * Client socket read task 179 */ 180 struct GNUNET_SCHEDULER_Task *rtask; 181 182 /** 183 * Client socket write task 184 */ 185 struct GNUNET_SCHEDULER_Task *wtask; 186 187 /** 188 * Hold the response obtained by modifying the original one. 189 */ 190 struct MHD_Response *mod_response; 191 192 /** 193 * MHD response object for this request. 194 */ 195 struct MHD_Response *response; 196 197 /** 198 * The URL to fetch 199 */ 200 char *url; 201 202 /** 203 * Handle to cURL 204 */ 205 CURL *curl; 206 207 /** 208 * Job handle for the CURL request in our event loop. 209 */ 210 struct GNUNET_CURL_Job *job; 211 212 /** 213 * HTTP request headers for the curl request. 214 */ 215 struct curl_slist *headers; 216 217 /** 218 * Headers from response 219 */ 220 struct HttpResponseHeader *header_head; 221 222 /** 223 * Headers from response 224 */ 225 struct HttpResponseHeader *header_tail; 226 227 /** 228 * Buffer we use for moving data between MHD and 229 * curl (in both directions). 230 */ 231 char *io_buf; 232 233 /** 234 * Number of bytes already in the IO buffer. 235 */ 236 size_t io_len; 237 238 /** 239 * Number of bytes allocated for the IO buffer. 240 */ 241 unsigned int io_size; 242 243 /** 244 * HTTP response code to give to MHD for the response. 245 */ 246 unsigned int response_code; 247 248 /** 249 * Request processing state machine. 250 */ 251 enum RequestState state; 252 253 /** 254 * Did we suspend MHD processing? 255 */ 256 enum GNUNET_GenericReturnValue suspended; 257 258 /** 259 * Concatenated value of the client's Via header(s), if any. Per 260 * RFC 9110 §7.6.3 a proxy must *append* its own entry to this 261 * list, not replace it; we capture the inbound value here before 262 * iterating over the other headers and emit our appended Via when 263 * building the curl request. 264 */ 265 char *client_via; 266 267 /** 268 * Concatenated value of the client's Connection header(s), if 269 * any. Per RFC 9110 §7.6.1 this lists additional header names 270 * that are connection-specific and must not be forwarded; we 271 * consult this list in `con_val_iter` to filter such headers out 272 * of the upstream request. 273 */ 274 char *client_connection; 275 }; 276 277 278 /** 279 * DLL of active HTTP requests. 280 */ 281 static struct HttpRequest *hr_head; 282 283 /** 284 * DLL of active HTTP requests. 285 */ 286 static struct HttpRequest *hr_tail; 287 288 /** 289 * Response we return on cURL failures. 290 */ 291 static struct MHD_Response *curl_failure_response; 292 293 /** 294 * Response we return if the HTTP method is not allowed. 295 */ 296 static struct MHD_Response *method_failure_response; 297 298 /** 299 * Response we return if the upload is too big. 300 */ 301 static struct MHD_Response *upload_failure_response; 302 303 /** 304 * Response we return if we encountered an internal failure. 305 */ 306 static struct MHD_Response *internal_failure_response; 307 308 309 /** 310 * Create HTML response using @a body 311 * 312 * @param body UTF-8 encoded 0-terminated body to use 313 * @return NULL on error 314 */ 315 static struct MHD_Response * 316 make_html_response (const char *body) 317 { 318 struct MHD_Response *ret; 319 320 ret = MHD_create_response_from_buffer_static (strlen (body), 321 body); 322 if (NULL == ret) 323 { 324 GNUNET_break (0); 325 return NULL; 326 } 327 GNUNET_break (MHD_YES == 328 MHD_add_response_header (ret, 329 MHD_HTTP_HEADER_CONTENT_TYPE, 330 "text/html; charset=utf-8")); 331 return ret; 332 } 333 334 335 bool 336 PAIVANA_HTTPD_reverse_init (void) 337 { 338 static const char *curl_failure_body = 339 "<!DOCTYPE html>\n" 340 "<html><head><title>Bad Gateway</title></head>" 341 "<body><h1>502 Bad Gateway</h1>" 342 "<p>The upstream server could not be reached.</p>" 343 "</body></html>\n"; 344 static const char *internal_failure_body = 345 "<!DOCTYPE html>\n" 346 "<html><head><title>Internal server failure</title></head>" 347 "<body><h1>500 Internal Server Failure</h1>" 348 "<p>The server experienced an internal failure.</p>" 349 "</body></html>\n"; 350 static const char *upload_failure_body = 351 "<!DOCTYPE html>\n" 352 "<html><head><title>Content too large</title></head>" 353 "<body><h1>413 Content too large</h1>" 354 "<p>The size of the body exceeds the limit.</p>" 355 "</body></html>\n"; 356 static const char *method_failure_body = 357 "<!DOCTYPE html>\n" 358 "<html><head><title>Method not allowed</title></head>" 359 "<body><h1>405 Method not allowed</h1>" 360 "<p>The HTTP method specified is not allowed.</p>" 361 "</body></html>\n"; 362 363 curl_failure_response 364 = make_html_response (curl_failure_body); 365 if (NULL == curl_failure_response) 366 { 367 GNUNET_break (0); 368 return false; 369 } 370 upload_failure_response 371 = make_html_response (upload_failure_body); 372 if (NULL == upload_failure_response) 373 { 374 GNUNET_break (0); 375 return false; 376 } 377 method_failure_response 378 = make_html_response (method_failure_body); 379 if (NULL == method_failure_response) 380 { 381 GNUNET_break (0); 382 return false; 383 } 384 internal_failure_response 385 = make_html_response (internal_failure_body); 386 if (NULL == internal_failure_response) 387 { 388 GNUNET_break (0); 389 return false; 390 } 391 return true; 392 } 393 394 395 void 396 PAIVANA_HTTPD_reverse_shutdown (void) 397 { 398 for (struct HttpRequest *hr = hr_head; 399 NULL != hr; 400 hr = hr->next) 401 { 402 if (GNUNET_YES == hr->suspended) 403 { 404 hr->suspended = GNUNET_NO; 405 MHD_resume_connection (hr->con); 406 } 407 } 408 if (NULL != curl_failure_response) 409 { 410 MHD_destroy_response (curl_failure_response); 411 curl_failure_response = NULL; 412 } 413 if (NULL != upload_failure_response) 414 { 415 MHD_destroy_response (upload_failure_response); 416 upload_failure_response = NULL; 417 } 418 if (NULL != method_failure_response) 419 { 420 MHD_destroy_response (method_failure_response); 421 method_failure_response = NULL; 422 } 423 if (NULL != internal_failure_response) 424 { 425 MHD_destroy_response (internal_failure_response); 426 internal_failure_response = NULL; 427 } 428 } 429 430 431 /** 432 * Is @a name a hop-by-hop HTTP header name that a proxy must not 433 * forward (RFC 9110 section 7.6.1 and RFC 7230, section 6.1). 434 * The client may name *additional* hop-by-hop headers in its 435 * Connection header; those are handled separately in 436 * `connection_lists_header()`. 437 * 438 * @param name header field name 439 * @return true if @a name is a hop-by-hop header 440 */ 441 static bool 442 is_hop_by_hop_header (const char *name) 443 { 444 static const char *const hop_headers[] = { 445 MHD_HTTP_HEADER_CONNECTION, 446 MHD_HTTP_HEADER_KEEP_ALIVE, 447 MHD_HTTP_HEADER_PROXY_AUTHENTICATE, 448 MHD_HTTP_HEADER_PROXY_AUTHORIZATION, 449 MHD_HTTP_HEADER_TE, 450 MHD_HTTP_HEADER_TRAILER, 451 MHD_HTTP_HEADER_TRANSFER_ENCODING, 452 MHD_HTTP_HEADER_UPGRADE, 453 NULL 454 }; 455 456 for (unsigned int i = 0; NULL != hop_headers[i]; i++) 457 if (0 == strcasecmp (name, 458 hop_headers[i])) 459 return true; 460 return false; 461 } 462 463 464 /** 465 * Return true if @a name appears (case-insensitively) as a 466 * comma-separated element of @a list. Used to honor RFC 9110 467 * §7.6.1: the client's Connection header lists additional 468 * hop-by-hop header names that must not be forwarded. 469 * 470 * @param list comma-separated list of header names, may be NULL 471 * @param name header name to look for 472 * @return true if @a list names @a name 473 */ 474 static bool 475 connection_lists_header (const char *list, 476 const char *name) 477 { 478 size_t namelen; 479 const char *p; 480 481 if (NULL == list) 482 return false; 483 namelen = strlen (name); 484 p = list; 485 while ('\0' != *p) 486 { 487 const char *comma; 488 size_t len; 489 490 while ( (' ' == *p) || 491 ('\t' == *p) || 492 (',' == *p) ) 493 p++; 494 if ('\0' == *p) 495 break; 496 comma = strchr (p, ','); 497 len = (NULL == comma) ? strlen (p) : (size_t) (comma - p); 498 while ( (len > 0) && 499 ( (' ' == p[len - 1]) || 500 ('\t' == p[len - 1]) ) ) 501 len--; 502 if ( (len == namelen) && 503 (0 == strncasecmp (p, name, namelen)) ) 504 return true; 505 if (NULL == comma) 506 break; 507 p = comma + 1; 508 } 509 return false; 510 } 511 512 513 /** 514 * Pre-iteration collector: records the client's Via and Connection 515 * headers on @a hr so the subsequent forwarding pass can append to 516 * Via (RFC 9110 §7.6.3) and honor the hop-by-hop names listed in 517 * Connection (RFC 9110 §7.6.1). Multiple values for the same 518 * header are joined with ", ", matching the list-header combining 519 * rule. 520 * 521 * @param cls our `struct HttpRequest *` 522 * @param kind value kind (unused) 523 * @param key header field name 524 * @param value header field value 525 * @return #MHD_YES to continue iteration 526 */ 527 static enum MHD_Result 528 collect_proxy_state (void *cls, 529 enum MHD_ValueKind kind, 530 const char *key, 531 const char *value) 532 { 533 struct HttpRequest *hr = cls; 534 char **target; 535 536 (void) kind; 537 if (NULL == value) 538 return MHD_YES; 539 if (0 == strcasecmp (MHD_HTTP_HEADER_VIA, 540 key)) 541 target = &hr->client_via; 542 else if (0 == strcasecmp (MHD_HTTP_HEADER_CONNECTION, 543 key)) 544 target = &hr->client_connection; 545 else 546 return MHD_YES; 547 if (NULL == *target) 548 { 549 *target = GNUNET_strdup (value); 550 } 551 else 552 { 553 char *combined; 554 555 GNUNET_asprintf (&combined, 556 "%s, %s", 557 *target, 558 value); 559 GNUNET_free (*target); 560 *target = combined; 561 } 562 return MHD_YES; 563 } 564 565 566 /* *************** HTTP handling with cURL ***************** */ 567 568 569 /** 570 * Transform _one_ CURL header (gotten from the request) into 571 * MHD format and put it into the response headers list; mostly 572 * copies the headers, but makes special adjustments based on 573 * control requests. 574 * 575 * @param buffer curl buffer with a single 576 * line of header data; not 0-terminated! 577 * @param size curl blocksize 578 * @param nmemb curl blocknumber 579 * @param cls our `struct HttpRequest *` 580 * @return size of processed bytes 581 */ 582 static size_t 583 curl_check_hdr (void *buffer, 584 size_t size, 585 size_t nmemb, 586 void *cls) 587 { 588 struct HttpRequest *hr = cls; 589 struct HttpResponseHeader *header; 590 size_t bytes = size * nmemb; 591 char *ndup; 592 const char *hdr_type; 593 char *hdr_val; 594 char *tok; 595 596 /* Raw line is not guaranteed to be null-terminated. */ 597 ndup = GNUNET_malloc (bytes + 1); 598 memcpy (ndup, 599 buffer, 600 bytes); 601 ndup[bytes] = '\0'; 602 hdr_type = strtok (ndup, ":"); 603 if (NULL == hdr_type) 604 { 605 GNUNET_free (ndup); 606 return bytes; 607 } 608 hdr_val = strtok (NULL, ""); 609 if (NULL == hdr_val) 610 { 611 GNUNET_free (ndup); 612 return bytes; 613 } 614 if (' ' == *hdr_val) 615 hdr_val++; 616 617 /* MHD does not allow certain characters in values, 618 * remove those, plus those could alter strings matching. */ 619 if (NULL != (tok = strchr (hdr_val, '\n'))) 620 *tok = '\0'; 621 if (NULL != (tok = strchr (hdr_val, '\r'))) 622 *tok = '\0'; 623 if (NULL != (tok = strchr (hdr_val, '\t'))) 624 *tok = '\0'; 625 PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n", 626 hdr_type, 627 hdr_val); 628 /* Skip "Content-length:" header as it will be wrong, given 629 that we are man-in-the-middling the connection */ 630 if (0 == strcasecmp (hdr_type, 631 MHD_HTTP_HEADER_CONTENT_LENGTH)) 632 { 633 GNUNET_free (ndup); 634 return bytes; 635 } 636 /* Skip hop-by-hop headers. In particular Transfer-Encoding 637 must not leak through: libcurl has already dechunked the 638 body for us and MHD will decide whether to re-chunk. */ 639 if (is_hop_by_hop_header (hdr_type)) 640 { 641 GNUNET_free (ndup); 642 return bytes; 643 } 644 if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ 645 { 646 header = GNUNET_new (struct HttpResponseHeader); 647 header->type = GNUNET_strdup (hdr_type); 648 header->value = GNUNET_strdup (hdr_val); 649 GNUNET_CONTAINER_DLL_insert (hr->header_head, 650 hr->header_tail, 651 header); 652 } 653 GNUNET_free (ndup); 654 return bytes; 655 } 656 657 658 /** 659 * Handle response payload data from cURL. 660 * 661 * @param cls our `struct HttpRequest *` 662 * @param response_code HTTP status returned by the server 663 * @param body response body 664 * @param body_size number of blocks of data 665 */ 666 static void 667 curl_download_cb (void *cls, 668 long response_code, 669 const void *body, 670 size_t body_size) 671 { 672 struct HttpRequest *hr = cls; 673 674 hr->job = NULL; 675 if (0 == response_code) 676 { 677 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_FAILED; 678 } 679 else 680 { 681 hr->response = MHD_create_response_from_buffer_copy (body_size, 682 body); 683 hr->response_code = response_code; 684 for (struct HttpResponseHeader *header = hr->header_head; 685 NULL != header; 686 header = header->next) 687 { 688 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 689 "Adding MHD response header %s->%s\n", 690 header->type, 691 header->value); 692 GNUNET_break (MHD_YES == 693 MHD_add_response_header (hr->response, 694 header->type, 695 header->value)); 696 } 697 if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || 698 ( (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) && 699 (0 == hr->io_len) ) ) 700 { 701 /* Either the request body was empty (CURLOPT_POSTFIELDSIZE = 0, 702 so libcurl never called our read callback) or the upload 703 already drained but we have not yet entered upload_cb to 704 flip the state — either way, the upload is complete and we 705 can move straight on to consuming the response. */ 706 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 707 } 708 } 709 if (GNUNET_YES == hr->suspended) 710 { 711 hr->suspended = GNUNET_NO; 712 MHD_resume_connection (hr->con); 713 TALER_MHD_daemon_trigger (); 714 } 715 } 716 717 718 /** 719 * cURL callback for uploaded (PUT/POST) data. 720 * Copies from our `io_buf` to make it available to cURL. 721 * 722 * @param buf where to write the data 723 * @param size number of bytes per member 724 * @param nmemb number of members available in @a buf 725 * @param cls our `struct HttpRequest` that generated the data 726 * @return number of bytes copied to @a buf 727 */ 728 static size_t 729 curl_upload_cb (void *buf, 730 size_t size, 731 size_t nmemb, 732 void *cls) 733 { 734 struct HttpRequest *hr = cls; 735 size_t len = size * nmemb; 736 size_t to_copy; 737 738 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 739 "Upload cb is working...\n"); 740 741 if (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) 742 { 743 /* Should not happen: the curl task only declares the transfer 744 done after consuming POSTFIELDSIZE bytes from us. */ 745 GNUNET_break (0); 746 return CURL_READFUNC_ABORT; 747 } 748 749 if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) 750 { 751 /* We have already drained io_buf and flipped the state in a 752 previous call. With CURLOPT_POSTFIELDSIZE set, libcurl 753 should not ask for more bytes — but if it does, signal a 754 clean end-of-upload rather than aborting the transfer. */ 755 GNUNET_assert (0 == hr->io_len); 756 return 0; 757 } 758 759 GNUNET_assert (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state); 760 if (0 == hr->io_len) 761 { 762 /* Should not happen: start_curl_request runs only after the 763 client body is fully buffered, so io_len begins > 0 (or 0 764 for an empty body, which CURLOPT_POSTFIELDSIZE = 0 keeps 765 libcurl from asking about). */ 766 GNUNET_break (0); 767 return CURL_READFUNC_PAUSE; 768 } 769 770 to_copy = GNUNET_MIN (hr->io_len, 771 len); 772 GNUNET_memcpy (buf, 773 hr->io_buf, 774 to_copy); 775 /* shift remaining data back to the beginning of the buffer. */ 776 memmove (hr->io_buf, 777 &hr->io_buf[to_copy], 778 hr->io_len - to_copy); 779 hr->io_len -= to_copy; 780 if (0 == hr->io_len) 781 { 782 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 783 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 784 "Completed CURL UPLOAD\n"); 785 } 786 return to_copy; 787 } 788 789 790 /* ************** helper functions ************* */ 791 792 /** 793 * Extract the hostname from a complete URL. 794 * 795 * @param url full fledged URL 796 * @return pointer to the 0-terminated hostname, to be freed 797 * by the caller. 798 */ 799 static char * 800 build_host_header (const char *url) 801 { 802 #define MARKER "://" 803 804 char *header; 805 char *end; 806 char *hostname; 807 char *dup = GNUNET_strdup (url); 808 809 hostname = strstr (dup, 810 MARKER); 811 hostname += 3; 812 end = strchrnul (hostname, '/'); 813 *end = '\0'; 814 GNUNET_asprintf (&header, 815 "Host: %s", 816 hostname); 817 GNUNET_free (dup); 818 return header; 819 } 820 821 822 /* ************** main loop of cURL interaction ************* */ 823 824 825 /** 826 * "Filter" function that translates MHD request headers to 827 * cURL's. 828 * 829 * @param cls our `struct HttpRequest` 830 * @param kind value kind 831 * @param key field key 832 * @param value field value 833 * @return #MHD_YES to continue to iterate 834 */ 835 static enum MHD_Result 836 con_val_iter (void *cls, 837 enum MHD_ValueKind kind, 838 const char *key, 839 const char *value) 840 { 841 struct HttpRequest *hr = cls; 842 char *hdr; 843 char *new_value = NULL; 844 845 (void) kind; 846 if (0 == strcasecmp (MHD_HTTP_HEADER_HOST, 847 key)) 848 { 849 /* We don't take the host header as given in the request. 850 * We'll instead put the proxied service's hostname in it*/ 851 return MHD_YES; 852 } 853 if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH, 854 key)) 855 { 856 /* libcurl sets Content-Length itself from CURLOPT_POSTFIELDSIZE 857 / CURLOPT_INFILESIZE. */ 858 return MHD_YES; 859 } 860 if (0 == strcasecmp (MHD_HTTP_HEADER_EXPECT, 861 key)) 862 { 863 /* libcurl manages Expect: 100-continue on its own. */ 864 return MHD_YES; 865 } 866 if (is_hop_by_hop_header (key)) 867 return MHD_YES; 868 /* RFC 9110 §7.6.1: suppress any header named by the client's 869 Connection list (additional hop-by-hop headers). */ 870 if (connection_lists_header (hr->client_connection, 871 key)) 872 return MHD_YES; 873 if ( (0 == strncasecmp ("X-Forwarded-", 874 key, 875 strlen ("X-Forwarded-"))) || 876 (0 == strcasecmp (MHD_HTTP_HEADER_FORWARDED, 877 key)) ) 878 { 879 /* We will replace these with our own below. */ 880 return MHD_YES; 881 } 882 if (0 == strcasecmp (MHD_HTTP_HEADER_VIA, 883 key)) 884 { 885 /* The client's Via was captured in `collect_proxy_state` and 886 will be emitted below with our own entry appended (RFC 9110 887 §7.6.3). Drop the raw header here so we don't forward it 888 twice. */ 889 return MHD_YES; 890 } 891 GNUNET_asprintf (&hdr, 892 "%s: %s", 893 key, 894 value); 895 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 896 "Adding header `%s' to HTTP request\n", 897 hdr); 898 hr->headers = curl_slist_append (hr->headers, 899 hdr); 900 GNUNET_free (hdr); 901 GNUNET_free (new_value); 902 return MHD_YES; 903 } 904 905 906 struct HttpRequest * 907 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection, 908 const char *url) 909 { 910 struct HttpRequest *hr; 911 912 hr = GNUNET_new (struct HttpRequest); 913 hr->state = REQUEST_STATE_HEADERS_PENDING; 914 hr->con = connection; 915 hr->url = GNUNET_strdup (url); 916 GNUNET_CONTAINER_DLL_insert (hr_head, 917 hr_tail, 918 hr); 919 return hr; 920 } 921 922 923 void 924 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr) 925 { 926 struct HttpResponseHeader *header; 927 928 if (NULL != hr->curl) 929 { 930 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 931 "Resetting cURL handle\n"); 932 curl_easy_cleanup (hr->curl); 933 hr->curl = NULL; 934 hr->io_len = 0; 935 } 936 if (NULL != hr->job) 937 { 938 GNUNET_CURL_job_cancel (hr->job); 939 hr->job = NULL; 940 } 941 if (NULL != hr->headers) 942 { 943 curl_slist_free_all (hr->headers); 944 hr->headers = NULL; 945 } 946 if ( (NULL != hr->response) && 947 (curl_failure_response != hr->response) ) 948 /* Destroy non-error responses... (?) */ 949 MHD_destroy_response (hr->response); 950 951 for (header = hr->header_head; 952 header != NULL; 953 header = hr->header_head) 954 { 955 GNUNET_CONTAINER_DLL_remove (hr->header_head, 956 hr->header_tail, 957 header); 958 GNUNET_free (header->type); 959 GNUNET_free (header->value); 960 GNUNET_free (header); 961 } 962 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 963 "Proxying of '%s' completely done\n", 964 hr->url); 965 966 GNUNET_free (hr->url); 967 GNUNET_free (hr->io_buf); 968 GNUNET_free (hr->client_via); 969 GNUNET_free (hr->client_connection); 970 GNUNET_CONTAINER_DLL_remove (hr_head, 971 hr_tail, 972 hr); 973 GNUNET_free (hr); 974 } 975 976 977 /** 978 * Parse the client's Content-Length header (if any) and decide 979 * whether the declared body size fits within our per-request 980 * buffer cap. Returns false if the header is present, well-formed, 981 * and exceeds `PH_request_buffer_max`; the caller must then 982 * transition to the reject path to suppress the implicit 100 983 * Continue and avoid buffering a body we would only throw away. 984 * 985 * @param con MHD connection to look up the header on 986 * @return true if OK to continue accepting the body 987 */ 988 static bool 989 content_length_ok (struct MHD_Connection *con) 990 { 991 const char *cl_str; 992 char *endptr; 993 unsigned long long cl; 994 995 cl_str = MHD_lookup_connection_value (con, 996 MHD_HEADER_KIND, 997 MHD_HTTP_HEADER_CONTENT_LENGTH); 998 if (NULL == cl_str) 999 return true; 1000 errno = 0; 1001 cl = strtoull (cl_str, 1002 &endptr, 1003 10); 1004 if ( (0 != errno) || 1005 ('\0' != *endptr) ) 1006 return true; /* unparseable — defer judgment to the drain path */ 1007 if (cl <= PH_request_buffer_max) 1008 return true; 1009 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1010 "Rejecting upload: Content-Length %llu exceeds %llu byte limit\n", 1011 cl, 1012 PH_request_buffer_max); 1013 return false; 1014 } 1015 1016 1017 /** 1018 * Append an upload chunk from the client into `hr->io_buf`, growing 1019 * the buffer as needed. Returns false when the chunk would push 1020 * the total past `PH_request_buffer_max`; the caller must then 1021 * transition to the drain path (MHD disallows queuing a response 1022 * while `BODY_RECEIVING`, so we silently discard the rest and 1023 * queue the 413 once MHD calls us back at `FULL_REQ_RECEIVED`). 1024 * 1025 * @param[in,out] hr request we are handling 1026 * @param upload_data_size number of bytes in @a upload_data 1027 * @param upload_data data being uploaded 1028 * @return true on success, false if the upload is too big 1029 */ 1030 static bool 1031 buffer_upload_chunk (struct HttpRequest *hr, 1032 size_t upload_data_size, 1033 const char upload_data[static upload_data_size]) 1034 { 1035 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1036 "Processing %u bytes UPLOAD\n", 1037 (unsigned int) upload_data_size); 1038 if (hr->io_len + upload_data_size > PH_request_buffer_max) 1039 { 1040 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1041 "Upload exceeds %llu byte limit, rejecting\n", 1042 PH_request_buffer_max); 1043 return false; 1044 } 1045 if (hr->io_size - hr->io_len < upload_data_size) 1046 { 1047 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 1048 GNUNET_assert (upload_data_size + hr->io_len > hr->io_len); 1049 GNUNET_array_grow (hr->io_buf, 1050 hr->io_size, 1051 GNUNET_MAX 1052 (hr->io_size * 2 + 1024, 1053 upload_data_size + hr->io_len)); 1054 } 1055 GNUNET_memcpy (&hr->io_buf[hr->io_len], 1056 upload_data, 1057 upload_data_size); 1058 hr->io_len += upload_data_size; 1059 return true; 1060 } 1061 1062 1063 /** 1064 * Choose the curl options for the HTTP method we're proxying and 1065 * set the next proxy state accordingly. Queues an error response 1066 * and returns the corresponding MHD_Result for unsupported methods; 1067 * otherwise returns #MHD_YES and leaves @a hr->state advanced to 1068 * either #PROXY_UPLOAD_STARTED (method has a body to forward) or 1069 * #PROXY_DOWNLOAD_STARTED (bodyless request). 1070 * 1071 * On error, the "curl" handle is set to NULL (!). 1072 * 1073 * @param[in,out] hr the request 1074 * @param con client connection handle from MHD 1075 * @param meth HTTP method specified by the client 1076 * @return MHD status to return 1077 */ 1078 static enum MHD_Result 1079 configure_curl_method (struct HttpRequest *hr, 1080 struct MHD_Connection *con, 1081 const char *meth) 1082 { 1083 if (0 == strcasecmp (meth, 1084 MHD_HTTP_METHOD_GET)) 1085 { 1086 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1087 curl_easy_setopt (hr->curl, 1088 CURLOPT_HTTPGET, 1089 1L); 1090 return MHD_YES; 1091 } 1092 if (0 == strcasecmp (meth, 1093 MHD_HTTP_METHOD_POST)) 1094 { 1095 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1096 "Crafting a CURL POST request\n"); 1097 curl_easy_setopt (hr->curl, 1098 CURLOPT_POST, 1099 1L); 1100 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1101 return MHD_YES; 1102 } 1103 if (0 == strcasecmp (meth, 1104 MHD_HTTP_METHOD_HEAD)) 1105 { 1106 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1107 curl_easy_setopt (hr->curl, 1108 CURLOPT_NOBODY, 1109 1L); 1110 return MHD_YES; 1111 } 1112 if (0 == strcasecmp (meth, 1113 MHD_HTTP_METHOD_PUT)) 1114 { 1115 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1116 "Crafting a CURL PUT request\n"); 1117 curl_easy_setopt (hr->curl, 1118 CURLOPT_UPLOAD, 1119 1L); 1120 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1121 return MHD_YES; 1122 } 1123 if (0 == strcasecmp (meth, 1124 MHD_HTTP_METHOD_DELETE)) 1125 { 1126 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1127 "Crafting a CURL DELETE request\n"); 1128 curl_easy_setopt (hr->curl, 1129 CURLOPT_CUSTOMREQUEST, 1130 "DELETE"); 1131 if (0 != hr->io_len) 1132 { 1133 /* DELETE with a request body is unusual but legal. */ 1134 curl_easy_setopt (hr->curl, 1135 CURLOPT_POST, 1136 1L); 1137 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1138 } 1139 else 1140 { 1141 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1142 } 1143 return MHD_YES; 1144 } 1145 if (0 == strcasecmp (meth, 1146 MHD_HTTP_METHOD_PATCH)) 1147 { 1148 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1149 "Crafting a CURL PATCH request\n"); 1150 /* CURLOPT_POST=1 turns on body upload via the read callback; 1151 CURLOPT_CUSTOMREQUEST then overrides the verb on the wire. */ 1152 curl_easy_setopt (hr->curl, 1153 CURLOPT_POST, 1154 1L); 1155 curl_easy_setopt (hr->curl, 1156 CURLOPT_CUSTOMREQUEST, 1157 "PATCH"); 1158 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1159 return MHD_YES; 1160 } 1161 if (0 == strcasecmp (meth, 1162 MHD_HTTP_METHOD_OPTIONS)) 1163 { 1164 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1165 curl_easy_setopt (hr->curl, 1166 CURLOPT_CUSTOMREQUEST, 1167 "OPTIONS"); 1168 return MHD_YES; 1169 } 1170 /* TRACE leaks headers back to the client; CONNECT is for TLS 1171 tunnelling and doesn't fit the reverse-proxy model. Reject 1172 anything else with a proper 405. */ 1173 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1174 "Unsupported HTTP method `%s'\n", 1175 meth); 1176 curl_easy_cleanup (hr->curl); 1177 hr->curl = NULL; 1178 return MHD_queue_response (con, 1179 MHD_HTTP_METHOD_NOT_ALLOWED, 1180 method_failure_response); 1181 } 1182 1183 1184 /** 1185 * Attach the reverse-proxy forwarding headers (X-Forwarded-For / 1186 * -Proto / -Host and Via) to `hr->headers`. Our X-Forwarded-* 1187 * replace any client-supplied values (filtered in `con_val_iter`); 1188 * Via is appended to whatever chain the client already carried, 1189 * per RFC 9110 §7.6.3. 1190 * 1191 * @param[in,out] hr the request 1192 * @param con MHD connection we are processing 1193 * @param ver HTTP version of the client, as given by MHD 1194 */ 1195 static void 1196 append_forwarded_headers (struct HttpRequest *hr, 1197 struct MHD_Connection *con, 1198 const char *ver) 1199 { 1200 const union MHD_ConnectionInfo *ci; 1201 char *hdr; 1202 const char *proto; 1203 const char *fhost; 1204 const char *via_ver = "1.1"; 1205 1206 ci = MHD_get_connection_info (con, 1207 MHD_CONNECTION_INFO_CLIENT_ADDRESS); 1208 if ( (NULL != ci) && 1209 (NULL != ci->client_addr) ) 1210 { 1211 char ipbuf[INET6_ADDRSTRLEN]; 1212 const char *ip = NULL; 1213 1214 switch (ci->client_addr->sa_family) 1215 { 1216 case AF_INET: 1217 ip = inet_ntop ( 1218 AF_INET, 1219 &((const struct sockaddr_in *) ci->client_addr)->sin_addr, 1220 ipbuf, sizeof (ipbuf)); 1221 break; 1222 case AF_INET6: 1223 ip = inet_ntop ( 1224 AF_INET6, 1225 &((const struct sockaddr_in6 *) ci->client_addr)->sin6_addr, 1226 ipbuf, sizeof (ipbuf)); 1227 break; 1228 default: 1229 break; 1230 } 1231 if (NULL != ip) 1232 { 1233 GNUNET_asprintf (&hdr, 1234 "X-Forwarded-For: %s", 1235 ip); 1236 hr->headers = curl_slist_append (hr->headers, 1237 hdr); 1238 GNUNET_free (hdr); 1239 } 1240 } 1241 proto = (GNUNET_YES == TALER_mhd_is_https (con)) 1242 ? "https" : "http"; 1243 GNUNET_asprintf (&hdr, 1244 "X-Forwarded-Proto: %s", 1245 proto); 1246 hr->headers = curl_slist_append (hr->headers, 1247 hdr); 1248 GNUNET_free (hdr); 1249 fhost = MHD_lookup_connection_value (con, 1250 MHD_HEADER_KIND, 1251 MHD_HTTP_HEADER_HOST); 1252 if (NULL != fhost) 1253 { 1254 GNUNET_asprintf (&hdr, 1255 "X-Forwarded-Host: %s", 1256 fhost); 1257 hr->headers = curl_slist_append (hr->headers, 1258 hdr); 1259 GNUNET_free (hdr); 1260 } 1261 /* MHD hands us e.g. "HTTP/1.1" but Via wants just "1.1". */ 1262 if ( (NULL != ver) && 1263 (0 == strncasecmp (ver, 1264 "HTTP/", 1265 strlen ("HTTP/"))) ) 1266 via_ver = ver + 5; 1267 if (NULL != hr->client_via) 1268 GNUNET_asprintf (&hdr, 1269 "%s: %s, %s paivana", 1270 MHD_HTTP_HEADER_VIA, 1271 hr->client_via, 1272 via_ver); 1273 else 1274 GNUNET_asprintf (&hdr, 1275 "%s: %s paivana", 1276 MHD_HTTP_HEADER_VIA, 1277 via_ver); 1278 hr->headers = curl_slist_append (hr->headers, 1279 hdr); 1280 GNUNET_free (hdr); 1281 } 1282 1283 1284 /** 1285 * Initialize the curl handle, attach the forwarding headers, and 1286 * hand the request off to the curl multi loop. On success 1287 * advances @a hr->state to one of the PROXY_*_STARTED states and 1288 * returns #MHD_YES. On any failure queues an appropriate error 1289 * response and returns its MHD_Result. 1290 * 1291 * On error, the "curl" handle is set to NULL (!). 1292 * 1293 * @param[in,out] hr request we are handling 1294 * @param con MHD connection handle 1295 * @param meth HTTP method of the request 1296 * @param ver HTTP version to use 1297 * @return MHD status code to return 1298 */ 1299 static enum MHD_Result 1300 start_curl_request (struct HttpRequest *hr, 1301 struct MHD_Connection *con, 1302 const char *meth, 1303 const char *ver) 1304 { 1305 enum MHD_Result r; 1306 1307 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1308 "Generating curl request\n"); 1309 hr->curl = curl_easy_init (); 1310 if (NULL == hr->curl) 1311 { 1312 PAIVANA_LOG_ERROR ("Could not init the curl handle\n"); 1313 hr->suspended = GNUNET_NO; 1314 MHD_resume_connection (hr->con); 1315 return MHD_queue_response (con, 1316 MHD_HTTP_INTERNAL_SERVER_ERROR, 1317 internal_failure_response); 1318 } 1319 1320 /* No need to check whether we're POSTing or PUTting. 1321 * If not needed, one of the following values will be 1322 * ignored.*/ 1323 curl_easy_setopt (hr->curl, 1324 CURLOPT_POSTFIELDSIZE, 1325 hr->io_len); 1326 curl_easy_setopt (hr->curl, 1327 CURLOPT_INFILESIZE, 1328 hr->io_len); 1329 curl_easy_setopt (hr->curl, 1330 CURLOPT_HEADERFUNCTION, 1331 &curl_check_hdr); 1332 curl_easy_setopt (hr->curl, 1333 CURLOPT_HEADERDATA, 1334 hr); 1335 curl_easy_setopt (hr->curl, 1336 CURLOPT_FOLLOWLOCATION, 1337 0); 1338 curl_easy_setopt (hr->curl, 1339 CURLOPT_CONNECTTIMEOUT, 1340 60L); 1341 curl_easy_setopt (hr->curl, 1342 CURLOPT_TIMEOUT, 1343 60L); 1344 curl_easy_setopt (hr->curl, 1345 CURLOPT_NOSIGNAL, 1346 1L); 1347 curl_easy_setopt (hr->curl, 1348 CURLOPT_PRIVATE, 1349 hr); 1350 curl_easy_setopt (hr->curl, 1351 CURLOPT_VERBOSE, 1352 0); 1353 curl_easy_setopt (hr->curl, 1354 CURLOPT_READFUNCTION, 1355 &curl_upload_cb); 1356 curl_easy_setopt (hr->curl, 1357 CURLOPT_READDATA, 1358 hr); 1359 { 1360 char *curlurl; 1361 1362 GNUNET_asprintf (&curlurl, 1363 "%s%s", 1364 PH_target_server_base_url, 1365 hr->url); 1366 curl_easy_setopt (hr->curl, 1367 CURLOPT_URL, 1368 curlurl); 1369 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1370 "Forwarding request to: %s\n", 1371 curlurl); 1372 GNUNET_free (curlurl); 1373 } 1374 1375 if (NULL != PH_target_server_unixpath) 1376 { 1377 curl_easy_setopt (hr->curl, 1378 CURLOPT_UNIX_SOCKET_PATH, 1379 PH_target_server_unixpath); 1380 1381 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1382 "Forwarding using unixpath: %s\n", 1383 PH_target_server_unixpath); 1384 } 1385 1386 { 1387 char *host_hdr; 1388 1389 host_hdr = build_host_header (PH_target_server_base_url); 1390 PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", 1391 host_hdr); 1392 hr->headers = curl_slist_append (hr->headers, 1393 host_hdr); 1394 GNUNET_free (host_hdr); 1395 } 1396 1397 r = configure_curl_method (hr, 1398 con, 1399 meth); 1400 if (NULL == hr->curl) 1401 { 1402 hr->suspended = GNUNET_NO; 1403 MHD_resume_connection (hr->con); 1404 return r; /* unsupported method: response already queued */ 1405 } 1406 /* First pass: collect Via / Connection so `con_val_iter` can 1407 honor them (append to Via; drop headers named by Connection). */ 1408 MHD_get_connection_values (con, 1409 MHD_HEADER_KIND, 1410 &collect_proxy_state, 1411 hr); 1412 MHD_get_connection_values (con, 1413 MHD_HEADER_KIND, 1414 &con_val_iter, 1415 hr); 1416 append_forwarded_headers (hr, 1417 con, 1418 ver); 1419 hr->job = GNUNET_CURL_job_add_raw (PH_ctx, 1420 hr->curl, 1421 hr->headers, 1422 &curl_download_cb, 1423 hr); 1424 hr->curl = NULL; 1425 if (NULL == hr->job) 1426 { 1427 GNUNET_break (0); 1428 hr->suspended = GNUNET_NO; 1429 MHD_resume_connection (hr->con); 1430 return MHD_queue_response (con, 1431 MHD_HTTP_BAD_GATEWAY, 1432 curl_failure_response); 1433 } 1434 return MHD_YES; 1435 } 1436 1437 1438 /** 1439 * Build the final MHD response from the accumulated upstream body 1440 * (or the pre-built failure page, on curl error) and queue it. 1441 * 1442 * @param[in,out] request handle 1443 * @param con MHD client connection to send response on 1444 */ 1445 static enum MHD_Result 1446 finalize_response (struct HttpRequest *hr, 1447 struct MHD_Connection *con) 1448 { 1449 /* `hr->response` may already be set to `curl_failure_response` by 1450 the curl task on upstream failure; in that case, don't build a 1451 buffer response and don't attach per-request headers to the 1452 shared failure response. */ 1453 if (NULL == hr->response) 1454 { 1455 GNUNET_break (0); 1456 hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 1457 hr->response = internal_failure_response; 1458 } 1459 return MHD_queue_response (con, 1460 hr->response_code, 1461 hr->response); 1462 } 1463 1464 1465 /** 1466 * Main MHD callback for reverse proxy. 1467 * 1468 * Pure state-machine dispatch: each invocation picks up @a hr->state, 1469 * runs the transitions it can without new input from MHD, and 1470 * either returns to wait for more MHD data / curl progress, or 1471 * cascades through the `while` loop to the next applicable state. 1472 * 1473 * @param hr the HTTP request context 1474 * @param con MHD connection handle 1475 * @param url the url in the request (unused; kept for ABI symmetry 1476 * with the MHD access handler signature) 1477 * @param meth the HTTP method used ("GET", "PUT", etc.) 1478 * @param ver the HTTP version string (i.e. "HTTP/1.1") 1479 * @param upload_data the data being uploaded (excluding HEADERS) 1480 * @param upload_data_size set initially to the size of the 1481 * @a upload_data provided; the method must update this 1482 * value to the number of bytes NOT processed; 1483 * @return #MHD_YES if the connection was handled successfully, 1484 * #MHD_NO if the socket must be closed due to a serious 1485 * error while handling the request 1486 */ 1487 enum MHD_Result 1488 PAIVANA_HTTPD_reverse (struct HttpRequest *hr, 1489 struct MHD_Connection *con, 1490 const char *url, 1491 const char *meth, 1492 const char *ver, 1493 const char *upload_data, 1494 size_t *upload_data_size) 1495 { 1496 (void) url; 1497 1498 while (true) 1499 { 1500 switch (hr->state) 1501 { 1502 case REQUEST_STATE_HEADERS_PENDING: 1503 /* MHD's HEADERS_PROCESSED callback: we can still queue a 1504 response here before the client body is consumed, so this 1505 is our only chance to short-circuit an oversized upload 1506 based on Content-Length and suppress the implicit 100 1507 Continue (RFC 7231 §5.1.1). */ 1508 if (! content_length_ok (con)) 1509 { 1510 hr->state = REQUEST_STATE_REJECT_UPLOAD; 1511 continue; 1512 } 1513 hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED; 1514 return MHD_YES; 1515 1516 case REQUEST_STATE_CLIENT_UPLOAD_STARTED: 1517 if (0 == *upload_data_size) 1518 { 1519 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1520 "Finished processing UPLOAD\n"); 1521 hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; 1522 continue; 1523 } 1524 if (buffer_upload_chunk (hr, 1525 *upload_data_size, 1526 upload_data)) 1527 { 1528 *upload_data_size = 0; 1529 return MHD_YES; 1530 } 1531 hr->state = REQUEST_STATE_REJECT_UPLOAD_DRAIN; 1532 continue; 1533 1534 case REQUEST_STATE_REJECT_UPLOAD_DRAIN: 1535 /* MHD disallows queuing a response while BODY_RECEIVING, so 1536 silently discard the remaining body bytes. Once MHD calls 1537 us with `upload_data_size == 0` (FULL_REQ_RECEIVED) we can 1538 transition to REJECT_UPLOAD and queue the deferred 413. */ 1539 if (0 != *upload_data_size) 1540 { 1541 *upload_data_size = 0; 1542 return MHD_YES; 1543 } 1544 hr->state = REQUEST_STATE_REJECT_UPLOAD; 1545 continue; 1546 1547 case REQUEST_STATE_REJECT_UPLOAD: 1548 return MHD_queue_response (con, 1549 MHD_HTTP_CONTENT_TOO_LARGE, 1550 upload_failure_response); 1551 1552 case REQUEST_STATE_CLIENT_UPLOAD_DONE: 1553 /* start_curl_request advances state to PROXY_UPLOAD_STARTED 1554 or PROXY_DOWNLOAD_STARTED on success and returns MHD_YES; 1555 on error it queues a response and returns its MHD_Result. */ 1556 GNUNET_assert (GNUNET_NO == hr->suspended); 1557 MHD_suspend_connection (con); 1558 hr->suspended = GNUNET_YES; 1559 return start_curl_request (hr, 1560 con, 1561 meth, 1562 ver); 1563 1564 case REQUEST_STATE_PROXY_UPLOAD_STARTED: 1565 case REQUEST_STATE_PROXY_UPLOAD_DONE: 1566 case REQUEST_STATE_PROXY_DOWNLOAD_STARTED: 1567 /* we should not have been resumed in this state, 1568 how did we get here? */ 1569 GNUNET_break (0); 1570 GNUNET_assert (GNUNET_NO == hr->suspended); 1571 MHD_suspend_connection (con); 1572 hr->suspended = GNUNET_YES; 1573 return MHD_YES; 1574 1575 case REQUEST_STATE_PROXY_DOWNLOAD_DONE: 1576 return finalize_response (hr, 1577 con); 1578 case REQUEST_STATE_PROXY_DOWNLOAD_FAILED: 1579 return MHD_queue_response (con, 1580 MHD_HTTP_BAD_GATEWAY, 1581 curl_failure_response); 1582 } 1583 GNUNET_assert (0); /* unreachable */ 1584 } 1585 }