mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
1028 lines
28 KiB
C
1028 lines
28 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
Copyright (C) 2013 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 "MHD_config.h"
|
|
#include "platform.h"
|
|
#include <microhttpd.h>
|
|
#include <unistd.h>
|
|
#include <pthread.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>
|
|
|
|
#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)
|
|
{
|
|
(void) MHD_add_response_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)
|
|
{
|
|
(void) pthread_mutex_lock (&mutex);
|
|
if (NULL != cached_directory_response)
|
|
MHD_destroy_response (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_len;
|
|
|
|
/**
|
|
* Current position where we append to 'buf'. Must be smaller or equal to 'buf_len'.
|
|
*/
|
|
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 #MHD_YES on success, #MHD_NO on error
|
|
*/
|
|
static enum MHD_Result
|
|
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 MHD_NO;
|
|
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_len)
|
|
{
|
|
void *r;
|
|
|
|
if ( (2 * rdc->buf_len + 1024) < rdc->buf_len)
|
|
break; /* more than SIZE_T _index_ size? Too big for us */
|
|
rdc->buf_len = 2 * rdc->buf_len + 1024;
|
|
if (NULL == (r = realloc (rdc->buf, rdc->buf_len)))
|
|
break; /* out of memory */
|
|
rdc->buf = r;
|
|
}
|
|
res = snprintf (&rdc->buf[rdc->off],
|
|
rdc->buf_len - rdc->off,
|
|
"<li><a href=\"/%s\">%s</a></li>\n",
|
|
fullname,
|
|
de->d_name);
|
|
if (0 >= res)
|
|
continue; /* snprintf() error */
|
|
if (rdc->buf_len - rdc->off <= (size_t) res)
|
|
continue; /* buffer too small?? */
|
|
rdc->off += (size_t) res;
|
|
}
|
|
(void) closedir (dir);
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.buf_len = initial_allocation;
|
|
if (NULL == (rdc.buf = malloc (rdc.buf_len)))
|
|
{
|
|
update_cached_response (NULL);
|
|
return;
|
|
}
|
|
len = strlen (INDEX_PAGE_HEADER);
|
|
if (rdc.buf_len <= len)
|
|
{ /* buffer too small */
|
|
free (rdc.buf);
|
|
update_cached_response (NULL);
|
|
return;
|
|
}
|
|
memcpy (rdc.buf, 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_len - rdc.off,
|
|
"<h2>%s</h2>\n",
|
|
language->longname);
|
|
if (0 >= res)
|
|
continue; /* snprintf() error */
|
|
if (rdc.buf_len - 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_len - rdc.off,
|
|
"<h3>%s</h3>\n",
|
|
category);
|
|
if (0 >= res)
|
|
continue; /* snprintf() error */
|
|
if (rdc.buf_len - rdc.off <= (size_t) res)
|
|
continue; /* buffer too small?? */
|
|
rdc.off += (size_t) res;
|
|
|
|
if (MHD_NO == 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_len - rdc.off <= len)
|
|
{ /* buffer too small */
|
|
free (rdc.buf);
|
|
update_cached_response (NULL);
|
|
return;
|
|
}
|
|
memcpy (rdc.buf, INDEX_PAGE_FOOTER, len);
|
|
rdc.off += len;
|
|
initial_allocation = rdc.buf_len; /* remember for next time */
|
|
response =
|
|
MHD_create_response_from_buffer_with_free_callback (rdc.off,
|
|
rdc.buf,
|
|
&free);
|
|
mark_as_html (response);
|
|
#ifdef FORCE_CLOSE
|
|
(void) MHD_add_response_header (response,
|
|
MHD_HTTP_HEADER_CONNECTION,
|
|
"close");
|
|
#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 the file on disk (used to remove on errors).
|
|
*/
|
|
char *filename;
|
|
|
|
/**
|
|
* Language for the upload.
|
|
*/
|
|
char *language;
|
|
|
|
/**
|
|
* Category for the upload.
|
|
*/
|
|
char *category;
|
|
|
|
/**
|
|
* Post processor we're using to process the upload.
|
|
*/
|
|
struct MHD_PostProcessor *pp;
|
|
|
|
/**
|
|
* Handle to connection that we're processing the upload for.
|
|
*/
|
|
struct MHD_Connection *connection;
|
|
|
|
/**
|
|
* Response to generate, NULL to use directory.
|
|
*/
|
|
struct MHD_Response *response;
|
|
};
|
|
|
|
|
|
/**
|
|
* Append the 'size' bytes from 'data' to '*ret', adding
|
|
* 0-termination. If '*ret' is NULL, allocate an empty string first.
|
|
*
|
|
* @param ret string to update, NULL or 0-terminated
|
|
* @param data data to append
|
|
* @param size number of bytes in 'data'
|
|
* @return #MHD_NO on allocation failure, #MHD_YES on success
|
|
*/
|
|
static enum MHD_Result
|
|
do_append (char **ret,
|
|
const char *data,
|
|
size_t size)
|
|
{
|
|
char *buf;
|
|
size_t old_len;
|
|
|
|
if (NULL == *ret)
|
|
old_len = 0;
|
|
else
|
|
old_len = strlen (*ret);
|
|
if (NULL == (buf = malloc (old_len + size + 1)))
|
|
return MHD_NO;
|
|
if (NULL != *ret)
|
|
{
|
|
memcpy (buf,
|
|
*ret,
|
|
old_len);
|
|
free (*ret);
|
|
}
|
|
memcpy (&buf[old_len],
|
|
data,
|
|
size);
|
|
buf[old_len + size] = '\0';
|
|
*ret = buf;
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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, always MHD_POSTDATA_KIND when called from MHD
|
|
* @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
|
|
process_upload_data (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 UploadContext *uc = cls;
|
|
size_t i;
|
|
int res;
|
|
(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, "category"))
|
|
return do_append (&uc->category, data, size);
|
|
if (0 == strcmp (key, "language"))
|
|
return do_append (&uc->language, data, size);
|
|
if (0 != strcmp (key, "upload"))
|
|
{
|
|
fprintf (stderr,
|
|
"Ignoring unexpected form value `%s'\n",
|
|
key);
|
|
return MHD_YES; /* ignore */
|
|
}
|
|
if (NULL == filename)
|
|
{
|
|
fprintf (stderr, "No filename, aborting upload.\n");
|
|
return MHD_NO; /* no filename, error */
|
|
}
|
|
if ( (NULL == uc->category) ||
|
|
(NULL == uc->language) )
|
|
{
|
|
fprintf (stderr,
|
|
"Missing form data for upload `%s'\n",
|
|
filename);
|
|
uc->response = request_refused_response;
|
|
return MHD_NO;
|
|
}
|
|
if (-1 == uc->fd)
|
|
{
|
|
char fn[PATH_MAX];
|
|
|
|
if ( (NULL != strstr (filename, "..")) ||
|
|
(NULL != strchr (filename, '/')) ||
|
|
(NULL != strchr (filename, '\\')) )
|
|
{
|
|
uc->response = request_refused_response;
|
|
return MHD_NO;
|
|
}
|
|
/* create directories -- if they don't exist already */
|
|
#ifdef WINDOWS
|
|
(void) mkdir (uc->language);
|
|
#else
|
|
(void) mkdir (uc->language, S_IRWXU);
|
|
#endif
|
|
snprintf (fn, sizeof (fn),
|
|
"%s/%s",
|
|
uc->language,
|
|
uc->category);
|
|
#ifdef WINDOWS
|
|
(void) mkdir (fn);
|
|
#else
|
|
(void) mkdir (fn, S_IRWXU);
|
|
#endif
|
|
/* open file */
|
|
res = snprintf (fn, sizeof (fn),
|
|
"%s/%s/%s",
|
|
uc->language,
|
|
uc->category,
|
|
filename);
|
|
if ((0 >= res) || (sizeof (fn) <= (size_t) res))
|
|
{
|
|
uc->response = request_refused_response;
|
|
return MHD_NO;
|
|
}
|
|
for (i = 0; i < (size_t) res; i++)
|
|
if (! isprint ((unsigned char) fn[i]))
|
|
fn[i] = '_';
|
|
uc->fd = open (fn,
|
|
O_CREAT | O_EXCL
|
|
#ifdef O_LARGEFILE
|
|
| O_LARGEFILE
|
|
#endif
|
|
| O_WRONLY,
|
|
S_IRUSR | S_IWUSR);
|
|
if (-1 == uc->fd)
|
|
{
|
|
fprintf (stderr,
|
|
"Error opening file `%s' for upload: %s\n",
|
|
fn,
|
|
strerror (errno));
|
|
uc->response = request_refused_response;
|
|
return MHD_NO;
|
|
}
|
|
uc->filename = strdup (fn);
|
|
}
|
|
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->filename,
|
|
strerror (errno));
|
|
uc->response = internal_error_response;
|
|
(void) close (uc->fd);
|
|
uc->fd = -1;
|
|
if (NULL != uc->filename)
|
|
{
|
|
unlink (uc->filename);
|
|
free (uc->filename);
|
|
uc->filename = NULL;
|
|
}
|
|
return MHD_NO;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function called whenever a request was completed.
|
|
* Used to clean up 'struct UploadContext' objects.
|
|
*
|
|
* @param cls client-defined closure, NULL
|
|
* @param connection connection handle
|
|
* @param req_cls value as set by the last call to
|
|
* the MHD_AccessHandlerCallback, points to NULL if this was
|
|
* not an upload
|
|
* @param toe reason for request termination
|
|
*/
|
|
static void
|
|
response_completed_callback (void *cls,
|
|
struct MHD_Connection *connection,
|
|
void **req_cls,
|
|
enum MHD_RequestTerminationCode toe)
|
|
{
|
|
struct UploadContext *uc = *req_cls;
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) connection; /* Unused. Silent compiler warning. */
|
|
(void) toe; /* Unused. Silent compiler warning. */
|
|
|
|
if (NULL == uc)
|
|
return; /* this request wasn't an upload request */
|
|
if (NULL != uc->pp)
|
|
{
|
|
MHD_destroy_post_processor (uc->pp);
|
|
uc->pp = NULL;
|
|
}
|
|
if (-1 != uc->fd)
|
|
{
|
|
(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->filename);
|
|
}
|
|
}
|
|
if (NULL != uc->filename)
|
|
free (uc->filename);
|
|
free (uc);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the current directory listing.
|
|
*
|
|
* @param connection connection to return the directory for
|
|
* @return #MHD_YES on success, #MHD_NO on error
|
|
*/
|
|
static enum MHD_Result
|
|
return_directory_response (struct MHD_Connection *connection)
|
|
{
|
|
enum MHD_Result ret;
|
|
|
|
(void) pthread_mutex_lock (&mutex);
|
|
if (NULL == cached_directory_response)
|
|
ret = MHD_queue_response (connection,
|
|
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
|
internal_error_response);
|
|
else
|
|
ret = MHD_queue_response (connection,
|
|
MHD_HTTP_OK,
|
|
cached_directory_response);
|
|
(void) pthread_mutex_unlock (&mutex);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Main callback from MHD, used to generate the page.
|
|
*
|
|
* @param cls NULL
|
|
* @param connection connection handle
|
|
* @param url requested URL
|
|
* @param method GET, PUT, POST, etc.
|
|
* @param version HTTP version
|
|
* @param upload_data data from upload (PUT/POST)
|
|
* @param upload_data_size number of bytes in @a upload_data
|
|
* @param req_cls our context
|
|
* @return #MHD_YES on success, #MHD_NO to drop connection
|
|
*/
|
|
static enum MHD_Result
|
|
generate_page (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;
|
|
enum MHD_Result ret;
|
|
int fd;
|
|
struct stat buf;
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) version; /* Unused. Silent compiler warning. */
|
|
|
|
if (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;
|
|
|
|
if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) &&
|
|
(0 != strcmp (method, MHD_HTTP_METHOD_HEAD)) )
|
|
return MHD_NO; /* unexpected method (we're not polite...) */
|
|
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_queue_response (connection,
|
|
MHD_HTTP_NOT_FOUND,
|
|
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 (-1 != got)
|
|
mime = magic_buffer (magic, file_data, 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";
|
|
}
|
|
|
|
}
|
|
|
|
if (NULL == (response = MHD_create_response_from_fd ((size_t) buf.st_size,
|
|
fd)))
|
|
{
|
|
/* internal error (i.e. out of memory) */
|
|
(void) close (fd);
|
|
return MHD_NO;
|
|
}
|
|
|
|
/* add mime type if we had one */
|
|
if (NULL != mime)
|
|
(void) MHD_add_response_header (response,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
mime);
|
|
ret = MHD_queue_response (connection,
|
|
MHD_HTTP_OK,
|
|
response);
|
|
MHD_destroy_response (response);
|
|
return ret;
|
|
}
|
|
|
|
if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
|
|
{
|
|
/* upload! */
|
|
struct UploadContext *uc = *req_cls;
|
|
|
|
if (NULL == uc)
|
|
{
|
|
if (NULL == (uc = malloc (sizeof (struct UploadContext))))
|
|
return MHD_NO; /* out of memory, close connection */
|
|
memset (uc, 0, sizeof (struct UploadContext));
|
|
uc->fd = -1;
|
|
uc->connection = connection;
|
|
uc->pp = MHD_create_post_processor (connection,
|
|
64 * 1024 /* buffer size */,
|
|
&process_upload_data, uc);
|
|
if (NULL == uc->pp)
|
|
{
|
|
/* out of memory, close connection */
|
|
free (uc);
|
|
return MHD_NO;
|
|
}
|
|
*req_cls = uc;
|
|
return MHD_YES;
|
|
}
|
|
if (0 != *upload_data_size)
|
|
{
|
|
if (NULL == uc->response)
|
|
(void) MHD_post_process (uc->pp,
|
|
upload_data,
|
|
*upload_data_size);
|
|
*upload_data_size = 0;
|
|
return MHD_YES;
|
|
}
|
|
/* end of upload, finish it! */
|
|
MHD_destroy_post_processor (uc->pp);
|
|
uc->pp = NULL;
|
|
if (-1 != uc->fd)
|
|
{
|
|
close (uc->fd);
|
|
uc->fd = -1;
|
|
}
|
|
if (NULL != uc->response)
|
|
{
|
|
return MHD_queue_response (connection,
|
|
MHD_HTTP_FORBIDDEN,
|
|
uc->response);
|
|
}
|
|
else
|
|
{
|
|
update_directory ();
|
|
return return_directory_response (connection);
|
|
}
|
|
}
|
|
if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
|
|
(0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
|
|
{
|
|
return return_directory_response (connection);
|
|
}
|
|
|
|
/* unexpected request, refuse */
|
|
return MHD_queue_response (connection,
|
|
MHD_HTTP_FORBIDDEN,
|
|
request_refused_response);
|
|
}
|
|
|
|
|
|
#ifndef MINGW
|
|
/**
|
|
* 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;
|
|
}
|
|
#ifndef MINGW
|
|
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_create_response_from_buffer_static (strlen (FILE_NOT_FOUND_PAGE),
|
|
(const void *) FILE_NOT_FOUND_PAGE);
|
|
mark_as_html (file_not_found_response);
|
|
request_refused_response =
|
|
MHD_create_response_from_buffer_static (strlen (REQUEST_REFUSED_PAGE),
|
|
(const void *)
|
|
REQUEST_REFUSED_PAGE);
|
|
mark_as_html (request_refused_response);
|
|
internal_error_response =
|
|
MHD_create_response_from_buffer_static (strlen (INTERNAL_ERROR_PAGE),
|
|
(const void *) INTERNAL_ERROR_PAGE);
|
|
mark_as_html (internal_error_response);
|
|
update_directory ();
|
|
d = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD
|
|
| MHD_USE_ERROR_LOG,
|
|
(uint16_t) port,
|
|
NULL, NULL,
|
|
&generate_page, NULL,
|
|
MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256
|
|
* 1024),
|
|
#ifdef PRODUCTION
|
|
MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
|
|
#endif
|
|
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned
|
|
int) (120 /* seconds */),
|
|
MHD_OPTION_THREAD_POOL_SIZE, (unsigned
|
|
int) NUMBER_OF_THREADS,
|
|
MHD_OPTION_NOTIFY_COMPLETED,
|
|
&response_completed_callback, NULL,
|
|
MHD_OPTION_END);
|
|
if (NULL == d)
|
|
return 1;
|
|
fprintf (stderr, "HTTP server running. Press ENTER to stop the server.\n");
|
|
(void) getc (stdin);
|
|
MHD_stop_daemon (d);
|
|
MHD_destroy_response (file_not_found_response);
|
|
MHD_destroy_response (request_refused_response);
|
|
MHD_destroy_response (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 */
|