libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 4b0588d78b33db21c483d52ade209bb0bf27a6dc
parent 7b8fb41d1e766ed2285f3ad57f9d21f174471a53
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 31 Mar 2013 20:22:02 +0000

add mime types to demo

Diffstat:
Mconfigure.ac | 8++++++++
Msrc/examples/Makefile.am | 8++++++--
Msrc/examples/demo.c | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 246 insertions(+), 71 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -255,6 +255,14 @@ then AC_DEFINE_UNQUOTED([MHD_REQ_CURL_GNUTLS_VERSION], "$MHD_REQ_CURL_GNUTLS_VERSION", [gnuTLS lib version - used in conjunction with cURL]) AC_DEFINE_UNQUOTED([MHD_REQ_CURL_NSS_VERSION], "$MHD_REQ_CURL_NSS_VERSION", [NSS lib version - used in conjunction with cURL]) fi + +AC_MSG_CHECKING(for magic_open -lmagic) +AC_CHECK_LIB(magic, magic_open, + [AC_CHECK_HEADERS([magic.h], + AM_CONDITIONAL(HAVE_MAGIC, true), + AM_CONDITIONAL(HAVE_MAGIC, false))], + AM_CONDITIONAL(HAVE_MAGIC, false)) + LIBS=$SAVE_LIBS AM_CONDITIONAL(HAVE_CURL, test x$curl = x1) diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am @@ -29,8 +29,11 @@ noinst_PROGRAMS += https_fileserver_example endif if HAVE_POSTPROCESSOR noinst_PROGRAMS += \ - demo \ post_example +if HAVE_MAGIC +noinst_PROGRAMS += \ + demo +endif endif if ENABLE_DAUTH @@ -55,7 +58,8 @@ minimal_example_LDADD = \ demo_SOURCES = \ demo.c demo_LDADD = \ - $(top_builddir)/src/daemon/libmicrohttpd.la + $(top_builddir)/src/daemon/libmicrohttpd.la \ + -lmagic dual_stack_example_SOURCES = \ dual_stack_example.c diff --git a/src/examples/demo.c b/src/examples/demo.c @@ -23,17 +23,23 @@ * @author Christian Grothoff * * TODO: - * - even with LARGE memory pool & buffers, uploads proceed at 5000 bytes/call (ugh) * - should have a slightly more ambitious upload form & file listing (structure!) - * - may want to add MIME-types to replies */ #include "platform.h" #include <microhttpd.h> #include <unistd.h> #include <pthread.h> #include <sys/types.h> +#include <sys/stat.h> #include <dirent.h> +#include <magic.h> +/** + * 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) /** * Page returned for file-not-found. @@ -48,6 +54,12 @@ /** + * 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"\ @@ -63,6 +75,7 @@ #define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>" + /** * Response returned if the requested file does not exist (or is not accessible). */ @@ -79,10 +92,34 @@ static struct MHD_Response *internal_error_response; 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; +/** + * Global handle to MAGIC data. + */ +static magic_t magic; + + +/** + * Mark the given response as HTML for the brower. + * + * @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 @@ -102,68 +139,119 @@ update_cached_response (struct MHD_Response *response) /** - * Re-scan our local directory and re-build the index. + * Context keeping the data for the response we're building. */ -static void -update_directory () +struct ResponseDataContext { - static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */ - DIR *dir; - struct dirent *de; - struct MHD_Response *response; + /** + * 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; - dir = opendir ("."); - if (NULL == dir) - goto err; - buf_len = initial_allocation; - buf = malloc (buf_len); - if (NULL == buf) - { - closedir (dir); - goto err; - } - off = snprintf (buf, buf_len, - "%s", - INDEX_PAGE_HEADER); +}; + + +/** + * 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 int +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))) { if ('.' == de->d_name[0]) continue; - if (off + 1024 > buf_len) + if (sizeof (fullname) <= + 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 * buf_len + 1024) < buf_len) + if ( (2 * rdc->buf_len + 1024) < rdc->buf_len) break; /* more than SIZE_T _index_ size? Too big for us */ - buf_len = 2 * buf_len + 1024; - if (NULL == (r = realloc (buf, buf_len))) + rdc->buf_len = 2 * rdc->buf_len + 1024; + if (NULL == (r = realloc (rdc->buf, rdc->buf_len))) break; /* out of memory */ - buf = r; + rdc->buf = r; } - off += snprintf (&buf[off], buf_len - off, - "<li><a href=\"/%s\">%s</a></li>\n", - de->d_name, - de->d_name); + rdc->off += snprintf (&rdc->buf[rdc->off], + rdc->buf_len - rdc->off, + "<li><a href=\"/%s\">%s</a></li>\n", + de->d_name, + de->d_name); + } + (void) closedir (dir); + return MHD_YES; +} + + +/** + * Re-scan our local directory and re-build the index. + */ +static void +update_directory () +{ + static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */ + struct MHD_Response *response; + struct ResponseDataContext rdc; + + rdc.buf_len = initial_allocation; + if (NULL == (rdc.buf = malloc (rdc.buf_len))) + { + update_cached_response (NULL); + return; + } + rdc.off = snprintf (rdc.buf, rdc.buf_len, + "%s", + INDEX_PAGE_HEADER); + + if (MHD_NO == list_directory (&rdc, ".")) + { + 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. */ - off += snprintf (&buf[off], buf_len - off, - "%s", - INDEX_PAGE_FOOTER); - closedir (dir); - initial_allocation = buf_len; /* remember for next time */ - response = MHD_create_response_from_buffer (off, - buf, + rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, + "%s", + INDEX_PAGE_FOOTER); + initial_allocation = rdc.buf_len; /* remember for next time */ + response = MHD_create_response_from_buffer (rdc.off, + rdc.buf, MHD_RESPMEM_MUST_FREE); + mark_as_html (response); update_cached_response (response); - return; - err: - /* failed to list directory, use error page */ - update_cached_response (NULL); } @@ -178,6 +266,11 @@ struct UploadContext int fd; /** + * Name of the file on disk (used to remove on errors). + */ + char *filename; + + /** * Post processor we're using to process the upload. */ struct MHD_PostProcessor *pp; @@ -186,6 +279,11 @@ struct UploadContext * 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; }; @@ -228,32 +326,48 @@ process_upload_data (void *cls, } if (-1 == uc->fd) { + if ( (NULL != strstr (filename, "..")) || + (NULL != strchr (filename, '/')) || + (NULL != strchr (filename, '\\')) ) + { + uc->response = request_refused_response; + return MHD_NO; + } uc->fd = open (filename, O_CREAT | O_EXCL #if O_LARGEFILE | O_LARGEFILE #endif -#if O_NONBLOCK - | O_NONBLOCK -#endif | O_WRONLY, S_IRUSR | S_IWUSR); if (-1 == uc->fd) { - // FIXME: generate error page NICELY - fprintf (stderr, "Error opening file to write!\n"); + fprintf (stderr, + "Error opening file `%s' for upload: %s\n", + filename, + strerror (errno)); + uc->response = request_refused_response; return MHD_NO; } } - else if (0 == size) - sleep (1); - - + uc->filename = strdup (filename); if ( (0 != size) && (size != write (uc->fd, data, size)) ) { - // FIXME: generate error page NICELY - fprintf (stderr, "Error writing to disk!\n"); + /* write failed; likely: disk full */ + fprintf (stderr, + "Error writing to file `%s': %s\n", + filename, + strerror (errno)); + uc->response = internal_error_response; + 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; @@ -288,10 +402,17 @@ response_completed_callback (void *cls, } if (-1 != uc->fd) { - close (uc->fd); - fprintf (stderr, "Possible upload failure, need to remove file?!\n"); - /* FIXME: unlink here on error!? */ + (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); } @@ -303,7 +424,7 @@ response_completed_callback (void *cls, * @return MHD_YES on success, MHD_NO on error */ static int -list_directory (struct MHD_Connection *connection) +return_directory_response (struct MHD_Connection *connection) { int ret; @@ -321,7 +442,6 @@ list_directory (struct MHD_Connection *connection) } - /** * Main callback from MHD, used to generate the page. * @@ -352,6 +472,10 @@ generate_page (void *cls, if (0 != strcmp (url, "/")) { /* should be file download */ + char file_data[MAGIC_HEADER_SIZE]; + ssize_t got; + const char *mime; + if (0 != strcmp (method, MHD_HTTP_METHOD_GET)) return MHD_NO; /* unexpected method (we're not polite...) */ if ( (0 == stat (&url[1], &buf)) && @@ -364,6 +488,14 @@ generate_page (void *cls, return MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, file_not_found_response); + /* read beginning of the file to determine mime type */ + got = read (fd, file_data, sizeof (file_data)); + if (-1 != got) + mime = magic_buffer (magic, file_data, got); + else + mime = NULL; + (void) lseek (fd, 0, SEEK_SET); + if (NULL == (response = MHD_create_response_from_fd (buf.st_size, fd))) { @@ -371,6 +503,12 @@ generate_page (void *cls, (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); @@ -387,6 +525,8 @@ generate_page (void *cls, { if (NULL == (uc = malloc (sizeof (struct UploadContext)))) return MHD_NO; /* out of memory, close connection */ + uc->response = NULL; + uc->filename = NULL; uc->fd = -1; uc->connection = connection; uc->pp = MHD_create_post_processor (connection, @@ -400,14 +540,15 @@ generate_page (void *cls, } *ptr = uc; return MHD_YES; - } + } if (0 != *upload_data_size) { - ret = MHD_post_process (uc->pp, - upload_data, - *upload_data_size); + if (NULL != uc->response) + (void) MHD_post_process (uc->pp, + upload_data, + *upload_data_size); *upload_data_size = 0; - return ret; + return MHD_YES; } /* end of upload, finish it! */ MHD_destroy_post_processor (uc->pp); @@ -417,22 +558,32 @@ generate_page (void *cls, close (uc->fd); uc->fd = -1; } - update_directory (); - return list_directory (connection); + 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)) - return list_directory (connection); + return return_directory_response (connection); /* unexpected request, refuse */ - fprintf (stderr, "Unexpected request, refusing\n"); - return MHD_NO; + return MHD_queue_response (connection, + MHD_HTTP_FORBIDDEN, + request_refused_response); } /** * Entry point to demo. Note: this HTTP server will make all * files in the current directory and its subdirectories available - * to anyone. + * 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 @@ -452,13 +603,22 @@ main (int argc, char *const *argv) "%s PORT\n", argv[0]); return 1; } + magic = magic_open (MAGIC_MIME_TYPE); + (void) magic_load (magic, NULL); + (void) pthread_mutex_init (&mutex, NULL); file_not_found_response = MHD_create_response_from_buffer (strlen (FILE_NOT_FOUND_PAGE), (void *) FILE_NOT_FOUND_PAGE, MHD_RESPMEM_PERSISTENT); + mark_as_html (file_not_found_response); + request_refused_response = MHD_create_response_from_buffer (strlen (REQUEST_REFUSED_PAGE), + (void *) REQUEST_REFUSED_PAGE, + MHD_RESPMEM_PERSISTENT); + mark_as_html (request_refused_response); internal_error_response = MHD_create_response_from_buffer (strlen (INTERNAL_ERROR_PAGE), (void *) INTERNAL_ERROR_PAGE, MHD_RESPMEM_PERSISTENT); + mark_as_html (internal_error_response); update_directory (); d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, port, @@ -470,12 +630,15 @@ main (int argc, char *const *argv) 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); + magic_close (magic); return 0; }