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, &not_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