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:
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 */