From c4b294c890c28004d33f650b7af5064c4099f608 Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Wed, 27 Apr 2016 03:51:42 +0200 Subject: blogc-runserver: import external tool to blogc repository still in the effort to reduce maintenance work, I'm importing blogc-runserver tool to the main blogc repository. the tool is build by default, if needed headers and libraries are found. --- src/blogc-runserver.c | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 src/blogc-runserver.c (limited to 'src') diff --git a/src/blogc-runserver.c b/src/blogc-runserver.c new file mode 100644 index 0000000..81bd5f9 --- /dev/null +++ b/src/blogc-runserver.c @@ -0,0 +1,389 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + + +/** + * this mapping is used to declare "supported" file types, that are forced over + * whatever detected by libmagic, but we will still use the charset provided by + * libmagic anyway. it also helps detecting index files when the client asks + * for a directory. + */ +static const struct content_type_map { + const char *mimetype; + const char *extension; + const char *index; +} content_types[] = { + {"text/html", "html", "index.html"}, + {"text/html", "htm", "index.htm"}, + {"text/xml", "xml", "index.xml"}, + {"text/plain", "txt", "index.txt"}, + {"text/css", "css", NULL}, + {"application/javascript", "js", NULL}, + {NULL, NULL, NULL} +}; + + +static magic_t magic_all = NULL; +static magic_t magic_charset = NULL; + + +static const char* +get_extension(const char *filename) +{ + const char *ext = NULL; + unsigned int i; + for (i = strlen(filename); i > 0; i--) { + if (filename[i] == '.') { + ext = filename + i + 1; + break; + } + } + if (i == 0) + return NULL; + return ext; +} + + +static char* +guess_content_type(const char *filename, int fd) +{ + int newfd; + + // try "supported" types first, and just use libmagic for charset + const char *extension = get_extension(filename); + if (extension == NULL) + goto libmagic; + const char *supported = NULL; + for (unsigned int i = 0; content_types[i].extension != NULL; i++) + if (0 == strcmp(content_types[i].extension, extension)) + supported = content_types[i].mimetype; + if (supported != NULL) { + newfd = dup(fd); + if (-1 != newfd) { + const char* charset = magic_descriptor(magic_charset, newfd); + close(newfd); + if (charset != NULL) + return sb_strdup_printf("%s; charset=%s", supported, charset); + } + return sb_strdup(supported); + } + +libmagic: + + // fallback to use libmagic for everything + newfd = dup(fd); + if (-1 != newfd) { + const char* content_type = magic_descriptor(magic_all, newfd); + close(newfd); + if (content_type != NULL) + return sb_strdup(content_type); + } + return sb_strdup("application/octet-stream"); +} + + +static void +handler(struct evhttp_request *request, void *ptr) +{ + const char *root = ptr; + const char *uri = evhttp_request_get_uri(request); + + struct evhttp_uri *decoded_uri = evhttp_uri_parse(uri); + if (decoded_uri == NULL) { + evhttp_send_error(request, 400, "Bad request"); + return; + } + + const char *path = evhttp_uri_get_path(decoded_uri); + if (path == NULL) + path = "/"; + + char *decoded_path = evhttp_uridecode(path, 0, NULL); + if (decoded_path == NULL) { + evhttp_send_error(request, 400, "Bad request"); + goto point1; + } + + char *abs_path = sb_strdup_printf("%s/%s", root, decoded_path); + char *real_path = realpath(abs_path, NULL); + free(abs_path); + + if (real_path == NULL) { + evhttp_send_error(request, 404, "Not found"); + goto point2; + } + + char *real_root = realpath(root, NULL); + if (real_root == NULL) { + evhttp_send_error(request, 500, "Internal server error"); + goto point3; + } + + if (0 != strncmp(real_root, real_path, strlen(real_root))) { + evhttp_send_error(request, 404, "Not found"); + goto point4; + } + + struct stat st; + if (0 > stat(real_path, &st)) { + evhttp_send_error(request, 404, "Not found"); + goto point4; + } + + bool add_slash = false; + + if (S_ISDIR(st.st_mode)) { + char *found = NULL; + + for (unsigned int i = 0; content_types[i].mimetype != NULL; i++) { + if (content_types[i].index == NULL) + continue; + char *f = sb_strdup_printf("%s/%s", real_path, + content_types[i].index); + if (0 == access(f, F_OK)) { + found = sb_strdup(f); + break; + } + free(f); + } + + if (found == NULL) { + evhttp_send_error(request, 403, "Forbidden"); + goto point4; + } + + size_t path_len = strlen(path); + if (path_len > 0 && path[path_len - 1] != '/') + add_slash = true; + + free(real_path); + real_path = found; + } + + int fd; + if ((fd = open(real_path, O_RDONLY)) < 0) { + evhttp_send_error(request, 500, "Internal server error"); + goto point4; + } + + char *type = guess_content_type(real_path, fd); + + if (fstat(fd, &st) < 0) { + evhttp_send_error(request, 500, "Internal server error"); + goto point5; + } + + struct evkeyvalq *headers = evhttp_request_get_output_headers(request); + + if (add_slash) { + char *tmp = sb_strdup_printf("%s/", path); + evhttp_add_header(headers, "Location", tmp); + free(tmp); + // production webservers usually returns 301 in such cases, but 302 is + // better for development/testing. + evhttp_send_reply(request, 302, "Found", NULL); + goto point5; + } + + evhttp_add_header(headers, "Content-Type", type); + char *content_length = sb_strdup_printf("%zu", st.st_size); + evhttp_add_header(headers, "Content-Length", content_length); + free(content_length); + + struct evbuffer *evb = evbuffer_new(); + evbuffer_add_file(evb, fd, 0, st.st_size); + evhttp_send_reply(request, 200, "OK", evb); + +point5: + free(type); +point4: + free(real_root); +point3: + free(real_path); +point2: + free(decoded_path); +point1: + evhttp_uri_free(decoded_uri); +} + + +static int +runserver(const char *address, unsigned short port, const char *root) +{ + struct event_base *base = event_base_new(); + if (base == NULL) { + fprintf(stderr, "error: failed to initialize event base\n"); + return 1; + } + + struct evhttp *http = evhttp_new(base); + if (http == NULL) { + fprintf(stderr, "error: failed to initialize HTTP server\n"); + return 1; + } + + evhttp_set_gencb(http, handler, (char*) root); + + evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_HEAD); + + if (0 != evhttp_bind_socket(http, address, port)) { + fprintf(stderr, "error: failed to bind socket to %s:%d\n", address, + port); + return 1; + } + + fprintf(stderr, " * Running on http://%s:%d/\n", address, port); + + event_base_dispatch(base); + + return 0; +} + + +static void +print_help(void) +{ + printf( + "usage:\n" + " blogc-runserver [-h] [-v] [-t HOST] [-p PORT] DOCROOT\n" + " - A simple HTTP server to test blogc websites.\n" + "\n" + "positional arguments:\n" + " DOCROOT document root directory\n" + "\n" + "optional arguments:\n" + " -h show this help message and exit\n" + " -v show version and exit\n" + " -t HOST set server listen address (default: 127.0.0.1)\n" + " -p PORT set server listen port (default: 8080)\n"); +} + + +static void +print_usage(void) +{ + printf("usage: blogc-runserver [-h] [-v] [-t HOST] [-p PORT] DOCROOT\n"); +} + + +int +main(int argc, char **argv) +{ + signal(SIGPIPE, SIG_IGN); + + int rv = 0; + char *host = NULL; + char *docroot = NULL; + unsigned short port = 8080; + + unsigned int args = 0; + + for (unsigned int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'h': + print_help(); + goto cleanup; + case 'v': + printf("%s\n", PACKAGE_STRING); + goto cleanup; + case 't': + if (argv[i][2] != '\0') + host = sb_strdup(argv[i] + 2); + else + host = sb_strdup(argv[++i]); + break; + case 'p': + if (argv[i][2] != '\0') + port = strtoul(argv[i] + 2, NULL, 10); + else + port = strtoul(argv[++i], NULL, 10); + break; + default: + print_usage(); + fprintf(stderr, "blogc-runserver: error: invalid " + "argument: -%c\n", argv[i][1]); + rv = 2; + goto cleanup; + } + } + else { + if (args > 0) { + print_usage(); + fprintf(stderr, "blogc-runserver: error: only one positional " + "argument allowed\n"); + rv = 2; + goto cleanup; + } + args++; + docroot = sb_strdup(argv[i]); + } + } + + if (docroot == NULL) { + print_usage(); + fprintf(stderr, "blogc-runserver: error: document root directory " + "required\n"); + rv = 2; + goto cleanup; + } + + if (host == NULL) + host = sb_strdup("127.0.0.1"); + + magic_all = magic_open(MAGIC_MIME); + magic_charset = magic_open(MAGIC_MIME_ENCODING); + if (magic_all == NULL || magic_charset == NULL) { + fprintf(stderr, "error: failed to initialize libmagic\n"); + rv = 1; + goto cleanup; + } + + if ((0 != magic_load(magic_all, NULL)) || + (0 != magic_load(magic_charset, NULL))) + { + fprintf(stderr, "error: failed to load libmagic data\n"); + magic_close(magic_all); + magic_close(magic_charset); + rv = 1; + goto cleanup; + } + + rv = runserver(host, port, docroot); + + magic_close(magic_all); + magic_close(magic_charset); + +cleanup: + free(host); + free(docroot); + + return rv; +} -- cgit v1.2.3-18-g5258