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