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