first semi-functional implementation of chunked transfer encoding for request bodies webdav

Fri, 06 May 2022 22:45:53 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 06 May 2022 22:45:53 +0200
branch
webdav
changeset 333
bb536d4bc174
parent 332
6f82ede01e1c
child 334
a55491f66003

first semi-functional implementation of chunked transfer encoding for request bodies

src/server/daemon/httprequest.c file | annotate | diff | comparison | revisions
src/server/daemon/session.h file | annotate | diff | comparison | revisions
src/server/util/io.c file | annotate | diff | comparison | revisions
src/server/util/io.h file | annotate | diff | comparison | revisions
--- a/src/server/daemon/httprequest.c	Thu May 05 21:46:10 2022 +0200
+++ b/src/server/daemon/httprequest.c	Fri May 06 22:45:53 2022 +0200
@@ -396,6 +396,21 @@
             net_io->max_read = ctlen - cur_input_available;
         }
     }
+    char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers);
+    if(transfer_encoding) {
+        if(!strcmp(transfer_encoding, "chunked")) {
+            netbuf *nb = sn->netbuf;
+            sn->buffer = (char*)nb->inbuf;
+            sn->pos = nb->pos;
+            sn->cursize = nb->cursize;
+            
+            if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) {
+                pool_destroy(pool);
+                // TODO: error 500 
+                return 1;
+            }
+        }
+    }
     
     //
     // Send the request to the NSAPI system
--- a/src/server/daemon/session.h	Thu May 05 21:46:10 2022 +0200
+++ b/src/server/daemon/session.h	Fri May 06 22:45:53 2022 +0200
@@ -47,6 +47,10 @@
     threadpool_t *currentpool;
     threadpool_t *defaultpool;
     
+    char *buffer;
+    int pos;
+    int cursize;
+    
     UcxAllocator allocator;
     
     ServerConfiguration *config;
--- a/src/server/util/io.c	Thu May 05 21:46:10 2022 +0200
+++ b/src/server/util/io.c	Fri May 06 22:45:53 2022 +0200
@@ -253,10 +253,49 @@
     st->max_read = 0;
     st->read = 0;
     st->chunked_enc = WS_FALSE;
-    st->buffered = WS_FALSE;
+    st->chunk_buf_pos = 0;
     return (IOStream*)st;
 }
 
+int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos) {
+    if(st->read != (io_read_f)net_http_read) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_read: IOStream is not an HttpStream");
+        return 1;
+    }
+    st->read = (io_read_f)net_http_read_chunked;
+    HttpStream *http = (HttpStream*)st;
+    http->max_read = 0;
+    http->read = 0;
+    http->readbuf = buffer;
+    http->bufsize = bufsize;
+    http->buflen = *cursize; // TODO: store buflen as pointer
+    http->bufpos = pos;
+    http->chunk_buf_pos = 0;
+    http->remaining_len = 0;
+    http->remaining_pos = 0;
+    return 0;
+}
+
+int httpstream_enable_chunked_write(IOStream *st) {
+    if(st->write != (io_write_f)net_http_write) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_write: IOStream is not an HttpStream");
+        return 1;
+    }
+    HttpStream *http = (HttpStream*)st;
+    http->chunked_enc = WS_TRUE;
+    return 0;
+}
+
+int httpstream_set_max_read(IOStream *st, int64_t maxread) {
+    if(st->write != (io_write_f)net_http_write) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_set_max_read: IOStream is not an HttpStream");
+        return 1;
+    }
+    HttpStream *http = (HttpStream*)st;
+    http->max_read = maxread;
+    return 0;
+}
+
 ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) {
     IOStream *fd = st->fd;
     if(st->chunked_enc) {
@@ -308,10 +347,246 @@
         return 0;
     }
     ssize_t r = st->fd->read(st->fd, buf, nbytes);
+    if(r < 0) {
+        st->st.io_errno = st->fd->io_errno;
+    }
     st->read += r;
     return r;
 }
 
+#define BUF_UNNEEDED_DIFF 64
+/*
+ * read from st->chunk_buf first, read from st->fd if perform_io is true
+ */
+static ssize_t net_http_read_buffered(HttpStream *st, char *buf, size_t nbytes, WSBool read_data, WSBool *perform_io) {
+    ssize_t r = 0;
+    
+    // remaining bytes from the chunkbuf
+    if(st->remaining_len > 0) {
+        size_t cplen = st->remaining_len > nbytes ? nbytes : st->remaining_len;
+        WSBool ret = FALSE;
+        if(read_data) {
+            // if we read data (and not a chunk header), we limit the
+            // amount of bytes we copy
+            size_t chunk_available = st->max_read - st->read;
+            if(cplen > chunk_available) {
+                cplen = chunk_available;
+                ret = TRUE;
+            }
+            st->read += cplen;
+        }
+        memcpy(buf, &st->remaining_buf[st->remaining_pos], cplen);
+        st->remaining_pos += cplen;
+        st->remaining_len -= cplen;
+        buf += cplen;
+        nbytes -= cplen;
+        r += cplen;
+        if(st->remaining_len == 0) {
+            st->remaining_pos = 0;
+        }
+        if(ret) {
+            return r;
+        }
+    }
+    
+    // copy available data from st->readbuf to buf
+    int pos = *st->bufpos;
+    size_t buf_available = st->buflen - pos;
+    if(buf_available) {
+        size_t cplen = buf_available > nbytes ? nbytes : buf_available;
+        if(read_data) {
+            // if we read data (and not a chunk header), we limit the
+            // amount of bytes we copy
+            size_t chunk_available = st->max_read - st->read;
+            cplen = cplen > chunk_available ? chunk_available : cplen;
+            st->read += cplen;
+        }
+        memcpy(buf, st->readbuf + pos, cplen);
+        *st->bufpos += cplen;
+        r += cplen;
+        buf += cplen;
+        nbytes -= cplen;
+    }
+    
+    if(*perform_io && nbytes > 0) {
+        // fill buffer again
+        ssize_t rlen = st->fd->read(st->fd, st->readbuf, st->bufsize);
+        st->buflen = rlen;
+        *st->bufpos = 0;
+        *perform_io = WS_FALSE;
+        if(rlen < 0) {
+            st->st.io_errno = st->fd->io_errno;
+        }
+        
+        if(rlen > 0) {
+            // call func again to get data from buffer (no IO will be performed)
+            r += net_http_read_buffered(st, buf, nbytes, read_data, perform_io);
+        }
+    }
+    
+    return r;
+}
+
+
+/*
+ * parses a chunk header
+ * the chunk length is stored in chunklen
+ * return:  0 if the data is incomplete
+ *         -1 if an error occured
+ *         >0 chunk header length
+ */
+static int parse_chunk_header(char *str, int len, WSBool first, int64_t *chunklen) {
+    char *hdr_start = NULL;
+    char *hdr_end = NULL;
+    int i = 0;
+    if(first) {
+        hdr_start = str;
+    } else {
+        if(len < 3) {
+            return 0;
+        }
+        if(str[0] == '\r' && str[1] == '\n') {
+            hdr_start = str+2;
+            i = 2;
+        } else if(str[0] == '\n') {
+            hdr_start = str+1;
+            i = 1;
+        } else {
+            return -1;
+        }
+    }
+    
+    for(;i<len;i++) {
+        char c = str[i];
+        if(c == '\r' || c == '\n') {
+            hdr_end = str+i;
+            break;
+        }
+    }
+    if(!hdr_end || i == len) {
+        return 0; // incomplete
+    }
+    
+    if(*hdr_end == '\r') {
+        // we also need '\n'
+        if(hdr_end[1] != '\n') {
+            return -1;
+        }
+        i++; // '\n' found
+    }
+    
+    // parse
+    char save_c = *hdr_end;
+    *hdr_end = '\0';
+    char *end;
+    int64_t clen;
+    errno = 0;
+    clen = strtoll(hdr_start, &end, 16);
+    *hdr_end = save_c;
+    if(errno) {
+        return -1;
+    }
+    i++;
+    
+    if(clen == 0) {
+        // chunk length of 0 indicates the end
+        // an additional \r\n is required (we also accept \n)
+        if(i >= len) {
+            return 0;
+        }
+        if(str[i] == '\n') {
+            i++;
+        } else if(str[i] == '\r') {
+            if(++i >= len) {
+                return 0;
+            }
+            if(str[i] == '\n') {
+                i++;
+            } else {
+                return -1;
+            }
+        } else {
+            return -1;
+        }
+    }
+    
+    *chunklen = clen;
+    return i;
+}
+
+ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes) {
+    if(st->read_eof) {
+        return 0;
+    }
+    
+    char *rbuf = buf; // buffer pos
+    size_t rd = 0; // number of bytes read
+    size_t rbuflen = nbytes; // number of bytes until end of buf
+    WSBool perform_io = WS_TRUE; // we do only 1 read before we abort
+    while(rd < nbytes && (perform_io || (st->max_read - st->read) > 0)) {
+        // how many bytes are available in the current chunk
+        size_t chunk_available = st->max_read - st->read;
+        if(chunk_available > 0) {
+            ssize_t r = net_http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io);
+            if(r == 0) {
+                break;
+            }
+            rd += r;
+            st->read_total += r;
+            rbuf += r;
+            rbuflen -= r;
+        } else {
+            int chunkbuf_avail = HTTP_STREAM_CBUF_SIZE - st->chunk_buf_pos;
+            if(chunkbuf_avail == 0) {
+                // for some reason HTTP_STREAM_CBUF_SIZE is not enough
+                // to store the chunk header
+                // this indicates that something has gone wrong (or this is an attack)
+                st->read_eof = WS_TRUE;
+                return -1;
+            }
+            // fill st->chunk_buf
+            ssize_t r = net_http_read_buffered(st, &st->chunk_buf[st->chunk_buf_pos], chunkbuf_avail, FALSE, &perform_io);
+            if(r == 0) {
+                break;
+            }
+            int chunkbuf_len = st->chunk_buf_pos + r;
+            int64_t chunklen;
+            int ret = parse_chunk_header(st->chunk_buf, chunkbuf_len, st->read_total > 0 ? FALSE : TRUE, &chunklen);
+            if(ret == 0) {
+                // incomplete chunk header
+                st->chunk_buf_pos = chunkbuf_len;
+            } else if(ret < 0) {
+                // error
+                st->read_eof = WS_TRUE;
+                return -1;
+            } else if(ret > 0) {
+                st->max_read = chunklen;
+                st->read = 0;
+                st->remaining_len = chunkbuf_len - ret;
+                if(st->remaining_len > 0) {
+                    memcpy(st->remaining_buf, st->chunk_buf, HTTP_STREAM_CBUF_SIZE);
+                    st->remaining_pos = st->chunk_buf_pos + ret;
+                } else {
+                    st->remaining_pos = 0;
+                }
+                st->remaining_len = chunkbuf_len - ret;
+                st->chunk_buf_pos = 0;
+                
+                if(chunklen == 0) {
+                    st->read_eof = WS_TRUE;
+                    break;
+                }
+            }
+        }
+        
+        if(!perform_io && rd == 0) {
+            perform_io = WS_TRUE;
+        }
+    }
+    
+    return rd;
+}
+
 ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) {  
     ssize_t ret = 0;
     // TODO: support chunked transfer encoding
--- a/src/server/util/io.h	Thu May 05 21:46:10 2022 +0200
+++ b/src/server/util/io.h	Fri May 06 22:45:53 2022 +0200
@@ -89,13 +89,24 @@
 #endif
 };
 
+#define HTTP_STREAM_CBUF_SIZE 16
 struct HttpStream {
     IOStream st;
     IOStream *fd;
     uint64_t max_read;
     uint64_t read;
+    uint64_t read_total;
+    char     *readbuf;
+    size_t   bufsize; // allocated buffer size
+    size_t   buflen;  // currently number of bytes in the buffer
+    int      *bufpos; // current buffer position
+    int      chunk_buf_pos;
+    char     chunk_buf[HTTP_STREAM_CBUF_SIZE];
+    char     remaining_buf[HTTP_STREAM_CBUF_SIZE];
+    int      remaining_len;
+    int      remaining_pos;
     WSBool   chunked_enc;
-    WSBool   buffered;
+    WSBool   read_eof;
 };
 
 typedef struct SSLStream {
@@ -120,9 +131,14 @@
 /* http stream */
 IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd);
 
+int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos);
+int httpstream_enable_chunked_write(IOStream *st);
+int httpstream_set_max_read(IOStream *st, int64_t maxread);
+
 ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt);
 ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes);
+ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd);
 void    net_http_close(HttpStream *st);
 void    net_http_finish(HttpStream *st);

mercurial