mobley

C Git HTTP server
Log | Files | Refs | Submodules | README | git clone https://git.ne02ptzero.me/git/mobley

commit 19d132a19f6baa74a347be7764afeffd72173d82
Author: Louis Solofrizzo <lsolofrizzo@online.net>
Date:   Fri, 14 Dec 2018 17:52:23 +0100

Create tree and basic HTTP server:

This commit introduce a basic code tree, with a CMakeLists.txt.

There's also some code for a pretty basic HTTP server:
- Port & IP binding via libevhtp
- Routing
- Static file handler
- HTML helpers
- Basic templating

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>

 ______________________________________
/ People say I live in my own little   \
| fantasy world... well, at least they |
\ *know* me there! -- D. L. Roth       /
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Diffstat:
A.gitignore | 1+
ACMakeLists.txt | 43+++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 12++++++++++++
Aargs.c | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aargs.h | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtml.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtml.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Aindex.c | 23+++++++++++++++++++++++
Aindex.h | 24++++++++++++++++++++++++
Alist.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alist.h | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobley.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver.h | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic.h | 24++++++++++++++++++++++++
Astr.h | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atemplate.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atemplate.h | 25+++++++++++++++++++++++++
Autils.h | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
21 files changed, 1890 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.4) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message (FATAL_ERROR "Don't use 'cmake .' Create a build folder first") +endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + +set(MOBLEY_NAME "mobley") +set(MOBLEY_MAJOR "1") +set(MOBLEY_MINOR "0") + +add_compile_options("-Wall;-Wextra;-Werror;-Wno-unused-parameter;-Wno-unused-result;-O3;-g") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexceptions") # Enable table-based thread cancellation +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") # Compile / Run time stack protection +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe") # Avoid temporary files, speeding up builds +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2") # Run-time buffer overflow detection +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=format-security") # Reject potentially unsafe format string arguents +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunreachable-code") # Warn if the compiler detects that code will never be executed +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wswitch-enum") # Force a switch on an enum to have all the cases possible + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_definitions(-DMOBLEY_NAME="${MOBLEY_NAME}") +add_definitions(-DMOBLEY_MAJOR="${MOBLEY_MAJOR}") +add_definitions(-DMOBLEY_MINOR="${MOBLEY_MINOR}") + +include_directories(".") + +link_libraries(pthread event event_openssl crypto ssl evhtp) + +add_executable(${MOBLEY_NAME} main.c + args.c + server.c + list.c + static.c + html.c + index.c + template.c +) + +install(TARGETS ${MOBLEY_NAME} DESTINATION bin) diff --git a/README.md b/README.md @@ -0,0 +1,12 @@ +# Mobley + +HTTP Git C Server + +# Dependencies + +- libevhtp-git + +# Build +``` +mkdir -p build && cd build && cmake .. && make +``` diff --git a/args.c b/args.c @@ -0,0 +1,255 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <args.h> +#include <str.h> +#include <string.h> +#include <stdio.h> + +static void *read_arg_default(const char *str) +{ + UNUSED_ARG(str); + return (void *)0x1; +} + +static void *read_arg_str(const char *str) +{ + if (str == NULL) + return NULL; + + return strdup(str); +} + +static void *read_arg_int(const char *str) +{ + int *ptr; + + ptr = malloc(sizeof(int)); + if (unlikely(ptr == NULL)) + return NULL; + + if (!str_to_int32(str, ptr, BASE_TEN)) + { + free(ptr); + return NULL; + } + return ptr; +} + +static void *read_arg_float(const char *str) +{ + float *ptr; + + ptr = malloc(sizeof(float)); + if (unlikely(ptr == NULL)) + return NULL; + + if (!str_to_float(str, ptr)) + { + free(ptr); + return NULL; + } + return ptr; +} + +static void *read_arg_file(const char *str) +{ + if (file_exist(str)) + return strdup(str); + return NULL; +} + +typedef void *(*opt_arg_read_fn_t)(const char *str); + +static const opt_arg_read_fn_t g_check_arg[] = +{ + [OPT_TYPE_NONE] = read_arg_default, + [OPT_TYPE_STRING] = read_arg_str, + [OPT_TYPE_INT] = read_arg_int, + [OPT_TYPE_FLOAT] = read_arg_float, + [OPT_TYPE_FILE] = read_arg_file, +}; + +static bool check_required_args(opts_t *args, size_t args_size) +{ + bool ret = true; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].required == true && args[i].result == NULL) + { + if (args[i].letter != '\0') + fprintf(stderr, "Error: Option '-%c' is mandatory\n", args[i].letter); + else + fprintf(stderr, "Error: Argument '--%s' is mandatory\n", args[i].name); + ret = false; + } + } + + return ret; +} + +static bool read_single_opt_arg(char opt, opts_t *args, size_t args_size, const char **av, int *cur, int ac) +{ + opts_t *arg = NULL; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].letter == opt) + { + arg = (opts_t *)&(args[i]); + break; + } + } + + if (arg == NULL) + { + fprintf(stderr, "Error: Unknown option '%c'\n", opt); + return false; + } + + if (*cur + 1 >= ac && arg->arg) + { + fprintf(stderr, "Error: Option '%c' require an argument\n", opt); + return false; + } + + if (arg->arg) + (*cur)++; + + arg->result = g_check_arg[arg->option_type](av[*cur]); + if (arg->result == NULL) + { + fprintf(stderr, "Error: Argument '%s' is invalid for option '%c'\n", av[*cur], opt); + return false; + } + return true; +} + +static bool read_long_opt_arg(const char *opt, opts_t *args, size_t args_size, const char **av, int *cur, int ac) +{ + opts_t *arg = NULL; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].name == NULL) + continue; + + if (strcmp(args[i].name, opt) == 0) + { + arg = (opts_t *)&(args[i]); + break; + } + } + + if (arg == NULL) + { + fprintf(stderr, "Error: Unknown option '%s'\n", opt); + return false; + } + + if (*cur + 1 >= ac && arg->arg) + { + fprintf(stderr, "Error: Option '%s' require an argument\n", opt); + return false; + } + + if (arg->arg) + (*cur)++; + + arg->result = g_check_arg[arg->option_type](av[*cur]); + if (arg->result == NULL) + { + fprintf(stderr, "Error: Argument '%s' is invalid for option '%s'\n", av[*cur], opt); + return false; + } + + return true; + +} + +#define STR_IS_SINGLE_OPT(str) (strlen(str) >= 2 && str[0] == '-' && str[1] != '-') +#define STR_IS_LONG_OPT(str) (strlen(str) >= 3 && str[0] == '-' && str[1] == '-' && str[2] != '-') +#define STR_IS_OPT_STOP_READ(str) (strlen(str) == 2 && str[0] == '-' && str[1] == '-') +bool parse_args(const int ac, const char **av, opts_t *args, size_t args_size, void (*help_fn)(void)) +{ + const char *current = NULL; + + for (int i = 0; i < ac; i++) + { + current = av[i]; + + if (current[0] != '-') + continue; + + if (STR_IS_SINGLE_OPT(current)) + { + for (size_t j = 1; j < strlen(current); j++) + { + if (current[j] == 'h') + { + help_fn(); + exit(0); + } + + if (!read_single_opt_arg(current[j], args, args_size, av, &i, ac)) + goto error; + } + } + else if (STR_IS_LONG_OPT(current)) + { + if (strcmp(current + 2, "help") == 0) + { + help_fn(); + exit(0); + } + + if (!read_long_opt_arg(current + 2, args, args_size, av, &i, ac)) + goto error; + } + else if (STR_IS_OPT_STOP_READ(current)) + { + goto stop_read; + } + else + { + fprintf(stderr, "Error: Parse error '%s'\n", current); + goto error; + } + } + + if (!check_required_args(args, args_size)) + return false; + + return true; + +error: + help_fn(); + return false; + +stop_read: + return true; +} + +void free_args(opts_t *args, size_t args_size) +{ + for (size_t i = 0; i < args_size; i++) + { + if (args[i].result != (void *)0x1) + free(args[i].result); + + args[i].result = NULL; + } +} diff --git a/args.h b/args.h @@ -0,0 +1,175 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef BASE_ARGS_H +# define BASE_ARGS_H + +# include <utils.h> + +# include <stdbool.h> +# include <stdlib.h> + +typedef enum { + OPT_TYPE_NONE = 0, + OPT_TYPE_STRING, + OPT_TYPE_INT, + OPT_TYPE_FLOAT, + OPT_TYPE_FILE, +} e_opt_type_t; + +# define SINGLE_OPT(l) .letter = l +# define LONG_OPT(str) .name = str +# define REQUIRED .required = true +# define ARG_TYPE(type) .option_type = type, .arg = true + +# define GET_STR_ARG(opts, id) (char *)((opts)[id].result) +# define GET_INT_ARG(opts, id) *((int *)((opts)[id].result)) +# define GET_FLOAT_ARG(opts, id) *((float *)((opts)[id].result)) +# define GET_IPV4_ARG(opts, id) *((ipv4_t *)((opts)[id].result)) +# define IS_ARG_HERE(opts, id) ((opts)[id].result != NULL) +# define GET_FILE_ARG GET_STR_ARG + +typedef struct { + const char letter; + const e_opt_type_t option_type; + const bool arg; + const bool required; + const char *name; + void *result; +} opts_t; + +/*! + * \brief Parse command line arguments with preset options + * + * \param[in] ac Number of command line arguments + * \param[in] av Command line arguments + * \param[in,out] args Preset arguments + * \param[in] args_size Number of preset arguments + * \param[in] help_fn Callback help function + * + * The purpose of this function is to parse command line arguments, and return + * an usable structure for option treatment. + * This structure is called "Preset Arguments" and should look like the + * following: + * ``` + * static opts_t g_variable_name[] = { + * [OPT_X] = + * { + * ARG_FLAG + * }, + * [OPT_Y] = + * { + * [...] + * }; + * ``` + * + * OPT_X and Y are used to define flag 'names'; There are to be used later + * in order to retrieve information about them. One can use the implicit array + * order of GCC, but I strongly advise against it: It is much clearer with + * defines. Therefore, you must define OPT_X to an integer value, starting from + * 0. Those defines shall served as array keys, so the order must be a coherent + * one: 0,1,2,3,...,X + * ``` + * #define OPT_X 0 + * #define OPT_Y 1 + * ``` + * + * In the short code above, you can see a 'ARG_FLAG'. These are the macros + * that you can flag your option with. There are to be used like a list + * enumeration: + * ``` + * ARG_FLAG_1, + * ARG_FLAG_2, + * ARG_FLAG_WITH_PARAM(5) + * ``` + * Here is the list of all the availables flags for an argument: + * - SINGLE_OPT(letter): + * The command line flag shall be one with a single letter: + * > -z -a + * 'letter' is an argument that defines the flag letter; If 'a' where to + * be used, the flag would be '-a'. + * + * - LONG_OPT(string): + * The command line flag shall be one with an entire word: + * > --verbose, --debug + * 'string' is an argument that defines the flag word; If the string + * 'hello' where to be used, the flag would be '--hello' + * + * - REQUIRED: + * This flag indicates that the corresponding flag must be met in The + * command line. It is worth noting that if a required argument is not met + * in the command line, this function shall return false + * + * - TYPE(type): + * This flag defines the type of argument that a flag shall receive. + * The 'type' argument is used to describe the said type; One must + * use one of the value present in e_opt_type_t: + * - OPT_TYPE_STRING: Simple string + * - OPT_TYPE_INT: 4 bytes integer + * - OPT_TYPE_FLOAT: Floating point integer + * - OPT_TYPE_FILE: An existing file + * - OPT_TYPE_IPV4: A valid IPv4 + * + * It is worth noting that if an argument is not in the expected format + * this function shall call the help_fn callback then exit(3) the main + * thread. + * + * With this structure filled, one must call the present function in order + * to parse the command line. If a parse error happens, or a memory error, + * this function shall call the help_fn callback, then exit(3) the main + * thread. + * + * If everything is successfull, one can retrieve the parsed value with the + * following mechanic: + * GET_XXX_ARG(g_variable_name, OPT_X); + * Here, 'g_variable_name' is the name of the opts_t array that was used to + * call this function. + * 'OPT_X' is the flag index. + * + * The 'GET_XXX_ARG' represent one of the many calls that can be used to + * retrieve an argument in the correct format: + * - GET_STR_ARG: Get a string argument (char *) + * - GET_INT_ARG: Get a 4 bytes integer argument (int) + * - GET_FLOAT_ARG: Get a floating point argument (float) + * - GET_IPV4_ARG: Get an ipv4 argument (ipv4_t) + * - GET_FILE_ARG: Alias to GET_STR_ARG + * + * In all thoses cases, the return variable has been safely allocated by + * this function, and are freed by free_args. + * + * If one must use a flag without an argument (a presence flag), he must use: + * IS_ARG_HERE(g_variable_name, OPT_X) + * This macro shall return true if the corresponding flag has been parsed, + * false otherwise. + * + * \return true on parse success, otherwise false + */ +__NON_NULL_ALL +bool parse_args(const int ac, const char **av, opts_t *args, size_t args_size, + void (*help_fn)(void)); + +/*! + * \brief Free a opts_t array + * + * \param[in,out] args Array to free + * \param[in] args_size Array size + */ +__NON_NULL_ALL +void free_args(opts_t *args, size_t args_size); + +# ifdef __cplusplus +} +# endif /* __cplusplus */ +#endif /* BASE_ARGS_H */ diff --git a/html.c b/html.c @@ -0,0 +1,70 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <html.h> + +void _html(server_req_t *r, const mobley_t *ctx, const char *b, const char *d, + html_args_t *a) +{ + evbuffer_add_printf(r->req->buffer_out, "<%s", b); + + if (a->href != NULL) + { + if (a->href[0] == '/') + { + evbuffer_add_printf(r->req->buffer_out, + " href=\"http://%s:%d%s\"", ctx->ip, ctx->port, a->href); + } + else if (strncmp(a->href, "http", sizeof("http") -1) == 0) + { + evbuffer_add_printf(r->req->buffer_out, " href=\"%s\"", a->href); + } + else + { + evbuffer_add_printf(r->req->buffer_out, + " href=\"http://%s:%d/%s/%s\"", ctx->ip, ctx->port, + (r->route == NULL ? "" : r->route), a->href); + } + } + if (a->class != NULL) + evbuffer_add_printf(r->req->buffer_out, " class=\"%s\"", a->class); + if (a->rel != NULL) + evbuffer_add_printf(r->req->buffer_out, " rel=\"%s\"", a->rel); + if (a->name != NULL) + evbuffer_add_printf(r->req->buffer_out, " name=\"%s\"", a->name); + if (a->content != NULL) + evbuffer_add_printf(r->req->buffer_out, " content=\"%s\"", a->content); + if (a->type != NULL) + evbuffer_add_printf(r->req->buffer_out, " type=\"%s\"", a->type); + if (a->src != NULL) + evbuffer_add_printf(r->req->buffer_out, " src=\"%s\"", a->src); + if (a->title != NULL) + evbuffer_add_printf(r->req->buffer_out, " title=\"%s\"", a->title); + if (a->lang != NULL) + evbuffer_add_printf(r->req->buffer_out, " lang=\"%s\"", a->lang); + if (a->charset != NULL) + evbuffer_add_printf(r->req->buffer_out, " charset=\"%s\"", a->charset); + + if (d == NULL) + goto closing; + + evbuffer_add_printf(r->req->buffer_out, ">%s</%s", d, b); + +closing: + if (strcmp(b, "img") == 0 || strcmp(b, "meta") == 0) + evbuffer_add_printf(r->req->buffer_out, "/>"); + else + evbuffer_add_printf(r->req->buffer_out, ">"); +} diff --git a/html.h b/html.h @@ -0,0 +1,46 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef HTML_H +# define HTMl_H + +# include <server.h> +# include <mobley.h> + +typedef struct { + const char *href; + const char *class; + const char *rel; + const char *name; + const char *content; + const char *type; + const char *src; + const char *title; + const char *lang; + const char *charset; + bool closing_slash; +} html_args_t; + +void _html(server_req_t *r, const mobley_t *ctx, + const char *b, const char *d, html_args_t *a); +# define html(b, d, ...) _html(r, ctx, b, d, &(html_args_t){__VA_ARGS__}); +# define html_nd(b, ...) _html(r, ctx, b, NULL, &(html_args_t){__VA_ARGS__}); + +# define html_raw(s, ...) evbuffer_add_printf((r)->req->buffer_out, s, ##__VA_ARGS__) +# define html_open(s) evbuffer_add_printf((r)->req->buffer_out, "<" s ">"); +# define html_close(s) evbuffer_add_printf((r)->req->buffer_out, "</" s ">") +# define html_simple(s) html_open(s) + +#endif /* HTML_H */ diff --git a/index.c b/index.c @@ -0,0 +1,23 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <index.h> +#include <html.h> + +bool index_route_handler(const mobley_t *ctx, server_req_t *r) +{ + html("h1", "Salut!"); + return true; +} diff --git a/index.h b/index.h @@ -0,0 +1,24 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef INDEX_H +# define INDEX_H + +# include <server.h> +# include <mobley.h> + +bool index_route_handler(const mobley_t *ctx, server_req_t *r); + +#endif /* INDEX_H */ diff --git a/list.c b/list.c @@ -0,0 +1,148 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <list.h> +#include <stdint.h> +#include <string.h> + +#define MAX_LIST_LENGTH_BITS 20 + +/** + * Returns a list organized in an intermediate format suited + * to chaining of merge() calls: null-terminated, no reserved or + * sentinel head node, "prev" links not maintained. + */ +static list_head_t *merge(void *priv, + int (*cmp)(void *priv, list_head_t *a, list_head_t *b), + list_head_t *a, list_head_t *b) +{ + list_head_t head, *tail = &head; + + while (a && b) + { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) + { + tail->next = a; + a = a->next; + } + else + { + tail->next = b; + b = b->next; + } + tail = tail->next; + } + tail->next = a?:b; + + return head.next; +} + +/** + * Combine final list merge with restoration of standard doubly-linked + * list structure. This approach duplicates code from merge(), but + * runs faster than the tidier alternatives of either a separate final + * prev-link restoration pass, or maintaining the prev links + * throughout. + */ +static void merge_and_restore_back_links(void *priv, + int (*cmp)(void *priv, list_head_t *a, list_head_t *b), + list_head_t *head, list_head_t *a, list_head_t *b) +{ + list_head_t *tail = head; + uint8_t count = 0; + + while (a && b) + { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) + { + tail->next = a; + a->prev = tail; + a = a->next; + } + else + { + tail->next = b; + b->prev = tail; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? : b; + + do + { + /* + * In worst cases this loop may run many iterations. + * Continue callbacks to the client even though no + * element comparison is needed, so the client's cmp() + * routine can invoke cond_resched() periodically. + */ + if (unlikely(!(++count))) + (*cmp)(priv, tail->next, tail->next); + + tail->next->prev = tail; + tail = tail->next; + + } while (tail->next); + + tail->next = head; + head->prev = tail; +} + +void list_sort(void *priv, list_head_t *head, int (*cmp)(void *priv, list_head_t *a, list_head_t *b)) +{ + list_head_t *part[MAX_LIST_LENGTH_BITS + 1] = { 0 }; /* sorted partial lists last slot is a sentinel */ + int lev; /* index into part[] */ + int max_lev = 0; + list_head_t *list; + + if (list_empty(head)) + return; + + head->prev->next = NULL; + list = head->next; + + while (list) + { + list_head_t *cur = list; + list = list->next; + cur->next = NULL; + + for (lev = 0; part[lev]; lev++) + { + cur = merge(priv, cmp, part[lev], cur); + part[lev] = NULL; + } + + if (lev > max_lev) + { + if (unlikely(lev >= (int)(COUNT_OF(part) - 1))) + lev--; + + max_lev = lev; + } + part[lev] = cur; + } + + for (lev = 0; lev < max_lev; lev++) + { + if (part[lev]) + list = merge(priv, cmp, part[lev], list); + } + + merge_and_restore_back_links(priv, cmp, head, part[max_lev], list); +} diff --git a/list.h b/list.h @@ -0,0 +1,129 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef LIST_H +# define LIST_H + +# include <utils.h> + +typedef struct list_head { + struct list_head *next, *prev; +} list_head_t; + +# define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +# define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +# define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +# define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_first_entry(head, typeof(*pos), member), \ + n = list_next_entry(pos, member); \ + &pos->member != (head); \ + pos = n, n = list_next_entry(n, member)) + +# define list_entry(ptr, type, member) container_of(ptr, type, member) +# define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member) +# define list_last_entry(ptr, type, member) list_entry((ptr)->prev, type, member) +# define list_next_entry(pos, member) list_entry((pos)->member.next, typeof(*(pos)), member) +# define list_prev_entry(pos, member) list_entry((pos)->member.prev, typeof(*(pos)), member) + +static inline void INIT_LIST_HEAD(list_head_t *list) +{ + list->next = list; + list->prev = list; +} + +static inline void list_add(list_head_t *new, + list_head_t *prev, + list_head_t *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add_tail(list_head_t *new, list_head_t *head) +{ + list_add(new, head->prev, head); +} + +static inline void __list_del(list_head_t *prev, list_head_t *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del(list_head_t *entry) +{ + __list_del(entry->prev, entry->next); +} + +/*! + * \brief Check wheter or not a list is empty + * + * \param[in] head Head of the list + * + * \return true if the list is empty, false otherwise + */ +static inline bool list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/*! + * \brief Count the number of elements in a list + * + * \param[in] head Head of the list + * + * \return Size of the list + */ +static inline size_t list_count(const list_head_t *head) +{ + list_head_t *iter; + size_t i = 0; + + list_for_each(iter, head) + i++; + + return i; +} + +/** + * \brief Sort a list + * + * \param[in] priv private data, opaque to list_sort(), passed to cmp + * \param[in,out] head the list to sort + * \param[in] cmp the elements comparison function + * + * This function implements "merge sort", which has O(nlog(n)) + * complexity. + * + * The comparison function @cmp must return a negative value if @a + * should sort before @b, and a positive value if @a should sort after + * @b. If @a and @b are equivalent, and their original relative + * ordering is to be preserved, @cmp must return 0. + */ +void list_sort(void *priv, list_head_t *head, int (*cmp)(void *priv, list_head_t *a, list_head_t *b)); + + +#endif /*LIST_H */ diff --git a/main.c b/main.c @@ -0,0 +1,136 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <args.h> +#include <stdio.h> +#include <signal.h> + +#include <event2/event.h> +#include <event2/event-config.h> +#include <event2/util.h> + +#include <mobley.h> + +enum { + OPT_VERSION = 0, + OPT_IP, + OPT_PORT +}; + +static opts_t options[] = { + [OPT_VERSION] = { + LONG_OPT("version"), + SINGLE_OPT('v') + }, + [OPT_IP] = { + LONG_OPT("addr"), + SINGLE_OPT('a'), + ARG_TYPE(OPT_TYPE_STRING) +#define DEFAULT_IP "127.0.0.1" + }, + [OPT_PORT] = { + LONG_OPT("port"), + SINGLE_OPT('p'), + ARG_TYPE(OPT_TYPE_INT) +#define DEFAULT_PORT 4242 + } +}; + +#define usage(str, ...) fprintf(stdout, str "\n", ##__VA_ARGS__) +static void help(void) +{ + usage("Usage: " MOBLEY_NAME " [-a IP] [-p PORT] -[v]"); + usage("HTTP Git server"); + usage(""); + usage("Options:"); + usage(" -v, --version Print software version"); + usage(" -a, --addr=IP IP Address to bind on. Default is " DEFAULT_IP); + usage(" -p, --port=PORT Port to bind on. Default is " STR(DEFAULT_PORT)); + usage(""); + usage("Sources are available at <https://git.ne02ptzero.me/mobley>"); + usage("Found a bug? Please shoot me an email!"); + usage("Louis Solofrizzo <louis at ne02ptzero dot me>"); +} + +static void version(void) +{ + usage(MOBLEY_NAME " " MOBLEY_MAJOR "." MOBLEY_MINOR); + usage("License GPLv3+: GNU GPL version 3 or later " + "<https://gnu.org/licenses/gpl.html>."); + usage("This is free software: you are free to change and redistribute it."); + usage("There is NO WARRANTY, to the extent permitted by law."); + usage(""); + usage("Sources are available at <https://git.ne02ptzero.me/mobley>"); + usage("Found a bug? Please shoot me an email!"); + usage("Louis Solofrizzo <louis at ne02ptzero dot me>"); +} +#undef usage + +static bool mobley_options(mobley_t *ctx) +{ + if (IS_ARG_HERE(options, OPT_VERSION)) + { + version(); + return false; + } + + if (IS_ARG_HERE(options, OPT_IP)) + ctx->ip = GET_STR_ARG(options, OPT_IP); + else + ctx->ip = DEFAULT_IP; + + if (IS_ARG_HERE(options, OPT_PORT)) + ctx->port = GET_INT_ARG(options, OPT_PORT); + else + ctx->port = DEFAULT_PORT; + + ctx->static_dir = "/usr/local/share/mobley/"; + + return true; +} + +static void sighandler(int signal, short events, void *base) +{ + event_base_loopbreak(base); +} + +int main(int ac, const char **av) +{ + int ret = 1; + mobley_t ctx = { 0 }; + struct event *ev_sigint = NULL; + + if (!parse_args(ac, av, options, COUNT_OF(options), &help)) + return 1; + + if (!mobley_options(&ctx)) + goto end; + + if (!mobley_init(&ctx)) + goto end; + + ev_sigint = evsignal_new(ctx.evbase, SIGINT, sighandler, ctx.evbase); + evsignal_add(ev_sigint, NULL); + + if (!mobley_run(&ctx)) + goto end; + + ret = 0; +end: + event_free(ev_sigint); + mobley_dtr(&ctx); + free_args(options, COUNT_OF(options)); + return ret; +} diff --git a/mobley.h b/mobley.h @@ -0,0 +1,79 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef MOBLEY_H +# define MOBLEY_H + +# include <stdbool.h> + +# include <evhtp/evhtp.h> +# include <evhtp/log.h> + +# include <server.h> + +typedef struct { + const char *ip; + uint16_t port; + + const char *static_dir; + + struct event_base *evbase; + struct evhtp *htp; +} mobley_t; + +static inline void mobley_dtr(mobley_t *ctx) +{ + evhtp_unbind_socket(ctx->htp); + evhtp_free(ctx->htp); + event_base_free(ctx->evbase); +} + +static inline void mobley_config(mobley_t *ctx) +{ + evhtp_set_cb(ctx->htp, "/", &server_process_request, ctx); + evhtp_enable_flag(ctx->htp, EVHTP_FLAG_ENABLE_ALL); + +#ifndef EVHTP_DISABLE_EVTHR + evhtp_use_threads_wexit(ctx->htp, NULL, NULL, 4, NULL); +#endif + + evhtp_bind_socket(ctx->htp, ctx->ip, ctx->port, 128); + printf("Mobley is running on http://%s:%d\n", ctx->ip, ctx->port); +} + +static inline bool mobley_init(mobley_t *ctx) +{ + ctx->evbase = event_base_new(); + if (ctx->evbase == NULL) + goto fail; + + ctx->htp = evhtp_new(ctx->evbase, NULL); + if (ctx->htp == NULL) + goto fail; + + mobley_config(ctx); + return true; +fail: + mobley_dtr(ctx); + return false; +} + +static inline bool mobley_run(mobley_t *ctx) +{ + event_base_loop(ctx->evbase, 0); + return true; +} + +#endif /* MOBLEY_H */ diff --git a/server.c b/server.c @@ -0,0 +1,109 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <server.h> +#include <mobley.h> +#include <str.h> + +#include <static.h> +#include <index.h> +#include <template.h> + +typedef struct { + const char *name; + bool (*fn)(const mobley_t *, server_req_t *); + bool is_plain; +} route_t; + +static const route_t routes[] = { + { + .name = "static", + .fn = &static_route_handler, + .is_plain = true + } +}; + +static bool server_route_request(server_req_t *r, mobley_t *ctx) +{ + string_array_t *first = list_first_entry(&r->uri, string_array_t, node); + bool ret = false; + + if (first->val == NULL) + { + server_req_set_content_type(r, "text/html; charset=iso-8859-1"); + template_begin(ctx, r); + ret = index_route_handler(ctx, r); + template_end(ctx, r); + goto end; + } + + STATIC_ARRAY_FOREACH(const route_t *route, routes) + { + if (strcmp(route->name, first->val) == 0) + { + if (!route->is_plain) + { + template_begin(ctx, r); + server_req_set_content_type(r, "text/html; charset=iso-8859-1"); + } + + list_del(&first->node); + r->route = first->val; + free(first); + + ret = route->fn(ctx, r); + + if (!route->is_plain) + template_end(ctx, r); + + goto end; + } + } + + r->res = EVHTP_RES_NOTFOUND; +end: + return ret; +} + +static void server_error_handler(server_req_t *r) +{ + server_req_set_content_type(r, "text/html; charset=iso-8859-1"); + + switch (r->res) + { + case EVHTP_RES_NOTFOUND: + evbuffer_add_printf(r->req->buffer_out, "<h1>404 Not Found</h1>"); + break; + } +} + +void server_process_request(evhtp_request_t *req, void *arg) +{ + server_req_t r = { 0 }; + + if (!server_req_init(&r, req)) + goto end; + + if (!server_route_request(&r, arg)) + goto end; + + r.res = EVHTP_RES_OK; +end: + if (r.res > 400) + server_error_handler(&r); + + evhtp_send_reply(req, r.res); + server_req_dtr(&r); +} diff --git a/server.h b/server.h @@ -0,0 +1,62 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef SERVER_H +# define SERVER_H + +# define _GNU_SOURCE +# include <stdio.h> + +# include <evhtp/evhtp.h> + +# include <list.h> +# include <str.h> + +typedef struct { + evhtp_request_t *req; + evhtp_res res; + list_head_t uri; + char *route; +} server_req_t; + +static inline void server_req_dtr(server_req_t *req) +{ + string_array_free(&req->uri); + free(req->route); +} + +static inline bool server_req_init(server_req_t *req, evhtp_request_t *r) +{ + req->req = r; + req->res = EVHTP_RES_ERROR; + INIT_LIST_HEAD(&req->uri); + + if (!str_to_string_array(r->uri->path->full, &req->uri, "/")) + goto fail; + + return true; +fail: + return false; +} + +static inline void server_req_set_content_type(server_req_t *r, const char *type) +{ + evhtp_headers_add_header(r->req->headers_out, + evhtp_header_new("Content-Type", type, 0, 1)); +} + +void server_process_request(evhtp_request_t *req, void *arg); + +#endif /* SERVER_H */ diff --git a/static.c b/static.c @@ -0,0 +1,61 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <static.h> + +static void static_set_content_type(server_req_t *r, const char *path) +{ + if (is_ext(path, ".css")) + server_req_set_content_type(r, "text/css"); +} + +static bool static_send_file(server_req_t *r, const char *path) +{ + int fd; + struct stat st = { 0 }; + + fd = open(path, O_RDONLY); + if (fd == -1) + { + r->res = EVHTP_RES_NOTFOUND; + return false; + } + + fstat(fd, &st); + static_set_content_type(r, path); + + evbuffer_add_file(r->req->buffer_out, fd, 0, st.st_size); + close(fd); + + return true; +} + +bool static_route_handler(const mobley_t *ctx, server_req_t *r) +{ + string_array_t *node; + char path[PATH_MAX] = { 0 }; + char *cur = path; + char *end = path + sizeof(path); + + cur += snprintf(path, end - cur, "%s", ctx->static_dir); + + list_for_each_entry(node, &r->uri, node) + cur += snprintf(cur, end - cur, "/%s", node->val); + + if (!static_send_file(r, path)) + return false; + + return true; +} diff --git a/static.h b/static.h @@ -0,0 +1,24 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef STATIC_H +# define STATIC_H + +#include <server.h> +#include <mobley.h> + +bool static_route_handler(const mobley_t *ctx, server_req_t *r); + +#endif /* STATIC_H */ diff --git a/str.h b/str.h @@ -0,0 +1,216 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef STR_H +# define STR_H + +# include <stdlib.h> +# include <string.h> +# include <errno.h> +# include <assert.h> +# include <stdio.h> + +# include <utils.h> +# include <list.h> +# define BASE_TEN 10 + +/*! + * \brief Check if a string ends with another string + * + * \param[in] str Hastack + * \param[in] ext Needle + * + * \return true if end match, false otherwise + */ +static inline bool str_ends_with(const char *str, const char *ext) +{ + return strcmp(str + strlen(str) - strlen(ext), ext) == 0; +} + +/*! + * \brief Convert a string to a 4 bytes integer + * + * \param[in] in Input string + * \param[out] out Output integer + * \param[in] base Numeric base + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_int32(const char *in, int *out, int base) +{ + char *end; + + errno = 0; + + *out = strtol(in, &end, base); + + if (end == in || strlen(end) > 0 || errno) + return false; + return true; +} + +/*! + * \brief Convert a string to a 1 byte integer + * + * \param[in] in Input string + * \param[out] out Output integer + * \param[in] base Numeric base + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_uint8(const char *in, unsigned char *out, int base) +{ + char *end; + int tmp; + + errno = 0; + + tmp = strtol(in, &end, base); + + if (end == in || strlen(end) > 0 || errno) + return false; + if (tmp < 0 || tmp > 255) + return false; + + *out = (unsigned char)tmp; + return true; +} + +/*! + * \brief Convert a string to a floating point + * + * \param[in] in Input string + * \param[out] out Output floating integer + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_float(const char *in, float *out) +{ + char *end; + + errno = 0; + + *out = strtof(in, &end); + + if (end == in || strlen(end) > 0 || errno) + return false; + return true; +} + +/*! + * \brief Convert a string to a boolean + * + * \param[in] in Input string + * \param[out] out Output boolean + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_bool(const char *in, bool *out) +{ + if (strcmp(in, "true") == 0) + { + *out = true; + return true; + } + else if (strcmp(in, "false") == 0) + { + *out = false; + return true; + } + + return false; +} + +/*! + * \brief Convert a string to a percentage + * + * \param[in] in Input string + * \param[out] out Output percentage + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_percentage(const char *in, unsigned char *out) +{ + if (!str_to_uint8(in, out, BASE_TEN) || *out > 100) + return false; + + return true; +} + +typedef struct { + char *val; + list_head_t node; +} string_array_t; + +static inline void string_array_free(list_head_t *head) +{ + string_array_t *node, *tmp; + + list_for_each_entry_safe(node, tmp, head, node) + { + free(node->val); + free(node); + } +} + +static inline void string_array_node_del(string_array_t *node) +{ + list_del(&node->node); + free(node->val); + free(node); +} + +static inline bool str_to_string_array(const char *in, list_head_t *out, const char *delims) +{ + string_array_t *node; + char *data = strdup(in), *ptr = data; + char *line; + + if (in == NULL) + return NULL; + + while ((line = strsep(&data, delims)) != NULL) + { + if (line == NULL || strlen(line) == 0) + continue; + + node = calloc(1, sizeof(*node)); + if (node == NULL) + goto fail; + + node->val = strdup(line); + if (node->val == NULL) + { + free(node); + goto fail; + } + + list_add_tail(&node->node, out); + } + + free(ptr); + return true; +fail: + string_array_free(out); + free(data); + return false; +} + +#endif /* STR_H */ diff --git a/template.c b/template.c @@ -0,0 +1,72 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <template.h> +#include <html.h> + +static void template_meta(const mobley_t *ctx, server_req_t *r) +{ + html_nd("meta", + .name = "generator", + .content = MOBLEY_NAME, + .closing_slash = true + ); + + html_nd("meta", + .name = "robots", + .content = "index,nofollow", + .closing_slash = true + ); + + html_nd("meta", + .name = "viewport", + .content = "width=device-width, initial-scale=1", + .closing_slash = true + ); + + html_nd("meta", + .charset = "utf-8", + .closing_slash = true + ); +} + +void template_begin(const mobley_t *ctx, server_req_t *r) +{ + html_simple("!DOCTYPE html"); + html("html", NULL, .lang = "en") { + html_open("head") { + + template_meta(ctx, r); + + html("title", "git://"); + + html_nd("link", + .rel = "stylesheet", + .type = "text/css", + .href = "/static/main.css", + .closing_slash = true + ); + + } html_close("head"); + + html_open("body"); + } +} + +void template_end(const mobley_t *ctx, server_req_t *r) +{ + html_close("body"); + html_close("html"); +} diff --git a/template.h b/template.h @@ -0,0 +1,25 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef TEMPLATE_H +# define TEMPLATE_H + +# include <server.h> +# include <mobley.h> + +void template_begin(const mobley_t *ctx, server_req_t *r); +void template_end(const mobley_t *ctx, server_req_t *r); + +#endif /* TEMPLATE_H */ diff --git a/utils.h b/utils.h @@ -0,0 +1,180 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef UTILS_H +# define UTILS_H + +# include <fcntl.h> +# include <unistd.h> +# include <stdbool.h> +# include <string.h> +# include <dirent.h> +# include <sys/types.h> +# include <sys/stat.h> +# if defined(__APPLE__) || defined(__FreeBSD__) +# include <copyfile.h> +# else +# include <sys/sendfile.h> +# endif + +/* Compile-time assertion */ +# define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] +# define COUNT_OF(ptr) sizeof(ptr) / sizeof(ptr[0]) + +# define QUOTE_ME(x) #x +# define STR(x) QUOTE_ME(x) + +# define EMPTY_STR() { '\0' } +# define STR_OR_EMPTY(str) (str != NULL ? str : "") +# define STR_NULL_OR_EMPTY(str) (str == NULL || (str != NULL && *str == '\0')) +# define FREE(ptr) free(ptr); ptr = NULL; +# define UNUSED_ARG(arg) (void)arg +# define STRLEN(str) str == NULL ? 0 : strlen(str) + +/* Magic things */ +# undef offsetof +# ifdef __compiler_offsetof +# define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER) +# else +# define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) +# endif /* __compiler_offsetof */ + +# define container_of(ptr, type, member) ({\ + const typeof( ((type *)0)->member ) *__mptr = (ptr);\ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +# define ___UNIQ(name, n) name##n +# define __UNIQ(name, n) ___UNIQ(name, n) +# define _UNIQ(name, n) __UNIQ(name, n) +# define UNIQ(name) _UNIQ(name, __LINE__) + +# define __STATIC_ARRAY_FOREACH(index, keep, item, array) \ + for (size_t keep = 1, index = 0; keep && index < COUNT_OF(array); keep = !keep, index++) \ + for (item = &array[index]; keep; keep = !keep) + +# define STATIC_ARRAY_FOREACH_I(index, item, array) __STATIC_ARRAY_FOREACH(index, UNIQ(__foreach_keep), item, array) +# define STATIC_ARRAY_FOREACH(item, array) __STATIC_ARRAY_FOREACH(UNIQ(__foreach_index), UNIQ(__foreach_keep), item, array) + +# define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +# define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) + +# define __HOT_FUNCTION __attribute__((hot)) +# define __COLD_FUNCTION __attribute__((cold)) +# ifndef __DEPRECATED +# define __DEPRECATED __attribute__((deprecated)) +# endif /* __DEPRECATED */ +# define __CONST_FUNCTION __attribute__((const)) +# define __NORETURN_FUNCTION __attribute__((noreturn)) +# define __INLINE_FUNCTION __attribute__((always_inline)) +# define __NON_NULL_ARGS(...) __attribute__((nonnull(__VA_ARGS__))) +# define __NON_NULL_ALL __attribute__((nonnull)) +# define __PURE_FUNCTION __attribute__((pure)) +# define __PRINTF_FUNCTION(...) __attribute__((format (printf, __VA_ARGS__))) +# define likely(x) __builtin_expect((x), 1) +# define unlikely(x) __builtin_expect((x), 0) + +# define UNREACHABLE_CODE assert(!"Unreachable code " __FILE__ ": " STR(__FUNCTION__)) + +/*! + * \brief Get a filename from a path + */ +static inline char *get_filename(char *path) +{ + size_t len = strlen(path); + + for (; len > 0 && path[len] != '/'; len--) + ; + + if (len == 0) + return path; + return path + len + 1; +} + +static inline bool is_ext(const char *path, const char *ext) +{ + size_t len = strlen(path); + + for (; len > 0 && path[len] != '.'; len--) + ; + + return strcmp(path + len, ext) == 0; +} + +/*! + * \brief Create a directory if non-existent from a path + */ +static inline bool create_dir(const char *path) +{ + DIR *fd = opendir(path); + + if (fd == NULL) + { + if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) + return false; + } + else + closedir(fd); + + return true; +} + +/*! + * \brief Zero-buffering kernel space copy of a file + */ +static inline bool fast_copy(const char *from, const char *to) +{ + int in_fd; + int out_fd; + int result; + + in_fd = open(from, O_RDONLY); + if (in_fd == -1) + return false; + + out_fd = creat(to, 0660); + if (out_fd == -1) + { + close(in_fd); + return false; + } + +#if defined(__APPLE__) || defined(__FreeBSD__) + result = fcopyfile(in_fd, out_fd, 0, COPYFILE_ALL); +#else + off_t bytesCopied = 0; + struct stat fileinfo = { 0 }; + + fstat(in_fd, &fileinfo); + result = sendfile(out_fd, in_fd, &bytesCopied, fileinfo.st_size); +#endif + + close(in_fd); + close(out_fd); + return result != -1; +} + +/*! + * \brief Check if a file is there or not + * + * \param[in] str File path + * + * \return true if the file exist, false otherwise + */ +static inline bool file_exist(const char *str) +{ + return access(str, O_RDONLY) == 0; +} + +#endif /* UTILS_H */