123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 |
- /* Feel free to use this example code in any way
- you see fit (Public Domain) */
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- #include <errno.h>
- #include <time.h>
- #include <microhttpd.h>
- /**
- * Invalid method page.
- */
- #define METHOD_ERROR \
- "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
- /**
- * Invalid URL page.
- */
- #define NOT_FOUND_ERROR \
- "<html><head><title>Not found</title></head><body>Go away.</body></html>"
- /**
- * Front page. (/)
- */
- #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>"
- #define FORM_V1 MAIN_PAGE
- /**
- * Second page. (/2)
- */
- #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>"
- #define FORM_V1_V2 SECOND_PAGE
- /**
- * Second page (/S)
- */
- #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>"
- /**
- * Last page.
- */
- #define LAST_PAGE \
- "<html><head><title>Thank you</title></head><body>Thank you.</body></html>"
- /**
- * Name of our cookie.
- */
- #define COOKIE_NAME "session"
- /**
- * State we keep for each user/session/browser.
- */
- struct Session
- {
- /**
- * We keep all sessions in a linked list.
- */
- struct Session *next;
- /**
- * Unique ID for this session.
- */
- char sid[33];
- /**
- * Reference counter giving the number of connections
- * currently using this session.
- */
- unsigned int rc;
- /**
- * Time when this session was last active.
- */
- time_t start;
- /**
- * String submitted via form.
- */
- char value_1[64];
- /**
- * Another value submitted via form.
- */
- char value_2[64];
- };
- /**
- * Data kept per request.
- */
- struct Request
- {
- /**
- * Associated session.
- */
- struct Session *session;
- /**
- * Post processor handling form data (IF this is
- * a POST request).
- */
- struct MHD_PostProcessor *pp;
- /**
- * URL to serve in response to this POST (if this request
- * was a 'POST')
- */
- const char *post_url;
- };
- /**
- * Linked list of all active sessions. Yes, O(n) but a
- * hash table would be overkill for a simple example...
- */
- static struct Session *sessions;
- /**
- * Return the session handle for this connection, or
- * create one if this is a new user.
- */
- static struct Session *
- get_session (struct MHD_Connection *connection)
- {
- struct Session *ret;
- const char *cookie;
- cookie = MHD_lookup_connection_value (connection,
- MHD_COOKIE_KIND,
- COOKIE_NAME);
- if (cookie != NULL)
- {
- /* find existing session */
- ret = sessions;
- while (NULL != ret)
- {
- if (0 == strcmp (cookie, ret->sid))
- break;
- ret = ret->next;
- }
- if (NULL != ret)
- {
- ret->rc++;
- return ret;
- }
- }
- /* create fresh session */
- ret = calloc (1, sizeof (struct Session));
- if (NULL == ret)
- {
- fprintf (stderr, "calloc error: %s\n", strerror (errno));
- return NULL;
- }
- /* not a super-secure way to generate a random session ID,
- but should do for a simple example... */
- snprintf (ret->sid,
- sizeof (ret->sid),
- "%X%X%X%X",
- (unsigned int) rand (),
- (unsigned int) rand (),
- (unsigned int) rand (),
- (unsigned int) rand ());
- ret->rc++;
- ret->start = time (NULL);
- ret->next = sessions;
- sessions = ret;
- return ret;
- }
- /**
- * Type of handler that generates a reply.
- *
- * @param cls content for the page (handler-specific)
- * @param mime mime type to use
- * @param session session information
- * @param connection connection to process
- * @param #MHD_YES on success, #MHD_NO on failure
- */
- typedef enum MHD_Result (*PageHandler)(const void *cls,
- const char *mime,
- struct Session *session,
- struct MHD_Connection *connection);
- /**
- * Entry we generate for each page served.
- */
- struct Page
- {
- /**
- * Acceptable URL for this page.
- */
- const char *url;
- /**
- * Mime type to set for the page.
- */
- const char *mime;
- /**
- * Handler to call to generate response.
- */
- PageHandler handler;
- /**
- * Extra argument to handler.
- */
- const void *handler_cls;
- };
- /**
- * Add header to response to set a session cookie.
- *
- * @param session session to use
- * @param response response to modify
- */
- static void
- add_session_cookie (struct Session *session,
- struct MHD_Response *response)
- {
- char cstr[256];
- snprintf (cstr,
- sizeof (cstr),
- "%s=%s",
- COOKIE_NAME,
- session->sid);
- if (MHD_NO ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_SET_COOKIE,
- cstr))
- {
- fprintf (stderr,
- "Failed to set session cookie header!\n");
- }
- }
- /**
- * Handler that returns a simple static HTTP page that
- * is passed in via 'cls'.
- *
- * @param cls a 'const char *' with the HTML webpage to return
- * @param mime mime type to use
- * @param session session handle
- * @param connection connection to use
- */
- static enum MHD_Result
- serve_simple_form (const void *cls,
- const char *mime,
- struct Session *session,
- struct MHD_Connection *connection)
- {
- enum MHD_Result ret;
- const char *form = cls;
- struct MHD_Response *response;
- /* return static form */
- response = MHD_create_response_from_buffer_static (strlen (form), form);
- add_session_cookie (session, response);
- if (MHD_YES !=
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- mime))
- {
- fprintf (stderr,
- "Failed to set content type header!\n");
- /* return response without content type anyway ... */
- }
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- response);
- MHD_destroy_response (response);
- return ret;
- }
- /**
- * Handler that adds the 'v1' value to the given HTML code.
- *
- * @param cls a 'const char *' with the HTML webpage to return
- * @param mime mime type to use
- * @param session session handle
- * @param connection connection to use
- */
- static enum MHD_Result
- fill_v1_form (const void *cls,
- const char *mime,
- struct Session *session,
- struct MHD_Connection *connection)
- {
- enum MHD_Result ret;
- char *reply;
- struct MHD_Response *response;
- int reply_len;
- (void) cls; /* Unused */
- /* Emulate 'asprintf' */
- reply_len = snprintf (NULL, 0, FORM_V1, session->value_1);
- if (0 > reply_len)
- return MHD_NO; /* Internal error */
- reply = (char *) malloc ((size_t) ((size_t) reply_len + 1));
- if (NULL == reply)
- return MHD_NO; /* Out-of-memory error */
- if (reply_len != snprintf (reply,
- (size_t) (((size_t) reply_len) + 1),
- FORM_V1,
- session->value_1))
- {
- free (reply);
- return MHD_NO; /* printf error */
- }
- /* return static form */
- response =
- MHD_create_response_from_buffer_with_free_callback ((size_t) reply_len,
- (void *) reply,
- &free);
- if (NULL != response)
- {
- add_session_cookie (session, response);
- if (MHD_YES !=
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- mime))
- {
- fprintf (stderr,
- "Failed to set content type header!\n");
- /* return response without content type anyway ... */
- }
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- response);
- MHD_destroy_response (response);
- }
- else
- {
- free (reply);
- ret = MHD_NO;
- }
- return ret;
- }
- /**
- * Handler that adds the 'v1' and 'v2' values to the given HTML code.
- *
- * @param cls a 'const char *' with the HTML webpage to return
- * @param mime mime type to use
- * @param session session handle
- * @param connection connection to use
- */
- static enum MHD_Result
- fill_v1_v2_form (const void *cls,
- const char *mime,
- struct Session *session,
- struct MHD_Connection *connection)
- {
- enum MHD_Result ret;
- char *reply;
- struct MHD_Response *response;
- int reply_len;
- (void) cls; /* Unused */
- /* Emulate 'asprintf' */
- reply_len = snprintf (NULL, 0, FORM_V1_V2, session->value_1,
- session->value_2);
- if (0 > reply_len)
- return MHD_NO; /* Internal error */
- reply = (char *) malloc ((size_t) ((size_t) reply_len + 1));
- if (NULL == reply)
- return MHD_NO; /* Out-of-memory error */
- if (reply_len != snprintf (reply,
- (size_t) ((size_t) reply_len + 1),
- FORM_V1_V2,
- session->value_1,
- session->value_2))
- {
- free (reply);
- return MHD_NO; /* printf error */
- }
- /* return static form */
- response =
- MHD_create_response_from_buffer_with_free_callback ((size_t) reply_len,
- (void *) reply,
- &free);
- if (NULL != response)
- {
- add_session_cookie (session, response);
- if (MHD_YES !=
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- mime))
- {
- fprintf (stderr,
- "Failed to set content type header!\n");
- /* return response without content type anyway ... */
- }
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- response);
- MHD_destroy_response (response);
- }
- else
- {
- free (reply);
- ret = MHD_NO;
- }
- return ret;
- }
- /**
- * Handler used to generate a 404 reply.
- *
- * @param cls a 'const char *' with the HTML webpage to return
- * @param mime mime type to use
- * @param session session handle
- * @param connection connection to use
- */
- static enum MHD_Result
- not_found_page (const void *cls,
- const char *mime,
- struct Session *session,
- struct MHD_Connection *connection)
- {
- enum MHD_Result ret;
- struct MHD_Response *response;
- (void) cls; /* Unused. Silent compiler warning. */
- (void) session; /* Unused. Silent compiler warning. */
- /* unsupported HTTP method */
- response = MHD_create_response_from_buffer_static (strlen (NOT_FOUND_ERROR),
- NOT_FOUND_ERROR);
- ret = MHD_queue_response (connection,
- MHD_HTTP_NOT_FOUND,
- response);
- if (MHD_YES !=
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- mime))
- {
- fprintf (stderr,
- "Failed to set content type header!\n");
- /* return response without content type anyway ... */
- }
- MHD_destroy_response (response);
- return ret;
- }
- /**
- * List of all pages served by this HTTP server.
- */
- static const struct Page pages[] = {
- { "/", "text/html", &fill_v1_form, NULL },
- { "/2", "text/html", &fill_v1_v2_form, NULL },
- { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE },
- { "/F", "text/html", &serve_simple_form, LAST_PAGE },
- { NULL, NULL, ¬_found_page, NULL } /* 404 */
- };
- /**
- * Iterator over key-value pairs where the value
- * maybe made available in increments and/or may
- * not be zero-terminated. Used for processing
- * POST data.
- *
- * @param cls user-specified closure
- * @param kind type of the value
- * @param key 0-terminated key for the value
- * @param filename name of the uploaded file, NULL if not known
- * @param content_type mime-type of the data, NULL if not known
- * @param transfer_encoding encoding of the data, NULL if not known
- * @param data pointer to size bytes of data at the
- * specified offset
- * @param off offset of data in the overall value
- * @param size number of bytes in data available
- * @return #MHD_YES to continue iterating,
- * #MHD_NO to abort the iteration
- */
- 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)
- {
- struct Request *request = cls;
- struct Session *session = request->session;
- (void) kind; /* Unused. Silent compiler warning. */
- (void) filename; /* Unused. Silent compiler warning. */
- (void) content_type; /* Unused. Silent compiler warning. */
- (void) transfer_encoding; /* Unused. Silent compiler warning. */
- if (0 == strcmp ("DONE", key))
- {
- fprintf (stdout,
- "Session `%s' submitted `%s', `%s'\n",
- session->sid,
- session->value_1,
- session->value_2);
- return MHD_YES;
- }
- if (0 == strcmp ("v1", key))
- {
- if (off >= sizeof(session->value_1) - 1)
- return MHD_YES; /* Discard extra data */
- if (size + off >= sizeof(session->value_1))
- size = (size_t) (sizeof (session->value_1) - off - 1); /* crop extra data */
- memcpy (&session->value_1[off],
- data,
- size);
- if (size + off < sizeof (session->value_1))
- session->value_1[size + off] = '\0';
- return MHD_YES;
- }
- if (0 == strcmp ("v2", key))
- {
- if (off >= sizeof(session->value_2) - 1)
- return MHD_YES; /* Discard extra data */
- if (size + off >= sizeof(session->value_2))
- size = (size_t) (sizeof (session->value_2) - off - 1); /* crop extra data */
- memcpy (&session->value_2[off],
- data,
- size);
- if (size + off < sizeof (session->value_2))
- session->value_2[size + off] = '\0';
- return MHD_YES;
- }
- fprintf (stderr, "Unsupported form value `%s'\n", key);
- return MHD_YES;
- }
- /**
- * Main MHD callback for handling requests.
- *
- *
- * @param cls argument given together with the function
- * pointer when the handler was registered with MHD
- * @param connection handle to connection which is being processed
- * @param url the requested url
- * @param method the HTTP method used ("GET", "PUT", etc.)
- * @param version the HTTP version string (i.e. "HTTP/1.1")
- * @param upload_data the data being uploaded (excluding HEADERS,
- * for a POST that fits into memory and that is encoded
- * with a supported encoding, the POST data will NOT be
- * given in upload_data and is instead available as
- * part of MHD_get_connection_values; very large POST
- * data *will* be made available incrementally in
- * upload_data)
- * @param upload_data_size set initially to the size of the
- * upload_data provided; the method must update this
- * value to the number of bytes NOT processed;
- * @param req_cls pointer that the callback can set to some
- * address and that will be preserved by MHD for future
- * calls for this request; since the access handler may
- * be called many times (i.e., for a PUT/POST operation
- * with plenty of upload data) this allows the application
- * to easily associate some request-specific state.
- * If necessary, this state can be cleaned up in the
- * global "MHD_RequestCompleted" callback (which
- * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
- * Initially, <tt>*req_cls</tt> will be NULL.
- * @return MHS_YES if the connection was handled successfully,
- * MHS_NO if the socket must be closed due to a serious
- * error while handling the request
- */
- 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 **req_cls)
- {
- struct MHD_Response *response;
- struct Request *request;
- struct Session *session;
- enum MHD_Result ret;
- unsigned int i;
- (void) cls; /* Unused. Silent compiler warning. */
- (void) version; /* Unused. Silent compiler warning. */
- request = *req_cls;
- if (NULL == request)
- {
- request = calloc (1, sizeof (struct Request));
- if (NULL == request)
- {
- fprintf (stderr, "calloc error: %s\n", strerror (errno));
- return MHD_NO;
- }
- *req_cls = request;
- if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
- {
- request->pp = MHD_create_post_processor (connection, 1024,
- &post_iterator, request);
- if (NULL == request->pp)
- {
- fprintf (stderr, "Failed to setup post processor for `%s'\n",
- url);
- return MHD_NO; /* internal error */
- }
- }
- return MHD_YES;
- }
- if (NULL == request->session)
- {
- request->session = get_session (connection);
- if (NULL == request->session)
- {
- fprintf (stderr, "Failed to setup session for `%s'\n",
- url);
- return MHD_NO; /* internal error */
- }
- }
- session = request->session;
- session->start = time (NULL);
- if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
- {
- /* evaluate POST data */
- if (MHD_YES !=
- MHD_post_process (request->pp,
- upload_data,
- *upload_data_size))
- return MHD_NO; /* internal error */
- if (0 != *upload_data_size)
- {
- *upload_data_size = 0;
- return MHD_YES;
- }
- /* done with POST data, serve response */
- MHD_destroy_post_processor (request->pp);
- request->pp = NULL;
- method = MHD_HTTP_METHOD_GET; /* fake 'GET' */
- if (NULL != request->post_url)
- url = request->post_url;
- }
- if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
- (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
- {
- /* find out which page to serve */
- i = 0;
- while ( (pages[i].url != NULL) &&
- (0 != strcmp (pages[i].url, url)) )
- i++;
- ret = pages[i].handler (pages[i].handler_cls,
- pages[i].mime,
- session, connection);
- if (ret != MHD_YES)
- fprintf (stderr, "Failed to create page for `%s'\n",
- url);
- return ret;
- }
- /* unsupported HTTP method */
- response = MHD_create_response_from_buffer_static (strlen (METHOD_ERROR),
- METHOD_ERROR);
- ret = MHD_queue_response (connection,
- MHD_HTTP_NOT_ACCEPTABLE,
- response);
- MHD_destroy_response (response);
- return ret;
- }
- /**
- * Callback called upon completion of a request.
- * Decrements session reference counter.
- *
- * @param cls not used
- * @param connection connection that completed
- * @param req_cls session handle
- * @param toe status code
- */
- static void
- request_completed_callback (void *cls,
- struct MHD_Connection *connection,
- void **req_cls,
- enum MHD_RequestTerminationCode toe)
- {
- struct Request *request = *req_cls;
- (void) cls; /* Unused. Silent compiler warning. */
- (void) connection; /* Unused. Silent compiler warning. */
- (void) toe; /* Unused. Silent compiler warning. */
- if (NULL == request)
- return;
- if (NULL != request->session)
- request->session->rc--;
- if (NULL != request->pp)
- MHD_destroy_post_processor (request->pp);
- free (request);
- }
- /**
- * Clean up handles of sessions that have been idle for
- * too long.
- */
- static void
- expire_sessions (void)
- {
- struct Session *pos;
- struct Session *prev;
- struct Session *next;
- time_t now;
- now = time (NULL);
- prev = NULL;
- pos = sessions;
- while (NULL != pos)
- {
- next = pos->next;
- if (now - pos->start > 60 * 60)
- {
- /* expire sessions after 1h */
- if (NULL == prev)
- sessions = pos->next;
- else
- prev->next = next;
- free (pos);
- }
- else
- prev = pos;
- pos = next;
- }
- }
- /**
- * Call with the port number as the only argument.
- * Never terminates (other than by signals, such as CTRL-C).
- */
- int
- main (int argc, char *const *argv)
- {
- struct MHD_Daemon *d;
- struct timeval tv;
- struct timeval *tvp;
- fd_set rs;
- fd_set ws;
- fd_set es;
- MHD_socket max;
- uint64_t mhd_timeout;
- unsigned int port;
- if (argc != 2)
- {
- printf ("%s PORT\n", argv[0]);
- return 1;
- }
- if ( (1 != sscanf (argv[1], "%u", &port)) ||
- (0 == port) || (65535 < port) )
- {
- fprintf (stderr,
- "Port must be a number between 1 and 65535.\n");
- return 1;
- }
- /* initialize PRNG */
- srand ((unsigned int) time (NULL));
- d = MHD_start_daemon (MHD_USE_ERROR_LOG,
- (uint16_t) port,
- NULL, NULL,
- &create_response, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15,
- MHD_OPTION_NOTIFY_COMPLETED,
- &request_completed_callback, NULL,
- MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
- MHD_OPTION_END);
- if (NULL == d)
- return 1;
- while (1)
- {
- expire_sessions ();
- max = 0;
- FD_ZERO (&rs);
- FD_ZERO (&ws);
- FD_ZERO (&es);
- if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
- break; /* fatal internal error */
- if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES)
- {
- #if ! defined(_WIN32) || defined(__CYGWIN__)
- tv.tv_sec = (time_t) (mhd_timeout / 1000);
- #else /* Native W32 */
- tv.tv_sec = (long) (mhd_timeout / 1000);
- #endif /* Native W32 */
- tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000;
- tvp = &tv;
- }
- else
- tvp = NULL;
- if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp))
- {
- if (EINTR != errno)
- fprintf (stderr,
- "Aborting due to error during select: %s\n",
- strerror (errno));
- break;
- }
- MHD_run (d);
- }
- MHD_stop_daemon (d);
- return 0;
- }
|