libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 64e88643d14e66ff6fc241bc2de433100078efc1
parent 7971df739ffdf324abc87a7886b901375644807a
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Tue, 13 May 2025 12:31:46 +0200

re-add example code

Diffstat:
Adoc/examples/.gitignore | 12++++++++++++
Adoc/examples/Makefile.am | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/basicauthentication.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/hellobrowser.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/largepost.c | 349+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/logging.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/responseheaders.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/sessions.c | 831+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/simplepost.c | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/tlsauthentication.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/websocket.c | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 2482 insertions(+), 0 deletions(-)

diff --git a/doc/examples/.gitignore b/doc/examples/.gitignore @@ -0,0 +1,12 @@ +/tlsauthentication +/simplepost +/sessions +/responseheaders +/logging +/largepost +/hellobrowser +/basicauthentication +*.exe +*.o +*.lo +*.la diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am @@ -0,0 +1,93 @@ +# This Makefile.am is in the public domain +SUBDIRS = . + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(CPPFLAGS_ac) + +AM_CFLAGS = $(CFLAGS_ac) @LIBGCRYPT_CFLAGS@ + +AM_LDFLAGS = $(LDFLAGS_ac) + +AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac) + +if USE_COVERAGE + AM_CFLAGS += --coverage +endif + +$(top_builddir)/src/microhttpd/libmicrohttpd.la: $(top_builddir)/src/microhttpd/Makefile + @echo ' cd $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la'; \ + $(am__cd) $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la + +# example programs +noinst_PROGRAMS = \ + hellobrowser \ + logging \ + responseheaders + +if MHD_SUPPORT_AUTH_BASIC +noinst_PROGRAMS += \ + basicauthentication +if MHD_SUPPORT_HTTPS +noinst_PROGRAMS += \ + tlsauthentication +endif +endif + +if MHD_SUPPORT_POST_PARSER +noinst_PROGRAMS += simplepost largepost sessions +endif + +if HAVE_W32 +AM_CPPFLAGS += -DWINDOWS +endif + +if HAVE_EXPERIMENTAL +noinst_PROGRAMS += websocket +endif + +basicauthentication_SOURCES = \ + basicauthentication.c +basicauthentication_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +hellobrowser_SOURCES = \ + hellobrowser.c +hellobrowser_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +logging_SOURCES = \ + logging.c +logging_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +responseheaders_SOURCES = \ + responseheaders.c +responseheaders_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +sessions_SOURCES = \ + sessions.c +sessions_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +tlsauthentication_SOURCES = \ + tlsauthentication.c +tlsauthentication_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +simplepost_SOURCES = \ + simplepost.c +simplepost_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +largepost_SOURCES = \ + largepost.c +largepost_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + +websocket_SOURCES = \ + websocket.c +websocket_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + $(top_builddir)/src/microhttpd_ws/libmicrohttpd_ws.la diff --git a/doc/examples/basicauthentication.c b/doc/examples/basicauthentication.c @@ -0,0 +1,98 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define PORT 8888 + + +static enum MHD_Result +answer_to_connection (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_BasicAuthInfo *auth_info; + enum MHD_Result ret; + struct MHD_Response *response; + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; + if (NULL == *req_cls) + { + *req_cls = connection; + return MHD_YES; + } + auth_info = MHD_basic_auth_get_username_password3 (connection); + if (NULL == auth_info) + { + static const char *page = + "<html><body>Authorization required</body></html>"; + response = MHD_create_response_from_buffer_static (strlen (page), page); + ret = MHD_queue_basic_auth_required_response3 (connection, + "admins", + MHD_YES, + response); + } + else if ((strlen ("root") != auth_info->username_len) || + (0 != memcmp (auth_info->username, "root", + auth_info->username_len)) || + /* The next check against NULL is optional, + * if 'password' is NULL then 'password_len' is always zero. */ + (NULL == auth_info->password) || + (strlen ("pa$$w0rd") != auth_info->password_len) || + (0 != memcmp (auth_info->password, "pa$$w0rd", + auth_info->password_len))) + { + static const char *page = + "<html><body>Wrong username or password</body></html>"; + response = MHD_create_response_from_buffer_static (strlen (page), page); + ret = MHD_queue_basic_auth_required_response3 (connection, + "admins", + MHD_YES, + response); + } + else + { + static const char *page = "<html><body>A secret.</body></html>"; + response = MHD_create_response_from_buffer_static (strlen (page), page); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + } + if (NULL != auth_info) + MHD_free (auth_info); + MHD_destroy_response (response); + return ret; +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL, + &answer_to_connection, NULL, MHD_OPTION_END); + if (NULL == daemon) + return 1; + + (void) getchar (); + + MHD_stop_daemon (daemon); + return 0; +} diff --git a/doc/examples/hellobrowser.c b/doc/examples/hellobrowser.c @@ -0,0 +1,57 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <string.h> +#include <microhttpd.h> +#include <stdio.h> + +#define PORT 8888 + +static enum MHD_Result +answer_to_connection (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) +{ + const char *page = "<html><body>Hello, browser!</body></html>"; + struct MHD_Response *response; + enum MHD_Result ret; + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) method; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + (void) req_cls; /* Unused. Silent compiler warning. */ + + response = MHD_create_response_from_buffer_static (strlen (page), page); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + + return ret; +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD, + PORT, NULL, NULL, + &answer_to_connection, NULL, MHD_OPTION_END); + if (NULL == daemon) + return 1; + + (void) getchar (); + + MHD_stop_daemon (daemon); + return 0; +} diff --git a/doc/examples/largepost.c b/doc/examples/largepost.c @@ -0,0 +1,349 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <microhttpd.h> + +#if defined(_MSC_VER) && _MSC_VER + 0 <= 1800 +/* Substitution is OK while return value is not used */ +#define snprintf _snprintf +#endif + +#define PORT 8888 +#define POSTBUFFERSIZE 512 +#define MAXCLIENTS 2 + +enum ConnectionType +{ + GET = 0, + POST = 1 +}; + +static unsigned int nr_of_uploading_clients = 0; + + +/** + * Information we keep per connection. + */ +struct connection_info_struct +{ + enum ConnectionType connectiontype; + + /** + * Handle to the POST processing state. + */ + struct MHD_PostProcessor *postprocessor; + + /** + * File handle where we write uploaded data. + */ + FILE *fp; + + /** + * HTTP response body we will return, NULL if not yet known. + */ + const char *answerstring; + + /** + * HTTP status code we will return, 0 for undecided. + */ + unsigned int answercode; +}; + + +#define ASKPAGE \ + "<html><body>\n" \ + "Upload a file, please!<br>\n" \ + "There are %u clients uploading at the moment.<br>\n" \ + "<form action=\"/filepost\" method=\"post\" enctype=\"multipart/form-data\">\n" \ + "<input name=\"file\" type=\"file\">\n" \ + "<input type=\"submit\" value=\" Send \"></form>\n" \ + "</body></html>" +static const char *busypage = + "<html><body>This server is busy, please try again later.</body></html>"; +static const char *completepage = + "<html><body>The upload has been completed.</body></html>"; +static const char *errorpage = + "<html><body>This doesn't seem to be right.</body></html>"; +static const char *servererrorpage = + "<html><body>Invalid request.</body></html>"; +static const char *fileexistspage = + "<html><body>This file already exists.</body></html>"; +static const char *fileioerror = + "<html><body>IO error writing to disk.</body></html>"; +static const char *const postprocerror = + "<html><head><title>Error</title></head><body>Error processing POST data</body></html>"; + + +static enum MHD_Result +send_page (struct MHD_Connection *connection, + const char *page, + unsigned int status_code) +{ + enum MHD_Result ret; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer_static (strlen (page), page); + if (! response) + return MHD_NO; + if (MHD_YES != + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/html")) + { + fprintf (stderr, + "Failed to set content type header!\n"); + } + ret = MHD_queue_response (connection, + status_code, + response); + MHD_destroy_response (response); + + return ret; +} + + +static enum MHD_Result +iterate_post (void *coninfo_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 connection_info_struct *con_info = coninfo_cls; + FILE *fp; + (void) kind; /* Unused. Silent compiler warning. */ + (void) content_type; /* Unused. Silent compiler warning. */ + (void) transfer_encoding; /* Unused. Silent compiler warning. */ + (void) off; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (key, "file")) + { + con_info->answerstring = servererrorpage; + con_info->answercode = MHD_HTTP_BAD_REQUEST; + return MHD_YES; + } + + if (! con_info->fp) + { + if (0 != con_info->answercode) /* something went wrong */ + return MHD_YES; + if (NULL != (fp = fopen (filename, "rb"))) + { + fclose (fp); + con_info->answerstring = fileexistspage; + con_info->answercode = MHD_HTTP_FORBIDDEN; + return MHD_YES; + } + /* NOTE: This is technically a race with the 'fopen()' above, + but there is no easy fix, short of moving to open(O_EXCL) + instead of using fopen(). For the example, we do not care. */ + con_info->fp = fopen (filename, "ab"); + if (! con_info->fp) + { + con_info->answerstring = fileioerror; + con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR; + return MHD_YES; + } + } + + if (size > 0) + { + if (! fwrite (data, sizeof (char), size, con_info->fp)) + { + con_info->answerstring = fileioerror; + con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR; + return MHD_YES; + } + } + + return MHD_YES; +} + + +static void +request_completed (void *cls, + struct MHD_Connection *connection, + void **req_cls, + enum MHD_RequestTerminationCode toe) +{ + struct connection_info_struct *con_info = *req_cls; + (void) cls; /* Unused. Silent compiler warning. */ + (void) connection; /* Unused. Silent compiler warning. */ + (void) toe; /* Unused. Silent compiler warning. */ + + if (NULL == con_info) + return; + + if (con_info->connectiontype == POST) + { + if (NULL != con_info->postprocessor) + { + MHD_destroy_post_processor (con_info->postprocessor); + nr_of_uploading_clients--; + } + + if (con_info->fp) + fclose (con_info->fp); + } + + free (con_info); + *req_cls = NULL; +} + + +static enum MHD_Result +answer_to_connection (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) +{ + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + + if (NULL == *req_cls) + { + /* First call, setup data structures */ + struct connection_info_struct *con_info; + + if (nr_of_uploading_clients >= MAXCLIENTS) + return send_page (connection, + busypage, + MHD_HTTP_SERVICE_UNAVAILABLE); + + con_info = malloc (sizeof (struct connection_info_struct)); + if (NULL == con_info) + return MHD_NO; + con_info->answercode = 0; /* none yet */ + con_info->fp = NULL; + + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + { + con_info->postprocessor = + MHD_create_post_processor (connection, + POSTBUFFERSIZE, + &iterate_post, + (void *) con_info); + + if (NULL == con_info->postprocessor) + { + free (con_info); + return MHD_NO; + } + + nr_of_uploading_clients++; + + con_info->connectiontype = POST; + } + else + { + con_info->connectiontype = GET; + } + + *req_cls = (void *) con_info; + + return MHD_YES; + } + + if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) + { + /* We just return the standard form for uploads on all GET requests */ + char buffer[1024]; + + snprintf (buffer, + sizeof (buffer), + ASKPAGE, + nr_of_uploading_clients); + return send_page (connection, + buffer, + MHD_HTTP_OK); + } + + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + { + struct connection_info_struct *con_info = *req_cls; + + if (0 != *upload_data_size) + { + /* Upload not yet done */ + if (0 != con_info->answercode) + { + /* we already know the answer, skip rest of upload */ + *upload_data_size = 0; + return MHD_YES; + } + if (MHD_YES != + MHD_post_process (con_info->postprocessor, + upload_data, + *upload_data_size)) + { + con_info->answerstring = postprocerror; + con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR; + } + *upload_data_size = 0; + + return MHD_YES; + } + /* Upload finished */ + if (NULL != con_info->fp) + { + fclose (con_info->fp); + con_info->fp = NULL; + } + if (0 == con_info->answercode) + { + /* No errors encountered, declare success */ + con_info->answerstring = completepage; + con_info->answercode = MHD_HTTP_OK; + } + return send_page (connection, + con_info->answerstring, + con_info->answercode); + } + + /* Note a GET or a POST, generate error */ + return send_page (connection, + errorpage, + MHD_HTTP_BAD_REQUEST); +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, + PORT, NULL, NULL, + &answer_to_connection, NULL, + MHD_OPTION_NOTIFY_COMPLETED, &request_completed, + NULL, + MHD_OPTION_END); + if (NULL == daemon) + { + fprintf (stderr, + "Failed to start daemon.\n"); + return 1; + } + (void) getchar (); + MHD_stop_daemon (daemon); + return 0; +} diff --git a/doc/examples/logging.c b/doc/examples/logging.c @@ -0,0 +1,62 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <stdio.h> + +#define PORT 8888 + + +static enum MHD_Result +print_out_key (void *cls, enum MHD_ValueKind kind, const char *key, + const char *value) +{ + (void) cls; /* Unused. Silent compiler warning. */ + (void) kind; /* Unused. Silent compiler warning. */ + printf ("%s: %s\n", key, value); + return MHD_YES; +} + + +static enum MHD_Result +answer_to_connection (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) +{ + (void) cls; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + (void) req_cls; /* Unused. Silent compiler warning. */ + printf ("New %s request for %s using version %s\n", method, url, version); + + MHD_get_connection_values (connection, MHD_HEADER_KIND, print_out_key, + NULL); + + return MHD_NO; +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL, + &answer_to_connection, NULL, MHD_OPTION_END); + if (NULL == daemon) + return 1; + + (void) getchar (); + + MHD_stop_daemon (daemon); + return 0; +} diff --git a/doc/examples/responseheaders.c b/doc/examples/responseheaders.c @@ -0,0 +1,102 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <time.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> + +#define PORT 8888 +#define FILENAME "picture.png" +#define MIMETYPE "image/png" + +static enum MHD_Result +answer_to_connection (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; + int fd; + enum MHD_Result ret; + struct stat sbuf; + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + (void) req_cls; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; + + if ( (-1 == (fd = open (FILENAME, O_RDONLY))) || + (0 != fstat (fd, &sbuf)) ) + { + const char *errorstr = + "<html><body>An internal server error has occurred!\ + </body></html>"; + /* error accessing file */ + if (fd != -1) + (void) close (fd); + response = + MHD_create_response_from_buffer_static (strlen (errorstr), errorstr); + if (NULL != response) + { + ret = + MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + response); + MHD_destroy_response (response); + + return ret; + } + else + return MHD_NO; + } + response = + MHD_create_response_from_fd_at_offset64 ((size_t) sbuf.st_size, + fd, + 0); + if (MHD_YES != + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + MIMETYPE)) + { + fprintf (stderr, + "Failed to set content type header!\n"); + /* return response without content encoding anyway ... */ + } + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + + return ret; +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL, + &answer_to_connection, NULL, MHD_OPTION_END); + if (NULL == daemon) + return 1; + + (void) getchar (); + + MHD_stop_daemon (daemon); + + return 0; +} diff --git a/doc/examples/sessions.c b/doc/examples/sessions.c @@ -0,0 +1,831 @@ +/* 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, &not_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; +} diff --git a/doc/examples/simplepost.c b/doc/examples/simplepost.c @@ -0,0 +1,215 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#if defined(_MSC_VER) && _MSC_VER + 0 <= 1800 +/* Substitution is OK while return value is not used */ +#define snprintf _snprintf +#endif + +#define PORT 8888 +#define POSTBUFFERSIZE 512 +#define MAXNAMESIZE 20 +#define MAXANSWERSIZE 512 + +#define GET 0 +#define POST 1 + +struct connection_info_struct +{ + int connectiontype; + char *answerstring; + struct MHD_PostProcessor *postprocessor; +}; + +static const char *askpage = + "<html><body>\n" + "What's your name, Sir?<br>\n" + "<form action=\"/namepost\" method=\"post\">\n" + "<input name=\"name\" type=\"text\">\n" + "<input type=\"submit\" value=\" Send \"></form>\n" + "</body></html>"; + +#define GREETINGPAGE \ + "<html><body><h1>Welcome, %s!</center></h1></body></html>" + +static const char *errorpage = + "<html><body>This doesn't seem to be right.</body></html>"; + + +static enum MHD_Result +send_page (struct MHD_Connection *connection, const char *page) +{ + enum MHD_Result ret; + struct MHD_Response *response; + + + response = MHD_create_response_from_buffer_static (strlen (page), page); + if (! response) + return MHD_NO; + + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + + return ret; +} + + +static enum MHD_Result +iterate_post (void *coninfo_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 connection_info_struct *con_info = coninfo_cls; + (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. */ + (void) off; /* Unused. Silent compiler warning. */ + + if (0 == strcmp (key, "name")) + { + if ((size > 0) && (size <= MAXNAMESIZE)) + { + char *answerstring; + answerstring = malloc (MAXANSWERSIZE); + if (! answerstring) + return MHD_NO; + + snprintf (answerstring, MAXANSWERSIZE, GREETINGPAGE, data); + con_info->answerstring = answerstring; + } + else + con_info->answerstring = NULL; + + return MHD_NO; + } + + return MHD_YES; +} + + +static void +request_completed (void *cls, struct MHD_Connection *connection, + void **req_cls, enum MHD_RequestTerminationCode toe) +{ + struct connection_info_struct *con_info = *req_cls; + (void) cls; /* Unused. Silent compiler warning. */ + (void) connection; /* Unused. Silent compiler warning. */ + (void) toe; /* Unused. Silent compiler warning. */ + + if (NULL == con_info) + return; + + if (con_info->connectiontype == POST) + { + MHD_destroy_post_processor (con_info->postprocessor); + if (con_info->answerstring) + free (con_info->answerstring); + } + + free (con_info); + *req_cls = NULL; +} + + +static enum MHD_Result +answer_to_connection (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) +{ + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + + if (NULL == *req_cls) + { + struct connection_info_struct *con_info; + + con_info = malloc (sizeof (struct connection_info_struct)); + if (NULL == con_info) + return MHD_NO; + con_info->answerstring = NULL; + + if (0 == strcmp (method, "POST")) + { + con_info->postprocessor = + MHD_create_post_processor (connection, POSTBUFFERSIZE, + iterate_post, (void *) con_info); + + if (NULL == con_info->postprocessor) + { + free (con_info); + return MHD_NO; + } + + con_info->connectiontype = POST; + } + else + con_info->connectiontype = GET; + + *req_cls = (void *) con_info; + + return MHD_YES; + } + + if (0 == strcmp (method, "GET")) + { + return send_page (connection, askpage); + } + + if (0 == strcmp (method, "POST")) + { + struct connection_info_struct *con_info = *req_cls; + + if (*upload_data_size != 0) + { + if (MHD_YES != + MHD_post_process (con_info->postprocessor, + upload_data, + *upload_data_size)) + return MHD_NO; + *upload_data_size = 0; + + return MHD_YES; + } + else if (NULL != con_info->answerstring) + return send_page (connection, con_info->answerstring); + } + + return send_page (connection, errorpage); +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD, + PORT, NULL, NULL, + &answer_to_connection, NULL, + MHD_OPTION_NOTIFY_COMPLETED, request_completed, + NULL, MHD_OPTION_END); + if (NULL == daemon) + return 1; + + (void) getchar (); + + MHD_stop_daemon (daemon); + + return 0; +} diff --git a/doc/examples/tlsauthentication.c b/doc/examples/tlsauthentication.c @@ -0,0 +1,214 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#define PORT 8888 + +#define REALM "Maintenance" +#define USER "a legitimate user" +#define PASSWORD "and his password" + +#define SERVERKEYFILE "server.key" +#define SERVERCERTFILE "server.pem" + + +static size_t +get_file_size (const char *filename) +{ + FILE *fp; + + fp = fopen (filename, "rb"); + if (fp) + { + long size; + + if ((0 != fseek (fp, 0, SEEK_END)) || (-1 == (size = ftell (fp)))) + size = 0; + + fclose (fp); + + return (size_t) size; + } + else + return 0; +} + + +static char * +load_file (const char *filename) +{ + FILE *fp; + char *buffer; + size_t size; + + size = get_file_size (filename); + if (0 == size) + return NULL; + + fp = fopen (filename, "rb"); + if (! fp) + return NULL; + + buffer = malloc (size + 1); + if (! buffer) + { + fclose (fp); + return NULL; + } + buffer[size] = '\0'; + + if (size != fread (buffer, 1, size, fp)) + { + free (buffer); + buffer = NULL; + } + + fclose (fp); + return buffer; +} + + +static enum MHD_Result +ask_for_authentication (struct MHD_Connection *connection, const char *realm) +{ + enum MHD_Result ret; + struct MHD_Response *response; + + response = MHD_create_response_empty (MHD_RF_NONE); + if (! response) + return MHD_NO; + + ret = MHD_queue_basic_auth_required_response3 (connection, + realm, + MHD_YES, + response); + MHD_destroy_response (response); + return ret; +} + + +static int +is_authenticated (struct MHD_Connection *connection, + const char *username, + const char *password) +{ + struct MHD_BasicAuthInfo *auth_info; + int authenticated; + + auth_info = MHD_basic_auth_get_username_password3 (connection); + if (NULL == auth_info) + return 0; + authenticated = + ( (strlen (username) == auth_info->username_len) && + (0 == memcmp (auth_info->username, username, auth_info->username_len)) && + /* The next check against NULL is optional, + * if 'password' is NULL then 'password_len' is always zero. */ + (NULL != auth_info->password) && + (strlen (password) == auth_info->password_len) && + (0 == memcmp (auth_info->password, password, auth_info->password_len)) ); + + MHD_free (auth_info); + + return authenticated; +} + + +static enum MHD_Result +secret_page (struct MHD_Connection *connection) +{ + enum MHD_Result ret; + struct MHD_Response *response; + const char *page = "<html><body>A secret.</body></html>"; + + response = MHD_create_response_from_buffer_static (strlen (page), page); + if (! response) + return MHD_NO; + + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + + return ret; +} + + +static enum MHD_Result +answer_to_connection (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) +{ + (void) cls; /* Unused. Silent compiler warning. */ + (void) url; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; + if (NULL == *req_cls) + { + *req_cls = connection; + return MHD_YES; + } + + if (! is_authenticated (connection, USER, PASSWORD)) + return ask_for_authentication (connection, REALM); + + return secret_page (connection); +} + + +int +main (void) +{ + struct MHD_Daemon *daemon; + char *key_pem; + char *cert_pem; + + key_pem = load_file (SERVERKEYFILE); + cert_pem = load_file (SERVERCERTFILE); + + if ((key_pem == NULL) || (cert_pem == NULL)) + { + printf ("The key/certificate files could not be read.\n"); + if (NULL != key_pem) + free (key_pem); + if (NULL != cert_pem) + free (cert_pem); + return 1; + } + + daemon = + MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS, PORT, NULL, + NULL, &answer_to_connection, NULL, + MHD_OPTION_HTTPS_MEM_KEY, key_pem, + MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_END); + if (NULL == daemon) + { + printf ("%s\n", cert_pem); + + free (key_pem); + free (cert_pem); + + return 1; + } + + (void) getchar (); + + MHD_stop_daemon (daemon); + free (key_pem); + free (cert_pem); + + return 0; +} diff --git a/doc/examples/websocket.c b/doc/examples/websocket.c @@ -0,0 +1,449 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#include <fcntl.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <microhttpd_ws.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#define PORT 80 + +#define PAGE \ + "<!DOCTYPE html>\n" \ + "<html>\n" \ + "<head>\n" \ + "<meta charset=\"UTF-8\">\n" \ + "<title>Websocket Demo</title>\n" \ + "<script>\n" \ + "\n" \ + "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \ + " + ':/" "/' +\n" \ + " window.location.host + '/chat';\n" \ + "let socket = null;\n" \ + "\n" \ + "window.onload = function(event) {\n" \ + " socket = new WebSocket(url);\n" \ + " socket.onopen = function(event) {\n" \ + " document.write('The websocket connection has been " \ + "established.<br>');\n" \ + "\n" \ + " /" "/ Send some text\n" \ + " socket.send('Hello from JavaScript!');\n" \ + " }\n" \ + "\n" \ + " socket.onclose = function(event) {\n" \ + " document.write('The websocket connection has been closed.<br>');\n" \ + " }\n" \ + "\n" \ + " socket.onerror = function(event) {\n" \ + " document.write('An error occurred during the websocket " \ + "communication.<br>');\n" \ + " }\n" \ + "\n" \ + " socket.onmessage = function(event) {\n" \ + " document.write('Websocket message received: ' + " \ + "event.data + '<br>');\n" \ + " }\n" \ + "}\n" \ + "\n" \ + "</script>\n" \ + "</head>\n" \ + "<body>\n" \ + "</body>\n" \ + "</html>" + +#define PAGE_NOT_FOUND \ + "404 Not Found" + +#define PAGE_INVALID_WEBSOCKET_REQUEST \ + "Invalid WebSocket request!" + +static void +send_all (MHD_socket fd, + const char *buf, + size_t len); + +static void +make_blocking (MHD_socket fd); + +static void +upgrade_handler (void *cls, + struct MHD_Connection *connection, + void *req_cls, + const char *extra_in, + size_t extra_in_size, + MHD_socket fd, + struct MHD_UpgradeResponseHandle *urh) +{ + /* make the socket blocking (operating-system-dependent code) */ + make_blocking (fd); + + /* create a websocket stream for this connection */ + struct MHD_WebSocketStream *ws; + int result = MHD_websocket_stream_init (&ws, + 0, + 0); + if (0 != result) + { + /* Couldn't create the websocket stream. + * So we close the socket and leave + */ + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + return; + } + + /* Let's wait for incoming data */ + const size_t buf_len = 256; + char buf[buf_len]; + ssize_t got; + while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws)) + { + got = recv (fd, + buf, + buf_len, + 0); + if (0 >= got) + { + /* the TCP/IP socket has been closed */ + break; + } + + /* parse the entire received data */ + size_t buf_offset = 0; + while (buf_offset < (size_t) got) + { + size_t new_offset = 0; + char *frame_data = NULL; + size_t frame_len = 0; + int status = MHD_websocket_decode (ws, + buf + buf_offset, + ((size_t) got) - buf_offset, + &new_offset, + &frame_data, + &frame_len); + if (0 > status) + { + /* an error occurred and the connection must be closed */ + if (NULL != frame_data) + { + MHD_websocket_free (ws, frame_data); + } + break; + } + else + { + buf_offset += new_offset; + if (0 < status) + { + /* the frame is complete */ + switch (status) + { + case MHD_WEBSOCKET_STATUS_TEXT_FRAME: + /* The client has sent some text. + * We will display it and answer with a text frame. + */ + if (NULL != frame_data) + { + printf ("Received message: %s\n", frame_data); + MHD_websocket_free (ws, frame_data); + frame_data = NULL; + } + result = MHD_websocket_encode_text (ws, + "Hello", + 5, /* length of "Hello" */ + 0, + &frame_data, + &frame_len, + NULL); + if (0 == result) + { + send_all (fd, + frame_data, + frame_len); + } + break; + + case MHD_WEBSOCKET_STATUS_CLOSE_FRAME: + /* if we receive a close frame, we will respond with one */ + MHD_websocket_free (ws, + frame_data); + frame_data = NULL; + + result = MHD_websocket_encode_close (ws, + 0, + NULL, + 0, + &frame_data, + &frame_len); + if (0 == result) + { + send_all (fd, + frame_data, + frame_len); + } + break; + + case MHD_WEBSOCKET_STATUS_PING_FRAME: + /* if we receive a ping frame, we will respond */ + /* with the corresponding pong frame */ + { + char *pong = NULL; + size_t pong_len = 0; + result = MHD_websocket_encode_pong (ws, + frame_data, + frame_len, + &pong, + &pong_len); + if (0 == result) + { + send_all (fd, + pong, + pong_len); + } + MHD_websocket_free (ws, + pong); + } + break; + + default: + /* Other frame types are ignored + * in this minimal example. + * This is valid, because they become + * automatically skipped if we receive them unexpectedly + */ + break; + } + } + if (NULL != frame_data) + { + MHD_websocket_free (ws, frame_data); + } + } + } + } + + /* free the websocket stream */ + MHD_websocket_stream_free (ws); + + /* close the socket when it is not needed anymore */ + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); +} + + +/* This helper function is used for the case that + * we need to resend some data + */ +static void +send_all (MHD_socket fd, + const char *buf, + size_t len) +{ + ssize_t ret; + size_t off; + + for (off = 0; off < len; off += ret) + { + ret = send (fd, + &buf[off], + (int) (len - off), + 0); + if (0 > ret) + { + if (EAGAIN == errno) + { + ret = 0; + continue; + } + break; + } + if (0 == ret) + break; + } +} + + +/* This helper function contains operating-system-dependent code and + * is used to make a socket blocking. + */ +static void +make_blocking (MHD_socket fd) +{ +#ifndef _WIN32 + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + abort (); + if ((flags & ~O_NONBLOCK) != flags) + if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) + abort (); +#else /* _WIN32 */ + unsigned long flags = 0; + + if (0 != ioctlsocket (fd, (int) FIONBIO, &flags)) + abort (); +#endif /* _WIN32 */ +} + + +static enum MHD_Result +access_handler (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) +{ + static int aptr; + struct MHD_Response *response; + int ret; + + (void) cls; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; /* unexpected method */ + if (&aptr != *req_cls) + { + /* do never respond on first call */ + *req_cls = &aptr; + return MHD_YES; + } + *req_cls = NULL; /* reset when done */ + + if (0 == strcmp (url, "/")) + { + /* Default page for visiting the server */ + struct MHD_Response *response; + response = MHD_create_response_from_buffer_static (strlen (PAGE), + PAGE); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + } + else if (0 == strcmp (url, "/chat")) + { + char is_valid = 1; + const char *value = NULL; + char sec_websocket_accept[29]; + + if (0 != MHD_websocket_check_http_version (version)) + { + is_valid = 0; + } + value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if (0 != MHD_websocket_check_connection_header (value)) + { + is_valid = 0; + } + value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_UPGRADE); + if (0 != MHD_websocket_check_upgrade_header (value)) + { + is_valid = 0; + } + value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); + if (0 != MHD_websocket_check_version_header (value)) + { + is_valid = 0; + } + value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); + if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept)) + { + is_valid = 0; + } + + if (1 == is_valid) + { + /* upgrade the connection */ + response = MHD_create_response_for_upgrade (&upgrade_handler, + NULL); + MHD_add_response_header (response, + MHD_HTTP_HEADER_UPGRADE, + "websocket"); + MHD_add_response_header (response, + MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + sec_websocket_accept); + ret = MHD_queue_response (connection, + MHD_HTTP_SWITCHING_PROTOCOLS, + response); + MHD_destroy_response (response); + } + else + { + /* return error page */ + struct MHD_Response *response; + response = + MHD_create_response_from_buffer_static (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + } + } + else + { + struct MHD_Response *response; + response = + MHD_create_response_from_buffer_static (strlen (PAGE_NOT_FOUND), + PAGE_NOT_FOUND); + ret = MHD_queue_response (connection, + MHD_HTTP_NOT_FOUND, + response); + MHD_destroy_response (response); + } + + return ret; +} + + +int +main (int argc, + char *const *argv) +{ + (void) argc; /* Unused. Silent compiler warning. */ + (void) argv; /* Unused. Silent compiler warning. */ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD + | MHD_USE_THREAD_PER_CONNECTION + | MHD_ALLOW_UPGRADE + | MHD_USE_ERROR_LOG, + PORT, NULL, NULL, + &access_handler, NULL, + MHD_OPTION_END); + + if (NULL == daemon) + return 1; + (void) getc (stdin); + + MHD_stop_daemon (daemon); + + return 0; +}