sessions.c (22950B)
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 *req_cls = request; 618 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 619 { 620 request->pp = MHD_create_post_processor (connection, 1024, 621 &post_iterator, request); 622 if (NULL == request->pp) 623 { 624 fprintf (stderr, "Failed to setup post processor for `%s'\n", 625 url); 626 return MHD_NO; /* internal error */ 627 } 628 } 629 return MHD_YES; 630 } 631 if (NULL == request->session) 632 { 633 request->session = get_session (connection); 634 if (NULL == request->session) 635 { 636 fprintf (stderr, "Failed to setup session for `%s'\n", 637 url); 638 return MHD_NO; /* internal error */ 639 } 640 } 641 session = request->session; 642 session->start = time (NULL); 643 if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) 644 { 645 /* evaluate POST data */ 646 if (MHD_YES != 647 MHD_post_process (request->pp, 648 upload_data, 649 *upload_data_size)) 650 return MHD_NO; /* internal error */ 651 if (0 != *upload_data_size) 652 { 653 *upload_data_size = 0; 654 return MHD_YES; 655 } 656 /* done with POST data, serve response */ 657 MHD_destroy_post_processor (request->pp); 658 request->pp = NULL; 659 method = MHD_HTTP_METHOD_GET; /* fake 'GET' */ 660 if (NULL != request->post_url) 661 url = request->post_url; 662 } 663 664 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) || 665 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) ) 666 { 667 /* find out which page to serve */ 668 i = 0; 669 while ( (pages[i].url != NULL) && 670 (0 != strcmp (pages[i].url, url)) ) 671 i++; 672 ret = pages[i].handler (pages[i].handler_cls, 673 pages[i].mime, 674 session, connection); 675 if (ret != MHD_YES) 676 fprintf (stderr, "Failed to create page for `%s'\n", 677 url); 678 return ret; 679 } 680 /* unsupported HTTP method */ 681 response = MHD_create_response_from_buffer_static (strlen (METHOD_ERROR), 682 METHOD_ERROR); 683 ret = MHD_queue_response (connection, 684 MHD_HTTP_NOT_ACCEPTABLE, 685 response); 686 MHD_destroy_response (response); 687 return ret; 688 } 689 690 691 /** 692 * Callback called upon completion of a request. 693 * Decrements session reference counter. 694 * 695 * @param cls not used 696 * @param connection connection that completed 697 * @param req_cls session handle 698 * @param toe status code 699 */ 700 static void 701 request_completed_callback (void *cls, 702 struct MHD_Connection *connection, 703 void **req_cls, 704 enum MHD_RequestTerminationCode toe) 705 { 706 struct Request *request = *req_cls; 707 (void) cls; /* Unused. Silent compiler warning. */ 708 (void) connection; /* Unused. Silent compiler warning. */ 709 (void) toe; /* Unused. Silent compiler warning. */ 710 711 if (NULL == request) 712 return; 713 if (NULL != request->session) 714 request->session->rc--; 715 if (NULL != request->pp) 716 MHD_destroy_post_processor (request->pp); 717 free (request); 718 } 719 720 721 /** 722 * Clean up handles of sessions that have been idle for 723 * too long. 724 */ 725 static void 726 expire_sessions (void) 727 { 728 struct Session *pos; 729 struct Session *prev; 730 struct Session *next; 731 time_t now; 732 733 now = time (NULL); 734 prev = NULL; 735 pos = sessions; 736 while (NULL != pos) 737 { 738 next = pos->next; 739 if (now - pos->start > 60 * 60) 740 { 741 /* expire sessions after 1h */ 742 if (NULL == prev) 743 sessions = pos->next; 744 else 745 prev->next = next; 746 free (pos); 747 } 748 else 749 prev = pos; 750 pos = next; 751 } 752 } 753 754 755 /** 756 * Call with the port number as the only argument. 757 * Never terminates (other than by signals, such as CTRL-C). 758 */ 759 int 760 main (int argc, char *const *argv) 761 { 762 struct MHD_Daemon *d; 763 struct timeval tv; 764 struct timeval *tvp; 765 fd_set rs; 766 fd_set ws; 767 fd_set es; 768 MHD_socket max; 769 uint64_t mhd_timeout; 770 unsigned int port; 771 772 if (argc != 2) 773 { 774 printf ("%s PORT\n", argv[0]); 775 return 1; 776 } 777 if ( (1 != sscanf (argv[1], "%u", &port)) || 778 (0 == port) || (65535 < port) ) 779 { 780 fprintf (stderr, 781 "Port must be a number between 1 and 65535.\n"); 782 return 1; 783 } 784 785 /* initialize PRNG */ 786 srand ((unsigned int) time (NULL)); 787 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 788 (uint16_t) port, 789 NULL, NULL, 790 &create_response, NULL, 791 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, 792 MHD_OPTION_NOTIFY_COMPLETED, 793 &request_completed_callback, NULL, 794 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 795 MHD_OPTION_END); 796 if (NULL == d) 797 return 1; 798 while (1) 799 { 800 expire_sessions (); 801 max = 0; 802 FD_ZERO (&rs); 803 FD_ZERO (&ws); 804 FD_ZERO (&es); 805 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 806 break; /* fatal internal error */ 807 if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES) 808 { 809 #if ! defined(_WIN32) || defined(__CYGWIN__) 810 tv.tv_sec = (time_t) (mhd_timeout / 1000); 811 #else /* Native W32 */ 812 tv.tv_sec = (long) (mhd_timeout / 1000); 813 #endif /* Native W32 */ 814 tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000; 815 tvp = &tv; 816 } 817 else 818 tvp = NULL; 819 if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp)) 820 { 821 if (EINTR != errno) 822 fprintf (stderr, 823 "Aborting due to error during select: %s\n", 824 strerror (errno)); 825 break; 826 } 827 MHD_run (d); 828 } 829 MHD_stop_daemon (d); 830 return 0; 831 }