• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &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  */
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