├── .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 |
--------------------------------------------------------------------------------