diff --git a/example/example.c b/example/example.c index 5e452e2..9f70bf6 100644 --- a/example/example.c +++ b/example/example.c @@ -29,19 +29,27 @@ #include "uhttpd.h" +static bool serve_file = false; +static const char *docroot = "."; +static const char *index_page = "index.html"; + static void on_request(struct uh_connection *conn) { - int body_len; - const char *body = conn->get_body(conn, &body_len); + if (!serve_file) { + int body_len; + const char *body = conn->get_body(conn, &body_len); - conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); - conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); - conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); - conn->chunk_printf(conn, "Path: %s\n", conn->get_path(conn)); - conn->chunk_printf(conn, "Query: %s\n", conn->get_query(conn)); - conn->chunk_printf(conn, "User-Agent: %s\n", conn->get_header(conn, "User-Agent")); - conn->chunk_printf(conn, "Body: %.*s\n", body_len, body); - conn->chunk_end(conn); + conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); + conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); + conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); + conn->chunk_printf(conn, "Path: %s\n", conn->get_path(conn)); + conn->chunk_printf(conn, "Query: %s\n", conn->get_query(conn)); + conn->chunk_printf(conn, "User-Agent: %s\n", conn->get_header(conn, "User-Agent")); + conn->chunk_printf(conn, "Body: %.*s\n", body_len, body); + conn->chunk_end(conn); + } else { + conn->serve_file(conn, docroot, index_page); + } } static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) @@ -58,6 +66,7 @@ static void usage(const char *prog) fprintf(stderr, "Usage: %s [option]\n" " -p port # Default port is 8080\n" " -s # SSl on\n" + " -f # Serve file\n" " -v # verbose\n", prog); exit(1); } @@ -72,7 +81,7 @@ int main(int argc, char **argv) int port = 8080; int opt; - while ((opt = getopt(argc, argv, "p:sv")) != -1) { + while ((opt = getopt(argc, argv, "p:sfv")) != -1) { switch (opt) { case 'p': port = atoi(optarg); @@ -80,6 +89,9 @@ int main(int argc, char **argv) case 's': ssl = true; break; + case 'f': + serve_file = true; + break; case 'v': verbose = true; break; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 434df5f..b98c8b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,7 +24,7 @@ if(HAVE_DLOPEN) add_definitions(-DHAVE_DLOPEN) endif() -set(SOURCE_FILES uhttpd.c log.c connection.c buffer/buffer.c http-parser/http_parser.c ssl.c) +set(SOURCE_FILES uhttpd.c log.c connection.c buffer/buffer.c http-parser/http_parser.c ssl.c file.c mimetypes.c) option(BUILD_SHARED_LIBS "Build shared library" ON) option(BUILD_STATIC_LIBS "Build static library" ON) diff --git a/src/connection.c b/src/connection.c index f939646..7142042 100644 --- a/src/connection.c +++ b/src/connection.c @@ -33,6 +33,7 @@ #include "connection.h" #include "uhttpd.h" #include "utils.h" +#include "file.h" #include "ssl.h" @@ -573,6 +574,7 @@ struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct conn->send_head = conn_send_head; conn->error = conn_error; conn->redirect = conn_redirect; + conn->serve_file = serve_file; conn->chunk_send = conn_chunk_send; conn->chunk_printf = conn_chunk_printf; diff --git a/src/connection.h b/src/connection.h index 04c51be..3dfc10d 100644 --- a/src/connection.h +++ b/src/connection.h @@ -97,6 +97,7 @@ struct uh_connection { void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); void (*error)(struct uh_connection *conn, int code, const char *reason); void (*redirect)(struct uh_connection *conn, int code, const char *location, ...); + void (*serve_file)(struct uh_connection *conn, const char *docroot, const char *index_page); void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); void (*chunk_printf)(struct uh_connection *conn, const char *format, ...); void (*chunk_vprintf)(struct uh_connection *conn, const char *format, va_list arg); diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..6530d96 --- /dev/null +++ b/src/file.c @@ -0,0 +1,194 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define _DEFAULT_SOURCE +#define _XOPEN_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "connection.h" +#include "mimetypes.h" + + +static const char *file_mktag(struct stat *s, char *buf, int len) +{ + snprintf(buf, len, "\"%" PRIx64 "-%" PRIx64 "-%" PRIx64 "\"", + s->st_ino, s->st_size, (uint64_t)s->st_mtime); + + return buf; +} + +static char *unix2date(time_t ts, char *buf, int len) +{ + struct tm *t = gmtime(&ts); + + strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT", t); + + return buf; +} + +static time_t date2unix(const char *date) +{ + struct tm t; + + memset(&t, 0, sizeof(t)); + + if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL) + return timegm(&t); + + return 0; +} + +static void file_response_ok_hdrs(struct uh_connection *conn, struct stat *s) +{ + char buf[128]; + + if (s) { + conn->printf(conn, "ETag: %s\r\n", file_mktag(s, buf, sizeof(buf))); + conn->printf(conn, "Last-Modified: %s\r\n", unix2date(s->st_mtime, buf, sizeof(buf))); + + } + conn->printf(conn, "Date: %s\r\n", unix2date(time(NULL), buf, sizeof(buf))); +} + +static void file_response_304(struct uh_connection *conn, struct stat *s) +{ + conn->send_status_line(conn, HTTP_STATUS_NOT_MODIFIED, NULL); + + file_response_ok_hdrs(conn, s); +} + +static bool file_if_modified_since(struct uh_connection *conn, struct stat *s) +{ + const char *hdr = conn->get_header(conn, "If-Modified-Since"); + if (!hdr[0]) + return true; + + if (date2unix(hdr) >= s->st_mtime) { + file_response_304(conn, s); + return false; + } + + return true; +} + +static bool file_if_range(struct uh_connection *conn, struct stat *s) +{ + const char *hdr = conn->get_header(conn, "If-Range"); + if (hdr[0]) { + conn->error(conn, HTTP_STATUS_PRECONDITION_FAILED, NULL); + return false; + } + + return true; +} + +static bool file_if_unmodified_since(struct uh_connection *conn, struct stat *s) +{ + const char *hdr = conn->get_header(conn, "If-Modified-Since"); + if (hdr[0] && date2unix(hdr) <= s->st_mtime) { + conn->error(conn, HTTP_STATUS_PRECONDITION_FAILED, NULL); + return false; + } + + return true; +} + +void serve_file(struct uh_connection *conn, const char *docroot, const char *index_page) +{ + const char *path = conn->get_path(conn); + static char fullpath[512]; + struct stat st; + + if (!docroot || !docroot[0]) + docroot = "."; + + if (!index_page || !index_page[0]) + index_page = "index.html"; + + strcpy(fullpath, docroot); + + if (!strcmp(path, "/")) { + strcat(fullpath, "/"); + path = index_page; + } + + strcat(fullpath, path); + + if (stat(fullpath, &st) < 0) { + int code; + + switch (errno) { + case EACCES: + code = HTTP_STATUS_FORBIDDEN; + break; + case ENOENT: + code = HTTP_STATUS_NOT_FOUND; + break; + default: + code = HTTP_STATUS_INTERNAL_SERVER_ERROR; + }; + + conn->error(conn, code, NULL); + return; + } + + if (!S_ISLNK(st.st_mode) && !S_ISREG(st.st_mode)) { + conn->error(conn, 403, NULL); + return; + } + + switch (conn->get_method(conn)) { + case HTTP_GET: + case HTTP_HEAD: + break; + default: + conn->error(conn, HTTP_STATUS_METHOD_NOT_ALLOWED, NULL); + return; + } + + if (!file_if_modified_since(conn, &st) || + !file_if_range(conn, &st) || + !file_if_unmodified_since(conn, &st)) { + conn->printf(conn, "\r\n"); + return; + } + + conn->send_status_line(conn, HTTP_STATUS_OK, NULL); + file_response_ok_hdrs(conn, &st); + + conn->printf(conn, "Content-Type: %s\r\n", file_mime_lookup(path)); + conn->printf(conn, "Content-Length: %" PRIu64 "\r\n\r\n", st.st_size); + + if (conn->get_method(conn) == HTTP_HEAD) + return; + + conn->send_file(conn, fullpath); +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..ddb47a2 --- /dev/null +++ b/src/file.h @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _UH_FILE_H +#define _UH_FILE_H + +#include "connection.h" + +void serve_file(struct uh_connection *conn, const char *docroot, const char *index_page); + +#endif diff --git a/src/mimetypes.c b/src/mimetypes.c new file mode 100644 index 0000000..ec16173 --- /dev/null +++ b/src/mimetypes.c @@ -0,0 +1,116 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include "mimetypes.h" + +static const struct mimetype uh_mime_types[] = { + { "txt", "text/plain" }, + { "log", "text/plain" }, + { "js", "text/javascript" }, + { "css", "text/css" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "diff", "text/x-patch" }, + { "patch", "text/x-patch" }, + { "c", "text/x-csrc" }, + { "h", "text/x-chdr" }, + { "o", "text/x-object" }, + { "ko", "text/x-object" }, + + { "bmp", "image/bmp" }, + { "gif", "image/gif" }, + { "png", "image/png" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "svg", "image/svg+xml" }, + + { "json", "application/json" }, + { "jsonp", "application/javascript" }, + { "zip", "application/zip" }, + { "pdf", "application/pdf" }, + { "xml", "application/xml" }, + { "xsl", "application/xml" }, + { "doc", "application/msword" }, + { "ppt", "application/vnd.ms-powerpoint" }, + { "xls", "application/vnd.ms-excel" }, + { "odt", "application/vnd.oasis.opendocument.text" }, + { "odp", "application/vnd.oasis.opendocument.presentation" }, + { "pl", "application/x-perl" }, + { "sh", "application/x-shellscript" }, + { "php", "application/x-php" }, + { "deb", "application/x-deb" }, + { "iso", "application/x-cd-image" }, + { "tar.gz", "application/x-compressed-tar" }, + { "tgz", "application/x-compressed-tar" }, + { "gz", "application/x-gzip" }, + { "tar.bz2", "application/x-bzip-compressed-tar" }, + { "tbz", "application/x-bzip-compressed-tar" }, + { "bz2", "application/x-bzip" }, + { "tar", "application/x-tar" }, + { "rar", "application/x-rar-compressed" }, + + { "mp3", "audio/mpeg" }, + { "ogg", "audio/x-vorbis+ogg" }, + { "wav", "audio/x-wav" }, + + { "mpg", "video/mpeg" }, + { "mpeg", "video/mpeg" }, + { "avi", "video/x-msvideo" }, + + { "README", "text/plain" }, + { "log", "text/plain" }, + { "cfg", "text/plain" }, + { "conf", "text/plain" }, + + { "pac", "application/x-ns-proxy-autoconfig" }, + { "wpad.dat", "application/x-ns-proxy-autoconfig" }, + { "appcache", "text/cache-manifest" }, + { "manifest", "text/cache-manifest" }, + + { NULL, NULL } +}; + +const char *file_mime_lookup(const char *path) +{ + const struct mimetype *m = &uh_mime_types[0]; + const char *e; + + while (m->extn) { + e = &path[strlen(path)-1]; + + while (e >= path) { + if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn)) + return m->mime; + + e--; + } + + m++; + } + + return "application/octet-stream"; +} diff --git a/src/mimetypes.h b/src/mimetypes.h new file mode 100644 index 0000000..7efbc4f --- /dev/null +++ b/src/mimetypes.h @@ -0,0 +1,35 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _UH_MIMETYPES_H +#define _UH_MIMETYPES_H + +struct mimetype { + const char *extn; + const char *mime; +}; + +const char *file_mime_lookup(const char *path); + +#endif