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)
15static int
16asprintf (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 */
93struct 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 */
132struct 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 */
159static 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 */
168static struct Session *
169get_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 */
226typedef 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 */
235struct 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 */
265static void
266add_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 */
295static int
296serve_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 */
329static int
330fill_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 */
371static int
372fill_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 */
414static int
415not_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 */
441static 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, &not_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 */
471static int
472post_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 */
553static int
554create_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 */
660static void
661request_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 */
682static void
683expire_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 */
716int
717main (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