src/server/util/io.c

branch
webdav
changeset 333
bb536d4bc174
parent 332
6f82ede01e1c
child 334
a55491f66003
--- 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

mercurial