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