libmicrohttpd2

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

commit 0f5c32a4bb7f2a627926825bf28697e08afef1b5
parent bff98a30e524ffbbc98e8a662b181b134b8aa974
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Wed,  4 Sep 2024 17:56:21 +0200

got demo rewrite to compile

Diffstat:
Msrc/examples2/.gitignore | 3++-
Msrc/examples2/Makefile.am | 5++++-
Asrc/examples2/demo.c | 1192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1198 insertions(+), 2 deletions(-)

diff --git a/src/examples2/.gitignore b/src/examples2/.gitignore @@ -1 +1,2 @@ -minimal_example2 +/minimal_example2 +/demo diff --git a/src/examples2/Makefile.am b/src/examples2/Makefile.am @@ -26,7 +26,10 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile # example programs noinst_PROGRAMS = \ - minimal_example2 + minimal_example2 demo minimal_example2_SOURCES = \ minimal_example2.c + +demo_SOURCES = \ + demo.c diff --git a/src/examples2/demo.c b/src/examples2/demo.c @@ -0,0 +1,1192 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2013-2024 Christian Grothoff (and other contributing authors) + Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file demo.c + * @brief complex demonstration site: create directory index, offer + * upload via form and HTTP POST, download with mime type detection + * and error reporting (403, etc.) --- and all of this with + * high-performance settings (large buffers, thread pool). + * If you want to benchmark MHD, this code should be used to + * run tests against. Note that the number of threads may need + * to be adjusted depending on the number of available cores. + * @author Christian Grothoff + * @author Karlson2k (Evgeny Grin) + */ +#include <microhttpd2.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <pthread.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#ifdef MHD_HAVE_LIBMAGIC +#include <magic.h> +#endif /* MHD_HAVE_LIBMAGIC */ +#include <limits.h> +#include <ctype.h> +#include <errno.h> + +#if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 +#undef MHD_CPU_COUNT +#endif +#if ! defined(MHD_CPU_COUNT) +#define MHD_CPU_COUNT 2 +#endif + +#ifndef PATH_MAX +/* Some platforms (namely: GNU Hurd) do no define PATH_MAX. + As it is only example for MHD, just use reasonable value for PATH_MAX. */ +#define PATH_MAX 16384 +#endif + +/** + * Number of threads to run in the thread pool. Should (roughly) match + * the number of cores on your system. + */ +#define NUMBER_OF_THREADS MHD_CPU_COUNT + +#ifdef MHD_HAVE_LIBMAGIC +/** + * How many bytes of a file do we give to libmagic to determine the mime type? + * 16k might be a bit excessive, but ought not hurt performance much anyway, + * and should definitively be on the safe side. + */ +#define MAGIC_HEADER_SIZE (16 * 1024) +#endif /* MHD_HAVE_LIBMAGIC */ + + +/** + * Page returned for file-not-found. + */ +#define FILE_NOT_FOUND_PAGE \ + "<html><head><title>File not found</title></head><body>File not found</body></html>" + + +/** + * Page returned for internal errors. + */ +#define INTERNAL_ERROR_PAGE \ + "<html><head><title>Internal error</title></head><body>Internal error</body></html>" + + +/** + * Page returned for refused requests. + */ +#define REQUEST_REFUSED_PAGE \ + "<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>" + + +/** + * Head of index page. + */ +#define INDEX_PAGE_HEADER \ + "<html>\n<head><title>Welcome</title></head>\n<body>\n" \ + "<h1>Upload</h1>\n" \ + "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">\n" \ + "<dl><dt>Content type:</dt><dd>" \ + "<input type=\"radio\" name=\"category\" value=\"books\">Book</input>" \ + "<input type=\"radio\" name=\"category\" value=\"images\">Image</input>" \ + "<input type=\"radio\" name=\"category\" value=\"music\">Music</input>" \ + "<input type=\"radio\" name=\"category\" value=\"software\">Software</input>" \ + "<input type=\"radio\" name=\"category\" value=\"videos\">Videos</input>\n" \ + "<input type=\"radio\" name=\"category\" value=\"other\" checked>Other</input></dd>" \ + "<dt>Language:</dt><dd>" \ + "<input type=\"radio\" name=\"language\" value=\"no-lang\" checked>none</input>" \ + "<input type=\"radio\" name=\"language\" value=\"en\">English</input>" \ + "<input type=\"radio\" name=\"language\" value=\"de\">German</input>" \ + "<input type=\"radio\" name=\"language\" value=\"fr\">French</input>" \ + "<input type=\"radio\" name=\"language\" value=\"es\">Spanish</input></dd>\n" \ + "<dt>File:</dt><dd>" \ + "<input type=\"file\" name=\"upload\"/></dd></dl>" \ + "<input type=\"submit\" value=\"Send!\"/>\n" \ + "</form>\n" \ + "<h1>Download</h1>\n" \ + "<ol>\n" + +/** + * Footer of index page. + */ +#define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>" + + +/** + * NULL-terminated array of supported upload categories. Should match HTML + * in the form. + */ +static const char *const categories[] = { + "books", + "images", + "music", + "software", + "videos", + "other", + NULL, +}; + + +/** + * Specification of a supported language. + */ +struct Language +{ + /** + * Directory name for the language. + */ + const char *dirname; + + /** + * Long name for humans. + */ + const char *longname; + +}; + +/** + * NULL-terminated array of supported upload categories. Should match HTML + * in the form. + */ +static const struct Language languages[] = { + { "no-lang", "No language specified" }, + { "en", "English" }, + { "de", "German" }, + { "fr", "French" }, + { "es", "Spanish" }, + { NULL, NULL }, +}; + + +/** + * Response returned if the requested file does not exist (or is not accessible). + */ +static struct MHD_Response *file_not_found_response; + +/** + * Response returned for internal errors. + */ +static struct MHD_Response *internal_error_response; + +/** + * Response returned for '/' (GET) to list the contents of the directory and allow upload. + */ +static struct MHD_Response *cached_directory_response; + +/** + * Response returned for refused uploads. + */ +static struct MHD_Response *request_refused_response; + +/** + * Mutex used when we update the cached directory response object. + */ +static pthread_mutex_t mutex; + +#ifdef MHD_HAVE_LIBMAGIC +/** + * Global handle to MAGIC data. + */ +static magic_t magic; +#endif /* MHD_HAVE_LIBMAGIC */ + + +/** + * Mark the given response as HTML for the browser. + * + * @param response response to mark + */ +static void +mark_as_html (struct MHD_Response *response) +{ + if (NULL == response) + return; + (void) MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/html"); +} + + +/** + * Replace the existing 'cached_directory_response' with the + * given response. + * + * @param response new directory response + */ +static void +update_cached_response (struct MHD_Response *response) +{ + if (NULL != response) + { + if (MHD_SC_OK != + MHD_response_set_option (response, + &MHD_R_OPTION_REUSABLE ( + MHD_YES))) + exit (1); + } + (void) pthread_mutex_lock (&mutex); + if (NULL != cached_directory_response) + MHD_response_destroy (cached_directory_response); + cached_directory_response = response; + (void) pthread_mutex_unlock (&mutex); +} + + +/** + * Context keeping the data for the response we're building. + */ +struct ResponseDataContext +{ + /** + * Response data string. + */ + char *buf; + + /** + * Number of bytes allocated for 'buf'. + */ + size_t buf_size; + + /** + * Current position where we append to 'buf'. Must be smaller or equal to 'buf_size'. + */ + size_t off; + +}; + + +/** + * Create a listing of the files in 'dirname' in HTML. + * + * @param rdc where to store the list of files + * @param dirname name of the directory to list + * @return true on success, false on error + */ +static bool +list_directory (struct ResponseDataContext *rdc, + const char *dirname) +{ + char fullname[PATH_MAX]; + struct stat sbuf; + DIR *dir; + struct dirent *de; + + if (NULL == (dir = opendir (dirname))) + return false; + while (NULL != (de = readdir (dir))) + { + int res; + if ('.' == de->d_name[0]) + continue; + if (sizeof (fullname) <= (unsigned int) + snprintf (fullname, sizeof (fullname), + "%s/%s", + dirname, de->d_name)) + continue; /* ugh, file too long? how can this be!? */ + if (0 != stat (fullname, &sbuf)) + continue; /* ugh, failed to 'stat' */ + if (! S_ISREG (sbuf.st_mode)) + continue; /* not a regular file, skip */ + if (rdc->off + 1024 > rdc->buf_size) + { + void *r; + + if ( (2 * rdc->buf_size + 1024) < rdc->buf_size) + break; /* more than SIZE_T _index_ size? Too big for us */ + rdc->buf_size = 2 * rdc->buf_size + 1024; + if (NULL == (r = realloc (rdc->buf, rdc->buf_size))) + break; /* out of memory */ + rdc->buf = r; + } + res = snprintf (rdc->buf + rdc->off, + rdc->buf_size - rdc->off, + "<li><a href=\"/%s\">%s</a></li>\n", + fullname, + de->d_name); + if (0 >= res) + continue; /* snprintf() error */ + if (rdc->buf_size - rdc->off <= (size_t) res) + continue; /* buffer too small?? */ + rdc->off += (size_t) res; + } + (void) closedir (dir); + return true; +} + + +/** + * Re-scan our local directory and re-build the index. + */ +static void +update_directory (void) +{ + static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */ + struct MHD_Response *response; + struct ResponseDataContext rdc; + unsigned int language_idx; + unsigned int category_idx; + const struct Language *language; + const char *category; + char dir_name[128]; + struct stat sbuf; + int res; + size_t len; + + rdc.off = 0; + rdc.buf_size = initial_allocation; + rdc.buf = malloc (rdc.buf_size); + if (NULL == rdc.buf) + { + update_cached_response (NULL); + return; + } + len = strlen (INDEX_PAGE_HEADER); + if (rdc.buf_size < len) + { /* buffer too small */ + free (rdc.buf); + update_cached_response (NULL); + return; + } + memcpy (rdc.buf + rdc.off, + INDEX_PAGE_HEADER, + len); + rdc.off += len; + for (language_idx = 0; NULL != languages[language_idx].dirname; + language_idx++) + { + language = &languages[language_idx]; + + if (0 != stat (language->dirname, &sbuf)) + continue; /* empty */ + /* we ensured always +1k room, filenames are ~256 bytes, + so there is always still enough space for the header + without need for an additional reallocation check. */ + res = snprintf (rdc.buf + rdc.off, rdc.buf_size - rdc.off, + "<h2>%s</h2>\n", + language->longname); + if (0 >= res) + continue; /* snprintf() error */ + if (rdc.buf_size - rdc.off <= (size_t) res) + continue; /* buffer too small?? */ + rdc.off += (size_t) res; + for (category_idx = 0; NULL != categories[category_idx]; category_idx++) + { + category = categories[category_idx]; + res = snprintf (dir_name, sizeof (dir_name), + "%s/%s", + language->dirname, + category); + if ((0 >= res) || (sizeof (dir_name) <= (size_t) res)) + continue; /* cannot print dir name */ + if (0 != stat (dir_name, &sbuf)) + continue; /* empty */ + + /* we ensured always +1k room, filenames are ~256 bytes, + so there is always still enough space for the header + without need for an additional reallocation check. */ + res = snprintf (rdc.buf + rdc.off, rdc.buf_size - rdc.off, + "<h3>%s</h3>\n", + category); + if (0 >= res) + continue; /* snprintf() error */ + if (rdc.buf_size - rdc.off <= (size_t) res) + continue; /* buffer too small?? */ + rdc.off += (size_t) res; + + if (! list_directory (&rdc, + dir_name)) + { + free (rdc.buf); + update_cached_response (NULL); + return; + } + } + } + /* we ensured always +1k room, filenames are ~256 bytes, + so there is always still enough space for the footer + without need for a final reallocation check. */ + len = strlen (INDEX_PAGE_FOOTER); + if (rdc.buf_size - rdc.off < len) + { /* buffer too small */ + free (rdc.buf); + update_cached_response (NULL); + return; + } + memcpy (rdc.buf + rdc.off, INDEX_PAGE_FOOTER, len); + rdc.off += len; + initial_allocation = rdc.buf_size; /* remember for next time */ + response = + MHD_response_from_buffer (MHD_HTTP_STATUS_OK, + rdc.off, + rdc.buf, + &free, + rdc.buf); + mark_as_html (response); +#ifdef FORCE_CLOSE + (void) MHD_response_set_option (response, + &MHD_R_OPTION_CONN_CLOSE (MHD_YES)); +#endif + update_cached_response (response); +} + + +/** + * Context we keep for an upload. + */ +struct UploadContext +{ + /** + * Handle where we write the uploaded file to. + */ + int fd; + + /** + * Name of our temporary file where we initially read to. + */ + char tmpname[PATH_MAX]; + + /** + * Name of the file on disk. + */ + char *filename; + + /** + * True once @a tmpfile exists. + */ + bool have_file; + + /** + * True if we had an error handling the upload. + */ + bool error_file; + +}; + + +/** + * "Stream" reader for POST data. + * This callback is called to incrementally process parsed POST data sent by + * the client. + * The pointers to the MHD_String and MHD_StringNullable are valid only until + * return from this callback. + * The pointers to the strings and the @a data are valid only until return from + * this callback. + * + * @param req the request + * @param cls user-specified closure + * @param name the name of the POST field + * @param filename the name of the uploaded file, @a cstr member is NULL if not + * known / not provided + * @param content_type the mime-type of the data, cstr member is NULL if not + * known / not provided + * @param encoding the encoding of the data, cstr member is NULL if not known / + * not provided + * @param size the number of bytes in @a data available, may be zero if + * the @a final_data is #MHD_YES + * @param data the pointer to @a size bytes of data at the specified + * @a off offset, NOT zero-terminated + * @param off the offset of @a data in the overall value, always equal to + * the sum of sizes of previous calls for the same field / file; + * client may provide more than one field with the same name and + * the same filename, the new filed (or file) is indicated by zero + * value of @a off (and the end is indicated by @a final_data) + * @param final_data if set to #MHD_YES then full field data is provided, + * if set to #MHD_NO then more field data may be provided + * @return action specifying how to proceed: + * #MHD_upload_action_continue() if all is well, + * #MHD_upload_action_suspend() to stop reading the upload until + * the request is resumed, + * #MHD_upload_action_abort_request() to close the socket, + * or a response to discard the rest of the upload and transmit + * the response + * @ingroup action + */ +static const struct MHD_UploadAction * +stream_reader (struct MHD_Request *req, + void *cls, + const struct MHD_String *name, + const struct MHD_StringNullable *filename, + const struct MHD_StringNullable *content_type, + const struct MHD_StringNullable *encoding, + size_t size, + const void *data, + uint_fast64_t off, + enum MHD_Bool final_data) +{ + struct UploadContext *uc = cls; + + (void) content_type; /* Unused. Silent compiler warning. */ + (void) encoding; /* Unused. Silent compiler warning. */ + (void) off; /* Unused. Silent compiler warning. */ + if ( (0 == strcmp (name->cstr, + "category")) || + (0 == strcmp (name->cstr, + "filename")) || + (0 == strcmp (name->cstr, + "language")) ) + { + return MHD_upload_action_from_response (req, + request_refused_response); + } + if (0 != strcmp (name->cstr, + "upload")) + { + fprintf (stderr, + "Ignoring unexpected form value `%s'\n", + name->cstr); + return MHD_upload_action_continue (req); + } + if (NULL == filename->cstr) + { + fprintf (stderr, + "No filename, aborting upload.\n"); + return MHD_upload_action_from_response (req, + request_refused_response); + } + if (-1 == uc->fd) + { + if (0 != filename->len) + { + if ( (NULL != strstr (filename->cstr, + "..")) || + (NULL != strchr (filename->cstr, + '/')) || + (NULL != strchr (filename->cstr, + '\\')) ) + { + return MHD_upload_action_from_response (req, + request_refused_response); + } + uc->filename = strdup (filename->cstr); + } + if (NULL == uc->filename) + { + fprintf (stderr, + "No filename for incremental upload\n"); + return MHD_upload_action_from_response (req, + internal_error_response); + } + + { + size_t slen = strlen (uc->filename); + size_t i; + + for (i = 0; i < slen; i++) + if (! isprint ((unsigned char) uc->filename[i])) + uc->filename[i] = '_'; + } + uc->fd = mkstemp (uc->tmpname); + if (-1 == uc->fd) + { + fprintf (stderr, + "Error creating temporary file `%s' for upload: %s\n", + uc->tmpname, + strerror (errno)); + return MHD_upload_action_from_response (req, + request_refused_response); + } + } + if ( (0 != size) && +#if ! defined(_WIN32) || defined(__CYGWIN__) + (size != + (size_t) write (uc->fd, + data, + size)) +#else /* Native W32 */ + (size != + (size_t) write (uc->fd, + data, + (unsigned int) size)) +#endif /* Native W32 */ + ) + { + /* write failed; likely: disk full */ + fprintf (stderr, + "Error writing to file `%s': %s\n", + uc->tmpname, + strerror (errno)); + (void) close (uc->fd); + uc->fd = -1; + unlink (uc->tmpname); + return MHD_upload_action_from_response (req, + internal_error_response); + } + if (final_data) + { + (void) close (uc->fd); + uc->fd = -1; + uc->have_file = true; + } + return MHD_upload_action_continue (req); +} + + +/** + * Iterator over POST data. + * + * The @a data pointer is valid only until return from this function. + * + * The pointers to the strings in @a data are valid until any MHD_UploadAction + * is provided. If the data is needed beyond this point, it should be copied. + * + * @param cls closure + * @param data the element of the post data, the pointer is valid only until + * return from this function + * @return #MHD_YES to continue iterating, + * #MHD_NO to abort the iteration + * @ingroup request + */ +static enum MHD_Bool +handle_full_upload (void *cls, + const struct MHD_PostField *data) +{ + struct UploadContext *uc = cls; + int fd; + + if (0 != strcmp ("upload", + data->name.cstr)) + return MHD_YES; + if (uc->have_file) + { + uc->error_file = true; + return MHD_NO; + } + if (NULL == data->filename.cstr) + { + fprintf (stderr, + "Filename missing for full upload\n"); + uc->error_file = true; + return MHD_NO; + } + uc->filename = strdup (data->filename.cstr); + fd = mkstemp (uc->tmpname); + if (-1 == fd) + { + fprintf (stderr, + "Error creating temporary file `%s' for upload: %s\n", + uc->tmpname, + strerror (errno)); + uc->error_file = true; + return MHD_NO; + } + if (NULL != data->value.cstr) + { + // FIXME: error handling, ... +#if ! defined(_WIN32) || defined(__CYGWIN__) + write (fd, + data->value.cstr, + data->value.len); +#else /* Native W32 */ + write (fd, + data->value.cstr, + (unsigned int) data->value.len); +#endif /* Native W32 */ + } + close (fd); + uc->have_file = true; + return MHD_NO; +} + + +/** + * The callback to be called when finished with processing + * of the postprocessor upload data. + * @param req the request + * @param cls the closure + * @param parsing_result the result of POST data parsing + * @return the action to proceed + */ +static const struct MHD_UploadAction * +done_cb (struct MHD_Request *req, + void *cls, + enum MHD_PostParseResult parsing_result) +{ + struct UploadContext *uc = cls; + const struct MHD_UploadAction *ret; + const struct MHD_StringNullable *cat; + const struct MHD_StringNullable *lang; + char fn[PATH_MAX]; + int res; + + if (MHD_POST_PARSE_RES_OK != parsing_result) + { + fprintf (stderr, + "Upload parsing failed with status %d\n", + (int) parsing_result); + ret = MHD_upload_action_from_response (req, + request_refused_response); + goto cleanup; + } + if (-1 != uc->fd) + { + fprintf (stderr, + "Upload incomplete (fd still open)\n"); + (void) close (uc->fd); + if (NULL != uc->filename) + { + fprintf (stderr, + "Upload of file `%s' failed (incomplete or aborted), removing file.\n", + uc->filename); + } + (void) unlink (uc->tmpname); + ret = MHD_upload_action_from_response (req, + internal_error_response); + goto cleanup; + } + cat = MHD_request_get_value (req, + MHD_VK_POSTDATA, + "category"); + lang = MHD_request_get_value (req, + MHD_VK_POSTDATA, + "language"); + if ( (NULL == lang) || + (NULL == lang->cstr) || + (NULL == cat) || + (NULL == cat->cstr) ) + { + fprintf (stderr, + "Required argument missing\n"); + if (uc->have_file) + (void) unlink (uc->tmpname); + ret = MHD_upload_action_from_response (req, + request_refused_response); + goto cleanup; + } + /* FIXME: ugly that we may have to deal with upload + here as well! */ + MHD_request_get_post_data_cb (req, + &handle_full_upload, + uc); + if (uc->error_file) + { + fprintf (stderr, + "Upload data provided twice!\n"); + ret = MHD_upload_action_from_response (req, + internal_error_response); + goto cleanup; + } + if (NULL == uc->filename) + { + fprintf (stderr, + "Filename unavailable!?\n"); + ret = MHD_upload_action_from_response (req, + internal_error_response); + goto cleanup; + } + /* create directories -- if they don't exist already */ +#if ! defined(_WIN32) || defined(__CYGWIN__) + (void) mkdir (lang->cstr, + S_IRWXU); +#else + (void) mkdir (lang->cstr); +#endif + res = snprintf (fn, + sizeof (fn), + "%s/%s", + lang->cstr, + cat->cstr); + if ( (0 >= res) || + (sizeof (fn) <= (size_t) res) ) + { + fprintf (stderr, + "snprintf() failed at %d\n", __LINE__); + ret = MHD_upload_action_from_response (req, + request_refused_response); + goto cleanup; + } +#if ! defined(_WIN32) || defined(__CYGWIN__) + (void) mkdir (fn, + S_IRWXU); +#else + (void) mkdir (fn); +#endif + /* compute filename */ + res = snprintf (fn, + sizeof (fn), + "%s/%s/%s", + lang->cstr, + cat->cstr, + uc->filename); + if ( (0 >= res) || + (sizeof (fn) <= (size_t) res) ) + { + fprintf (stderr, + "snprintf() failed at %d\n", __LINE__); + ret = MHD_upload_action_from_response (req, + request_refused_response); + goto cleanup; + } + if (0 != + rename (uc->tmpname, + fn)) + { + fprintf (stderr, + "Failed to rename %s to %s: %s\n", + uc->tmpname, + fn, + strerror (errno)); + ret = MHD_upload_action_from_response (req, + request_refused_response); + goto cleanup; + } + chmod (uc->filename, + S_IRUSR | S_IWUSR); + + update_directory (); + (void) pthread_mutex_lock (&mutex); + if (NULL == cached_directory_response) + ret = MHD_upload_action_from_response (req, + internal_error_response); + else + ret = MHD_upload_action_from_response (req, + cached_directory_response); + (void) pthread_mutex_unlock (&mutex); +cleanup: + if (NULL != uc->filename) + free (uc->filename); + free (uc); + return ret; +} + + +/** + * Main callback from MHD, used to generate the page. + * + * @param cls NULL + * @param request the request object + * @param path the requested uri (without arguments after "?") + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param upload_size the size of the message upload content payload, + * #MHD_SIZE_UNKNOWN for chunked uploads (if the + * final chunk has not been processed yet) + * @return action how to proceed, NULL + * if the request must be aborted due to a serious + * error while handling the request (implies closure + * of underling data stream, for HTTP/1.1 it means + * socket closure). */ +static const struct MHD_Action * +generate_page (void *cls, + struct MHD_Request *request, + const struct MHD_String *path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + struct MHD_Response *response; + const char *url = path->cstr; + + (void ) cls; + + if ((0 != upload_size) && + ( (MHD_HTTP_METHOD_GET == method) || + (MHD_HTTP_METHOD_HEAD == method) )) + { + /* Wrong request, refuse */ + return MHD_action_from_response (request, + request_refused_response); + } + + if ( ( (MHD_HTTP_METHOD_GET == method) || + (MHD_HTTP_METHOD_HEAD == method) ) && + (0 != strcmp (url, + "/")) ) + { + /* should be file download */ +#ifdef MHD_HAVE_LIBMAGIC + char file_data[MAGIC_HEADER_SIZE]; + ssize_t got; +#endif /* MHD_HAVE_LIBMAGIC */ + const char *mime; + int fd = -1; + struct stat buf; + + fd = -1; + if ( (NULL == strstr (&url[1], + "..")) && + ('/' != url[1]) ) + { + fd = open (&url[1], + O_RDONLY); + if ( (-1 != fd) && + ( (0 != fstat (fd, + &buf)) || + (! S_ISREG (buf.st_mode)) ) ) + { + (void) close (fd); + fd = -1; + } + } + if (-1 == fd) + return MHD_action_from_response (request, + file_not_found_response); +#ifdef MHD_HAVE_LIBMAGIC + /* read beginning of the file to determine mime type */ + got = read (fd, + file_data, + sizeof (file_data)); + (void) lseek (fd, + 0, + SEEK_SET); + if (0 < got) + mime = magic_buffer (magic, + file_data, + (size_t) got); + else +#endif /* MHD_HAVE_LIBMAGIC */ + mime = NULL; + { + /* Set mime-type by file-extension in some cases */ + const char *ldot = strrchr (&url[1], + '.'); + + if (NULL != ldot) + { + if (0 == strcasecmp (ldot, + ".html")) + mime = "text/html"; + if (0 == strcasecmp (ldot, + ".css")) + mime = "text/css"; + if (0 == strcasecmp (ldot, + ".css3")) + mime = "text/css"; + if (0 == strcasecmp (ldot, + ".js")) + mime = "application/javascript"; + } + } + + response = MHD_response_from_fd (MHD_HTTP_STATUS_OK, + fd, + 0LLU /* offset */, + (uint_fast64_t) buf.st_size); + if (NULL == response) + { + /* internal error (i.e. out of memory) */ + (void) close (fd); + return MHD_action_abort_request (request); + } + + /* add mime type if we had one */ + if (NULL != mime) + (void) MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime); + return MHD_action_from_response (request, + response); + } + + if ( (MHD_HTTP_METHOD_POST == method) && + (0 == strcmp (path->cstr, + "/")) ) + { + struct UploadContext *uc; + + uc = malloc (sizeof (struct UploadContext)); + if (NULL == uc) + return MHD_action_abort_request (request); /* out of memory, close connection */ + memset (uc, + 0, + sizeof (struct UploadContext)); + strcpy (uc->tmpname, + "mhd-demo-XXXXXX"); + uc->fd = -1; + return MHD_action_parse_post (request, + 64 * 1024 /* buffer size */, + 1024 /* max non-stream size */, + MHD_HTTP_POST_ENCODING_OTHER, + &stream_reader, + uc, + &done_cb, + uc); + } + if ( ( (MHD_HTTP_METHOD_GET == method) || + (MHD_HTTP_METHOD_HEAD == method) ) && + (0 == strcmp (url, + "/")) ) + { + const struct MHD_Action *ret; + + (void) pthread_mutex_lock (&mutex); + if (NULL == cached_directory_response) + ret = MHD_action_from_response (request, + internal_error_response); + else + ret = MHD_action_from_response (request, + cached_directory_response); + (void) pthread_mutex_unlock (&mutex); + return ret; + } + /* unexpected request, refuse */ + return MHD_action_from_response (request, + request_refused_response); +} + + +#if ! defined(_WIN32) || defined(__CYGWIN__) +/** + * Function called if we get a SIGPIPE. Does nothing. + * + * @param sig will be SIGPIPE (ignored) + */ +static void +catcher (int sig) +{ + (void) sig; /* Unused. Silent compiler warning. */ + /* do nothing */ +} + + +/** + * setup handlers to ignore SIGPIPE. + */ +static void +ignore_sigpipe (void) +{ + struct sigaction oldsig; + struct sigaction sig; + + sig.sa_handler = &catcher; + sigemptyset (&sig.sa_mask); +#ifdef SA_INTERRUPT + sig.sa_flags = SA_INTERRUPT; /* SunOS */ +#else + sig.sa_flags = SA_RESTART; +#endif + if (0 != sigaction (SIGPIPE, + &sig, + &oldsig)) + fprintf (stderr, + "Failed to install SIGPIPE handler: %s\n", strerror (errno)); +} + + +#endif + + +/** + * Entry point to demo. Note: this HTTP server will make all + * files in the current directory and its subdirectories available + * to anyone. Press ENTER to stop the server once it has started. + * + * @param argc number of arguments in argv + * @param argv first and only argument should be the port number + * @return 0 on success + */ +int +main (int argc, + char *const *argv) +{ + struct MHD_Daemon *d; + unsigned int port; + + if ( (argc != 2) || + (1 != sscanf (argv[1], "%u", &port)) || + (UINT16_MAX < port) ) + { + fprintf (stderr, + "%s PORT\n", argv[0]); + return 1; + } +#if ! defined(_WIN32) || defined(__CYGWIN__) + ignore_sigpipe (); +#endif +#ifdef MHD_HAVE_LIBMAGIC + magic = magic_open (MAGIC_MIME_TYPE); + (void) magic_load (magic, NULL); +#endif /* MHD_HAVE_LIBMAGIC */ + + (void) pthread_mutex_init (&mutex, NULL); + file_not_found_response = + MHD_response_from_buffer_static ( + MHD_HTTP_STATUS_NOT_FOUND, + strlen (FILE_NOT_FOUND_PAGE), + (const void *) FILE_NOT_FOUND_PAGE); + mark_as_html (file_not_found_response); + if (MHD_SC_OK != + MHD_response_set_option (file_not_found_response, + &MHD_R_OPTION_REUSABLE ( + MHD_YES))) + return 1; + request_refused_response = + MHD_response_from_buffer_static ( + MHD_HTTP_STATUS_FORBIDDEN, + strlen (REQUEST_REFUSED_PAGE), + (const void *) REQUEST_REFUSED_PAGE); + mark_as_html (request_refused_response); + if (MHD_SC_OK != + MHD_response_set_option (request_refused_response, + &MHD_R_OPTION_REUSABLE ( + MHD_YES))) + return 1; + internal_error_response = + MHD_response_from_buffer_static ( + MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, + strlen (INTERNAL_ERROR_PAGE), + (const void *) INTERNAL_ERROR_PAGE); + mark_as_html (internal_error_response); + if (MHD_SC_OK != + MHD_response_set_option (internal_error_response, + &MHD_R_OPTION_REUSABLE ( + MHD_YES))) + return 1; + update_directory (); + d = MHD_daemon_create (&generate_page, + NULL); + if (NULL == d) + return 1; + if (MHD_SC_OK != + MHD_DAEMON_SET_OPTIONS ( + d, + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_WORKER_THREADS (NUMBER_OF_THREADS), +#ifdef PRODUCTION + MHD_D_OPTION_PER_IP_LIMIT (64), +#endif + MHD_D_OPTION_DEFAULT_TIMEOUT (120 /* seconds */), + MHD_D_OPTION_CONN_MEMORY_LIMIT (256 * 1024), + MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, + (uint_least16_t) port))) + return 1; + if (MHD_SC_OK != + MHD_daemon_start (d)) + { + MHD_daemon_destroy (d); + return 1; + } + fprintf (stderr, "HTTP server running. Press ENTER to stop the server.\n"); + (void) getc (stdin); + MHD_daemon_destroy (d); + MHD_response_destroy (file_not_found_response); + MHD_response_destroy (request_refused_response); + MHD_response_destroy (internal_error_response); + update_cached_response (NULL); + (void) pthread_mutex_destroy (&mutex); +#ifdef MHD_HAVE_LIBMAGIC + magic_close (magic); +#endif /* MHD_HAVE_LIBMAGIC */ + return 0; +} + + +/* end of demo.c */