nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@3: #include nuclear@3: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@5: #include nuclear@0: #include nuclear@5: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@1: #include "http.h" nuclear@5: #include "mime.h" nuclear@1: nuclear@1: /* HTTP version */ nuclear@1: #define HTTP_VER_MAJOR 1 nuclear@1: #define HTTP_VER_MINOR 1 nuclear@1: #define HTTP_VER_STR "1.1" nuclear@1: nuclear@1: /* maximum request length: 64mb */ nuclear@1: #define MAX_REQ_LENGTH (65536 * 1024) nuclear@0: nuclear@0: struct client { nuclear@0: int s; nuclear@0: char *rcvbuf; nuclear@0: int bufsz; nuclear@0: struct client *next; nuclear@0: }; nuclear@0: nuclear@0: int start_server(void); nuclear@0: int accept_conn(int lis); nuclear@3: void close_conn(struct client *c); nuclear@0: int handle_client(struct client *c); nuclear@5: int do_get(struct client *c, const char *uri, int with_body); nuclear@1: void respond_error(struct client *c, int errcode); nuclear@3: void sighandler(int s); nuclear@1: int parse_args(int argc, char **argv); nuclear@0: nuclear@0: static int lis; nuclear@0: static int port = 8080; nuclear@0: static struct client *clist; nuclear@0: nuclear@5: static const char *indexfiles[] = { nuclear@5: "index.cgi", nuclear@5: "index.html", nuclear@5: "index.htm", nuclear@5: 0 nuclear@5: }; nuclear@5: nuclear@5: nuclear@0: int main(int argc, char **argv) nuclear@0: { nuclear@1: if(parse_args(argc, argv) == -1) { nuclear@1: return 1; nuclear@1: } nuclear@1: nuclear@3: signal(SIGINT, sighandler); nuclear@3: signal(SIGTERM, sighandler); nuclear@3: signal(SIGQUIT, sighandler); nuclear@3: nuclear@0: if((lis = start_server()) == -1) { nuclear@0: return 1; nuclear@0: } nuclear@0: nuclear@0: for(;;) { nuclear@0: struct client *c, dummy; nuclear@0: int maxfd = lis; nuclear@0: fd_set rdset; nuclear@0: nuclear@0: FD_ZERO(&rdset); nuclear@0: FD_SET(lis, &rdset); nuclear@0: nuclear@0: c = clist; nuclear@0: while(c) { nuclear@0: if(c->s > maxfd) { nuclear@0: maxfd = c->s; nuclear@0: } nuclear@0: FD_SET(c->s, &rdset); nuclear@0: c = c->next; nuclear@0: } nuclear@0: nuclear@0: while(select(maxfd + 1, &rdset, 0, 0, 0) == -1 && errno == EINTR); nuclear@0: nuclear@0: c = clist; nuclear@0: while(c) { nuclear@0: if(FD_ISSET(c->s, &rdset)) { nuclear@0: handle_client(c); nuclear@0: } nuclear@0: c = c->next; nuclear@0: } nuclear@0: nuclear@0: if(FD_ISSET(lis, &rdset)) { nuclear@0: accept_conn(lis); nuclear@0: } nuclear@0: nuclear@0: dummy.next = clist; nuclear@0: c = &dummy; nuclear@0: nuclear@0: while(c->next) { nuclear@0: struct client *n = c->next; nuclear@0: nuclear@0: if(n->s == -1) { nuclear@0: /* marked for removal */ nuclear@0: c->next = n->next; nuclear@0: free(n); nuclear@0: } else { nuclear@0: c = c->next; nuclear@0: } nuclear@0: } nuclear@4: clist = dummy.next; nuclear@0: } nuclear@3: nuclear@3: return 0; /* unreachable */ nuclear@0: } nuclear@0: nuclear@0: int start_server(void) nuclear@0: { nuclear@0: int s; nuclear@0: struct sockaddr_in sa; nuclear@0: nuclear@0: if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { nuclear@0: perror("failed to create listening socket"); nuclear@0: return -1; nuclear@0: } nuclear@0: fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); nuclear@0: nuclear@0: memset(&sa, 0, sizeof sa); nuclear@0: sa.sin_family = AF_INET; nuclear@0: sa.sin_addr.s_addr = INADDR_ANY; nuclear@0: sa.sin_port = htons(port); nuclear@0: nuclear@0: if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) { nuclear@0: fprintf(stderr, "failed to bind socket to port %d: %s\n", port, strerror(errno)); nuclear@0: return -1; nuclear@0: } nuclear@0: listen(s, 16); nuclear@0: nuclear@0: return s; nuclear@0: } nuclear@0: nuclear@0: int accept_conn(int lis) nuclear@0: { nuclear@0: int s; nuclear@0: struct client *c; nuclear@0: struct sockaddr_in addr; nuclear@0: socklen_t addr_sz = sizeof addr; nuclear@0: nuclear@0: if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) { nuclear@0: perror("failed to accept incoming connection"); nuclear@0: return -1; nuclear@0: } nuclear@0: fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); nuclear@0: nuclear@0: if(!(c = malloc(sizeof *c))) { nuclear@0: perror("failed to allocate memory while accepting connection"); nuclear@0: return -1; nuclear@0: } nuclear@0: c->s = s; nuclear@0: c->rcvbuf = 0; nuclear@0: c->bufsz = 0; nuclear@0: c->next = clist; nuclear@0: clist = c; nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@1: void close_conn(struct client *c) nuclear@1: { nuclear@1: close(c->s); nuclear@1: c->s = -1; /* mark it for removal */ nuclear@1: free(c->rcvbuf); nuclear@1: } nuclear@1: nuclear@0: int handle_client(struct client *c) nuclear@0: { nuclear@3: struct http_req_header hdr; nuclear@0: static char buf[2048]; nuclear@3: int rdsz, status; nuclear@0: nuclear@0: while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) { nuclear@4: char *newbuf; nuclear@4: int newsz = c->bufsz + rdsz; nuclear@4: if(newsz > MAX_REQ_LENGTH) { nuclear@4: respond_error(c, 413); nuclear@4: return -1; nuclear@4: } nuclear@1: nuclear@4: if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) { nuclear@4: fprintf(stderr, "failed to allocate %d byte buffer\n", newsz); nuclear@4: respond_error(c, 503); nuclear@4: return -1; nuclear@4: } nuclear@0: nuclear@4: memcpy(newbuf + c->bufsz, buf, rdsz); nuclear@4: newbuf[newsz] = 0; nuclear@0: nuclear@4: c->rcvbuf = newbuf; nuclear@4: c->bufsz = newsz; nuclear@0: } nuclear@0: nuclear@3: if((status = http_parse_header(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) { nuclear@3: http_print_header(&hdr); nuclear@3: switch(status) { nuclear@3: case HTTP_HDR_INVALID: nuclear@3: respond_error(c, 400); nuclear@3: return -1; nuclear@3: nuclear@3: case HTTP_HDR_NOMEM: nuclear@3: respond_error(c, 503); nuclear@3: return -1; nuclear@3: nuclear@3: case HTTP_HDR_PARTIAL: nuclear@3: return 0; /* partial header, continue reading */ nuclear@3: } nuclear@3: } nuclear@3: http_print_header(&hdr); nuclear@3: nuclear@3: /* we only support GET and HEAD at this point, so freak out on anything else */ nuclear@3: switch(hdr.method) { nuclear@3: case HTTP_GET: nuclear@5: do_get(c, hdr.uri, 1); nuclear@5: break; nuclear@5: nuclear@3: case HTTP_HEAD: nuclear@5: do_get(c, hdr.uri, 0); nuclear@3: break; nuclear@3: nuclear@3: default: nuclear@3: respond_error(c, 501); nuclear@3: return -1; nuclear@1: } nuclear@1: nuclear@3: close_conn(c); nuclear@3: return 0; nuclear@3: } nuclear@1: nuclear@5: int do_get(struct client *c, const char *uri, int with_body) nuclear@3: { nuclear@5: const char *ptr; nuclear@5: struct http_resp_header resp; nuclear@5: nuclear@5: if((ptr = strstr(uri, "://"))) { nuclear@5: /* skip the host part */ nuclear@5: if(!(uri = strchr(ptr + 3, '/'))) { nuclear@5: respond_error(c, 404); nuclear@5: return -1; nuclear@5: } nuclear@5: ++uri; nuclear@5: } nuclear@5: nuclear@5: if(*uri) { nuclear@5: struct stat st; nuclear@5: char *path = 0; nuclear@5: char *buf; nuclear@5: const char *type; nuclear@5: int fd, size; nuclear@5: nuclear@5: if(stat(uri, &st) == -1) { nuclear@5: respond_error(c, 404); nuclear@5: return -1; nuclear@5: } nuclear@5: nuclear@5: if(S_ISDIR(st.st_mode)) { nuclear@5: int i; nuclear@5: path = alloca(strlen(uri) + 64); nuclear@5: nuclear@5: for(i=0; indexfiles[i]; i++) { nuclear@5: sprintf(path, "%s/%s", uri, indexfiles[i]); nuclear@5: if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) { nuclear@5: break; nuclear@5: } nuclear@5: } nuclear@5: nuclear@5: if(indexfiles[i] == 0) { nuclear@5: respond_error(c, 404); nuclear@5: return -1; nuclear@5: } nuclear@5: } else { nuclear@5: path = (char*)uri; nuclear@5: } nuclear@5: nuclear@5: if((fd = open(path, O_RDONLY)) == -1) { nuclear@5: respond_error(c, 403); nuclear@5: return -1; nuclear@5: } nuclear@5: nuclear@5: /* construct response header */ nuclear@5: http_init_resp(&resp); nuclear@5: http_add_resp_field(&resp, "Content-Length: %d", st.st_size); nuclear@5: if((type = mime_type(path))) { nuclear@5: http_add_resp_field(&resp, "Content-Type: %s", type); nuclear@5: } nuclear@5: nuclear@5: size = http_serialize_resp(&resp, 0); nuclear@5: buf = alloca(size); nuclear@5: http_serialize_resp(&resp, buf); nuclear@5: send(c->s, buf, size, 0); nuclear@5: nuclear@5: if(with_body) { nuclear@5: char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); nuclear@5: if(cont == (void*)-1) { nuclear@5: respond_error(c, 503); nuclear@5: close(fd); nuclear@5: return -1; nuclear@5: } nuclear@5: } nuclear@5: nuclear@5: close(fd); nuclear@5: } nuclear@5: return 0; nuclear@1: } nuclear@0: nuclear@1: void respond_error(struct client *c, int errcode) nuclear@1: { nuclear@1: char buf[512]; nuclear@1: nuclear@1: sprintf(buf, HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode)); nuclear@1: nuclear@1: send(c->s, buf, strlen(buf), 0); nuclear@1: close_conn(c); nuclear@1: } nuclear@1: nuclear@3: void sighandler(int s) nuclear@3: { nuclear@3: if(s == SIGINT || s == SIGTERM || s == SIGQUIT) { nuclear@3: close(lis); nuclear@3: while(clist) { nuclear@3: struct client *c = clist; nuclear@3: clist = clist->next; nuclear@3: close_conn(c); nuclear@3: free(c); nuclear@3: } nuclear@3: clist = 0; nuclear@3: nuclear@3: printf("bye!\n"); nuclear@3: exit(0); nuclear@3: } nuclear@3: } nuclear@3: nuclear@1: nuclear@1: static void print_help(const char *argv0) nuclear@1: { nuclear@1: printf("Usage: %s [options]\n", argv0); nuclear@1: printf("Options:\n"); nuclear@1: printf(" -p set the TCP/IP port number to use\n"); nuclear@1: printf(" -h print usage help and exit\n"); nuclear@1: } nuclear@1: nuclear@1: int parse_args(int argc, char **argv) nuclear@1: { nuclear@1: int i; nuclear@1: nuclear@1: for(i=1; i