sessions.c (23089B)
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 /* NOTE: headers must be added _before_ the response is queued, 453 MHD refuses to modify a response that was already queued. */ 454 if (MHD_YES != 455 MHD_add_response_header (response, 456 MHD_HTTP_HEADER_CONTENT_TYPE, 457 mime)) 458 { 459 fprintf (stderr, 460 "Failed to set content type header!\n"); 461 /* return response without content type anyway ... */ 462 } 463 ret = MHD_queue_response (connection, 464 MHD_HTTP_NOT_FOUND, 465 response); 466 MHD_destroy_response (response); 467 return ret; 468 } 469 470 471 /** 472 * List of all pages served by this HTTP server. 473 */ 474 static const struct Page pages[] = { 475 { "/", "text/html", &fill_v1_form, NULL }, 476 { "/2", "text/html", &fill_v1_v2_form, NULL }, 477 { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE }, 478 { "/F", "text/html", &serve_simple_form, LAST_PAGE }, 479 { NULL, "text/html", ¬_found_page, NULL } /* 404 */ 480 }; 481 482 483 /** 484 * Iterator over key-value pairs where the value 485 * maybe made available in increments and/or may 486 * not be zero-terminated. Used for processing 487 * POST data. 488 * 489 * @param cls user-specified closure 490 * @param kind type of the value 491 * @param key 0-terminated key for the value 492 * @param filename name of the uploaded file, NULL if not known 493 * @param content_type mime-type of the data, NULL if not known 494 * @param transfer_encoding encoding of the data, NULL if not known 495 * @param data pointer to size bytes of data at the 496 * specified offset 497 * @param off offset of data in the overall value 498 * @param size number of bytes in data available 499 * @return #MHD_YES to continue iterating, 500 * #MHD_NO to abort the iteration 501 */ 502 static enum MHD_Result 503 post_iterator (void *cls, 504 enum MHD_ValueKind kind, 505 const char *key, 506 const char *filename, 507 const char *content_type, 508 const char *transfer_encoding, 509 const char *data, uint64_t off, size_t size) 510 { 511 struct Request *request = cls; 512 struct Session *session = request->session; 513 (void) kind; /* Unused. Silent compiler warning. */ 514 (void) filename; /* Unused. Silent compiler warning. */ 515 (void) content_type; /* Unused. Silent compiler warning. */ 516 (void) transfer_encoding; /* Unused. Silent compiler warning. */ 517 518 if (0 == strcmp ("DONE", key)) 519 { 520 fprintf (stdout, 521 "Session `%s' submitted `%s', `%s'\n", 522 session->sid, 523 session->value_1, 524 session->value_2); 525 return MHD_YES; 526 } 527 if (0 == strcmp ("v1", key)) 528 { 529 if (off >= sizeof(session->value_1) - 1) 530 return MHD_YES; /* Discard extra data */ 531 if (size + off >= sizeof(session->value_1)) 532 size = (size_t) (sizeof (session->value_1) - off - 1); /* crop extra data */ 533 memcpy (&session->value_1[off], 534 data, 535 size); 536 if (size + off < sizeof (session->value_1)) 537 session->value_1[size + off] = '\0'; 538 return MHD_YES; 539 } 540 if (0 == strcmp ("v2", key)) 541 { 542 if (off >= sizeof(session->value_2) - 1) 543 return MHD_YES; /* Discard extra data */ 544 if (size + off >= sizeof(session->value_2)) 545 size = (size_t) (sizeof (session->value_2) - off - 1); /* crop extra data */ 546 memcpy (&session->value_2[off], 547 data, 548 size); 549 if (size + off < sizeof (session->value_2)) 550 session->value_2[size + off] = '\0'; 551 return MHD_YES; 552 } 553 fprintf (stderr, "Unsupported form value `%s'\n", key); 554 return MHD_YES; 555 } 556 557 558 /** 559 * Main MHD callback for handling requests. 560 * 561 * 562 * @param cls argument given together with the function 563 * pointer when the handler was registered with MHD 564 * @param connection handle to connection which is being processed 565 * @param url the requested url 566 * @param method the HTTP method used ("GET", "PUT", etc.) 567 * @param version the HTTP version string (i.e. "HTTP/1.1") 568 * @param upload_data the data being uploaded (excluding HEADERS, 569 * for a POST that fits into memory and that is encoded 570 * with a supported encoding, the POST data will NOT be 571 * given in upload_data and is instead available as 572 * part of MHD_get_connection_values; very large POST 573 * data *will* be made available incrementally in 574 * upload_data) 575 * @param upload_data_size set initially to the size of the 576 * upload_data provided; the method must update this 577 * value to the number of bytes NOT processed; 578 * @param req_cls pointer that the callback can set to some 579 * address and that will be preserved by MHD for future 580 * calls for this request; since the access handler may 581 * be called many times (i.e., for a PUT/POST operation 582 * with plenty of upload data) this allows the application 583 * to easily associate some request-specific state. 584 * If necessary, this state can be cleaned up in the 585 * global "MHD_RequestCompleted" callback (which 586 * can be set with the MHD_OPTION_NOTIFY_COMPLETED). 587 * Initially, <tt>*req_cls</tt> will be NULL. 588 * @return MHS_YES if the connection was handled successfully, 589 * MHS_NO if the socket must be closed due to a serious 590 * error while handling the request 591 */ 592 static enum MHD_Result 593 create_response (void *cls, 594 struct MHD_Connection *connection, 595 const char *url, 596 const char *method, 597 const char *version, 598 const char *upload_data, 599 size_t *upload_data_size, 600 void **req_cls) 601 { 602 struct MHD_Response *response; 603 struct Request *request; 604 struct Session *session; 605 enum MHD_Result ret; 606 unsigned int i; 607 (void) cls; /* Unused. Silent compiler warning. */ 608 (void) version; /* Unused. Silent compiler warning. */ 609 610 request = *req_cls; 611 if (NULL == request) 612 { 613 request = calloc (1, sizeof (struct Request)); 614 if (NULL == request) 615 { 616 fprintf (stderr, "calloc error: %s\n", strerror (errno)); 617 return MHD_NO; 618 } 619 *req_cls = request; 620 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 621 { 622 request->pp = MHD_create_post_processor (connection, 1024, 623 &post_iterator, request); 624 if (NULL == request->pp) 625 { 626 fprintf (stderr, "Failed to setup post processor for `%s'\n", 627 url); 628 return MHD_NO; /* internal error */ 629 } 630 } 631 return MHD_YES; 632 } 633 if (NULL == request->session) 634 { 635 request->session = get_session (connection); 636 if (NULL == request->session) 637 { 638 fprintf (stderr, "Failed to setup session for `%s'\n", 639 url); 640 return MHD_NO; /* internal error */ 641 } 642 } 643 session = request->session; 644 session->start = time (NULL); 645 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 646 { 647 /* evaluate POST data */ 648 if (MHD_YES != 649 MHD_post_process (request->pp, 650 upload_data, 651 *upload_data_size)) 652 return MHD_NO; /* internal error */ 653 if (0 != *upload_data_size) 654 { 655 *upload_data_size = 0; 656 return MHD_YES; 657 } 658 /* done with POST data, serve response */ 659 MHD_destroy_post_processor (request->pp); 660 request->pp = NULL; 661 method = MHD_HTTP_METHOD_GET; /* fake 'GET' */ 662 if (NULL != request->post_url) 663 url = request->post_url; 664 } 665 666 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) || 667 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) ) 668 { 669 /* find out which page to serve */ 670 i = 0; 671 while ( (pages[i].url != NULL) && 672 (0 != strcmp (pages[i].url, url)) ) 673 i++; 674 ret = pages[i].handler (pages[i].handler_cls, 675 pages[i].mime, 676 session, connection); 677 if (ret != MHD_YES) 678 fprintf (stderr, "Failed to create page for `%s'\n", 679 url); 680 return ret; 681 } 682 /* unsupported HTTP method */ 683 response = MHD_create_response_from_buffer_static (strlen (METHOD_ERROR), 684 METHOD_ERROR); 685 ret = MHD_queue_response (connection, 686 MHD_HTTP_NOT_ACCEPTABLE, 687 response); 688 MHD_destroy_response (response); 689 return ret; 690 } 691 692 693 /** 694 * Callback called upon completion of a request. 695 * Decrements session reference counter. 696 * 697 * @param cls not used 698 * @param connection connection that completed 699 * @param req_cls session handle 700 * @param toe status code 701 */ 702 static void 703 request_completed_callback (void *cls, 704 struct MHD_Connection *connection, 705 void **req_cls, 706 enum MHD_RequestTerminationCode toe) 707 { 708 struct Request *request = *req_cls; 709 (void) cls; /* Unused. Silent compiler warning. */ 710 (void) connection; /* Unused. Silent compiler warning. */ 711 (void) toe; /* Unused. Silent compiler warning. */ 712 713 if (NULL == request) 714 return; 715 if (NULL != request->session) 716 request->session->rc--; 717 if (NULL != request->pp) 718 MHD_destroy_post_processor (request->pp); 719 free (request); 720 } 721 722 723 /** 724 * Clean up handles of sessions that have been idle for 725 * too long. 726 */ 727 static void 728 expire_sessions (void) 729 { 730 struct Session *pos; 731 struct Session *prev; 732 struct Session *next; 733 time_t now; 734 735 now = time (NULL); 736 prev = NULL; 737 pos = sessions; 738 while (NULL != pos) 739 { 740 next = pos->next; 741 if (now - pos->start > 60 * 60) 742 { 743 /* expire sessions after 1h */ 744 if (NULL == prev) 745 sessions = pos->next; 746 else 747 prev->next = next; 748 free (pos); 749 } 750 else 751 prev = pos; 752 pos = next; 753 } 754 } 755 756 757 /** 758 * Call with the port number as the only argument. 759 * Never terminates (other than by signals, such as CTRL-C). 760 */ 761 int 762 main (int argc, char *const *argv) 763 { 764 struct MHD_Daemon *d; 765 struct timeval tv; 766 struct timeval *tvp; 767 fd_set rs; 768 fd_set ws; 769 fd_set es; 770 MHD_socket max; 771 uint64_t mhd_timeout; 772 unsigned int port; 773 774 if (argc != 2) 775 { 776 printf ("%s PORT\n", argv[0]); 777 return 1; 778 } 779 if ( (1 != sscanf (argv[1], "%u", &port)) || 780 (0 == port) || (65535 < port) ) 781 { 782 fprintf (stderr, 783 "Port must be a number between 1 and 65535.\n"); 784 return 1; 785 } 786 787 /* initialize PRNG */ 788 srand ((unsigned int) time (NULL)); 789 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 790 (uint16_t) port, 791 NULL, NULL, 792 &create_response, NULL, 793 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, 794 MHD_OPTION_NOTIFY_COMPLETED, 795 &request_completed_callback, NULL, 796 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 797 MHD_OPTION_END); 798 if (NULL == d) 799 return 1; 800 while (1) 801 { 802 expire_sessions (); 803 max = 0; 804 FD_ZERO (&rs); 805 FD_ZERO (&ws); 806 FD_ZERO (&es); 807 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 808 break; /* fatal internal error */ 809 if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES) 810 { 811 #if ! defined(_WIN32) || defined(__CYGWIN__) 812 tv.tv_sec = (time_t) (mhd_timeout / 1000); 813 #else /* Native W32 */ 814 tv.tv_sec = (long) (mhd_timeout / 1000); 815 #endif /* Native W32 */ 816 tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000; 817 tvp = &tv; 818 } 819 else 820 tvp = NULL; 821 if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp)) 822 { 823 if (EINTR != errno) 824 fprintf (stderr, 825 "Aborting due to error during select: %s\n", 826 strerror (errno)); 827 break; 828 } 829 MHD_run (d); 830 } 831 MHD_stop_daemon (d); 832 return 0; 833 }