post_example.c (22772B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2011 Christian Grothoff (and other contributing authors) 4 Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) 5 6 This library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 This library is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with this library; if not, write to the Free Software 18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 /** 21 * @file post_example.c 22 * @brief example for processing POST requests using libmicrohttpd 23 * @author Christian Grothoff 24 * @author Karlson2k (Evgeny Grin) 25 */ 26 27 #include <stdlib.h> 28 #include <string.h> 29 #include <stdio.h> 30 #include <errno.h> 31 #include <time.h> 32 #include <microhttpd.h> 33 34 /** 35 * Invalid method page. 36 */ 37 #define METHOD_ERROR \ 38 "<html><head><title>Illegal request</title></head><body>Go away.</body></html>" 39 40 /** 41 * Invalid URL page. 42 */ 43 #define NOT_FOUND_ERROR \ 44 "<html><head><title>Not found</title></head><body>Go away.</body></html>" 45 46 /** 47 * Front page. (/) 48 */ 49 #define MAIN_PAGE \ 50 "<html><head><title>Welcome</title></head><body><form action=\"/2\" method=\"post\">What is your name? <input type=\"text\" name=\"v1\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></form></body></html>" 51 52 /** 53 * Second page. (/2) 54 */ 55 #define SECOND_PAGE \ 56 "<html><head><title>Tell me more</title></head><body><a href=\"/\">previous</a> <form action=\"/S\" method=\"post\">%s, what is your job? <input type=\"text\" name=\"v2\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></form></body></html>" 57 58 /** 59 * Second page (/S) 60 */ 61 #define SUBMIT_PAGE \ 62 "<html><head><title>Ready to submit?</title></head><body><form action=\"/F\" method=\"post\"><a href=\"/2\">previous </a> <input type=\"hidden\" name=\"DONE\" value=\"yes\" /><input type=\"submit\" value=\"Submit\" /></form></body></html>" 63 64 /** 65 * Last page. 66 */ 67 #define LAST_PAGE \ 68 "<html><head><title>Thank you</title></head><body>Thank you.</body></html>" 69 70 /** 71 * Name of our cookie. 72 */ 73 #define COOKIE_NAME "session" 74 75 76 /** 77 * State we keep for each user/session/browser. 78 */ 79 struct Session 80 { 81 /** 82 * We keep all sessions in a linked list. 83 */ 84 struct Session *next; 85 86 /** 87 * Unique ID for this session. 88 */ 89 char sid[33]; 90 91 /** 92 * Reference counter giving the number of connections 93 * currently using this session. 94 */ 95 unsigned int rc; 96 97 /** 98 * Time when this session was last active. 99 */ 100 time_t start; 101 102 /** 103 * String submitted via form. 104 */ 105 char value_1[64]; 106 107 /** 108 * Another value submitted via form. 109 */ 110 char value_2[64]; 111 112 }; 113 114 115 /** 116 * Data kept per request. 117 */ 118 struct Request 119 { 120 121 /** 122 * Associated session. 123 */ 124 struct Session *session; 125 126 /** 127 * Post processor handling form data (IF this is 128 * a POST request). 129 */ 130 struct MHD_PostProcessor *pp; 131 132 /** 133 * URL to serve in response to this POST (if this request 134 * was a 'POST') 135 */ 136 const char *post_url; 137 138 }; 139 140 141 /** 142 * Linked list of all active sessions. Yes, O(n) but a 143 * hash table would be overkill for a simple example... 144 */ 145 static struct Session *sessions; 146 147 148 /** 149 * Return the session handle for this connection, or 150 * create one if this is a new user. 151 */ 152 static struct Session * 153 get_session (struct MHD_Connection *connection) 154 { 155 struct Session *ret; 156 const char *cookie; 157 158 cookie = MHD_lookup_connection_value (connection, 159 MHD_COOKIE_KIND, 160 COOKIE_NAME); 161 if (cookie != NULL) 162 { 163 /* find existing session */ 164 ret = sessions; 165 while (NULL != ret) 166 { 167 if (0 == strcmp (cookie, ret->sid)) 168 break; 169 ret = ret->next; 170 } 171 if (NULL != ret) 172 { 173 ret->rc++; 174 return ret; 175 } 176 } 177 /* create fresh session */ 178 ret = calloc (1, sizeof (struct Session)); 179 if (NULL == ret) 180 { 181 fprintf (stderr, "calloc error: %s\n", strerror (errno)); 182 return NULL; 183 } 184 /* not a super-secure way to generate a random session ID, 185 but should do for a simple example... */ 186 snprintf (ret->sid, 187 sizeof (ret->sid), 188 "%X%X%X%X", 189 (unsigned int) rand (), 190 (unsigned int) rand (), 191 (unsigned int) rand (), 192 (unsigned int) rand ()); 193 ret->rc++; 194 ret->start = time (NULL); 195 ret->next = sessions; 196 sessions = ret; 197 return ret; 198 } 199 200 201 /** 202 * Type of handler that generates a reply. 203 * 204 * @param cls content for the page (handler-specific) 205 * @param mime mime type to use 206 * @param session session information 207 * @param connection connection to process 208 * @param #MHD_YES on success, #MHD_NO on failure 209 */ 210 typedef enum MHD_Result 211 (*PageHandler)(const void *cls, 212 const char *mime, 213 struct Session *session, 214 struct MHD_Connection *connection); 215 216 217 /** 218 * Entry we generate for each page served. 219 */ 220 struct Page 221 { 222 /** 223 * Acceptable URL for this page. 224 */ 225 const char *url; 226 227 /** 228 * Mime type to set for the page. 229 */ 230 const char *mime; 231 232 /** 233 * Handler to call to generate response. 234 */ 235 PageHandler handler; 236 237 /** 238 * Extra argument to handler. 239 */ 240 const void *handler_cls; 241 }; 242 243 244 /** 245 * Add header to response to set a session cookie. 246 * 247 * @param session session to use 248 * @param response response to modify 249 */ 250 static void 251 add_session_cookie (struct Session *session, 252 struct MHD_Response *response) 253 { 254 char cstr[256]; 255 snprintf (cstr, 256 sizeof (cstr), 257 "%s=%s", 258 COOKIE_NAME, 259 session->sid); 260 if (MHD_NO == 261 MHD_add_response_header (response, 262 MHD_HTTP_HEADER_SET_COOKIE, 263 cstr)) 264 { 265 fprintf (stderr, 266 "Failed to set session cookie header!\n"); 267 } 268 } 269 270 271 /** 272 * Handler that returns a simple static HTTP page that 273 * is passed in via 'cls'. 274 * 275 * @param cls a 'const char *' with the HTML webpage to return 276 * @param mime mime type to use 277 * @param session session handle 278 * @param connection connection to use 279 */ 280 static enum MHD_Result 281 serve_simple_form (const void *cls, 282 const char *mime, 283 struct Session *session, 284 struct MHD_Connection *connection) 285 { 286 enum MHD_Result ret; 287 const char *form = cls; 288 struct MHD_Response *response; 289 290 /* return static form */ 291 response = MHD_create_response_from_buffer_static (strlen (form), 292 (const void *) form); 293 if (NULL == response) 294 return MHD_NO; 295 add_session_cookie (session, response); 296 if (MHD_YES != 297 MHD_add_response_header (response, 298 MHD_HTTP_HEADER_CONTENT_ENCODING, 299 mime)) 300 { 301 fprintf (stderr, 302 "Failed to set content encoding header!\n"); 303 } 304 ret = MHD_queue_response (connection, 305 MHD_HTTP_OK, 306 response); 307 MHD_destroy_response (response); 308 return ret; 309 } 310 311 312 /** 313 * Handler that adds the 'v1' value to the given HTML code. 314 * 315 * @param cls unused 316 * @param mime mime type to use 317 * @param session session handle 318 * @param connection connection to use 319 */ 320 static enum MHD_Result 321 fill_v1_form (const void *cls, 322 const char *mime, 323 struct Session *session, 324 struct MHD_Connection *connection) 325 { 326 enum MHD_Result ret; 327 size_t slen; 328 char *reply; 329 struct MHD_Response *response; 330 (void) cls; /* Unused. Silent compiler warning. */ 331 332 slen = strlen (MAIN_PAGE) + strlen (session->value_1); 333 reply = malloc (slen + 1); 334 if (NULL == reply) 335 return MHD_NO; 336 snprintf (reply, 337 slen + 1, 338 MAIN_PAGE, 339 session->value_1); 340 /* return static form */ 341 response = 342 MHD_create_response_from_buffer_with_free_callback (slen, 343 (void *) reply, 344 &free); 345 if (NULL == response) 346 { 347 free (reply); 348 return MHD_NO; 349 } 350 add_session_cookie (session, response); 351 if (MHD_YES != 352 MHD_add_response_header (response, 353 MHD_HTTP_HEADER_CONTENT_ENCODING, 354 mime)) 355 { 356 fprintf (stderr, 357 "Failed to set content encoding header!\n"); 358 } 359 ret = MHD_queue_response (connection, 360 MHD_HTTP_OK, 361 response); 362 MHD_destroy_response (response); 363 return ret; 364 } 365 366 367 /** 368 * Handler that adds the 'v1' and 'v2' values to the given HTML code. 369 * 370 * @param cls unused 371 * @param mime mime type to use 372 * @param session session handle 373 * @param connection connection to use 374 */ 375 static enum MHD_Result 376 fill_v1_v2_form (const void *cls, 377 const char *mime, 378 struct Session *session, 379 struct MHD_Connection *connection) 380 { 381 enum MHD_Result ret; 382 char *reply; 383 struct MHD_Response *response; 384 size_t slen; 385 (void) cls; /* Unused. Silent compiler warning. */ 386 387 slen = strlen (SECOND_PAGE) + strlen (session->value_1) 388 + strlen (session->value_2); 389 reply = malloc (slen + 1); 390 if (NULL == reply) 391 return MHD_NO; 392 snprintf (reply, 393 slen + 1, 394 SECOND_PAGE, 395 session->value_1, 396 session->value_2); 397 /* return static form */ 398 response = 399 MHD_create_response_from_buffer_with_free_callback (slen, 400 (void *) reply, 401 &free); 402 if (NULL == response) 403 { 404 free (reply); 405 return MHD_NO; 406 } 407 add_session_cookie (session, response); 408 if (MHD_YES != 409 MHD_add_response_header (response, 410 MHD_HTTP_HEADER_CONTENT_ENCODING, 411 mime)) 412 { 413 fprintf (stderr, 414 "Failed to set content encoding header!\n"); 415 } 416 ret = MHD_queue_response (connection, 417 MHD_HTTP_OK, 418 response); 419 MHD_destroy_response (response); 420 return ret; 421 } 422 423 424 /** 425 * Handler used to generate a 404 reply. 426 * 427 * @param cls a 'const char *' with the HTML webpage to return 428 * @param mime mime type to use 429 * @param session session handle 430 * @param connection connection to use 431 */ 432 static enum MHD_Result 433 not_found_page (const void *cls, 434 const char *mime, 435 struct Session *session, 436 struct MHD_Connection *connection) 437 { 438 enum MHD_Result ret; 439 struct MHD_Response *response; 440 (void) cls; /* Unused. Silent compiler warning. */ 441 (void) session; /* Unused. Silent compiler warning. */ 442 443 /* unsupported HTTP method */ 444 response = 445 MHD_create_response_from_buffer_static (strlen (NOT_FOUND_ERROR), 446 (const void *) NOT_FOUND_ERROR); 447 if (NULL == response) 448 return MHD_NO; 449 ret = MHD_queue_response (connection, 450 MHD_HTTP_NOT_FOUND, 451 response); 452 if (MHD_YES != 453 MHD_add_response_header (response, 454 MHD_HTTP_HEADER_CONTENT_ENCODING, 455 mime)) 456 { 457 fprintf (stderr, 458 "Failed to set content encoding header!\n"); 459 } 460 MHD_destroy_response (response); 461 return ret; 462 } 463 464 465 /** 466 * List of all pages served by this HTTP server. 467 */ 468 static struct Page pages[] = { 469 { "/", "text/html", &fill_v1_form, NULL }, 470 { "/2", "text/html", &fill_v1_v2_form, NULL }, 471 { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE }, 472 { "/F", "text/html", &serve_simple_form, LAST_PAGE }, 473 { NULL, NULL, ¬_found_page, NULL } /* 404 */ 474 }; 475 476 477 /** 478 * Iterator over key-value pairs where the value 479 * maybe made available in increments and/or may 480 * not be zero-terminated. Used for processing 481 * POST data. 482 * 483 * @param cls user-specified closure 484 * @param kind type of the value 485 * @param key 0-terminated key for the value 486 * @param filename name of the uploaded file, NULL if not known 487 * @param content_type mime-type of the data, NULL if not known 488 * @param transfer_encoding encoding of the data, NULL if not known 489 * @param data pointer to size bytes of data at the 490 * specified offset 491 * @param off offset of data in the overall value 492 * @param size number of bytes in data available 493 * @return MHD_YES to continue iterating, 494 * MHD_NO to abort the iteration 495 */ 496 static enum MHD_Result 497 post_iterator (void *cls, 498 enum MHD_ValueKind kind, 499 const char *key, 500 const char *filename, 501 const char *content_type, 502 const char *transfer_encoding, 503 const char *data, uint64_t off, size_t size) 504 { 505 struct Request *request = cls; 506 struct Session *session = request->session; 507 (void) kind; /* Unused. Silent compiler warning. */ 508 (void) filename; /* Unused. Silent compiler warning. */ 509 (void) content_type; /* Unused. Silent compiler warning. */ 510 (void) transfer_encoding; /* Unused. Silent compiler warning. */ 511 512 if (0 == strcmp ("DONE", key)) 513 { 514 fprintf (stdout, 515 "Session `%s' submitted `%s', `%s'\n", 516 session->sid, 517 session->value_1, 518 session->value_2); 519 return MHD_YES; 520 } 521 if (0 == strcmp ("v1", key)) 522 { 523 if (off >= sizeof(session->value_1) - 1) 524 return MHD_YES; /* Discard extra data */ 525 if (size + off >= sizeof(session->value_1)) 526 size = (size_t) (sizeof (session->value_1) - off - 1); /* crop extra data */ 527 memcpy (&session->value_1[off], 528 data, 529 size); 530 session->value_1[size + off] = '\0'; 531 return MHD_YES; 532 } 533 if (0 == strcmp ("v2", key)) 534 { 535 if (off >= sizeof(session->value_2) - 1) 536 return MHD_YES; /* Discard extra data */ 537 if (size + off >= sizeof(session->value_2)) 538 size = (size_t) (sizeof (session->value_2) - off - 1); /* crop extra data */ 539 memcpy (&session->value_2[off], 540 data, 541 size); 542 session->value_2[size + off] = '\0'; 543 return MHD_YES; 544 } 545 fprintf (stderr, 546 "Unsupported form value `%s'\n", 547 key); 548 return MHD_YES; 549 } 550 551 552 /** 553 * Main MHD callback for handling requests. 554 * 555 * @param cls argument given together with the function 556 * pointer when the handler was registered with MHD 557 * @param connection handle identifying the incoming connection 558 * @param url the requested url 559 * @param method the HTTP method used ("GET", "PUT", etc.) 560 * @param version the HTTP version string (i.e. "HTTP/1.1") 561 * @param upload_data the data being uploaded (excluding HEADERS, 562 * for a POST that fits into memory and that is encoded 563 * with a supported encoding, the POST data will NOT be 564 * given in upload_data and is instead available as 565 * part of MHD_get_connection_values; very large POST 566 * data *will* be made available incrementally in 567 * upload_data) 568 * @param upload_data_size set initially to the size of the 569 * upload_data provided; the method must update this 570 * value to the number of bytes NOT processed; 571 * @param req_cls pointer that the callback can set to some 572 * address and that will be preserved by MHD for future 573 * calls for this request; since the access handler may 574 * be called many times (i.e., for a PUT/POST operation 575 * with plenty of upload data) this allows the application 576 * to easily associate some request-specific state. 577 * If necessary, this state can be cleaned up in the 578 * global "MHD_RequestCompleted" callback (which 579 * can be set with the MHD_OPTION_NOTIFY_COMPLETED). 580 * Initially, <tt>*req_cls</tt> will be NULL. 581 * @return MHS_YES if the connection was handled successfully, 582 * MHS_NO if the socket must be closed due to a serious 583 * error while handling the request 584 */ 585 static enum MHD_Result 586 create_response (void *cls, 587 struct MHD_Connection *connection, 588 const char *url, 589 const char *method, 590 const char *version, 591 const char *upload_data, 592 size_t *upload_data_size, 593 void **req_cls) 594 { 595 struct MHD_Response *response; 596 struct Request *request; 597 struct Session *session; 598 enum MHD_Result ret; 599 unsigned int i; 600 (void) cls; /* Unused. Silent compiler warning. */ 601 (void) version; /* Unused. Silent compiler warning. */ 602 603 request = *req_cls; 604 if (NULL == request) 605 { 606 request = calloc (1, sizeof (struct Request)); 607 if (NULL == request) 608 { 609 fprintf (stderr, "calloc error: %s\n", strerror (errno)); 610 return MHD_NO; 611 } 612 *req_cls = request; 613 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 614 { 615 request->pp = MHD_create_post_processor (connection, 1024, 616 &post_iterator, request); 617 if (NULL == request->pp) 618 { 619 fprintf (stderr, "Failed to setup post processor for `%s'\n", 620 url); 621 return MHD_NO; /* internal error */ 622 } 623 } 624 return MHD_YES; 625 } 626 if (NULL == request->session) 627 { 628 request->session = get_session (connection); 629 if (NULL == request->session) 630 { 631 fprintf (stderr, "Failed to setup session for `%s'\n", 632 url); 633 return MHD_NO; /* internal error */ 634 } 635 } 636 session = request->session; 637 session->start = time (NULL); 638 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 639 { 640 /* evaluate POST data */ 641 if (MHD_YES != 642 MHD_post_process (request->pp, 643 upload_data, 644 *upload_data_size)) 645 return MHD_NO; 646 if (0 != *upload_data_size) 647 { 648 *upload_data_size = 0; 649 return MHD_YES; 650 } 651 /* done with POST data, serve response */ 652 MHD_destroy_post_processor (request->pp); 653 request->pp = NULL; 654 method = MHD_HTTP_METHOD_GET; /* fake 'GET' */ 655 if (NULL != request->post_url) 656 url = request->post_url; 657 } 658 659 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) || 660 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) ) 661 { 662 /* find out which page to serve */ 663 i = 0; 664 while ( (pages[i].url != NULL) && 665 (0 != strcmp (pages[i].url, url)) ) 666 i++; 667 ret = pages[i].handler (pages[i].handler_cls, 668 pages[i].mime, 669 session, connection); 670 if (ret != MHD_YES) 671 fprintf (stderr, "Failed to create page for `%s'\n", 672 url); 673 return ret; 674 } 675 /* unsupported HTTP method */ 676 response = 677 MHD_create_response_from_buffer_static (strlen (METHOD_ERROR), 678 (const void *) METHOD_ERROR); 679 ret = MHD_queue_response (connection, 680 MHD_HTTP_NOT_ACCEPTABLE, 681 response); 682 MHD_destroy_response (response); 683 return ret; 684 } 685 686 687 /** 688 * Callback called upon completion of a request. 689 * Decrements session reference counter. 690 * 691 * @param cls not used 692 * @param connection connection that completed 693 * @param req_cls session handle 694 * @param toe status code 695 */ 696 static void 697 request_completed_callback (void *cls, 698 struct MHD_Connection *connection, 699 void **req_cls, 700 enum MHD_RequestTerminationCode toe) 701 { 702 struct Request *request = *req_cls; 703 (void) cls; /* Unused. Silent compiler warning. */ 704 (void) connection; /* Unused. Silent compiler warning. */ 705 (void) toe; /* Unused. Silent compiler warning. */ 706 707 if (NULL == request) 708 return; 709 if (NULL != request->session) 710 request->session->rc--; 711 if (NULL != request->pp) 712 MHD_destroy_post_processor (request->pp); 713 free (request); 714 } 715 716 717 /** 718 * Clean up handles of sessions that have been idle for 719 * too long. 720 */ 721 static void 722 expire_sessions (void) 723 { 724 struct Session *pos; 725 struct Session *prev; 726 struct Session *next; 727 time_t now; 728 729 now = time (NULL); 730 prev = NULL; 731 pos = sessions; 732 while (NULL != pos) 733 { 734 next = pos->next; 735 if (now - pos->start > 60 * 60) 736 { 737 /* expire sessions after 1h */ 738 if (NULL == prev) 739 sessions = pos->next; 740 else 741 prev->next = next; 742 free (pos); 743 } 744 else 745 prev = pos; 746 pos = next; 747 } 748 } 749 750 751 /** 752 * Call with the port number as the only argument. 753 * Never terminates (other than by signals, such as CTRL-C). 754 */ 755 int 756 main (int argc, char *const *argv) 757 { 758 struct MHD_Daemon *d; 759 struct timeval tv; 760 struct timeval *tvp; 761 fd_set rs; 762 fd_set ws; 763 fd_set es; 764 MHD_socket max; 765 uint64_t mhd_timeout; 766 int port; 767 768 if (argc != 2) 769 { 770 printf ("%s PORT\n", argv[0]); 771 return 1; 772 } 773 port = atoi (argv[1]); 774 if ( (1 > port) || (port > 65535) ) 775 { 776 fprintf (stderr, 777 "Port must be a number between 1 and 65535.\n"); 778 return 1; 779 } 780 /* initialize PRNG */ 781 srand ((unsigned int) time (NULL)); 782 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 783 (uint16_t) port, 784 NULL, NULL, 785 &create_response, NULL, 786 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, 787 MHD_OPTION_NOTIFY_COMPLETED, 788 &request_completed_callback, NULL, 789 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 790 MHD_OPTION_END); 791 if (NULL == d) 792 return 1; 793 while (1) 794 { 795 expire_sessions (); 796 max = 0; 797 FD_ZERO (&rs); 798 FD_ZERO (&ws); 799 FD_ZERO (&es); 800 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 801 break; /* fatal internal error */ 802 if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES) 803 { 804 #if ! defined(_WIN32) || defined(__CYGWIN__) 805 tv.tv_sec = (time_t) (mhd_timeout / 1000LL); 806 #else /* Native W32 */ 807 tv.tv_sec = (long) (mhd_timeout / 1000LL); 808 #endif /* Native W32 */ 809 tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000; 810 tvp = &tv; 811 } 812 else 813 tvp = NULL; 814 if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp)) 815 { 816 if (EINTR != errno) 817 abort (); 818 } 819 MHD_run (d); 820 } 821 MHD_stop_daemon (d); 822 return 0; 823 }