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;

Reply via email to