Hi, I found strange behavior in relayd when it comes to content-length and transfer-encoding chunked.
When the server sends a "Content-Length: 0" relayd got confused and passed all data without reading the http header anymore. To fix this, I need more state and converted toread from size_t to off_t to use negative values. The content length is more a stream length than memory size so I think 64 bit is more appropriate. It is also the same type as the splicing length and I want to use that in the future. The chunking code did not look rfc compliant to me. So I did a rewrite of that. There I also need more state in toread to handle it correctly. ok? bluhm Index: usr.sbin/relayd/relay.c =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/relayd/relay.c,v retrieving revision 1.141 diff -u -p -r1.141 relay.c --- usr.sbin/relayd/relay.c 4 Sep 2011 20:26:58 -0000 1.141 +++ usr.sbin/relayd/relay.c 13 Sep 2011 23:09:32 -0000 @@ -1090,17 +1090,17 @@ relay_read_httpcontent(struct buffereven if (gettimeofday(&con->se_tv_last, NULL) == -1) goto fail; size = EVBUFFER_LENGTH(src); - DPRINTF("%s: size %d, to read %d", __func__, - size, cre->toread); + DPRINTF("%s: size %d, to read %lld", __func__, size, cre->toread); if (!size) return; if (relay_bufferevent_write_buffer(cre->dst, src) == -1) goto fail; - if (size >= cre->toread) + if (size >= cre->toread) { + cre->toread = -1; bev->readcb = relay_read_http; - cre->toread -= size; - DPRINTF("%s: done, size %d, to read %d", __func__, - size, cre->toread); + } else + cre->toread -= size; + DPRINTF("%s: done, size %d, to read %lld", __func__, size, cre->toread); if (con->se_done) goto done; if (bev->readcb != relay_read_httpcontent) @@ -1120,84 +1120,109 @@ relay_read_httpchunks(struct bufferevent struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; struct rsession *con = cre->con; struct evbuffer *src = EVBUFFER_INPUT(bev); - char *line; - long lval; + char *line = NULL; size_t size; if (gettimeofday(&con->se_tv_last, NULL) == -1) goto fail; - size = EVBUFFER_LENGTH(src); - DPRINTF("%s: size %d, to read %d", __func__, - size, cre->toread); - if (!size) - return; - if (!cre->toread) { - line = evbuffer_readline(src); - if (line == NULL) { - /* Ignore empty line, continue */ - bufferevent_enable(bev, EV_READ); - return; - } - if (!strlen(line)) { - free(line); - goto next; - } + while ((((size = EVBUFFER_LENGTH(src)) > 0 && cre->toread > 0) || + (line = evbuffer_readline(src)) != NULL) && + bev->readcb == relay_read_httpchunks) { + DPRINTF("%s: size %d, to read %lld, line %p", __func__, + size, cre->toread, line); + switch (cre->toread) { + case -1: + /* + * If the previous buffer terminated with '\r' + * this buffer starts with '\n'. Ignore this line. + */ + if (strlen(line) == 0) { + free(line); + break; + } - /* Read prepended chunk size in hex, ingore the trailer */ - if (sscanf(line, "%lx", &lval) != 1) { - free(line); - relay_close(con, "invalid chunk size"); - return; - } + /* Read prepended chunk size in hex */ + if (sscanf(line, "%llx", &cre->toread) != 1 || + cre->toread < 0) { + free(line); + relay_close(con, "invalid chunk-size"); + return; + } + DPRINTF("%s: chunk-size, to read %lld", __func__, + cre->toread); - if (relay_bufferevent_print(cre->dst, line) == -1 || - relay_bufferevent_print(cre->dst, "\r\n") == -1) { + if (relay_bufferevent_print(cre->dst, line) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) { + free(line); + goto fail; + } free(line); - goto fail; - } - free(line); - /* Last chunk is 0 bytes followed by an empty newline */ - if ((cre->toread = lval) == 0) { - DPRINTF("%s: last chunk", __func__); - - line = evbuffer_readline(src); - if (line == NULL) { - relay_close(con, "invalid last chunk"); - return; + /* length 0 from server means to process the trailer */ + if (cre->toread == 0) { + cre->toread = -2; + DPRINTF("%s: last-chunk", __func__); + } + break; + + case -2: + /* Last chunk is 0 bytes, trailer and empty newline */ + + /* Chunk is terminated by an empty newline */ + if (strlen(line) == 0) { + /* Switch to HTTP header mode */ + bev->readcb = relay_read_http; + cre->toread = -1; + DPRINTF("%s: last-chunk finished", __func__); + } else { + /* Forward trailer */ + if (relay_bufferevent_print(cre->dst, line) + == -1) { + free(line); + goto fail; + } + DPRINTF("%s: chunk-trailer", __func__); } free(line); if (relay_bufferevent_print(cre->dst, "\r\n") == -1) goto fail; + break; - /* Switch to HTTP header mode */ - bev->readcb = relay_read_http; - } - } else { - /* Read chunk data */ - if (size > cre->toread) - size = cre->toread; - if (relay_bufferevent_write_chunk(cre->dst, src, size) == -1) - goto fail; - cre->toread -= size; - DPRINTF("%s: done, size %d, to read %d", __func__, - size, cre->toread); + default: + /* Read chunk data */ + if (size > cre->toread) + size = cre->toread; + if (relay_bufferevent_write_chunk(cre->dst, src, size) + == -1) + goto fail; + cre->toread -= size; + DPRINTF("%s: chunk-data, size %d, to read %lld", + __func__, size, cre->toread); + break; - if (cre->toread == 0) { - /* Chunk is terminated by an empty (empty) newline */ - line = evbuffer_readline(src); - if (line != NULL) + /* Read terminating new line after the chunk */ + case 0: + /* Chunk-data is terminated by a newline */ + if (strlen(line) != 0) { free(line); - if (relay_bufferevent_print(cre->dst, "\r\n\r\n") == -1) + relay_close(con, "invalid chunk-data"); + return; + } + cre->toread = -1; + DPRINTF("%s: chunk-data finished", __func__); + + free(line); + if (relay_bufferevent_print(cre->dst, "\r\n") == -1) goto fail; + break; } + line = NULL; } - next: if (con->se_done) goto done; - if (EVBUFFER_LENGTH(src)) + if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_httpchunks) bev->readcb(bev, arg); bufferevent_enable(bev, EV_READ); return; @@ -1249,11 +1274,11 @@ relay_read_http(struct bufferevent *bev, if (gettimeofday(&con->se_tv_last, NULL) == -1) goto fail; size = EVBUFFER_LENGTH(src); - DPRINTF("%s: size %d, to read %d", __func__, size, cre->toread); + DPRINTF("%s: size %d, to read %lld", __func__, size, cre->toread); if (!size) { if (cre->dir == RELAY_DIR_RESPONSE) return; - cre->toread = 0; + cre->toread = -1; goto done; } @@ -1391,7 +1416,7 @@ relay_read_http(struct bufferevent *bev, * the carriage return? And some browsers seem to * include the line length in the content-length. */ - cre->toread = strtonum(pk.value, 0, INT_MAX, &errstr); + cre->toread = strtonum(pk.value, 0, LLONG_MAX, &errstr); if (errstr) { relay_close_http(con, 500, errstr, 0); goto abort; @@ -1478,13 +1503,12 @@ relay_read_http(struct bufferevent *bev, case HTTP_METHOD_PUT: case HTTP_METHOD_RESPONSE: /* HTTP request payload */ - if (cre->toread) { + if (cre->toread > 0) bev->readcb = relay_read_httpcontent; - break; - } /* Single-pass HTTP response */ - bev->readcb = relay_read; + if (cre->toread < 0) + bev->readcb = relay_read; break; default: /* HTTP handler */ @@ -1493,7 +1517,7 @@ relay_read_http(struct bufferevent *bev, } if (cre->chunked) { /* Chunked transfer encoding */ - cre->toread = 0; + cre->toread = -1; bev->readcb = relay_read_httpchunks; } @@ -1504,7 +1528,7 @@ relay_read_http(struct bufferevent *bev, relay_http_request_close(cre); done: - if (cre->dir == RELAY_DIR_REQUEST && !cre->toread && + if (cre->dir == RELAY_DIR_REQUEST && cre->toread < 0 && proto->lateconnect && cre->dst->bev == NULL) { if (rlay->rl_conf.fwdmode == FWD_TRANS) { relay_bindanyreq(con, 0, IPPROTO_TCP); @@ -1937,6 +1961,8 @@ relay_accept(int fd, short sig, void *ar con->se_out.con = con; con->se_in.splicelen = -1; con->se_out.splicelen = -1; + con->se_in.toread = -1; + con->se_out.toread = -1; con->se_relay = rlay; con->se_id = ++relay_conid; con->se_relayid = rlay->rl_conf.id; Index: usr.sbin/relayd/relayd.h =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/relayd/relayd.h,v retrieving revision 1.151 diff -u -p -r1.151 relayd.h --- usr.sbin/relayd/relayd.h 4 Sep 2011 20:26:58 -0000 1.151 +++ usr.sbin/relayd/relayd.h 11 Sep 2011 23:06:23 -0000 @@ -169,7 +169,7 @@ struct ctl_relay_event { off_t splicelen; int line; - size_t toread; + off_t toread; int chunked; int done; enum httpmethod method;