├── README.markdown ├── minhttp-80x24.c ├── minhttp-commented.c └── minhttp.c /README.markdown: -------------------------------------------------------------------------------- 1 | minHTTP 2 | ------- 3 | 4 | A tiny and surprisingly featureful webserver. 5 | 6 | 7 | ### minhttp.c 8 | 9 | The full version, containing some documentation and configuration 10 | instructions. Builds as a single file with gcc or any other C 11 | compiler. 12 | 13 | gcc minhttp.c -o minhttp 14 | ./minhttp 15 | 16 | ### minhttp-80x24.c 17 | 18 | Smaller version to fit in an 80x24 terminal. Still has the same 19 | features, but reconfiguring is a little fiddly. You'll need to consult 20 | minttp.c to find out what the config variables mean, and the change 21 | the -DVAR=foo as desired. There are no documentation comments in this 22 | version and it requires a trickier set of compiler flags to make it 23 | compile. 24 | 25 | Luckily, the program is also a valid shell script which will correctly 26 | compile itself and then run. 27 | 28 | ./minhttp-80x24.c 29 | -------------------------------------------------------------------------------- /minhttp-80x24.c: -------------------------------------------------------------------------------- 1 | /*bin/true;sed -n 's/.*.\/\*\(.*\)../\1/p' "$0"|I="$0" sh;exit;[minHTTP 80x24]*/ 2 | #define T(k)(i=(x[k]|32)-48,i-=39*(i>9),i)/*echo gcc -includesys/sendfile,sys\*/ 3 | #define I(x,y);if(d,x){y;close(c);goto g;}/*/socket,netinet/in,sys/time,sys/s\*/ 4 | #define Y(f)(close(d),d=open(f,O_RDONLY))%d File Not Found",404))}else{fstat(d,&t);if(S_ISDIR(t.st_mode)){I(i,R("301 Moved\r\n","Locat" 20 | "ion:http:/%s/","",f))strcpy(h=y+strlen(y),"index.html")I(Y(y)0,DIR*v;R("200 OK" 21 | "\r\n","%s","<",M)*h=0;S("head>Index of %s",x) 22 | S("

%s

",x)for(v=opendir(y);w=readdir(v);){y=w->d_name;S("",y)S("%s
",y)}closedir(v);S("",h))fstat(d,&t);}R("200 O" 24 | "K\r\n","Content-Length: %d","",(int)t.st_size)}I(1,while(sendfile(c,d,0,X)>0))} 25 | -------------------------------------------------------------------------------- /minhttp-commented.c: -------------------------------------------------------------------------------- 1 | /* _ _ _ _____ _____ ____ */ 2 | /* _ __ ___ (_)_ __ | | | |_ _|_ _| _ \ */ 3 | /* | '_ ` _ \| | '_ \| |_| | | | | | | |_) | */ 4 | /* | | | | | | | | | | _ | | | | | | __/ */ 5 | /* |_| |_| |_|_|_| |_|_| |_| |_| |_| |_| */ 6 | /* */ 7 | /* commented version */ 8 | /* */ 9 | /* This file contains exactly the same code as */ 10 | /* minhttp.c, except for whitespace and comments */ 11 | /* */ 12 | /* Stephen Dolan */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /*** Configuration (defaults for testing only, try the values in brackets). ***/ 28 | #define N 3 /*[20] Max number of concurrent requests (# of processes). */ 29 | #define P 8000 /*[80] Port to listen on. */ 30 | #define E 0 /*["/404"] Path to custom 404 error message, or 0 for default. */ 31 | #define C 0 /*["/web"] Document root to chroot() into (0 to serve from /). */ 32 | #define V 0 /*[1] HTTP VHosts: if 1, docs are at DocRoot/Host/Path. */ 33 | #define U 0 /*[65534] UID to drop privilege to (0 to leave UID unchanged).*/ 34 | 35 | /* Decode a hex digit. T(k) is x[k] parsed as 0-9, a-f or A-F. 36 | i is used as scratch. x[k]|32 is a cheap trick for converting to lowercase. 37 | (see an ASCII table if you don't believe me :P). Subtracting 48 maps '0' to 38 | zero, and subtracting 39 maps 'a' to 10. */ 39 | #define T(k) ( i = (x[k]|32) - 48, i -= 39 * (i>9), i ) 40 | 41 | /* Possibly terminate the current connection. I(cond, action) performs "action" 42 | and accepts a new connection if "cond" is true. The "d," is a no-op to make 43 | the code layout fit better. It starts with a semicolon since that saves a 44 | semicolon at each point where it's used. */ 45 | #define I(x,y) ; if (d, x){ y; close(c); goto g; } 46 | 47 | /* Try opening a local file. Y(filename)0 evaluates to true if the open failed, 48 | otherwise "d" is the file descriptor. The 0 is not part of the macro for 49 | space reasons (that again :D). It first closes d, to limit the number of open 50 | filehandles (otherwise, the server dies after a few thousand requests). */ 51 | #define Y(f) (close(d), d=open(f,O_RDONLY))< 52 | 53 | /* O(option) performs a call to 54 | setsockopt(current_socket, SOL_SOCKET, option, &i, sizeof(i)) 55 | which sets the option according to the current value of "i". */ 56 | #define O(o) setsockopt(c, L, o, &i, sizeof(i)) 57 | 58 | /* Send a response to the current client. S(format, arg) where format is a 59 | printf-style format and arg is an argument to the format sends the data by 60 | sprintf'ing it into the buffer "r" and writing it to the socket. The sprintf 61 | call returns the number of written bytes, so it can be passed directly to 62 | write. Assigning to i is filler. */ 63 | #define S(f,a) write(i=c, r, sprintf(r,f,a)); 64 | 65 | /* A useful string to prevent HTTP/1.1 clients trying to pipeline requests. 66 | Again, the () is just to make it fit better. */ 67 | #define H() "\r\nConnection: close\r\n\r\n" 68 | 69 | /* Send a HTTP response. R(response, headers, data, arg) sends a HTTP response, 70 | where response, headers and data may contain at most one printf-style 71 | argument, filled by arg. It also sends the H() headers. */ 72 | #define R(c,h,d,a) S("HTTP/1.1 " c h H() d, a) 73 | 74 | int main(){ 75 | /* This enum gives short names to constants that are used multiple times, or 76 | that just fit in better here. */ 77 | enum{ Z = AF_INET, L = SOL_SOCKET, D = SO_RCVTIMEO, 78 | F = SIGPIPE, G = SO_REUSEADDR, X = 9001 }; 79 | 80 | /* C89-compatible, so we have to declare all variables at the start of 81 | a block. */ 82 | struct dirent* w; /* used to generate directory listings */ 83 | int i = 1, /* used as a temporary */ 84 | s, /* server socket file descriptor */ 85 | d = -1, /* file descriptor for files being served */ 86 | /* c is the client socket file descriptor, but it is initialised with 87 | the server socket since the O macro for setsockopt is defined in 88 | terms of c. */ 89 | c = socket(Z/*AF_INET*/, SOCK_STREAM, 0); 90 | char *x, /* points to request URI (usually) */ 91 | *y, /* points to request URI or physical path (usually) */ 92 | *h, /* points to hostname */ 93 | q[X] = {0}, /* buffer for requests */ 94 | M[]="Content-type:text/html;charset=UTF-8", /* constant string */ 95 | f[X], /* buffer for physical paths when using VHosts */ 96 | r[X]; /* buffer for responses (used in S above) */ 97 | 98 | /* Some C hackery: if a structure is given an incomplete initialiser, the rest 99 | of it is set to zero. So, initialising "a" to {AF_INET} sets the sin_port 100 | and sin_addr parts to zero. */ 101 | struct sockaddr_in a={Z/*AF_INET*/}; 102 | struct stat t; 103 | 104 | /* More C hackery: casts to void* and back are implicit, so getting a void* 105 | pointer to "a" means we don't have to write out the full cast for bind 106 | below. Otherwise, we'd have to write "(struct sockaddr*)a". */ 107 | void* A = &a; 108 | 109 | /* since sin_addr was initialised to zero, this sets a to 0.0.0.0:P */ 110 | a.sin_port = htons(P); 111 | 112 | /* set SO_REUSEADDR to 1 (i = 1 earlier) */ 113 | O(G/*SO_REUSEADDR*/); 114 | 115 | /* && has higher precedence than ||, so we can avoid brackets below. Most Unix 116 | syscalls return 0 on success and nonzero on failure, so combining several 117 | with || gives you a condition which is true if any failed. */ 118 | if( C && chroot(C) /* chroot if configured (i.e. C is not null) */ 119 | || bind(s=c, A, sizeof(a)) /* bind the socket to an address */ 120 | || listen(s, 5) /* listen for connections */ 121 | || U && setuid(U)) /* drop privileges if configured */ 122 | /* If setup failed, print an error and exit. Separating the function calls 123 | using C's comma operator means we don't need braces around the if body, 124 | since it's all "one statement". */ 125 | perror(0), 126 | exit(1); 127 | 128 | /* We will be sent a SIGPIPE if we write to a socket that the other end has 129 | closed. So, ignoring this signal means a client dropping won't bring down 130 | the server */ 131 | signal(F,SIG_IGN); 132 | 133 | /* Fork off a number of worker processes to handle connections. fork() returns 134 | 0 in the child and nonzero in the parent, so the condition in this for loop 135 | makes the children exit the loop immediately. */ 136 | for(i=N; fork() && --i; ); 137 | /* In the parent, i == 0 by this point. The parent thus calls pause and the 138 | children do the work. */ 139 | i || pause(); 140 | 141 | /* Main connection loop. This is a label because "g:goto g;" is shorter than 142 | "while (1){continue;}" */ 143 | g: 144 | 145 | /* Accept a connection. Accept also takes a struct sockaddr* in which to store 146 | the peer address, but we don't care so we just pass it a random buffer. */ 147 | c=accept(s,A,(void*)q); 148 | 149 | { 150 | /* Hackery: we make a new variable i, inside a nested block. This shadows 151 | the outer copy of i, so the O macro refers to our struct timeval instead 152 | of the enclosing scope's int. We also reuse the partial initialiser 153 | trick from earlier to avoid needing to initialise i.tv_usec */ 154 | struct timeval i={5}; 155 | O(D/*SO_RCVTIMEO*/); 156 | O(SO_SNDTIMEO); 157 | } 158 | 159 | /* Read from the socket into the buffer "q". If the request isn't a GET 160 | request, or if the read failed, we just close the socket and accept a new 161 | connection. This will fail on very slow HTTP clients which don't send a 162 | full request in the first packet (since read can return without having read 163 | a full request), but in practice it works. */ 164 | I( read(c,q,X/2) < 0 || 165 | strncmp(q,"GET /",5), 166 | ;) 167 | 168 | /* Percent-encoding for URLs: URLs may contain %NN where the Ns are hex 169 | digits. These are generally used to encode non-ascii or whitespace 170 | characters. They should be decoded into the byte 0xNN before the file 171 | is looked up in the filesystem. 172 | 173 | The Request-URI starts just after the "GET ", so at position q+4. The 174 | decoding can only make the string shorter, so we can write the output 175 | to the same location as the input and be sure we won't clobber unread 176 | data. We set x (input) and y (output) to point to the start of the URI 177 | and loop until we find a space character (ascii 32). */ 178 | for(y = x = q+4; *x & ~32;) 179 | if (*x - 37) /* if (x != '%'), but shorter */ 180 | *y++ = *x++; /* just copy the byte, not a percent-encoding */ 181 | else{ 182 | *y = T(1) << 4; /* decode first hex digit (high 4 bits) */ 183 | *y++ += T(2); /* decode second hex digit and advance y */ 184 | x+=3; /* advance x 3 places ('%' and two digits) */ 185 | } 186 | /* y now points to the end of the URI, which may be before the original 187 | end if %-encoded characters were found. The following line makes the 188 | URI (at q+4) a null-terminated string. */ 189 | *y=0; 190 | 191 | /* HTTP/1.1 VHosting: we need to find a Host header. Technically, parsing out 192 | a HTTP header is quite complex: the header name is case-insensitive, it may 193 | be followed by any amount of whitespace, and the value may contain 194 | "continuation lines" (newlines followed by whitespace). However, every HTTP 195 | client ever emits "Host: " with that capitalisation and a single space 196 | and no continuation lines, so that's what we parse. */ 197 | if(/* If a Host header is found, set h to point to the start of the line */ 198 | (h=strstr(x+1,"\nHost:")) && 199 | (x=strchr(h += 7 /* h now points to hostname */,13))) 200 | *x=0; /* Put a 0 at the end of the hostname */ 201 | /* Now, h either points to a null-terminated hostname (if a Host header was 202 | found), or is 0 (if strstr failed). */ 203 | h = h ? h : ""; /* set h to empty string if no hostname found */ 204 | 205 | /* f is set to "/hostname/filename", which is the correct physical path if 206 | VHosts are being used. A side-effect is that "x" is set to q+4, which is 207 | the URI-string without a host part. */ 208 | sprintf(f,"/%s%s",h,x=q+4); 209 | 210 | /* We care whether the last character of the URI is a '/' since we want to 211 | produce a redirect from /foo to /foo/ if foo is a directory, as this makes 212 | relative URIs work properly in /foo/index.html. 213 | if (x[strlen(x) - 1] == '/'){ i = 0; }else{ i = ; } but shorter */ 214 | i = y[-1] - 47; 215 | 216 | /* We want to open the file given by f if VHosts are enabled, or x if not. We 217 | use the config variable V and save the correct filename in y for later. */ 218 | if(Y(y=V?f:x)0){ 219 | /* Open failed, send a 404 response. */ 220 | R("404 Not Found\r\n","%s","",M) /* M specifies a HTML Content-type. */ 221 | 222 | /* If the config variable E is set, it gives the filename of a custom 404 223 | error page. If we manage to open E, we fall through to the server code 224 | below and serve E as if it were the original request. Otherwise, we 225 | send a default 404 message, close the connection and accept a new one. */ 226 | I(Y(E)0, 227 | S("

%d File Not Found",404) 228 | ) 229 | }else{ 230 | /* The file we opened may be a directory. If so: 231 | - if the URL doesn't end with a /, redirect to URL + "/" since relative 232 | links in HTML documents will fail otherwise (relative link to "bar" 233 | from directory "/foo" should go to "/foo/bar", not "/foobar", but 234 | browsers just concatenate strings for relative links). 235 | - next, we search for an index.html and serve that if we can open it 236 | - otherwise, we generate a directory listing. 237 | */ 238 | fstat(d,&t); 239 | if (S_ISDIR(t.st_mode)){ 240 | /* It's a directory */ 241 | I(i, /* i is nonzero if the URL does not end with a '/', see above */ 242 | /* The string below was broken for layout purposes (end of a line) */ 243 | R("301 Moved\r\n","Locat" "ion:http:/%s/","",f) 244 | ) 245 | /* Set h to point to the end of the URI, and append "index.html" */ 246 | strcpy(h=y+strlen(y),"index.html") 247 | /* Try to open the index.html file. If we could open it, we fall through 248 | to the code below and serve as if it was the original requested file */ 249 | I(Y(y)0, 250 | /* Couldn't open index.html, so we make a directory listing. */ 251 | DIR* v; 252 | R("200 OK" "\r\n","%s","<", M) /* M gives a HTML Content-type */ 253 | 254 | /* h points to the end of the path just before we added index.html, so 255 | putting a 0 there gets us the original directory path back in y. */ 256 | *h=0; 257 | 258 | /* x points to the original request-URI without any VHosts prepended 259 | to form a path, so it gives us the user-visible directory name. */ 260 | S("head>Index of %s",x) 261 | S("

%s

",x) 262 | 263 | /* For each item in the directory */ 264 | for (v = opendir(y); w = readdir(v); ){ 265 | y = w->d_name; 266 | /* Output a link to this item */ 267 | S("",y) 268 | S("%s
",y) 269 | } 270 | closedir(v); 271 | 272 | /* Correct HTML, strangely enough. */ 273 | S("",h) 274 | ) 275 | /* If we get here, d now points to a different file than it did when we 276 | last called fstat (it is now either E or an index.html file), so we 277 | re-run fstat to get the correct file length. */ 278 | fstat(d,&t); 279 | } 280 | /* We're serving a file, one of: 281 | - the file the user asked for 282 | - a custom 404 error message (E) 283 | - an index.html file. 284 | Adding a Content-Length header is not strictly neccessary (since we're 285 | closing the connection after the file is sent), but it means we get 286 | proper progress bars on most browsers for large or slow downloads. */ 287 | R("200 O" "K\r\n","Content-Length: %d","",(int)t.st_size) 288 | } 289 | /* We've gotten a file open, so we use sendfile to serve it up. sendfile will 290 | return 0 on end-of-file and <0 on error, so we keep going as long as it 291 | keeps returning a positive value. */ 292 | I(1, 293 | while (sendfile(c,d,0,X)>0) 294 | ) 295 | /* the previous unconditional "I" will have goto'd the accept loop, so this 296 | is unreachable. */ 297 | } 298 | -------------------------------------------------------------------------------- /minhttp.c: -------------------------------------------------------------------------------- 1 | #include /* _ _ _ _____ _____ ____ */ 2 | #include /* _ __ ___ (_)_ __ | | | |_ _|_ _| _ \ */ 3 | #include /* | '_ ` _ \| | '_ \| |_| | | | | | | |_) | */ 4 | #include /* | | | | | | | | | | _ | | | | | | __/ */ 5 | #include /* |_| |_| |_|_|_| |_|_| |_| |_| |_| |_| */ 6 | #include /* A tiny and surprisingly featureful webserver */ 7 | #include /* -Source is smaller than Apache's config file. */ 8 | #include /* -Compiles faster than lighttpd starts up. */ 9 | #include /* -Actual code fits in an 80x24 screen. */ 10 | #include /* -Portable (pure POSIX), Unicode support. */ 11 | #include /* -Directory listings (if no index.html is found). */ 12 | #include /* -Fast sendfile() I/O, timeouts to prevent DoS. */ 13 | /*** Configuration (defaults for testing only, try the values in brackets). ***/ 14 | #define N 3 /*[20] Max number of concurrent requests (# of processes). */ 15 | #define P 8000 /*[80] Port to listen on. */ 16 | #define E 0 /*["/404"] Path to custom 404 error message, or 0 for default. */ 17 | #define C 0 /*["/web"] Document root to chroot() into (0 to serve from /). */ 18 | #define V 0 /*[1] HTTP VHosts: if 1, docs are at DocRoot/Host/Path. */ 19 | #define U 0 /*[65534] UID to drop privilege to (0 to leave UID unchanged).*/ 20 | #define T(k)(i=(x[k]|32)-48,i-=39*(i>9),i) /***********************************/ 21 | #define I(x,y);if(d,x){y;close(c);goto g;} /* */ 22 | #define Y(f)(close(d),d=open(f,O_RDONLY))< /* (c) Stephen Dolan, 2010 */ 23 | #define O(o)setsockopt(c,L,o,&i,sizeof(i)) /* minHTTP is provided under the */ 24 | #define S(f,a)write(i=c,r,sprintf(r,f,a)); /* terms of the X11 License. */ 25 | #define H()"\r\nConnection: close\r\n\r\n" /* */ 26 | #define R(c,h,d,a)S("HTTP/1.1 "c h H()d,a) /***********************************/ 27 | int main(){enum{Z=AF_INET,L=SOL_SOCKET,D=SO_RCVTIMEO,F=SIGPIPE,G=SO_REUSEADDR,X= 28 | 9001};struct dirent*w;int i=1,s,d=-1,c=socket(Z,SOCK_STREAM,0);char*x,*y,*h,q[X] 29 | ={0},M[]="Content-type:text/html;charset=UTF-8",f[X],r[X];struct sockaddr_in a={ 30 | Z};struct stat t;void*A=&a;a.sin_port=htons(P);O(G);if(C&&chroot(C)||bind(s=c,A, 31 | sizeof(a))||listen(s,5)||U&&setuid(U))perror(0),exit(1);signal(F,SIG_IGN);for(i= 32 | N;fork()&&--i;);i||pause();g:c=accept(s,A,(void*)q);{struct timeval i={5};O(D);O 33 | (SO_SNDTIMEO);}I(read(c,q,X/2)<0||strncmp(q,"GET /",5),;)for(y=x=q+4;*x&~32;)if( 34 | *x-37)*y++=*x++;else{*y=T(1)<<4;*y+++=T(2);x+=3;}*y=0;if((h=strstr(x+1,"\nHost:" 35 | ))&&(x=strchr(h+=7,13)))*x=0;h=h?h:"";sprintf(f,"/%s%s",h,x=q+4);i=y[-1]-47;if(Y 36 | (y=V?f:x)0){R("404 Not Found\r\n","%s","",M)I(Y(E)0,S("

%d File Not Found",404))}else{fstat(d,&t);if(S_ISDIR(t.st_mode)){I(i,R("301 Moved\r\n","Locat" 38 | "ion:http:/%s/","",f))strcpy(h=y+strlen(y),"index.html")I(Y(y)0,DIR*v;R("200 OK" 39 | "\r\n","%s","<",M)*h=0;S("head>Index of %s",x) 40 | S("

%s

",x)for(v=opendir(y);w=readdir(v);){y=w->d_name;S("",y)S("%s
",y)}closedir(v);S("",h))fstat(d,&t);}R("200 O" 42 | "K\r\n","Content-Length: %d","",(int)t.st_size)}I(1,while(sendfile(c,d,0,X)>0))} 43 | --------------------------------------------------------------------------------