├── _clang-format ├── configure ├── compile_commands.json ├── meson_options.txt ├── subprojects ├── .gitignore └── a3.wrap ├── .gitignore ├── .clang-tidy ├── .gitmodules ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── shell.nix ├── .build.yml ├── src ├── config_runtime.h ├── event │ ├── handle.h │ ├── internal.h │ ├── handle.c │ ├── init.c │ └── mod.c ├── http │ ├── parse.h │ ├── response.h │ ├── request.h │ ├── headers.h │ ├── connection.h │ ├── headers.c │ ├── connection.c │ ├── request.c │ ├── types.c │ ├── types.h │ ├── parse.c │ └── response.c ├── file_handle.h ├── listen.h ├── timeout.h ├── forward.h ├── file.h ├── uri.h ├── config.h ├── listen.c ├── connection.h ├── event.h ├── timeout.c ├── file.c ├── uri.c ├── main.c └── connection.c ├── meson.build ├── test └── uri.cc ├── README.md └── LICENSE /_clang-format: -------------------------------------------------------------------------------- 1 | boilerplate/_clang-format -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | boilerplate/meson_configure -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | build/debug_clang/compile_commands.json -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('profile', type: 'boolean', value: 'false') 2 | -------------------------------------------------------------------------------- /subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !*.wrap 4 | highwayhash.wrap 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .clangd 2 | .cache 3 | build 4 | subprojects/* 5 | .envrc 6 | 7 | !subprojects/*.wrap 8 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-clang-analyzer-core.UndefinedBinaryOperatorResult,-clang-diagnostic-c99-designator' 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "boilerplate"] 2 | path = boilerplate 3 | url = https://github.com/3541/boilerplate.git 4 | branch = trunk 5 | -------------------------------------------------------------------------------- /subprojects/a3.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/3541/liba3.git 3 | push-url = git@github.com:3541/liba3.git 4 | revision = v0.3.5 5 | 6 | [provide] 7 | dependency_names = a3, a3_hash 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "gitsubmodule" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | 3 | pkgs.mkShell { 4 | packages = with pkgs; [ 5 | gcc 6 | gdb 7 | rr 8 | # NOTE: The placement of clang-tools /before/ llvmPackages.clang is critical. Otherwise, the 9 | # latter package will provide an un-wrapped `clangd`, which is unable to find system headers. 10 | (clang-tools.override { llvmPackages = llvmPackages_12; }) 11 | llvmPackages_latest.clang 12 | doxygen 13 | meson 14 | gtest 15 | pkg-config 16 | ninja 17 | liburing 18 | wrk 19 | valgrind 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /.build.yml: -------------------------------------------------------------------------------- 1 | image: alpine/edge 2 | packages: 3 | - cmake 4 | - ninja 5 | - liburing-dev 6 | - linux-headers 7 | - gtest-dev 8 | sources: 9 | - https://github.com/3541/short-circuit 10 | tasks: 11 | - submodules: | 12 | cd short-circuit 13 | git submodule update --init --recursive 14 | - setup: | 15 | mkdir -p short-circuit/build 16 | cmake -GNinja -S short-circuit -B short-circuit/build -DCMAKE_BUILD_TYPE=Release -DUSE_WERROR=1 17 | - build: | 18 | cmake --build short-circuit/build 19 | - test: | 20 | cmake --build short-circuit/build --target sc_check 21 | -------------------------------------------------------------------------------- /src/config_runtime.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: RUNTIME CONFIG -- Global configuration. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include 25 | 26 | typedef struct Config { 27 | A3CString web_root; 28 | int log_level; 29 | in_port_t listen_port; 30 | } Config; 31 | 32 | extern Config CONFIG; 33 | -------------------------------------------------------------------------------- /src/event/handle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT HANDLE -- Batch event handler. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include "forward.h" 25 | 26 | void event_queue_init(EventQueue*); 27 | void event_synth_deliver(EventQueue*, struct io_uring*, int32_t status); 28 | void event_handle_all(EventQueue*, struct io_uring*); 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - '**/*.c' 8 | - '**/*.h' 9 | - '**/*.cc' 10 | - '**/*.hh' 11 | - '.github/workflows/build.yml' 12 | - '**/meson.build' 13 | - 'subprojects/a3.wrap' 14 | - 'boilerplate' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | with: 23 | submodules: 'recursive' 24 | 25 | - uses: actions/checkout@v2 26 | with: 27 | repository: axboe/liburing 28 | path: liburing 29 | 30 | - name: Build and install liburing 31 | run: | 32 | cd ${{runner.workspace}}/short-circuit/liburing 33 | ./configure 34 | make 35 | sudo make install 36 | 37 | - name: Install dependencies 38 | run: pip install meson ninja 39 | 40 | - name: Configure 41 | run: meson setup --buildtype debug --werror -Db_sanitize=address,undefined build 42 | 43 | - name: Build 44 | run: meson compile -C build 45 | 46 | - name: Test 47 | run: meson test -C build 48 | -------------------------------------------------------------------------------- /src/http/parse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP PARSE -- Incremental HTTP request parser. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "forward.h" 23 | #include "http/types.h" 24 | 25 | HttpRequestStateResult http_request_first_line_parse(HttpRequest*, struct io_uring*); 26 | HttpRequestStateResult http_request_headers_add(HttpRequest*, struct io_uring*); 27 | HttpRequestStateResult http_request_headers_parse(HttpRequest*, struct io_uring*); 28 | -------------------------------------------------------------------------------- /src/file_handle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * FILE HANDLE -- The file handle type. See file.h and file.c 3 | * 4 | * Copyright (c) 2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | // Some weirdness on NixOS. 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "event.h" 30 | #include "forward.h" 31 | 32 | typedef struct FileHandle { 33 | A3_REFCOUNTED; 34 | EVENT_TARGET; 35 | EventQueue waiting; 36 | 37 | struct statx stat; 38 | 39 | A3CString path; 40 | fd file; 41 | int32_t flags; 42 | } FileHandle; 43 | -------------------------------------------------------------------------------- /src/listen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: LISTEN -- Socket listener. Keeps an accept event queued on a 3 | * given socket. 4 | * 5 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 6 | * 7 | * This program is free software: you can redistribute it and/or modify it under 8 | * the terms of the GNU Affero General Public License as published by the Free 9 | * Software Foundation, either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "connection.h" 29 | #include "forward.h" 30 | 31 | typedef struct Listener { 32 | fd socket; 33 | ConnectionTransport transport; 34 | bool accept_queued; 35 | } Listener; 36 | 37 | void listener_init(Listener*, in_port_t, ConnectionTransport); 38 | Connection* listener_accept_submit(Listener*, struct io_uring*); 39 | void listener_accept_all(Listener*, size_t n_listeners, struct io_uring*); 40 | -------------------------------------------------------------------------------- /src/http/response.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP RESPONSE -- HTTP response submission. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include "forward.h" 25 | #include "http/types.h" 26 | 27 | typedef struct HttpResponse { 28 | HttpContentType content_type; 29 | HttpTransferEncoding transfer_encodings; 30 | size_t body_sent; 31 | } HttpResponse; 32 | 33 | void http_response_init(HttpResponse*); 34 | void http_response_reset(HttpResponse*); 35 | 36 | #define HTTP_RESPONSE_CLOSE true 37 | #define HTTP_RESPONSE_ALLOW false 38 | bool http_response_error_submit(HttpResponse*, struct io_uring*, HttpStatus, bool close); 39 | 40 | bool http_response_file_submit(HttpResponse*, struct io_uring*); 41 | -------------------------------------------------------------------------------- /src/timeout.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: TIMEOUT -- Timeout queue using the uring. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "event.h" 28 | #include "forward.h" 29 | 30 | struct Timeout; 31 | typedef struct Timeout Timeout; 32 | 33 | typedef void (*TimeoutExec)(Timeout*, struct io_uring*); 34 | 35 | struct Timeout { 36 | Timespec threshold; 37 | TimeoutExec fire; 38 | A3LL queue_link; 39 | }; 40 | 41 | typedef struct TimeoutQueue { 42 | EVENT_TARGET; 43 | A3LL queue; 44 | } TimeoutQueue; 45 | 46 | void timeout_queue_init(TimeoutQueue*); 47 | bool timeout_schedule(TimeoutQueue*, Timeout*, struct io_uring*); 48 | bool timeout_is_scheduled(Timeout*); 49 | bool timeout_cancel(Timeout*); 50 | bool timeout_event_handle(TimeoutQueue*, struct io_uring*, int32_t status); 51 | -------------------------------------------------------------------------------- /src/http/request.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP REQUEST -- HTTP request handlers. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include "forward.h" 27 | #include "http/headers.h" 28 | #include "http/types.h" 29 | #include "uri.h" 30 | 31 | typedef struct HttpRequest { 32 | HttpHeaders headers; 33 | 34 | Uri target; 35 | A3CString host; 36 | A3String target_path; 37 | ssize_t content_length; 38 | HttpTransferEncoding transfer_encodings; 39 | } HttpRequest; 40 | 41 | HttpConnection* http_request_connection(HttpRequest*); 42 | HttpResponse* http_request_response(HttpRequest*); 43 | 44 | void http_request_init(HttpRequest*); 45 | void http_request_reset(HttpRequest*); 46 | 47 | bool http_request_handle(Connection*, struct io_uring*, bool success, int32_t status); 48 | -------------------------------------------------------------------------------- /src/forward.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: FORWARD -- Forward declarations. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | // liburing.h 25 | struct io_uring; 26 | struct io_uring_cqe; 27 | 28 | // netinet/in.h 29 | struct sockaddr_in; 30 | 31 | // connection.h 32 | struct Connection; 33 | typedef struct Connection Connection; 34 | 35 | // event.h 36 | struct Event; 37 | typedef struct Event Event; 38 | 39 | typedef struct A3SLL EventQueue; 40 | typedef struct A3SLL EventTarget; 41 | 42 | // http_connection.h 43 | struct HttpConnection; 44 | typedef struct HttpConnection HttpConnection; 45 | 46 | // http_request.h 47 | struct HttpRequest; 48 | typedef struct HttpRequest HttpRequest; 49 | 50 | // http_response.h 51 | struct HttpResponse; 52 | typedef struct HttpResponse HttpResponse; 53 | 54 | // listen.h 55 | struct Listener; 56 | typedef struct Listener Listener; 57 | 58 | // socket.h 59 | typedef int fd; 60 | 61 | // timeout.h 62 | typedef struct __kernel_timespec Timespec; 63 | -------------------------------------------------------------------------------- /src/file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: FILE -- Open file descriptor cache. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "forward.h" 28 | 29 | struct statx; 30 | 31 | typedef struct FileHandle FileHandle; 32 | typedef void (*FileHandleHandler)(EventTarget* target, struct io_uring*, void* ctx, bool success, 33 | int32_t status); 34 | 35 | #define FILE_STATX_MASK (STATX_TYPE | STATX_MTIME | STATX_INO | STATX_SIZE) 36 | 37 | void file_cache_init(void); 38 | FileHandle* file_open(EventTarget*, struct io_uring*, FileHandleHandler, void* ctx, A3CString path, 39 | int32_t flags); 40 | FileHandle* file_openat(EventTarget*, struct io_uring*, FileHandleHandler, void* ctx, 41 | FileHandle* dir, A3CString name, int32_t flags); 42 | fd file_handle_fd(FileHandle*); 43 | fd file_handle_fd_unchecked(FileHandle*); 44 | struct statx* file_handle_stat(FileHandle*); 45 | A3CString file_handle_path(FileHandle*); 46 | bool file_handle_waiting(FileHandle*); 47 | bool file_handle_close(FileHandle*, struct io_uring*); 48 | void file_cache_destroy(struct io_uring*); 49 | -------------------------------------------------------------------------------- /src/uri.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: URI -- URI parsing and decoding. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include 25 | 26 | #define URI_SCHEME_ENUM \ 27 | _SCHEME(URI_SCHEME_UNSPECIFIED, "") \ 28 | _SCHEME(URI_SCHEME_HTTP, "http") \ 29 | _SCHEME(URI_SCHEME_HTTPS, "https") \ 30 | _SCHEME(URI_SCHEME_INVALID, "") 31 | 32 | typedef enum UriScheme { 33 | #define _SCHEME(T, S) T, 34 | URI_SCHEME_ENUM 35 | #undef _SCHEME 36 | } UriScheme; 37 | 38 | typedef struct Uri { 39 | UriScheme scheme; 40 | A3String authority; 41 | A3String path; 42 | A3String query; 43 | A3String fragment; 44 | } Uri; 45 | 46 | typedef enum UriParseResult { 47 | URI_PARSE_ERROR, 48 | URI_PARSE_BAD_URI, 49 | URI_PARSE_TOO_LONG, 50 | URI_PARSE_SUCCESS 51 | } UriParseResult; 52 | 53 | UriParseResult uri_parse(Uri*, A3String); 54 | A3String uri_path_if_contained(Uri*, A3CString real_root); 55 | bool uri_is_initialized(Uri*); 56 | void uri_free(Uri*); 57 | -------------------------------------------------------------------------------- /src/event/internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT INTERNAL -- Private event handling types and utilities. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include "config.h" 25 | #include "event.h" 26 | #include "event/handle.h" 27 | #include "forward.h" 28 | 29 | // A caller can set an expected return code, either in the form of a status "class" described 30 | // here, or a precise value. 31 | typedef enum ExpectedStatus { 32 | EXPECTED_STATUS_NONE = INT32_MIN, 33 | EXPECTED_STATUS_NONNEGATIVE = INT32_MIN + 1, 34 | EXPECTED_STATUS_POSITIVE = INT32_MIN + 2, 35 | } ExpectedStatus; 36 | 37 | // An event to be submitted asynchronously. 38 | struct Event { 39 | // Event queues use an inline linked list. 40 | A3SLink queue_link; 41 | 42 | bool success; 43 | union { 44 | ExpectedStatus expected_status; 45 | int32_t expected_return; 46 | int32_t status; 47 | }; 48 | EventTarget* target; 49 | 50 | // On completion, the handler is called. The context variable can be anything, but will in many 51 | // cases be another callback to be invoked by a more general handler. See connection.c. 52 | EventHandler handler; 53 | void* handler_ctx; 54 | }; 55 | 56 | Event* event_from_link(A3SLink* link); 57 | void event_free(Event*); 58 | 59 | extern A3Pool* EVENT_POOL; 60 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: CONFIG -- Configurable settings. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | // This is the minimum kernel version which supports IORING_REGISTER_PROBE. 26 | #define MIN_KERNEL_VERSION_MAJOR 5 27 | #define MIN_KERNEL_VERSION_MINOR 6 28 | 29 | #define DEFAULT_LISTEN_PORT 8000 30 | #define LISTEN_BACKLOG 1024 31 | #define DEFAULT_WEB_ROOT A3_CS(".") 32 | #define INDEX_FILENAME A3_CS("index.html") 33 | 34 | #define PROFILE_DURATION 20 35 | 36 | #define EVENT_POOL_SIZE 7268 37 | 38 | #define FD_CACHE_SIZE 256 39 | 40 | #define URING_ENTRIES 2048 41 | #define URING_SQ_LEAVE_SPACE 10 42 | #define URING_SQE_RETRY_MAX 128 43 | 44 | #define CONNECTION_POOL_SIZE 1280 45 | 46 | #ifndef NDEBUG 47 | #define CONNECTION_TIMEOUT 6000 48 | #else 49 | #define CONNECTION_TIMEOUT 60 50 | #endif 51 | 52 | #define RECV_BUF_INITIAL_CAPACITY 2048 53 | #define RECV_BUF_MAX_CAPACITY 10240 54 | #define SEND_BUF_INITIAL_CAPACITY 2048 55 | #define SEND_BUF_MAX_CAPACITY 20480 56 | 57 | #define HTTP_ERROR_BODY_MAX_LENGTH 512 58 | #define HTTP_REQUEST_LINE_MAX_LENGTH 2048 59 | #define HTTP_REQUEST_HEADER_MAX_LENGTH 2048 60 | #define HTTP_REQUEST_HOST_MAX_LENGTH 512 61 | #define HTTP_REQUEST_URI_MAX_LENGTH 512 62 | #define HTTP_REQUEST_CONTENT_MAX_LENGTH 10240 63 | 64 | #define HTTP_TIME_FORMAT "%a, %d %b %Y %H:%M:%S GMT" 65 | #define HTTP_TIME_BUF_LENGTH 30 66 | #define HTTP_TIME_CACHE 13 67 | -------------------------------------------------------------------------------- /src/http/headers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP HEADERS -- HTTP header parsing and storage. 3 | * 4 | * Copyright (c) 2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "forward.h" 27 | #include "http/types.h" 28 | 29 | A3_HT_DEFINE_STRUCTS(A3CString, A3String) 30 | 31 | typedef struct HttpHeaders { 32 | A3_HT(A3CString, A3String) headers; 33 | } HttpHeaders; 34 | 35 | void http_headers_init(HttpHeaders*); 36 | void http_headers_destroy(HttpHeaders*); 37 | 38 | bool http_header_add(HttpHeaders*, A3CString name, A3CString value); 39 | A3String http_header_get(HttpHeaders*, A3CString name); 40 | 41 | HttpConnectionType http_header_connection(HttpHeaders*); 42 | HttpTransferEncoding http_header_transfer_encodings(HttpHeaders*); 43 | ssize_t http_header_content_length(HttpHeaders*); 44 | 45 | #define HTTP_HEADER_FOR_EACH_VALUE(HEADERS, NAME, VAL) \ 46 | A3String _header_val = http_header_get((HEADERS), (NAME)); \ 47 | A3Buffer _header_buf = { \ 48 | .data = _header_val, .tail = _header_val.len, .head = 0, .max_cap = _header_val.len \ 49 | }; \ 50 | for (A3CString VAL = A3_S_CONST(a3_buf_token_next(&_header_buf, A3_CS(","), A3_PRES_END_NO)); \ 51 | VAL.ptr && a3_buf_len(&_header_buf); \ 52 | VAL = A3_S_CONST(a3_buf_token_next(&_header_buf, A3_CS(","), A3_PRES_END_NO))) 53 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'short-circuit', 3 | ['c', 'cpp'], 4 | version: '0.1.0-alpha', 5 | default_options: [ 6 | 'c_std=c11', 7 | 8 | 'warning_level=2', 9 | 'buildtype=debug', 10 | 'b_ndebug=if-release', 11 | 'default_library=static' 12 | ] 13 | ) 14 | 15 | # TODO: Library rework and tests. 16 | 17 | c = meson.get_compiler('c') 18 | 19 | sc_common_flags = [ 20 | '-DSC_VERSION="' + meson.project_version() + '"', 21 | '-Dtypeof=__typeof__', 22 | '-D_XOPEN_SOURCE=700' 23 | ] 24 | sc_c_flags = [] 25 | 26 | if get_option('profile') 27 | sc_common_flags += '-DPROFILE' 28 | endif 29 | 30 | # NOTE: If this ever goes cross-platform, this will need to support other syntaxes. 31 | sc_flags_wanted = ['-fstack-protector', '-fstack-clash-protection'] 32 | add_project_arguments(c.get_supported_arguments(sc_flags_wanted), language: 'c') 33 | 34 | sc_warnings_wanted = [ 35 | '-Wdisabled-optimization', '-Wduplicated-branches', '-Wduplicated-cond', '-Wfloat-equal', 36 | '-Wformat-nonliteral', '-Wformat-security', '-Wlogical-op', '-Wmissing-declarations', 37 | '-Wmissing-include-dirs', '-Wnull-dereference', '-Wpacked', '-Wshadow', '-Wstack-protector', 38 | '-Wundef', '-Wcast-align', '-Wbad-function-cast', '-Wimplicit', '-Wmissing-prototypes', 39 | '-Wnested-externs', '-Wstrict-prototypes' 40 | ] 41 | 42 | sc_c_flags += c.get_supported_arguments(sc_warnings_wanted) 43 | 44 | if (c.get_id() != 'gcc' or not c.version().startswith('9')) 45 | # -Wconversion is too aggressive on GCC <= 9. 46 | sc_c_flags += c.get_supported_arguments(['-Wconversion']) 47 | endif 48 | 49 | sc_include = include_directories('src') 50 | sc_src = files( 51 | [ 52 | 'src/main.c', 53 | 54 | 'src/event/init.c', 55 | 'src/event/mod.c', 56 | 'src/event/handle.c', 57 | 'src/file.c', 58 | 'src/connection.c', 59 | 'src/http/connection.c', 60 | 'src/http/headers.c', 61 | 'src/http/parse.c', 62 | 'src/http/request.c', 63 | 'src/http/response.c', 64 | 'src/http/types.c', 65 | 'src/listen.c', 66 | 'src/timeout.c', 67 | 'src/uri.c' 68 | ] 69 | ) 70 | 71 | liburing = dependency('liburing') 72 | a3 = dependency('a3', fallback: ['a3', 'a3_dep']) 73 | a3_hash = dependency('a3_hash', fallback: ['a3', 'a3_hash_dep']) 74 | 75 | sc = executable( 76 | 'sc', 77 | sc_src, 78 | include_directories: sc_include, 79 | dependencies: [liburing, a3, a3_hash], 80 | c_args: sc_c_flags + sc_common_flags, 81 | gnu_symbol_visibility: 'hidden', 82 | build_by_default: true 83 | ) 84 | -------------------------------------------------------------------------------- /src/http/connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP CONNECTION -- HTTP-specific layer on top of a connection. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "../connection.h" 28 | #include "file.h" 29 | #include "http/request.h" 30 | #include "http/response.h" 31 | #include "http/types.h" 32 | 33 | typedef enum HttpConnectionState { 34 | HTTP_CONNECTION_INIT, 35 | HTTP_CONNECTION_PARSED_FIRST_LINE, 36 | HTTP_CONNECTION_ADDED_HEADERS, 37 | HTTP_CONNECTION_PARSED_HEADERS, 38 | HTTP_CONNECTION_OPENING_FILE, 39 | HTTP_CONNECTION_RESPONDING, 40 | HTTP_CONNECTION_CLOSING, 41 | } HttpConnectionState; 42 | 43 | typedef struct HttpConnection { 44 | Connection conn; 45 | 46 | HttpConnectionState state; 47 | HttpVersion version; 48 | HttpMethod method; 49 | HttpConnectionType connection_type; 50 | 51 | HttpRequest request; 52 | HttpResponse response; 53 | 54 | FileHandle* target_file; 55 | } HttpConnection; 56 | 57 | void http_connection_pool_init(void); 58 | HttpConnection* http_connection_new(void); 59 | void http_connection_free(HttpConnection*, struct io_uring*); 60 | void http_connection_pool_free(void); 61 | 62 | bool http_connection_init(HttpConnection*); 63 | bool http_connection_close_submit(HttpConnection*, struct io_uring*); 64 | bool http_connection_reset(HttpConnection*, struct io_uring*); 65 | 66 | A3_ALWAYS_INLINE bool http_connection_keep_alive(HttpConnection* conn) { 67 | if (!conn) 68 | return false; 69 | return conn->connection_type == HTTP_CONNECTION_TYPE_KEEP_ALIVE; 70 | } 71 | 72 | A3_ALWAYS_INLINE HttpConnection* connection_http(Connection* conn) { 73 | assert(conn); 74 | 75 | return A3_CONTAINER_OF(conn, HttpConnection, conn); 76 | } 77 | -------------------------------------------------------------------------------- /src/listen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: LISTEN -- Socket listener. Keeps an accept event queued on a 3 | * given socket. 4 | * 5 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 6 | * 7 | * This program is free software: you can redistribute it and/or modify it under 8 | * the terms of the GNU Affero General Public License as published by the Free 9 | * Software Foundation, either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include "listen.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include "config.h" 32 | #include "connection.h" 33 | #include "forward.h" 34 | #include "http/request.h" 35 | 36 | static fd socket_listen(in_port_t port) { 37 | fd ret; 38 | A3_UNWRAPS(ret, socket(AF_INET, SOCK_STREAM, 0)); 39 | 40 | const int enable = 1; 41 | A3_UNWRAPSD(setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable))); 42 | A3_UNWRAPSD(setsockopt(ret, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable))); 43 | 44 | struct sockaddr_in addr; 45 | memset(&addr, 0, sizeof(addr)); 46 | addr.sin_family = AF_INET; 47 | addr.sin_port = htons(port); 48 | addr.sin_addr.s_addr = INADDR_ANY; 49 | 50 | A3_UNWRAPSD(bind(ret, (struct sockaddr*)&addr, sizeof(addr))); 51 | A3_UNWRAPSD(listen(ret, LISTEN_BACKLOG)); 52 | 53 | return ret; 54 | } 55 | 56 | void listener_init(Listener* listener, in_port_t port, ConnectionTransport transport) { 57 | assert(listener); 58 | 59 | listener->socket = socket_listen(port); 60 | listener->accept_queued = false; 61 | listener->transport = transport; 62 | } 63 | 64 | Connection* listener_accept_submit(Listener* listener, struct io_uring* uring) { 65 | assert(listener); 66 | assert(!listener->accept_queued); 67 | assert(uring); 68 | 69 | // TODO: Generic over connection types. 70 | Connection* ret = connection_accept_submit(listener, uring, http_request_handle); 71 | if (ret) 72 | listener->accept_queued = true; 73 | 74 | return ret; 75 | } 76 | 77 | void listener_accept_all(Listener* listeners, size_t n_listeners, struct io_uring* uring) { 78 | assert(listeners); 79 | assert(uring); 80 | 81 | for (size_t i = 0; i < n_listeners; i++) 82 | if (!listeners[i].accept_queued) 83 | listener_accept_submit(&listeners[i], uring); 84 | } 85 | -------------------------------------------------------------------------------- /test/uri.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "uri.h" 6 | 7 | class UriTest : public ::testing::Test { 8 | protected: 9 | Uri uri {}; 10 | 11 | void TearDown() override { 12 | if (uri_is_initialized(&uri)) 13 | uri_free(&uri); 14 | } 15 | }; 16 | 17 | TEST_F(UriTest, parse_trivial) { 18 | A3String s = a3_string_clone(A3_CS("/test.txt")); 19 | 20 | EXPECT_EQ(uri_parse(&uri, s), URI_PARSE_SUCCESS); 21 | 22 | EXPECT_EQ(uri.scheme, URI_SCHEME_UNSPECIFIED); 23 | EXPECT_FALSE(uri.authority.ptr); 24 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.path), A3_CS("/test.txt")), 0); 25 | EXPECT_FALSE(uri.query.ptr); 26 | EXPECT_FALSE(uri.fragment.ptr); 27 | 28 | a3_string_free(&s); 29 | } 30 | 31 | TEST_F(UriTest, parse_scheme_authority) { 32 | A3String s1 = a3_string_clone(A3_CS("http://example.com/test.txt")); 33 | A3String s2 = a3_string_clone(A3_CS("https://example.com/asdf.txt")); 34 | 35 | EXPECT_EQ(uri_parse(&uri, s1), URI_PARSE_SUCCESS); 36 | EXPECT_EQ(uri.scheme, URI_SCHEME_HTTP); 37 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.authority), A3_CS("example.com")), 0); 38 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.path), A3_CS("/test.txt")), 0); 39 | EXPECT_FALSE(uri.query.ptr); 40 | EXPECT_FALSE(uri.fragment.ptr); 41 | uri_free(&uri); 42 | 43 | EXPECT_EQ(uri_parse(&uri, s2), URI_PARSE_SUCCESS); 44 | EXPECT_EQ(uri.scheme, URI_SCHEME_HTTPS); 45 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.authority), A3_CS("example.com")), 0); 46 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.path), A3_CS("/asdf.txt")), 0); 47 | EXPECT_FALSE(uri.query.ptr); 48 | EXPECT_FALSE(uri.fragment.ptr); 49 | 50 | a3_string_free(&s1); 51 | a3_string_free(&s2); 52 | } 53 | 54 | TEST_F(UriTest, parse_components) { 55 | A3String s = a3_string_clone(A3_CS("http://example.com/test.txt?query=1#fragment")); 56 | 57 | EXPECT_EQ(uri_parse(&uri, s), URI_PARSE_SUCCESS); 58 | EXPECT_EQ(uri.scheme, URI_SCHEME_HTTP); 59 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.authority), A3_CS("example.com")), 0); 60 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.path), A3_CS("/test.txt")), 0); 61 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.query), A3_CS("query=1")), 0); 62 | EXPECT_EQ(a3_string_cmp(A3_S_CONST(uri.fragment), A3_CS("fragment")), 0); 63 | 64 | a3_string_free(&s); 65 | } 66 | 67 | TEST_F(UriTest, path_contained) { 68 | uri = { URI_SCHEME_HTTP, A3_S_NULL, a3_string_clone(A3_CS("/index.html")), A3_S_NULL, 69 | A3_S_NULL }; 70 | 71 | A3String path = uri_path_if_contained(&uri, A3_CS("/var/www")); 72 | EXPECT_TRUE(path.ptr); 73 | EXPECT_EQ(a3_string_cmp(path, A3_CS("/var/www/index.html")), 0); 74 | uri_free(&uri); 75 | a3_string_free(&path); 76 | 77 | uri = { URI_SCHEME_HTTP, A3_S_NULL, a3_string_clone(A3_CS("/../../../etc/passwd")), A3_S_NULL, 78 | A3_S_NULL }; 79 | path = uri_path_if_contained(&uri, A3_CS("/var/www")); 80 | EXPECT_FALSE(path.ptr); 81 | } 82 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: CONNECTION -- Abstract connection on top of the event 3 | * interface. 4 | * 5 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 6 | * 7 | * This program is free software: you can redistribute it and/or modify it under 8 | * the terms of the GNU Affero General Public License as published by the Free 9 | * Software Foundation, either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "event.h" 33 | #include "forward.h" 34 | #include "timeout.h" 35 | 36 | typedef enum { SPLICE_IN, SPLICE_OUT } SpliceDirection; 37 | 38 | typedef bool (*ConnectionHandler)(Connection*, struct io_uring*, bool success, int32_t status); 39 | typedef bool (*ConnectionSpliceHandler)(Connection*, struct io_uring*, SpliceDirection, 40 | bool success, int32_t status); 41 | 42 | typedef enum ConnectionTransport { TRANSPORT_PLAIN, TRANSPORT_TLS } ConnectionTransport; 43 | 44 | typedef struct Connection { 45 | EVENT_TARGET; 46 | 47 | A3Buffer recv_buf; 48 | A3Buffer send_buf; 49 | 50 | Timeout timeout; 51 | 52 | Listener* listener; 53 | 54 | struct sockaddr_in client_addr; 55 | socklen_t addr_len; 56 | fd socket; 57 | fd pipe[2]; 58 | 59 | ConnectionTransport transport; 60 | } Connection; 61 | 62 | void connection_timeout_init(void); 63 | 64 | bool connection_init(Connection*); 65 | bool connection_reset(Connection*, struct io_uring*); 66 | 67 | Connection* connection_accept_submit(Listener*, struct io_uring*, ConnectionHandler); 68 | bool connection_recv_submit(Connection*, struct io_uring*, ConnectionHandler); 69 | bool connection_send_submit(Connection*, struct io_uring*, ConnectionHandler, uint32_t send_flags, 70 | uint8_t sqe_flags); 71 | bool connection_splice_submit(Connection*, struct io_uring*, ConnectionSpliceHandler, 72 | ConnectionHandler, fd src, size_t file_offset, size_t len, 73 | uint8_t sqe_flags); 74 | bool connection_splice_retry(Connection*, struct io_uring*, ConnectionSpliceHandler, 75 | ConnectionHandler, fd src, size_t in_buf, size_t file_offset, 76 | size_t remaining, uint8_t sqe_flags); 77 | bool connection_close_submit(Connection*, struct io_uring*, ConnectionHandler); 78 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT -- Event submission. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "forward.h" 33 | 34 | #define EVENT_FALLBACK_ALLOW true 35 | #define EVENT_FALLBACK_FORBID false 36 | 37 | #define EVENT_NO_QUEUE false 38 | 39 | typedef struct Event Event; 40 | 41 | typedef void (*EventHandler)(EventTarget*, struct io_uring*, void* ctx, bool success, 42 | int32_t status); 43 | 44 | // Include this as a member to make an object a viable event target. 45 | #define EVENT_TARGET EventTarget _events_queued 46 | #define EVT(O) (&(O)->_events_queued) 47 | #define EVT_PTR(T, TY) A3_CONTAINER_OF((T), TY, _events_queued) 48 | 49 | struct io_uring event_init(void); 50 | 51 | bool event_accept_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd socket, 52 | struct sockaddr_in* out_client_addr, socklen_t* inout_addr_len); 53 | bool event_close_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd file, 54 | uint32_t sqe_flags, bool fallback_sync); 55 | bool event_openat_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd dir, 56 | A3CString path, int32_t open_flags, mode_t mode); 57 | bool event_read_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd file, 58 | A3String out_data, size_t nbytes, off_t offset, uint32_t sqe_flags); 59 | bool event_recv_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd socket, 60 | A3String out_data); 61 | bool event_send_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd socket, 62 | A3CString data, uint32_t send_flags, uint32_t sqe_flags); 63 | bool event_splice_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, fd in, 64 | uint64_t off_in, fd out, size_t len, uint32_t splice_flags, 65 | uint32_t sqe_flags); 66 | bool event_stat_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, A3CString path, 67 | uint32_t field_mask, struct statx*, uint32_t sqe_flags); 68 | bool event_timeout_submit(EventTarget*, struct io_uring*, EventHandler, void* ctx, Timespec*, 69 | uint32_t timeout_flags); 70 | 71 | // Synthesize an event. This Event is _not_ queued, but is useful for situations 72 | // in which one completion from the uring must notify multiple targets. See 73 | // file.c for an example of usage. 74 | Event* event_create(EventTarget*, EventHandler, void* ctx); 75 | 76 | bool event_cancel_all(EventTarget*); 77 | 78 | A3SLink* event_queue_link(Event*); 79 | -------------------------------------------------------------------------------- /src/timeout.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: TIMEOUT -- Timeout queue using the uring. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "timeout.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "event.h" 32 | #include "forward.h" 33 | 34 | #include 35 | 36 | // Compare a kernel timespec and libc timespec. 37 | static ssize_t timespec_compare(Timespec lhs, struct timespec rhs) { 38 | return (lhs.tv_sec != rhs.tv_sec) ? lhs.tv_sec - rhs.tv_sec : lhs.tv_nsec - rhs.tv_nsec; 39 | } 40 | 41 | static bool timeout_schedule_next(TimeoutQueue*, struct io_uring*); 42 | 43 | void timeout_queue_init(TimeoutQueue* timeouts) { 44 | assert(timeouts); 45 | a3_ll_init(&timeouts->queue); 46 | } 47 | 48 | static void timeout_handle(EventTarget* target, struct io_uring* uring, void* ctx, bool success, 49 | int32_t status) { 50 | assert(target); 51 | assert(uring); 52 | assert(status > 0 || status == -ETIME); 53 | (void)ctx; 54 | (void)success; 55 | (void)status; 56 | 57 | TimeoutQueue* timeouts = EVT_PTR(target, TimeoutQueue); 58 | 59 | A3_TRACE("Timeout firing."); 60 | 61 | struct timespec current; 62 | A3_UNWRAPSD(clock_gettime(CLOCK_MONOTONIC, ¤t)); 63 | 64 | A3LL* peek; 65 | while ((peek = a3_ll_peek(&timeouts->queue)) && 66 | timespec_compare(A3_CONTAINER_OF(peek, Timeout, queue_link)->threshold, current) <= 0) { 67 | Timeout* timeout = A3_CONTAINER_OF(a3_ll_dequeue(&timeouts->queue), Timeout, queue_link); 68 | timeout->fire(timeout, uring); 69 | } 70 | 71 | if (!timeout_schedule_next(timeouts, uring)) 72 | A3_ERROR("Unable to schedule timeout."); 73 | } 74 | 75 | static bool timeout_schedule_next(TimeoutQueue* timeouts, struct io_uring* uring) { 76 | assert(timeouts); 77 | assert(uring); 78 | 79 | A3LL* next = a3_ll_peek(&timeouts->queue); 80 | if (!next) 81 | return true; 82 | 83 | return event_timeout_submit(EVT(timeouts), uring, timeout_handle, NULL, 84 | &A3_CONTAINER_OF(next, Timeout, queue_link)->threshold, 85 | IORING_TIMEOUT_ABS); 86 | } 87 | 88 | bool timeout_schedule(TimeoutQueue* timeouts, Timeout* timeout, struct io_uring* uring) { 89 | assert(timeouts); 90 | assert(timeout); 91 | assert(uring); 92 | assert(!timeout_is_scheduled(timeout)); 93 | 94 | a3_ll_enqueue(&timeouts->queue, &timeout->queue_link); 95 | if (a3_ll_peek(&timeouts->queue) == &timeout->queue_link) 96 | return timeout_schedule_next(timeouts, uring); 97 | 98 | return true; 99 | } 100 | 101 | bool timeout_is_scheduled(Timeout* timeout) { 102 | assert(timeout); 103 | 104 | return timeout->queue_link.next && timeout->queue_link.prev; 105 | } 106 | 107 | bool timeout_cancel(Timeout* timeout) { 108 | assert(timeout); 109 | assert(timeout_is_scheduled(timeout)); 110 | 111 | // There is no need to actually fiddle with events here. 112 | a3_ll_remove(&timeout->queue_link); 113 | 114 | return true; 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Short Circuit 2 | 3 | [![.github/workflows/build.yml](https://github.com/3541/short-circuit/actions/workflows/build.yml/badge.svg)](https://github.com/3541/short-circuit/actions/workflows/build.yml) 4 | 5 | A lightweight and performant web server for Linux, built on top of io_uring. Capable of ~~60,000~~ 6 | ~~80,000~~ ~~120,000~~ ~~135,000 requests per second on static files~~. Suffice it to say, it is 7 | fast, and getting faster. Actual, more rigorous benchmarks will come once the project gets closer to 8 | completion. 9 | 10 | [io_uring](https://kernel.dk/io_uring.pdf?source=techstories.org) is a new asynchronous I/O system 11 | on Linux which offers a significant performance advantage (and, subjectively, a much nicer 12 | interface) over its competitors (POSIX `aio`, `epoll`, etc...). 13 | 14 | _NOTE_: Most development lately has been on the [co branch](https://github.com/3541/short-circuit/tree/co), 15 | which converts the server to use a coroutine-based IO model. It isn't yet up to feature parity with 16 | `trunk`, but it is likely to supersede the main branch eventually. 17 | 18 | ## Building 19 | Dependencies: 20 | * A C compiler supporting C11. 21 | * Meson 0.55 or later. 22 | * [liburing](https://github.com/axboe/liburing). 23 | * Linux 5.7 or later. 24 | 25 | To build, first ensure all submodules have been downloaded (`git submodule update --init 26 | --recursive`), and then run `meson setup ` to configure the build system in `BUILDDIR`. 27 | Alternatively, a script to generate various build configurations is provided (`./configure`). 28 | 29 | Some versions of Meson (before 0.56) may refuse to pull in transitive dependencies, and produce error messages of 30 | the form `WARNING: Dependency highwayhash not found but it is available in a sub-subproject.`. If 31 | this occurs, simply run the command `meson wrap promote 32 | subprojects/a3/subprojects/highwayhash.wrap`. 33 | 34 | After, run `meson compile -C ` to build the project. This produces a binary `sc`, which 35 | can be run directly. By default, the server listens on port `8000`. `sc --help` will show the 36 | available options and parameters. 37 | 38 | Note: on most Linux distributions, you may see warnings about the locked memory and open file 39 | resource limits. See [here](#queue-size) for more information. 40 | 41 | ## Notes and disclaimer 42 | This is _very_ new software, which is nowhere near feature complete, let alone stable. There are 43 | critical bugs, known and unknown. At this time it should not under any circumstances be used in 44 | production or for anything which matters even a little bit. 45 | 46 | ### Queue size 47 | On most distributions, the locked memory limit is too low to open an `io_uring` queue of the size 48 | that Short Circuit does by default. This can be fixed either (preferably) by increasing this limit 49 | (usually in `/etc/security/limits.conf`), or by lowering `URING_ENTRIES` in `config.h`. It probably 50 | needs to be at least halved to work with the default limit. 51 | 52 | ### Open file limit 53 | To serve static files, Short Circuit uses `IO_URING_OP_PIPE`. For performance purposes, these pipes 54 | are cached across requests. As a result, under high load (particularly when it is generated by many 55 | simultaneous connections), many systems will hit the open file limit. This can be fixed by raising 56 | the open file limit, or by decreasing `CONNECTION_POOL_SIZE` in `config.h`. The former can be done 57 | in `/etc/security/limits.conf`. A good number is a bit over three times the maximum expected number 58 | of concurrent connections, since each connection requires an open file for the socket and two for 59 | the pipe. 60 | 61 | ### `ulimit`s 62 | Both of these only require that the hard limit be changed, as Short Circuit will automatically raise 63 | its own soft limits at runtime. 64 | 65 | ## Licensing 66 | 67 | Short Circuit is licensed under the GNU Affero GPL, the terms of which are described 68 | [here](https://github.com/3541/short-circuit/blob/trunk/LICENSE). 69 | 70 | Short Circuit depends on the following other projects: 71 | 72 | ### liba3 73 | [liba3](https://github.com/3541/liba3) is licensed under the [3-clause BSD 74 | license](https://github.com/3541/liba3/blob/trunk/LICENSE). 75 | 76 | `liba3` also links with and otherwise uses other software projects, as detailed 77 | [here](https://github.com/3541/liba3/blob/trunk/README.md#licensing). 78 | 79 | ### liburing 80 | [liburing](https://github.com/axboe/liburing) is licensed under the [MIT 81 | license](https://github.com/axboe/liburing/blob/master/LICENSE). 82 | -------------------------------------------------------------------------------- /src/event/handle.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT HANDLE -- Batch event handler. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "event/handle.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "config.h" 31 | #include "connection.h" 32 | #include "event.h" 33 | #include "event/internal.h" 34 | #include "file.h" 35 | #include "file_handle.h" 36 | #include "timeout.h" 37 | 38 | #include 39 | 40 | void event_queue_init(EventQueue* queue) { 41 | assert(queue); 42 | 43 | a3_sll_init(queue); 44 | } 45 | 46 | static void event_handle(Event* event, struct io_uring* uring) { 47 | assert(event); 48 | assert(uring); 49 | 50 | if (event->handler) 51 | event->handler(event->target, uring, event->handler_ctx, event->success, event->status); 52 | event_free(event); 53 | } 54 | 55 | // Handle all events pending on the queue. 56 | static void event_queue_handle_all(EventQueue* queue, struct io_uring* uring) { 57 | assert(queue); 58 | assert(uring); 59 | 60 | if (io_uring_sq_space_left(uring) <= URING_SQ_LEAVE_SPACE) 61 | return; 62 | 63 | Event* event = event_from_link(a3_sll_peek(queue)); 64 | while (event && io_uring_sq_space_left(uring) > URING_SQ_LEAVE_SPACE) { 65 | event = event_from_link(a3_sll_dequeue(queue)); 66 | event_handle(event, uring); 67 | 68 | event = event_from_link(a3_sll_peek(queue)); 69 | } 70 | } 71 | 72 | // Deliver a queue of synthetic events with a given status. 73 | void event_synth_deliver(EventQueue* queue, struct io_uring* uring, int32_t status) { 74 | assert(queue); 75 | assert(uring); 76 | 77 | A3_SLL_FOR_EACH(Event, event, queue, queue_link) { event->status = status; } 78 | 79 | event_queue_handle_all(queue, uring); 80 | } 81 | 82 | // Dequeue all CQEs and handle as many as possible. 83 | void event_handle_all(EventQueue* queue, struct io_uring* uring) { 84 | assert(queue); 85 | assert(uring); 86 | 87 | struct io_uring_cqe* cqe; 88 | for (io_uring_peek_cqe(uring, &cqe); cqe; io_uring_peek_cqe(uring, &cqe)) { 89 | Event* event = io_uring_cqe_get_data(cqe); 90 | int status = cqe->res; 91 | 92 | // Remove from the CQ. 93 | io_uring_cqe_seen(uring, cqe); 94 | 95 | if (!event) { 96 | if (status < 0) 97 | A3_ERRNO(-status, "event without target failed"); 98 | continue; 99 | } 100 | 101 | EventTarget* target = event->target; 102 | // Remove from the in-flight list. 103 | a3_sll_remove(target, &event->queue_link); 104 | 105 | if (!event->target) { 106 | // Canceled. 107 | event_free(event); 108 | continue; 109 | } 110 | 111 | switch (event->expected_status) { 112 | case EXPECTED_STATUS_NONE: 113 | break; 114 | case EXPECTED_STATUS_NONNEGATIVE: 115 | event->success = status >= 0; 116 | break; 117 | case EXPECTED_STATUS_POSITIVE: 118 | event->success = status > 0; 119 | break; 120 | default: 121 | event->success = status == event->expected_return; 122 | } 123 | event->status = status; 124 | 125 | if (!event->success && event->status == -ECANCELED) { 126 | event_free(event); 127 | continue; 128 | } 129 | 130 | // Add to the to-process queue. 131 | a3_sll_enqueue(queue, &event->queue_link); 132 | } 133 | 134 | // Now, handle as many of the queued CQEs as possible without filling the 135 | // SQ. 136 | event_queue_handle_all(queue, uring); 137 | } 138 | -------------------------------------------------------------------------------- /src/event/init.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT INIT -- Event system initialization. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "config.h" 27 | #include "event.h" 28 | #include "internal.h" 29 | 30 | // Check that the kernel is recent enough to support io_uring and 31 | // io_uring_probe. 32 | static void event_check_kver(void) { 33 | struct utsname info; 34 | A3_UNWRAPSD(uname(&info)); 35 | 36 | char* release = strdup(info.release); 37 | 38 | long version_major = strtol(strtok(info.release, "."), NULL, 10); 39 | long version_minor = strtol(strtok(NULL, "."), NULL, 10); 40 | 41 | if (version_major < MIN_KERNEL_VERSION_MAJOR || 42 | (version_major == MIN_KERNEL_VERSION_MAJOR && version_minor < MIN_KERNEL_VERSION_MINOR)) 43 | A3_PANIC_FMT("Kernel version %s is not supported. At least %d.%d is required.", release, 44 | MIN_KERNEL_VERSION_MAJOR, MIN_KERNEL_VERSION_MINOR); 45 | 46 | free(release); 47 | } 48 | 49 | #define REQUIRE_OP(P, OP) \ 50 | do { \ 51 | if (!io_uring_opcode_supported(P, OP)) \ 52 | A3_PANIC_FMT("Required io_uring op %s is not supported by the kernel.", #OP); \ 53 | } while (0) 54 | 55 | // All ops used should be checked here. 56 | static void event_check_ops(struct io_uring* uring) { 57 | struct io_uring_probe* probe = io_uring_get_probe_ring(uring); 58 | 59 | REQUIRE_OP(probe, IORING_OP_ACCEPT); 60 | REQUIRE_OP(probe, IORING_OP_ASYNC_CANCEL); 61 | REQUIRE_OP(probe, IORING_OP_CLOSE); 62 | REQUIRE_OP(probe, IORING_OP_READ); 63 | REQUIRE_OP(probe, IORING_OP_RECV); 64 | REQUIRE_OP(probe, IORING_OP_SEND); 65 | REQUIRE_OP(probe, IORING_OP_SPLICE); 66 | REQUIRE_OP(probe, IORING_OP_TIMEOUT); 67 | 68 | free(probe); 69 | } 70 | 71 | // Set the given resource to its hard limit and return the new state. 72 | static struct rlimit rlimit_maximize(int resource) { 73 | struct rlimit lim; 74 | 75 | A3_UNWRAPSD(getrlimit(resource, &lim)); 76 | lim.rlim_cur = lim.rlim_max; 77 | A3_UNWRAPSD(setrlimit(resource, &lim)); 78 | return lim; 79 | } 80 | 81 | // Check and set resource limits. 82 | static void event_limits_init(void) { 83 | struct rlimit lim_memlock = rlimit_maximize(RLIMIT_MEMLOCK); 84 | // This is a crude check, but opening the queue will almost certainly fail 85 | // if the limit is this low. 86 | if (lim_memlock.rlim_cur <= 96 * URING_ENTRIES) 87 | A3_WARN_F("The memlock limit (%d) is too low. The queue will probably " 88 | "fail to open. Either raise the limit or lower `URING_ENTRIES`.", 89 | lim_memlock.rlim_cur); 90 | 91 | struct rlimit lim_nofile = rlimit_maximize(RLIMIT_NOFILE); 92 | if (lim_nofile.rlim_cur <= CONNECTION_POOL_SIZE * 3) 93 | A3_WARN_F("The open file limit (%d) is low. Large numbers of concurrent " 94 | "connections will probably cause \"too many open files\" errors.", 95 | lim_nofile.rlim_cur); 96 | } 97 | 98 | struct io_uring event_init() { 99 | event_check_kver(); 100 | event_limits_init(); 101 | 102 | struct io_uring ret; 103 | 104 | bool opened = false; 105 | for (size_t queue_size = URING_ENTRIES; queue_size >= 512; queue_size /= 2) { 106 | if (!io_uring_queue_init(URING_ENTRIES, &ret, 0)) { 107 | opened = true; 108 | break; 109 | } 110 | } 111 | if (!opened) 112 | A3_PANIC("Unable to open queue. The memlock limit is probably too low."); 113 | 114 | event_check_ops(&ret); 115 | EVENT_POOL = A3_POOL_OF(Event, EVENT_POOL_SIZE, A3_POOL_ZERO_BLOCKS, NULL, NULL); 116 | 117 | return ret; 118 | } 119 | -------------------------------------------------------------------------------- /src/http/headers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP HEADERS -- HTTP header parsing and storage. 3 | * 4 | * Copyright (c) 2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "http/headers.h" 21 | #include "http/types.h" 22 | 23 | #include 24 | #include 25 | 26 | A3_HT_DECLARE_METHODS(A3CString, A3String) 27 | A3_HT_DEFINE_METHODS(A3CString, A3String, a3_string_cptr, a3_string_len, a3_string_cmp) 28 | 29 | static bool http_headers_combine(A3String* current_value, A3String new_value) { 30 | assert(current_value); 31 | 32 | A3String combined_value = a3_string_alloc(current_value->len + new_value.len + 1); 33 | a3_string_concat(combined_value, 3, *current_value, A3_CS(","), new_value); 34 | 35 | a3_string_free(current_value); 36 | a3_string_free(&new_value); 37 | *current_value = combined_value; 38 | 39 | return true; 40 | } 41 | 42 | void http_headers_init(HttpHeaders* headers) { 43 | assert(headers); 44 | 45 | A3_HT_INIT(A3CString, A3String)(&headers->headers, A3_HT_NO_HASH_KEY, A3_HT_ALLOW_GROWTH); 46 | A3_HT_SET_DUPLICATE_CB(A3CString, A3String)(&headers->headers, http_headers_combine); 47 | } 48 | 49 | void http_headers_destroy(HttpHeaders* headers) { 50 | assert(headers); 51 | 52 | A3_HT_FOR_EACH(A3CString, A3String, &headers->headers, key, value) { 53 | a3_string_free((A3String*)key); 54 | a3_string_free((A3String*)value); 55 | } 56 | 57 | A3_HT_DESTROY(A3CString, A3String)(&headers->headers); 58 | } 59 | 60 | bool http_header_add(HttpHeaders* headers, A3CString name, A3CString value) { 61 | assert(headers); 62 | assert(name.ptr); 63 | assert(value.ptr); 64 | 65 | A3String key = a3_string_to_lowercase(name); 66 | 67 | return A3_HT_INSERT(A3CString, A3String)(&headers->headers, A3_S_CONST(key), 68 | a3_string_clone(value)); 69 | } 70 | 71 | A3String http_header_get(HttpHeaders* headers, A3CString name) { 72 | assert(headers); 73 | assert(name.ptr); 74 | 75 | A3String key = a3_string_to_lowercase(name); 76 | 77 | A3String* ret = A3_HT_FIND(A3CString, A3String)(&headers->headers, A3_S_CONST(key)); 78 | a3_string_free(&key); 79 | if (!ret) 80 | return A3_S_NULL; 81 | return *ret; 82 | } 83 | 84 | HttpConnectionType http_header_connection(HttpHeaders* headers) { 85 | assert(headers); 86 | 87 | A3String* connection = A3_HT_FIND(A3CString, A3String)(&headers->headers, A3_CS("connection")); 88 | if (!connection) 89 | return HTTP_CONNECTION_TYPE_UNSPECIFIED; 90 | 91 | A3CString conn = A3_S_CONST(*connection); 92 | if (a3_string_cmpi(conn, A3_CS("Keep-Alive")) == 0) 93 | return HTTP_CONNECTION_TYPE_KEEP_ALIVE; 94 | else if (a3_string_cmpi(conn, A3_CS("Close")) == 0) 95 | return HTTP_CONNECTION_TYPE_CLOSE; 96 | else 97 | return HTTP_CONNECTION_TYPE_INVALID; 98 | } 99 | 100 | HttpTransferEncoding http_header_transfer_encodings(HttpHeaders* headers) { 101 | assert(headers); 102 | 103 | HttpTransferEncoding ret = HTTP_TRANSFER_ENCODING_INVALID; 104 | 105 | HTTP_HEADER_FOR_EACH_VALUE(headers, A3_CS("Transfer-Encoding"), encoding) { 106 | HttpTransferEncoding new_encoding = http_transfer_encoding_parse(encoding); 107 | if (!new_encoding) 108 | return HTTP_TRANSFER_ENCODING_INVALID; 109 | ret |= new_encoding; 110 | } 111 | 112 | if (!ret) 113 | return HTTP_TRANSFER_ENCODING_IDENTITY; 114 | return ret; 115 | } 116 | 117 | ssize_t http_header_content_length(HttpHeaders* headers) { 118 | assert(headers); 119 | 120 | ssize_t ret = HTTP_CONTENT_LENGTH_UNSPECIFIED; 121 | 122 | HTTP_HEADER_FOR_EACH_VALUE(headers, A3_CS("Content-Length"), content_length) { 123 | char* endptr = NULL; 124 | ssize_t new_length = strtol(a3_string_cstr(content_length), &endptr, 10); 125 | 126 | if (*endptr != '\0' || (ret != HTTP_CONTENT_LENGTH_UNSPECIFIED && ret != new_length) || 127 | new_length < 0) 128 | return HTTP_CONTENT_LENGTH_INVALID; 129 | 130 | ret = new_length; 131 | } 132 | 133 | return ret; 134 | } 135 | -------------------------------------------------------------------------------- /src/http/connection.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP CONNECTION -- HTTP-specific layer on top of a connection. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "http/connection.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "config.h" 34 | #include "connection.h" 35 | #include "event.h" 36 | #include "file.h" 37 | #include "forward.h" 38 | #include "http/types.h" 39 | 40 | static A3Pool* HTTP_CONNECTION_POOL = NULL; 41 | 42 | static void connection_pool_free_cb(void* slot) { 43 | assert(slot); 44 | 45 | HttpConnection* conn = slot; 46 | if (conn->conn.pipe[0] || conn->conn.pipe[1]) { 47 | close(conn->conn.pipe[0]); 48 | close(conn->conn.pipe[1]); 49 | } 50 | } 51 | 52 | void http_connection_pool_init() { 53 | HTTP_CONNECTION_POOL = A3_POOL_OF(HttpConnection, CONNECTION_POOL_SIZE, A3_POOL_PRESERVE_BLOCKS, 54 | NULL, connection_pool_free_cb); 55 | } 56 | 57 | HttpConnection* http_connection_new() { 58 | HttpConnection* ret = a3_pool_alloc_block(HTTP_CONNECTION_POOL); 59 | 60 | if (ret && !http_connection_init(ret)) 61 | http_connection_free(ret, NULL); 62 | 63 | return ret; 64 | } 65 | 66 | void http_connection_free(HttpConnection* conn, struct io_uring* uring) { 67 | assert(conn); 68 | assert(uring); 69 | 70 | // If the socket hasn't been closed, arrange it. The close handle event will 71 | // call free when it's done. 72 | if (conn->conn.socket != -1) { 73 | event_cancel_all(EVT(&conn->conn)); 74 | // If the submission was successful, we're done for now. 75 | if (http_connection_close_submit(conn, uring)) 76 | return; 77 | 78 | // Make a last-ditch attempt to close, but do not block. Theoretically 79 | // this could cause a leak of sockets, but if both the close request and 80 | // the actual close here fail, there are probably larger issues at play. 81 | int flags = fcntl(conn->conn.socket, F_GETFL); 82 | if (fcntl(conn->conn.socket, F_SETFL, flags | O_NONBLOCK) != 0 || 83 | close(conn->conn.socket) != 0) 84 | A3_ERRNO(errno, "Failed to close socket."); 85 | } 86 | 87 | http_connection_reset(conn, uring); 88 | 89 | if (a3_buf_initialized(&conn->conn.recv_buf)) 90 | a3_buf_destroy(&conn->conn.recv_buf); 91 | if (a3_buf_initialized(&conn->conn.send_buf)) 92 | a3_buf_destroy(&conn->conn.send_buf); 93 | 94 | a3_pool_free_block(HTTP_CONNECTION_POOL, conn); 95 | } 96 | 97 | void http_connection_pool_free() { a3_pool_free(HTTP_CONNECTION_POOL); } 98 | 99 | bool http_connection_init(HttpConnection* conn) { 100 | assert(conn); 101 | 102 | A3_TRYB(connection_init(&conn->conn)); 103 | 104 | http_request_init(&conn->request); 105 | http_response_init(&conn->response); 106 | 107 | conn->state = HTTP_CONNECTION_INIT; 108 | conn->version = HTTP_VERSION_11; 109 | conn->connection_type = HTTP_CONNECTION_TYPE_KEEP_ALIVE; 110 | conn->target_file = NULL; 111 | 112 | return true; 113 | } 114 | 115 | static bool http_connection_close_handle(Connection* connection, struct io_uring* uring, 116 | bool success, int32_t status) { 117 | assert(connection); 118 | assert(uring); 119 | assert(success); 120 | (void)success; 121 | (void)status; 122 | 123 | http_connection_free(connection_http(connection), uring); 124 | 125 | return true; 126 | } 127 | 128 | bool http_connection_close_submit(HttpConnection* conn, struct io_uring* uring) { 129 | assert(conn); 130 | assert(uring); 131 | 132 | conn->state = HTTP_CONNECTION_CLOSING; 133 | return connection_close_submit(&conn->conn, uring, http_connection_close_handle); 134 | } 135 | 136 | bool http_connection_reset(HttpConnection* conn, struct io_uring* uring) { 137 | assert(conn); 138 | assert(uring); 139 | 140 | if (conn->target_file) { 141 | file_handle_close(conn->target_file, uring); 142 | conn->target_file = NULL; 143 | } 144 | 145 | http_request_reset(&conn->request); 146 | http_response_reset(&conn->response); 147 | 148 | return connection_reset(&conn->conn, uring); 149 | } 150 | -------------------------------------------------------------------------------- /src/http/request.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP REQUEST -- HTTP request handlers. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "http/request.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "forward.h" 30 | #include "http/connection.h" 31 | #include "http/parse.h" 32 | #include "http/response.h" 33 | #include "http/types.h" 34 | #include "uri.h" 35 | 36 | HttpConnection* http_request_connection(HttpRequest* req) { 37 | assert(req); 38 | 39 | return A3_CONTAINER_OF(req, HttpConnection, request); 40 | } 41 | 42 | HttpResponse* http_request_response(HttpRequest* req) { 43 | return &http_request_connection(req)->response; 44 | } 45 | 46 | // TODO: Perhaps handle things other than static files. 47 | static HttpRequestStateResult http_request_get_head_handle(HttpRequest* req, 48 | struct io_uring* uring) { 49 | assert(req); 50 | assert(uring); 51 | 52 | // TODO: GET things other than static files. 53 | A3_RET_MAP(http_response_file_submit(http_request_response(req), uring), 54 | HTTP_REQUEST_STATE_DONE, HTTP_REQUEST_STATE_ERROR); 55 | } 56 | 57 | // Do whatever is appropriate for the parsed method. 58 | static HttpRequestStateResult http_request_method_handle(HttpRequest* req, struct io_uring* uring) { 59 | assert(req); 60 | assert(uring); 61 | 62 | HttpConnection* conn = http_request_connection(req); 63 | 64 | switch (conn->method) { 65 | case HTTP_METHOD_HEAD: 66 | case HTTP_METHOD_GET: 67 | return http_request_get_head_handle(req, uring); 68 | case HTTP_METHOD_BREW: 69 | conn->version = HTCPCP_VERSION_10; 70 | A3_RET_MAP(http_response_error_submit(http_request_response(req), uring, 71 | HTCPCP_STATUS_IM_A_TEAPOT, HTTP_RESPONSE_ALLOW), 72 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 73 | case HTTP_METHOD_INVALID: 74 | case HTTP_METHOD_UNKNOWN: 75 | A3_UNREACHABLE(); 76 | } 77 | 78 | A3_UNREACHABLE(); 79 | } 80 | 81 | void http_request_init(HttpRequest* req) { 82 | assert(req); 83 | 84 | memset(req, 0, sizeof(*req)); 85 | 86 | req->transfer_encodings = HTTP_TRANSFER_ENCODING_IDENTITY; 87 | req->content_length = HTTP_CONTENT_LENGTH_UNSPECIFIED; 88 | http_headers_init(&req->headers); 89 | } 90 | 91 | void http_request_reset(HttpRequest* req) { 92 | assert(req); 93 | 94 | if (uri_is_initialized(&req->target)) 95 | uri_free(&req->target); 96 | if (req->target_path.ptr) 97 | a3_string_free(&req->target_path); 98 | http_headers_destroy(&req->headers); 99 | 100 | memset(req, 0, sizeof(*req)); 101 | } 102 | 103 | // Try to parse as much of the HTTP request as possible. 104 | bool http_request_handle(Connection* connection, struct io_uring* uring, bool success, 105 | int32_t status) { 106 | assert(connection); 107 | assert(uring); 108 | assert(success); 109 | (void)success; 110 | (void)status; 111 | 112 | HttpConnection* conn = connection_http(connection); 113 | // TODO: Get more data here instead of returning the error up. 114 | 115 | HttpRequestStateResult rc = HTTP_REQUEST_STATE_ERROR; 116 | 117 | // Go through as many states as possible with the data currently loaded. 118 | switch (conn->state) { 119 | case HTTP_CONNECTION_INIT: 120 | if ((rc = http_request_first_line_parse(&conn->request, uring)) != HTTP_REQUEST_STATE_DONE) 121 | break; 122 | // fallthrough 123 | case HTTP_CONNECTION_PARSED_FIRST_LINE: 124 | if ((rc = http_request_headers_add(&conn->request, uring)) != HTTP_REQUEST_STATE_DONE) 125 | break; 126 | // fallthrough 127 | case HTTP_CONNECTION_ADDED_HEADERS: 128 | if ((rc = http_request_headers_parse(&conn->request, uring)) != HTTP_REQUEST_STATE_DONE) 129 | break; 130 | // fallthrough 131 | case HTTP_CONNECTION_PARSED_HEADERS: 132 | if ((rc = http_request_method_handle(&conn->request, uring)) != HTTP_REQUEST_STATE_DONE) 133 | break; 134 | // fallthrough 135 | case HTTP_CONNECTION_OPENING_FILE: 136 | case HTTP_CONNECTION_RESPONDING: 137 | case HTTP_CONNECTION_CLOSING: 138 | return HTTP_REQUEST_COMPLETE; 139 | } 140 | 141 | switch (rc) { 142 | case HTTP_REQUEST_STATE_BAIL: 143 | case HTTP_REQUEST_STATE_DONE: 144 | case HTTP_REQUEST_STATE_SENDING: 145 | return true; 146 | case HTTP_REQUEST_STATE_ERROR: 147 | return false; 148 | case HTTP_REQUEST_STATE_NEED_DATA: 149 | break; 150 | } 151 | 152 | // Request more data. 153 | return connection_recv_submit(connection, uring, http_request_handle); 154 | } 155 | -------------------------------------------------------------------------------- /src/http/types.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP TYPES -- Utility parsing and stringification for HTTP 3 | * types. 4 | * 5 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 6 | * 7 | * This program is free software: you can redistribute it and/or modify it under 8 | * the terms of the GNU Affero General Public License as published by the Free 9 | * Software Foundation, either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include "http/types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | HttpMethod http_request_method_parse(A3CString str) { 30 | #define _METHOD(M, N) { M, A3_CS(N) }, 31 | static const struct { 32 | HttpMethod method; 33 | A3CString name; 34 | } HTTP_METHOD_NAMES[] = { HTTP_METHOD_ENUM }; 35 | #undef _METHOD 36 | 37 | if (!str.ptr || !*str.ptr) 38 | return HTTP_METHOD_INVALID; 39 | 40 | A3_TRYB_MAP(str.ptr && a3_string_isascii(str), HTTP_METHOD_INVALID); 41 | 42 | for (size_t i = 0; i < sizeof(HTTP_METHOD_NAMES) / sizeof(HTTP_METHOD_NAMES[0]); i++) { 43 | if (a3_string_cmpi(str, HTTP_METHOD_NAMES[i].name) == 0) 44 | return HTTP_METHOD_NAMES[i].method; 45 | } 46 | 47 | return HTTP_METHOD_UNKNOWN; 48 | } 49 | 50 | #define _VERSION(V, S) [V] = A3_CS(S), 51 | static const A3CString HTTP_VERSION_STRINGS[] = { HTTP_VERSION_ENUM }; 52 | #undef _VERSION 53 | 54 | A3CString http_version_string(HttpVersion version) { return HTTP_VERSION_STRINGS[version]; } 55 | 56 | HttpVersion http_version_parse(A3CString str) { 57 | if (!str.ptr || !*str.ptr) 58 | return HTTP_VERSION_INVALID; 59 | 60 | A3_TRYB_MAP(str.ptr && a3_string_isascii(str), HTTP_VERSION_INVALID); 61 | 62 | for (HttpVersion v = HTTP_VERSION_INVALID + 1; v < HTTP_VERSION_UNKNOWN; v++) 63 | if (a3_string_cmpi(str, HTTP_VERSION_STRINGS[v]) == 0) 64 | return v; 65 | 66 | return HTTP_VERSION_UNKNOWN; 67 | } 68 | 69 | #define _STATUS(CODE, TYPE, REASON) [TYPE] = { CODE, A3_CS(REASON) }, 70 | static const struct { 71 | uint16_t code; 72 | A3CString reason; 73 | } HTTP_STATUSES[] = { HTTP_STATUS_ENUM }; 74 | #undef STATUS 75 | 76 | A3CString http_status_reason(HttpStatus status) { return HTTP_STATUSES[status].reason; } 77 | 78 | uint16_t http_status_code(HttpStatus status) { return HTTP_STATUSES[status].code; } 79 | 80 | A3CString http_content_type_name(HttpContentType type) { 81 | #define _CTYPE(T, S) [T] = A3_CS(S), 82 | static const A3CString HTTP_CONTENT_TYPE_NAMES[] = { HTTP_CONTENT_TYPE_ENUM }; 83 | #undef _CTYPE 84 | 85 | return HTTP_CONTENT_TYPE_NAMES[type]; 86 | } 87 | 88 | HttpContentType http_content_type_from_path(A3CString path) { 89 | assert(path.ptr); 90 | 91 | static struct { 92 | A3CString ext; 93 | HttpContentType ctype; 94 | } EXTENSIONS[] = { 95 | { A3_CS("bmp"), HTTP_CONTENT_TYPE_IMAGE_BMP }, 96 | { A3_CS("gif"), HTTP_CONTENT_TYPE_IMAGE_GIF }, 97 | { A3_CS("ico"), HTTP_CONTENT_TYPE_IMAGE_ICO }, 98 | { A3_CS("jpg"), HTTP_CONTENT_TYPE_IMAGE_JPEG }, 99 | { A3_CS("jpeg"), HTTP_CONTENT_TYPE_IMAGE_JPEG }, 100 | { A3_CS("json"), HTTP_CONTENT_TYPE_APPLICATION_JSON }, 101 | { A3_CS("pdf"), HTTP_CONTENT_TYPE_APPLICATION_PDF }, 102 | { A3_CS("png"), HTTP_CONTENT_TYPE_IMAGE_PNG }, 103 | { A3_CS("svg"), HTTP_CONTENT_TYPE_IMAGE_SVG }, 104 | { A3_CS("webp"), HTTP_CONTENT_TYPE_IMAGE_WEBP }, 105 | { A3_CS("css"), HTTP_CONTENT_TYPE_TEXT_CSS }, 106 | { A3_CS("js"), HTTP_CONTENT_TYPE_TEXT_JAVASCRIPT }, 107 | { A3_CS("md"), HTTP_CONTENT_TYPE_TEXT_MARKDOWN }, 108 | { A3_CS("txt"), HTTP_CONTENT_TYPE_TEXT_PLAIN }, 109 | { A3_CS("htm"), HTTP_CONTENT_TYPE_TEXT_HTML }, 110 | { A3_CS("html"), HTTP_CONTENT_TYPE_TEXT_HTML }, 111 | }; 112 | 113 | A3CString last_dot = a3_string_rchr(path, '.'); 114 | if (!last_dot.ptr || last_dot.len < 2) 115 | return HTTP_CONTENT_TYPE_APPLICATION_OCTET_STREAM; 116 | 117 | A3CString last_slash = a3_string_rchr(path, '/'); 118 | if (last_slash.ptr && last_slash.ptr > last_dot.ptr) 119 | return HTTP_CONTENT_TYPE_APPLICATION_OCTET_STREAM; 120 | 121 | A3CString ext = { .ptr = last_dot.ptr + 1, .len = last_dot.len - 1 }; 122 | for (size_t i = 0; i < sizeof(EXTENSIONS) / sizeof(EXTENSIONS[0]); i++) { 123 | if (a3_string_cmpi(ext, EXTENSIONS[i].ext) == 0) 124 | return EXTENSIONS[i].ctype; 125 | } 126 | 127 | return HTTP_CONTENT_TYPE_APPLICATION_OCTET_STREAM; 128 | } 129 | 130 | HttpTransferEncoding http_transfer_encoding_parse(A3CString value) { 131 | #define _TENCODING(E, S) { HTTP_##E, A3_CS(S) }, 132 | static const struct { 133 | HttpTransferEncoding encoding; 134 | A3CString value; 135 | } HTTP_TRANSFER_ENCODING_VALUES[] = { HTTP_TRANSFER_ENCODING_ENUM }; 136 | #undef _TENCODING 137 | 138 | assert(value.ptr && *value.ptr); 139 | 140 | A3_TRYB_MAP(value.ptr && a3_string_isascii(value), HTTP_TRANSFER_ENCODING_INVALID); 141 | 142 | for (size_t i = 0; 143 | i < sizeof(HTTP_TRANSFER_ENCODING_VALUES) / sizeof(HTTP_TRANSFER_ENCODING_VALUES[0]); 144 | i++) { 145 | if (a3_string_cmpi(value, HTTP_TRANSFER_ENCODING_VALUES[i].value) == 0) 146 | return HTTP_TRANSFER_ENCODING_VALUES[i].encoding; 147 | } 148 | 149 | return HTTP_TRANSFER_ENCODING_INVALID; 150 | } 151 | -------------------------------------------------------------------------------- /src/http/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP TYPES -- Fundamental types for HTTP handling and parsing. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #include 25 | 26 | #define HTTP_NEWLINE A3_CS("\r\n") 27 | 28 | #define HTTP_METHOD_ENUM \ 29 | _METHOD(HTTP_METHOD_INVALID, "__INVALID") \ 30 | _METHOD(HTTP_METHOD_BREW, "BREW") \ 31 | _METHOD(HTTP_METHOD_GET, "GET") \ 32 | _METHOD(HTTP_METHOD_HEAD, "HEAD") \ 33 | _METHOD(HTTP_METHOD_UNKNOWN, "__UNKNOWN") 34 | 35 | typedef enum HttpMethod { 36 | #define _METHOD(M, N) M, 37 | HTTP_METHOD_ENUM 38 | #undef _METHOD 39 | } HttpMethod; 40 | 41 | #define HTTP_VERSION_ENUM \ 42 | _VERSION(HTTP_VERSION_INVALID, "") \ 43 | _VERSION(HTTP_VERSION_10, "HTTP/1.0") \ 44 | _VERSION(HTTP_VERSION_11, "HTTP/1.1") \ 45 | _VERSION(HTCPCP_VERSION_10, "HTCPCP/1.0") \ 46 | _VERSION(HTTP_VERSION_UNKNOWN, "") 47 | 48 | typedef enum HttpVersion { 49 | #define _VERSION(V, S) V, 50 | HTTP_VERSION_ENUM 51 | #undef _VERSION 52 | } HttpVersion; 53 | 54 | #define HTTP_CONTENT_TYPE_ENUM \ 55 | _CTYPE(HTTP_CONTENT_TYPE_INVALID, "") \ 56 | _CTYPE(HTTP_CONTENT_TYPE_APPLICATION_OCTET_STREAM, "application/octet-stream") \ 57 | _CTYPE(HTTP_CONTENT_TYPE_APPLICATION_JSON, "application/json") \ 58 | _CTYPE(HTTP_CONTENT_TYPE_APPLICATION_PDF, "application/pdf") \ 59 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_BMP, "image/bmp") \ 60 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_GIF, "image/gif") \ 61 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_ICO, "image/x-icon") \ 62 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_JPEG, "image/jpeg") \ 63 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_PNG, "image/png") \ 64 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_SVG, "image/svg+xml") \ 65 | _CTYPE(HTTP_CONTENT_TYPE_IMAGE_WEBP, "image/webp") \ 66 | _CTYPE(HTTP_CONTENT_TYPE_TEXT_CSS, "text/css") \ 67 | _CTYPE(HTTP_CONTENT_TYPE_TEXT_JAVASCRIPT, "text/javascript") \ 68 | _CTYPE(HTTP_CONTENT_TYPE_TEXT_MARKDOWN, "text/markdown") \ 69 | _CTYPE(HTTP_CONTENT_TYPE_TEXT_PLAIN, "text/plain") \ 70 | _CTYPE(HTTP_CONTENT_TYPE_TEXT_HTML, "text/html") 71 | 72 | typedef enum HttpContentType { 73 | #define _CTYPE(T, S) T, 74 | HTTP_CONTENT_TYPE_ENUM 75 | #undef _CTYPE 76 | } HttpContentType; 77 | 78 | #define HTTP_STATUS_ENUM \ 79 | _STATUS(0, HTTP_STATUS_INVALID, "Invalid error") \ 80 | _STATUS(200, HTTP_STATUS_OK, "OK") \ 81 | _STATUS(400, HTTP_STATUS_BAD_REQUEST, "Bad Request") \ 82 | _STATUS(404, HTTP_STATUS_NOT_FOUND, "Not Found") \ 83 | _STATUS(408, HTTP_STATUS_TIMEOUT, "Request Timeout") \ 84 | _STATUS(413, HTTP_STATUS_PAYLOAD_TOO_LARGE, "Payload Too Large") \ 85 | _STATUS(414, HTTP_STATUS_URI_TOO_LONG, "URI Too Long") \ 86 | _STATUS(418, HTCPCP_STATUS_IM_A_TEAPOT, "I'm a teapot") \ 87 | _STATUS(431, HTTP_STATUS_HEADER_TOO_LARGE, "Request Header Fields Too Large") \ 88 | _STATUS(500, HTTP_STATUS_SERVER_ERROR, "Internal Server Error") \ 89 | _STATUS(501, HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented") \ 90 | _STATUS(505, HTTP_STATUS_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported") 91 | 92 | typedef enum HttpStatus { 93 | #define _STATUS(CODE, TYPE, REASON) TYPE, 94 | HTTP_STATUS_ENUM 95 | #undef _STATUS 96 | } HttpStatus; 97 | 98 | #define HTTP_CONNECTION_TYPE_ENUM \ 99 | _CTYPE(HTTP_CONNECTION_TYPE_CLOSE, "Close") \ 100 | _CTYPE(HTTP_CONNECTION_TYPE_KEEP_ALIVE, "Keep-Alive") \ 101 | _CTYPE(HTTP_CONNECTION_TYPE_UNSPECIFIED, "") \ 102 | _CTYPE(HTTP_CONNECTION_TYPE_INVALID, "") 103 | 104 | typedef enum HttpConnectionType { 105 | #define _CTYPE(TYPE, STR) TYPE, 106 | HTTP_CONNECTION_TYPE_ENUM 107 | #undef _CTYPE 108 | } HttpConnectionType; 109 | 110 | #define HTTP_TRANSFER_ENCODING_ENUM \ 111 | _TENCODING(TRANSFER_ENCODING_IDENTITY, "identity") \ 112 | _TENCODING(TRANSFER_ENCODING_CHUNKED, "chunked") 113 | 114 | enum HttpTransferBits { 115 | #define _TENCODING(E, S) _HTTP_##E, 116 | HTTP_TRANSFER_ENCODING_ENUM 117 | #undef _TENCODING 118 | }; 119 | 120 | typedef uint8_t HttpTransferEncoding; 121 | #define _TENCODING(E, S) static const HttpTransferEncoding HTTP_##E = 1 << (_HTTP_##E); 122 | HTTP_TRANSFER_ENCODING_ENUM 123 | #undef _TENCODING 124 | #define HTTP_TRANSFER_ENCODING_INVALID (0U) 125 | 126 | #define HTTP_CONTENT_LENGTH_UNSPECIFIED (-1LL) 127 | #define HTTP_CONTENT_LENGTH_INVALID (-2LL) 128 | 129 | typedef enum HttpRequestResult { 130 | HTTP_REQUEST_ERROR, 131 | HTTP_REQUEST_NEED_DATA, 132 | HTTP_REQUEST_SENDING, 133 | HTTP_REQUEST_COMPLETE 134 | } HttpRequestResult; 135 | 136 | typedef enum HttpRequestStateResult { 137 | HTTP_REQUEST_STATE_ERROR = HTTP_REQUEST_ERROR, 138 | HTTP_REQUEST_STATE_NEED_DATA = HTTP_REQUEST_NEED_DATA, 139 | HTTP_REQUEST_STATE_SENDING = HTTP_REQUEST_SENDING, 140 | HTTP_REQUEST_STATE_BAIL = HTTP_REQUEST_COMPLETE, 141 | HTTP_REQUEST_STATE_DONE 142 | } HttpRequestStateResult; 143 | 144 | HttpMethod http_request_method_parse(A3CString str); 145 | HttpVersion http_version_parse(A3CString str); 146 | HttpContentType http_content_type_from_path(A3CString); 147 | HttpTransferEncoding http_transfer_encoding_parse(A3CString value); 148 | 149 | A3CString http_version_string(HttpVersion); 150 | A3CString http_status_reason(HttpStatus); 151 | uint16_t http_status_code(HttpStatus status); 152 | A3CString http_content_type_name(HttpContentType); 153 | -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: FILE -- Open file descriptor cache. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "file.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "config.h" 36 | #include "event.h" 37 | #include "event/handle.h" 38 | #include "file_handle.h" 39 | #include "forward.h" 40 | 41 | #define FILE_HANDLE_WAITING (-4242) 42 | 43 | typedef FileHandle* FileHandlePtr; 44 | 45 | A3_CACHE_DEFINE_STRUCTS(A3CString, FileHandlePtr) 46 | A3_CACHE_DECLARE_METHODS(A3CString, FileHandlePtr) 47 | A3_CACHE_DEFINE_METHODS(A3CString, FileHandlePtr, a3_string_cptr, a3_string_len, a3_string_cmp) 48 | typedef A3_CACHE(A3CString, FileHandlePtr) FileCache; 49 | 50 | static FileCache FILE_CACHE; 51 | 52 | static void file_evict_callback(void* uring, A3CString* key, FileHandle** value) { 53 | assert(uring); 54 | assert(key); 55 | assert(key->ptr); 56 | assert(value); 57 | 58 | A3_TRACE_F("Evicting file " A3_S_F ".", A3_S_FORMAT(*key)); 59 | file_handle_close(*value, uring); 60 | } 61 | 62 | void file_cache_init() { 63 | A3_CACHE_INIT(A3CString, FileHandlePtr) 64 | (&FILE_CACHE, FD_CACHE_SIZE, file_evict_callback); 65 | } 66 | 67 | static void file_handle_wait(EventTarget* target, FileHandle* handle, FileHandleHandler handler, 68 | void* ctx) { 69 | assert(target); 70 | assert(handle); 71 | assert(handler); 72 | 73 | A3_REF(handle); 74 | Event* event = event_create(target, handler, ctx); 75 | a3_sll_push(&handle->waiting, event_queue_link(event)); 76 | } 77 | 78 | static EventTarget* file_handle_target(FileHandle* handle) { 79 | assert(handle); 80 | 81 | A3_REF(handle); 82 | return EVT(handle); 83 | } 84 | 85 | static void file_handle_stat_handle(EventTarget* target, struct io_uring* uring, void* ctx, 86 | bool success, int32_t status) { 87 | assert(target); 88 | assert(uring); 89 | (void)ctx; 90 | 91 | FileHandle* handle = EVT_PTR(target, FileHandle); 92 | assert(file_handle_waiting(handle)); 93 | 94 | // Unref. 95 | if (file_handle_close(handle, uring)) 96 | return; 97 | 98 | // If there was an error, deliver it. Otherwise, wait for the open. 99 | if (!success) { 100 | handle->file = status; 101 | event_synth_deliver(&handle->waiting, uring, status); 102 | } 103 | } 104 | 105 | static void file_handle_openat_handle(EventTarget* target, struct io_uring* uring, void* ctx, 106 | bool success, int32_t status) { 107 | assert(target); 108 | assert(uring); 109 | (void)ctx; 110 | (void)success; 111 | 112 | FileHandle* handle = EVT_PTR(target, FileHandle); 113 | assert(file_handle_waiting(handle)); 114 | 115 | // Unref. 116 | if (file_handle_close(handle, uring)) 117 | return; 118 | 119 | handle->file = status; 120 | event_synth_deliver(&handle->waiting, uring, status); 121 | } 122 | 123 | FileHandle* file_open(EventTarget* target, struct io_uring* uring, FileHandleHandler handler, 124 | void* ctx, A3CString path, int32_t flags) { 125 | assert(handler); 126 | return file_openat(target, uring, handler, ctx, NULL, path, flags); 127 | } 128 | 129 | FileHandle* file_openat(EventTarget* target, struct io_uring* uring, FileHandleHandler handler, 130 | void* ctx, FileHandle* dir, A3CString name, int32_t flags) { 131 | assert(target); 132 | assert(uring); 133 | assert(handler); 134 | assert(name.ptr); 135 | assert(flags == O_RDONLY); 136 | 137 | A3String path = A3_S_NULL; 138 | 139 | if (dir) { 140 | path = a3_string_alloc(dir->path.len + name.len + 1); 141 | if (dir->path.ptr[dir->path.len - 1] == '/') 142 | a3_string_concat(path, 2, dir->path, name); 143 | else 144 | a3_string_concat(path, 3, dir->path, A3_CS("/"), name); 145 | } else { 146 | path = a3_string_clone(name); 147 | } 148 | 149 | FileHandle** handle_ptr = 150 | A3_CACHE_FIND(A3CString, FileHandlePtr)(&FILE_CACHE, A3_S_CONST(path)); 151 | if (handle_ptr && (*handle_ptr)->flags == flags) { 152 | FileHandle* handle = *handle_ptr; 153 | 154 | A3_TRACE_F("File cache hit (openat) on " A3_S_F ".", A3_S_FORMAT(path)); 155 | a3_string_free(&path); 156 | 157 | // The handle is not ready, but an open request is in flight. Synthesize 158 | // an event so the caller is notified when the file is opened. 159 | if (file_handle_waiting(handle)) { 160 | A3_TRACE(" Open in-flight. Waiting."); 161 | file_handle_wait(target, handle, handler, ctx); 162 | return handle; 163 | } 164 | 165 | A3_REF(handle); 166 | return handle; 167 | } 168 | 169 | A3_TRACE_F("File cache miss (openat) on " A3_S_F ".", A3_S_FORMAT(path)); 170 | FileHandle* handle = NULL; 171 | A3_UNWRAPN(handle, calloc(1, sizeof(FileHandle))); 172 | A3_REF_INIT(handle); 173 | handle->path = A3_S_CONST(path); 174 | handle->file = FILE_HANDLE_WAITING; 175 | 176 | if (!event_stat_submit(file_handle_target(handle), uring, file_handle_stat_handle, NULL, 177 | handle->path, FILE_STATX_MASK, &handle->stat, IOSQE_IO_LINK) || 178 | !event_openat_submit(file_handle_target(handle), uring, file_handle_openat_handle, NULL, 179 | dir ? file_handle_fd(dir) : -1, handle->path, flags, 0)) { 180 | A3_WARN("Unable to submit OPENAT event."); 181 | a3_string_free(&path); 182 | free(handle); 183 | return NULL; 184 | } 185 | 186 | file_handle_wait(target, handle, handler, ctx); 187 | A3_CACHE_INSERT(A3CString, FileHandlePtr)(&FILE_CACHE, handle->path, handle, uring); 188 | 189 | return handle; 190 | } 191 | 192 | fd file_handle_fd(FileHandle* handle) { 193 | assert(handle); 194 | assert(handle->file >= 0); 195 | return handle->file; 196 | } 197 | 198 | fd file_handle_fd_unchecked(FileHandle* handle) { 199 | assert(handle); 200 | return handle->file; 201 | } 202 | 203 | struct statx* file_handle_stat(FileHandle* handle) { 204 | assert(handle); 205 | return &handle->stat; 206 | } 207 | 208 | A3CString file_handle_path(FileHandle* handle) { 209 | assert(handle); 210 | return handle->path; 211 | } 212 | 213 | bool file_handle_waiting(FileHandle* handle) { 214 | assert(handle); 215 | 216 | return handle->file == FILE_HANDLE_WAITING; 217 | } 218 | 219 | // Returns true if the handle was freed. 220 | bool file_handle_close(FileHandle* handle, struct io_uring* uring) { 221 | assert(handle); 222 | assert(A3_REF_COUNT(handle)); 223 | assert(uring); 224 | 225 | A3_UNREF(handle); 226 | if (A3_REF_COUNT(handle)) 227 | return false; // Other users remain. 228 | 229 | if (handle->file >= 0) 230 | event_close_submit(NULL, uring, NULL, NULL, handle->file, 0, EVENT_FALLBACK_ALLOW); 231 | a3_string_free((A3String*)&handle->path); 232 | free(handle); 233 | return true; 234 | } 235 | 236 | void file_cache_destroy(struct io_uring* uring) { 237 | assert(uring); 238 | 239 | A3_CACHE_CLEAR(A3CString, FileHandlePtr)(&FILE_CACHE, uring); 240 | } 241 | -------------------------------------------------------------------------------- /src/uri.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: URI -- URI parsing and decoding. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "uri.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | static UriScheme uri_scheme_parse(A3CString name) { 33 | #define _SCHEME(SCHEME, S) { SCHEME, A3_CS(S) }, 34 | static const struct { 35 | UriScheme scheme; 36 | A3CString name; 37 | } URI_SCHEMES[] = { URI_SCHEME_ENUM }; 38 | #undef _SCHEME 39 | assert(name.ptr && *name.ptr); 40 | 41 | A3_TRYB_MAP(name.ptr && a3_string_isascii(name), URI_SCHEME_INVALID); 42 | 43 | for (size_t i = 0; i < sizeof(URI_SCHEMES) / sizeof(URI_SCHEMES[0]); i++) { 44 | if (a3_string_cmpi(name, URI_SCHEMES[i].name) == 0) 45 | return URI_SCHEMES[i].scheme; 46 | } 47 | 48 | return URI_SCHEME_INVALID; 49 | } 50 | 51 | static bool uri_decode(A3String str) { 52 | assert(str.ptr); 53 | const uint8_t* end = a3_string_end(A3_S_CONST(str)); 54 | 55 | for (uint8_t *wp, *rp = wp = str.ptr; wp < end && rp < end && *wp && *rp; wp++) { 56 | switch (*rp) { 57 | case '%': 58 | if (isxdigit(rp[1]) && isxdigit(rp[2])) { 59 | uint8_t n = 0; 60 | for (uint8_t i = 0; i < 2; i++) { 61 | n *= 16; 62 | uint8_t c = rp[2 - i]; 63 | if ('0' <= c && c <= '9') 64 | n += (uint8_t)(c - '0'); 65 | else 66 | n += (uint8_t)(toupper(c) - 'A' + 10); 67 | } 68 | 69 | if (n == 0) 70 | return false; 71 | 72 | *wp = n; 73 | rp += 3; 74 | } else { 75 | return false; 76 | } 77 | break; 78 | case '+': 79 | *wp = ' '; 80 | rp++; 81 | break; 82 | default: 83 | *wp = *rp++; 84 | break; 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | static void uri_collapse_dot_segments(A3String str) { 92 | assert(str.ptr); 93 | assert(*str.ptr == '/'); 94 | 95 | size_t segments = 0; 96 | for (size_t i = 0; i < str.len; segments += str.ptr[i++] == '/') 97 | ; 98 | if (!segments) 99 | return; 100 | size_t* segment_indices = calloc(segments, sizeof(size_t)); 101 | A3_UNWRAPND(segment_indices); 102 | 103 | size_t segment_index = 0; 104 | for (size_t ri, wi = ri = 0; ri < str.len && wi < str.len;) { 105 | if (!str.ptr[ri]) 106 | break; 107 | if (str.ptr[ri] == '/') { 108 | if (ri + 1 < str.len && str.ptr[ri + 1] == '.') { 109 | if (ri + 2 < str.len && str.ptr[ri + 2] == '.') { 110 | ri += 3; 111 | // Go back a segment. 112 | if (segment_index > 0) { 113 | wi = segment_indices[--segment_index] + 1; 114 | // Drop extra slashes. 115 | if (ri < str.len && str.ptr[ri] == '/') 116 | wi--; 117 | } else { 118 | memcpy(&str.ptr[wi], "/..", 3); 119 | wi += 3; 120 | } 121 | } else { 122 | // Skip "/.". 123 | ri += 2; 124 | } 125 | } else { 126 | // Create a segment. 127 | wi = ri++; 128 | segment_indices[segment_index++] = wi++; 129 | } 130 | } else { 131 | str.ptr[wi++] = str.ptr[ri++]; 132 | } 133 | } 134 | 135 | free(segment_indices); 136 | } 137 | 138 | static bool uri_normalize_path(A3String str) { 139 | assert(str.ptr); 140 | 141 | A3_TRYB(uri_decode(str)); 142 | uri_collapse_dot_segments(str); 143 | 144 | return true; 145 | } 146 | 147 | UriParseResult uri_parse(Uri* ret, A3String str) { 148 | assert(ret); 149 | assert(str.ptr); 150 | 151 | A3Buffer buf_ = { .data = str, .tail = str.len, .head = 0, .max_cap = str.len }; 152 | A3Buffer* buf = &buf_; 153 | 154 | memset(ret, 0, sizeof(Uri)); 155 | ret->scheme = URI_SCHEME_UNSPECIFIED; 156 | 157 | // [://][authority][query][fragment] 158 | if (a3_buf_memmem(buf, A3_CS("://")).ptr) { 159 | ret->scheme = 160 | uri_scheme_parse(A3_S_CONST(a3_buf_token_next(buf, A3_CS("://"), A3_PRES_END_NO))); 161 | if (ret->scheme == URI_SCHEME_INVALID) 162 | return URI_PARSE_BAD_URI; 163 | } 164 | 165 | // [authority][query][fragment] 166 | if (buf->data.ptr[buf->head] != '/' && ret->scheme != URI_SCHEME_UNSPECIFIED) { 167 | ret->authority = a3_string_clone(a3_buf_token_next(buf, A3_CS("/"), A3_PRES_END_NO)); 168 | A3_TRYB_MAP(ret->authority.ptr, URI_PARSE_BAD_URI); 169 | buf->data.ptr[--buf->head] = '/'; 170 | } 171 | 172 | // [query][fragment] 173 | ret->path = a3_buf_token_next_copy(buf, A3_CS("#?\r\n"), A3_PRES_END_NO); 174 | A3_TRYB_MAP(ret->path.ptr, URI_PARSE_BAD_URI); 175 | if (ret->path.len == 0) 176 | return URI_PARSE_BAD_URI; 177 | A3_TRYB_MAP(uri_normalize_path(ret->path), URI_PARSE_BAD_URI); 178 | if (a3_buf_len(buf) == 0) 179 | return URI_PARSE_SUCCESS; 180 | 181 | // [query][fragment] 182 | ret->query = a3_buf_token_next_copy(buf, A3_CS("#"), A3_PRES_END_NO); 183 | A3_TRYB_MAP(ret->query.ptr, URI_PARSE_BAD_URI); 184 | A3_TRYB_MAP(uri_decode(ret->query), URI_PARSE_BAD_URI); 185 | if (a3_buf_len(buf) == 0) 186 | return URI_PARSE_SUCCESS; 187 | 188 | // [fragment] 189 | ret->fragment = a3_buf_token_next_copy(buf, A3_CS(""), A3_PRES_END_NO); 190 | A3_TRYB_MAP(ret->fragment.ptr, URI_PARSE_BAD_URI); 191 | uri_decode(ret->fragment); 192 | assert(a3_buf_len(buf) == 0); 193 | 194 | return URI_PARSE_SUCCESS; 195 | } 196 | 197 | // Return the path to the pointed-to file if it is a child of the given root 198 | // path. 199 | A3String uri_path_if_contained(Uri* uri, A3CString real_root) { 200 | assert(uri); 201 | assert(real_root.ptr && *real_root.ptr); 202 | 203 | // Ensure there are no directory escaping shenanigans. This occurs after 204 | // decoding, so ".." should be the only way such a thing can occur. 205 | // 206 | // TODO: This only makes sense for static files since parts of the path 207 | // which are used by an endpoint are perfectly allowed to contain "..". 208 | for (size_t i = 0; i < uri->path.len - 1; i++) 209 | if (uri->path.ptr[i] == '.' && uri->path.ptr[i + 1] == '.') 210 | return A3_S_NULL; 211 | 212 | if (uri->path.len == 1 && *uri->path.ptr == '/') 213 | return a3_string_clone(real_root); 214 | 215 | A3String ret = a3_string_alloc(real_root.len + uri->path.len); 216 | a3_string_concat(ret, 2, real_root, uri->path); 217 | return ret; 218 | } 219 | 220 | bool uri_is_initialized(Uri* uri) { 221 | assert(uri); 222 | 223 | return uri->path.ptr; 224 | } 225 | 226 | void uri_free(Uri* uri) { 227 | assert(uri_is_initialized(uri)); 228 | 229 | if (uri->authority.ptr) 230 | a3_string_free(&uri->authority); 231 | if (uri->path.ptr) 232 | a3_string_free(&uri->path); 233 | if (uri->query.ptr) 234 | a3_string_free(&uri->query); 235 | if (uri->fragment.ptr) 236 | a3_string_free(&uri->fragment); 237 | } 238 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT -- A high-performance HTTP server for Linux, built on io_uring. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | * 19 | * Note: This whole file is a bit of a hack at the moment, and should probably 20 | * be regarded more as a test harness for development purposes than an actual 21 | * final interface. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #include "config.h" 39 | #include "config_runtime.h" 40 | #include "connection.h" 41 | #include "event.h" 42 | #include "event/handle.h" 43 | #include "file.h" 44 | #include "forward.h" 45 | #include "http/connection.h" 46 | #include "listen.h" 47 | 48 | Config CONFIG = { .web_root = DEFAULT_WEB_ROOT, 49 | .listen_port = DEFAULT_LISTEN_PORT, 50 | #ifdef NDEBUG 51 | .log_level = A3_LOG_WARN 52 | #else 53 | .log_level = A3_LOG_TRACE 54 | #endif 55 | }; 56 | 57 | static bool cont = true; 58 | 59 | static void sigint_handle(int no) { 60 | (void)no; 61 | cont = false; 62 | } 63 | 64 | static void webroot_check_exists(A3CString root) { 65 | struct stat s; 66 | 67 | if (stat(a3_string_cstr(root), &s) < 0) 68 | A3_PANIC_FMT("Web root %s is inaccessible.", root); 69 | if (!S_ISDIR(s.st_mode)) 70 | A3_PANIC_FMT("Web root %s is not a directory.", root); 71 | } 72 | 73 | static void usage(void) { 74 | fprintf(stderr, "USAGE:\n\n" 75 | "sc [options] [web root]\n" 76 | "Options:\n" 77 | "\t-h, --help\t\tShow this message and exit.\n" 78 | "\t-p, --port \tSpecify the port to listen on. (Default is 8000).\n" 79 | "\t-q, --quiet\t\tBe quieter (more 'q's for more silence).\n" 80 | "\t-v, --verbose\t\tPrint verbose output (more 'v's for even more output).\n" 81 | "\t --version\t\tPrint version information.\n"); 82 | exit(EXIT_FAILURE); 83 | } 84 | 85 | static void version(void) { 86 | printf("Short Circuit (sc) %s\n" 87 | "Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com>\n\n" 88 | "This program is free software: you can redistribute it and/or modify\n" 89 | "it under the terms of the GNU Affero General Public License as published\n" 90 | "by the Free Software Foundation, either version 3 of the License, or\n" 91 | "(at your option) any later version.\n\n" 92 | "This program is distributed in the hope that it will be useful,\n" 93 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 94 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 95 | "GNU Affero General Public License for more details.\n\n" 96 | "You should have received a copy of the GNU Affero General Public License\n" 97 | "along with this program. If not, see .\n", 98 | SC_VERSION); 99 | exit(EXIT_SUCCESS); 100 | } 101 | 102 | enum { OPT_HELP, OPT_PORT, OPT_QUIET, OPT_VERBOSE, OPT_VERSION, _OPT_COUNT }; 103 | 104 | static void config_parse(int argc, char** argv) { 105 | static struct option options[] = { 106 | [OPT_HELP] = { "help", no_argument, NULL, 'h' }, 107 | [OPT_PORT] = { "port", required_argument, NULL, 'p' }, 108 | [OPT_QUIET] = { "quiet", no_argument, NULL, 'q' }, 109 | [OPT_VERBOSE] = { "verbose", no_argument, NULL, 'v' }, 110 | [OPT_VERSION] = { "version", no_argument, NULL, '\0' }, 111 | [_OPT_COUNT] = { 0, 0, 0, 0 }, 112 | }; 113 | 114 | int opt; 115 | int longindex; 116 | uint64_t port_num; 117 | while ((opt = getopt_long(argc, argv, "hqvp:", options, &longindex)) != -1) { 118 | switch (opt) { 119 | case 'h': 120 | usage(); 121 | break; 122 | case 'p': 123 | port_num = strtoul(optarg, NULL, 10); 124 | if (port_num > UINT16_MAX) { 125 | A3_ERROR("Invalid port."); 126 | exit(EXIT_FAILURE); 127 | } 128 | 129 | CONFIG.listen_port = (in_port_t)port_num; 130 | break; 131 | case 'q': 132 | if (CONFIG.log_level < A3_LOG_ERROR) 133 | CONFIG.log_level++; 134 | break; 135 | case 'v': 136 | if (CONFIG.log_level > A3_LOG_TRACE) 137 | CONFIG.log_level--; 138 | break; 139 | default: 140 | if (opt == 0) { 141 | switch (longindex) { 142 | case OPT_VERSION: 143 | version(); 144 | break; 145 | default: 146 | fprintf(stderr, "Unrecognized long option.\n"); 147 | usage(); 148 | break; 149 | } 150 | } else { 151 | usage(); 152 | } 153 | break; 154 | } 155 | } 156 | 157 | // Non-option parameters to parse. 158 | if (optind < argc) { 159 | if (argc - optind > 1) { 160 | A3_ERROR("Too many parameters."); 161 | usage(); 162 | } 163 | 164 | CONFIG.web_root = a3_cstring_from(argv[optind]); 165 | } 166 | 167 | CONFIG.web_root = a3_cstring_from(realpath(a3_string_cstr(CONFIG.web_root), NULL)); 168 | } 169 | 170 | int main(int argc, char** argv) { 171 | (void)argv; 172 | 173 | a3_log_init(stderr, CONFIG.log_level); 174 | config_parse(argc, argv); 175 | // Re-initialize with the potentially changed log level. 176 | a3_log_init(stderr, CONFIG.log_level); 177 | 178 | srand((uint32_t)time(NULL)); 179 | 180 | webroot_check_exists(CONFIG.web_root); 181 | http_connection_pool_init(); 182 | file_cache_init(); 183 | connection_timeout_init(); 184 | struct io_uring uring = event_init(); 185 | 186 | Listener* listeners = NULL; 187 | size_t n_listeners = 0; 188 | 189 | // TODO: Support multiple listeners. 190 | n_listeners = 1; 191 | A3_UNWRAPN(listeners, calloc(1, sizeof(Listener))); 192 | listener_init(&listeners[0], CONFIG.listen_port, TRANSPORT_PLAIN); 193 | 194 | listener_accept_all(listeners, n_listeners, &uring); 195 | A3_UNWRAPND(io_uring_submit(&uring)); 196 | 197 | A3_UNWRAPND(signal(SIGINT, sigint_handle) != SIG_ERR); 198 | A3_UNWRAPND(signal(SIGPIPE, SIG_IGN) != SIG_ERR); 199 | A3_TRACE("Entering event loop."); 200 | 201 | #ifdef PROFILE 202 | time_t init_time = time(NULL); 203 | #endif 204 | 205 | EventQueue queue; 206 | event_queue_init(&queue); 207 | while (cont) { 208 | struct io_uring_cqe* cqe; 209 | int rc; 210 | #ifdef PROFILE 211 | Timespec timeout = { .tv_sec = 1, .tv_nsec = 0 }; 212 | if (((rc = io_uring_wait_cqe_timeout(&uring, &cqe, &timeout)) < 0 && rc != -ETIME) || 213 | time(NULL) > init_time + 20) { 214 | if (rc < 0) 215 | a3_log_error(-rc, "Breaking event loop."); 216 | break; 217 | } 218 | #else 219 | if ((rc = io_uring_wait_cqe(&uring, &cqe)) < 0 && rc != -ETIME) { 220 | A3_ERRNO(-rc, "Breaking event loop."); 221 | break; 222 | } 223 | #endif 224 | 225 | event_handle_all(&queue, &uring); 226 | listener_accept_all(listeners, n_listeners, &uring); 227 | 228 | if (io_uring_sq_ready(&uring) > 0) { 229 | int ev = io_uring_submit(&uring); 230 | (void)ev; 231 | A3_TRACE_F("Submitted %d event(s).", ev); 232 | } 233 | } 234 | 235 | http_connection_pool_free(); 236 | free(listeners); 237 | file_cache_destroy(&uring); 238 | 239 | return EXIT_SUCCESS; 240 | } 241 | -------------------------------------------------------------------------------- /src/event/mod.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: EVENT -- Event submission. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #define _GNU_SOURCE 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "config.h" 37 | #include "event.h" 38 | #include "event/internal.h" 39 | #include "forward.h" 40 | 41 | A3Pool* EVENT_POOL; 42 | 43 | static Event* event_new(EventTarget* target, EventHandler handler, void* handler_ctx, 44 | int32_t expected_return, bool queue) { 45 | Event* event = (Event*)a3_pool_alloc_block(EVENT_POOL); 46 | 47 | event->success = true; 48 | event->expected_return = expected_return; 49 | event->target = target; 50 | event->handler = handler; 51 | event->handler_ctx = handler_ctx; 52 | 53 | if (queue) 54 | a3_sll_push(target, &event->queue_link); 55 | 56 | return event; 57 | } 58 | 59 | Event* event_from_link(A3SLink* link) { 60 | if (!link) 61 | return NULL; 62 | return A3_CONTAINER_OF(link, Event, queue_link); 63 | } 64 | 65 | void event_free(Event* event) { 66 | assert(event); 67 | 68 | a3_pool_free_block(EVENT_POOL, event); 69 | } 70 | 71 | // Get an SQE. This may trigger a submission in an attempt to clear the SQ if it is full. This /can/ 72 | // return a null pointer if the SQ is full and, for whatever reason, it does not empty in time. 73 | static struct io_uring_sqe* event_get_sqe(struct io_uring* uring) { 74 | struct io_uring_sqe* ret = io_uring_get_sqe(uring); 75 | // Try to submit events until an SQE is available or too many retries have elapsed. 76 | for (size_t retries = 0; !ret && retries < URING_SQE_RETRY_MAX; 77 | ret = io_uring_get_sqe(uring), retries++) 78 | if (io_uring_submit(uring) < 0) 79 | break; 80 | if (!ret) 81 | A3_WARN("SQ full."); 82 | return ret; 83 | } 84 | 85 | static bool event_submit(EventTarget* target, struct io_uring_sqe* sqe, EventHandler handler, 86 | void* handler_ctx, int32_t expected_return, bool queue) { 87 | Event* event = event_new(target, handler, handler_ctx, expected_return, queue); 88 | A3_TRYB(event); 89 | io_uring_sqe_set_data(sqe, event); 90 | return true; 91 | } 92 | 93 | bool event_accept_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 94 | void* handler_ctx, fd socket, struct sockaddr_in* out_client_addr, 95 | socklen_t* inout_addr_len) { 96 | assert(target); 97 | assert(uring); 98 | assert(handler); 99 | assert(out_client_addr); 100 | 101 | struct io_uring_sqe* sqe = event_get_sqe(uring); 102 | A3_TRYB(sqe); 103 | 104 | io_uring_prep_accept(sqe, socket, (struct sockaddr*)out_client_addr, inout_addr_len, 0); 105 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_NONNEGATIVE, true); 106 | } 107 | 108 | static bool event_close_fallback(EventTarget* target, EventHandler handler, struct io_uring* uring, 109 | void* handler_ctx, fd file) { 110 | assert(file >= 0); 111 | 112 | int32_t status = close(file); 113 | bool success = status == 0; 114 | 115 | if (handler) 116 | handler(target, uring, handler_ctx, success, status); 117 | 118 | return success; 119 | } 120 | 121 | bool event_close_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 122 | void* handler_ctx, fd file, uint32_t sqe_flags, bool fallback_sync) { 123 | assert(uring); 124 | assert(file >= 0); 125 | 126 | struct io_uring_sqe* sqe = event_get_sqe(uring); 127 | if (!sqe && fallback_sync) 128 | return event_close_fallback(target, handler, uring, handler_ctx, file); 129 | 130 | io_uring_prep_close(sqe, file); 131 | io_uring_sqe_set_flags(sqe, sqe_flags); 132 | 133 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_NONNEGATIVE, true); 134 | } 135 | 136 | bool event_openat_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 137 | void* handler_ctx, fd dir, A3CString path, int32_t open_flags, 138 | mode_t mode) { 139 | assert(target); 140 | assert(uring); 141 | assert(handler); 142 | assert(path.ptr); 143 | 144 | struct io_uring_sqe* sqe = event_get_sqe(uring); 145 | A3_TRYB(sqe); 146 | 147 | io_uring_prep_openat(sqe, dir, a3_string_cstr(path), open_flags, mode); 148 | 149 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_NONNEGATIVE, true); 150 | } 151 | 152 | bool event_read_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 153 | void* handler_ctx, fd file, A3String out_data, size_t nbytes, off_t offset, 154 | uint32_t sqe_flags) { 155 | assert(target); 156 | assert(uring); 157 | assert(handler); 158 | assert(file >= 0); 159 | assert(out_data.ptr); 160 | assert(offset >= 0); 161 | 162 | struct io_uring_sqe* sqe = event_get_sqe(uring); 163 | A3_TRYB(sqe); 164 | 165 | uint32_t read_size = (uint32_t)MIN(out_data.len, nbytes); 166 | io_uring_prep_read(sqe, file, out_data.ptr, read_size, (uint64_t)offset); 167 | io_uring_sqe_set_flags(sqe, sqe_flags); 168 | 169 | return event_submit(target, sqe, handler, handler_ctx, (int32_t)read_size, true); 170 | } 171 | 172 | bool event_recv_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 173 | void* handler_ctx, fd socket, A3String data) { 174 | assert(target); 175 | assert(uring); 176 | assert(handler); 177 | assert(socket >= 0); 178 | assert(data.ptr); 179 | 180 | struct io_uring_sqe* sqe = event_get_sqe(uring); 181 | A3_TRYB(sqe); 182 | 183 | io_uring_prep_recv(sqe, socket, data.ptr, data.len, 0); 184 | 185 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_POSITIVE, true); 186 | } 187 | 188 | bool event_send_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 189 | void* handler_ctx, fd socket, A3CString data, uint32_t send_flags, 190 | uint32_t sqe_flags) { 191 | assert(target); 192 | assert(uring); 193 | assert(handler); 194 | assert(socket >= 0); 195 | assert(data.ptr); 196 | 197 | struct io_uring_sqe* sqe = event_get_sqe(uring); 198 | A3_TRYB(sqe); 199 | 200 | io_uring_prep_send(sqe, socket, data.ptr, data.len, (int32_t)send_flags); 201 | io_uring_sqe_set_flags(sqe, sqe_flags); 202 | 203 | return event_submit(target, sqe, handler, handler_ctx, (int32_t)data.len, true); 204 | } 205 | 206 | bool event_splice_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 207 | void* handler_ctx, fd in, uint64_t off_in, fd out, size_t len, 208 | uint32_t splice_flags, uint32_t sqe_flags) { 209 | assert(target); 210 | assert(uring); 211 | assert(handler); 212 | assert(in >= 0); 213 | assert(out >= 0); 214 | 215 | struct io_uring_sqe* sqe = event_get_sqe(uring); 216 | A3_TRYB(sqe); 217 | 218 | io_uring_prep_splice(sqe, in, (int64_t)off_in, out, -1, (uint32_t)len, 219 | SPLICE_F_MOVE | splice_flags); 220 | io_uring_sqe_set_flags(sqe, sqe_flags); 221 | 222 | return event_submit(target, sqe, handler, handler_ctx, (int32_t)len, true); 223 | } 224 | 225 | bool event_stat_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 226 | void* handler_ctx, A3CString path, uint32_t field_mask, 227 | struct statx* statx_buf, uint32_t sqe_flags) { 228 | assert(target); 229 | assert(uring); 230 | assert(handler); 231 | assert(path.ptr); 232 | assert(field_mask); 233 | assert(statx_buf); 234 | 235 | struct io_uring_sqe* sqe = event_get_sqe(uring); 236 | A3_TRYB(sqe); 237 | 238 | io_uring_prep_statx(sqe, -1, a3_string_cstr(path), 0, field_mask, statx_buf); 239 | io_uring_sqe_set_flags(sqe, sqe_flags); 240 | 241 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_NONNEGATIVE, true); 242 | } 243 | 244 | bool event_timeout_submit(EventTarget* target, struct io_uring* uring, EventHandler handler, 245 | void* handler_ctx, Timespec* threshold, uint32_t timeout_flags) { 246 | assert(target); 247 | assert(uring); 248 | assert(handler); 249 | assert(threshold); 250 | 251 | struct io_uring_sqe* sqe = event_get_sqe(uring); 252 | A3_TRYB(sqe); 253 | 254 | io_uring_prep_timeout(sqe, threshold, 0, timeout_flags); 255 | 256 | return event_submit(target, sqe, handler, handler_ctx, EXPECTED_STATUS_NONE, true); 257 | } 258 | 259 | bool event_cancel_all(EventTarget* target) { 260 | assert(target); 261 | 262 | for (Event* victim = event_from_link(a3_sll_pop(target)); victim; 263 | victim = event_from_link(a3_sll_pop(target))) 264 | victim->target = NULL; 265 | 266 | return true; 267 | } 268 | 269 | Event* event_create(EventTarget* target, EventHandler handler, void* handler_ctx) { 270 | assert(target); 271 | return event_new(target, handler, handler_ctx, EXPECTED_STATUS_NONE, EVENT_NO_QUEUE); 272 | } 273 | 274 | A3SLink* event_queue_link(Event* event) { 275 | assert(event); 276 | return &event->queue_link; 277 | } 278 | -------------------------------------------------------------------------------- /src/http/parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP PARSE -- Incremental HTTP request parser. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "http/parse.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "config.h" 35 | #include "config_runtime.h" 36 | #include "connection.h" 37 | #include "forward.h" 38 | #include "http/connection.h" 39 | #include "http/headers.h" 40 | #include "http/request.h" 41 | #include "http/response.h" 42 | #include "http/types.h" 43 | #include "uri.h" 44 | 45 | static inline A3Buffer* http_request_recv_buf(HttpRequest* req) { 46 | assert(req); 47 | 48 | return &http_request_connection(req)->conn.recv_buf; 49 | } 50 | 51 | // Try to parse the first line of the HTTP request. 52 | HttpRequestStateResult http_request_first_line_parse(HttpRequest* req, struct io_uring* uring) { 53 | assert(req); 54 | assert(uring); 55 | 56 | A3Buffer* buf = http_request_recv_buf(req); 57 | HttpConnection* conn = http_request_connection(req); 58 | HttpResponse* resp = http_request_response(req); 59 | 60 | // If no CRLF has appeared so far, and the length of the data is 61 | // permissible, bail and wait for more. 62 | if (!a3_buf_memmem(buf, HTTP_NEWLINE).ptr) { 63 | if (a3_buf_len(buf) < HTTP_REQUEST_LINE_MAX_LENGTH) 64 | return HTTP_REQUEST_STATE_NEED_DATA; 65 | A3_RET_MAP( 66 | http_response_error_submit(resp, uring, HTTP_STATUS_URI_TOO_LONG, HTTP_RESPONSE_CLOSE), 67 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 68 | } 69 | 70 | conn->method = http_request_method_parse( 71 | A3_S_CONST(a3_buf_token_next(buf, A3_CS(" \r\n"), A3_PRES_END_NO))); 72 | switch (conn->method) { 73 | case HTTP_METHOD_INVALID: 74 | A3_TRACE("Got an invalid method."); 75 | A3_RET_MAP( 76 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 77 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 78 | case HTTP_METHOD_UNKNOWN: 79 | A3_TRACE("Got an unknown method."); 80 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_NOT_IMPLEMENTED, 81 | HTTP_RESPONSE_ALLOW), 82 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 83 | default: 84 | // Valid methods. 85 | break; 86 | } 87 | 88 | A3String target_str = a3_buf_token_next(buf, A3_CS(" \r\n"), A3_PRES_END_NO); 89 | if (!target_str.ptr) 90 | A3_RET_MAP( 91 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 92 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 93 | switch (uri_parse(&req->target, target_str)) { 94 | case URI_PARSE_ERROR: 95 | case URI_PARSE_BAD_URI: 96 | A3_RET_MAP( 97 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_ALLOW), 98 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 99 | case URI_PARSE_TOO_LONG: 100 | A3_RET_MAP( 101 | http_response_error_submit(resp, uring, HTTP_STATUS_URI_TOO_LONG, HTTP_RESPONSE_CLOSE), 102 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 103 | default: 104 | break; 105 | } 106 | 107 | req->target_path = uri_path_if_contained(&req->target, CONFIG.web_root); 108 | if (!req->target_path.ptr) 109 | A3_RET_MAP( 110 | http_response_error_submit(resp, uring, HTTP_STATUS_NOT_FOUND, HTTP_RESPONSE_ALLOW), 111 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 112 | 113 | // Need to only eat one "\r\n". 114 | conn->version = 115 | http_version_parse(A3_S_CONST(a3_buf_token_next(buf, HTTP_NEWLINE, A3_PRES_END_YES))); 116 | if (!a3_buf_consume(buf, HTTP_NEWLINE) || conn->version == HTTP_VERSION_INVALID || 117 | conn->version == HTTP_VERSION_UNKNOWN || 118 | (conn->version == HTCPCP_VERSION_10 && conn->method != HTTP_METHOD_BREW)) { 119 | A3_TRACE("Got a bad HTTP version."); 120 | A3_RET_MAP(http_response_error_submit(resp, uring, 121 | (conn->version == HTTP_VERSION_INVALID) 122 | ? HTTP_STATUS_BAD_REQUEST 123 | : HTTP_STATUS_VERSION_NOT_SUPPORTED, 124 | HTTP_RESPONSE_CLOSE), 125 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 126 | } else if (conn->version == HTTP_VERSION_10) { 127 | // HTTP/1.0 is 'Connection: Close' by default. 128 | conn->connection_type = HTTP_CONNECTION_TYPE_CLOSE; 129 | } 130 | 131 | conn->state = HTTP_CONNECTION_PARSED_FIRST_LINE; 132 | 133 | return HTTP_REQUEST_STATE_DONE; 134 | } 135 | 136 | // Try to ingest all the headers. 137 | HttpRequestStateResult http_request_headers_add(HttpRequest* req, struct io_uring* uring) { 138 | assert(req); 139 | assert(uring); 140 | 141 | A3Buffer* buf = http_request_recv_buf(req); 142 | HttpResponse* resp = http_request_response(req); 143 | 144 | if (!a3_buf_memmem(buf, HTTP_NEWLINE).ptr) { 145 | if (a3_buf_len(buf) < HTTP_REQUEST_HEADER_MAX_LENGTH) 146 | return HTTP_REQUEST_STATE_NEED_DATA; 147 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_HEADER_TOO_LARGE, 148 | HTTP_RESPONSE_CLOSE), 149 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 150 | } 151 | 152 | if (!a3_buf_consume(buf, HTTP_NEWLINE)) { 153 | while (buf->data.ptr[buf->head] != '\r' && buf->head != buf->tail) { 154 | if (!a3_buf_memmem(buf, HTTP_NEWLINE).ptr) 155 | return HTTP_REQUEST_STATE_NEED_DATA; 156 | 157 | A3CString name = A3_S_CONST(a3_buf_token_next(buf, A3_CS(": "), A3_PRES_END_NO)); 158 | A3CString value = A3_S_CONST(a3_buf_token_next(buf, HTTP_NEWLINE, A3_PRES_END_YES)); 159 | A3_TRYB_MAP(a3_buf_consume(buf, HTTP_NEWLINE), HTTP_REQUEST_STATE_ERROR); 160 | 161 | // RFC7230 § 5.4: Invalid field-value -> 400. 162 | if (!name.ptr || !value.ptr) 163 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, 164 | HTTP_RESPONSE_CLOSE), 165 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 166 | 167 | if (!http_header_add(&req->headers, name, value)) 168 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_SERVER_ERROR, 169 | HTTP_RESPONSE_CLOSE), 170 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 171 | } 172 | 173 | if (!a3_buf_consume(buf, HTTP_NEWLINE)) 174 | return HTTP_REQUEST_STATE_NEED_DATA; 175 | } 176 | 177 | http_request_connection(req)->state = HTTP_CONNECTION_ADDED_HEADERS; 178 | 179 | return HTTP_REQUEST_STATE_DONE; 180 | } 181 | 182 | // Parse all the headers. 183 | HttpRequestStateResult http_request_headers_parse(HttpRequest* req, struct io_uring* uring) { 184 | assert(req); 185 | assert(uring); 186 | 187 | HttpConnection* conn = http_request_connection(req); 188 | HttpResponse* resp = http_request_response(req); 189 | HttpHeaders* headers = &req->headers; 190 | 191 | conn->connection_type = http_header_connection(headers); 192 | if (conn->connection_type == HTTP_CONNECTION_TYPE_UNSPECIFIED) 193 | conn->connection_type = (conn->version == HTTP_VERSION_10) 194 | ? HTTP_CONNECTION_TYPE_CLOSE 195 | : HTTP_CONNECTION_TYPE_KEEP_ALIVE; 196 | else if (conn->connection_type == HTTP_CONNECTION_TYPE_INVALID) { 197 | conn->connection_type = (conn->version == HTTP_VERSION_10) 198 | ? HTTP_CONNECTION_TYPE_CLOSE 199 | : HTTP_CONNECTION_TYPE_KEEP_ALIVE; 200 | A3_RET_MAP( 201 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 202 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 203 | } 204 | 205 | req->host = A3_S_CONST(http_header_get(headers, A3_CS("Host"))); 206 | // RFC7230 § 5.4: more than one Host header -> 400. 207 | if (req->host.ptr && a3_string_rchr(req->host, ',').ptr) { 208 | req->host = A3_CS_NULL; 209 | A3_RET_MAP( 210 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 211 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 212 | } 213 | // ibid. HTTP/1.1 messages must have a Host header. 214 | if (conn->version == HTTP_VERSION_11 && !req->host.ptr) 215 | A3_RET_MAP( 216 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 217 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 218 | 219 | req->transfer_encodings = http_header_transfer_encodings(headers); 220 | if (!req->transfer_encodings) 221 | A3_RET_MAP( 222 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 223 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 224 | // RFC7230 § 3.3.3, step 3: Transfer-Encoding without chunked is invalid in 225 | // a request, and the server MUST respond with a 400. 226 | if ((req->transfer_encodings & ~HTTP_TRANSFER_ENCODING_IDENTITY) && 227 | !(req->transfer_encodings & HTTP_TRANSFER_ENCODING_CHUNKED)) 228 | A3_RET_MAP( 229 | http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, HTTP_RESPONSE_CLOSE), 230 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 231 | // TODO: Support other transfer encodings. 232 | if ((req->transfer_encodings & ~HTTP_TRANSFER_ENCODING_IDENTITY)) 233 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_NOT_IMPLEMENTED, 234 | HTTP_RESPONSE_CLOSE), 235 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 236 | 237 | // ibid. Transfer-Encoding overrides Content-Length. 238 | if (!(req->transfer_encodings & ~HTTP_TRANSFER_ENCODING_IDENTITY)) { 239 | req->content_length = http_header_content_length(headers); 240 | if (req->content_length == HTTP_CONTENT_LENGTH_INVALID) 241 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_BAD_REQUEST, 242 | HTTP_RESPONSE_CLOSE), 243 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 244 | if (req->content_length != HTTP_CONTENT_LENGTH_UNSPECIFIED && 245 | (size_t)req->content_length > HTTP_REQUEST_CONTENT_MAX_LENGTH) 246 | A3_RET_MAP(http_response_error_submit(resp, uring, HTTP_STATUS_PAYLOAD_TOO_LARGE, 247 | HTTP_RESPONSE_CLOSE), 248 | HTTP_REQUEST_STATE_BAIL, HTTP_REQUEST_STATE_ERROR); 249 | // ibid. step 6: default to Content-Length of 0. 250 | if (req->content_length == HTTP_CONTENT_LENGTH_UNSPECIFIED) 251 | req->content_length = 0; 252 | } 253 | 254 | conn->state = HTTP_CONNECTION_PARSED_HEADERS; 255 | 256 | return HTTP_REQUEST_STATE_DONE; 257 | } 258 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: CONNECTION -- Abstract connection on top of the event 3 | * interface. 4 | * 5 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 6 | * 7 | * This program is free software: you can redistribute it and/or modify it under 8 | * the terms of the GNU Affero General Public License as published by the Free 9 | * Software Foundation, either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #define _GNU_SOURCE // For SPLICE_F_MORE. 22 | 23 | #include "connection.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include "config.h" 38 | #include "event.h" 39 | #include "forward.h" 40 | #include "http/connection.h" 41 | #include "http/request.h" 42 | #include "http/response.h" 43 | #include "http/types.h" 44 | #include "listen.h" 45 | #include "timeout.h" 46 | 47 | #include 48 | 49 | static bool connection_timeout_submit(Connection* conn, struct io_uring* uring, time_t delay); 50 | 51 | static void connection_recv_handle(EventTarget*, struct io_uring*, void* ctx, bool success, 52 | int32_t status); 53 | static void connection_timeout_handle(Timeout*, struct io_uring*); 54 | 55 | static TimeoutQueue connection_timeout_queue; 56 | 57 | void connection_timeout_init() { timeout_queue_init(&connection_timeout_queue); } 58 | 59 | bool connection_init(Connection* conn) { 60 | assert(conn); 61 | 62 | A3_TRYB(a3_buf_init(&conn->recv_buf, RECV_BUF_INITIAL_CAPACITY, RECV_BUF_MAX_CAPACITY)); 63 | return a3_buf_init(&conn->send_buf, SEND_BUF_INITIAL_CAPACITY, SEND_BUF_MAX_CAPACITY); 64 | } 65 | 66 | bool connection_reset(Connection* conn, struct io_uring* uring) { 67 | assert(conn); 68 | assert(uring); 69 | 70 | if (a3_buf_initialized(&conn->recv_buf)) 71 | a3_buf_reset(&conn->recv_buf); 72 | if (a3_buf_initialized(&conn->recv_buf)) 73 | a3_buf_reset(&conn->send_buf); 74 | if (timeout_is_scheduled(&conn->timeout)) { 75 | timeout_cancel(&conn->timeout); 76 | return connection_timeout_submit(conn, uring, CONNECTION_TIMEOUT); 77 | } 78 | 79 | return true; 80 | } 81 | 82 | static inline void connection_drop(Connection* conn, struct io_uring* uring) { 83 | assert(conn); 84 | assert(uring); 85 | 86 | A3_TRACE("Dropping connection."); 87 | // TODO: Make generic. 88 | http_connection_free(connection_http(conn), uring); 89 | } 90 | 91 | #define CTRYB(CONN, URING, T) \ 92 | do { \ 93 | if (!(T)) { \ 94 | connection_drop((CONN), (URING)); \ 95 | return; \ 96 | } \ 97 | } while (0) 98 | 99 | #define CTRYB_MAP(CONN, URING, T, E) \ 100 | do { \ 101 | if (!(T)) { \ 102 | connection_drop((CONN), (URING)); \ 103 | return (E); \ 104 | } \ 105 | } while (0) 106 | 107 | // Call a connection callback, and close on failure. 108 | static inline void connection_handler_call(Connection* conn, struct io_uring* uring, 109 | ConnectionHandler handler, bool success, 110 | int32_t status) { 111 | assert(conn); 112 | assert(uring); 113 | assert(handler); 114 | 115 | if (handler(conn, uring, success, status)) 116 | return; 117 | 118 | connection_drop(conn, uring); 119 | } 120 | 121 | // Handle the completion of an ACCEPT event. 122 | static void connection_accept_handle(EventTarget* target, struct io_uring* uring, void* handler, 123 | bool success, int32_t status) { 124 | assert(target); 125 | assert(uring); 126 | 127 | Connection* conn = EVT_PTR(target, Connection); 128 | 129 | A3_TRACE("Accept connection."); 130 | conn->listener->accept_queued = false; 131 | if (!success) { 132 | A3_ERRNO(-status, "accept failed"); 133 | connection_drop(conn, uring); 134 | return; 135 | } 136 | conn->socket = (fd)status; 137 | 138 | CTRYB(conn, uring, connection_timeout_submit(conn, uring, CONNECTION_TIMEOUT)); 139 | CTRYB(conn, uring, connection_recv_submit(conn, uring, handler)); 140 | } 141 | 142 | static void connection_close_handle(EventTarget* target, struct io_uring* uring, void* ctx, 143 | bool success, int32_t status) { 144 | assert(target); 145 | assert(uring); 146 | 147 | Connection* conn = EVT_PTR(target, Connection); 148 | 149 | conn->socket = -1; 150 | if (status < 0) 151 | A3_ERRNO(-status, "close failed"); 152 | 153 | if (ctx) 154 | connection_handler_call(conn, uring, ctx, success, status); 155 | } 156 | 157 | static void connection_recv_handle(EventTarget* target, struct io_uring* uring, void* ctx, 158 | bool success, int32_t status) { 159 | assert(target); 160 | assert(uring); 161 | assert(ctx); 162 | 163 | Connection* conn = EVT_PTR(target, Connection); 164 | 165 | if (status == 0 || status == -ECONNRESET) { 166 | A3_TRACE("Connection closed by peer."); 167 | connection_drop(conn, uring); 168 | return; 169 | } 170 | if (!success) { 171 | A3_ERRNO(-status, "recv failed"); 172 | connection_drop(conn, uring); 173 | return; 174 | } 175 | 176 | a3_buf_wrote(&conn->recv_buf, (size_t)status); 177 | 178 | connection_handler_call(conn, uring, ctx, success, status); 179 | } 180 | 181 | static void connection_send_handle(EventTarget* target, struct io_uring* uring, void* ctx, 182 | bool success, int32_t status) { 183 | assert(target); 184 | assert(uring); 185 | 186 | Connection* conn = EVT_PTR(target, Connection); 187 | 188 | if (status < 0) { 189 | A3_ERRNO(-status, "send failed"); 190 | connection_drop(conn, uring); 191 | return; 192 | } 193 | if (!success) { 194 | A3_ERROR_F("Short send of %d.", status); 195 | connection_drop(conn, uring); 196 | return; 197 | } 198 | 199 | a3_buf_read(&conn->send_buf, (size_t)status); 200 | 201 | if (ctx) 202 | connection_handler_call(conn, uring, ctx, success, status); 203 | } 204 | 205 | static void connection_splice_handle(EventTarget* target, struct io_uring* uring, void* ctx, 206 | bool success, int32_t status) { 207 | assert(target); 208 | assert(uring); 209 | 210 | Connection* conn = EVT_PTR(target, Connection); 211 | 212 | if (status < 0) { 213 | A3_ERRNO(-status, "splice failed"); 214 | connection_drop(conn, uring); 215 | return; 216 | } 217 | if (!success) { 218 | A3_ERROR_F("Short splice out of %d.", status); 219 | connection_drop(conn, uring); 220 | } 221 | 222 | if (ctx) 223 | connection_handler_call(conn, uring, ctx, success, status); 224 | } 225 | 226 | static void connection_splice_in_handle(EventTarget* target, struct io_uring* uring, void* ctx, 227 | bool success, int32_t status) { 228 | assert(target); 229 | assert(uring); 230 | 231 | Connection* conn = EVT_PTR(target, Connection); 232 | 233 | if (status < 0) { 234 | A3_ERRNO(-status, "splice in failed"); 235 | connection_drop(conn, uring); 236 | return; 237 | } 238 | if (!success) 239 | A3_WARN_F("Short splice of %d.", status); 240 | 241 | if (ctx) { 242 | ConnectionSpliceHandler handler = ctx; 243 | if (!handler(conn, uring, SPLICE_IN, success, status)) 244 | connection_drop(conn, uring); 245 | } 246 | } 247 | 248 | static void connection_splice_out_handle(EventTarget* target, struct io_uring* uring, void* ctx, 249 | bool success, int32_t status) { 250 | assert(target); 251 | assert(uring); 252 | 253 | Connection* conn = EVT_PTR(target, Connection); 254 | 255 | if (status < 0) { 256 | A3_ERRNO(-status, "splice out failed"); 257 | connection_drop(conn, uring); 258 | return; 259 | } 260 | if (!success) { 261 | A3_ERROR_F("Short splice out of %d.", status); 262 | connection_drop(conn, uring); 263 | return; 264 | } 265 | 266 | if (ctx) { 267 | ConnectionSpliceHandler handler = ctx; 268 | if (!handler(conn, uring, SPLICE_OUT, success, status)) 269 | connection_drop(conn, uring); 270 | } 271 | } 272 | 273 | static void connection_timeout_handle(Timeout* timeout, struct io_uring* uring) { 274 | assert(timeout); 275 | assert(uring); 276 | 277 | Connection* conn = A3_CONTAINER_OF(timeout, Connection, timeout); 278 | 279 | if (!http_response_error_submit(&connection_http(conn)->response, uring, HTTP_STATUS_TIMEOUT, 280 | HTTP_RESPONSE_CLOSE)) 281 | connection_drop(conn, uring); 282 | } 283 | 284 | // Submit an ACCEPT on the uring. The handler given will only be called upon the arrival of input 285 | // data. 286 | Connection* connection_accept_submit(Listener* listener, struct io_uring* uring, 287 | ConnectionHandler handler) { 288 | assert(listener); 289 | assert(uring); 290 | assert(handler); 291 | 292 | // TODO: This needs to be generic over connection types. Perhaps just take as a parameter. 293 | Connection* ret = (Connection*)http_connection_new(); 294 | if (!ret) 295 | return NULL; 296 | 297 | ret->listener = listener; 298 | ret->transport = listener->transport; 299 | ret->addr_len = sizeof(ret->client_addr); 300 | 301 | CTRYB_MAP(ret, uring, 302 | event_accept_submit(EVT(ret), uring, connection_accept_handle, handler, 303 | listener->socket, &ret->client_addr, &ret->addr_len), 304 | NULL); 305 | 306 | return ret; 307 | } 308 | 309 | // Submit a request to receive as much data as the buffer can handle. 310 | bool connection_recv_submit(Connection* conn, struct io_uring* uring, ConnectionHandler handler) { 311 | assert(conn); 312 | assert(uring); 313 | assert(handler); 314 | 315 | return event_recv_submit(EVT(conn), uring, connection_recv_handle, handler, conn->socket, 316 | a3_buf_write_ptr(&conn->recv_buf)); 317 | } 318 | 319 | bool connection_send_submit(Connection* conn, struct io_uring* uring, ConnectionHandler handler, 320 | uint32_t send_flags, uint8_t sqe_flags) { 321 | assert(conn); 322 | assert(uring); 323 | return event_send_submit(EVT(conn), uring, connection_send_handle, handler, conn->socket, 324 | a3_buf_read_ptr(&conn->send_buf), send_flags, sqe_flags); 325 | } 326 | 327 | #define PIPE_BUF_SIZE 65536ULL 328 | 329 | // Wraps splice so it can be used without a pipe. 330 | bool connection_splice_submit(Connection* conn, struct io_uring* uring, 331 | ConnectionSpliceHandler splice_handler, ConnectionHandler handler, 332 | fd src, size_t file_offset, size_t len, uint8_t sqe_flags) { 333 | assert(conn); 334 | assert(uring); 335 | assert(splice_handler); 336 | assert(handler); 337 | 338 | if (!conn->pipe[0] && !conn->pipe[1]) { 339 | A3_TRACE("Opening pipe."); 340 | if (pipe(conn->pipe) < 0) { 341 | A3_ERRNO(errno, "unable to open pipe"); 342 | return false; 343 | } 344 | } 345 | 346 | for (size_t remaining = len; remaining > 0;) { 347 | size_t req_len = MIN(PIPE_BUF_SIZE, remaining); 348 | bool more = remaining > PIPE_BUF_SIZE; 349 | A3_TRYB_MSG(event_splice_submit(EVT(conn), uring, connection_splice_in_handle, 350 | splice_handler, src, len - remaining + file_offset, 351 | conn->pipe[1], req_len, 0, sqe_flags | IOSQE_IO_LINK), 352 | A3_LOG_ERROR, "Failed to submit splice in."); 353 | A3_TRYB_MSG( 354 | event_splice_submit(EVT(conn), uring, 355 | more ? connection_splice_out_handle : connection_splice_handle, 356 | more ? (void*)splice_handler : (void*)handler, conn->pipe[0], 357 | (uint64_t)-1, conn->socket, req_len, more ? SPLICE_F_MORE : 0, 358 | sqe_flags | (more ? IOSQE_IO_LINK : 0)), 359 | A3_LOG_ERROR, "Failed to submit splice out."); 360 | if (more) 361 | remaining -= PIPE_BUF_SIZE; 362 | else 363 | remaining = 0; 364 | } 365 | 366 | return true; 367 | } 368 | 369 | // Retry an interrupted splice chain. 370 | bool connection_splice_retry(Connection* conn, struct io_uring* uring, 371 | ConnectionSpliceHandler splice_handler, ConnectionHandler handler, 372 | fd src, size_t in_buf, size_t file_offset, size_t remaining, 373 | uint8_t sqe_flags) { 374 | assert(conn); 375 | assert(uring); 376 | assert(splice_handler); 377 | assert(handler); 378 | 379 | bool more = remaining > 0; 380 | 381 | // Clear out data currently in the pipe. 382 | if (in_buf && 383 | !event_splice_submit(EVT(conn), uring, 384 | more ? connection_splice_out_handle : connection_splice_handle, 385 | more ? (void*)splice_handler : (void*)handler, conn->pipe[0], 386 | (uint64_t)-1, conn->socket, in_buf, more ? SPLICE_F_MORE : 0, 387 | sqe_flags | (more ? IOSQE_IO_LINK : 0))) { 388 | A3_ERROR("Failed to submit splice."); 389 | return false; 390 | } 391 | 392 | // Re-submit the chain. 393 | if (more) 394 | return connection_splice_submit(conn, uring, splice_handler, handler, src, file_offset, 395 | remaining, sqe_flags); 396 | return true; 397 | } 398 | 399 | static bool connection_timeout_submit(Connection* conn, struct io_uring* uring, time_t delay) { 400 | assert(conn); 401 | assert(uring); 402 | 403 | struct timespec t; 404 | A3_UNWRAPSD(clock_gettime(CLOCK_MONOTONIC, &t)); 405 | 406 | conn->timeout.threshold.tv_sec = t.tv_sec + delay; 407 | conn->timeout.threshold.tv_nsec = t.tv_nsec; 408 | conn->timeout.fire = connection_timeout_handle; 409 | return timeout_schedule(&connection_timeout_queue, &conn->timeout, uring); 410 | } 411 | 412 | bool connection_close_submit(Connection* conn, struct io_uring* uring, ConnectionHandler handler) { 413 | assert(conn); 414 | assert(uring); 415 | assert(handler); 416 | 417 | if (timeout_is_scheduled(&conn->timeout)) 418 | timeout_cancel(&conn->timeout); 419 | 420 | return event_close_submit(EVT(conn), uring, connection_close_handle, (void*)handler, 421 | conn->socket, 0, EVENT_FALLBACK_ALLOW); 422 | } 423 | -------------------------------------------------------------------------------- /src/http/response.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SHORT CIRCUIT: HTTP RESPONSE -- HTTP response submission. 3 | * 4 | * Copyright (c) 2020-2021, Alex O'Brien <3541ax@gmail.com> 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Affero General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "http/response.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "config.h" 41 | #include "connection.h" 42 | #include "event.h" 43 | #include "file.h" 44 | #include "http/connection.h" 45 | #include "http/request.h" 46 | #include "http/types.h" 47 | 48 | #include 49 | 50 | static bool http_response_splice_handle(Connection*, struct io_uring*, bool success, 51 | int32_t status); 52 | 53 | static inline HttpConnection* http_response_connection(HttpResponse* resp) { 54 | assert(resp); 55 | 56 | return A3_CONTAINER_OF(resp, HttpConnection, response); 57 | } 58 | 59 | static inline A3Buffer* http_response_send_buf(HttpResponse* resp) { 60 | return &http_response_connection(resp)->conn.send_buf; 61 | } 62 | 63 | void http_response_init(HttpResponse* resp) { 64 | assert(resp); 65 | 66 | resp->content_type = HTTP_CONTENT_TYPE_TEXT_HTML; 67 | resp->transfer_encodings = HTTP_TRANSFER_ENCODING_IDENTITY; 68 | resp->body_sent = 0; 69 | } 70 | 71 | void http_response_reset(HttpResponse* resp) { 72 | assert(resp); 73 | 74 | http_response_init(resp); 75 | } 76 | 77 | // Respond to a mid-response event. 78 | static bool http_response_handle(Connection* connection, struct io_uring* uring, bool success, 79 | int32_t status) { 80 | assert(connection); 81 | assert(uring); 82 | assert(success); 83 | (void)success; 84 | (void)status; 85 | 86 | HttpConnection* conn = connection_http(connection); 87 | HttpResponse* resp = &conn->response; 88 | 89 | switch (conn->state) { 90 | case HTTP_CONNECTION_OPENING_FILE: 91 | return http_response_file_submit(resp, uring); 92 | case HTTP_CONNECTION_RESPONDING: 93 | if (http_connection_keep_alive(conn)) { 94 | A3_TRYB(http_connection_reset(conn, uring)); 95 | A3_TRYB(http_connection_init(conn)); 96 | return connection_recv_submit(&conn->conn, uring, http_request_handle); 97 | } 98 | 99 | return http_connection_close_submit(conn, uring); 100 | case HTTP_CONNECTION_CLOSING: 101 | return true; 102 | default: 103 | A3_PANIC_FMT("Invalid state in response_handle: %d.", conn->state); 104 | } 105 | } 106 | 107 | static bool http_response_splice_chain_handle(Connection* connection, struct io_uring* uring, 108 | SpliceDirection dir, bool success, int32_t status) { 109 | assert(connection); 110 | assert(uring); 111 | assert(status >= 0); 112 | 113 | HttpConnection* conn = connection_http(connection); 114 | 115 | if (dir == SPLICE_IN) 116 | conn->response.body_sent += (size_t)status; 117 | 118 | if (!success) { 119 | A3_TRACE_F("Short splice %s of %d.", (dir == SPLICE_IN) ? "IN" : "OUT", status); 120 | if (dir == SPLICE_OUT) { 121 | A3_ERROR("TODO: Handle short splice out."); 122 | return false; 123 | } 124 | 125 | size_t sent = conn->response.body_sent; 126 | size_t remaining = file_handle_stat(conn->target_file)->stx_size - sent; 127 | A3_TRYB(connection_splice_retry( 128 | &conn->conn, uring, http_response_splice_chain_handle, http_response_splice_handle, 129 | file_handle_fd(conn->target_file), (size_t)status, sent, remaining, 130 | !http_connection_keep_alive(conn) ? IOSQE_IO_LINK : 0)); 131 | if (!http_connection_keep_alive(conn)) 132 | return http_connection_close_submit(conn, uring); 133 | return true; 134 | } 135 | 136 | return true; 137 | } 138 | 139 | static bool http_response_splice_handle(Connection* connection, struct io_uring* uring, 140 | bool success, int32_t status) { 141 | assert(connection); 142 | assert(uring); 143 | assert(status >= 0); 144 | 145 | A3_TRYB(http_response_splice_chain_handle(connection, uring, SPLICE_OUT, success, status)); 146 | return http_response_handle(connection, uring, success, status); 147 | } 148 | 149 | // Write the status line to the send buffer. 150 | static bool http_response_prep_status_line(HttpResponse* resp, HttpStatus status) { 151 | assert(resp); 152 | assert(status != HTTP_STATUS_INVALID); 153 | 154 | A3Buffer* buf = http_response_send_buf(resp); 155 | uint16_t status_code = http_status_code(status); 156 | 157 | A3_TRYB(a3_buf_write_str(buf, http_version_string(http_response_connection(resp)->version))); 158 | A3_TRYB(a3_buf_write_byte(buf, ' ')); 159 | A3_TRYB(a3_buf_write_num(buf, status_code)); 160 | A3_TRYB(a3_buf_write_byte(buf, ' ')); 161 | 162 | A3CString reason = http_status_reason(status); 163 | if (!reason.ptr) { 164 | A3_WARN_F("Invalid HTTP status %d.", status_code); 165 | return false; 166 | } 167 | A3_TRYB(a3_buf_write_str(buf, reason)); 168 | return a3_buf_write_str(buf, HTTP_NEWLINE); 169 | } 170 | 171 | static bool http_response_prep_header(HttpResponse* resp, A3CString name, A3CString value) { 172 | assert(resp); 173 | assert(name.ptr); 174 | assert(value.ptr); 175 | 176 | A3Buffer* buf = http_response_send_buf(resp); 177 | 178 | A3_TRYB(a3_buf_write_str(buf, name)); 179 | A3_TRYB(a3_buf_write_str(buf, A3_CS(": "))); 180 | A3_TRYB(a3_buf_write_str(buf, value)); 181 | return a3_buf_write_str(buf, HTTP_NEWLINE); 182 | } 183 | 184 | A3_FORMAT_FN(3, 4) 185 | static bool http_response_prep_header_fmt(HttpResponse* resp, A3CString name, const char* fmt, 186 | ...) { 187 | assert(resp); 188 | assert(name.ptr); 189 | assert(fmt); 190 | 191 | A3Buffer* buf = http_response_send_buf(resp); 192 | A3_TRYB(a3_buf_write_str(buf, name)); 193 | A3_TRYB(a3_buf_write_str(buf, A3_CS(": "))); 194 | 195 | va_list args; 196 | va_start(args, fmt); 197 | 198 | bool ret = a3_buf_write_vfmt(buf, fmt, args); 199 | ret = ret && a3_buf_write_str(buf, HTTP_NEWLINE); 200 | 201 | va_end(args); 202 | return ret; 203 | } 204 | 205 | static bool http_response_prep_header_num(HttpResponse* resp, A3CString name, size_t value) { 206 | assert(resp); 207 | assert(name.ptr); 208 | 209 | A3Buffer* buf = http_response_send_buf(resp); 210 | 211 | A3_TRYB(a3_buf_write_str(buf, name)); 212 | A3_TRYB(a3_buf_write_str(buf, A3_CS(": "))); 213 | A3_TRYB(a3_buf_write_num(buf, value)); 214 | return a3_buf_write_str(buf, HTTP_NEWLINE); 215 | } 216 | 217 | static bool http_response_prep_header_timestamp(HttpResponse* resp, A3CString name, time_t tv) { 218 | assert(resp); 219 | 220 | static A3_THREAD_LOCAL struct { 221 | time_t tv; 222 | uint8_t buf[HTTP_TIME_BUF_LENGTH]; 223 | size_t len; 224 | } TIMES[HTTP_TIME_CACHE] = { { 0, { '\0' }, 0 } }; 225 | 226 | size_t i = (size_t)tv % HTTP_TIME_CACHE; 227 | if (TIMES[i].tv != tv) 228 | A3_UNWRAPN(TIMES[i].len, strftime((char*)TIMES[i].buf, HTTP_TIME_BUF_LENGTH, 229 | HTTP_TIME_FORMAT, gmtime(&tv))); 230 | 231 | return http_response_prep_header(resp, name, 232 | (A3CString) { .ptr = TIMES[i].buf, .len = TIMES[i].len }); 233 | } 234 | 235 | static bool http_response_prep_header_date(HttpResponse* resp) { 236 | assert(resp); 237 | 238 | static A3_THREAD_LOCAL uint8_t DATE_BUF[HTTP_TIME_BUF_LENGTH] = { '\0' }; 239 | static A3_THREAD_LOCAL A3CString DATE = A3_CS_NULL; 240 | static A3_THREAD_LOCAL time_t LAST_TIME = 0; 241 | 242 | time_t current = time(NULL); 243 | if (current - LAST_TIME > 2 || !DATE.ptr) { 244 | A3_UNWRAPN(DATE.len, strftime((char*)DATE_BUF, HTTP_TIME_BUF_LENGTH, HTTP_TIME_FORMAT, 245 | gmtime(¤t))); 246 | DATE.ptr = DATE_BUF; 247 | LAST_TIME = current; 248 | } 249 | 250 | return http_response_prep_header(resp, A3_CS("Date"), DATE); 251 | } 252 | 253 | // Write the default status line and headers to the send buffer. 254 | static bool http_response_prep_default_headers(HttpResponse* resp, HttpStatus status, 255 | ssize_t content_length, bool close) { 256 | assert(resp); 257 | 258 | HttpConnection* conn = http_response_connection(resp); 259 | 260 | A3_TRYB(http_response_prep_status_line(resp, status)); 261 | A3_TRYB(http_response_prep_header_date(resp)); 262 | if (close || !http_connection_keep_alive(conn) || content_length < 0) { 263 | conn->connection_type = HTTP_CONNECTION_TYPE_CLOSE; 264 | A3_TRYB(http_response_prep_header(resp, A3_CS("Connection"), A3_CS("Close"))); 265 | } else { 266 | A3_TRYB(http_response_prep_header(resp, A3_CS("Connection"), A3_CS("Keep-Alive"))); 267 | } 268 | if (content_length >= 0) 269 | A3_TRYB( 270 | http_response_prep_header_num(resp, A3_CS("Content-Length"), (size_t)content_length)); 271 | A3_TRYB(http_response_prep_header(resp, A3_CS("Content-Type"), 272 | http_content_type_name(resp->content_type))); 273 | 274 | return true; 275 | } 276 | 277 | // Done writing headers. 278 | static bool http_response_prep_headers_done(HttpResponse* resp) { 279 | assert(resp); 280 | 281 | return a3_buf_write_str(http_response_send_buf(resp), HTTP_NEWLINE); 282 | } 283 | 284 | // Write out a response body to the send buffer. 285 | static bool http_response_prep_body(HttpResponse* resp, A3CString body) { 286 | assert(resp); 287 | 288 | return a3_buf_write_str(http_response_send_buf(resp), body); 289 | } 290 | 291 | static A3CString http_response_error_make_body(HttpResponse* resp, HttpStatus status) { 292 | assert(resp); 293 | assert(status != HTTP_STATUS_INVALID); 294 | 295 | static uint8_t body[HTTP_ERROR_BODY_MAX_LENGTH] = { '\0' }; 296 | ssize_t len = 0; 297 | uint16_t status_code = http_status_code(status); 298 | HttpConnection* conn = http_response_connection(resp); 299 | 300 | // TODO: De-uglify. Probably should load a template from somewhere. 301 | if ((len = snprintf((char*)body, HTTP_ERROR_BODY_MAX_LENGTH, 302 | "\n" 303 | "\n" 304 | "\n" 305 | "Error: %d\n" 306 | "\n" 307 | "\n" 308 | "

%s Error %d

\n" 309 | "

%s.

\n" 310 | "\n" 311 | "\n", 312 | status_code, http_version_string(conn->version).ptr, status_code, 313 | http_status_reason(status).ptr)) > HTTP_ERROR_BODY_MAX_LENGTH || 314 | len < 0) 315 | return A3_CS_NULL; 316 | 317 | return (A3CString) { .ptr = body, .len = (size_t)len }; 318 | } 319 | 320 | // Submit a write event for an HTTP error response. 321 | bool http_response_error_submit(HttpResponse* resp, struct io_uring* uring, HttpStatus status, 322 | bool close) { 323 | assert(resp); 324 | assert(uring); 325 | assert(status != HTTP_STATUS_INVALID); 326 | 327 | HttpConnection* conn = http_response_connection(resp); 328 | if (!http_connection_keep_alive(conn)) 329 | close = true; 330 | 331 | A3_DEBUG_F("HTTP error %d. %s", http_status_code(status), close ? "Closing connection." : ""); 332 | 333 | // Clear any previously written data. 334 | a3_buf_reset(http_response_send_buf(resp)); 335 | 336 | conn->state = HTTP_CONNECTION_RESPONDING; 337 | resp->content_type = HTTP_CONTENT_TYPE_TEXT_HTML; 338 | if (conn->version == HTTP_VERSION_INVALID || conn->version == HTTP_VERSION_UNKNOWN) 339 | conn->version = HTTP_VERSION_11; 340 | 341 | A3CString body = http_response_error_make_body(resp, status); 342 | A3_TRYB(body.ptr); 343 | 344 | A3_TRYB(http_response_prep_default_headers(resp, status, (ssize_t)body.len, close)); 345 | A3_TRYB(http_response_prep_headers_done(resp)); 346 | 347 | if (conn->method != HTTP_METHOD_HEAD) 348 | A3_TRYB(http_response_prep_body(resp, body)); 349 | 350 | A3_TRYB(connection_send_submit(&conn->conn, uring, close ? NULL : http_response_handle, 0, 351 | close ? IOSQE_IO_LINK : 0)); 352 | if (close) 353 | return http_connection_close_submit(conn, uring); 354 | 355 | return true; 356 | } 357 | 358 | static void http_response_file_open_handle(EventTarget* target, struct io_uring* uring, void* ctx, 359 | bool success, int32_t status) { 360 | assert(target); 361 | assert(uring); 362 | (void)ctx; 363 | (void)success; 364 | (void)status; 365 | 366 | Connection* connection = EVT_PTR(target, Connection); 367 | HttpConnection* conn = connection_http(connection); 368 | 369 | if (http_response_file_submit(&conn->response, uring)) 370 | return; 371 | 372 | http_connection_free(conn, uring); 373 | } 374 | 375 | bool http_response_file_submit(HttpResponse* resp, struct io_uring* uring) { 376 | assert(resp); 377 | assert(uring); 378 | 379 | HttpConnection* conn = http_response_connection(resp); 380 | 381 | if (!conn->target_file) { 382 | conn->state = HTTP_CONNECTION_OPENING_FILE; 383 | conn->target_file = file_open(EVT(&conn->conn), uring, http_response_file_open_handle, NULL, 384 | A3_S_CONST(conn->request.target_path), O_RDONLY); 385 | } 386 | 387 | if (!conn->target_file) 388 | return http_response_error_submit(resp, uring, HTTP_STATUS_SERVER_ERROR, 389 | HTTP_RESPONSE_ALLOW); 390 | 391 | if (file_handle_waiting(conn->target_file)) 392 | return true; 393 | 394 | fd target_file = file_handle_fd_unchecked(conn->target_file); 395 | if (target_file < 0) 396 | return http_response_error_submit(resp, uring, HTTP_STATUS_NOT_FOUND, HTTP_RESPONSE_ALLOW); 397 | 398 | bool index = false; 399 | struct statx* stat = file_handle_stat(conn->target_file); 400 | assert(stat->stx_mask & FILE_STATX_MASK); 401 | 402 | if (S_ISDIR(stat->stx_mode)) { 403 | FileHandle* index_file = 404 | file_openat(EVT(&conn->conn), uring, http_response_file_open_handle, NULL, 405 | conn->target_file, INDEX_FILENAME, O_RDONLY); 406 | if (!index_file) 407 | return http_response_error_submit(resp, uring, HTTP_STATUS_SERVER_ERROR, 408 | HTTP_RESPONSE_ALLOW); 409 | 410 | file_handle_close(conn->target_file, uring); 411 | conn->target_file = index_file; 412 | if (file_handle_waiting(conn->target_file)) 413 | return true; 414 | 415 | target_file = file_handle_fd_unchecked(conn->target_file); 416 | stat = file_handle_stat(conn->target_file); 417 | // TODO: Directory listings. 418 | if (target_file < 0) 419 | return http_response_error_submit(resp, uring, HTTP_STATUS_NOT_FOUND, 420 | HTTP_RESPONSE_ALLOW); 421 | } 422 | 423 | if (!S_ISREG(stat->stx_mode)) 424 | return http_response_error_submit(resp, uring, HTTP_STATUS_NOT_FOUND, HTTP_RESPONSE_ALLOW); 425 | 426 | conn->state = HTTP_CONNECTION_RESPONDING; 427 | 428 | if (index) 429 | resp->content_type = HTTP_CONTENT_TYPE_TEXT_HTML; 430 | else 431 | resp->content_type = http_content_type_from_path(file_handle_path(conn->target_file)); 432 | 433 | A3_TRYB(http_response_prep_default_headers(resp, HTTP_STATUS_OK, (ssize_t)stat->stx_size, 434 | HTTP_RESPONSE_ALLOW)); 435 | A3_TRYB( 436 | http_response_prep_header_timestamp(resp, A3_CS("Last-Modified"), stat->stx_mtime.tv_sec)); 437 | A3_TRYB(http_response_prep_header_fmt(resp, A3_CS("Etag"), "\"%lluX%lX%lX\"", stat->stx_ino, 438 | (uint64_t)stat->stx_mtime.tv_sec, 439 | (uint64_t)stat->stx_size)); 440 | A3_TRYB(http_response_prep_headers_done(resp)); 441 | 442 | bool body = conn->method != HTTP_METHOD_HEAD; 443 | // TODO: Perhaps instead of just sending here, it would be better to write into the same pipe 444 | // that is used for splice. 445 | A3_TRYB(connection_send_submit( 446 | &conn->conn, uring, body ? NULL : http_response_handle, body ? MSG_MORE : 0, 447 | (body || !http_connection_keep_alive(conn)) ? IOSQE_IO_LINK : 0)); 448 | if (!body) 449 | goto done; 450 | 451 | // TODO: This will not work for TLS. 452 | A3_TRYB(connection_splice_submit(&conn->conn, uring, http_response_splice_chain_handle, 453 | http_response_splice_handle, target_file, /* offset */ 0, 454 | stat->stx_size, 455 | !http_connection_keep_alive(conn) ? IOSQE_IO_LINK : 0)); 456 | 457 | done: 458 | if (!http_connection_keep_alive(conn)) 459 | return http_connection_close_submit(conn, uring); 460 | return true; 461 | } 462 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------