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 */
71struct 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 */
110struct 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 */
137static 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 */
146static struct Session *
147get_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 */
204typedef 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 */
213struct 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 */
243static void
244add_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 */
273static int
274serve_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 */
309static int
310fill_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 */
352static int
353fill_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 */
396static int
397not_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 */
425static 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 */
455static int
456post_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 */
536static int
537create_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 */
643static void
644request_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 */
665static void
666expire_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 */
699int
700main (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