├── .gitignore ├── asm.sh ├── test.html ├── README.md ├── start.S ├── UNLICENSE ├── httpd.c └── httpd.asm /.gitignore: -------------------------------------------------------------------------------- 1 | /httpd 2 | -------------------------------------------------------------------------------- /asm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | nasm -f bin -o httpd httpd.asm 4 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello world! 4 | 5 | 6 |

hello world!

7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | single-page http server for x86\_64 linux in a 1kb static binary, 2 | doesn't depend on libc 3 | 4 | huge credits to @tleydxdy for bringing this down from 5kb to 1kb and 5 | <1kb with the asm version 6 | 7 | # build and run 8 | ``` 9 | ./build.sh 10 | ./httpd 8080 test.html 11 | ``` 12 | 13 | if you don't have gcc, change build.sh to match your compiler 14 | 15 | build even smaller asm version with a custom elf header, < 1kb 16 | 17 | requires nasm 18 | 19 | ``` 20 | ./asm.sh 21 | ./httpd 8080 test.html 22 | ``` 23 | -------------------------------------------------------------------------------- /start.S: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | 3 | /* functions: rdi, rsi, rdx, rcx, r8, r9 */ 4 | /* syscalls: rdi, rsi, rdx, r10, r8, r9 */ 5 | /* ^^^ */ 6 | /* stack grows from a high address to a low address */ 7 | 8 | #define c(x, n) \ 9 | .global x; \ 10 | x:; \ 11 | add r9,n 12 | 13 | c(wait4, 1) /* 61 */ 14 | c(exit, 3) /* 60 */ 15 | c(fork, 3) /* 57 */ 16 | c(setsockopt, 4) /* 54 */ 17 | c(listen, 1) /* 50 */ 18 | c(bind, 6) /* 49 */ 19 | c(accept, 2) /* 43 */ 20 | c(socket, 38) /* 41 */ 21 | c(close, 1) /* 03 */ 22 | c(open, 1) /* 02 */ 23 | c(write, 1) /* 01 */ 24 | .global read /* 00 */ 25 | read: 26 | mov r10,rcx 27 | mov rax,r9 28 | xor r9,r9 29 | syscall 30 | ret 31 | 32 | .global _start 33 | _start: 34 | xor rbp,rbp 35 | xor r9,r9 36 | pop rdi /* argc */ 37 | mov rsi,rsp /* argv */ 38 | call main 39 | call exit 40 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /httpd.c: -------------------------------------------------------------------------------- 1 | #define AF_INET 2 2 | #define SOCK_STREAM 1 3 | #define IPPROTO_TCP 6 4 | #define SO_REUSEADDR 2 5 | #define SOL_SOCKET 1 6 | #define O_RDONLY 0 7 | #define WNOHANG 1 8 | 9 | typedef unsigned short uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned int size_t; 12 | typedef unsigned int socklen_t; 13 | typedef int ssize_t; 14 | 15 | /* must be padded to at least 16 bytes */ 16 | typedef struct { 17 | uint16_t sin_family; /* 2 */ 18 | uint16_t sin_port; /* 4 -> this is in big endian */ 19 | uint32_t sin_addr; /* 8 */ 20 | char sin_zero[8]; /* 16 */ 21 | } sockaddr_in_t; 22 | 23 | struct rusage; 24 | 25 | ssize_t read(int fd, void *buf, size_t nbyte); 26 | ssize_t write(int fd, const void *buf, size_t nbyte); 27 | int open(const char *path, int flags); 28 | int close(int fd); 29 | int socket(int domain, int type, int protocol); 30 | int accept(int socket, sockaddr_in_t *restrict address, 31 | socklen_t *restrict address_len); 32 | int bind(int socket, const sockaddr_in_t *address, socklen_t address_len); 33 | int listen(int socket, int backlog); 34 | int setsockopt(int socket, int level, int option_name, const void *option_value, 35 | socklen_t option_len); 36 | int fork(); 37 | int wait4(int pid, int *wstatus, int options, struct rusage *rusage); 38 | void exit(int status); 39 | 40 | static size_t strlen(const char *s) { 41 | const char *p = s; 42 | while (*p) 43 | ++p; 44 | return p - s; 45 | } 46 | 47 | static uint16_t swap_uint16(uint16_t x) { 48 | return (((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF)); 49 | } 50 | 51 | #define fprint(fd, s) write(fd, s, strlen(s)) 52 | 53 | #define fprintn(fd, s, n) write(fd, s, n) 54 | 55 | #define fprintl(fd, s) fprintn(fd, s, sizeof(s) - 1) 56 | 57 | #define fprintln(fd, s) fprintl(fd, s "\n") 58 | 59 | #define print(s) fprint(1, s) 60 | 61 | #define printn(s, n) fprintn(1, s, n) 62 | 63 | #define printl(s) fprintl(1, s) 64 | 65 | #define println(s) fprintln(1, s) 66 | 67 | #ifdef DEBUG 68 | #define die(s) \ 69 | println("FATAL: " s); \ 70 | exit(1) 71 | 72 | #define perror(s) println("ERROR: " s) 73 | #else 74 | #define die(s) exit(1) 75 | 76 | #define perror(s) 77 | #endif 78 | 79 | int tcp_listen(const sockaddr_in_t *addr, const void *option_value, 80 | socklen_t option_len) { 81 | int sock; 82 | if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 || 83 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, option_value, option_len) || 84 | bind(sock, addr, sizeof(sockaddr_in_t)) || listen(sock, 10)) { 85 | die("listen"); 86 | } 87 | return sock; 88 | } 89 | 90 | static void http_consume(int clientfd, char *http_buf, size_t buf_len) { 91 | int n; 92 | while ((n = read(clientfd, http_buf, buf_len)) > 0) { 93 | printn(http_buf, n); 94 | const char *p = http_buf + (n - 3); 95 | if (n < 3 || (*p == '\n' && *(p + 1) == '\r' && *(p + 2) == '\n')) { 96 | return; 97 | } 98 | } 99 | if (n < 0) { 100 | perror("read"); 101 | } 102 | } 103 | 104 | /* 105 | * we're supposed to send content-length but shutting down the 106 | * socket seems to be enough, saves some code 107 | * 108 | * a http server is usually expected to respond to HEAD 109 | * requests without sending the actual content, we're not gonna 110 | * do that just to keep it tiny 111 | * 112 | * also, we could cache the file in memory instead of opening 113 | * it every time but since this is an exercise in making tiny 114 | * binaries it also makes sense to keep the mem usage low 115 | */ 116 | 117 | #define http_code(fd, x) fprintl(fd, "HTTP/1.1 " x "\r\n\r\n" x); 118 | 119 | static int http_serve(int clientfd, const char *file_path, char *http_buf, 120 | size_t buf_len) { 121 | int f, n; 122 | http_consume(clientfd, http_buf, buf_len); 123 | if ((f = open(file_path, O_RDONLY)) < 0) { 124 | perror("open"); 125 | http_code(clientfd, "404 Not Found"); 126 | return 1; 127 | } 128 | fprintl(clientfd, "HTTP/1.1 200 OK\r\n\r\n"); 129 | while ((n = read(f, http_buf, buf_len)) > 0) { 130 | if (write(clientfd, http_buf, n) < 0) { 131 | perror("write"); 132 | return 1; 133 | } 134 | } 135 | if (n < 0) { 136 | perror("read"); 137 | } 138 | return 0; 139 | } 140 | 141 | static uint16_t string2port(const char *s) { 142 | uint16_t res = 0; 143 | for (; *s; ++s) { 144 | if (*s > '9' || *s < '0') { 145 | return 0; 146 | } 147 | res = res * 10 + *s - '0'; 148 | } 149 | return swap_uint16(res); 150 | } 151 | 152 | static void usage(const char *self) { 153 | printl("usage: "); 154 | print(self); 155 | println(" port file"); 156 | exit(1); 157 | } 158 | 159 | int main(int argc, char *argv[]) { 160 | int sock; 161 | uint16_t port; 162 | char http_buf[8192]; 163 | if (argc != 3 || (port = string2port(argv[1])) == 0) { 164 | usage(argv[0]); 165 | } 166 | const int yes = 1; 167 | const sockaddr_in_t addr = {AF_INET, port, 0}; 168 | sock = tcp_listen(&addr, &yes, sizeof(yes)); 169 | while (1) { 170 | int pid, clientfd; 171 | while (wait4(-1, 0, WNOHANG, 0) > 0); 172 | if ((clientfd = accept(sock, 0, 0)) < 0) { 173 | perror("accept"); 174 | } else if ((pid = fork()) < 0) { 175 | perror("fork"); 176 | } else if (pid == 0) { 177 | return http_serve(clientfd, argv[2], http_buf, sizeof(http_buf)); 178 | } 179 | close(clientfd); 180 | } 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /httpd.asm: -------------------------------------------------------------------------------- 1 | ; - code disassembled from C executable with `objconv -fnasm httpd temp.asm` 2 | ; - tweaked by prepending _start to _001 and removing the exit call 3 | ; - tiny elf header see http://muppetlabs.com/~breadbox/software/tiny/teensy.html 4 | 5 | BITS 64 6 | 7 | org 0x08048000 8 | 9 | ehdr: ; Elf32_Ehdr 10 | db 0x7F, "ELF", 2, 1, 1, 0 ; e_ident 11 | ?_033: 12 | db "usage: ", 0 13 | dw 2 ; e_type 14 | dw 62 ; e_machine 15 | dd 1 ; e_version 16 | dq _start ; e_entry 17 | dq phdr - $$ ; e_phoff 18 | ?_034: 19 | db " port fi" ; e_shoff 20 | db "le", 10, 0 ; e_flags 21 | dw ehdrsize ; e_ehsize 22 | dw phdrsize ; e_phentsize 23 | phdr: ; Elf32_Phdr 24 | dd 1 ; e_phnum ; p_type 25 | ; e_shentsize 26 | dd 5 ; e_shnum ; p_flags 27 | ; e_shstrndx 28 | 29 | ehdrsize equ $ - ehdr 30 | 31 | dq 0 ; p_offset 32 | dq $$ ; p_vaddr 33 | dq $$ ; p_paddr 34 | dq filesize ; p_filesz 35 | dq filesize ; p_memsz 36 | dq 0x1000 ; p_align 37 | 38 | phdrsize equ $ - phdr 39 | 40 | _start: 41 | xor rbp, rbp 42 | xor r9, r9 43 | pop rdi 44 | mov rsi, rsp 45 | 46 | ?_001: 47 | push r14 48 | push r13 49 | push r12 50 | mov r12, rsi 51 | push rbp 52 | push rbx 53 | sub rsp, 8224 54 | cmp edi, 3 55 | jnz ?_004 56 | mov rcx, qword [rsi+8H] 57 | xor eax, eax 58 | ?_002: movsx dx, byte [rcx] 59 | test dl, dl 60 | jz ?_003 61 | lea esi, [rdx-30H] 62 | cmp sil, 9 63 | ja ?_004 64 | imul eax, eax, 10 65 | inc rcx 66 | lea eax, [rax+rdx-30H] 67 | jmp ?_002 68 | 69 | ?_003: mov edx, eax 70 | xchg dl, dh 71 | test ax, ax 72 | jnz ?_007 73 | ?_004: mov rbx, qword [r12] 74 | mov edx, 7 75 | mov edi, 1 76 | lea rsi, [rel ?_033] 77 | call ?_027 78 | mov rdx, rbx 79 | ?_005: cmp byte [rdx], 0 80 | jz ?_006 81 | inc rdx 82 | jmp ?_005 83 | 84 | ?_006: sub edx, ebx 85 | mov rsi, rbx 86 | mov edi, 1 87 | call ?_027 88 | mov edx, 11 89 | mov edi, 1 90 | lea rsi, [rel ?_034] 91 | call ?_027 92 | mov edi, 1 93 | call ?_018 94 | xor edx, edx 95 | ?_007: mov word [rsp+12H], dx 96 | xor eax, eax 97 | xor ecx, ecx 98 | lea rsi, [rsp+0CH] 99 | lea rdi, [rsp+10H] 100 | mov edx, 4 101 | mov qword [rsp+14H], rax 102 | mov dword [rsp+0CH], 1 103 | mov dword [rsp+1CH], ecx 104 | 105 | mov word [rsp+10H], 2 106 | call ?_029 107 | mov r13d, eax 108 | ?_008: xor ecx, ecx 109 | xor esi, esi 110 | or edi, 0FFFFFFFFH 111 | mov edx, 1 112 | call ?_017 113 | test eax, eax 114 | jg ?_008 115 | xor edx, edx 116 | xor esi, esi 117 | mov edi, r13d 118 | call ?_023 119 | mov ebx, eax 120 | test eax, eax 121 | js ?_015 122 | xor eax, eax 123 | call ?_019 124 | mov ebp, eax 125 | test eax, eax 126 | jne ?_015 127 | mov r13, qword [r12+10H] 128 | lea r12, [rsp+20H] 129 | ?_009: mov edx, 8192 130 | mov rsi, r12 131 | mov edi, ebx 132 | call ?_028 133 | mov r14d, eax 134 | test eax, eax 135 | jle ?_010 136 | mov edx, r14d 137 | mov rsi, r12 138 | mov edi, 1 139 | call ?_027 140 | lea edx, [r14-3H] 141 | movsxd rdx, edx 142 | add rdx, r12 143 | cmp r14d, 2 144 | jg ?_011 145 | ?_010: mov rdi, r13 146 | xor esi, esi 147 | call ?_026 148 | mov r13d, eax 149 | test eax, eax 150 | jns ?_012 151 | mov edx, 39 152 | lea rsi, [rel ?_035] 153 | mov edi, ebx 154 | call ?_027 155 | jmp ?_014 156 | 157 | ?_011: cmp byte [rdx], 10 158 | jnz ?_009 159 | cmp byte [rdx+1H], 13 160 | jnz ?_009 161 | cmp byte [rdx+2H], 10 162 | jnz ?_009 163 | jmp ?_010 164 | 165 | ?_012: mov edx, 19 166 | lea rsi, [rel ?_036] 167 | mov edi, ebx 168 | call ?_027 169 | ?_013: mov edx, 8192 170 | mov rsi, r12 171 | mov edi, r13d 172 | call ?_028 173 | mov edx, eax 174 | test eax, eax 175 | jle ?_016 176 | mov rsi, r12 177 | mov edi, ebx 178 | call ?_027 179 | test eax, eax 180 | jns ?_013 181 | ?_014: mov ebp, 1 182 | jmp ?_016 183 | 184 | ?_015: mov edi, ebx 185 | call ?_025 186 | jmp ?_008 187 | 188 | ?_016: 189 | add rsp, 8224 190 | mov eax, ebp 191 | pop rbx 192 | pop rbp 193 | pop r12 194 | pop r13 195 | pop r14 196 | ret 197 | 198 | ?_017: 199 | add r9, 1 200 | ?_018: add r9, 3 201 | ?_019: add r9, 3 202 | ?_020: add r9, 4 203 | ?_021: add r9, 1 204 | ?_022: add r9, 6 205 | ?_023: add r9, 2 206 | ?_024: add r9, 38 207 | ?_025: add r9, 1 208 | ?_026: add r9, 1 209 | ?_027: add r9, 1 210 | ?_028: mov r10, rcx 211 | mov rax, r9 212 | xor r9, r9 213 | syscall 214 | ret 215 | 216 | ?_029: 217 | push r12 218 | mov r12, rsi 219 | mov esi, 1 220 | push rbp 221 | mov rbp, rdi 222 | mov edi, 2 223 | push rbx 224 | sub rsp, 16 225 | mov dword [rsp+0CH], edx 226 | mov edx, 6 227 | call ?_024 228 | mov r8d, dword [rsp+0CH] 229 | test eax, eax 230 | mov ebx, eax 231 | jns ?_031 232 | ?_030: mov edi, 1 233 | call ?_018 234 | jmp ?_032 235 | 236 | ?_031: mov rcx, r12 237 | mov edx, 2 238 | mov esi, 1 239 | mov edi, eax 240 | call ?_020 241 | test eax, eax 242 | jnz ?_030 243 | mov edx, 16 244 | mov rsi, rbp 245 | mov edi, ebx 246 | call ?_022 247 | test eax, eax 248 | jnz ?_030 249 | mov esi, 10 250 | mov edi, ebx 251 | call ?_021 252 | test eax, eax 253 | jnz ?_030 254 | ?_032: add rsp, 16 255 | mov eax, ebx 256 | pop rbx 257 | pop rbp 258 | pop r12 259 | ret 260 | 261 | ?_035: 262 | db "HTTP/1.1 404 Not Found" 263 | db 0DH, 0AH, 0DH, 0AH 264 | db "404 Not Found" 265 | 266 | ?_036: 267 | db "HTTP/1.1 200 OK" 268 | db 0DH, 0AH, 0DH, 0AH 269 | 270 | filesize equ $ - $$ 271 | --------------------------------------------------------------------------------