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