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>
28 #include <arpa/inet.h>
29 #include <netinet/in.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";
63 const char* content_type
;
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
},
81 CLIENT_DISCONNECTED
= 0,
102 size_t contentlength
;
104 } clientrequest_inner
;
107 clientrequest_inner i
;
108 char uri
[1024 - sizeof(clientrequest_inner
)];
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"
124 clientrequest
* uploadreq
;
128 int responsestream_header
;
130 int act_responsestream
;
133 #if (! defined(USER_BUFSIZE_KB)) || (USER_BUFSIZE_KB > 1024)
134 #define USER_BUFSIZE_KB 96
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.
147 httpclient clients
[USER_MAX_FD
];
148 rocksockserver serva
;
149 size_t maxrequestsize
;
156 static void free_stream(int* f
) {
163 // client is requesting big data
164 void httpserver_turbomode(httpserver
* self
, int client
) {
165 if(!(self
->clients
[client
].flags
& CL_NEEDS_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
) {
176 self
->clients
[client
].flags
&= ~CL_NEEDS_TURBO
;
179 rocksockserver_set_sleeptime(&self
->serva
, 5000);
182 const char* httpserver_get_contenttype(char* filename
, char* fileext
) {
184 unsigned char fe
[16] = {0};
185 unsigned char* fex
= (unsigned char*) fileext
;
188 for (i
= 0; i
< 4; i
++) {
193 while(typemap
[i
].fileext
) {
194 if(!memcmp(fe
, typemap
[i
].fileext
, 4))
195 return typemap
[i
].content_type
;
198 if((temp
= open(filename
, O_RDONLY
)) == -1)
199 return content_type_text_plain
;
201 i
= read(temp
, fe
, 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");
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
) {
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
));
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]))
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
) {
258 int httpserver_disconnect_client(httpserver
* self
, int client
, int doclose
) {
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
);
264 ulz_fprintf(1, "[%d] disconnecting client - forced: %d\n", client
, doclose
);
267 self
->clients
[client
].requeststream
= -1;
268 self
->clients
[client
].responsestream
= -1;
269 self
->clients
[client
].responsestream_header
= -1;
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;
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
));
284 rocksockserver_disconnect_client(&self
->serva
, client
);
285 if(doclose
== 1 || !doclose
) {
287 httpserver_idlemode(self
, client
);
290 self
->clients
[client
].status
= CLIENT_DISCONNECTED
;
291 self
->clients
[client
].flags
= CL_NONE
;
294 #ifdef DISCONNECT_DEBUG
295 #define _httpserver_disconnect_client(x, y, z) httpserver_disconnect_client(x, y, z, __LINE__, __FILE__, __FUNCTION__);
297 #define _httpserver_disconnect_client(x, y, z) httpserver_disconnect_client(x, y, z);
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
;
310 if(fd
< 0) return -1;
311 if(fd
>= USER_MAX_FD
) {
312 _httpserver_disconnect_client(self
, fd
, 1);
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;
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';
327 self
->buffer
[len
] = 0;
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
)
338 ulz_fprintf(1, "[%d] error writing info file\n", fd
);
339 _httpserver_disconnect_client(self
, fd
, 1);
343 ulz_fprintf(1, "[%d] Connect from %s\n", fd
, self
->buffer
);
348 // returns 1 if clients has reached timeout
349 int httpserver_check_timeout(httpserver
* self
, int client
) {
351 if((ret
= getFileModTime(httpserver_get_client_requeststream_fn(self
, client
))) && (ret
+ self
->timeoutsec
) < time(NULL
))
356 // TODO replace this lousy crap. possibly resetting FDs to -1 on read end.
357 static int myeof(int fildes
) {
360 off_t pos
= lseek(fildes
, 0, SEEK_CUR
);
364 ret2
= fstat(fildes
, &buf
);
365 if(ret2
== -1) log_perror("myeof2");
367 if(pos
== buf
.st_size
)
372 return 1; // signal eof in case of error
375 int httpserver_on_clientwantsdata (void* userdata
, int fd
) {
376 httpserver
* self
= (httpserver
*) userdata
;
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
;
392 self
->clients
[fd
].responsestream
= -1;
393 self
->clients
[fd
].act_responsestream
= -1;
395 if(self
->clients
[fd
].flags
& CL_KEEP_ALIVE
) {
396 self
->clients
[fd
].status
= CLIENT_CONNECTED
;
397 httpserver_idlemode(self
, fd
);
400 _httpserver_disconnect_client(self
, fd
, 1);
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
);
408 if(err
== EAGAIN
|| err
== EWOULDBLOCK
) nwritten
= 0;
410 if(err
!= EBADF
) self
->clients
[fd
].act_responsestream
= -1;
411 log_perror("writing");
412 _httpserver_disconnect_client(self
, fd
, 0);
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);
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;
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')
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';
450 self
->buffer
[nread
] = '\0';
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
;
459 } else if (*p
== 'P' && p
[1] == 'O' && p
[2] == 'S' && p
[3] == 'T' && p
[4] == ' ') {
460 req
->i
.rqt
= RQT_POST
;
464 req
->i
.streampos
= p
- self
->buffer
;
465 while(access_ok(++p
) && *p
!= '\r');
467 if(access_ok(p
) && *p
== '\r') {
469 len
= ((p
- self
->buffer
) - req
->i
.streampos
);
470 if(len
+ 1 > sizeof(req
->uri
))
472 memcpy(req
->uri
, self
->buffer
+ req
->i
.streampos
, len
+1);
473 req
->i
.urilength
= len
;
476 check_invalid_or_incomplete
:
477 if(nread
== sizeof(self
->buffer
)) // if we can't find a valid header in a full buffer
481 // p is pointing to the first \r at this point.
483 req
->i
.streampos
= (p
- self
->buffer
) + 3;
487 req
->i
.streampos
= p
- self
->buffer
;
488 while(access_ok(p
) && !checkrnrn
) 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;
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)) {
502 req
->i
.contentlength
= atoi(p
);
506 // search for keep-alive
507 if(( p
= strstr(self
->buffer
+ len
, "Connection: "))) {
509 if(access_ok(p
+10) && (!memcmp(p
, "Keep-Alive", 10) || !memcmp(p
, "keep-alive", 10)))
510 self
->clients
[client
].flags
|= CL_KEEP_ALIVE
;
514 } while (!done
&& nread
== sizeof(self
->buffer
));
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
);
526 while (*p
&& *p
!= '?' && *p
!= ' ') p
++;
527 req
->i
.urilength
= p
- req
->uri
;
530 #ifdef ALLOW_TRAVERSAL
531 #warning this is a dangerous flag and should only be set for testing!
533 || strstr(req
->uri
, "..")
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);
544 int httpserver_spawn(httpserver
* self
, char* script
, int client
, scripttype 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
);
559 chdir(self
->servedir
.ptr
);
560 execl(scriptcp
, scriptcp
, reqfn
, self
->pathbuf
, infofn
, NULL
);
561 } else if(pid
< 0) log_perror("failed to fork");
566 strncpy(self
->pathbuf
, scriptcp
, sizeof(self
->pathbuf
));
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);
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);
601 ret
= httpserver_get_filename(self
, req
);
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) {
613 ulz_fprintf(1, "[%d] script %s not executable\n", client
, self
->pathbuf
);
614 respond(err404
, err404l
, 2);
619 ret
= httpserver_spawn(self
, self
->pathbuf
, client
, st
);
621 ulz_fprintf(1, "[%d] script %s returned error code %d\n", client
, self
->pathbuf
, ret
);
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);
638 ulz_fprintf(1, "[%d] 500.\n", client
);
639 else if(rh
== err404
)
640 ulz_fprintf(1, "[%d] 404 %s\n", client
, req
->uri
);
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");
649 if(rh
&& write(self
->clients
[client
].responsestream_header
, rh
, rl
) != rl
) {
651 ulz_fprintf(1, "[%d] error writing to response file\n", client
);
652 _httpserver_disconnect_client(self
, client
, 1);
658 int httpserver_on_clientread (void* userdata
, int fd
, size_t nread
) {
659 httpserver
* self
= (httpserver
*) userdata
;
660 clientrequest req
, *curreq
;
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);
671 self
->clients
[fd
].status
= CLIENT_READING
;
672 self
->clients
[fd
].requestsize
= 0;
674 if(!self
->clients
[fd
].requestsize
&& nread
== sizeof(self
->buffer
))
675 httpserver_turbomode(self
, fd
);
678 self
->clients
[fd
].requestsize
+= nread
;
679 if(write(self
->clients
[fd
].requeststream
, self
->buffer
, nread
) != nread
) {
681 ulz_fprintf(1, "[%d] error writing to request file\n", fd
);
682 _httpserver_disconnect_client(self
, fd
, 1);
686 curreq
= self
->clients
[fd
].uploadreq
;
689 ret
= httpserver_request_header_complete(self
, fd
, &req
);
692 httpserver_deliver(self
, fd
, NULL
);
696 if(curreq
->i
.rqt
== RQT_GET
|| self
->clients
[fd
].requestsize
== curreq
->i
.contentlength
+ curreq
->i
.streampos
) {
697 httpserver_deliver(self
, fd
, curreq
);
699 SSNULL(self
->clients
[fd
].uploadreq
);
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
;
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
);
715 case CLIENT_UPLOADING
:
724 int httpserver_on_clientdisconnect (void* userdata
, int fd
) {
725 httpserver
* self
= (httpserver
*) userdata
;
726 if(fd
< 0 || fd
>= USER_MAX_FD
)
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
;
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");
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;
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");
771 int main(int argc
, char** argv
) {
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
);
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);