Log message:
Reimplement httpd's support for byte ranges.
The previous implementation loaded all the output into a single output buffer and used its size to determine the Content-Length of the body.
The new implementation calculates the body length first and writes the individual ranges in an async way using the bufferevent mechanism.
This prevents httpd from using too much memory and applies the watermark and throttling mechanisms to range requests.
Problem reported by Pierre Kim (pierre.kim.sec at gmail.com)
Patch:
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v
retrieving revision 1.111
retrieving revision 1.112
diff -u -r1.111 -r1.112
--- src/usr.sbin/httpd/server_http.c 2017/01/31 12:21:27 1.111
+++ src/usr.sbin/httpd/server_http.c 2017/01/31 14:39:47 1.112
@@ -1,7 +1,7 @@
-/* $OpenBSD: server_http.c,v 1.111 2017/01/31 12:21:27 reyk Exp $ */
+/* $OpenBSD: server_http.c,v 1.112 2017/01/31 14:39:47 reyk Exp $ */
/*
- * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -604,6 +604,101 @@
done:
server_close(clt, "last http chunk read (done)");
+ return;
+ fail:
+ server_close(clt, strerror(errno));
+}
+
+void
+server_read_httprange(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ size_t size;
+ struct media_type *media;
+ struct range_data *r = &clt->clt_ranges;
+ struct range *range;
+
+ getmonotime(&clt->clt_tv_last);
+
+ if (r->range_toread > 0) {
+ size = EVBUFFER_LENGTH(src);
+ if (!size)
+ return;
+
+ /* Read chunk data */
+ if ((off_t)size > r->range_toread) {
+ size = r->range_toread;
+ if (server_bufferevent_write_chunk(clt, src, size)
+ == -1)
+ goto fail;
+ r->range_toread = 0;
+ } else {
+ if (server_bufferevent_write_buffer(clt, src) == -1)
+ goto fail;
+ r->range_toread -= size;
+ }
+ if (r->range_toread < 1)
+ r->range_toread = TOREAD_HTTP_RANGE;
+ DPRINTF("%s: done, size %lu, to read %lld", __func__,
+ size, r->range_toread);
+ }
+
+ switch (r->range_toread) {
+ case TOREAD_HTTP_RANGE:
+ if (r->range_index >= r->range_count) {
+ if (r->range_count > 1) {
+ /* Add end marker */
+ if (server_bufferevent_printf(clt,
+ "\r\n--%llu--\r\n",
+ clt->clt_boundary) == -1)
+ goto fail;
+ }
+ r->range_toread = TOREAD_HTTP_NONE;
+ break;
+ }
+
+ range = &r->range[r->range_index];
+
+ if (r->range_count > 1) {
+ media = r->range_media;
+ if (server_bufferevent_printf(clt,
+ "\r\n--%llu\r\n"
+ "Content-Type: %s/%s\r\n"
+ "Content-Range: bytes %lld-%lld/%zu\r\n\r\n",
+ clt->clt_boundary,
+ media->media_type, media->media_subtype,
+ range->start, range->end, r->range_total) == -1)
+ goto fail;
+ }
+ r->range_toread = range->end - range->start + 1;
+
+ if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1)
+ goto fail;
+
+ /* Throw away bytes that are already in the input buffer */
+ evbuffer_drain(src, EVBUFFER_LENGTH(src));
+
+ /* Increment for the next part */
+ r->range_index++;
+ break;
+ case TOREAD_HTTP_NONE:
+ case 0:
+ break;
+ }
+
+ if (clt->clt_done)
+ goto done;
+
+ if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
+ SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) {
+ bufferevent_disable(clt->clt_srvbev, EV_READ);
+ clt->clt_srvbev_throttled = 1;
+ }
+
+ return;
+ done:
+ (*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
return;
fail:
server_close(clt, strerror(errno));