update README/Makefile re: RcB2
[rofl0r-rocksock-httpd.git] / httpserver.c
blob59fd60111d88816a85a03f422b0a80ce9e784535
1 /*
2 Copyright (C) 2010-2011 rofl0r
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <sys/select.h>
21 #include <sys/wait.h>
22 #include <sys/stat.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include <stdint.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <arpa/inet.h>
29 #include <netinet/in.h>
30 #include <grp.h>
32 #include "../rocksock/rocksockserver.h"
33 #include "../lib/include/stringptr.h"
34 #include "../lib/include/strlib.h"
35 #include "../lib/include/filelib.h"
36 #include "../lib/include/optparser.h"
37 #include "../lib/include/logger.h"
38 #include "../lib/include/proclib.h"
40 static const char content_type_text_html[] = "text/html";
41 static const char content_type_text_plain[] = "text/plain";
42 static const char content_type_binary_octet_stream[] = "binary/octet-stream";
43 static const char content_type_image_jpeg[] = "image/jpeg";
44 static const char content_type_image_gif[] = "image/gif";
45 static const char content_type_image_png[] = "image/png";
46 static const char content_type_application_pdf[] = "application/pdf";
49 static const char file_type_htm[] = "htm";
50 static const char file_type_html[] = "html";
51 static const char file_type_perl[] = "pl\0\0";
52 static const char file_type_sh[] = "sh\0\0";
53 static const char file_type_txt[] = "txt";
54 static const char file_type_gif[] = "gif";
55 static const char file_type_jpeg[] = "jpeg";
56 static const char file_type_jpg[] = "jpg";
57 static const char file_type_png[] = "png";
58 static const char file_type_cgi[] = "cgi";
59 static const char file_type_pdf[] = "pdf";
61 typedef struct {
62 const char* fileext;
63 const char* content_type;
64 } contenttype;
66 static const contenttype typemap[] = {
67 { file_type_htm, content_type_text_html },
68 { file_type_html, content_type_text_html },
69 { file_type_perl, content_type_text_plain },
70 { file_type_sh, content_type_text_plain },
71 { file_type_txt, content_type_text_plain },
72 { file_type_gif, content_type_image_gif },
73 { file_type_jpeg, content_type_image_jpeg },
74 { file_type_jpg, content_type_image_jpeg },
75 { file_type_png, content_type_image_png },
76 { file_type_pdf, content_type_application_pdf },
77 { NULL, NULL }
80 typedef enum {
81 CLIENT_DISCONNECTED = 0,
82 CLIENT_CONNECTED = 1,
83 CLIENT_READING = 2,
84 CLIENT_WRITING = 4,
85 CLIENT_UPLOADING = 8
86 } clientstatus;
88 typedef enum {
89 RQT_NONE = 0,
90 RQT_GET,
91 RQT_POST,
92 } requesttype;
94 typedef enum {
95 ST_NONE = 0,
96 ST_PERL,
97 } scripttype;
99 typedef struct {
100 size_t streampos;
101 size_t urilength;
102 size_t contentlength;
103 requesttype rqt;
104 } clientrequest_inner;
106 typedef struct {
107 clientrequest_inner i;
108 char uri[1024 - sizeof(clientrequest_inner)];
109 } clientrequest;
111 // we allow maximum 8 parallel uploads, for each slot will waste precious 1KB of stack RAM.
112 #define SSA_MAXELEM 8
113 #define SSA_ELEMSIZE (sizeof(clientrequest))
114 #include "../lib/include/ssalloc.c"
116 typedef enum {
117 CL_NONE = 0,
118 CL_KEEP_ALIVE = 1,
119 CL_NEEDS_TURBO = 2
120 } clientflags;
122 typedef struct {
123 size_t requestsize;
124 clientrequest* uploadreq;
125 clientstatus status;
126 clientflags flags;
127 int requeststream;
128 int responsestream_header;
129 int responsestream;
130 int act_responsestream;
131 } httpclient;
133 #if (! defined(USER_BUFSIZE_KB)) || (USER_BUFSIZE_KB > 1024)
134 #define USER_BUFSIZE_KB 96
135 #endif
137 typedef struct {
138 char buffer[USER_BUFSIZE_KB*1024];
139 /* with buffersize 96K we can deliver a smoking ~75MB/s in turbomode.
140 * bigger values doesnt seem to improve performance. maybe my hdd's were the bottleneck.
141 * 16K is sufficient for a 100Mbit line.
143 char pathbuf[256];
144 char tempdir[256];
145 stringptr workdir;
146 stringptr servedir;
147 httpclient clients[USER_MAX_FD];
148 rocksockserver serva;
149 size_t maxrequestsize;
150 time_t timeoutsec;
151 unsigned turbo;
152 unsigned numclients;
153 int log;
154 } httpserver;
156 static void free_stream(int* f) {
157 if(*f > -1) {
158 close(*f);
159 *f = -1;
163 // client is requesting big data
164 void httpserver_turbomode(httpserver* self, int client) {
165 if(!(self->clients[client].flags & CL_NEEDS_TURBO)) {
166 self->turbo++;
167 self->clients[client].flags |= CL_NEEDS_TURBO;
168 rocksockserver_set_sleeptime(&self->serva, 500);
172 // client connected, but not sending/receiving big amounts of data
173 void httpserver_idlemode(httpserver* self, int client) {
174 if(self->clients[client].flags & CL_NEEDS_TURBO) {
175 self->turbo--;
176 self->clients[client].flags &= ~CL_NEEDS_TURBO;
178 if(!self->turbo)
179 rocksockserver_set_sleeptime(&self->serva, 5000);
182 const char* httpserver_get_contenttype(char* filename, char* fileext) {
183 ssize_t i;
184 unsigned char fe[16] = {0};
185 unsigned char* fex = (unsigned char*) fileext;
186 int temp;
188 for (i = 0; i < 4; i++) {
189 if(!fex[i]) break;
190 fe[i] = fex[i];
192 i = 0;
193 while(typemap[i].fileext) {
194 if(!memcmp(fe, typemap[i].fileext, 4))
195 return typemap[i].content_type;
196 i++;
198 if((temp = open(filename, O_RDONLY)) == -1)
199 return content_type_text_plain;
201 i = read(temp, fe, sizeof(fe));
202 close(temp);
204 if(i < sizeof(fe))
205 return content_type_binary_octet_stream;
207 for(i = 0; i < sizeof(fe); i++)
208 if(fe[i] < 9 || fe[i] > 127)
209 return content_type_binary_octet_stream;
211 return content_type_text_plain;
214 __attribute__ ((noreturn))
215 void httpserver_handle_pathbuf_size(void) {
216 ulz_fprintf(2, "%s", "FATAL: filename on tempfs exceeding 256 bytes\n");
217 exit(1);
220 char* httpserver_get_client_requeststream_fn(httpserver* self, int client) {
221 if(ulz_snprintf(self->pathbuf, sizeof(self->pathbuf), "%s/%d.requ", self->workdir.ptr, client) >= (int) sizeof(self->pathbuf))
222 httpserver_handle_pathbuf_size();
223 return self->pathbuf;
226 char* httpserver_get_client_responsestream_fn(httpserver* self, int client) {
227 if(ulz_snprintf(self->pathbuf, sizeof(self->pathbuf), "%s/%d.resp", self->workdir.ptr, client) >= (int) sizeof(self->pathbuf))
228 httpserver_handle_pathbuf_size();
229 return self->pathbuf;
232 char* httpserver_get_client_infostream_fn(httpserver* self, int client) {
233 if(ulz_snprintf(self->pathbuf, sizeof(self->pathbuf), "%s/%d.info", self->workdir.ptr, client) >= (int) sizeof(self->pathbuf))
234 httpserver_handle_pathbuf_size();
235 return self->pathbuf;
238 char* httpserver_get_client_ip(httpserver* self, struct sockaddr_storage* ip) {
239 #ifndef IPV4_ONLY
240 if(ip->ss_family == PF_INET)
241 return (char*) inet_ntop(PF_INET, &((struct sockaddr_in*) ip)->sin_addr, self->buffer, sizeof(self->buffer));
242 else return (char*) inet_ntop(PF_INET6, &((struct sockaddr_in6*) ip)->sin6_addr, self->buffer, sizeof(self->buffer));
243 #else
244 if(ulz_snprintf(self->buffer, sizeof(self->buffer), "%d.%d.%d.%d",
245 ((unsigned char*)(&((struct sockaddr_in*)ip)->sin_addr))[0],
246 ((unsigned char*)(&((struct sockaddr_in*)ip)->sin_addr))[1],
247 ((unsigned char*)(&((struct sockaddr_in*)ip)->sin_addr))[2],
248 ((unsigned char*)(&((struct sockaddr_in*)ip)->sin_addr))[3]))
249 return self->buffer;
250 return NULL;
251 #endif
254 // doclose: 1: close conn, 0: client already disconnected, -1: init only
255 #ifdef DISCONNECT_DEBUG
256 int httpserver_disconnect_client(httpserver* self, int client, int doclose, int line, const char* file, const char* function) {
257 #else
258 int httpserver_disconnect_client(httpserver* self, int client, int doclose) {
259 #endif
260 if(doclose != -1 && self->log)
261 #ifdef DISCONNECT_DEBUG
262 ulz_fprintf(1, "[%d] disconnecting client (%s.%s:%d) - forced: %d\n", client, file, function, line, doclose);
263 #else
264 ulz_fprintf(1, "[%d] disconnecting client - forced: %d\n", client, doclose);
265 #endif
266 if(doclose == -1) {
267 self->clients[client].requeststream = -1;
268 self->clients[client].responsestream = -1;
269 self->clients[client].responsestream_header = -1;
270 } else {
271 free_stream(&self->clients[client].responsestream_header);
272 free_stream(&self->clients[client].responsestream);
273 free_stream(&self->clients[client].requeststream);
275 self->clients[client].act_responsestream = -1;
277 self->clients[client].requestsize = 0;
278 #ifndef DEBUG
279 unlink(httpserver_get_client_infostream_fn(self, client));
280 unlink(httpserver_get_client_requeststream_fn(self, client));
281 unlink(httpserver_get_client_responsestream_fn(self, client));
282 #endif
283 if(doclose == 1)
284 rocksockserver_disconnect_client(&self->serva, client);
285 if(doclose == 1 || !doclose) {
286 self->numclients--;
287 httpserver_idlemode(self, client);
290 self->clients[client].status = CLIENT_DISCONNECTED;
291 self->clients[client].flags = CL_NONE;
292 return 0;
294 #ifdef DISCONNECT_DEBUG
295 #define _httpserver_disconnect_client(x, y, z) httpserver_disconnect_client(x, y, z, __LINE__, __FILE__, __FUNCTION__);
296 #else
297 #define _httpserver_disconnect_client(x, y, z) httpserver_disconnect_client(x, y, z);
298 #endif
300 int httpserver_on_clientconnect (void* userdata, struct sockaddr_storage* clientaddr, int fd) {
301 static const char ip_msg[] = "IP: ";
302 static const size_t ip_msg_l = sizeof(ip_msg) - 1;
303 static const char dr_msg[] = "DR: ";
304 static const size_t dr_msg_l = sizeof(dr_msg) - 1;
305 httpserver* self = (httpserver*) userdata;
306 int info;
307 unsigned fail = 0;
308 size_t len;
310 if(fd < 0) return -1;
311 if(fd >= USER_MAX_FD) {
312 _httpserver_disconnect_client(self, fd, 1);
313 return -2;
315 // put into nonblocking mode, so that writes will not block the server
316 int flags = fcntl(fd, F_GETFL);
317 if(flags == -1) return -1;
318 if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -2;
319 self->numclients++;
320 _httpserver_disconnect_client(self, fd, -1); // make a clean state and delete the files. this is important for the timeout check.
321 self->clients[fd].status = CLIENT_CONNECTED;
322 self->clients[fd].uploadreq = NULL;
323 if(httpserver_get_client_infostream_fn(self, fd) && httpserver_get_client_ip(self, clientaddr) && (info = open(self->pathbuf, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) != -1) {
324 if((len = strlen(self->buffer)) && len < sizeof(self->buffer) -1) {
325 self->buffer[len] = '\n';
326 len++;
327 self->buffer[len] = 0;
328 } else fail = 1;
329 if(!fail && (
330 (write(info, ip_msg, ip_msg_l) < ip_msg_l) ||
331 (write(info, self->buffer, len) < len) ||
332 (write(info, dr_msg, dr_msg_l) < dr_msg_l) ||
333 (write(info, self->servedir.ptr, self->servedir.size) < self->servedir.size)
334 )) fail = 1;
335 close(info);
336 if(fail) {
337 if(self->log)
338 ulz_fprintf(1, "[%d] error writing info file\n", fd);
339 _httpserver_disconnect_client(self, fd, 1);
340 return -3;
342 if(self->log)
343 ulz_fprintf(1, "[%d] Connect from %s\n", fd, self->buffer);
345 return 0;
348 // returns 1 if clients has reached timeout
349 int httpserver_check_timeout(httpserver* self, int client) {
350 time_t ret;
351 if((ret = getFileModTime(httpserver_get_client_requeststream_fn(self, client))) && (ret + self->timeoutsec) < time(NULL))
352 return 1;
353 return 0;
356 // TODO replace this lousy crap. possibly resetting FDs to -1 on read end.
357 static int myeof(int fildes) {
358 struct stat buf;
359 int ret2;
360 off_t pos = lseek(fildes, 0, SEEK_CUR);
361 if(pos == -1) {
362 log_perror("myeof");
363 } else {
364 ret2 = fstat(fildes, &buf);
365 if(ret2 == -1) log_perror("myeof2");
366 else {
367 if(pos == buf.st_size)
368 return 1;
369 else return 0;
372 return 1; // signal eof in case of error
375 int httpserver_on_clientwantsdata (void* userdata, int fd) {
376 httpserver* self = (httpserver*) userdata;
377 ssize_t nread;
378 ssize_t nwritten;
379 int err;
380 if(self->clients[fd].act_responsestream == -1)
381 self->clients[fd].act_responsestream = self->clients[fd].responsestream_header;
382 if(self->clients[fd].status == CLIENT_WRITING) {
383 if(self->clients[fd].act_responsestream == -1) {
384 goto checkdisconnect;
386 if(myeof(self->clients[fd].act_responsestream)) {
387 close(self->clients[fd].act_responsestream);
388 if(self->clients[fd].act_responsestream == self->clients[fd].responsestream_header) {
389 self->clients[fd].responsestream_header = -1;
390 self->clients[fd].act_responsestream = self->clients[fd].responsestream;
391 } else {
392 self->clients[fd].responsestream = -1;
393 self->clients[fd].act_responsestream = -1;
394 checkdisconnect:
395 if(self->clients[fd].flags & CL_KEEP_ALIVE) {
396 self->clients[fd].status = CLIENT_CONNECTED;
397 httpserver_idlemode(self, fd);
399 else
400 _httpserver_disconnect_client(self, fd, 1);
402 } else {
403 nread = read(self->clients[fd].act_responsestream, self->buffer, sizeof(self->buffer));
404 if(nread <= 0) return 4;
405 nwritten = write(fd, self->buffer, nread);
406 if(nwritten == -1) {
407 err = errno;
408 if(err == EAGAIN || err == EWOULDBLOCK) nwritten = 0;
409 else {
410 if(err != EBADF) self->clients[fd].act_responsestream = -1;
411 log_perror("writing");
412 _httpserver_disconnect_client(self, fd, 0);
413 return 3;
416 if(nwritten < nread)
417 lseek(self->clients[fd].act_responsestream, -(nread - nwritten), SEEK_CUR);
419 } else if ((self->clients[fd].status != CLIENT_DISCONNECTED) &&
420 (!(rand() % 1000) && httpserver_check_timeout(self, fd))
422 _httpserver_disconnect_client(self, fd, 1);
424 return 1;
427 //parses the request, returns -1 if invalid, 0 if not complete, 1 if complete.
428 // if complete, the clientrequest member will be filled.
429 int httpserver_request_header_complete(httpserver* self, int client, clientrequest* req) {
430 static const char CL_LIT[] = "Content-*ength: ";
431 static const size_t CL_LITS = sizeof(CL_LIT) - 1;
432 ssize_t nread;
433 size_t len;
434 int done = 0;
435 char* p;
437 memset(req, 0, sizeof(clientrequest));
438 lseek(self->clients[client].requeststream, 0, SEEK_SET);
440 #define access_ok(x) ((x) - self->buffer < (ptrdiff_t) nread)
441 #define checkrnrn (access_ok(p+3) && *p == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n')
443 do {
444 if((nread = read(self->clients[client].requeststream, self->buffer, sizeof(self->buffer))) == -1) return -1;
445 // set zero termination, to prevent atoi and strstr going out of bounds.
446 // since the buffer is already written to disk, that won't corrupt data.
447 if(nread == sizeof(self->buffer)) {
448 self->buffer[sizeof(self->buffer) -1] = '\0';
449 } else {
450 self->buffer[nread] = '\0';
452 p = self->buffer;
453 if(req->i.rqt == RQT_NONE) {
454 if(!nread || (*p != 'G' && *p != 'P')) return -1; // invalid request, we only accept GET and POST.
455 if (nread <= 5) return 0;
456 if(*p == 'G' && p[1] == 'E' && p[2] == 'T' && p[3] == ' ') {
457 req->i.rqt = RQT_GET;
458 p += 4;
459 } else if (*p == 'P' && p[1] == 'O' && p[2] == 'S' && p[3] == 'T' && p[4] == ' ') {
460 req->i.rqt = RQT_POST;
461 p += 5;
462 } else return -1;
464 req->i.streampos = p - self->buffer;
465 while(access_ok(++p) && *p != '\r');
466 // search for URI.
467 if(access_ok(p) && *p == '\r') {
468 *p = 0;
469 len = ((p - self->buffer) - req->i.streampos);
470 if(len + 1 > sizeof(req->uri))
471 return -1;
472 memcpy(req->uri, self->buffer + req->i.streampos, len+1);
473 req->i.urilength = len;
474 *p = '\r';
475 } else {
476 check_invalid_or_incomplete:
477 if(nread == sizeof(self->buffer)) // if we can't find a valid header in a full buffer
478 return -1;
479 return 0;
481 // p is pointing to the first \r at this point.
482 if(checkrnrn) {
483 req->i.streampos = (p - self->buffer) + 3;
484 return 1;
486 p++;
487 req->i.streampos = p - self->buffer;
488 while(access_ok(p) && !checkrnrn) p++;
489 if(!access_ok(p)) {
490 goto check_invalid_or_incomplete;
492 // search for content-length.
493 len = req->i.streampos; // len points to the end of the GET/POST line.
494 req->i.streampos = (p - self->buffer) + 4;
495 *p = 0;
496 if(len == nread) return -1; // just to be sure...
497 if(req->i.rqt == RQT_POST) {
498 //if(( p = strstr(self->buffer + len, "Content-Length: "))) { // THANX LYNX for "Content-length"
499 if(( p = strstar(self->buffer + len, CL_LIT, CL_LITS))) {
500 if(access_ok(p + CL_LITS + 1)) {
501 p += CL_LITS;
502 req->i.contentlength = atoi(p);
503 } else return -1;
506 // search for keep-alive
507 if(( p = strstr(self->buffer + len, "Connection: "))) {
508 p += 12;
509 if(access_ok(p+10) && (!memcmp(p, "Keep-Alive", 10) || !memcmp(p, "keep-alive", 10)))
510 self->clients[client].flags |= CL_KEEP_ALIVE;
512 return 1;
514 } while (!done && nread == sizeof(self->buffer));
516 return 0;
517 #undef access_ok
518 #undef checkrnrn
521 int httpserver_get_filename(httpserver* self, clientrequest* req) {
522 static const char index_html[] = "index.html";
523 static const size_t index_html_l = sizeof(index_html);
524 char* p = req->uri;
525 size_t vlen = 0;
526 while (*p && *p != '?' && *p != ' ') p++;
527 req->i.urilength = p - req->uri;
528 *p = 0;
529 if(!req->i.urilength
530 #ifdef ALLOW_TRAVERSAL
531 #warning this is a dangerous flag and should only be set for testing!
532 #else
533 || strstr(req->uri, "..")
534 #endif
535 ) return -1;
536 vlen = (p[-1] == '/') ? index_html_l : 0;
537 if(self->servedir.size + req->i.urilength + vlen >= sizeof(self->pathbuf)) return -2;
538 memcpy(self->pathbuf, self->servedir.ptr, self->servedir.size);
539 memcpy(self->pathbuf + self->servedir.size, req->uri, req->i.urilength + 1);
540 if(vlen) memcpy(self->pathbuf + self->servedir.size + req->i.urilength, index_html, vlen+1);
541 return 0;
544 int httpserver_spawn(httpserver* self, char* script, int client, scripttype stype) {
545 char scriptcp[256];
546 char reqfn[256];
547 char infofn[256];
548 pid_t pid;
549 int ret;
550 (void) stype;
551 strncpy(scriptcp, script, sizeof(scriptcp));
552 httpserver_get_client_infostream_fn(self, client);
553 strncpy(infofn, self->pathbuf, sizeof(infofn));
554 httpserver_get_client_requeststream_fn(self, client);
555 strncpy(reqfn, self->pathbuf, sizeof(reqfn));
556 httpserver_get_client_responsestream_fn(self, client);
557 pid = fork();
558 if(!pid) {
559 chdir(self->servedir.ptr);
560 execl(scriptcp, scriptcp, reqfn, self->pathbuf, infofn, NULL);
561 } else if(pid < 0) log_perror("failed to fork");
562 else {
563 wait(&ret);
565 if(ret && self->log)
566 strncpy(self->pathbuf, scriptcp, sizeof(self->pathbuf));
567 return ret;
570 int httpserver_deliver(httpserver* self, int client, clientrequest* req) {
571 static const char err500[] = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nContent-Length: 28\r\n\r\n500 - Internal Server Error.";
572 static const size_t err500l = sizeof(err500);
573 static const char err404[] = "HTTP/1.1 404 Not found\r\nContent-Type: text/html\r\nContent-Length: 3\r\n\r\n404";
574 static const size_t err404l = sizeof(err404);
575 static const char err200[] = "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n";
576 //static const size_t err200l = sizeof(err200);
578 size_t len;
579 int ret = 0;
580 int res;
581 char* rh = NULL;
582 size_t rl;
583 char* fe;
584 scripttype st = ST_NONE;
585 free_stream(&self->clients[client].requeststream); // otherwise buffers may not be flushed to disk
586 self->clients[client].status = CLIENT_WRITING;
587 httpserver_idlemode(self, client);
589 #define respond(x, y, z) do {rh = (char*) x; rl = y; res = z; goto writeheader;} while (0);
591 #ifdef SERVER_MODE_BLANK
592 /* in this mode, the server always returns success and a blank html site
593 useful for sites you redirected to localhost via /etc/hosts */
595 static const char blank_page[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n<html></html>";
596 static const size_t blank_page_l = sizeof(blank_page);
597 respond(blank_page, blank_page_l, 0);
598 #endif
600 if(req)
601 ret = httpserver_get_filename(self, req);
603 if(!req || ret)
604 respond(err500, err500l, 1);
606 if(access(self->pathbuf, R_OK) == -1)
607 respond(err404, err404l, 2);
609 fe = getFileExt(self->pathbuf, strlen(self->pathbuf));
610 if(!memcmp(fe, "pl", 2) || !memcmp(fe, "cgi", 3)) {
611 if(access(self->pathbuf, X_OK) == -1) {
612 if(self->log)
613 ulz_fprintf(1, "[%d] script %s not executable\n", client, self->pathbuf);
614 respond(err404, err404l, 2);
616 st = ST_PERL;
617 goto runscript;
618 runscript:
619 ret = httpserver_spawn(self, self->pathbuf, client, st);
620 if(ret && self->log)
621 ulz_fprintf(1, "[%d] script %s returned error code %d\n", client, self->pathbuf, ret);
622 respond(NULL, 0, 0);
624 } else {
625 len = getfilesize(self->pathbuf);
626 if(len > sizeof(self->buffer))
627 httpserver_turbomode(self, client);
628 if((self->clients[client].responsestream = open(self->pathbuf, O_RDONLY)) != -1) {
629 respond(self->buffer, ulz_snprintf(self->buffer, sizeof(self->buffer), err200, httpserver_get_contenttype(self->pathbuf, fe), (int) len), 0);
630 } else respond(err404, err404l, 2);
633 #undef respond
635 writeheader:
636 if(self->log) {
637 if(rh == err500)
638 ulz_fprintf(1, "[%d] 500.\n", client);
639 else if(rh == err404)
640 ulz_fprintf(1, "[%d] 404 %s\n", client, req->uri);
641 else
642 ulz_fprintf(1, "[%d] 200 %s\n", client, req->uri);
644 if((self->clients[client].responsestream_header = open(httpserver_get_client_responsestream_fn(self, client), rh ? O_RDWR | O_CREAT | O_TRUNC : O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) == -1) {
645 ulz_fprintf(1, "%s\n", httpserver_get_client_responsestream_fn(self, client));
646 log_perror("failed to open response file");
647 return 1;
649 if(rh && write(self->clients[client].responsestream_header, rh, rl) != rl) {
650 if(self->log)
651 ulz_fprintf(1, "[%d] error writing to response file\n", client);
652 _httpserver_disconnect_client(self, client, 1);
655 return res;
658 int httpserver_on_clientread (void* userdata, int fd, size_t nread) {
659 httpserver* self = (httpserver*) userdata;
660 clientrequest req, *curreq;
661 int uploadflag = 0;
662 int ret;
663 switch(self->clients[fd].status) {
664 case CLIENT_CONNECTED:
665 self->clients[fd].requeststream = open(httpserver_get_client_requeststream_fn(self, fd), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
666 if(self->clients[fd].requeststream == -1) {
667 log_perror("failed to open requeststream");
668 _httpserver_disconnect_client(self, fd, 1);
669 return 2;
671 self->clients[fd].status = CLIENT_READING;
672 self->clients[fd].requestsize = 0;
673 case CLIENT_READING:
674 if(!self->clients[fd].requestsize && nread == sizeof(self->buffer))
675 httpserver_turbomode(self, fd);
677 readchunk:
678 self->clients[fd].requestsize += nread;
679 if(write(self->clients[fd].requeststream, self->buffer, nread) != nread) {
680 if(self->log)
681 ulz_fprintf(1, "[%d] error writing to request file\n", fd);
682 _httpserver_disconnect_client(self, fd, 1);
683 goto finish;
685 if(uploadflag) {
686 curreq = self->clients[fd].uploadreq;
687 } else {
688 curreq = &req;
689 ret = httpserver_request_header_complete(self, fd, &req);
690 if(!ret) return 0;
691 if(ret == -1) {
692 httpserver_deliver(self, fd, NULL);
693 goto finish;
696 if(curreq->i.rqt == RQT_GET || self->clients[fd].requestsize == curreq->i.contentlength + curreq->i.streampos) {
697 httpserver_deliver(self, fd, curreq);
698 if(uploadflag) {
699 SSNULL(self->clients[fd].uploadreq);
700 uploadflag = 0;
702 } else if(!uploadflag && req.i.rqt == RQT_POST) {
703 self->clients[fd].status = CLIENT_UPLOADING;
704 self->clients[fd].uploadreq = SSALLOC(sizeof(clientrequest));
705 if(!self->clients[fd].uploadreq) return -1;
706 *(self->clients[fd].uploadreq) = req;
707 uploadflag = 1;
708 } else
709 return 0;
710 finish:
711 if(self->clients[fd].responsestream_header != -1) lseek(self->clients[fd].responsestream_header, 0, SEEK_SET);
712 if(uploadflag) break;
713 free_stream(&self->clients[fd].requeststream);
714 break;
715 case CLIENT_UPLOADING:
716 uploadflag = 1;
717 goto readchunk;
718 default:
719 return 1;
721 return 0;
724 int httpserver_on_clientdisconnect (void* userdata, int fd) {
725 httpserver* self = (httpserver*) userdata;
726 if(fd < 0 || fd >= USER_MAX_FD)
727 return -1;
728 return _httpserver_disconnect_client(self, fd, 0);
731 int httpserver_init(httpserver* srv, char* listenip, short port, const char* workdir, const char* servedir, int log, int timeout, int uid, int gid) {
732 if(!srv || !workdir || !servedir) return 1;
733 memset(srv, 0, sizeof(httpserver));
734 srv->servedir.size = strlen(servedir);
735 srv->servedir.ptr = (char*) servedir;
736 srv->maxrequestsize = 20 * 1024 * 1024;
737 srv->timeoutsec = timeout;
738 srv->log = log;
739 if(rocksockserver_init(&srv->serva, listenip, port, (void*) srv)) return -1;
740 //dropping privs after bind()
741 if(gid != -1 && setgid(gid) == -1)
742 log_perror("setgid");
743 if(gid != -1 && setgroups(0, NULL) == -1)
744 log_perror("setgroups");
745 if(uid != -1 && setuid(uid) == -1)
746 log_perror("setuid");
748 // set up a temporary dir with 0700, and unpredictable name
749 ulz_snprintf(srv->tempdir, sizeof(srv->tempdir), "%s/XXXXXX", workdir);
750 if(!ulz_mkdtemp(srv->tempdir)) {
751 log_perror("mkdtemp");
752 exit(1);
755 srv->workdir.size = strlen(srv->tempdir);
756 srv->workdir.ptr = srv->tempdir;
758 if(rocksockserver_loop(&srv->serva, srv->buffer, sizeof(srv->buffer), &httpserver_on_clientconnect, &httpserver_on_clientread, &httpserver_on_clientwantsdata, &httpserver_on_clientdisconnect)) return -2;
759 return 0;
762 __attribute__ ((noreturn))
763 void syntax(op_state* opt) {
764 ulz_printf("progname -srvroot=/srv/htdocs -tempfs=/dev/shm/ -listenip=0.0.0.0 -port=80 -timeout=30 -log=0 -uid=0 -gid=0 -d\n");
765 ulz_printf("all options except tempfs and srvroot are optional\n");
766 ulz_printf("passed options were:\n");
767 op_printall(opt);
768 exit(1);
771 int main(int argc, char** argv) {
772 httpserver srv;
773 static const char defaultip[] = "127.0.0.1";
774 op_state opts, *opt = &opts;
775 op_init(opt, argc, argv);
776 SPDECLAREC(o_srvroot, op_get(opt, SPLITERAL("srvroot")));
777 SPDECLAREC(o_tempfs, op_get(opt, SPLITERAL("tempfs")));
778 SPDECLAREC(o_port, op_get(opt, SPLITERAL("port")));
779 SPDECLAREC(o_listenip, op_get(opt, SPLITERAL("listenip")));
780 SPDECLAREC(o_timeout, op_get(opt, SPLITERAL("timeout")));
781 SPDECLAREC(o_log, op_get(opt, SPLITERAL("log")));
782 SPDECLAREC(o_uid, op_get(opt, SPLITERAL("uid")));
783 SPDECLAREC(o_gid, op_get(opt, SPLITERAL("gid")));
785 int log = o_log->size ? atoi(o_log->ptr) : 1;
786 int timeout = o_timeout->size ? atoi(o_timeout->ptr) : 30;
787 char* ip = o_listenip->size ? o_listenip->ptr : (char*) defaultip;
788 int port = o_port->size ? atoi(o_port->ptr) : 80;
790 if(argc < 3 || !o_srvroot->size || !o_tempfs->size) syntax(opt);
791 SSINIT;
793 if(op_hasflag(opt, SPL("d"))) daemonize();
795 httpserver_init(&srv, ip, port, o_tempfs->ptr, o_srvroot->ptr, log, timeout, o_uid->size ? atoi(o_uid->ptr) : -1, o_gid->size ? atoi(o_gid->ptr) : -1);
797 return 0;