From 6ac53d4c783340ae9139c7f4dcfe9bfddace5892 Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Sun, 25 Sep 2016 02:57:21 +0200 Subject: runserver: reimplemented http server without libevent yeah, this patch implements a "complete" http server for static files. It is not the best code possible, and would be easily DDoS'able if used in production, as it spawns a thread for each request, without limiting. I'm sickish and this is the best code I can deliver now. At least it works! ;) --- src/blogc-runserver/httpd-utils.c | 100 ++++++++++++ src/blogc-runserver/httpd-utils.h | 19 +++ src/blogc-runserver/httpd.c | 254 ++++++++++++++++++++++++++++++ src/blogc-runserver/httpd.h | 14 ++ src/blogc-runserver/main.c | 316 +------------------------------------- src/blogc-runserver/mime.c | 168 ++++++++++++++++++++ src/blogc-runserver/mime.h | 15 ++ src/blogc/loader.c | 4 +- src/common/file.c | 6 +- src/common/file.h | 3 +- 10 files changed, 579 insertions(+), 320 deletions(-) create mode 100644 src/blogc-runserver/httpd-utils.c create mode 100644 src/blogc-runserver/httpd-utils.h create mode 100644 src/blogc-runserver/httpd.c create mode 100644 src/blogc-runserver/httpd.h create mode 100644 src/blogc-runserver/mime.c create mode 100644 src/blogc-runserver/mime.h (limited to 'src') diff --git a/src/blogc-runserver/httpd-utils.c b/src/blogc-runserver/httpd-utils.c new file mode 100644 index 0000000..e935668 --- /dev/null +++ b/src/blogc-runserver/httpd-utils.c @@ -0,0 +1,100 @@ +/* + * 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 "../common/utils.h" +#include "httpd-utils.h" + + +char* +br_readline(int socket) +{ + bc_string_t *rv = bc_string_new(); + char buffer[READLINE_BUFFER_SIZE]; + ssize_t len; + + while ((len = read(socket, buffer, READLINE_BUFFER_SIZE)) != -1) { + for (ssize_t i = 0; i < len; i++) { + if (buffer[i] == '\r' || buffer[i] == '\n') + goto end; + bc_string_append_c(rv, buffer[i]); + } + } + +end: + return bc_string_free(rv, false); +} + + +int +br_hextoi(const char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + + +char* +br_urldecode(const char *str) +{ + bc_string_t *rv = bc_string_new(); + + for (size_t i = 0; i < strlen(str); i++) { + switch (str[i]) { + case '%': + if (i + 2 < strlen(str)) { + int p1 = br_hextoi(str[i + 1]) * 16; + int p2 = br_hextoi(str[i + 2]); + if (p1 >= 0 && p2 >= 0) { + bc_string_append_c(rv, p1 + p2); + i += 2; + continue; + } + } + bc_string_append_c(rv, '%'); + break; + case '+': + bc_string_append_c(rv, ' '); + break; + default: + bc_string_append_c(rv, str[i]); + } + } + + return bc_string_free(rv, false); +} + + +const char* +br_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 ((filename[i] == '/') || (filename[i] == '\\')) + return NULL; + } + if (i == 0) + return NULL; + return ext; +} diff --git a/src/blogc-runserver/httpd-utils.h b/src/blogc-runserver/httpd-utils.h new file mode 100644 index 0000000..b9af852 --- /dev/null +++ b/src/blogc-runserver/httpd-utils.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#ifndef _HTTPD_UTILS_H +#define _HTTPD_UTILS_H + +#define READLINE_BUFFER_SIZE 1024 + +char* br_readline(int socket); +int br_hextoi(const char c); +char* br_urldecode(const char *str); +const char* br_get_extension(const char *filename); + +#endif /* _HTTPD_UTILS_H */ diff --git a/src/blogc-runserver/httpd.c b/src/blogc-runserver/httpd.c new file mode 100644 index 0000000..c659580 --- /dev/null +++ b/src/blogc-runserver/httpd.c @@ -0,0 +1,254 @@ +/* + * 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 "../common/error.h" +#include "../common/file.h" +#include "../common/utils.h" +#include "mime.h" +#include "httpd-utils.h" + +#define LISTEN_BACKLOG 100 + + +static void +error(int socket, int status_code, const char *error) +{ + char *str = bc_strdup_printf( + "HTTP/1.0 %d %s\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "

%s

\n", status_code, error, error); + write(socket, str, strlen(str)); + free(str); +} + + +typedef struct { + int socket; + const char *docroot; +} request_data_t; + + +static void* +handle_request(void *arg) +{ + request_data_t *req = arg; + int client_socket = req->socket; + const char *docroot = req->docroot; + free(arg); + + char *conn_line = br_readline(client_socket); + char **pieces = bc_str_split(conn_line, ' ', 3); + if (bc_strv_length(pieces) != 3) { + error(client_socket, 400, "Bad Request"); + goto point1; + } + + if (strcmp(pieces[0], "GET") != 0) { + error(client_socket, 405, "Method Not Allowed"); + goto point1; + } + + char **pieces2 = bc_str_split(pieces[1], '?', 2); + char *path = br_urldecode(pieces2[0]); + bc_strv_free(pieces2); + + if (path == NULL) { + error(client_socket, 400, "Bad Request"); + goto point1; + } + + char *abs_path = bc_strdup_printf("%s/%s", docroot, path); + char *real_path = realpath(abs_path, NULL); + free(abs_path); + + if (real_path == NULL) { + error(client_socket, 404, "Not Found"); + goto point1; + } + + char *real_root = realpath(docroot, NULL); + if (real_root == NULL) { + error(client_socket, 500, "Internal Server Error"); + goto point2; + } + + if (0 != strncmp(real_root, real_path, strlen(real_root))) { + error(client_socket, 404, "Not Found"); + goto point3; + } + + struct stat st; + if (0 > stat(real_path, &st)) { + error(client_socket, 404, "Not Found"); + goto point3; + } + + bool add_slash = false; + + if (S_ISDIR(st.st_mode)) { + char *found = br_mime_guess_index(real_path); + + if (found == NULL) { + error(client_socket, 403, "Forbidden"); + goto point3; + } + + size_t path_len = strlen(path); + if (path_len > 0 && path[path_len - 1] != '/') + add_slash = true; + + free(real_path); + real_path = found; + } + + if (0 != access(real_path, F_OK)) { + error(client_socket, 500, "Internal Server Error"); + goto point3; + } + + if (add_slash) { + // production webservers usually returns 301 in such cases, but 302 is + // better for development/testing. + char *tmp = bc_strdup_printf( + "HTTP/1.0 302 Found\r\n" + "Location: %s/\r\n" + "Connection: close\r\n" + "\r\n", path); + write(client_socket, tmp, strlen(tmp)); + free(tmp); + goto point3; + } + + size_t len; + bc_error_t *err = NULL; + char* contents = bc_file_get_contents(real_path, false, &len, &err); + if (err != NULL) { + error(client_socket, 500, "Internal Server Error"); + bc_error_free(err); + goto point3; + } + + char *out = bc_strdup_printf( + "HTTP/1.0 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "\r\n", br_mime_guess_content_type(real_path), len); + write(client_socket, out, strlen(out)); + free(out); + + write(client_socket, contents, len); + +point3: + free(real_root); +point2: + free(real_path); +point1: + bc_strv_free(pieces); + close(client_socket); + return NULL; +} + + +int +br_httpd_run(const char *host, unsigned short port, const char *docroot) +{ + int server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket == -1) { + fprintf(stderr, "Failed to open server socket: %s\n", strerror(errno)); + return 1; + } + + int rv = 0; + + int value = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) < 0) { + fprintf(stderr, "Failed to set socket option: %s\n", strerror(errno)); + rv = 1; + goto cleanup; + } + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(struct sockaddr_in)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + if ((server_addr.sin_addr.s_addr = inet_addr(host)) == -1) { + fprintf(stderr, "Invalid server listen address: %s\n", host); + rv = 1; + goto cleanup; + } + + if ((bind(server_socket, (struct sockaddr*) &server_addr, + sizeof(struct sockaddr_in))) == -1) + { + fprintf(stderr, "Failed to bind to server socket (%s:%d): %s\n", + host, port, strerror(errno)); + rv = 1; + goto cleanup; + } + + if (listen(server_socket, LISTEN_BACKLOG) == -1) { + fprintf(stderr, "Failed to listen to server socket: %s\n", strerror(errno)); + rv = 1; + goto cleanup; + } + + fprintf(stderr, + " * Running on http://%s:%d/\n" + "\n" + "WARNING!!! This is a development server, DO NOT RUN IT IN PRODUCTION!\n" + "\n", host, port); + + socklen_t len = sizeof(struct sockaddr_in); + + while (1) { + struct sockaddr_in client_addr; + int client_socket = accept(server_socket, + (struct sockaddr *) &client_addr, &len); + if (client_socket == -1) { + fprintf(stderr, "Failed to accept connection: %s\n", strerror(errno)); + rv = 1; + goto cleanup; + } + + request_data_t *arg = malloc(sizeof(request_data_t)); + arg->socket = client_socket; + arg->docroot = docroot; + + // this isn't really safe. the server can be easily DDoS'ed. + pthread_t thread; + if (pthread_create(&thread, NULL, handle_request, arg) != 0) { + fprintf(stderr, "Failed to create thread\n"); + rv = 1; + goto cleanup; + } + } + +cleanup: + close(server_socket); + return rv; +} diff --git a/src/blogc-runserver/httpd.h b/src/blogc-runserver/httpd.h new file mode 100644 index 0000000..a59a467 --- /dev/null +++ b/src/blogc-runserver/httpd.h @@ -0,0 +1,14 @@ +/* + * 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. + */ + +#ifndef _HTTPD_H +#define _HTTPD_H + +int br_httpd_run(const char *host, unsigned short port, const char *docroot); + +#endif /* _HTTPD_H */ diff --git a/src/blogc-runserver/main.c b/src/blogc-runserver/main.c index 685b6e3..d12fc99 100644 --- a/src/blogc-runserver/main.c +++ b/src/blogc-runserver/main.c @@ -10,324 +10,12 @@ #include #endif /* HAVE_CONFIG_H */ -#include -#include -#include #include -#include -#include #include #include #include -#include -#include -#include #include "../common/utils.h" - - -// mime types with index should be in the begin of the list. first NULL -// index aborts the lookup, for optimization -static const struct content_type_map { - const char *mimetype; - const char *extension; - const char *index; -} content_types[] = { - - // with index - {"text/html", "html", "index.html"}, - {"text/html", "htm", "index.htm"}, - {"text/html", "shtml", "index.shtml"}, - {"text/xml", "xml", "index.xml"}, - {"text/plain", "txt", "index.txt"}, - {"application/xhtml+xml", "xhtml", "index.xhtml"}, - - // without index - {"text/css", "css", NULL}, - {"image/gif", "gif", NULL}, - {"image/jpeg", "jpeg", NULL}, - {"image/jpeg", "jpg", NULL}, - {"application/javascript", "js", NULL}, - {"application/atom+xml", "atom", NULL}, - {"application/rss+xml", "rss", NULL}, - {"text/mathml", "mml", NULL}, - {"text/vnd.sun.j2me.app-descriptor", "jad", NULL}, - {"text/vnd.wap.wml", "wml", NULL}, - {"text/x-component", "htc", NULL}, - {"image/png", "png", NULL}, - {"image/tiff", "tif", NULL}, - {"image/tiff", "tiff", NULL}, - {"image/vnd.wap.wbmp", "wbmp", NULL}, - {"image/x-icon", "ico", NULL}, - {"image/x-jng", "jng", NULL}, - {"image/x-ms-bmp", "bmp", NULL}, - {"image/svg+xml", "svg", NULL}, - {"image/svg+xml", "svgz", NULL}, - {"image/webp", "webp", NULL}, - {"application/font-woff", "woff", NULL}, - {"application/java-archive", "jar", NULL}, - {"application/java-archive", "war", NULL}, - {"application/java-archive", "ear", NULL}, - {"application/json", "json", NULL}, - {"application/mac-binhex40", "hqx", NULL}, - {"application/msword", "doc", NULL}, - {"application/pdf", "pdf", NULL}, - {"application/postscript", "ps", NULL}, - {"application/postscript", "eps", NULL}, - {"application/postscript", "ai", NULL}, - {"application/rtf", "rtf", NULL}, - {"application/vnd.apple.mpegurl", "m3u8", NULL}, - {"application/vnd.ms-excel", "xls", NULL}, - {"application/vnd.ms-fontobject", "eot", NULL}, - {"application/vnd.ms-powerpoint", "ppt", NULL}, - {"application/vnd.wap.wmlc", "wmlc", NULL}, - {"application/vnd.google-earth.kml+xml", "kml", NULL}, - {"application/vnd.google-earth.kmz", "kmz", NULL}, - {"application/x-7z-compressed", "7z", NULL}, - {"application/x-cocoa", "cco", NULL}, - {"application/x-java-archive-diff", "jardiff", NULL}, - {"application/x-java-jnlp-file", "jnlp", NULL}, - {"application/x-makeself", "run", NULL}, - {"application/x-perl", "pl", NULL}, - {"application/x-perl", "pm", NULL}, - {"application/x-pilot", "prc", NULL}, - {"application/x-pilot", "pdb", NULL}, - {"application/x-rar-compressed", "rar", NULL}, - {"application/x-redhat-package-manager", "rpm", NULL}, - {"application/x-sea", "sea", NULL}, - {"application/x-shockwave-flash", "swf", NULL}, - {"application/x-stuffit", "sit", NULL}, - {"application/x-tcl", "tcl", NULL}, - {"application/x-tcl", "tk", NULL}, - {"application/x-x509-ca-cert", "der", NULL}, - {"application/x-x509-ca-cert", "pem", NULL}, - {"application/x-x509-ca-cert", "crt", NULL}, - {"application/x-xpinstall", "xpi", NULL}, - {"application/xspf+xml", "xspf", NULL}, - {"application/zip", "zip", NULL}, - {"application/octet-stream", "bin", NULL}, - {"application/octet-stream", "exe", NULL}, - {"application/octet-stream", "dll", NULL}, - {"application/octet-stream", "deb", NULL}, - {"application/octet-stream", "dmg", NULL}, - {"application/octet-stream", "iso", NULL}, - {"application/octet-stream", "img", NULL}, - {"application/octet-stream", "msi", NULL}, - {"application/octet-stream", "msp", NULL}, - {"application/octet-stream", "msm", NULL}, - {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL}, - {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL}, - {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL}, - {"audio/midi", "mid", NULL}, - {"audio/midi", "midi", NULL}, - {"audio/midi", "kar", NULL}, - {"audio/mpeg", "mp3", NULL}, - {"audio/ogg", "ogg", NULL}, - {"audio/x-m4a", "m4a", NULL}, - {"audio/x-realaudio", "ra", NULL}, - {"video/3gpp", "3gpp", NULL}, - {"video/3gpp", "3gp", NULL}, - {"video/mp2t", "ts", NULL}, - {"video/mp4", "mp4", NULL}, - {"video/mpeg", "mpeg", NULL}, - {"video/mpeg", "mpg", NULL}, - {"video/quicktime", "mov", NULL}, - {"video/webm", "webm", NULL}, - {"video/x-flv", "flv", NULL}, - {"video/x-m4v", "m4v", NULL}, - {"video/x-mng", "mng", NULL}, - {"video/x-ms-asf", "asx", NULL}, - {"video/x-ms-asf", "asf", NULL}, - {"video/x-ms-wmv", "wmv", NULL}, - {"video/x-msvideo", "avi", NULL}, - {NULL, NULL, 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 const char* -guess_content_type(const char *filename) -{ - const char *extension = get_extension(filename); - if (extension == NULL) - goto default_type; - for (unsigned int i = 0; content_types[i].extension != NULL; i++) { - if (0 == strcmp(content_types[i].extension, extension)) { - return content_types[i].mimetype; - } - } -default_type: - return "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 = bc_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].index != NULL; i++) { - char *f = bc_strdup_printf("%s/%s", real_path, - content_types[i].index); - if (0 == access(f, F_OK)) { - found = 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; - } - - const char *type = guess_content_type(real_path); - - if (fstat(fd, &st) < 0) { - evhttp_send_error(request, 500, "Internal server error"); - goto point4; - } - - struct evkeyvalq *headers = evhttp_request_get_output_headers(request); - - if (add_slash) { - char *tmp = bc_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 point4; - } - - evhttp_add_header(headers, "Content-Type", type); - char *content_length = bc_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); - -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; -} +#include "httpd.h" static void @@ -421,7 +109,7 @@ main(int argc, char **argv) if (host == NULL) host = bc_strdup("127.0.0.1"); - rv = runserver(host, port, docroot); + rv = br_httpd_run(host, port, docroot); cleanup: free(host); diff --git a/src/blogc-runserver/mime.c b/src/blogc-runserver/mime.c new file mode 100644 index 0000000..b42d70a --- /dev/null +++ b/src/blogc-runserver/mime.c @@ -0,0 +1,168 @@ +/* + * 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 "../common/utils.h" +#include "httpd-utils.h" + + +// mime types with index should be in the begin of the list. first NULL +// index aborts the lookup, for optimization +static const struct content_type_map { + const char *mimetype; + const char *extension; + const char *index; +} content_types[] = { + + // with index + {"text/html", "html", "index.html"}, + {"text/html", "htm", "index.htm"}, + {"text/html", "shtml", "index.shtml"}, + {"text/xml", "xml", "index.xml"}, + {"text/plain", "txt", "index.txt"}, + {"application/xhtml+xml", "xhtml", "index.xhtml"}, + + // without index + {"text/css", "css", NULL}, + {"image/gif", "gif", NULL}, + {"image/jpeg", "jpeg", NULL}, + {"image/jpeg", "jpg", NULL}, + {"application/javascript", "js", NULL}, + {"application/atom+xml", "atom", NULL}, + {"application/rss+xml", "rss", NULL}, + {"text/mathml", "mml", NULL}, + {"text/vnd.sun.j2me.app-descriptor", "jad", NULL}, + {"text/vnd.wap.wml", "wml", NULL}, + {"text/x-component", "htc", NULL}, + {"image/png", "png", NULL}, + {"image/tiff", "tif", NULL}, + {"image/tiff", "tiff", NULL}, + {"image/vnd.wap.wbmp", "wbmp", NULL}, + {"image/x-icon", "ico", NULL}, + {"image/x-jng", "jng", NULL}, + {"image/x-ms-bmp", "bmp", NULL}, + {"image/svg+xml", "svg", NULL}, + {"image/svg+xml", "svgz", NULL}, + {"image/webp", "webp", NULL}, + {"application/font-woff", "woff", NULL}, + {"application/java-archive", "jar", NULL}, + {"application/java-archive", "war", NULL}, + {"application/java-archive", "ear", NULL}, + {"application/json", "json", NULL}, + {"application/mac-binhex40", "hqx", NULL}, + {"application/msword", "doc", NULL}, + {"application/pdf", "pdf", NULL}, + {"application/postscript", "ps", NULL}, + {"application/postscript", "eps", NULL}, + {"application/postscript", "ai", NULL}, + {"application/rtf", "rtf", NULL}, + {"application/vnd.apple.mpegurl", "m3u8", NULL}, + {"application/vnd.ms-excel", "xls", NULL}, + {"application/vnd.ms-fontobject", "eot", NULL}, + {"application/vnd.ms-powerpoint", "ppt", NULL}, + {"application/vnd.wap.wmlc", "wmlc", NULL}, + {"application/vnd.google-earth.kml+xml", "kml", NULL}, + {"application/vnd.google-earth.kmz", "kmz", NULL}, + {"application/x-7z-compressed", "7z", NULL}, + {"application/x-cocoa", "cco", NULL}, + {"application/x-java-archive-diff", "jardiff", NULL}, + {"application/x-java-jnlp-file", "jnlp", NULL}, + {"application/x-makeself", "run", NULL}, + {"application/x-perl", "pl", NULL}, + {"application/x-perl", "pm", NULL}, + {"application/x-pilot", "prc", NULL}, + {"application/x-pilot", "pdb", NULL}, + {"application/x-rar-compressed", "rar", NULL}, + {"application/x-redhat-package-manager", "rpm", NULL}, + {"application/x-sea", "sea", NULL}, + {"application/x-shockwave-flash", "swf", NULL}, + {"application/x-stuffit", "sit", NULL}, + {"application/x-tcl", "tcl", NULL}, + {"application/x-tcl", "tk", NULL}, + {"application/x-x509-ca-cert", "der", NULL}, + {"application/x-x509-ca-cert", "pem", NULL}, + {"application/x-x509-ca-cert", "crt", NULL}, + {"application/x-xpinstall", "xpi", NULL}, + {"application/xspf+xml", "xspf", NULL}, + {"application/zip", "zip", NULL}, + {"application/octet-stream", "bin", NULL}, + {"application/octet-stream", "exe", NULL}, + {"application/octet-stream", "dll", NULL}, + {"application/octet-stream", "deb", NULL}, + {"application/octet-stream", "dmg", NULL}, + {"application/octet-stream", "iso", NULL}, + {"application/octet-stream", "img", NULL}, + {"application/octet-stream", "msi", NULL}, + {"application/octet-stream", "msp", NULL}, + {"application/octet-stream", "msm", NULL}, + {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL}, + {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL}, + {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL}, + {"audio/midi", "mid", NULL}, + {"audio/midi", "midi", NULL}, + {"audio/midi", "kar", NULL}, + {"audio/mpeg", "mp3", NULL}, + {"audio/ogg", "ogg", NULL}, + {"audio/x-m4a", "m4a", NULL}, + {"audio/x-realaudio", "ra", NULL}, + {"video/3gpp", "3gpp", NULL}, + {"video/3gpp", "3gp", NULL}, + {"video/mp2t", "ts", NULL}, + {"video/mp4", "mp4", NULL}, + {"video/mpeg", "mpeg", NULL}, + {"video/mpeg", "mpg", NULL}, + {"video/quicktime", "mov", NULL}, + {"video/webm", "webm", NULL}, + {"video/x-flv", "flv", NULL}, + {"video/x-m4v", "m4v", NULL}, + {"video/x-mng", "mng", NULL}, + {"video/x-ms-asf", "asx", NULL}, + {"video/x-ms-asf", "asf", NULL}, + {"video/x-ms-wmv", "wmv", NULL}, + {"video/x-msvideo", "avi", NULL}, + {NULL, NULL, NULL} +}; + + +const char* +br_mime_guess_content_type(const char *filename) +{ + const char *extension = br_get_extension(filename); + if (extension == NULL) + goto default_type; + for (size_t i = 0; content_types[i].extension != NULL; i++) { + if (0 == strcmp(content_types[i].extension, extension)) { + return content_types[i].mimetype; + } + } + +default_type: + return "application/octet-stream"; +} + + +char* +br_mime_guess_index(const char *path) +{ + char *found = NULL; + for (size_t i = 0; content_types[i].index != NULL; i++) { + char *f = bc_strdup_printf("%s/%s", path, content_types[i].index); + if (0 == access(f, F_OK)) { + found = f; + break; + } + free(f); + } + return found; +} diff --git a/src/blogc-runserver/mime.h b/src/blogc-runserver/mime.h new file mode 100644 index 0000000..a4b6409 --- /dev/null +++ b/src/blogc-runserver/mime.h @@ -0,0 +1,15 @@ +/* + * 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. + */ + +#ifndef _MIME_H +#define _MIME_H + +const char* br_mime_guess_content_type(const char *filename); +char* br_mime_guess_index(const char *path); + +#endif /* _MIME_H */ diff --git a/src/blogc/loader.c b/src/blogc/loader.c index b182e0b..c3e1a4a 100644 --- a/src/blogc/loader.c +++ b/src/blogc/loader.c @@ -63,7 +63,7 @@ blogc_template_parse_from_file(const char *f, bc_error_t **err) if (err == NULL || *err != NULL) return NULL; size_t len; - char *s = bc_file_get_contents(f, &len, err); + char *s = bc_file_get_contents(f, true, &len, err); if (s == NULL) return NULL; bc_slist_t *rv = blogc_template_parse(s, len, err); @@ -78,7 +78,7 @@ blogc_source_parse_from_file(const char *f, bc_error_t **err) if (err == NULL || *err != NULL) return NULL; size_t len; - char *s = bc_file_get_contents(f, &len, err); + char *s = bc_file_get_contents(f, true, &len, err); if (s == NULL) return NULL; bc_trie_t *rv = blogc_source_parse(s, len, err); diff --git a/src/common/file.c b/src/common/file.c index 70a5631..47c97af 100644 --- a/src/common/file.c +++ b/src/common/file.c @@ -19,7 +19,7 @@ char* -bc_file_get_contents(const char *path, size_t *len, bc_error_t **err) +bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err) { if (path == NULL || err == NULL || *err != NULL) return NULL; @@ -43,7 +43,7 @@ bc_file_get_contents(const char *path, size_t *len, bc_error_t **err) tmp = buffer; - if (str->len == 0 && read_len > 0) { + if (utf8 && str->len == 0 && read_len > 0) { // skipping BOM before validation, for performance. should be safe // enough size_t skip = bc_utf8_skip_bom((uint8_t*) buffer, read_len); @@ -56,7 +56,7 @@ bc_file_get_contents(const char *path, size_t *len, bc_error_t **err) } fclose(fp); - if (!bc_utf8_validate_str(str)) { + if (utf8 && !bc_utf8_validate_str(str)) { *err = bc_error_new_printf(BC_ERROR_FILE, "File content is not valid UTF-8: %s", path); bc_string_free(str, true); diff --git a/src/common/file.h b/src/common/file.h index e095de7..498f4f4 100644 --- a/src/common/file.h +++ b/src/common/file.h @@ -10,10 +10,11 @@ #define _FILE_H #include +#include #include "error.h" #define BC_FILE_CHUNK_SIZE 1024 -char* bc_file_get_contents(const char *path, size_t *len, bc_error_t **err); +char* bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err); #endif /* _FILE_H */ -- cgit v1.2.3-18-g5258