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 */
71 struct 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 */
110 struct 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 */
137 static 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 */
146 static struct Session *
get_session(struct MHD_Connection * connection)147 get_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 */
204 typedef 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 */
213 struct 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 */
243 static void
add_session_cookie(struct Session * session,struct MHD_Response * response)244 add_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 */
273 static int
serve_simple_form(const void * cls,const char * mime,struct Session * session,struct MHD_Connection * connection)274 serve_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 */
309 static int
fill_v1_form(const void * cls,const char * mime,struct Session * session,struct MHD_Connection * connection)310 fill_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 */
352 static int
fill_v1_v2_form(const void * cls,const char * mime,struct Session * session,struct MHD_Connection * connection)353 fill_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 */
396 static int
not_found_page(const void * cls,const char * mime,struct Session * session,struct MHD_Connection * connection)397 not_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 */
425 static 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 */
455 static int
post_iterator(void * cls,enum MHD_ValueKind kind,const char * key,const char * filename,const char * content_type,const char * transfer_encoding,const char * data,uint64_t off,size_t size)456 post_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 */
536 static int
create_response(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** ptr)537 create_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 */
643 static void
request_completed_callback(void * cls,struct MHD_Connection * connection,void ** con_cls,enum MHD_RequestTerminationCode toe)644 request_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 */
665 static void
expire_sessions()666 expire_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 */
699 int
main(int argc,char * const * argv)700 main (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