nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include nuclear@7: #include "tinyweb.h" nuclear@7: #include "http.h" nuclear@7: #include "mime.h" nuclear@7: #include "logger.h" nuclear@7: nuclear@7: /* HTTP version */ nuclear@7: #define HTTP_VER_MAJOR 1 nuclear@7: #define HTTP_VER_MINOR 1 nuclear@7: #define HTTP_VER_STR "1.1" nuclear@7: nuclear@7: /* maximum request length: 64mb */ nuclear@7: #define MAX_REQ_LENGTH (65536 * 1024) nuclear@7: nuclear@7: struct client { nuclear@7: int s; nuclear@7: char *rcvbuf; nuclear@7: int bufsz; nuclear@7: struct client *next; nuclear@7: }; nuclear@7: nuclear@7: static int accept_conn(int lis); nuclear@7: static void close_conn(struct client *c); nuclear@7: static int handle_client(struct client *c); nuclear@7: static int do_get(struct client *c, const char *uri, int with_body); nuclear@7: static void respond_error(struct client *c, int errcode); nuclear@7: nuclear@7: static int lis = -1; nuclear@7: static int maxfd; nuclear@7: static int port = 8080; nuclear@7: static struct client *clist; nuclear@7: static int num_clients; nuclear@7: nuclear@7: static const char *indexfiles[] = { nuclear@7: "index.cgi", nuclear@7: "index.html", nuclear@7: "index.htm", nuclear@7: 0 nuclear@7: }; nuclear@7: nuclear@7: void tw_set_port(int p) nuclear@7: { nuclear@7: port = p; nuclear@7: } nuclear@7: nuclear@7: int tw_set_root(const char *path) nuclear@7: { nuclear@7: return chdir(path); nuclear@7: } nuclear@7: nuclear@7: int tw_set_logfile(const char *fname) nuclear@7: { nuclear@7: return set_log_file(fname); nuclear@7: } nuclear@7: nuclear@7: int tw_start(void) nuclear@7: { nuclear@7: int s; nuclear@7: struct sockaddr_in sa; nuclear@7: nuclear@7: logmsg("starting server ...\n"); nuclear@7: nuclear@7: if(lis != -1) { nuclear@7: logmsg("can't start tinyweb server: already running!\n"); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { nuclear@7: logmsg("failed to create listening socket: %s\n", strerror(errno)); nuclear@7: return -1; nuclear@7: } nuclear@7: fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); nuclear@7: nuclear@7: memset(&sa, 0, sizeof sa); nuclear@7: sa.sin_family = AF_INET; nuclear@7: sa.sin_addr.s_addr = INADDR_ANY; nuclear@7: sa.sin_port = htons(port); nuclear@7: nuclear@7: if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) { nuclear@7: logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno)); nuclear@7: return -1; nuclear@7: } nuclear@7: listen(s, 16); nuclear@7: nuclear@7: lis = s; nuclear@7: return s; nuclear@7: } nuclear@7: nuclear@7: int tw_stop(void) nuclear@7: { nuclear@7: if(lis == -1) { nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: logmsg("stopping server...\n"); nuclear@7: nuclear@7: close(lis); nuclear@7: lis = -1; nuclear@7: nuclear@7: while(clist) { nuclear@7: struct client *c = clist; nuclear@7: clist = clist->next; nuclear@7: close_conn(c); nuclear@7: free(c); nuclear@7: } nuclear@7: clist = 0; nuclear@7: nuclear@7: return 0; nuclear@7: } nuclear@7: nuclear@7: int tw_get_sockets(int *socks) nuclear@7: { nuclear@7: struct client *c, dummy; nuclear@7: nuclear@7: if(!socks) { nuclear@7: /* just return the count */ nuclear@7: return num_clients + 1; /* +1 for the listening socket */ nuclear@7: } nuclear@7: nuclear@7: /* first cleanup the clients marked for removal */ nuclear@7: dummy.next = clist; nuclear@7: c = &dummy; nuclear@7: nuclear@7: while(c->next) { nuclear@7: struct client *n = c->next; nuclear@7: nuclear@7: if(n->s == -1) { nuclear@7: /* marked for removal */ nuclear@7: c->next = n->next; nuclear@7: free(n); nuclear@7: --num_clients; nuclear@7: } else { nuclear@7: c = c->next; nuclear@7: } nuclear@7: } nuclear@7: clist = dummy.next; nuclear@7: nuclear@7: /* go through the client list and populate the array */ nuclear@7: maxfd = lis; nuclear@7: *socks++ = lis; nuclear@7: nuclear@7: c = clist; nuclear@7: while(c) { nuclear@7: *socks++ = c->s; nuclear@7: if(c->s > maxfd) { nuclear@7: maxfd = c->s; nuclear@7: } nuclear@7: c = c->next; nuclear@7: } nuclear@7: return num_clients + 1; /* +1 for the listening socket */ nuclear@7: } nuclear@7: nuclear@7: int tw_get_maxfd(void) nuclear@7: { nuclear@7: return maxfd; nuclear@7: } nuclear@7: nuclear@7: int tw_handle_socket(int s) nuclear@7: { nuclear@7: struct client *c; nuclear@7: nuclear@7: if(s == lis) { nuclear@7: return accept_conn(s); nuclear@7: } nuclear@7: nuclear@7: /* find which client corresponds to this socket */ nuclear@7: c = clist; nuclear@7: while(c) { nuclear@7: if(c->s == s) { nuclear@7: return handle_client(c); nuclear@7: } nuclear@7: c = c->next; nuclear@7: } nuclear@7: nuclear@7: logmsg("socket %d doesn't correspond to any client\n"); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: static int accept_conn(int lis) nuclear@7: { nuclear@7: int s; nuclear@7: struct client *c; nuclear@7: struct sockaddr_in addr; nuclear@7: socklen_t addr_sz = sizeof addr; nuclear@7: nuclear@7: if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) { nuclear@7: logmsg("failed to accept incoming connection: %s\n", strerror(errno)); nuclear@7: return -1; nuclear@7: } nuclear@7: fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); nuclear@7: nuclear@7: if(!(c = malloc(sizeof *c))) { nuclear@7: logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno)); nuclear@7: return -1; nuclear@7: } nuclear@7: c->s = s; nuclear@7: c->rcvbuf = 0; nuclear@7: c->bufsz = 0; nuclear@7: c->next = clist; nuclear@7: clist = c; nuclear@7: return 0; nuclear@7: } nuclear@7: nuclear@7: static void close_conn(struct client *c) nuclear@7: { nuclear@7: close(c->s); nuclear@7: c->s = -1; /* mark it for removal */ nuclear@7: free(c->rcvbuf); nuclear@7: c->rcvbuf = 0; nuclear@7: } nuclear@7: nuclear@7: static int handle_client(struct client *c) nuclear@7: { nuclear@7: struct http_req_header hdr; nuclear@7: static char buf[2048]; nuclear@7: int rdsz, status; nuclear@7: nuclear@7: while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) { nuclear@7: char *newbuf; nuclear@7: int newsz = c->bufsz + rdsz; nuclear@7: if(newsz > MAX_REQ_LENGTH) { nuclear@7: respond_error(c, 413); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) { nuclear@7: logmsg("failed to allocate %d byte buffer\n", newsz); nuclear@7: respond_error(c, 503); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: memcpy(newbuf + c->bufsz, buf, rdsz); nuclear@7: newbuf[newsz] = 0; nuclear@7: nuclear@7: c->rcvbuf = newbuf; nuclear@7: c->bufsz = newsz; nuclear@7: } nuclear@7: nuclear@7: if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) { nuclear@7: http_log_request(&hdr); nuclear@7: switch(status) { nuclear@7: case HTTP_HDR_INVALID: nuclear@7: respond_error(c, 400); nuclear@7: return -1; nuclear@7: nuclear@7: case HTTP_HDR_NOMEM: nuclear@7: respond_error(c, 503); nuclear@7: return -1; nuclear@7: nuclear@7: case HTTP_HDR_PARTIAL: nuclear@7: return 0; /* partial header, continue reading */ nuclear@7: } nuclear@7: } nuclear@7: http_log_request(&hdr); nuclear@7: nuclear@7: /* we only support GET and HEAD at this point, so freak out on anything else */ nuclear@7: switch(hdr.method) { nuclear@7: case HTTP_GET: nuclear@7: if(do_get(c, hdr.uri, 1) == -1) { nuclear@7: return -1; nuclear@7: } nuclear@7: break; nuclear@7: nuclear@7: case HTTP_HEAD: nuclear@7: if(do_get(c, hdr.uri, 0) == -1) { nuclear@7: return -1; nuclear@7: } nuclear@7: break; nuclear@7: nuclear@7: default: nuclear@7: respond_error(c, 501); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: close_conn(c); nuclear@7: return 0; nuclear@7: } nuclear@7: nuclear@7: static int do_get(struct client *c, const char *uri, int with_body) nuclear@7: { nuclear@7: const char *ptr; nuclear@7: struct http_resp_header resp; nuclear@7: nuclear@7: if((ptr = strstr(uri, "://"))) { nuclear@7: uri = ptr + 3; nuclear@7: } nuclear@7: nuclear@7: /* skip the host part and the first slash if it exists */ nuclear@7: if((ptr = strchr(uri, '/'))) { nuclear@7: uri = ptr + 1; nuclear@7: } nuclear@7: nuclear@7: if(*uri) { nuclear@7: struct stat st; nuclear@7: char *path = 0; nuclear@7: char *rsphdr; nuclear@7: const char *type; nuclear@7: int fd, rspsize; nuclear@7: nuclear@7: if(stat(uri, &st) == -1) { nuclear@7: respond_error(c, 404); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: if(S_ISDIR(st.st_mode)) { nuclear@7: int i; nuclear@7: path = alloca(strlen(uri) + 64); nuclear@7: nuclear@7: for(i=0; indexfiles[i]; i++) { nuclear@7: sprintf(path, "%s/%s", uri, indexfiles[i]); nuclear@7: if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) { nuclear@7: break; nuclear@7: } nuclear@7: } nuclear@7: nuclear@7: if(indexfiles[i] == 0) { nuclear@7: respond_error(c, 404); nuclear@7: return -1; nuclear@7: } nuclear@7: } else { nuclear@7: path = (char*)uri; nuclear@7: } nuclear@7: nuclear@7: if((fd = open(path, O_RDONLY)) == -1) { nuclear@7: respond_error(c, 403); nuclear@7: return -1; nuclear@7: } nuclear@7: nuclear@7: /* construct response header */ nuclear@7: http_init_resp(&resp); nuclear@7: http_add_resp_field(&resp, "Content-Length: %d", st.st_size); nuclear@7: if((type = mime_type(path))) { nuclear@7: http_add_resp_field(&resp, "Content-Type: %s", type); nuclear@7: } nuclear@7: nuclear@7: rspsize = http_serialize_resp(&resp, 0); nuclear@7: rsphdr = alloca(rspsize); nuclear@7: http_serialize_resp(&resp, rsphdr); nuclear@7: nuclear@7: if(with_body) { nuclear@7: int cont_left = st.st_size; nuclear@7: char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); nuclear@7: if(cont == (void*)-1) { nuclear@7: respond_error(c, 503); nuclear@7: close(fd); nuclear@7: return -1; nuclear@7: } nuclear@7: ptr = cont; nuclear@7: nuclear@7: send(c->s, rsphdr, rspsize, 0); nuclear@7: while(cont_left > 0) { nuclear@7: int sz = cont_left < 4096 ? cont_left : 4096; nuclear@7: send(c->s, ptr, sz, 0); nuclear@7: ptr += sz; nuclear@7: } nuclear@7: nuclear@7: munmap(cont, st.st_size); nuclear@7: } else { nuclear@7: send(c->s, rsphdr, rspsize, 0); nuclear@7: } nuclear@7: nuclear@7: close(fd); nuclear@7: } nuclear@7: return 0; nuclear@7: } nuclear@7: nuclear@7: static void respond_error(struct client *c, int errcode) nuclear@7: { nuclear@7: char buf[512]; nuclear@7: nuclear@7: sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode)); nuclear@7: nuclear@7: send(c->s, buf, strlen(buf), 0); nuclear@7: close_conn(c); nuclear@7: } nuclear@7: