X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fuhttpd.git;a=blobdiff_plain;f=client.c;h=79201022a56fce1ce2ea7fc846b206d3d7bafc72;hp=a28067e193b87d1bf41028f4587a6f249cd201b1;hb=af526c6c2ede713237048a91aaddfa2ce621dc3c;hpb=8515c92b9302b258be10df5c5cee240dd4b137f6 diff --git a/client.c b/client.c index a28067e..7920102 100644 --- a/client.c +++ b/client.c @@ -27,12 +27,18 @@ static LIST_HEAD(clients); int n_clients = 0; struct config conf = {}; -static const char *http_versions[] = { +const char * const http_versions[] = { [UH_HTTP_VER_0_9] = "HTTP/0.9", [UH_HTTP_VER_1_0] = "HTTP/1.0", [UH_HTTP_VER_1_1] = "HTTP/1.1", }; +const char * const http_methods[] = { + [UH_HTTP_MSG_GET] = "GET", + [UH_HTTP_MSG_POST] = "POST", + [UH_HTTP_MSG_HEAD] = "HEAD", +}; + void uh_http_header(struct client *cl, int code, const char *summary) { const char *enc = "Transfer-Encoding: chunked\r\n"; @@ -53,17 +59,15 @@ void uh_http_header(struct client *cl, int code, const char *summary) static void uh_connection_close(struct client *cl) { - cl->state = CLIENT_STATE_DONE; + cl->state = CLIENT_STATE_CLOSE; cl->us->eof = true; ustream_state_change(cl->us); } static void uh_dispatch_done(struct client *cl) { - if (cl->dispatch_free) - cl->dispatch_free(cl); - cl->dispatch_free = NULL; - cl->dispatch_close_fds = NULL; + if (cl->dispatch.free) + cl->dispatch.free(cl); } void uh_request_done(struct client *cl) @@ -71,7 +75,7 @@ void uh_request_done(struct client *cl) uh_chunk_eof(cl); uh_dispatch_done(cl); cl->us->notify_write = NULL; - memset(&cl->data, 0, sizeof(cl->data)); + memset(&cl->dispatch, 0, sizeof(cl->dispatch)); if (cl->request.version != UH_HTTP_VER_1_1 || !conf.http_keepalive) { uh_connection_close(cl); @@ -115,11 +119,22 @@ static void client_timeout(struct uloop_timeout *timeout) uh_connection_close(cl); } +static int find_idx(const char * const *list, int max, const char *str) +{ + int i; + + for (i = 0; i < max; i++) + if (!strcmp(list[i], str)) + return i; + + return -1; +} + static int client_parse_request(struct client *cl, char *data) { struct http_request *req = &cl->request; char *type, *path, *version; - int i; + int h_method, h_version; type = strtok(data, " "); path = strtok(NULL, " "); @@ -127,26 +142,18 @@ static int client_parse_request(struct client *cl, char *data) if (!type || !path || !version) return CLIENT_STATE_DONE; - req->url = path; - if (!strcmp(type, "GET")) - req->method = UH_HTTP_MSG_GET; - else if (!strcmp(type, "POST")) - req->method = UH_HTTP_MSG_POST; - else if (!strcmp(type, "HEAD")) - req->method = UH_HTTP_MSG_HEAD; - else - return CLIENT_STATE_DONE; + blobmsg_add_string(&cl->hdr, "URL", path); - cl->request.version = -1; - i = array_size(http_versions); - while (i--) { - if (!strcmp(version, http_versions[i])) { - cl->request.version = i; - break; - } - } - if (cl->request.version < 0) + memset(&cl->request, 0, sizeof(cl->request)); + h_method = find_idx(http_methods, ARRAY_SIZE(http_methods), type); + h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version); + if (h_method < 0 || h_version < 0) { + req->version = UH_HTTP_VER_1_0; return CLIENT_STATE_DONE; + } + + req->method = h_method; + req->version = h_version; return CLIENT_STATE_HEADER; } @@ -161,52 +168,165 @@ static bool client_init_cb(struct client *cl, char *buf, int len) *newline = 0; blob_buf_init(&cl->hdr, 0); - blobmsg_add_string(&cl->hdr, "REQUEST", buf); + cl->state = client_parse_request(cl, buf); ustream_consume(cl->us, newline + 2 - buf); - cl->state = client_parse_request(cl, (char *) blobmsg_data(blob_data(cl->hdr.head))); if (cl->state == CLIENT_STATE_DONE) uh_header_error(cl, 400, "Bad Request"); return true; } +static bool rfc1918_filter_check(struct client *cl) +{ + if (!conf.rfc1918_filter) + return true; + + if (!uh_addr_rfc1918(&cl->peer_addr) || uh_addr_rfc1918(&cl->srv_addr)) + return true; + + uh_client_error(cl, 403, "Forbidden", + "Rejected request from RFC1918 IP " + "to public server address"); + return false; +} + static void client_header_complete(struct client *cl) { - uh_handle_file_request(cl); + if (!rfc1918_filter_check(cl)) + return; + + if (cl->request.expect_cont) + ustream_printf(cl->us, "HTTP/1.1 100 Continue\r\n\r\n"); + + uh_handle_request(cl); } -static int client_parse_header(struct client *cl, char *data) +static void client_parse_header(struct client *cl, char *data) { + struct http_request *r = &cl->request; + char *err; char *name; char *val; if (!*data) { uloop_timeout_cancel(&cl->timeout); + cl->state = CLIENT_STATE_DATA; client_header_complete(cl); - return CLIENT_STATE_DATA; + return; } - val = strchr(data, ':'); - if (!val) - return CLIENT_STATE_DONE; - - *val = 0; - val++; - - while (isspace(*val)) - val++; + val = uh_split_header(data); + if (!val) { + cl->state = CLIENT_STATE_DONE; + return; + } for (name = data; *name; name++) if (isupper(*name)) *name = tolower(*name); + if (!strcmp(data, "expect")) { + if (!strcasecmp(val, "100-continue")) + r->expect_cont = true; + else { + uh_header_error(cl, 412, "Precondition Failed"); + return; + } + } else if (!strcmp(data, "content-length")) { + r->content_length = strtoul(val, &err, 0); + if (err && *err) { + uh_header_error(cl, 400, "Bad Request"); + return; + } + } else if (!strcmp(data, "transfer-encoding")) { + if (!strcmp(val, "chunked")) + r->transfer_chunked = true; + } + + blobmsg_add_string(&cl->hdr, data, val); - return CLIENT_STATE_HEADER; + cl->state = CLIENT_STATE_HEADER; +} + +void client_poll_post_data(struct client *cl) +{ + struct dispatch *d = &cl->dispatch; + struct http_request *r = &cl->request; + char *buf; + int len; + + if (cl->state == CLIENT_STATE_DONE) + return; + + while (1) { + char *sep; + int offset = 0; + int cur_len; + + buf = ustream_get_read_buf(cl->us, &len); + if (!buf || !len) + break; + + if (!d->data_send) + return; + + cur_len = min(r->content_length, len); + if (cur_len) { + if (d->data_blocked) + break; + + if (d->data_send) + cur_len = d->data_send(cl, buf, cur_len); + + r->content_length -= cur_len; + ustream_consume(cl->us, cur_len); + continue; + } + + if (!r->transfer_chunked) + break; + + if (r->transfer_chunked > 1) + offset = 2; + + sep = strstr(buf + offset, "\r\n"); + if (!sep) + break; + + *sep = 0; + + r->content_length = strtoul(buf + offset, &sep, 16); + r->transfer_chunked++; + ustream_consume(cl->us, sep + 2 - buf); + + /* invalid chunk length */ + if (sep && *sep) { + r->content_length = 0; + r->transfer_chunked = 0; + break; + } + + /* empty chunk == eof */ + if (!r->content_length) { + r->transfer_chunked = false; + break; + } + } + + buf = ustream_get_read_buf(cl->us, &len); + if (!r->content_length && !r->transfer_chunked && + cl->state != CLIENT_STATE_DONE) { + if (cl->dispatch.data_done) + cl->dispatch.data_done(cl); + + cl->state = CLIENT_STATE_DONE; + } } static bool client_data_cb(struct client *cl, char *buf, int len) { + client_poll_post_data(cl); return false; } @@ -220,11 +340,11 @@ static bool client_header_cb(struct client *cl, char *buf, int len) return false; *newline = 0; - cl->state = client_parse_header(cl, buf); + client_parse_header(cl, buf); line_len = newline + 2 - buf; ustream_consume(cl->us, line_len); if (cl->state == CLIENT_STATE_DATA) - client_data_cb(cl, newline + 2, len - line_len); + return client_data_cb(cl, newline + 2, len - line_len); return true; } @@ -244,14 +364,15 @@ static void client_read_cb(struct client *cl) do { str = ustream_get_read_buf(us, &len); - if (!str) + if (!str || !len) break; if (cl->state >= array_size(read_cbs) || !read_cbs[cl->state]) break; if (!read_cbs[cl->state](cl, str, len)) { - if (len == us->r.buffer_len) + if (len == us->r.buffer_len && + cl->state != CLIENT_STATE_DATA) uh_header_error(cl, 413, "Request Entity Too Large"); break; } @@ -260,11 +381,13 @@ static void client_read_cb(struct client *cl) static void client_close(struct client *cl) { + n_clients--; uh_dispatch_done(cl); uloop_timeout_cancel(&cl->timeout); ustream_free(&cl->sfd.stream); close(cl->sfd.fd.fd); list_del(&cl->list); + blob_buf_free(&cl->hdr); free(cl); uh_unblock_listeners(); @@ -281,17 +404,38 @@ static void client_ustream_write_cb(struct ustream *s, int bytes) { struct client *cl = container_of(s, struct client, sfd); - if (cl->dispatch_write_cb) - cl->dispatch_write_cb(cl); + if (cl->dispatch.write_cb) + cl->dispatch.write_cb(cl); } static void client_notify_state(struct ustream *s) { struct client *cl = container_of(s, struct client, sfd); - if (cl->state == CLIENT_STATE_CLOSE || - (s->eof && !s->w.data_bytes) || s->write_error) - return client_close(cl); + if (!s->write_error) { + if (cl->state == CLIENT_STATE_DATA) + return; + + if (!s->eof || s->w.data_bytes) + return; + } + + return client_close(cl); +} + +static void set_addr(struct uh_addr *addr, void *src) +{ + struct sockaddr_in *sin = src; + struct sockaddr_in6 *sin6 = src; + + addr->family = sin->sin_family; + if (addr->family == AF_INET) { + addr->port = ntohs(sin->sin_port); + memcpy(&addr->in, &sin->sin_addr, sizeof(addr->in)); + } else { + addr->port = ntohs(sin6->sin6_port); + memcpy(&addr->in6, &sin6->sin6_addr, sizeof(addr->in6)); + } } void uh_accept_client(int fd) @@ -301,19 +445,22 @@ void uh_accept_client(int fd) unsigned int sl; int sfd; static int client_id = 0; + struct sockaddr_in6 addr; if (!next_client) next_client = calloc(1, sizeof(*next_client)); cl = next_client; - sl = sizeof(cl->peeraddr); - sfd = accept(fd, (struct sockaddr *) &cl->peeraddr, &sl); + sl = sizeof(addr); + sfd = accept(fd, (struct sockaddr *) &addr, &sl); if (sfd < 0) return; - sl = sizeof(cl->servaddr); - getsockname(fd, (struct sockaddr *) &cl->servaddr, &sl); + set_addr(&cl->peer_addr, &addr); + sl = sizeof(addr); + getsockname(fd, (struct sockaddr *) &addr, &sl); + set_addr(&cl->srv_addr, &addr); cl->us = &cl->sfd.stream; cl->us->string_data = true; cl->us->notify_read = client_ustream_read_cb; @@ -339,7 +486,7 @@ void uh_close_fds(void) uh_close_listen_fds(); list_for_each_entry(cl, &clients, list) { close(cl->sfd.fd.fd); - if (cl->dispatch_close_fds) - cl->dispatch_close_fds(cl); + if (cl->dispatch.close_fds) + cl->dispatch.close_fds(cl); } }