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