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