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