diff options
Diffstat (limited to 'src/blogc-runserver/httpd.c')
-rw-r--r-- | src/blogc-runserver/httpd.c | 413 |
1 files changed, 0 insertions, 413 deletions
diff --git a/src/blogc-runserver/httpd.c b/src/blogc-runserver/httpd.c deleted file mode 100644 index fe98288..0000000 --- a/src/blogc-runserver/httpd.c +++ /dev/null @@ -1,413 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <errno.h> -#include <stdio.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <pthread.h> -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "mime.h" -#include "httpd-utils.h" - -#define LISTEN_BACKLOG 100 - -typedef struct { - pthread_t thread; - bool initialized; -} thread_data_t; - -typedef struct { - size_t thread_id; - int socket; - char *ip; - const char *docroot; -} request_data_t; - - -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" - "Content-Length: %zu\r\n" - "Connection: close\r\n" - "\r\n" - "<h1>%s</h1>\n", status_code, error, strlen(error) + 10, error); - size_t str_len = strlen(str); - if (str_len != write(socket, str, str_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(str); -} - - -static void* -handle_request(void *arg) -{ - request_data_t *req = arg; - size_t thread_id = req->thread_id; - int client_socket = req->socket; - char *ip = req->ip; - const char *docroot = req->docroot; - free(arg); - - char *conn_line = br_readline(client_socket); - if (conn_line == NULL || conn_line[0] == '\0') - goto point0; - - unsigned short status_code = 200; - - char **pieces = bc_str_split(conn_line, ' ', 3); - if (bc_strv_length(pieces) != 3) { - status_code = 400; - error(client_socket, 400, "Bad Request"); - goto point1; - } - - if (strcmp(pieces[0], "GET") != 0) { - status_code = 405; - 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) { - status_code = 400; - error(client_socket, 400, "Bad Request"); - goto point2; - } - - char *abs_path = bc_strdup_printf("%s/%s", docroot, path); - char *real_path = realpath(abs_path, NULL); - free(abs_path); - - if (real_path == NULL) { - if (errno == ENOENT) { - status_code = 404; - error(client_socket, 404, "Not Found"); - } - else { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - } - goto point2; - } - - char *real_root = realpath(docroot, NULL); - if (real_root == NULL) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - goto point3; - } - - if (0 != strncmp(real_root, real_path, strlen(real_root))) { - status_code = 404; - error(client_socket, 404, "Not Found"); - goto point4; - } - - struct stat st; - if (0 > stat(real_path, &st)) { - status_code = 404; - error(client_socket, 404, "Not Found"); - goto point4; - } - - bool add_slash = false; - - if (S_ISDIR(st.st_mode)) { - char *found = br_mime_guess_index(real_path); - - if (found == NULL) { - status_code = 403; - error(client_socket, 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; - } - - if (0 != access(real_path, F_OK)) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - goto point4; - } - - 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" - "Content-Length: 0\r\n" - "Connection: close\r\n" - "\r\n", path); - status_code = 302; - size_t tmp_len = strlen(tmp); - if (tmp_len != write(client_socket, tmp, tmp_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(tmp); - goto point4; - } - - size_t len; - bc_error_t *err = NULL; - char* contents = bc_file_get_contents(real_path, false, &len, &err); - if (err != NULL) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - bc_error_free(err); - goto point4; - } - - char *out = bc_strdup_printf( - "HTTP/1.0 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %zu\r\n" - "Connection: close\r\n" - "\r\n", br_mime_guess_content_type(real_path), len); - size_t out_len = strlen(out); - if (out_len != write(client_socket, out, out_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(out); - - if (len != write(client_socket, contents, len)) { - fprintf(stderr, "warning: Failed to write full response body!\n"); - } - free(contents); - -point4: - free(real_root); -point3: - free(real_path); -point2: - free(path); -point1: - fprintf(stderr, "[Thread-%zu] %s - - \"%s\" %d\n", thread_id + 1, - ip, conn_line, status_code); - free(conn_line); - bc_strv_free(pieces); -point0: - free(ip); - close(client_socket); - return NULL; -} - - -static char* -br_httpd_get_ip(int af, const struct sockaddr *addr) -{ - char host[INET6_ADDRSTRLEN]; - if (af == AF_INET6) { - struct sockaddr_in6 *a = (struct sockaddr_in6*) addr; - inet_ntop(af, &(a->sin6_addr), host, INET6_ADDRSTRLEN); - } - else { - struct sockaddr_in *a = (struct sockaddr_in*) addr; - inet_ntop(af, &(a->sin_addr), host, INET6_ADDRSTRLEN); - } - return bc_strdup(host); -} - - -static uint16_t -br_httpd_get_port(int af, const struct sockaddr *addr) -{ - in_port_t port = 0; - if (af == AF_INET6) { - struct sockaddr_in6 *a = (struct sockaddr_in6*) addr; - port = a->sin6_port; - } - else { - struct sockaddr_in *a = (struct sockaddr_in*) addr; - port = a->sin_port; - } - return ntohs(port); -} - - -int -br_httpd_run(const char *host, const char *port, const char *docroot, - size_t max_threads) -{ - int err; - struct addrinfo *result; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE, - .ai_protocol = 0, - .ai_canonname = NULL, - .ai_addr = NULL, - .ai_next = NULL, - }; - if (0 != (err = getaddrinfo(host, port, &hints, &result))) { - fprintf(stderr, "Failed to get host:port info: %s\n", - gai_strerror(err)); - return 1; - } - - thread_data_t threads[max_threads]; - for (size_t i = 0; i < max_threads; i++) - threads[i].initialized = false; - - int rv = 0; - - struct addrinfo *rp; - int server_socket = 0; - - int ai_family = 0; - char *final_host = NULL; - uint16_t final_port = 0; - - for (rp = result; rp != NULL; rp = rp->ai_next) { - final_host = br_httpd_get_ip(rp->ai_family, rp->ai_addr); - final_port = br_httpd_get_port(rp->ai_family, rp->ai_addr); - server_socket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (server_socket == -1) { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to open server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup0; - } - continue; - } - int value = 1; - if (0 > setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, - sizeof(int))) - { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to set socket option (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - close(server_socket); - continue; - } - if (0 == bind(server_socket, rp->ai_addr, rp->ai_addrlen)) { - ai_family = rp->ai_family; - break; - } - else { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to bind to server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - } - free(final_host); - close(server_socket); - } - - if (-1 == listen(server_socket, LISTEN_BACKLOG)) { - fprintf(stderr, "Failed to listen to server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - - fprintf(stderr, " * Running on http://"); - if (ai_family == AF_INET6) - fprintf(stderr, "[%s]", final_host); - else - fprintf(stderr, "%s", final_host); - if (final_port != 80) - fprintf(stderr, ":%d", final_port); - fprintf(stderr, "/ (max threads: %zu)\n" - "\n" - "WARNING!!! This is a development server, DO NOT RUN IT IN PRODUCTION!\n" - "\n", max_threads); - - size_t current_thread = 0; - - while (1) { - struct sockaddr_in6 addr6; - struct sockaddr_in addr; - - socklen_t addrlen; - struct sockaddr *client_addr = NULL; - - if (ai_family == AF_INET6) { - addrlen = sizeof(addr6); - client_addr = (struct sockaddr*) &addr6; - } - else { - addrlen = sizeof(addr); - client_addr = (struct sockaddr*) &addr; - } - - int client_socket = accept(server_socket, client_addr, &addrlen); - 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->thread_id = current_thread; - arg->socket = client_socket; - arg->ip = br_httpd_get_ip(ai_family, client_addr); - arg->docroot = docroot; - - if (threads[current_thread].initialized) { - if (pthread_join(threads[current_thread].thread, NULL) != 0) { - fprintf(stderr, "Failed to join thread\n"); - free(arg->ip); - free(arg); - rv = 1; - goto cleanup; - } - } - - if (pthread_create(&(threads[current_thread].thread), NULL, - handle_request, arg) != 0) - { - fprintf(stderr, "Failed to create thread\n"); - rv = 1; - goto cleanup; - } - - threads[current_thread++].initialized = true; - - if (current_thread >= max_threads) - current_thread = 0; - } - -cleanup: - close(server_socket); - -cleanup0: - free(final_host); - freeaddrinfo(result); - return rv; -} |