summaryrefslogtreecommitdiffstats
path: root/debian/vendor-h2o/lib/handler
diff options
context:
space:
mode:
Diffstat (limited to 'debian/vendor-h2o/lib/handler')
-rw-r--r--debian/vendor-h2o/lib/handler/access_log.c153
-rw-r--r--debian/vendor-h2o/lib/handler/chunked.c113
-rw-r--r--debian/vendor-h2o/lib/handler/compress.c144
-rw-r--r--debian/vendor-h2o/lib/handler/compress/brotli.cc138
-rw-r--r--debian/vendor-h2o/lib/handler/compress/gzip.c190
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/access_log.c143
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/compress.c172
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/errordoc.c203
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/expires.c123
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/fastcgi.c388
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/file.c197
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/headers.c74
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/headers_util.c143
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/http2_debug_state.c46
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/mruby.c177
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/proxy.c392
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/redirect.c76
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/reproxy.c81
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/status.c87
-rw-r--r--debian/vendor-h2o/lib/handler/configurator/throttle_resp.c73
-rw-r--r--debian/vendor-h2o/lib/handler/errordoc.c145
-rw-r--r--debian/vendor-h2o/lib/handler/expires.c84
-rw-r--r--debian/vendor-h2o/lib/handler/fastcgi.c856
-rw-r--r--debian/vendor-h2o/lib/handler/file.c966
-rw-r--r--debian/vendor-h2o/lib/handler/file/_templates.c.h90
-rw-r--r--debian/vendor-h2o/lib/handler/file/templates.c.h171
-rw-r--r--debian/vendor-h2o/lib/handler/headers.c58
-rw-r--r--debian/vendor-h2o/lib/handler/headers_util.c114
-rw-r--r--debian/vendor-h2o/lib/handler/http2_debug_state.c69
-rw-r--r--debian/vendor-h2o/lib/handler/mimemap.c419
-rw-r--r--debian/vendor-h2o/lib/handler/mimemap/defaults.c.h109
-rw-r--r--debian/vendor-h2o/lib/handler/mruby.c938
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/chunked.c270
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded.c.h111
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb35
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/core.rb81
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb54
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/http_request.c500
-rw-r--r--debian/vendor-h2o/lib/handler/proxy.c165
-rw-r--r--debian/vendor-h2o/lib/handler/redirect.c106
-rw-r--r--debian/vendor-h2o/lib/handler/reproxy.c66
-rw-r--r--debian/vendor-h2o/lib/handler/status.c270
-rw-r--r--debian/vendor-h2o/lib/handler/status/durations.c207
-rw-r--r--debian/vendor-h2o/lib/handler/status/events.c112
-rw-r--r--debian/vendor-h2o/lib/handler/status/requests.c151
-rw-r--r--debian/vendor-h2o/lib/handler/throttle_resp.c156
46 files changed, 9416 insertions, 0 deletions
diff --git a/debian/vendor-h2o/lib/handler/access_log.c b/debian/vendor-h2o/lib/handler/access_log.c
new file mode 100644
index 0000000..4a77041
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/access_log.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include "h2o.h"
+#include "h2o/serverutil.h"
+
+struct st_h2o_access_log_filehandle_t {
+ h2o_logconf_t *logconf;
+ int fd;
+};
+
+struct st_h2o_access_logger_t {
+ h2o_logger_t super;
+ h2o_access_log_filehandle_t *fh;
+};
+
+static void log_access(h2o_logger_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_access_logger_t *self = (struct st_h2o_access_logger_t *)_self;
+ h2o_access_log_filehandle_t *fh = self->fh;
+ char *logline, buf[4096];
+ size_t len;
+
+ /* stringify */
+ len = sizeof(buf);
+ logline = h2o_log_request(fh->logconf, req, &len, buf);
+
+ /* emit */
+ write(fh->fd, logline, len);
+
+ /* free memory */
+ if (logline != buf)
+ free(logline);
+}
+
+static void on_dispose_handle(void *_fh)
+{
+ h2o_access_log_filehandle_t *fh = _fh;
+
+ h2o_logconf_dispose(fh->logconf);
+ close(fh->fd);
+}
+
+int h2o_access_log_open_log(const char *path)
+{
+ int fd;
+
+ if (path[0] == '|') {
+ int pipefds[2];
+ pid_t pid;
+ char *argv[4] = {"/bin/sh", "-c", (char *)(path + 1), NULL};
+ /* create pipe */
+ if (pipe(pipefds) != 0) {
+ perror("pipe failed");
+ return -1;
+ }
+ if (fcntl(pipefds[1], F_SETFD, FD_CLOEXEC) == -1) {
+ perror("failed to set FD_CLOEXEC on pipefds[1]");
+ return -1;
+ }
+ /* spawn the logger */
+ int mapped_fds[] = {pipefds[0], 0, /* map pipefds[0] to stdin */
+ -1};
+ if ((pid = h2o_spawnp(argv[0], argv, mapped_fds, 0)) == -1) {
+ fprintf(stderr, "failed to open logger: %s:%s\n", path + 1, strerror(errno));
+ return -1;
+ }
+ /* close the read side of the pipefds and return the write side */
+ close(pipefds[0]);
+ fd = pipefds[1];
+ } else {
+ if ((fd = open(path, O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC, 0644)) == -1) {
+ fprintf(stderr, "failed to open log file:%s:%s\n", path, strerror(errno));
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+h2o_access_log_filehandle_t *h2o_access_log_open_handle(const char *path, const char *fmt, int escape)
+{
+ h2o_logconf_t *logconf;
+ int fd;
+ h2o_access_log_filehandle_t *fh;
+ char errbuf[256];
+
+ /* default to combined log format */
+ if (fmt == NULL)
+ fmt = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\"";
+ if ((logconf = h2o_logconf_compile(fmt, escape, errbuf)) == NULL) {
+ fprintf(stderr, "%s\n", errbuf);
+ return NULL;
+ }
+
+ /* open log file */
+ if ((fd = h2o_access_log_open_log(path)) == -1) {
+ h2o_logconf_dispose(logconf);
+ return NULL;
+ }
+
+ fh = h2o_mem_alloc_shared(NULL, sizeof(*fh), on_dispose_handle);
+ fh->logconf = logconf;
+ fh->fd = fd;
+ return fh;
+}
+
+static void dispose(h2o_logger_t *_self)
+{
+ struct st_h2o_access_logger_t *self = (void *)_self;
+
+ h2o_mem_release_shared(self->fh);
+}
+
+h2o_logger_t *h2o_access_log_register(h2o_pathconf_t *pathconf, h2o_access_log_filehandle_t *fh)
+{
+ struct st_h2o_access_logger_t *self = (void *)h2o_create_logger(pathconf, sizeof(*self));
+
+ self->super.dispose = dispose;
+ self->super.log_access = log_access;
+ self->fh = fh;
+ h2o_mem_addref_shared(fh);
+
+ return &self->super;
+}
diff --git a/debian/vendor-h2o/lib/handler/chunked.c b/debian/vendor-h2o/lib/handler/chunked.c
new file mode 100644
index 0000000..b6ad434
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/chunked.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "h2o.h"
+
+typedef struct st_chunked_encoder_t {
+ h2o_ostream_t super;
+ char buf[64];
+} chunked_encoder_t;
+
+static void send_chunk(h2o_ostream_t *_self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ chunked_encoder_t *self = (void *)_self;
+ h2o_iovec_t *outbufs = alloca(sizeof(h2o_iovec_t) * (inbufcnt + 2));
+ size_t chunk_size, outbufcnt = 0, i;
+
+ /* calc chunk size */
+ chunk_size = 0;
+ for (i = 0; i != inbufcnt; ++i)
+ chunk_size += inbufs[i].len;
+ req->bytes_sent += chunk_size;
+
+ /* create chunk header and output data */
+ if (chunk_size != 0) {
+ outbufs[outbufcnt].base = self->buf;
+ outbufs[outbufcnt].len = sprintf(self->buf, "%zx\r\n", chunk_size);
+ assert(outbufs[outbufcnt].len < sizeof(self->buf));
+ outbufcnt++;
+ memcpy(outbufs + outbufcnt, inbufs, sizeof(h2o_iovec_t) * inbufcnt);
+ outbufcnt += inbufcnt;
+ if (state != H2O_SEND_STATE_ERROR) {
+ outbufs[outbufcnt].base = "\r\n0\r\n\r\n";
+ outbufs[outbufcnt].len = state == H2O_SEND_STATE_FINAL ? 7 : 2;
+ outbufcnt++;
+ }
+ } else if (state == H2O_SEND_STATE_FINAL) {
+ outbufs[outbufcnt].base = "0\r\n\r\n";
+ outbufs[outbufcnt].len = 5;
+ outbufcnt++;
+ }
+
+ /* if state is error, send a broken chunk to pass the error down to the browser */
+ if (state == H2O_SEND_STATE_ERROR) {
+ outbufs[outbufcnt].base = "\r\n1\r\n";
+ outbufs[outbufcnt].len = 5;
+ outbufcnt++;
+ }
+
+ h2o_ostream_send_next(&self->super, req, outbufs, outbufcnt, state);
+}
+
+static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ chunked_encoder_t *encoder;
+
+ /* do nothing if not HTTP/1.1 or content-length is known */
+ if (req->res.content_length != SIZE_MAX || req->version != 0x101)
+ goto Next;
+ /* RFC 2616 4.4 states that the following status codes (and response to a HEAD method) should not include message body */
+ if ((100 <= req->res.status && req->res.status <= 199) || req->res.status == 204 || req->res.status == 304)
+ goto Next;
+ else if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")))
+ goto Next;
+ /* we cannot handle certain responses (like 101 switching protocols) */
+ if (req->res.status != 200) {
+ req->http1_is_persistent = 0;
+ goto Next;
+ }
+ /* skip if content-encoding header is being set */
+ if (h2o_find_header(&req->res.headers, H2O_TOKEN_TRANSFER_ENCODING, -1) != -1)
+ goto Next;
+
+ /* set content-encoding header */
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_TRANSFER_ENCODING, NULL, H2O_STRLIT("chunked"));
+
+ /* set the flag that tells finalostream that req->bytes_sent is already counted */
+ req->bytes_counted_by_ostream = 1;
+
+ /* setup filter */
+ encoder = (void *)h2o_add_ostream(req, sizeof(chunked_encoder_t), slot);
+ encoder->super.do_send = send_chunk;
+ slot = &encoder->super.next;
+
+Next:
+ h2o_setup_next_ostream(req, slot);
+}
+
+void h2o_chunked_register(h2o_pathconf_t *pathconf)
+{
+ h2o_filter_t *self = h2o_create_filter(pathconf, sizeof(*self));
+ self->on_setup_ostream = on_setup_ostream;
+}
diff --git a/debian/vendor-h2o/lib/handler/compress.c b/debian/vendor-h2o/lib/handler/compress.c
new file mode 100644
index 0000000..6159771
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/compress.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2015,2016 Justin Zhu, DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include "h2o.h"
+
+#ifndef BUF_SIZE
+#define BUF_SIZE 8192
+#endif
+
+struct st_compress_filter_t {
+ h2o_filter_t super;
+ h2o_compress_args_t args;
+};
+
+struct st_compress_encoder_t {
+ h2o_ostream_t super;
+ h2o_compress_context_t *compressor;
+};
+
+static void do_send(h2o_ostream_t *_self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ struct st_compress_encoder_t *self = (void *)_self;
+ h2o_iovec_t *outbufs;
+ size_t outbufcnt;
+
+ self->compressor->transform(self->compressor, inbufs, inbufcnt, state, &outbufs, &outbufcnt);
+ h2o_ostream_send_next(&self->super, req, outbufs, outbufcnt, state);
+}
+
+static void on_setup_ostream(h2o_filter_t *_self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ struct st_compress_filter_t *self = (void *)_self;
+ struct st_compress_encoder_t *encoder;
+ int compressible_types;
+ h2o_compress_context_t *compressor;
+ ssize_t i;
+
+ if (req->version < 0x101)
+ goto Next;
+ if (req->res.status != 200)
+ goto Next;
+ if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")))
+ goto Next;
+
+ switch (req->compress_hint) {
+ case H2O_COMPRESS_HINT_DISABLE:
+ /* compression was explicitely disabled, skip */
+ goto Next;
+ case H2O_COMPRESS_HINT_ENABLE:
+ /* compression was explicitely enabled */
+ break;
+ case H2O_COMPRESS_HINT_AUTO:
+ default:
+ /* no hint from the producer, decide whether to compress based
+ on the configuration */
+ if (req->res.mime_attr == NULL)
+ h2o_req_fill_mime_attributes(req);
+ if (!req->res.mime_attr->is_compressible)
+ goto Next;
+ if (req->res.content_length < self->args.min_size)
+ goto Next;
+ }
+
+ /* skip if failed to gather the list of compressible types */
+ if ((compressible_types = h2o_get_compressible_types(&req->headers)) == 0)
+ goto Next;
+
+ /* skip if content-encoding header is being set (as well as obtain the location of accept-ranges) */
+ size_t content_encoding_header_index = -1, accept_ranges_header_index = -1;
+ for (i = 0; i != req->res.headers.size; ++i) {
+ if (req->res.headers.entries[i].name == &H2O_TOKEN_CONTENT_ENCODING->buf)
+ content_encoding_header_index = i;
+ else if (req->res.headers.entries[i].name == &H2O_TOKEN_ACCEPT_RANGES->buf)
+ accept_ranges_header_index = i;
+ else
+ continue;
+ }
+ if (content_encoding_header_index != -1)
+ goto Next;
+
+/* open the compressor */
+#if H2O_USE_BROTLI
+ if (self->args.brotli.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_BROTLI) != 0) {
+ compressor = h2o_compress_brotli_open(&req->pool, self->args.brotli.quality, req->res.content_length);
+ } else
+#endif
+ if (self->args.gzip.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_GZIP) != 0) {
+ compressor = h2o_compress_gzip_open(&req->pool, self->args.gzip.quality);
+ } else {
+ /* let proxies know that we looked at accept-encoding when deciding not to compress */
+ h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding"));
+ goto Next;
+ }
+
+ /* adjust the response headers */
+ req->res.content_length = SIZE_MAX;
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, NULL, compressor->name.base, compressor->name.len);
+ h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding"));
+ if (accept_ranges_header_index != -1) {
+ req->res.headers.entries[accept_ranges_header_index].value = h2o_iovec_init(H2O_STRLIT("none"));
+ } else {
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCEPT_RANGES, NULL, H2O_STRLIT("none"));
+ }
+
+ /* setup filter */
+ encoder = (void *)h2o_add_ostream(req, sizeof(*encoder), slot);
+ encoder->super.do_send = do_send;
+ slot = &encoder->super.next;
+ encoder->compressor = compressor;
+
+ /* adjust preferred chunk size (compress by 8192 bytes) */
+ if (req->preferred_chunk_size > BUF_SIZE)
+ req->preferred_chunk_size = BUF_SIZE;
+
+Next:
+ h2o_setup_next_ostream(req, slot);
+}
+
+void h2o_compress_register(h2o_pathconf_t *pathconf, h2o_compress_args_t *args)
+{
+ struct st_compress_filter_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self));
+ self->super.on_setup_ostream = on_setup_ostream;
+ self->args = *args;
+}
diff --git a/debian/vendor-h2o/lib/handler/compress/brotli.cc b/debian/vendor-h2o/lib/handler/compress/brotli.cc
new file mode 100644
index 0000000..d4d06e9
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/compress/brotli.cc
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <cassert>
+#include <cstdlib>
+#include <vector>
+#include <zlib.h>
+#include "h2o.h"
+#include "encode.h"
+
+namespace {
+ class brotli_context : public h2o_compress_context_t {
+ protected:
+ brotli::BrotliCompressor *brotli_;
+ brotli::BrotliParams params_;
+ std::vector<h2o_iovec_t> bufs_; // all bufs_[nnn].base must be free(3)ed
+ public:
+ brotli_context(int quality, size_t estimated_content_length) : brotli_(NULL) {
+ name = h2o_iovec_init(H2O_STRLIT("br"));
+ transform = _compress;
+ params_.quality = quality;
+ if (estimated_content_length != std::numeric_limits<size_t>::max())
+ _update_lgwin(params_, estimated_content_length);
+ }
+ ~brotli_context() {
+ _clear_bufs();
+ delete brotli_;
+ }
+ static void dispose(void *_self) {
+ brotli_context *self = static_cast<brotli_context *>(_self);
+ self->~brotli_context();
+ }
+ private:
+ void _clear_bufs();
+ void _emit(bool is_last, bool force_flush);
+ void _compress(h2o_iovec_t *inbufs, size_t inbufcnt, int is_final, h2o_iovec_t **outbufs, size_t *outbufcnt);
+ static void _compress(h2o_compress_context_t *self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt) {
+ static_cast<brotli_context*>(self)->_compress(inbufs, inbufcnt, !h2o_send_state_is_in_progress(state), outbufs, outbufcnt);
+ }
+ static void _update_lgwin(brotli::BrotliParams &params, size_t estimated_content_length);
+ };
+}
+
+void brotli_context::_clear_bufs()
+{
+ for (std::vector<h2o_iovec_t>::iterator i = bufs_.begin(); i != bufs_.end(); ++i)
+ free(i->base);
+ bufs_.clear();
+}
+
+void brotli_context::_emit(bool is_last, bool force_flush)
+{
+ uint8_t *output;
+ size_t out_size;
+ bool ret = brotli_->WriteBrotliData(is_last, force_flush, &out_size, &output);
+ assert(ret);
+ (void)ret;
+ if (out_size != 0)
+ bufs_.push_back(h2o_strdup(NULL, reinterpret_cast<const char *>(output), out_size));
+}
+
+void brotli_context::_compress(h2o_iovec_t *inbufs, size_t inbufcnt, int is_final, h2o_iovec_t **outbufs, size_t *outbufcnt)
+{
+ if (brotli_ == NULL) {
+ if (is_final) {
+ uint64_t len = 0;
+ for (size_t i = 0; i != inbufcnt; ++i)
+ len += inbufs[i].len;
+ if (len < std::numeric_limits<size_t>::max())
+ _update_lgwin(params_, len);
+ }
+ brotli_ = new brotli::BrotliCompressor(params_);
+ }
+
+ _clear_bufs();
+
+ if (inbufcnt != 0) {
+ size_t inbufindex = 0, offset = 0, block_space = brotli_->input_block_size();
+ while (inbufindex != inbufcnt) {
+ size_t copy_len = std::min(block_space, inbufs[inbufindex].len - offset);
+ brotli_->CopyInputToRingBuffer(copy_len, reinterpret_cast<const uint8_t *>(inbufs[inbufindex].base) + offset);
+ offset += copy_len;
+ if (inbufs[inbufindex].len == offset) {
+ if (++inbufindex == inbufcnt)
+ break;
+ offset = 0;
+ }
+ if (block_space == 0) {
+ _emit(false, false);
+ block_space = brotli_->input_block_size();
+ }
+ }
+ _emit(is_final, !is_final);
+ } else {
+ if (is_final)
+ _emit(true, false);
+ }
+
+ if (is_final) {
+ delete brotli_;
+ brotli_ = NULL;
+ }
+
+ *outbufs = &bufs_.front();
+ *outbufcnt = bufs_.size();
+}
+
+void brotli_context::_update_lgwin(brotli::BrotliParams &params, size_t estimated_content_length)
+{
+ int bits = estimated_content_length > 1 ? sizeof(unsigned long long) * 8 - __builtin_clzll(estimated_content_length - 1) : 1;
+ if (bits < params.lgwin)
+ params.lgwin = std::max(bits, brotli::kMinWindowBits);
+}
+
+h2o_compress_context_t *h2o_compress_brotli_open(h2o_mem_pool_t *pool, int quality, size_t estimated_content_length)
+{
+ brotli_context *ctx = static_cast<brotli_context *>(h2o_mem_alloc_shared(pool, sizeof(*ctx), brotli_context::dispose));
+ return new (ctx) brotli_context(quality, estimated_content_length);
+}
diff --git a/debian/vendor-h2o/lib/handler/compress/gzip.c b/debian/vendor-h2o/lib/handler/compress/gzip.c
new file mode 100644
index 0000000..c12260d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/compress/gzip.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <zlib.h>
+#include "h2o.h"
+
+#define WINDOW_BITS 31
+#ifndef BUF_SIZE /* is altered by unit test */
+#define BUF_SIZE 8192
+#endif
+
+typedef H2O_VECTOR(h2o_iovec_t) iovec_vector_t;
+
+struct st_gzip_context_t {
+ h2o_compress_context_t super;
+ z_stream zs;
+ int zs_is_open;
+ iovec_vector_t bufs;
+};
+
+static void do_compress(h2o_compress_context_t *_self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt);
+static void do_decompress(h2o_compress_context_t *_self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt);
+
+static void *alloc_cb(void *_unused, unsigned int cnt, unsigned int sz)
+{
+ return h2o_mem_alloc(cnt * (size_t)sz);
+}
+
+static void free_cb(void *_unused, void *p)
+{
+ free(p);
+}
+
+static void expand_buf(iovec_vector_t *bufs)
+{
+ h2o_vector_reserve(NULL, bufs, bufs->size + 1);
+ bufs->entries[bufs->size++] = h2o_iovec_init(h2o_mem_alloc(BUF_SIZE), 0);
+}
+
+typedef int (*processor)(z_streamp strm, int flush);
+
+static size_t process_chunk(struct st_gzip_context_t *self, const void *src, size_t len, int flush, size_t bufindex, processor proc)
+{
+ int ret;
+
+ self->zs.next_in = (void *)src;
+ self->zs.avail_in = (unsigned)len;
+
+ /* man says: If inflate/deflate returns with avail_out == 0, this function must be called again with the same value of the flush
+ * parameter and more output space (updated avail_out), until the flush is complete (function returns with non-zero avail_out).
+ */
+ do {
+ /* expand buffer (note: in case of Z_SYNC_FLUSH we need to supply at least 6 bytes of output buffer) */
+ if (self->bufs.entries[bufindex].len + 32 > BUF_SIZE) {
+ ++bufindex;
+ if (bufindex == self->bufs.size)
+ expand_buf(&self->bufs);
+ self->bufs.entries[bufindex].len = 0;
+ }
+ self->zs.next_out = (void *)(self->bufs.entries[bufindex].base + self->bufs.entries[bufindex].len);
+ self->zs.avail_out = (unsigned)(BUF_SIZE - self->bufs.entries[bufindex].len);
+ ret = proc(&self->zs, flush);
+ /* inflate() returns Z_BUF_ERROR if flush is set to Z_FINISH at the middle of the compressed data */
+ assert(ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR);
+ self->bufs.entries[bufindex].len = BUF_SIZE - self->zs.avail_out;
+ } while (self->zs.avail_out == 0 && ret != Z_STREAM_END);
+
+ return bufindex;
+}
+
+static void do_process(h2o_compress_context_t *_self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt, processor proc)
+{
+ struct st_gzip_context_t *self = (void *)_self;
+ size_t outbufindex;
+ h2o_iovec_t last_buf;
+
+ outbufindex = 0;
+ self->bufs.entries[0].len = 0;
+
+ if (inbufcnt != 0) {
+ size_t i;
+ for (i = 0; i != inbufcnt - 1; ++i)
+ outbufindex = process_chunk(self, inbufs[i].base, inbufs[i].len, Z_NO_FLUSH, outbufindex, proc);
+ last_buf = inbufs[i];
+ } else {
+ last_buf = h2o_iovec_init(NULL, 0);
+ }
+ outbufindex = process_chunk(self, last_buf.base, last_buf.len, h2o_send_state_is_in_progress(state) ? Z_SYNC_FLUSH : Z_FINISH,
+ outbufindex, proc);
+
+ *outbufs = self->bufs.entries;
+ *outbufcnt = outbufindex + 1;
+
+ if (!h2o_send_state_is_in_progress(state)) {
+ if (self->super.transform == do_compress) {
+ deflateEnd(&self->zs);
+ } else {
+ inflateEnd(&self->zs);
+ }
+ self->zs_is_open = 0;
+ }
+}
+
+static void do_compress(h2o_compress_context_t *_self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt)
+{
+ do_process(_self, inbufs, inbufcnt, state, outbufs, outbufcnt, (processor)deflate);
+}
+
+static void do_decompress(h2o_compress_context_t *_self, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state,
+ h2o_iovec_t **outbufs, size_t *outbufcnt)
+{
+ do_process(_self, inbufs, inbufcnt, state, outbufs, outbufcnt, (processor)inflate);
+}
+
+static void do_free(void *_self)
+{
+ struct st_gzip_context_t *self = _self;
+ size_t i;
+
+ if (self->zs_is_open) {
+ if (self->super.transform == do_compress) {
+ deflateEnd(&self->zs);
+ } else {
+ inflateEnd(&self->zs);
+ }
+ }
+
+ for (i = 0; i != self->bufs.size; ++i)
+ free(self->bufs.entries[i].base);
+ free(self->bufs.entries);
+}
+
+static struct st_gzip_context_t *gzip_open(h2o_mem_pool_t *pool)
+{
+ struct st_gzip_context_t *self = h2o_mem_alloc_shared(pool, sizeof(*self), do_free);
+
+ self->super.name = h2o_iovec_init(H2O_STRLIT("gzip"));
+ self->super.transform = NULL;
+ self->zs.zalloc = alloc_cb;
+ self->zs.zfree = free_cb;
+ self->zs.opaque = NULL;
+ self->zs_is_open = 1;
+ self->bufs = (iovec_vector_t){NULL};
+ expand_buf(&self->bufs);
+
+ return self;
+}
+
+h2o_compress_context_t *h2o_compress_gzip_open(h2o_mem_pool_t *pool, int quality)
+{
+ struct st_gzip_context_t *self = gzip_open(pool);
+ self->super.transform = do_compress;
+ /* Z_BEST_SPEED for on-the-fly compression, memlevel set to 8 as suggested by the manual */
+ deflateInit2(&self->zs, quality, Z_DEFLATED, WINDOW_BITS, 8, Z_DEFAULT_STRATEGY);
+
+ return &self->super;
+}
+
+h2o_compress_context_t *h2o_compress_gunzip_open(h2o_mem_pool_t *pool)
+{
+ struct st_gzip_context_t *self = gzip_open(pool);
+ self->super.transform = do_decompress;
+ inflateInit2(&self->zs, WINDOW_BITS);
+
+ return &self->super;
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/access_log.c b/debian/vendor-h2o/lib/handler/configurator/access_log.c
new file mode 100644
index 0000000..de63808
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/access_log.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+typedef H2O_VECTOR(h2o_access_log_filehandle_t *) st_h2o_access_log_filehandle_vector_t;
+
+struct st_h2o_access_log_configurator_t {
+ h2o_configurator_t super;
+ st_h2o_access_log_filehandle_vector_t *handles;
+ st_h2o_access_log_filehandle_vector_t _handles_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_access_log_configurator_t *self = (void *)cmd->configurator;
+ const char *path, *fmt = NULL;
+ int escape = H2O_LOGCONF_ESCAPE_APACHE;
+ h2o_access_log_filehandle_t *fh;
+
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ path = node->data.scalar;
+ break;
+ case YOML_TYPE_MAPPING: {
+ yoml_t *t;
+ /* get path */
+ if ((t = yoml_get(node, "path")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "could not find mandatory key `path`");
+ return -1;
+ }
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, t, "`path` must be scalar");
+ return -1;
+ }
+ path = t->data.scalar;
+ /* get format */
+ if ((t = yoml_get(node, "format")) != NULL) {
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, t, "`format` must be a scalar");
+ return -1;
+ }
+ fmt = t->data.scalar;
+ }
+ /* get escape */
+ if ((t = yoml_get(node, "escape")) != NULL) {
+ switch (h2o_configurator_get_one_of(cmd, t, "apache,json")) {
+ case 0:
+ escape = H2O_LOGCONF_ESCAPE_APACHE;
+ break;
+ case 1:
+ escape = H2O_LOGCONF_ESCAPE_JSON;
+ break;
+ default:
+ return -1;
+ }
+ }
+ } break;
+ default:
+ h2o_configurator_errprintf(cmd, node, "node must be a scalar or a mapping");
+ return -1;
+ }
+
+ if (!ctx->dry_run) {
+ if ((fh = h2o_access_log_open_handle(path, fmt, escape)) == NULL)
+ return -1;
+ h2o_vector_reserve(NULL, self->handles, self->handles->size + 1);
+ self->handles->entries[self->handles->size++] = fh;
+ }
+
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_access_log_configurator_t *self = (void *)_self;
+ size_t i;
+
+ /* push the stack pointer */
+ ++self->handles;
+
+ /* link the handles */
+ memset(self->handles, 0, sizeof(*self->handles));
+ h2o_vector_reserve(NULL, self->handles, self->handles[-1].size + 1);
+ for (i = 0; i != self->handles[-1].size; ++i) {
+ h2o_access_log_filehandle_t *fh = self->handles[-1].entries[i];
+ self->handles[0].entries[self->handles[0].size++] = fh;
+ h2o_mem_addref_shared(fh);
+ }
+
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_access_log_configurator_t *self = (void *)_self;
+ size_t i;
+
+ /* register all handles, and decref them */
+ for (i = 0; i != self->handles->size; ++i) {
+ h2o_access_log_filehandle_t *fh = self->handles->entries[i];
+ if (ctx->pathconf != NULL)
+ h2o_access_log_register(ctx->pathconf, fh);
+ h2o_mem_release_shared(fh);
+ }
+ /* free the vector */
+ free(self->handles->entries);
+
+ /* pop the stack pointer */
+ --self->handles;
+
+ return 0;
+}
+
+void h2o_access_log_register_configurator(h2o_globalconf_t *conf)
+{
+ struct st_h2o_access_log_configurator_t *self = (void *)h2o_configurator_create(conf, sizeof(*self));
+
+ self->super.enter = on_config_enter;
+ self->super.exit = on_config_exit;
+ self->handles = self->_handles_stack;
+
+ h2o_configurator_define_command(&self->super, "access-log", H2O_CONFIGURATOR_FLAG_ALL_LEVELS, on_config);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/compress.c b/debian/vendor-h2o/lib/handler/configurator/compress.c
new file mode 100644
index 0000000..c023dd5
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/compress.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+#define DEFAULT_GZIP_QUALITY 1
+#define DEFAULT_BROTLI_QUALITY 1
+
+struct compress_configurator_t {
+ h2o_configurator_t super;
+ h2o_compress_args_t *vars, _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static const h2o_compress_args_t all_off = {0, {-1}, {-1}}, all_on = {100, {DEFAULT_GZIP_QUALITY}, {DEFAULT_BROTLI_QUALITY}};
+
+static int on_config_gzip(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct compress_configurator_t *self = (void *)cmd->configurator;
+ int mode;
+
+ if ((mode = (int)h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1)
+ return -1;
+
+ *self->vars = all_off;
+ if (mode != 0)
+ self->vars->gzip.quality = DEFAULT_GZIP_QUALITY;
+
+ return 0;
+}
+
+static int obtain_quality(yoml_t *node, int min_quality, int max_quality, int default_quality, int *slot)
+{
+ int tmp;
+ if (node->type != YOML_TYPE_SCALAR)
+ return -1;
+ if (strcasecmp(node->data.scalar, "OFF") == 0) {
+ *slot = -1;
+ return 0;
+ }
+ if (strcasecmp(node->data.scalar, "ON") == 0) {
+ *slot = default_quality;
+ return 0;
+ }
+ if (sscanf(node->data.scalar, "%d", &tmp) == 1 && (min_quality <= tmp && tmp <= max_quality)) {
+ *slot = tmp;
+ return 0;
+ }
+ return -1;
+}
+
+static int on_config_compress_min_size(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct compress_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%zu", &self->vars->min_size);
+}
+
+static int on_config_compress(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct compress_configurator_t *self = (void *)cmd->configurator;
+ size_t i;
+
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ if (strcasecmp(node->data.scalar, "OFF") == 0) {
+ *self->vars = all_off;
+ } else if (strcasecmp(node->data.scalar, "ON") == 0) {
+ *self->vars = all_on;
+ } else {
+ h2o_configurator_errprintf(cmd, node, "scalar argument must be either of: `OFF`, `ON`");
+ return -1;
+ }
+ break;
+ case YOML_TYPE_SEQUENCE:
+ *self->vars = all_off;
+ for (i = 0; i != node->data.sequence.size; ++i) {
+ yoml_t *element = node->data.sequence.elements[i];
+ if (element->type == YOML_TYPE_SCALAR && strcasecmp(element->data.scalar, "gzip") == 0) {
+ self->vars->gzip.quality = DEFAULT_GZIP_QUALITY;
+ } else if (element->type == YOML_TYPE_SCALAR && strcasecmp(element->data.scalar, "br") == 0) {
+ self->vars->brotli.quality = DEFAULT_BROTLI_QUALITY;
+ } else {
+ h2o_configurator_errprintf(cmd, element, "element of the sequence must be either of: `gzip`, `br`");
+ return -1;
+ }
+ }
+ break;
+ case YOML_TYPE_MAPPING:
+ *self->vars = all_off;
+ for (i = 0; i != node->data.mapping.size; ++i) {
+ yoml_t *key = node->data.mapping.elements[i].key;
+ yoml_t *value = node->data.mapping.elements[i].value;
+ if (key->type == YOML_TYPE_SCALAR && strcasecmp(key->data.scalar, "gzip") == 0) {
+ if (obtain_quality(value, 1, 9, DEFAULT_GZIP_QUALITY, &self->vars->gzip.quality) != 0) {
+ h2o_configurator_errprintf(
+ cmd, value, "value of gzip attribute must be either of `OFF`, `ON` or an integer value between 1 and 9");
+ return -1;
+ }
+ } else if (key->type == YOML_TYPE_SCALAR && strcasecmp(key->data.scalar, "br") == 0) {
+ if (obtain_quality(value, 0, 11, DEFAULT_BROTLI_QUALITY, &self->vars->brotli.quality) != 0) {
+ h2o_configurator_errprintf(
+ cmd, value, "value of br attribute must be either of `OFF`, `ON` or an integer between 0 and 11");
+ return -1;
+ }
+ } else {
+ h2o_configurator_errprintf(cmd, key, "key must be either of: `gzip`, `br`");
+ return -1;
+ }
+ }
+ break;
+ default:
+ h2o_fatal("unexpected node type");
+ break;
+ }
+
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *configurator, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct compress_configurator_t *self = (void *)configurator;
+
+ ++self->vars;
+ self->vars[0] = self->vars[-1];
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *configurator, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct compress_configurator_t *self = (void *)configurator;
+
+ if (ctx->pathconf != NULL && (self->vars->gzip.quality != -1 || self->vars->brotli.quality != -1))
+ h2o_compress_register(ctx->pathconf, self->vars);
+
+ --self->vars;
+ return 0;
+}
+
+void h2o_compress_register_configurator(h2o_globalconf_t *conf)
+{
+ struct compress_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+ h2o_configurator_define_command(&c->super, "compress", H2O_CONFIGURATOR_FLAG_ALL_LEVELS, on_config_compress);
+ h2o_configurator_define_command(&c->super, "compress-minimum-size",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_compress_min_size);
+ h2o_configurator_define_command(&c->super, "gzip", H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_gzip);
+ c->vars = c->_vars_stack;
+ c->vars->gzip.quality = -1;
+ c->vars->brotli.quality = -1;
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/errordoc.c b/debian/vendor-h2o/lib/handler/configurator/errordoc.c
new file mode 100644
index 0000000..ab24923
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/errordoc.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct errordoc_configurator_t {
+ h2o_configurator_t super;
+ h2o_mem_pool_t pool;
+ H2O_VECTOR(h2o_errordoc_t) * vars, _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int scan_and_check_status(h2o_configurator_command_t *cmd, yoml_t *value, int *slot)
+{
+ if (value->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, value, "status must be must be either of: scalar, sequence of scalar");
+ return -1;
+ }
+ if (h2o_configurator_scanf(cmd, value, "%d", slot) != 0)
+ return -1;
+ if (!(400 <= *slot && *slot <= 599)) {
+ h2o_configurator_errprintf(cmd, value, "status must be within range of 400 to 599");
+ return -1;
+ }
+ return 0;
+}
+
+static int register_errordoc(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *hash)
+{
+ struct errordoc_configurator_t *self = (void *)cmd->configurator;
+ int status[200];
+ size_t status_len = 0;
+ int parsed;
+ const char *url = NULL;
+ size_t i, j, k;
+ yoml_t *key, *value;
+
+ for (i = 0; i != hash->data.mapping.size; ++i) {
+ key = hash->data.mapping.elements[i].key;
+ value = hash->data.mapping.elements[i].value;
+ if (key->type != YOML_TYPE_SCALAR)
+ goto UnknownKeyError;
+ if (strcmp(key->data.scalar, "status") == 0) {
+ if (status_len != 0)
+ goto KeyAlreadyDefinedError;
+
+ if (value->type == YOML_TYPE_SEQUENCE) {
+ if (value->data.sequence.size == 0) {
+ h2o_configurator_errprintf(cmd, value, "status sequence must not be empty");
+ return -1;
+ }
+ for (j = 0; j != value->data.sequence.size; ++j) {
+ if (scan_and_check_status(cmd, value->data.sequence.elements[j], &parsed) != 0)
+ return -1;
+ /* check the scanned status hasn't already appeared */
+ for (k = 0; k != status_len; ++k) {
+ if (status[k] == parsed) {
+ h2o_configurator_errprintf(cmd, value, "status %d appears multiple times", status[k]);
+ return -1;
+ }
+ }
+ status[status_len++] = parsed;
+ }
+ } else {
+ if (scan_and_check_status(cmd, value, &parsed) != 0)
+ return -1;
+ status[status_len++] = parsed;
+ }
+
+ } else if (strcmp(key->data.scalar, "url") == 0) {
+ if (url != NULL)
+ goto KeyAlreadyDefinedError;
+ if (value->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, value, "URL must be a scalar");
+ return -1;
+ }
+ url = value->data.scalar;
+ } else {
+ goto UnknownKeyError;
+ }
+ }
+
+ if (status_len == 0) {
+ h2o_configurator_errprintf(cmd, hash, "mandatory key `status` is not defined");
+ return -1;
+ }
+ if (url == NULL) {
+ h2o_configurator_errprintf(cmd, hash, "mandatory key `url` is not defined");
+ return -1;
+ }
+
+ h2o_iovec_t _url = h2o_strdup(&self->pool, url, SIZE_MAX);
+ for (i = 0; i != status_len; ++i){
+ /* register */
+ h2o_vector_reserve(&self->pool, self->vars, self->vars->size + 1);
+ h2o_errordoc_t *errordoc = self->vars->entries + self->vars->size++;
+ errordoc->status = status[i];
+ errordoc->url = _url;
+ }
+
+ return 0;
+
+UnknownKeyError:
+ h2o_configurator_errprintf(cmd, key, "key must be either of: `status`, `url`");
+ return -1;
+KeyAlreadyDefinedError:
+ h2o_configurator_errprintf(cmd, key, "the key cannot be defined more than once");
+ return -1;
+}
+
+static int on_config_errordoc(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ switch (node->type) {
+ case YOML_TYPE_SEQUENCE: {
+ size_t i;
+ for (i = 0; i != node->data.sequence.size; ++i) {
+ yoml_t *e = node->data.sequence.elements[i];
+ if (e->type != YOML_TYPE_MAPPING) {
+ h2o_configurator_errprintf(cmd, e, "element must be a mapping");
+ return -1;
+ }
+ if (register_errordoc(cmd, ctx, e) != 0)
+ return -1;
+ }
+ return 0;
+ }
+ case YOML_TYPE_MAPPING:
+ return register_errordoc(cmd, ctx, node);
+ default:
+ break;
+ }
+
+ h2o_configurator_errprintf(cmd, node, "argument must be either of: sequence, mapping");
+ return -1;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct errordoc_configurator_t *self = (void *)_self;
+
+ if (self->vars == self->_vars_stack) {
+ /* entering global level */
+ h2o_mem_init_pool(&self->pool);
+ }
+
+ /* copy vars */
+ memset(&self->vars[1], 0, sizeof(self->vars[1]));
+ h2o_vector_reserve(&self->pool, &self->vars[1], self->vars[0].size);
+ h2o_memcpy(self->vars[1].entries, self->vars[0].entries, sizeof(self->vars[0].entries[0]) * self->vars[0].size);
+ self->vars[1].size = self->vars[0].size;
+
+ ++self->vars;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct errordoc_configurator_t *self = (void *)_self;
+
+ if (ctx->pathconf != NULL && self->vars->size != 0)
+ h2o_errordoc_register(ctx->pathconf, self->vars->entries, self->vars->size);
+
+ --self->vars;
+ if (self->vars == self->_vars_stack) {
+ /* exitting global level */
+ h2o_mem_clear_pool(&self->pool);
+ }
+
+ return 0;
+}
+
+void h2o_errordoc_register_configurator(h2o_globalconf_t *conf)
+{
+ struct errordoc_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ /* set default vars */
+ c->vars = c->_vars_stack;
+
+ /* setup handlers */
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+
+ /* reproxy: ON | OFF */
+ h2o_configurator_define_command(&c->super, "error-doc", H2O_CONFIGURATOR_FLAG_ALL_LEVELS, on_config_errordoc);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/expires.c b/debian/vendor-h2o/lib/handler/configurator/expires.c
new file mode 100644
index 0000000..01d404d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/expires.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct expires_configurator_t {
+ h2o_configurator_t super;
+ h2o_expires_args_t **args;
+ h2o_expires_args_t *_args_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_expires(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct expires_configurator_t *self = (void *)cmd->configurator;
+ uint64_t value;
+ char unit[32];
+
+ if (strcasecmp(node->data.scalar, "OFF") == 0) {
+ free(*self->args);
+ *self->args = NULL;
+ } else if (sscanf(node->data.scalar, "%" SCNu64 " %31s", &value, unit) == 2) {
+ /* convert value to seconds depending on the unit */
+ if (strncasecmp(unit, H2O_STRLIT("second")) == 0) {
+ /* ok */
+ } else if (strncasecmp(unit, H2O_STRLIT("minute")) == 0) {
+ value *= 60;
+ } else if (strncasecmp(unit, H2O_STRLIT("hour")) == 0) {
+ value *= 60 * 60;
+ } else if (strncasecmp(unit, H2O_STRLIT("day")) == 0) {
+ value *= 24 * 60 * 60;
+ } else if (strncasecmp(unit, H2O_STRLIT("month")) == 0) {
+ value *= 30 * 24 * 60 * 60;
+ } else if (strncasecmp(unit, H2O_STRLIT("year")) == 0) {
+ value *= 365 * 30 * 24 * 60 * 60;
+ } else {
+ /* TODO add support for H2O_EXPIRES_MODE_MAX_ABSOLUTE that sets the Expires header? */
+ h2o_configurator_errprintf(cmd, node, "unknown unit:`%s` (see --help)", unit);
+ return -1;
+ }
+ /* save the value */
+ if (*self->args == NULL)
+ *self->args = h2o_mem_alloc(sizeof(**self->args));
+ (*self->args)->mode = H2O_EXPIRES_MODE_MAX_AGE;
+ (*self->args)->data.max_age = value;
+ } else {
+ h2o_configurator_errprintf(cmd, node,
+ "failed to parse the value, should be in form of: `<number> <unit>` or `OFF` (see --help)");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct expires_configurator_t *self = (void *)_self;
+
+ if (self->args[0] != NULL) {
+ /* duplicate */
+ assert(self->args[0]->mode == H2O_EXPIRES_MODE_MAX_AGE);
+ self->args[1] = h2o_mem_alloc(sizeof(**self->args));
+ *self->args[1] = *self->args[0];
+ } else {
+ self->args[1] = NULL;
+ }
+ ++self->args;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct expires_configurator_t *self = (void *)_self;
+
+ if (*self->args != NULL) {
+ /* setup */
+ if (ctx->pathconf != NULL) {
+ h2o_expires_register(ctx->pathconf, *self->args);
+ }
+ /* destruct */
+ assert((*self->args)->mode == H2O_EXPIRES_MODE_MAX_AGE);
+ free(*self->args);
+ *self->args = NULL;
+ }
+
+ --self->args;
+ return 0;
+}
+
+void h2o_expires_register_configurator(h2o_globalconf_t *conf)
+{
+ struct expires_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ /* set default vars */
+ c->args = c->_args_stack;
+
+ /* setup handlers */
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+ h2o_configurator_define_command(&c->super, "expires", H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_expires);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/fastcgi.c b/debian/vendor-h2o/lib/handler/configurator/fastcgi.c
new file mode 100644
index 0000000..bf89b7b
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/fastcgi.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd. Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+#include "h2o/serverutil.h"
+
+struct fastcgi_configurator_t {
+ h2o_configurator_t super;
+ h2o_fastcgi_config_vars_t *vars;
+ h2o_fastcgi_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_timeout_io(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->io_timeout);
+}
+
+static int on_config_timeout_keepalive(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->keepalive_timeout);
+}
+
+static int on_config_document_root(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+
+ if (node->data.scalar[0] == '\0') {
+ /* unset */
+ self->vars->document_root = h2o_iovec_init(NULL, 0);
+ } else if (node->data.scalar[0] == '/') {
+ /* set */
+ self->vars->document_root = h2o_iovec_init(node->data.scalar, strlen(node->data.scalar));
+ } else {
+ h2o_configurator_errprintf(cmd, node, "value does not start from `/`");
+ return -1;
+ }
+ return 0;
+}
+
+static int on_config_send_delegated_uri(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+ ssize_t v;
+
+ if ((v = h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1)
+ return -1;
+ self->vars->send_delegated_uri = (int)v;
+ return 0;
+}
+
+static int on_config_connect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+ const char *hostname = "127.0.0.1", *servname = NULL, *type = "tcp";
+
+ /* fetch servname (and hostname) */
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ servname = node->data.scalar;
+ break;
+ case YOML_TYPE_MAPPING: {
+ yoml_t *t;
+ if ((t = yoml_get(node, "host")) != NULL) {
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, t, "`host` is not a string");
+ return -1;
+ }
+ hostname = t->data.scalar;
+ }
+ if ((t = yoml_get(node, "port")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "cannot find mandatory property `port`");
+ return -1;
+ }
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, node, "`port` is not a string");
+ return -1;
+ }
+ servname = t->data.scalar;
+ if ((t = yoml_get(node, "type")) != NULL) {
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, t, "`type` is not a string");
+ return -1;
+ }
+ type = t->data.scalar;
+ }
+ } break;
+ default:
+ h2o_configurator_errprintf(cmd, node,
+ "value must be a string or a mapping (with keys: `port` and optionally `host` and `type`)");
+ return -1;
+ }
+
+ if (strcmp(type, "unix") == 0) {
+ /* unix socket */
+ struct sockaddr_un sa;
+ memset(&sa, 0, sizeof(sa));
+ if (strlen(servname) >= sizeof(sa.sun_path)) {
+ h2o_configurator_errprintf(cmd, node, "path:%s is too long as a unix socket name", servname);
+ return -1;
+ }
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, servname);
+ h2o_fastcgi_register_by_address(ctx->pathconf, (void *)&sa, sizeof(sa), self->vars);
+ } else if (strcmp(type, "tcp") == 0) {
+ /* tcp socket */
+ uint16_t port;
+ if (sscanf(servname, "%" SCNu16, &port) != 1) {
+ h2o_configurator_errprintf(cmd, node, "invalid port number:%s", servname);
+ return -1;
+ }
+ h2o_fastcgi_register_by_hostport(ctx->pathconf, hostname, port, self->vars);
+ } else {
+ h2o_configurator_errprintf(cmd, node, "unknown listen type: %s", type);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_spawnproc(h2o_configurator_command_t *cmd, yoml_t *node, const char *dirname, char *const *argv,
+ struct sockaddr_un *sa, struct passwd *pw)
+{
+ int ret, listen_fd = -1, pipe_fds[2] = {-1, -1};
+
+ /* build socket path */
+ sa->sun_family = AF_UNIX;
+ ret = snprintf(sa->sun_path, sizeof(sa->sun_path), "%s/_", dirname);
+ if (ret < 0 || ret >= sizeof(sa->sun_path)) {
+ h2o_configurator_errprintf(cmd, node, "unix socket path too long: %s", dirname);
+ goto Error;
+ }
+
+ /* create socket */
+ if ((listen_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ h2o_configurator_errprintf(cmd, node, "socket(2) failed: %s", strerror(errno));
+ goto Error;
+ }
+ if (bind(listen_fd, (void *)sa, sizeof(*sa)) != 0) {
+ h2o_configurator_errprintf(cmd, node, "bind(2) failed: %s", strerror(errno));
+ goto Error;
+ }
+ if (listen(listen_fd, H2O_SOMAXCONN) != 0) {
+ h2o_configurator_errprintf(cmd, node, "listen(2) failed: %s", strerror(errno));
+ goto Error;
+ }
+ /* change ownership of socket */
+ if (pw != NULL && chown(sa->sun_path, pw->pw_uid, pw->pw_gid) != 0) {
+ h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of socket:%s:%s", sa->sun_path, strerror(errno));
+ goto Error;
+ }
+
+ /* create pipe which is used to notify the termination of the server */
+ if (pipe(pipe_fds) != 0) {
+ h2o_configurator_errprintf(cmd, node, "pipe(2) failed: %s", strerror(errno));
+ pipe_fds[0] = -1;
+ pipe_fds[1] = -1;
+ goto Error;
+ }
+ if (fcntl(pipe_fds[1], F_SETFD, FD_CLOEXEC) < 0)
+ goto Error;
+
+ /* spawn */
+ int mapped_fds[] = {listen_fd, 0, /* listen_fd to 0 */
+ pipe_fds[0], 5, /* pipe_fds[0] to 5 */
+ -1};
+ pid_t pid = h2o_spawnp(argv[0], argv, mapped_fds, 0);
+ if (pid == -1) {
+ fprintf(stderr, "[lib/handler/fastcgi.c] failed to launch helper program %s:%s\n", argv[0], strerror(errno));
+ goto Error;
+ }
+
+ close(listen_fd);
+ listen_fd = -1;
+ close(pipe_fds[0]);
+ pipe_fds[0] = -1;
+
+ return pipe_fds[1];
+
+Error:
+ if (pipe_fds[0] != -1)
+ close(pipe_fds[0]);
+ if (pipe_fds[1])
+ close(pipe_fds[1]);
+ if (listen_fd != -1)
+ close(listen_fd);
+ unlink(sa->sun_path);
+ return -1;
+}
+
+static void spawnproc_on_dispose(h2o_fastcgi_handler_t *handler, void *data)
+{
+ int pipe_fd = (int)((char *)data - (char *)NULL);
+ close(pipe_fd);
+}
+
+static int on_config_spawn(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)cmd->configurator;
+ char *spawn_user = NULL, *spawn_cmd;
+ char *kill_on_close_cmd_path = NULL, *setuidgid_cmd_path = NULL;
+ char dirname[] = "/tmp/h2o.fcgisock.XXXXXX";
+ char *argv[10];
+ int spawner_fd;
+ struct sockaddr_un sa;
+ h2o_fastcgi_config_vars_t config_vars;
+ int ret = -1;
+ struct passwd h2o_user_pwbuf, *h2o_user_pw;
+ char h2o_user_buf[65536];
+
+ memset(&sa, 0, sizeof(sa));
+
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ spawn_user = ctx->globalconf->user;
+ spawn_cmd = node->data.scalar;
+ break;
+ case YOML_TYPE_MAPPING: {
+ yoml_t *t;
+ if ((t = yoml_get(node, "command")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "mandatory attribute `command` does not exist");
+ return -1;
+ }
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, node, "attribute `command` must be scalar");
+ return -1;
+ }
+ spawn_cmd = t->data.scalar;
+ spawn_user = ctx->globalconf->user;
+ if ((t = yoml_get(node, "user")) != NULL) {
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, node, "attribute `user` must be scalar");
+ return -1;
+ }
+ spawn_user = t->data.scalar;
+ }
+ } break;
+ default:
+ h2o_configurator_errprintf(cmd, node, "argument must be scalar or mapping");
+ return -1;
+ }
+
+ /* obtain uid & gid of the client that connects to the FastCGI daemon (i.e. H2O after dropping privileges) */
+ if (ctx->globalconf->user != NULL) {
+ /* change ownership of temporary directory */
+ if (getpwnam_r(ctx->globalconf->user, &h2o_user_pwbuf, h2o_user_buf, sizeof(h2o_user_buf), &h2o_user_pw) != 0 ||
+ h2o_user_pw == NULL) {
+ h2o_configurator_errprintf(cmd, node, "getpwnam_r(3) failed to obtain uid of user:%s", ctx->globalconf->user);
+ goto Exit;
+ }
+ } else {
+ h2o_user_pw = NULL;
+ }
+
+ { /* build args */
+ size_t i = 0;
+ argv[i++] = kill_on_close_cmd_path = h2o_configurator_get_cmd_path("share/h2o/kill-on-close");
+ argv[i++] = "--rm";
+ argv[i++] = dirname;
+ argv[i++] = "--";
+ if (spawn_user != NULL) {
+ argv[i++] = setuidgid_cmd_path = h2o_configurator_get_cmd_path("share/h2o/setuidgid");
+ argv[i++] = spawn_user;
+ }
+ argv[i++] = "/bin/sh";
+ argv[i++] = "-c";
+ argv[i++] = spawn_cmd;
+ argv[i++] = NULL;
+ assert(i <= sizeof(argv) / sizeof(argv[0]));
+ }
+
+ if (ctx->dry_run) {
+ dirname[0] = '\0';
+ spawner_fd = -1;
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, "/dry-run.nonexistent");
+ } else {
+ /* create temporary directory */
+ if (mkdtemp(dirname) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "mkdtemp(3) failed to create temporary directory:%s:%s", dirname,
+ strerror(errno));
+ dirname[0] = '\0';
+ goto Exit;
+ }
+ /* change ownership of temporary directory */
+ if (h2o_user_pw != NULL && chown(dirname, h2o_user_pw->pw_uid, h2o_user_pw->pw_gid) != 0) {
+ h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of temporary directory:%s:%s", dirname,
+ strerror(errno));
+ goto Exit;
+ }
+ /* launch spawnfcgi command */
+ if ((spawner_fd = create_spawnproc(cmd, node, dirname, argv, &sa, h2o_user_pw)) == -1) {
+ goto Exit;
+ }
+ }
+
+ config_vars = *self->vars;
+ config_vars.callbacks.dispose = spawnproc_on_dispose;
+ config_vars.callbacks.data = (char *)NULL + spawner_fd;
+ h2o_fastcgi_register_by_address(ctx->pathconf, (void *)&sa, sizeof(sa), &config_vars);
+
+ ret = 0;
+Exit:
+ if (dirname[0] != '\0')
+ unlink(dirname);
+ free(kill_on_close_cmd_path);
+ free(setuidgid_cmd_path);
+ return ret;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)_self;
+
+ memcpy(self->vars + 1, self->vars, sizeof(*self->vars));
+ ++self->vars;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct fastcgi_configurator_t *self = (void *)_self;
+
+ --self->vars;
+ return 0;
+}
+
+void h2o_fastcgi_register_configurator(h2o_globalconf_t *conf)
+{
+ struct fastcgi_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ /* set default vars */
+ c->vars = c->_vars_stack;
+ c->vars->io_timeout = H2O_DEFAULT_FASTCGI_IO_TIMEOUT;
+ c->vars->keepalive_timeout = 0;
+
+ /* setup handlers */
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+
+ h2o_configurator_define_command(&c->super, "fastcgi.connect",
+ H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED,
+ on_config_connect);
+ h2o_configurator_define_command(&c->super, "fastcgi.spawn",
+ H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED,
+ on_config_spawn);
+ h2o_configurator_define_command(&c->super, "fastcgi.timeout.io",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_timeout_io);
+ h2o_configurator_define_command(&c->super, "fastcgi.timeout.keepalive",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_timeout_keepalive);
+ h2o_configurator_define_command(&c->super, "fastcgi.document_root",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_document_root);
+ h2o_configurator_define_command(&c->super, "fastcgi.send-delegated-uri",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_send_delegated_uri);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/file.c b/debian/vendor-h2o/lib/handler/configurator/file.c
new file mode 100644
index 0000000..c1c779c
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/file.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct st_h2o_file_config_vars_t {
+ const char **index_files;
+ int flags;
+};
+
+struct st_h2o_file_configurator_t {
+ h2o_configurator_t super;
+ struct st_h2o_file_config_vars_t *vars;
+ struct st_h2o_file_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_dir(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+
+ h2o_file_register(ctx->pathconf, node->data.scalar, self->vars->index_files, *ctx->mimemap, self->vars->flags);
+ return 0;
+}
+
+static int on_config_file(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+ h2o_mimemap_type_t *mime_type =
+ h2o_mimemap_get_type_by_extension(*ctx->mimemap, h2o_get_filext(node->data.scalar, strlen(node->data.scalar)));
+ h2o_file_register_file(ctx->pathconf, node->data.scalar, mime_type, self->vars->flags);
+ return 0;
+}
+
+static int on_config_index(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+ size_t i;
+
+ free(self->vars->index_files);
+ self->vars->index_files = h2o_mem_alloc(sizeof(self->vars->index_files[0]) * (node->data.sequence.size + 1));
+ for (i = 0; i != node->data.sequence.size; ++i) {
+ yoml_t *element = node->data.sequence.elements[i];
+ if (element->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, element, "argument must be a sequence of scalars");
+ return -1;
+ }
+ self->vars->index_files[i] = element->data.scalar;
+ }
+ self->vars->index_files[i] = NULL;
+
+ return 0;
+}
+
+static int on_config_etag(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+
+ switch (h2o_configurator_get_one_of(cmd, node, "OFF,ON")) {
+ case 0: /* off */
+ self->vars->flags |= H2O_FILE_FLAG_NO_ETAG;
+ break;
+ case 1: /* on */
+ self->vars->flags &= ~H2O_FILE_FLAG_NO_ETAG;
+ break;
+ default: /* error */
+ return -1;
+ }
+
+ return 0;
+}
+
+static int on_config_send_compressed(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+
+ switch (h2o_configurator_get_one_of(cmd, node, "OFF,ON,gunzip")) {
+ case 0: /* off */
+ self->vars->flags &= ~H2O_FILE_FLAG_SEND_COMPRESSED;
+ break;
+ case 1: /* on */
+ self->vars->flags |= H2O_FILE_FLAG_SEND_COMPRESSED;
+ break;
+ case 2: /* gunzip */
+ self->vars->flags |= (H2O_FILE_FLAG_SEND_COMPRESSED | H2O_FILE_FLAG_GUNZIP);
+ break;
+ default: /* error */
+ return -1;
+ }
+
+ return 0;
+}
+
+static int on_config_dir_listing(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
+
+ switch (h2o_configurator_get_one_of(cmd, node, "OFF,ON")) {
+ case 0: /* off */
+ self->vars->flags &= ~H2O_FILE_FLAG_DIR_LISTING;
+ break;
+ case 1: /* on */
+ self->vars->flags |= H2O_FILE_FLAG_DIR_LISTING;
+ break;
+ default: /* error */
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char **dup_strlist(const char **s)
+{
+ size_t i;
+ const char **ret;
+
+ for (i = 0; s[i] != NULL; ++i)
+ ;
+ ret = h2o_mem_alloc(sizeof(*ret) * (i + 1));
+ for (i = 0; s[i] != NULL; ++i)
+ ret[i] = s[i];
+ ret[i] = NULL;
+
+ return ret;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)_self;
+ ++self->vars;
+ self->vars[0].index_files = dup_strlist(self->vars[-1].index_files);
+ self->vars[0].flags = self->vars[-1].flags;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_h2o_file_configurator_t *self = (void *)_self;
+ free(self->vars->index_files);
+ --self->vars;
+ return 0;
+}
+
+void h2o_file_register_configurator(h2o_globalconf_t *globalconf)
+{
+ struct st_h2o_file_configurator_t *self = (void *)h2o_configurator_create(globalconf, sizeof(*self));
+
+ self->super.enter = on_config_enter;
+ self->super.exit = on_config_exit;
+ self->vars = self->_vars_stack;
+ self->vars->index_files = h2o_file_default_index_files;
+
+ h2o_configurator_define_command(&self->super, "file.dir", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR |
+ H2O_CONFIGURATOR_FLAG_DEFERRED,
+ on_config_dir);
+ h2o_configurator_define_command(&self->super, "file.file", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR |
+ H2O_CONFIGURATOR_FLAG_DEFERRED,
+ on_config_file);
+ h2o_configurator_define_command(&self->super, "file.index",
+ (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE,
+ on_config_index);
+ h2o_configurator_define_command(&self->super, "file.etag",
+ (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_etag);
+ h2o_configurator_define_command(&self->super, "file.send-compressed",
+ (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_send_compressed);
+ h2o_configurator_define_command(&self->super, "file.send-gzip",
+ (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_send_compressed);
+ h2o_configurator_define_command(&self->super, "file.dirlisting",
+ (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_dir_listing);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/headers.c b/debian/vendor-h2o/lib/handler/configurator/headers.c
new file mode 100644
index 0000000..68536c0
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/headers.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <string.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct headers_configurator_t {
+ h2o_configurator_t super;
+ h2o_headers_command_t **cmds, *_cmd_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct headers_configurator_t *self = (void *)_self;
+
+ self->cmds[1] = self->cmds[0];
+ if (self->cmds[1] != NULL)
+ h2o_mem_addref_shared(self->cmds[1]);
+
+ ++self->cmds;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct headers_configurator_t *self = (void *)_self;
+
+ if (ctx->pathconf != NULL && *self->cmds != NULL) {
+ if (*self->cmds != NULL)
+ h2o_mem_addref_shared(*self->cmds);
+ h2o_headers_register(ctx->pathconf, *self->cmds);
+ }
+
+ if (*self->cmds != NULL)
+ h2o_mem_release_shared(*self->cmds);
+ --self->cmds;
+ return 0;
+}
+
+static h2o_headers_command_t **get_headers_commands(h2o_configurator_t *_self)
+{
+ struct headers_configurator_t *self = (void *)_self;
+ return self->cmds;
+}
+
+void h2o_headers_register_configurator(h2o_globalconf_t *conf)
+{
+ struct headers_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+
+ h2o_configurator_define_headers_commands(conf, &c->super, "header", get_headers_commands);
+ c->cmds = c->_cmd_stack;
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/headers_util.c b/debian/vendor-h2o/lib/handler/configurator/headers_util.c
new file mode 100644
index 0000000..c05b9b7
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/headers_util.c
@@ -0,0 +1,143 @@
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct headers_util_configurator_t {
+ h2o_configurator_t super;
+ h2o_configurator_t *child;
+ h2o_configurator_get_headers_commands_cb get_commands;
+};
+
+static int extract_name(const char *src, size_t len, h2o_iovec_t **_name)
+{
+ h2o_iovec_t name;
+ const h2o_token_t *name_token;
+
+ name = h2o_str_stripws(src, len);
+ if (name.len == 0)
+ return -1;
+
+ name = h2o_strdup(NULL, name.base, name.len);
+ h2o_strtolower(name.base, name.len);
+
+ if ((name_token = h2o_lookup_token(name.base, name.len)) != NULL) {
+ *_name = (h2o_iovec_t *)&name_token->buf;
+ free(name.base);
+ } else {
+ *_name = h2o_mem_alloc(sizeof(**_name));
+ **_name = name;
+ }
+
+ return 0;
+}
+
+static int extract_name_value(const char *src, h2o_iovec_t **name, h2o_iovec_t *value)
+{
+ const char *colon = strchr(src, ':');
+
+ if (colon == NULL)
+ return -1;
+
+ if (extract_name(src, colon - src, name) != 0)
+ return -1;
+ *value = h2o_str_stripws(colon + 1, strlen(colon + 1));
+ *value = h2o_strdup(NULL, value->base, value->len);
+
+ return 0;
+}
+
+static int add_cmd(h2o_configurator_command_t *cmd, yoml_t *node, int cmd_id, h2o_iovec_t *name, h2o_iovec_t value,
+ h2o_headers_command_t **cmds)
+{
+ if (h2o_iovec_is_token(name)) {
+ const h2o_token_t *token = (void *)name;
+ if (h2o_headers_is_prohibited_name(token)) {
+ h2o_configurator_errprintf(cmd, node, "the named header cannot be rewritten");
+ return -1;
+ }
+ }
+
+ h2o_headers_append_command(cmds, cmd_id, name, value);
+ return 0;
+}
+
+static int on_config_header_2arg(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, int cmd_id, yoml_t *node,
+ h2o_headers_command_t **headers_cmds)
+{
+ h2o_iovec_t *name, value;
+
+ if (extract_name_value(node->data.scalar, &name, &value) != 0) {
+ h2o_configurator_errprintf(cmd, node, "failed to parse the value; should be in form of `name: value`");
+ return -1;
+ }
+ if (add_cmd(cmd, node, cmd_id, name, value, headers_cmds) != 0) {
+ if (!h2o_iovec_is_token(name))
+ free(name->base);
+ free(value.base);
+ return -1;
+ }
+ return 0;
+}
+
+static int on_config_header_unset(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ h2o_iovec_t *name;
+ struct headers_util_configurator_t *self = (void *)cmd->configurator;
+
+ if (extract_name(node->data.scalar, strlen(node->data.scalar), &name) != 0) {
+ h2o_configurator_errprintf(cmd, node, "invalid header name");
+ return -1;
+ }
+ if (add_cmd(cmd, node, H2O_HEADERS_CMD_UNSET, name, (h2o_iovec_t){NULL}, self->get_commands(self->child)) != 0) {
+ if (!h2o_iovec_is_token(name))
+ free(name->base);
+ return -1;
+ }
+ return 0;
+}
+
+#define DEFINE_2ARG(fn, cmd_id) \
+ static int fn(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) \
+ { \
+ struct headers_util_configurator_t *self = (void *)cmd->configurator; \
+ return on_config_header_2arg(cmd, ctx, cmd_id, node, self->get_commands(self->child)); \
+ }
+
+DEFINE_2ARG(on_config_header_add, H2O_HEADERS_CMD_ADD)
+DEFINE_2ARG(on_config_header_append, H2O_HEADERS_CMD_APPEND)
+DEFINE_2ARG(on_config_header_merge, H2O_HEADERS_CMD_MERGE)
+DEFINE_2ARG(on_config_header_set, H2O_HEADERS_CMD_SET)
+DEFINE_2ARG(on_config_header_setifempty, H2O_HEADERS_CMD_SETIFEMPTY)
+
+#undef DEFINE_2ARG
+
+void h2o_configurator_define_headers_commands(h2o_globalconf_t *global_conf, h2o_configurator_t *conf, const char *prefix,
+ h2o_configurator_get_headers_commands_cb get_commands)
+{
+ struct headers_util_configurator_t *c = (void *)h2o_configurator_create(global_conf, sizeof(*c));
+ c->child = conf;
+ c->get_commands = get_commands;
+ size_t prefix_len = strlen(prefix);
+
+#define DEFINE_CMD_NAME(name, suffix) \
+ char *name = h2o_mem_alloc(prefix_len + sizeof(suffix)); \
+ memcpy(name, prefix, prefix_len); \
+ memcpy(name + prefix_len, suffix, sizeof(suffix))
+
+ DEFINE_CMD_NAME(add_directive, ".add");
+ DEFINE_CMD_NAME(append_directive, ".append");
+ DEFINE_CMD_NAME(merge_directive, ".merge");
+ DEFINE_CMD_NAME(set_directive, ".set");
+ DEFINE_CMD_NAME(setifempty_directive, ".setifempty");
+ DEFINE_CMD_NAME(unset_directive, ".unset");
+#undef DEFINE_CMD_NAME
+
+#define DEFINE_CMD(name, cb) \
+ h2o_configurator_define_command(&c->super, name, H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, cb)
+ DEFINE_CMD(add_directive, on_config_header_add);
+ DEFINE_CMD(append_directive, on_config_header_append);
+ DEFINE_CMD(merge_directive, on_config_header_merge);
+ DEFINE_CMD(set_directive, on_config_header_set);
+ DEFINE_CMD(setifempty_directive, on_config_header_setifempty);
+ DEFINE_CMD(unset_directive, on_config_header_unset);
+#undef DEFINE_CMD
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/http2_debug_state.c b/debian/vendor-h2o/lib/handler/configurator/http2_debug_state.c
new file mode 100644
index 0000000..419da9d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/http2_debug_state.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Ichito Nagata
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+static int on_config_debug_state(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ switch (h2o_configurator_get_one_of(cmd, node, "minimum,hpack")) {
+ case 0: /* minimum */
+ h2o_http2_debug_state_register(ctx->hostconf, 0);
+ return 0;
+ case 1: /* with hpack state*/
+ h2o_http2_debug_state_register(ctx->hostconf, 1);
+ return 0;
+ default: /* error */
+ return -1;
+ }
+}
+
+void h2o_http2_debug_state_register_configurator(h2o_globalconf_t *conf)
+{
+ struct st_h2o_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ h2o_configurator_define_command(c, "http2-debug-state", H2O_CONFIGURATOR_FLAG_HOST | H2O_CONFIGURATOR_FLAG_DEFERRED |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_debug_state);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/mruby.c b/debian/vendor-h2o/lib/handler/configurator/mruby.c
new file mode 100644
index 0000000..1cef8f4
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/mruby.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku, Ryosuke Matsumoto
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+#include "h2o/mruby_.h"
+
+struct mruby_configurator_t {
+ h2o_configurator_t super;
+ h2o_mruby_config_vars_t *vars;
+ h2o_mruby_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+ mrb_state *mrb; /* will be lazily initialized */
+};
+
+static int compile_test(mrb_state *mrb, h2o_mruby_config_vars_t *config, char *errbuf)
+{
+ mrb_value result = h2o_mruby_compile_code(mrb, config, errbuf);
+ int ok = !mrb_nil_p(result);
+ return ok;
+}
+
+static mrb_state *get_mrb(struct mruby_configurator_t *self)
+{
+ if (self->mrb == NULL) {
+ self->mrb = mrb_open();
+ if (self->mrb == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ h2o_mruby_setup_globals(self->mrb);
+ }
+ return self->mrb;
+}
+
+static int on_config_mruby_handler(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct mruby_configurator_t *self = (void *)cmd->configurator;
+
+ /* set source */
+ self->vars->source = h2o_strdup(NULL, node->data.scalar, SIZE_MAX);
+ self->vars->path = node->filename;
+ self->vars->lineno = (int)node->line + 1;
+
+ /* check if there is any error in source */
+ char errbuf[1024];
+ if (!compile_test(get_mrb(self), self->vars, errbuf)) {
+ h2o_configurator_errprintf(cmd, node, "ruby compile error:%s", errbuf);
+ return -1;
+ }
+
+ /* register */
+ h2o_mruby_register(ctx->pathconf, self->vars);
+
+ return 0;
+}
+
+static int on_config_mruby_handler_file(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct mruby_configurator_t *self = (void *)cmd->configurator;
+ FILE *fp = NULL;
+ h2o_iovec_t buf = {NULL};
+ int ret = -1;
+
+ /* open and read file */
+ if ((fp = fopen(node->data.scalar, "rt")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "failed to open file: %s:%s", node->data.scalar, strerror(errno));
+ goto Exit;
+ }
+ while (!feof(fp)) {
+ buf.base = h2o_mem_realloc(buf.base, buf.len + 65536);
+ buf.len += fread(buf.base + buf.len, 1, 65536, fp);
+ if (ferror(fp)) {
+ h2o_configurator_errprintf(cmd, node, "I/O error occurred while reading file:%s:%s", node->data.scalar,
+ strerror(errno));
+ goto Exit;
+ }
+ }
+
+ /* set source */
+ self->vars->source = buf;
+ buf.base = NULL;
+ self->vars->path = node->data.scalar; /* the value is retained until the end of the configuration phase */
+ self->vars->lineno = 0;
+
+ /* check if there is any error in source */
+ char errbuf[1024];
+ if (!compile_test(get_mrb(self), self->vars, errbuf)) {
+ h2o_configurator_errprintf(cmd, node, "failed to compile file:%s:%s", node->data.scalar, errbuf);
+ goto Exit;
+ }
+
+ /* register */
+ h2o_mruby_register(ctx->pathconf, self->vars);
+
+ ret = 0;
+
+Exit:
+ if (fp != NULL)
+ fclose(fp);
+ if (buf.base != NULL)
+ free(buf.base);
+ return ret;
+}
+
+static int on_config_mruby_handler_path(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ h2o_configurator_errprintf(cmd, node, "the command has been removed; see https://github.com/h2o/h2o/pull/467");
+ return -1;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct mruby_configurator_t *self = (void *)_self;
+
+ memcpy(self->vars + 1, self->vars, sizeof(*self->vars));
+ ++self->vars;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct mruby_configurator_t *self = (void *)_self;
+
+ /* free if the to-be-exitted frame level contains a different source */
+ if (self->vars[-1].source.base != self->vars[0].source.base)
+ free(self->vars->source.base);
+
+ --self->vars;
+
+ /* release mrb only when global configuration exited */
+ if (self->mrb != NULL && ctx->parent == NULL) {
+ mrb_close(self->mrb);
+ self->mrb = NULL;
+ }
+
+ return 0;
+}
+
+void h2o_mruby_register_configurator(h2o_globalconf_t *conf)
+{
+ struct mruby_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ c->vars = c->_vars_stack;
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+
+ h2o_configurator_define_command(&c->super, "mruby.handler", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_DEFERRED |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_mruby_handler);
+ h2o_configurator_define_command(&c->super, "mruby.handler-file", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_DEFERRED |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_mruby_handler_file);
+ h2o_configurator_define_command(&c->super, "mruby.handler_path", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_DEFERRED,
+ on_config_mruby_handler_path);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/proxy.c b/debian/vendor-h2o/lib/handler/configurator/proxy.c
new file mode 100644
index 0000000..cfc9cbf
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/proxy.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <inttypes.h>
+#include <errno.h>
+#include <stdio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct proxy_configurator_t {
+ h2o_configurator_t super;
+ h2o_proxy_config_vars_t *vars;
+ h2o_proxy_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_timeout_io(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->io_timeout);
+}
+
+static int on_config_timeout_keepalive(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->keepalive_timeout);
+}
+
+static int on_config_preserve_host(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ self->vars->preserve_host = (int)ret;
+ return 0;
+}
+
+static int on_config_proxy_protocol(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ self->vars->use_proxy_protocol = (int)ret;
+ return 0;
+}
+
+static int on_config_websocket_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->websocket.timeout);
+}
+
+static int on_config_websocket(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ self->vars->websocket.enabled = (int)ret;
+ return 0;
+}
+
+static SSL_CTX *create_ssl_ctx(void)
+{
+ SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
+ SSL_CTX_set_options(ctx, SSL_CTX_get_options(ctx) | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ return ctx;
+}
+
+static h2o_cache_t *create_ssl_session_cache(size_t capacity, uint64_t duration)
+{
+ return h2o_cache_create(H2O_CACHE_FLAG_MULTITHREADED, capacity, duration, h2o_socket_ssl_destroy_session_cache_entry);
+}
+
+static void update_ssl_ctx(SSL_CTX **ctx, X509_STORE *cert_store, int verify_mode, h2o_cache_t **session_cache)
+{
+ assert(*ctx != NULL);
+
+ /* inherit the properties that weren't specified */
+ if (cert_store == NULL)
+ cert_store = SSL_CTX_get_cert_store(*ctx);
+ X509_STORE_up_ref(cert_store);
+ if (verify_mode == -1)
+ verify_mode = SSL_CTX_get_verify_mode(*ctx);
+ h2o_cache_t *new_session_cache;
+ if (session_cache == NULL) {
+ h2o_cache_t *current = h2o_socket_ssl_get_session_cache(*ctx);
+ new_session_cache =
+ current == NULL ? NULL : create_ssl_session_cache(h2o_cache_get_capacity(current), h2o_cache_get_duration(current));
+ } else {
+ new_session_cache = *session_cache;
+ }
+
+ /* free the existing context */
+ if (*ctx != NULL)
+ SSL_CTX_free(*ctx);
+
+ /* create new ctx */
+ *ctx = create_ssl_ctx();
+ SSL_CTX_set_cert_store(*ctx, cert_store);
+ SSL_CTX_set_verify(*ctx, verify_mode, NULL);
+ if (new_session_cache != NULL)
+ h2o_socket_ssl_set_session_cache(*ctx, new_session_cache);
+}
+
+static int on_config_ssl_verify_peer(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+
+ update_ssl_ctx(&self->vars->ssl_ctx, NULL, ret != 0 ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE,
+ NULL);
+
+ return 0;
+}
+
+static int on_config_ssl_cafile(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ X509_STORE *store = X509_STORE_new();
+ int ret = -1;
+
+ if (X509_STORE_load_locations(store, node->data.scalar, NULL) == 1) {
+ update_ssl_ctx(&self->vars->ssl_ctx, store, -1, NULL);
+ ret = 0;
+ } else {
+ h2o_configurator_errprintf(cmd, node, "failed to load certificates file:%s", node->data.scalar);
+ ERR_print_errors_fp(stderr);
+ }
+
+ X509_STORE_free(store);
+ return ret;
+}
+
+static int on_config_ssl_session_cache(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ size_t capacity = 0;
+ uint64_t duration = 0;
+ h2o_cache_t *current_cache = h2o_socket_ssl_get_session_cache(self->vars->ssl_ctx);
+
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ if (strcasecmp(node->data.scalar, "OFF") == 0) {
+ if (current_cache != NULL) {
+ /* set the cache NULL */
+ h2o_cache_t *empty_cache = NULL;
+ update_ssl_ctx(&self->vars->ssl_ctx, NULL, -1, &empty_cache);
+ }
+ return 0;
+ } else if (strcasecmp(node->data.scalar, "ON") == 0) {
+ /* use default values */
+ capacity = H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_CAPACITY;
+ duration = H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_DURATION;
+ } else {
+ h2o_configurator_errprintf(cmd, node, "scalar argument must be either of: `OFF`, `ON`");
+ return -1;
+ }
+ break;
+ case YOML_TYPE_MAPPING: {
+ size_t i;
+ for (i = 0; i != node->data.mapping.size; ++i) {
+ yoml_t *key = node->data.mapping.elements[i].key;
+ yoml_t *value = node->data.mapping.elements[i].value;
+ if (key->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, key, "key must be a scalar");
+ return -1;
+ }
+ if (strcasecmp(key->data.scalar, "capacity") == 0) {
+ if (h2o_configurator_scanf(cmd, value, "%zu", &capacity) != 0)
+ return -1;
+ if (capacity == 0) {
+ h2o_configurator_errprintf(cmd, key, "capacity must be greater than zero");
+ return -1;
+ }
+ } else if (strcasecmp(key->data.scalar, "lifetime") == 0) {
+ unsigned lifetime = 0;
+ if (h2o_configurator_scanf(cmd, value, "%u", &lifetime) != 0)
+ return -1;
+ if (lifetime == 0) {
+ h2o_configurator_errprintf(cmd, key, "lifetime must be greater than zero");
+ return -1;
+ }
+ duration = (uint64_t)lifetime * 1000;
+ } else {
+ h2o_configurator_errprintf(cmd, key, "key must be either of: `capacity`, `lifetime`");
+ return -1;
+ }
+ }
+ if (capacity == 0 || duration == 0) {
+ h2o_configurator_errprintf(cmd, node, "`capacity` and `lifetime` are required");
+ return -1;
+ }
+ } break;
+ default:
+ h2o_configurator_errprintf(cmd, node, "node must be a scalar or a mapping");
+ return -1;
+ }
+
+ if (current_cache != NULL) {
+ size_t current_capacity = h2o_cache_get_capacity(current_cache);
+ uint64_t current_duration = h2o_cache_get_duration(current_cache);
+ if (capacity == current_capacity && duration == current_duration) {
+ /* parameters aren't changed, so reuse it */
+ return 0;
+ }
+ }
+
+ h2o_cache_t *new_cache = create_ssl_session_cache(capacity, duration);
+ update_ssl_ctx(&self->vars->ssl_ctx, NULL, -1, &new_cache);
+ return 0;
+}
+
+static int on_config_reverse_url(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)cmd->configurator;
+ h2o_url_t parsed;
+
+ if (h2o_url_parse(node->data.scalar, SIZE_MAX, &parsed) != 0) {
+ h2o_configurator_errprintf(cmd, node, "failed to parse URL: %s\n", node->data.scalar);
+ return -1;
+ }
+ if (self->vars->keepalive_timeout != 0 && self->vars->use_proxy_protocol) {
+ h2o_configurator_errprintf(cmd, node, "please either set `proxy.use-proxy-protocol` to `OFF` or disable keep-alive by "
+ "setting `proxy.timeout.keepalive` to zero; the features are mutually exclusive");
+ return -1;
+ }
+
+ if (self->vars->headers_cmds != NULL)
+ h2o_mem_addref_shared(self->vars->headers_cmds);
+
+ /* register */
+ h2o_proxy_register_reverse_proxy(ctx->pathconf, &parsed, self->vars);
+
+ return 0;
+}
+
+static int on_config_emit_x_forwarded_headers(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ ctx->globalconf->proxy.emit_x_forwarded_headers = (int)ret;
+ return 0;
+}
+
+static int on_config_emit_via_header(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ ctx->globalconf->proxy.emit_via_header = (int)ret;
+ return 0;
+}
+
+static int on_config_preserve_x_forwarded_proto(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ ctx->globalconf->proxy.preserve_x_forwarded_proto = (int)ret;
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)_self;
+
+ memcpy(self->vars + 1, self->vars, sizeof(*self->vars));
+ if (self->vars[1].headers_cmds != NULL)
+ h2o_mem_addref_shared(self->vars[1].headers_cmds);
+ ++self->vars;
+
+ if (ctx->pathconf == NULL && ctx->hostconf == NULL) {
+ /* is global conf, setup the default SSL context */
+ self->vars->ssl_ctx = create_ssl_ctx();
+ char *ca_bundle = h2o_configurator_get_cmd_path("share/h2o/ca-bundle.crt");
+ if (SSL_CTX_load_verify_locations(self->vars->ssl_ctx, ca_bundle, NULL) != 1)
+ fprintf(stderr, "Warning: failed to load the default certificates file at %s. Proxying to HTTPS servers may fail.\n",
+ ca_bundle);
+ free(ca_bundle);
+ SSL_CTX_set_verify(self->vars->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+ h2o_cache_t *ssl_session_cache =
+ create_ssl_session_cache(H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_CAPACITY, H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_DURATION);
+ h2o_socket_ssl_set_session_cache(self->vars->ssl_ctx, ssl_session_cache);
+ } else {
+ SSL_CTX_up_ref(self->vars->ssl_ctx);
+ }
+
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct proxy_configurator_t *self = (void *)_self;
+
+ if (ctx->pathconf == NULL && ctx->hostconf == NULL) {
+ /* is global conf */
+ ctx->globalconf->proxy.io_timeout = self->vars->io_timeout;
+ ctx->globalconf->proxy.ssl_ctx = self->vars->ssl_ctx;
+ } else {
+ SSL_CTX_free(self->vars->ssl_ctx);
+ }
+
+ if (self->vars->headers_cmds != NULL)
+ h2o_mem_release_shared(self->vars->headers_cmds);
+
+ --self->vars;
+ return 0;
+}
+
+static h2o_headers_command_t **get_headers_commands(h2o_configurator_t *_self)
+{
+ struct proxy_configurator_t *self = (void *)_self;
+ return &self->vars->headers_cmds;
+}
+
+void h2o_proxy_register_configurator(h2o_globalconf_t *conf)
+{
+ struct proxy_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ /* set default vars */
+ c->vars = c->_vars_stack;
+ c->vars->io_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT;
+ c->vars->keepalive_timeout = 2000;
+ c->vars->websocket.enabled = 0; /* have websocket proxying disabled by default; until it becomes non-experimental */
+ c->vars->websocket.timeout = H2O_DEFAULT_PROXY_WEBSOCKET_TIMEOUT;
+
+ /* setup handlers */
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+ h2o_configurator_define_command(
+ &c->super, "proxy.reverse.url",
+ H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR | H2O_CONFIGURATOR_FLAG_DEFERRED, on_config_reverse_url);
+ h2o_configurator_define_command(&c->super, "proxy.preserve-host",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_preserve_host);
+ h2o_configurator_define_command(&c->super, "proxy.proxy-protocol",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_proxy_protocol);
+ h2o_configurator_define_command(&c->super, "proxy.timeout.io",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_timeout_io);
+ h2o_configurator_define_command(&c->super, "proxy.timeout.keepalive",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_timeout_keepalive);
+ h2o_configurator_define_command(&c->super, "proxy.websocket",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_websocket);
+ h2o_configurator_define_command(&c->super, "proxy.websocket.timeout",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_websocket_timeout);
+ h2o_configurator_define_command(&c->super, "proxy.ssl.verify-peer",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_ssl_verify_peer);
+ h2o_configurator_define_command(&c->super, "proxy.ssl.cafile",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_ssl_cafile);
+ h2o_configurator_define_command(&c->super, "proxy.ssl.session-cache", H2O_CONFIGURATOR_FLAG_ALL_LEVELS,
+ on_config_ssl_session_cache);
+ h2o_configurator_define_command(&c->super, "proxy.preserve-x-forwarded-proto",
+ H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_preserve_x_forwarded_proto);
+ h2o_configurator_define_command(&c->super, "proxy.emit-x-forwarded-headers",
+ H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_emit_x_forwarded_headers);
+ h2o_configurator_define_command(&c->super, "proxy.emit-via-header",
+ H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_emit_via_header);
+ h2o_configurator_define_headers_commands(conf, &c->super, "proxy.header", get_headers_commands);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/redirect.c b/debian/vendor-h2o/lib/handler/configurator/redirect.c
new file mode 100644
index 0000000..4ebbb99
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/redirect.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+static int on_config(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ const char *dest;
+ int status = 302; /* default is temporary redirect */
+ int internal = 0; /* default is external redirect */
+ yoml_t *t;
+
+ switch (node->type) {
+ case YOML_TYPE_SCALAR:
+ dest = node->data.scalar;
+ break;
+ case YOML_TYPE_MAPPING:
+ if ((t = yoml_get(node, "url")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "mandatory property `url` is missing");
+ return -1;
+ }
+ if (t->type != YOML_TYPE_SCALAR) {
+ h2o_configurator_errprintf(cmd, t, "property `url` must be a string");
+ return -1;
+ }
+ dest = t->data.scalar;
+ if ((t = yoml_get(node, "status")) == NULL) {
+ h2o_configurator_errprintf(cmd, node, "mandatory property `status` is missing");
+ return -1;
+ }
+ if (h2o_configurator_scanf(cmd, t, "%d", &status) != 0)
+ return -1;
+ if (!(300 <= status && status <= 399)) {
+ h2o_configurator_errprintf(cmd, t, "value of property `status` should be within 300 to 399");
+ return -1;
+ }
+ if ((t = yoml_get(node, "internal")) != NULL) {
+ if ((internal = (int)h2o_configurator_get_one_of(cmd, t, "NO,YES")) == -1)
+ return -1;
+ }
+ break;
+ default:
+ h2o_configurator_errprintf(cmd, node, "value must be a string or a mapping");
+ return -1;
+ }
+
+ h2o_redirect_register(ctx->pathconf, internal, status, dest);
+
+ return 0;
+}
+
+void h2o_redirect_register_configurator(h2o_globalconf_t *conf)
+{
+ h2o_configurator_t *c = h2o_configurator_create(conf, sizeof(*c));
+
+ h2o_configurator_define_command(c, "redirect", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_DEFERRED, on_config);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/reproxy.c b/debian/vendor-h2o/lib/handler/configurator/reproxy.c
new file mode 100644
index 0000000..21650e4
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/reproxy.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015 Daisuke Maki, DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <inttypes.h>
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct config_t {
+ int enabled;
+};
+
+struct reproxy_configurator_t {
+ h2o_configurator_t super;
+ struct config_t *vars, _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_reproxy(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct reproxy_configurator_t *self = (void *)cmd->configurator;
+
+ ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
+ if (ret == -1)
+ return -1;
+ self->vars->enabled = (int)ret;
+
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct reproxy_configurator_t *self = (void *)_self;
+
+ self->vars[1] = self->vars[0];
+ ++self->vars;
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct reproxy_configurator_t *self = (void *)_self;
+
+ if (ctx->pathconf != NULL && self->vars->enabled != 0)
+ h2o_reproxy_register(ctx->pathconf);
+
+ --self->vars;
+ return 0;
+}
+
+void h2o_reproxy_register_configurator(h2o_globalconf_t *conf)
+{
+ struct reproxy_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ /* set default vars */
+ c->vars = c->_vars_stack;
+
+ /* setup handlers */
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+
+ /* reproxy: ON | OFF */
+ h2o_configurator_define_command(&c->super, "reproxy", H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_reproxy);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/status.c b/debian/vendor-h2o/lib/handler/configurator/status.c
new file mode 100644
index 0000000..8645aa3
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/status.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+static int on_config_status(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ switch (h2o_configurator_get_one_of(cmd, node, "OFF,ON")) {
+ case 0: /* OFF */
+ return 0;
+ case 1: /* ON */
+ h2o_status_register(ctx->pathconf);
+ return 0;
+ default: /* error */
+ return -1;
+ }
+}
+
+struct st_status_configurator {
+ h2o_configurator_t super;
+ int stack;
+ int duration_stats;
+};
+
+static int on_config_duration_stats(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_status_configurator *c = (void *)cmd->configurator;
+ ssize_t ret;
+ switch (ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON")) {
+ case 0: /* OFF */
+ case 1: /* ON */
+ c->duration_stats = (int)ret;
+ return 0;
+ default: /* error */
+ return -1;
+ }
+}
+
+int on_enter_status(h2o_configurator_t *_conf, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_status_configurator *c = (void *)_conf;
+ c->stack++;
+ return 0;
+}
+
+int on_exit_status(h2o_configurator_t *_conf, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct st_status_configurator *c = (void *)_conf;
+ c->stack--;
+ if (!c->stack && c->duration_stats) {
+ h2o_duration_stats_register(ctx->globalconf);
+ }
+ return 0;
+}
+
+void h2o_status_register_configurator(h2o_globalconf_t *conf)
+{
+ struct st_status_configurator *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+ c->super.enter = on_enter_status;
+ c->super.exit = on_exit_status;
+
+ h2o_configurator_define_command(&c->super, "status", H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_DEFERRED |
+ H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_status);
+
+ h2o_configurator_define_command(&c->super, "duration-stats", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_duration_stats);
+}
diff --git a/debian/vendor-h2o/lib/handler/configurator/throttle_resp.c b/debian/vendor-h2o/lib/handler/configurator/throttle_resp.c
new file mode 100644
index 0000000..61431f7
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/configurator/throttle_resp.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016 Justin Zhu
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+#include "h2o/configurator.h"
+
+struct throttle_resp_config_vars_t {
+ int on;
+};
+
+struct throttle_resp_configurator_t {
+ h2o_configurator_t super;
+ struct throttle_resp_config_vars_t *vars, _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
+};
+
+static int on_config_throttle_resp(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct throttle_resp_configurator_t *self = (void *)cmd->configurator;
+
+ if ((self->vars->on = (int)h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1)
+ return -1;
+ return 0;
+}
+
+static int on_config_enter(h2o_configurator_t *configurator, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct throttle_resp_configurator_t *self = (void *)configurator;
+
+ ++self->vars;
+ self->vars[0] = self->vars[-1];
+ return 0;
+}
+
+static int on_config_exit(h2o_configurator_t *configurator, h2o_configurator_context_t *ctx, yoml_t *node)
+{
+ struct throttle_resp_configurator_t *self = (void *)configurator;
+
+ if (ctx->pathconf != NULL && self->vars->on)
+ h2o_throttle_resp_register(ctx->pathconf);
+
+ --self->vars;
+ return 0;
+}
+
+void h2o_throttle_resp_register_configurator(h2o_globalconf_t *conf)
+{
+ struct throttle_resp_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
+
+ c->super.enter = on_config_enter;
+ c->super.exit = on_config_exit;
+ h2o_configurator_define_command(&c->super, "throttle-response",
+ H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
+ on_config_throttle_resp);
+ c->vars = c->_vars_stack;
+}
diff --git a/debian/vendor-h2o/lib/handler/errordoc.c b/debian/vendor-h2o/lib/handler/errordoc.c
new file mode 100644
index 0000000..72744f3
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/errordoc.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+
+/* used to rewrite status code to the original code */
+struct st_errordoc_prefilter_t {
+ h2o_req_prefilter_t super;
+ h2o_headers_t req_headers;
+ int status;
+ const char *reason;
+ h2o_headers_t res_headers;
+};
+
+/* used to capture an error response */
+struct st_errordoc_filter_t {
+ h2o_filter_t super;
+ H2O_VECTOR(h2o_errordoc_t) errordocs;
+};
+
+static void add_header(h2o_mem_pool_t *pool, h2o_headers_t *headers, const h2o_header_t *header)
+{
+ h2o_vector_reserve(pool, headers, headers->size + 1);
+ headers->entries[headers->size++] = *header;
+}
+
+static void on_prefilter_setup_stream(h2o_req_prefilter_t *_self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ struct st_errordoc_prefilter_t *self = (void *)_self;
+ h2o_headers_t headers_merged = {NULL};
+ size_t i;
+
+ /* restore request headers (for logging) and response status */
+ req->headers = self->req_headers;
+ req->res.status = self->status;
+ req->res.reason = self->reason;
+
+ /* generate response headers (by merging the preserved and given) */
+ for (i = 0; i != self->res_headers.size; ++i)
+ add_header(&req->pool, &headers_merged, self->res_headers.entries + i);
+ for (i = 0; i != req->res.headers.size; ++i) {
+ const h2o_header_t *header = req->res.headers.entries + i;
+ if (header->name == &H2O_TOKEN_CONTENT_TYPE->buf || header->name == &H2O_TOKEN_CONTENT_LANGUAGE->buf ||
+ header->name == &H2O_TOKEN_SET_COOKIE->buf)
+ add_header(&req->pool, &headers_merged, header);
+ }
+ req->res.headers = headers_merged;
+
+ h2o_setup_next_prefilter(&self->super, req, slot);
+}
+
+static void on_ostream_send(h2o_ostream_t *self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ /* nothing to do */
+}
+
+static int prefilter_is_registered(h2o_req_t *req)
+{
+ h2o_req_prefilter_t *prefilter;
+ for (prefilter = req->prefilters; prefilter != NULL; prefilter = prefilter->next)
+ if (prefilter->on_setup_ostream == on_prefilter_setup_stream)
+ return 1;
+ return 0;
+}
+
+static void on_filter_setup_ostream(h2o_filter_t *_self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ struct st_errordoc_filter_t *self = (void *)_self;
+ h2o_errordoc_t *errordoc;
+ struct st_errordoc_prefilter_t *prefilter;
+ h2o_iovec_t method;
+ h2o_ostream_t *ostream;
+ size_t i;
+
+ if (req->res.status >= 400 && !prefilter_is_registered(req)) {
+ size_t i;
+ for (i = 0; i != self->errordocs.size; ++i) {
+ errordoc = self->errordocs.entries + i;
+ if (errordoc->status == req->res.status)
+ goto Found;
+ }
+ }
+
+ /* bypass to the next filter */
+ h2o_setup_next_ostream(req, slot);
+ return;
+
+Found:
+ /* register prefilter that rewrites the status code after the internal redirect is processed */
+ prefilter = (void *)h2o_add_prefilter(req, sizeof(*prefilter));
+ prefilter->super.on_setup_ostream = on_prefilter_setup_stream;
+ prefilter->req_headers = req->headers;
+ prefilter->status = req->res.status;
+ prefilter->reason = req->res.reason;
+ prefilter->res_headers = (h2o_headers_t){NULL};
+ for (i = 0; i != req->res.headers.size; ++i) {
+ const h2o_header_t *header = req->res.headers.entries + i;
+ if (!(header->name == &H2O_TOKEN_CONTENT_TYPE->buf || header->name == &H2O_TOKEN_CONTENT_LANGUAGE->buf))
+ add_header(&req->pool, &prefilter->res_headers, header);
+ }
+ /* redirect internally to the error document */
+ method = req->method;
+ if (h2o_memis(method.base, method.len, H2O_STRLIT("POST")))
+ method = h2o_iovec_init(H2O_STRLIT("GET"));
+ req->headers = (h2o_headers_t){NULL};
+ req->res.headers = (h2o_headers_t){NULL};
+ h2o_send_redirect_internal(req, method, errordoc->url.base, errordoc->url.len, 0);
+ /* create fake ostream that swallows the contents emitted by the generator */
+ ostream = h2o_add_ostream(req, sizeof(*ostream), slot);
+ ostream->do_send = on_ostream_send;
+}
+
+void h2o_errordoc_register(h2o_pathconf_t *pathconf, h2o_errordoc_t *errdocs, size_t cnt)
+{
+ struct st_errordoc_filter_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self));
+ size_t i;
+
+ self->super.on_setup_ostream = on_filter_setup_ostream;
+ h2o_vector_reserve(NULL, &self->errordocs, cnt);
+ self->errordocs.size = cnt;
+ for (i = 0; i != cnt; ++i) {
+ const h2o_errordoc_t *src = errdocs + i;
+ h2o_errordoc_t *dst = self->errordocs.entries + i;
+ dst->status = src->status;
+ dst->url = h2o_strdup(NULL, src->url.base, src->url.len);
+ }
+}
diff --git a/debian/vendor-h2o/lib/handler/expires.c b/debian/vendor-h2o/lib/handler/expires.c
new file mode 100644
index 0000000..c772256
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/expires.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "h2o.h"
+
+struct st_expires_t {
+ h2o_filter_t super;
+ int mode;
+ h2o_iovec_t value;
+};
+
+static void on_setup_ostream(h2o_filter_t *_self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ struct st_expires_t *self = (void *)_self;
+
+ switch (req->res.status) {
+ case 200:
+ case 201:
+ case 204:
+ case 206:
+ case 301:
+ case 302:
+ case 303:
+ case 304:
+ case 307:
+ switch (self->mode) {
+ case H2O_EXPIRES_MODE_ABSOLUTE:
+ h2o_set_header(&req->pool, &req->res.headers, H2O_TOKEN_EXPIRES, self->value.base, self->value.len, 0);
+ break;
+ case H2O_EXPIRES_MODE_MAX_AGE:
+ h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, self->value.base, self->value.len);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ h2o_setup_next_ostream(req, slot);
+}
+
+void h2o_expires_register(h2o_pathconf_t *pathconf, h2o_expires_args_t *args)
+{
+ struct st_expires_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self));
+ self->super.on_setup_ostream = on_setup_ostream;
+ self->mode = args->mode;
+ switch (args->mode) {
+ case H2O_EXPIRES_MODE_ABSOLUTE:
+ self->value = h2o_strdup(NULL, args->data.absolute, SIZE_MAX);
+ break;
+ case H2O_EXPIRES_MODE_MAX_AGE:
+ self->value.base = h2o_mem_alloc(128);
+ self->value.len = sprintf(self->value.base, "max-age=%" PRIu64, args->data.max_age);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
diff --git a/debian/vendor-h2o/lib/handler/fastcgi.c b/debian/vendor-h2o/lib/handler/fastcgi.c
new file mode 100644
index 0000000..d6bcf9e
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/fastcgi.c
@@ -0,0 +1,856 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd. Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <inttypes.h>
+#include <stdio.h>
+#include "picohttpparser.h"
+#include "h2o.h"
+
+#define FCGI_VERSION_1 1
+
+#define FCGI_RESPONDER 1
+#define FCGI_KEEP_CONN 1
+
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+
+#define FCGI_RECORD_HEADER_SIZE (sizeof(struct st_fcgi_record_header_t))
+#define FCGI_BEGIN_REQUEST_BODY_SIZE 8
+
+#define MODULE_NAME "lib/handler/fastcgi.c"
+
+#define APPEND_BLOCKSIZE 512 /* the size should be small enough to be allocated within the buffer of the memory pool */
+
+struct st_fcgi_record_header_t {
+ uint8_t version;
+ uint8_t type;
+ uint16_t requestId;
+ uint16_t contentLength;
+ uint8_t paddingLength;
+ uint8_t reserved;
+};
+
+struct st_fcgi_begin_request_body_t {
+ uint16_t role;
+ uint8_t flags;
+ uint8_t reserved[5];
+};
+
+typedef H2O_VECTOR(h2o_iovec_t) iovec_vector_t;
+
+struct st_fcgi_context_t {
+ h2o_fastcgi_handler_t *handler;
+ h2o_timeout_t io_timeout;
+};
+
+struct st_fcgi_generator_t {
+ h2o_generator_t super;
+ struct st_fcgi_context_t *ctx;
+ h2o_req_t *req;
+ h2o_socketpool_connect_request_t *connect_req;
+ h2o_socket_t *sock;
+ int sent_headers;
+ size_t leftsize; /* remaining amount of the content to receive (or SIZE_MAX if unknown) */
+ struct {
+ h2o_doublebuffer_t sending;
+ h2o_buffer_t *receiving;
+ } resp;
+ h2o_timeout_entry_t timeout;
+};
+
+struct st_h2o_fastcgi_handler_t {
+ h2o_handler_t super;
+ h2o_socketpool_t sockpool;
+ h2o_fastcgi_config_vars_t config;
+};
+
+static void encode_uint16(void *_p, unsigned v)
+{
+ unsigned char *p = _p;
+ p[0] = (unsigned char)(v >> 8);
+ p[1] = (unsigned char)v;
+}
+
+static void encode_record_header(void *p, uint8_t type, uint16_t reqId, uint16_t sz)
+{
+ struct st_fcgi_record_header_t *header = p;
+ header->version = FCGI_VERSION_1;
+ header->type = type;
+ encode_uint16(&header->requestId, reqId);
+ encode_uint16(&header->contentLength, sz);
+ header->paddingLength = 0;
+ header->reserved = 0;
+}
+
+static void encode_begin_request(void *p, uint16_t reqId, uint16_t role, uint8_t flags)
+{
+ encode_record_header(p, FCGI_BEGIN_REQUEST, reqId, FCGI_BEGIN_REQUEST_BODY_SIZE);
+ struct st_fcgi_begin_request_body_t *body = (void *)((char *)p + FCGI_RECORD_HEADER_SIZE);
+ encode_uint16(&body->role, role);
+ body->flags = flags;
+ memset(body->reserved, 0, sizeof(body->reserved));
+}
+
+static h2o_iovec_t create_begin_request(h2o_mem_pool_t *pool, uint16_t reqId, uint16_t role, uint8_t flags)
+{
+ h2o_iovec_t rec = h2o_iovec_init(h2o_mem_alloc_pool(pool, FCGI_RECORD_HEADER_SIZE + FCGI_BEGIN_REQUEST_BODY_SIZE),
+ FCGI_RECORD_HEADER_SIZE + FCGI_BEGIN_REQUEST_BODY_SIZE);
+ encode_begin_request(rec.base, reqId, role, flags);
+ return rec;
+}
+
+static h2o_iovec_t create_header(h2o_mem_pool_t *pool, uint8_t type, uint16_t reqId, uint16_t sz)
+{
+ h2o_iovec_t rec = h2o_iovec_init(h2o_mem_alloc_pool(pool, FCGI_RECORD_HEADER_SIZE), FCGI_RECORD_HEADER_SIZE);
+ encode_record_header(rec.base, type, reqId, sz);
+ return rec;
+}
+
+static void decode_header(struct st_fcgi_record_header_t *decoded, const void *s)
+{
+ memcpy(decoded, s, sizeof(*decoded));
+ decoded->requestId = htons(decoded->requestId);
+ decoded->contentLength = htons(decoded->contentLength);
+}
+
+static void *append(h2o_mem_pool_t *pool, iovec_vector_t *blocks, const void *s, size_t len)
+{
+ h2o_iovec_t *slot;
+
+ if (blocks->entries[blocks->size - 1].len + len > APPEND_BLOCKSIZE) {
+ h2o_vector_reserve(pool, blocks, blocks->size + 1);
+ slot = blocks->entries + blocks->size++;
+ slot->base = h2o_mem_alloc_pool(pool, len < APPEND_BLOCKSIZE ? APPEND_BLOCKSIZE : len);
+ slot->len = 0;
+ } else {
+ slot = blocks->entries + blocks->size - 1;
+ }
+
+ if (s != NULL)
+ memcpy(slot->base + slot->len, s, len);
+ slot->len += len;
+
+ return slot->base + slot->len - len;
+}
+
+static char *encode_length_of_pair(char *p, size_t len)
+{
+ if (len < 127) {
+ *p++ = (char)len;
+ } else {
+ *p++ = (unsigned char)(len >> 24) | 0x80;
+ *p++ = (unsigned char)(len >> 16);
+ *p++ = (unsigned char)(len >> 8);
+ *p++ = (unsigned char)len;
+ }
+ return p;
+}
+
+static void *append_pair(h2o_mem_pool_t *pool, iovec_vector_t *blocks, const char *name, size_t namelen, const char *value,
+ size_t valuelen)
+{
+ char lenbuf[8];
+ void *name_buf;
+
+ append(pool, blocks, lenbuf, encode_length_of_pair(encode_length_of_pair(lenbuf, namelen), valuelen) - lenbuf);
+ name_buf = append(pool, blocks, name, namelen);
+ if (valuelen != 0)
+ append(pool, blocks, value, valuelen);
+
+ return name_buf;
+}
+
+static void append_address_info(h2o_req_t *req, iovec_vector_t *vecs, const char *addrlabel, size_t addrlabel_len,
+ const char *portlabel, size_t portlabel_len, socklen_t (*cb)(h2o_conn_t *conn, struct sockaddr *))
+{
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+ char buf[NI_MAXHOST];
+
+ if ((sslen = cb(req->conn, (void *)&ss)) == 0)
+ return;
+
+ size_t l = h2o_socket_getnumerichost((void *)&ss, sslen, buf);
+ if (l != SIZE_MAX)
+ append_pair(&req->pool, vecs, addrlabel, addrlabel_len, buf, l);
+ int32_t port = h2o_socket_getport((void *)&ss);
+ if (port != -1) {
+ char buf[6];
+ int l = sprintf(buf, "%" PRIu16, (uint16_t)port);
+ append_pair(&req->pool, vecs, portlabel, portlabel_len, buf, (size_t)l);
+ }
+}
+
+static int envname_is_headername(const h2o_iovec_t *env, const h2o_iovec_t *header)
+{
+ const char *ep, *hp, *hend;
+
+ if (env->len != 5 + header->len)
+ return 0;
+ if (memcmp(env->base, "HTTP_", 5) != 0)
+ return 0;
+ for (ep = env->base + 5, hp = header->base, hend = hp + header->len; hp < hend; ++ep, ++hp)
+ if (*ep != h2o_toupper(*hp))
+ return 0;
+ return 1;
+}
+
+static void append_params(h2o_req_t *req, iovec_vector_t *vecs, h2o_fastcgi_config_vars_t *config)
+{
+ h2o_iovec_t path_info = {NULL};
+
+ /* CONTENT_LENGTH */
+ if (req->entity.base != NULL) {
+ char buf[32];
+ int l = sprintf(buf, "%zu", req->entity.len);
+ append_pair(&req->pool, vecs, H2O_STRLIT("CONTENT_LENGTH"), buf, (size_t)l);
+ }
+ /* SCRIPT_FILENAME, SCRIPT_NAME, PATH_INFO */
+ if (req->filereq != NULL) {
+ h2o_filereq_t *filereq = req->filereq;
+ append_pair(&req->pool, vecs, H2O_STRLIT("SCRIPT_FILENAME"), filereq->local_path.base, filereq->local_path.len);
+ append_pair(&req->pool, vecs, H2O_STRLIT("SCRIPT_NAME"), filereq->script_name.base, filereq->script_name.len);
+ path_info = filereq->path_info;
+ } else {
+ append_pair(&req->pool, vecs, H2O_STRLIT("SCRIPT_NAME"), NULL, 0);
+ path_info = req->path_normalized;
+ }
+ if (path_info.base != NULL)
+ append_pair(&req->pool, vecs, H2O_STRLIT("PATH_INFO"), path_info.base, path_info.len);
+ /* DOCUMENT_ROOT and PATH_TRANSLATED */
+ if (config->document_root.base != NULL) {
+ append_pair(&req->pool, vecs, H2O_STRLIT("DOCUMENT_ROOT"), config->document_root.base, config->document_root.len);
+ if (path_info.base != NULL) {
+ append_pair(&req->pool, vecs, H2O_STRLIT("PATH_TRANSLATED"), NULL, config->document_root.len + path_info.len);
+ char *dst_end = vecs->entries[vecs->size - 1].base + vecs->entries[vecs->size - 1].len;
+ memcpy(dst_end - path_info.len, path_info.base, path_info.len);
+ memcpy(dst_end - path_info.len - config->document_root.len, config->document_root.base, config->document_root.len);
+ }
+ }
+ /* QUERY_STRING (and adjust PATH_INFO) */
+ if (req->query_at != SIZE_MAX) {
+ append_pair(&req->pool, vecs, H2O_STRLIT("QUERY_STRING"), req->path.base + req->query_at + 1,
+ req->path.len - (req->query_at + 1));
+ } else {
+ append_pair(&req->pool, vecs, H2O_STRLIT("QUERY_STRING"), NULL, 0);
+ }
+ /* REMOTE_ADDR & REMOTE_PORT */
+ append_address_info(req, vecs, H2O_STRLIT("REMOTE_ADDR"), H2O_STRLIT("REMOTE_PORT"), req->conn->callbacks->get_peername);
+ { /* environment variables (REMOTE_USER, etc.) */
+ size_t i;
+ for (i = 0; i != req->env.size; i += 2) {
+ h2o_iovec_t *name = req->env.entries + i, *value = name + 1;
+ append_pair(&req->pool, vecs, name->base, name->len, value->base, value->len);
+ }
+ }
+ /* REQUEST_METHOD */
+ append_pair(&req->pool, vecs, H2O_STRLIT("REQUEST_METHOD"), req->method.base, req->method.len);
+ /* HTTP_HOST & REQUEST_URI */
+ if (config->send_delegated_uri) {
+ append_pair(&req->pool, vecs, H2O_STRLIT("HTTP_HOST"), req->authority.base, req->authority.len);
+ append_pair(&req->pool, vecs, H2O_STRLIT("REQUEST_URI"), req->path.base, req->path.len);
+ } else {
+ append_pair(&req->pool, vecs, H2O_STRLIT("HTTP_HOST"), req->input.authority.base, req->input.authority.len);
+ append_pair(&req->pool, vecs, H2O_STRLIT("REQUEST_URI"), req->input.path.base, req->input.path.len);
+ }
+ /* SERVER_ADDR & SERVER_PORT */
+ append_address_info(req, vecs, H2O_STRLIT("SERVER_ADDR"), H2O_STRLIT("SERVER_PORT"), req->conn->callbacks->get_sockname);
+ /* SERVER_NAME */
+ append_pair(&req->pool, vecs, H2O_STRLIT("SERVER_NAME"), req->hostconf->authority.host.base, req->hostconf->authority.host.len);
+ { /* SERVER_PROTOCOL */
+ char buf[sizeof("HTTP/1.1")];
+ size_t l = h2o_stringify_protocol_version(buf, req->version);
+ append_pair(&req->pool, vecs, H2O_STRLIT("SERVER_PROTOCOL"), buf, l);
+ }
+ /* SERVER_SOFTWARE */
+ append_pair(&req->pool, vecs, H2O_STRLIT("SERVER_SOFTWARE"), req->conn->ctx->globalconf->server_name.base,
+ req->conn->ctx->globalconf->server_name.len);
+ /* set HTTPS: on if necessary */
+ if (req->scheme == &H2O_URL_SCHEME_HTTPS)
+ append_pair(&req->pool, vecs, H2O_STRLIT("HTTPS"), H2O_STRLIT("on"));
+ { /* headers */
+ const h2o_header_t *h = req->headers.entries, *h_end = h + req->headers.size;
+ size_t cookie_length = 0;
+ for (; h != h_end; ++h) {
+ if (h->name == &H2O_TOKEN_CONTENT_TYPE->buf) {
+ append_pair(&req->pool, vecs, H2O_STRLIT("CONTENT_TYPE"), h->value.base, h->value.len);
+ } else if (h->name == &H2O_TOKEN_COOKIE->buf) {
+ /* accumulate the length of the cookie, together with the separator */
+ cookie_length += h->value.len + 1;
+ } else {
+ size_t i;
+ for (i = 0; i != req->env.size; i += 2) {
+ h2o_iovec_t *envname = req->env.entries + i;
+ if (envname_is_headername(envname, h->name))
+ goto NextHeader;
+ }
+ char *dst = append_pair(&req->pool, vecs, NULL, h->name->len + sizeof("HTTP_") - 1, h->value.base, h->value.len);
+ const char *src = h->name->base, *src_end = src + h->name->len;
+ *dst++ = 'H';
+ *dst++ = 'T';
+ *dst++ = 'T';
+ *dst++ = 'P';
+ *dst++ = '_';
+ for (; src != src_end; ++src)
+ *dst++ = *src == '-' ? '_' : h2o_toupper(*src);
+ }
+ NextHeader:;
+ }
+ if (cookie_length != 0) {
+ /* emit the cookie merged */
+ cookie_length -= 1;
+ append_pair(&req->pool, vecs, H2O_STRLIT("HTTP_COOKIE"), NULL, cookie_length);
+ char *dst = vecs->entries[vecs->size - 1].base + vecs->entries[vecs->size - 1].len - cookie_length;
+ for (h = req->headers.entries;; ++h) {
+ if (h->name == &H2O_TOKEN_COOKIE->buf) {
+ if (cookie_length == h->value.len)
+ break;
+ memcpy(dst, h->value.base, h->value.len);
+ dst += h->value.len;
+ *dst++ = ';';
+ cookie_length -= h->value.len + 1;
+ }
+ }
+ memcpy(dst, h->value.base, h->value.len);
+ }
+ }
+}
+
+static void annotate_params(h2o_mem_pool_t *pool, iovec_vector_t *vecs, unsigned request_id, size_t max_record_size)
+{
+ size_t index = 2, recsize = 0, header_slot = 1;
+
+ while (index != vecs->size) {
+ if (recsize + vecs->entries[index].len < max_record_size) {
+ recsize += vecs->entries[index].len;
+ ++index;
+ } else {
+ vecs->entries[header_slot] = create_header(pool, FCGI_PARAMS, request_id, max_record_size);
+ if (recsize + vecs->entries[index].len == max_record_size) {
+ h2o_vector_reserve(pool, vecs, vecs->size + 1);
+ memmove(vecs->entries + index + 2, vecs->entries + index + 1,
+ (vecs->size - (index + 1)) * sizeof(vecs->entries[0]));
+ ++vecs->size;
+ } else {
+ h2o_vector_reserve(pool, vecs, vecs->size + 2);
+ memmove(vecs->entries + index + 2, vecs->entries + index, (vecs->size - index) * sizeof(vecs->entries[0]));
+ vecs->size += 2;
+ size_t lastsz = max_record_size - recsize;
+ vecs->entries[index].len = lastsz;
+ vecs->entries[index + 2].base += lastsz;
+ vecs->entries[index + 2].len -= lastsz;
+ }
+ header_slot = index + 1;
+ index += 2;
+ recsize = 0;
+ }
+ }
+
+ vecs->entries[header_slot] = create_header(pool, FCGI_PARAMS, request_id, recsize);
+ if (recsize != 0) {
+ h2o_vector_reserve(pool, vecs, vecs->size + 1);
+ vecs->entries[vecs->size++] = create_header(pool, FCGI_PARAMS, request_id, 0);
+ }
+}
+
+static void build_request(h2o_req_t *req, iovec_vector_t *vecs, unsigned request_id, size_t max_record_size,
+ h2o_fastcgi_config_vars_t *config)
+{
+ *vecs = (iovec_vector_t){NULL};
+
+ /* first entry is FCGI_BEGIN_REQUEST */
+ h2o_vector_reserve(&req->pool, vecs, 5 /* we send at least 5 iovecs */);
+ vecs->entries[0] =
+ create_begin_request(&req->pool, request_id, FCGI_RESPONDER, config->keepalive_timeout != 0 ? FCGI_KEEP_CONN : 0);
+ /* second entry is reserved for FCGI_PARAMS header */
+ vecs->entries[1] = h2o_iovec_init(NULL, APPEND_BLOCKSIZE); /* dummy value set to prevent params being appended to the entry */
+ vecs->size = 2;
+ /* accumulate the params data, and annotate them with FCGI_PARAM headers */
+ append_params(req, vecs, config);
+ annotate_params(&req->pool, vecs, request_id, max_record_size);
+ /* setup FCGI_STDIN headers */
+ if (req->entity.len != 0) {
+ size_t off = 0;
+ for (; off + max_record_size < req->entity.len; off += max_record_size) {
+ h2o_vector_reserve(&req->pool, vecs, vecs->size + 2);
+ vecs->entries[vecs->size++] = create_header(&req->pool, FCGI_STDIN, request_id, max_record_size);
+ vecs->entries[vecs->size++] = h2o_iovec_init(req->entity.base + off, max_record_size);
+ }
+ if (off != req->entity.len) {
+ h2o_vector_reserve(&req->pool, vecs, vecs->size + 2);
+ vecs->entries[vecs->size++] = create_header(&req->pool, FCGI_STDIN, request_id, req->entity.len - off);
+ vecs->entries[vecs->size++] = h2o_iovec_init(req->entity.base + off, req->entity.len - off);
+ }
+ }
+ h2o_vector_reserve(&req->pool, vecs, vecs->size + 1);
+ vecs->entries[vecs->size++] = create_header(&req->pool, FCGI_STDIN, request_id, 0);
+}
+
+static void set_timeout(struct st_fcgi_generator_t *generator, h2o_timeout_t *timeout, h2o_timeout_cb cb)
+{
+ if (h2o_timeout_is_linked(&generator->timeout))
+ h2o_timeout_unlink(&generator->timeout);
+
+ generator->timeout.cb = cb;
+ h2o_timeout_link(generator->req->conn->ctx->loop, timeout, &generator->timeout);
+}
+
+static void close_generator(struct st_fcgi_generator_t *generator)
+{
+ /* can be called more than once */
+
+ if (h2o_timeout_is_linked(&generator->timeout))
+ h2o_timeout_unlink(&generator->timeout);
+ if (generator->connect_req != NULL) {
+ h2o_socketpool_cancel_connect(generator->connect_req);
+ generator->connect_req = NULL;
+ }
+ if (generator->sock != NULL) {
+ h2o_socket_close(generator->sock);
+ generator->sock = NULL;
+ }
+ if (generator->resp.sending.buf != NULL)
+ h2o_doublebuffer_dispose(&generator->resp.sending);
+ if (generator->resp.receiving != NULL)
+ h2o_buffer_dispose(&generator->resp.receiving);
+}
+
+static void do_send(struct st_fcgi_generator_t *generator)
+{
+ h2o_iovec_t vecs[1];
+ size_t veccnt;
+ int is_final;
+
+ vecs[0] = h2o_doublebuffer_prepare(&generator->resp.sending, &generator->resp.receiving, generator->req->preferred_chunk_size);
+ veccnt = vecs[0].len != 0 ? 1 : 0;
+ if (generator->sock == NULL && vecs[0].len == generator->resp.sending.buf->size && generator->resp.receiving->size == 0) {
+ is_final = 1;
+ if (!(generator->leftsize == 0 || generator->leftsize == SIZE_MAX))
+ generator->req->http1_is_persistent = 0;
+ } else {
+ if (veccnt == 0)
+ return;
+ is_final = 0;
+ }
+ h2o_send(generator->req, vecs, veccnt, is_final ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS);
+}
+
+static void send_eos_and_close(struct st_fcgi_generator_t *generator, int can_keepalive)
+{
+ if (generator->ctx->handler->config.keepalive_timeout != 0 && can_keepalive)
+ h2o_socketpool_return(&generator->ctx->handler->sockpool, generator->sock);
+ else
+ h2o_socket_close(generator->sock);
+ generator->sock = NULL;
+
+ if (h2o_timeout_is_linked(&generator->timeout))
+ h2o_timeout_unlink(&generator->timeout);
+
+ if (generator->resp.sending.bytes_inflight == 0)
+ do_send(generator);
+}
+
+static void errorclose(struct st_fcgi_generator_t *generator)
+{
+ if (generator->sent_headers) {
+ send_eos_and_close(generator, 0);
+ } else {
+ h2o_req_t *req = generator->req;
+ close_generator(generator);
+ h2o_send_error_503(req, "Internal Server Error", "Internal Server Error", 0);
+ }
+}
+
+static int _isdigit(int ch)
+{
+ return '0' <= ch && ch <= '9';
+}
+
+static int fill_headers(h2o_req_t *req, struct phr_header *headers, size_t num_headers)
+{
+ size_t i;
+
+ /* set the defaults */
+ req->res.status = 200;
+ req->res.reason = "OK";
+ req->res.content_length = SIZE_MAX;
+
+ for (i = 0; i != num_headers; ++i) {
+ const h2o_token_t *token;
+ h2o_strtolower((char *)headers[i].name, headers[i].name_len);
+ if ((token = h2o_lookup_token(headers[i].name, headers[i].name_len)) != NULL) {
+ if (token->proxy_should_drop_for_res) {
+ /* skip */
+ } else if (token == H2O_TOKEN_CONTENT_LENGTH) {
+ if (req->res.content_length != SIZE_MAX) {
+ h2o_req_log_error(req, MODULE_NAME, "received multiple content-length headers from fcgi");
+ return -1;
+ }
+ if ((req->res.content_length = h2o_strtosize(headers[i].value, headers[i].value_len)) == SIZE_MAX) {
+ h2o_req_log_error(req, MODULE_NAME, "failed to parse content-length header sent from fcgi: %.*s",
+ (int)headers[i].value_len, headers[i].value);
+ return -1;
+ }
+ } else {
+ /*
+ RFC 3875 defines three headers to have special meaning: Content-Type, Status, Location.
+ Status is handled as below.
+ Content-Type does not seem to have any need to be handled specially.
+ RFC suggests abs-path-style Location headers should trigger an internal redirection, but is that how the web servers
+ work?
+ */
+ h2o_add_header(&req->pool, &req->res.headers, token, NULL,
+ h2o_strdup(&req->pool, headers[i].value, headers[i].value_len).base, headers[i].value_len);
+ if (token == H2O_TOKEN_LINK)
+ h2o_push_path_in_link_header(req, headers[i].value, headers[i].value_len);
+ }
+ } else if (h2o_memis(headers[i].name, headers[i].name_len, H2O_STRLIT("status"))) {
+ h2o_iovec_t value = h2o_iovec_init(headers[i].value, headers[i].value_len);
+ if (value.len < 3 || !(_isdigit(value.base[0]) && _isdigit(value.base[1]) && _isdigit(value.base[2])) ||
+ (value.len >= 4 && value.base[3] != ' ')) {
+ h2o_req_log_error(req, MODULE_NAME, "failed to parse Status header, got: %.*s", (int)value.len, value.base);
+ return -1;
+ }
+ req->res.status = (value.base[0] - '0') * 100 + (value.base[1] - '0') * 10 + (value.base[2] - '0');
+ req->res.reason = value.len >= 5 ? h2o_strdup(&req->pool, value.base + 4, value.len - 4).base : "OK";
+ } else {
+ h2o_iovec_t name_duped = h2o_strdup(&req->pool, headers[i].name, headers[i].name_len),
+ value_duped = h2o_strdup(&req->pool, headers[i].value, headers[i].value_len);
+ h2o_add_header_by_str(&req->pool, &req->res.headers, name_duped.base, name_duped.len, 0, NULL, value_duped.base,
+ value_duped.len);
+ }
+ }
+
+ return 0;
+}
+
+static void append_content(struct st_fcgi_generator_t *generator, const void *src, size_t len)
+{
+ /* do not accumulate more than content-length bytes */
+ if (generator->leftsize != SIZE_MAX) {
+ if (generator->leftsize < len) {
+ len = generator->leftsize;
+ if (len == 0)
+ return;
+ }
+ generator->leftsize -= len;
+ }
+
+ h2o_iovec_t reserved = h2o_buffer_reserve(&generator->resp.receiving, len);
+ memcpy(reserved.base, src, len);
+ generator->resp.receiving->size += len;
+}
+
+static int handle_stdin_record(struct st_fcgi_generator_t *generator, struct st_fcgi_record_header_t *header)
+{
+ h2o_buffer_t *input = generator->sock->input;
+ struct phr_header headers[100];
+ size_t num_headers;
+ int parse_result;
+
+ if (header->contentLength == 0)
+ return 0;
+
+ if (generator->sent_headers) {
+ /* simply accumulate the data to response buffer */
+ append_content(generator, input->bytes + FCGI_RECORD_HEADER_SIZE, header->contentLength);
+ return 0;
+ }
+
+ /* parse the headers using the input buffer (or keep it in response buffer and parse) */
+ num_headers = sizeof(headers) / sizeof(headers[0]);
+ if (generator->resp.receiving->size == 0) {
+ parse_result = phr_parse_headers(input->bytes + FCGI_RECORD_HEADER_SIZE, header->contentLength, headers, &num_headers, 0);
+ } else {
+ size_t prevlen = generator->resp.receiving->size;
+ memcpy(h2o_buffer_reserve(&generator->resp.receiving, header->contentLength).base, input->bytes + FCGI_RECORD_HEADER_SIZE,
+ header->contentLength);
+ generator->resp.receiving->size = prevlen + header->contentLength;
+ parse_result =
+ phr_parse_headers(generator->resp.receiving->bytes, generator->resp.receiving->size, headers, &num_headers, prevlen);
+ }
+ if (parse_result < 0) {
+ if (parse_result == -2) {
+ /* incomplete */
+ if (generator->resp.receiving->size == 0) {
+ memcpy(h2o_buffer_reserve(&generator->resp.receiving, header->contentLength).base,
+ input->bytes + FCGI_RECORD_HEADER_SIZE, header->contentLength);
+ generator->resp.receiving->size = header->contentLength;
+ }
+ return 0;
+ } else {
+ h2o_req_log_error(generator->req, MODULE_NAME, "received broken response");
+ return -1;
+ }
+ }
+
+ /* fill-in the headers, and start the response */
+ if (fill_headers(generator->req, headers, num_headers) != 0)
+ return -1;
+ generator->leftsize = generator->req->res.content_length;
+ h2o_start_response(generator->req, &generator->super);
+ generator->sent_headers = 1;
+
+ /* rest of the contents should be stored in the response buffer */
+ if (generator->resp.receiving->size == 0) {
+ size_t leftlen = header->contentLength - parse_result;
+ if (leftlen != 0) {
+ append_content(generator, input->bytes + FCGI_RECORD_HEADER_SIZE + parse_result, leftlen);
+ }
+ } else {
+ h2o_buffer_consume(&generator->resp.receiving, parse_result);
+ }
+
+ return 0;
+}
+
+static void on_rw_timeout(h2o_timeout_entry_t *entry)
+{
+ struct st_fcgi_generator_t *generator = H2O_STRUCT_FROM_MEMBER(struct st_fcgi_generator_t, timeout, entry);
+
+ h2o_req_log_error(generator->req, MODULE_NAME, "I/O timeout");
+ errorclose(generator);
+}
+
+static void on_read(h2o_socket_t *sock, const char *err)
+{
+ struct st_fcgi_generator_t *generator = sock->data;
+ int can_keepalive = 0;
+
+ if (err != NULL) {
+ /* note: FastCGI server is allowed to close the connection any time after sending an empty FCGI_STDOUT record */
+ if (!generator->sent_headers)
+ h2o_req_log_error(generator->req, MODULE_NAME, "fastcgi connection closed unexpectedly");
+ errorclose(generator);
+ return;
+ }
+
+ /* handle the records */
+ while (1) {
+ struct st_fcgi_record_header_t header;
+ size_t recsize;
+ if (sock->input->size < FCGI_RECORD_HEADER_SIZE)
+ break;
+ decode_header(&header, sock->input->bytes);
+ recsize = FCGI_RECORD_HEADER_SIZE + header.contentLength + header.paddingLength;
+ if (sock->input->size < recsize)
+ break;
+ /* we have a complete record */
+ switch (header.type) {
+ case FCGI_STDOUT:
+ if (handle_stdin_record(generator, &header) != 0)
+ goto Error;
+ h2o_buffer_consume(&sock->input, recsize);
+ break;
+ case FCGI_STDERR:
+ if (header.contentLength != 0)
+ h2o_req_log_error(generator->req, MODULE_NAME, "%.*s", (int)header.contentLength,
+ sock->input->bytes + FCGI_RECORD_HEADER_SIZE);
+ h2o_buffer_consume(&sock->input, recsize);
+ break;
+ case FCGI_END_REQUEST:
+ if (!generator->sent_headers) {
+ h2o_req_log_error(generator->req, MODULE_NAME, "received FCGI_END_REQUEST before end of the headers");
+ goto Error;
+ }
+ h2o_buffer_consume(&sock->input, recsize);
+ can_keepalive = 1;
+ goto EOS_Received;
+ default:
+ h2o_req_log_error(generator->req, MODULE_NAME, "received unexpected record, type: %u", header.type);
+ h2o_buffer_consume(&sock->input, recsize);
+ if (!generator->sent_headers)
+ goto Error;
+ goto EOS_Received;
+ }
+ }
+
+ /* send data if necessary */
+ if (generator->sent_headers && generator->resp.sending.bytes_inflight == 0)
+ do_send(generator);
+
+ set_timeout(generator, &generator->ctx->io_timeout, on_rw_timeout);
+ return;
+
+EOS_Received:
+ send_eos_and_close(generator, can_keepalive);
+ return;
+
+Error:
+ errorclose(generator);
+}
+
+static void on_send_complete(h2o_socket_t *sock, const char *err)
+{
+ struct st_fcgi_generator_t *generator = sock->data;
+
+ set_timeout(generator, &generator->ctx->io_timeout, on_rw_timeout);
+ /* do nothing else! all the rest is handled by the on_read */
+}
+
+static void on_connect(h2o_socket_t *sock, const char *errstr, void *data)
+{
+ struct st_fcgi_generator_t *generator = data;
+ iovec_vector_t vecs;
+
+ generator->connect_req = NULL;
+
+ if (sock == NULL) {
+ h2o_req_log_error(generator->req, MODULE_NAME, "connection failed:%s", errstr);
+ errorclose(generator);
+ return;
+ }
+
+ generator->sock = sock;
+ sock->data = generator;
+
+ build_request(generator->req, &vecs, 1, 65535, &generator->ctx->handler->config);
+
+ /* start sending the response */
+ h2o_socket_write(generator->sock, vecs.entries, vecs.size, on_send_complete);
+
+ set_timeout(generator, &generator->ctx->io_timeout, on_rw_timeout);
+
+ /* activate the receiver; note: FCGI spec allows the app to start sending the response before it receives FCGI_STDIN */
+ h2o_socket_read_start(sock, on_read);
+}
+
+static void do_proceed(h2o_generator_t *_generator, h2o_req_t *req)
+{
+ struct st_fcgi_generator_t *generator = (void *)_generator;
+
+ h2o_doublebuffer_consume(&generator->resp.sending);
+ do_send(generator);
+}
+
+static void do_stop(h2o_generator_t *_generator, h2o_req_t *req)
+{
+ struct st_fcgi_generator_t *generator = (void *)_generator;
+ close_generator(generator);
+}
+
+static void on_connect_timeout(h2o_timeout_entry_t *entry)
+{
+ struct st_fcgi_generator_t *generator = H2O_STRUCT_FROM_MEMBER(struct st_fcgi_generator_t, timeout, entry);
+
+ h2o_req_log_error(generator->req, MODULE_NAME, "connect timeout");
+ errorclose(generator);
+}
+
+static int on_req(h2o_handler_t *_handler, h2o_req_t *req)
+{
+ h2o_fastcgi_handler_t *handler = (void *)_handler;
+ struct st_fcgi_generator_t *generator;
+
+ generator = h2o_mem_alloc_shared(&req->pool, sizeof(*generator), (void (*)(void *))close_generator);
+ generator->super.proceed = do_proceed;
+ generator->super.stop = do_stop;
+ generator->ctx = h2o_context_get_handler_context(req->conn->ctx, &handler->super);
+ generator->req = req;
+ generator->sock = NULL;
+ generator->sent_headers = 0;
+ h2o_doublebuffer_init(&generator->resp.sending, &h2o_socket_buffer_prototype);
+ h2o_buffer_init(&generator->resp.receiving, &h2o_socket_buffer_prototype);
+ generator->timeout = (h2o_timeout_entry_t){0};
+
+ set_timeout(generator, &generator->ctx->io_timeout, on_connect_timeout);
+ h2o_socketpool_connect(&generator->connect_req, &handler->sockpool, req->conn->ctx->loop,
+ &req->conn->ctx->receivers.hostinfo_getaddr, on_connect, generator);
+
+ return 0;
+}
+
+static void on_context_init(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_fastcgi_handler_t *handler = (void *)_handler;
+ struct st_fcgi_context_t *handler_ctx = h2o_mem_alloc(sizeof(*handler_ctx));
+
+ /* use the first event loop for handling timeouts of the socket pool */
+ if (handler->sockpool.timeout == UINT64_MAX)
+ h2o_socketpool_set_timeout(&handler->sockpool, ctx->loop,
+ handler->config.keepalive_timeout != 0 ? handler->config.keepalive_timeout : 60000);
+
+ handler_ctx->handler = handler;
+ h2o_timeout_init(ctx->loop, &handler_ctx->io_timeout, handler->config.io_timeout);
+
+ h2o_context_set_handler_context(ctx, &handler->super, handler_ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_fastcgi_handler_t *handler = (void *)_handler;
+ struct st_fcgi_context_t *handler_ctx = h2o_context_get_handler_context(ctx, &handler->super);
+
+ if (handler_ctx == NULL)
+ return;
+
+ h2o_timeout_dispose(ctx->loop, &handler_ctx->io_timeout);
+ free(handler_ctx);
+}
+
+static void on_handler_dispose(h2o_handler_t *_handler)
+{
+ h2o_fastcgi_handler_t *handler = (void *)_handler;
+
+ if (handler->config.callbacks.dispose != NULL)
+ handler->config.callbacks.dispose(handler, handler->config.callbacks.data);
+
+ h2o_socketpool_dispose(&handler->sockpool);
+ free(handler->config.document_root.base);
+}
+
+static h2o_fastcgi_handler_t *register_common(h2o_pathconf_t *pathconf, h2o_fastcgi_config_vars_t *vars)
+{
+ h2o_fastcgi_handler_t *handler = (void *)h2o_create_handler(pathconf, sizeof(*handler));
+
+ handler->super.on_context_init = on_context_init;
+ handler->super.on_context_dispose = on_context_dispose;
+ handler->super.dispose = on_handler_dispose;
+ handler->super.on_req = on_req;
+ handler->config = *vars;
+ if (vars->document_root.base != NULL)
+ handler->config.document_root = h2o_strdup(NULL, vars->document_root.base, vars->document_root.len);
+
+ return handler;
+}
+
+h2o_fastcgi_handler_t *h2o_fastcgi_register_by_hostport(h2o_pathconf_t *pathconf, const char *host, uint16_t port,
+ h2o_fastcgi_config_vars_t *vars)
+{
+ h2o_fastcgi_handler_t *handler = register_common(pathconf, vars);
+
+ h2o_socketpool_init_by_hostport(&handler->sockpool, h2o_iovec_init(host, strlen(host)), port, 0, SIZE_MAX /* FIXME */);
+ return handler;
+}
+
+h2o_fastcgi_handler_t *h2o_fastcgi_register_by_address(h2o_pathconf_t *pathconf, struct sockaddr *sa, socklen_t salen,
+ h2o_fastcgi_config_vars_t *vars)
+{
+ h2o_fastcgi_handler_t *handler = register_common(pathconf, vars);
+
+ h2o_socketpool_init_by_address(&handler->sockpool, sa, salen, 0, SIZE_MAX /* FIXME */);
+ return handler;
+}
diff --git a/debian/vendor-h2o/lib/handler/file.c b/debian/vendor-h2o/lib/handler/file.c
new file mode 100644
index 0000000..5d7c7a2
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/file.c
@@ -0,0 +1,966 @@
+/*
+ * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Domingo Alvarez Duarte,
+ * Tatsuhiko Kubo, Nick Desaulniers, Marc Hoersken,
+ * Justin Zhu, Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "h2o.h"
+
+#define MAX_BUF_SIZE 65000
+#define BOUNDARY_SIZE 20
+#define FIXED_PART_SIZE (sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("\r\nContent-Range: bytes=-/\r\nContent-Type: \r\n\r\n") - 1)
+
+struct st_h2o_sendfile_generator_t {
+ h2o_generator_t super;
+ struct {
+ h2o_filecache_ref_t *ref;
+ off_t off;
+ } file;
+ h2o_req_t *req;
+ size_t bytesleft;
+ h2o_iovec_t content_encoding;
+ unsigned send_vary : 1;
+ unsigned send_etag : 1;
+ unsigned gunzip : 1;
+ char *buf;
+ struct {
+ size_t filesize;
+ size_t range_count;
+ size_t *range_infos; /* size_t shows in pair. first is start offset, then length */
+ h2o_iovec_t boundary; /* boundary used for multipart/byteranges */
+ h2o_iovec_t mimetype; /* original mimetype for multipart */
+ size_t current_range; /* range that processing now */
+ } ranged;
+ struct {
+ char last_modified[H2O_TIMESTR_RFC1123_LEN + 1];
+ char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
+ } header_bufs;
+};
+
+struct st_h2o_file_handler_t {
+ h2o_handler_t super;
+ h2o_iovec_t conf_path; /* has "/" appended at last */
+ h2o_iovec_t real_path; /* has "/" appended at last */
+ h2o_mimemap_t *mimemap;
+ int flags;
+ size_t max_index_file_len;
+ h2o_iovec_t index_files[1];
+};
+
+struct st_h2o_specific_file_handler_t {
+ h2o_handler_t super;
+ h2o_iovec_t real_path;
+ h2o_mimemap_type_t *mime_type;
+ int flags;
+};
+
+struct st_gzip_decompress_t {
+ h2o_ostream_t super;
+ h2o_compress_context_t *decompressor;
+};
+
+static const char *default_index_files[] = {"index.html", "index.htm", "index.txt", NULL};
+
+const char **h2o_file_default_index_files = default_index_files;
+
+#include "file/templates.c.h"
+
+static int tm_is_lessthan(struct tm *x, struct tm *y)
+{
+#define CMP(f) \
+ if (x->f < y->f) \
+ return 1; \
+ else if (x->f > y->f) \
+ return 0;
+ CMP(tm_year);
+ CMP(tm_mon);
+ CMP(tm_mday);
+ CMP(tm_hour);
+ CMP(tm_min);
+ CMP(tm_sec);
+ return 0;
+#undef CMP
+}
+
+static void do_close(h2o_generator_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_sendfile_generator_t *self = (void *)_self;
+ h2o_filecache_close_file(self->file.ref);
+}
+
+static void do_proceed(h2o_generator_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_sendfile_generator_t *self = (void *)_self;
+ size_t rlen;
+ ssize_t rret;
+ h2o_iovec_t vec;
+ h2o_send_state_t send_state;
+
+ /* read the file */
+ rlen = self->bytesleft;
+ if (rlen > MAX_BUF_SIZE)
+ rlen = MAX_BUF_SIZE;
+ while ((rret = pread(self->file.ref->fd, self->buf, rlen, self->file.off)) == -1 && errno == EINTR)
+ ;
+ if (rret == -1) {
+ h2o_send(req, NULL, 0, H2O_SEND_STATE_ERROR);
+ do_close(&self->super, req);
+ return;
+ }
+ self->file.off += rret;
+ self->bytesleft -= rret;
+ if (self->bytesleft == 0) {
+ send_state = H2O_SEND_STATE_FINAL;
+ } else {
+ send_state = H2O_SEND_STATE_IN_PROGRESS;
+ }
+
+ /* send (and close if done) */
+ vec.base = self->buf;
+ vec.len = rret;
+ h2o_send(req, &vec, 1, send_state);
+ if (send_state == H2O_SEND_STATE_FINAL)
+ do_close(&self->super, req);
+}
+
+static void do_multirange_proceed(h2o_generator_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_sendfile_generator_t *self = (void *)_self;
+ size_t rlen, used_buf = 0;
+ ssize_t rret, vecarrsize;
+ h2o_iovec_t vec[2];
+ h2o_send_state_t send_state;
+
+ if (self->bytesleft == 0) {
+ size_t *range_cur = self->ranged.range_infos + 2 * self->ranged.current_range;
+ size_t range_end = *range_cur + *(range_cur + 1) - 1;
+ if (H2O_LIKELY(self->ranged.current_range != 0))
+ used_buf =
+ sprintf(self->buf, "\r\n--%s\r\nContent-Type: %s\r\nContent-Range: bytes %zd-%zd/%zd\r\n\r\n",
+ self->ranged.boundary.base, self->ranged.mimetype.base, *range_cur, range_end, self->ranged.filesize);
+ else
+ used_buf =
+ sprintf(self->buf, "--%s\r\nContent-Type: %s\r\nContent-Range: bytes %zd-%zd/%zd\r\n\r\n",
+ self->ranged.boundary.base, self->ranged.mimetype.base, *range_cur, range_end, self->ranged.filesize);
+ self->ranged.current_range++;
+ self->file.off = *range_cur;
+ self->bytesleft = *++range_cur;
+ }
+ rlen = self->bytesleft;
+ if (rlen + used_buf > MAX_BUF_SIZE)
+ rlen = MAX_BUF_SIZE - used_buf;
+ while ((rret = pread(self->file.ref->fd, self->buf + used_buf, rlen, self->file.off)) == -1 && errno == EINTR)
+ ;
+ if (rret == -1)
+ goto Error;
+ self->file.off += rret;
+ self->bytesleft -= rret;
+
+ vec[0].base = self->buf;
+ vec[0].len = rret + used_buf;
+ if (self->ranged.current_range == self->ranged.range_count && self->bytesleft == 0) {
+ vec[1].base = h2o_mem_alloc_pool(&req->pool, sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("--\r\n"));
+ vec[1].len = sprintf(vec[1].base, "\r\n--%s--\r\n", self->ranged.boundary.base);
+ vecarrsize = 2;
+ send_state = H2O_SEND_STATE_FINAL;
+ } else {
+ vecarrsize = 1;
+ send_state = H2O_SEND_STATE_IN_PROGRESS;
+ }
+ h2o_send(req, vec, vecarrsize, send_state);
+ if (send_state == H2O_SEND_STATE_FINAL)
+ do_close(&self->super, req);
+ return;
+
+Error:
+ h2o_send(req, NULL, 0, H2O_SEND_STATE_ERROR);
+ do_close(&self->super, req);
+ return;
+}
+
+static h2o_send_state_t do_pull(h2o_generator_t *_self, h2o_req_t *req, h2o_iovec_t *buf)
+{
+ struct st_h2o_sendfile_generator_t *self = (void *)_self;
+ ssize_t rret;
+
+ if (self->bytesleft < buf->len)
+ buf->len = self->bytesleft;
+ while ((rret = pread(self->file.ref->fd, buf->base, buf->len, self->file.off)) == -1 && errno == EINTR)
+ ;
+ if (rret <= 0) {
+ buf->len = 0;
+ self->bytesleft = 0;
+ do_close(&self->super, req);
+ return H2O_SEND_STATE_ERROR;
+ } else {
+ buf->len = rret;
+ self->file.off += rret;
+ self->bytesleft -= rret;
+ }
+
+ if (self->bytesleft != 0)
+ return H2O_SEND_STATE_IN_PROGRESS;
+ do_close(&self->super, req);
+ return H2O_SEND_STATE_FINAL;
+}
+
+static struct st_h2o_sendfile_generator_t *create_generator(h2o_req_t *req, const char *path, size_t path_len, int *is_dir,
+ int flags)
+{
+ struct st_h2o_sendfile_generator_t *self;
+ h2o_filecache_ref_t *fileref;
+ h2o_iovec_t content_encoding = (h2o_iovec_t){NULL};
+ unsigned gunzip = 0;
+
+ *is_dir = 0;
+
+ if ((flags & H2O_FILE_FLAG_SEND_COMPRESSED) != 0 && req->version >= 0x101) {
+ int compressible_types = h2o_get_compressible_types(&req->headers);
+ if (compressible_types != 0) {
+ char *variant_path = h2o_mem_alloc_pool(&req->pool, path_len + sizeof(".gz"));
+ memcpy(variant_path, path, path_len);
+#define TRY_VARIANT(mask, enc, ext) \
+ if ((compressible_types & mask) != 0) { \
+ strcpy(variant_path + path_len, ext); \
+ if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, variant_path, O_RDONLY | O_CLOEXEC)) != NULL) { \
+ content_encoding = h2o_iovec_init(enc, sizeof(enc) - 1); \
+ goto Opened; \
+ } \
+ }
+ TRY_VARIANT(H2O_COMPRESSIBLE_BROTLI, "br", ".br");
+ TRY_VARIANT(H2O_COMPRESSIBLE_GZIP, "gzip", ".gz");
+#undef TRY_VARIANT
+ }
+ }
+ if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, path, O_RDONLY | O_CLOEXEC)) != NULL) {
+ goto Opened;
+ }
+ if ((flags & H2O_FILE_FLAG_GUNZIP) != 0 && req->version >= 0x101) {
+ char *variant_path = h2o_mem_alloc_pool(&req->pool, path_len + sizeof(".gz"));
+ memcpy(variant_path, path, path_len);
+ strcpy(variant_path + path_len, ".gz");
+ if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, variant_path, O_RDONLY | O_CLOEXEC)) != NULL) {
+ gunzip = 1;
+ goto Opened;
+ }
+ }
+ return NULL;
+
+Opened:
+ if (S_ISDIR(fileref->st.st_mode)) {
+ h2o_filecache_close_file(fileref);
+ *is_dir = 1;
+ return NULL;
+ }
+
+ self = h2o_mem_alloc_pool(&req->pool, sizeof(*self));
+ self->super.proceed = do_proceed;
+ self->super.stop = do_close;
+ self->file.ref = fileref;
+ self->file.off = 0;
+ self->req = NULL;
+ self->bytesleft = self->file.ref->st.st_size;
+ self->ranged.range_count = 0;
+ self->ranged.range_infos = NULL;
+ self->content_encoding = content_encoding;
+ self->send_vary = (flags & H2O_FILE_FLAG_SEND_COMPRESSED) != 0;
+ self->send_etag = (flags & H2O_FILE_FLAG_NO_ETAG) == 0;
+ self->gunzip = gunzip;
+
+ return self;
+}
+
+static void add_headers_unconditional(struct st_h2o_sendfile_generator_t *self, h2o_req_t *req)
+{
+ /* RFC 7232 4.1: The server generating a 304 response MUST generate any of the following header fields that would have been sent
+ * in a 200 (OK) response to the same request: Cache-Control, Content-Location, Date, ETag, Expires, and Vary (snip) a sender
+ * SHOULD NOT generate representation metadata other than the above listed fields unless said metadata exists for the purpose of
+ * guiding cache updates. */
+ if (self->send_etag) {
+ size_t etag_len = h2o_filecache_get_etag(self->file.ref, self->header_bufs.etag);
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ETAG, NULL, self->header_bufs.etag, etag_len);
+ }
+ if (self->send_vary)
+ h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding"));
+}
+
+static void send_decompressed(h2o_ostream_t *_self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ struct st_gzip_decompress_t *self = (void *)_self;
+ h2o_iovec_t *outbufs;
+ size_t outbufcnt;
+
+ self->decompressor->transform(self->decompressor, inbufs, inbufcnt, state, &outbufs, &outbufcnt);
+ h2o_ostream_send_next(&self->super, req, outbufs, outbufcnt, state);
+}
+
+static void do_send_file(struct st_h2o_sendfile_generator_t *self, h2o_req_t *req, int status, const char *reason,
+ h2o_iovec_t mime_type, h2o_mime_attributes_t *mime_attr, int is_get)
+{
+ /* link the request */
+ self->req = req;
+
+ /* setup response */
+ req->res.status = status;
+ req->res.reason = reason;
+ req->res.content_length = self->gunzip ? SIZE_MAX : self->bytesleft;
+ req->res.mime_attr = mime_attr;
+
+ if (self->ranged.range_count > 1) {
+ mime_type.base = h2o_mem_alloc_pool(&req->pool, 52);
+ mime_type.len = sprintf(mime_type.base, "multipart/byteranges; boundary=%s", self->ranged.boundary.base);
+ }
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, mime_type.base, mime_type.len);
+ h2o_filecache_get_last_modified(self->file.ref, self->header_bufs.last_modified);
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_LAST_MODIFIED, NULL, self->header_bufs.last_modified,
+ H2O_TIMESTR_RFC1123_LEN);
+ add_headers_unconditional(self, req);
+ if (self->content_encoding.base != NULL)
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, NULL, self->content_encoding.base,
+ self->content_encoding.len);
+ if (self->ranged.range_count == 0)
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCEPT_RANGES, NULL, H2O_STRLIT("bytes"));
+ else if (self->ranged.range_count == 1) {
+ h2o_iovec_t content_range;
+ content_range.base = h2o_mem_alloc_pool(&req->pool, 128);
+ content_range.len = sprintf(content_range.base, "bytes %zd-%zd/%zd", self->ranged.range_infos[0],
+ self->ranged.range_infos[0] + self->ranged.range_infos[1] - 1, self->ranged.filesize);
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_RANGE, NULL, content_range.base, content_range.len);
+ }
+
+ /* special path for cases where we do not need to send any data */
+ if (!is_get || self->bytesleft == 0) {
+ static h2o_generator_t generator = {NULL, NULL};
+ h2o_start_response(req, &generator);
+ h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL);
+ do_close(&self->super, req);
+ return;
+ }
+
+ /* send data */
+ h2o_start_response(req, &self->super);
+
+ /* dynamically setup gzip decompress ostream */
+ if (self->gunzip) {
+ struct st_gzip_decompress_t *decoder = (void *)h2o_add_ostream(req, sizeof(struct st_gzip_decompress_t), &req->_ostr_top);
+ decoder->decompressor = h2o_compress_gunzip_open(&req->pool);
+ decoder->super.do_send = send_decompressed;
+ }
+
+ if (self->ranged.range_count == 1)
+ self->file.off = self->ranged.range_infos[0];
+ if (req->_ostr_top->start_pull != NULL && self->ranged.range_count < 2) {
+ req->_ostr_top->start_pull(req->_ostr_top, do_pull);
+ } else {
+ size_t bufsz = MAX_BUF_SIZE;
+ if (self->bytesleft < bufsz)
+ bufsz = self->bytesleft;
+ self->buf = h2o_mem_alloc_pool(&req->pool, bufsz);
+ if (self->ranged.range_count < 2)
+ do_proceed(&self->super, req);
+ else {
+ self->bytesleft = 0;
+ self->super.proceed = do_multirange_proceed;
+ do_multirange_proceed(&self->super, req);
+ }
+ }
+}
+
+int h2o_file_send(h2o_req_t *req, int status, const char *reason, const char *path, h2o_iovec_t mime_type, int flags)
+{
+ struct st_h2o_sendfile_generator_t *self;
+ int is_dir;
+
+ if ((self = create_generator(req, path, strlen(path), &is_dir, flags)) == NULL)
+ return -1;
+ /* note: is_dir is not handled */
+ do_send_file(self, req, status, reason, mime_type, NULL, 1);
+ return 0;
+}
+
+static int send_dir_listing(h2o_req_t *req, const char *path, size_t path_len, int is_get)
+{
+ static h2o_generator_t generator = {NULL, NULL};
+ DIR *dp;
+ h2o_buffer_t *body;
+ h2o_iovec_t bodyvec;
+
+ /* build html */
+ if ((dp = opendir(path)) == NULL)
+ return -1;
+ body = build_dir_listing_html(&req->pool, req->path_normalized, dp);
+ closedir(dp);
+
+ bodyvec = h2o_iovec_init(body->bytes, body->size);
+ h2o_buffer_link_to_pool(body, &req->pool);
+
+ /* send response */
+ req->res.status = 200;
+ req->res.reason = "OK";
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/html; charset=utf-8"));
+
+ /* send headers */
+ if (!is_get) {
+ h2o_send_inline(req, NULL, 0);
+ return 0;
+ }
+
+ /* send data */
+ h2o_start_response(req, &generator);
+ h2o_send(req, &bodyvec, 1, H2O_SEND_STATE_FINAL);
+ return 0;
+}
+
+static size_t *process_range(h2o_mem_pool_t *pool, h2o_iovec_t *range_value, size_t file_size, size_t *ret)
+{
+#define CHECK_EOF() \
+ if (buf == buf_end) \
+ return NULL;
+
+#define CHECK_OVERFLOW(range) \
+ if (range == SIZE_MAX) \
+ return NULL;
+
+ size_t range_start = SIZE_MAX, range_count = 0;
+ char *buf = range_value->base, *buf_end = buf + range_value->len;
+ int needs_comma = 0;
+ H2O_VECTOR(size_t) ranges = {NULL};
+
+ if (range_value->len < 6 || memcmp(buf, "bytes=", 6) != 0)
+ return NULL;
+
+ buf += 6;
+ CHECK_EOF();
+
+ /* most range requests contain only one range */
+ do {
+ while (1) {
+ if (*buf != ',') {
+ if (needs_comma)
+ return NULL;
+ break;
+ }
+ needs_comma = 0;
+ buf++;
+ while (H2O_UNLIKELY(*buf == ' ') || H2O_UNLIKELY(*buf == '\t')) {
+ buf++;
+ CHECK_EOF();
+ }
+ }
+ if (H2O_UNLIKELY(buf == buf_end))
+ break;
+ if (H2O_LIKELY((range_start = h2o_strtosizefwd(&buf, buf_end - buf)) != SIZE_MAX)) {
+ CHECK_EOF();
+ if (*buf++ != '-')
+ return NULL;
+ range_count = h2o_strtosizefwd(&buf, buf_end - buf);
+ if (H2O_UNLIKELY(range_start >= file_size)) {
+ range_start = SIZE_MAX;
+ } else if (H2O_LIKELY(range_count != SIZE_MAX)) {
+ if (H2O_UNLIKELY(range_count > file_size - 1))
+ range_count = file_size - 1;
+ if (H2O_LIKELY(range_start <= range_count))
+ range_count -= range_start - 1;
+ else
+ range_start = SIZE_MAX;
+ } else {
+ range_count = file_size - range_start;
+ }
+ } else if (H2O_LIKELY(*buf++ == '-')) {
+ CHECK_EOF();
+ range_count = h2o_strtosizefwd(&buf, buf_end - buf);
+ if (H2O_UNLIKELY(range_count == SIZE_MAX))
+ return NULL;
+ if (H2O_LIKELY(range_count != 0)) {
+ if (H2O_UNLIKELY(range_count > file_size))
+ range_count = file_size;
+ range_start = file_size - range_count;
+ } else {
+ range_start = SIZE_MAX;
+ }
+ } else {
+ return NULL;
+ }
+
+ if (H2O_LIKELY(range_start != SIZE_MAX)) {
+ h2o_vector_reserve(pool, &ranges, ranges.size + 2);
+ ranges.entries[ranges.size++] = range_start;
+ ranges.entries[ranges.size++] = range_count;
+ }
+ if (buf != buf_end)
+ while (H2O_UNLIKELY(*buf == ' ') || H2O_UNLIKELY(*buf == '\t')) {
+ buf++;
+ CHECK_EOF();
+ }
+ needs_comma = 1;
+ } while (H2O_UNLIKELY(buf < buf_end));
+ *ret = ranges.size / 2;
+ return ranges.entries;
+#undef CHECK_EOF
+#undef CHECK_OVERFLOW
+}
+
+static void gen_rand_string(h2o_iovec_t *s)
+{
+ int i;
+ static const char alphanum[] = "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+
+ for (i = 0; i < s->len; ++i) {
+ s->base[i] = alphanum[h2o_rand() % (sizeof(alphanum) - 1)];
+ }
+
+ s->base[s->len] = 0;
+}
+
+static int delegate_dynamic_request(h2o_req_t *req, h2o_iovec_t script_name, h2o_iovec_t path_info, const char *local_path,
+ size_t local_path_len, h2o_mimemap_type_t *mime_type)
+{
+ h2o_filereq_t *filereq;
+ h2o_handler_t *handler;
+
+ assert(mime_type->data.dynamic.pathconf.handlers.size == 1);
+
+ filereq = h2o_mem_alloc_pool(&req->pool, sizeof(*filereq));
+ filereq->script_name = script_name;
+ filereq->path_info = path_info;
+ filereq->local_path = h2o_strdup(&req->pool, local_path, local_path_len);
+
+ h2o_req_bind_conf(req, req->hostconf, &mime_type->data.dynamic.pathconf);
+ req->filereq = filereq;
+
+ handler = mime_type->data.dynamic.pathconf.handlers.entries[0];
+ return handler->on_req(handler, req);
+}
+
+static int try_dynamic_request(h2o_file_handler_t *self, h2o_req_t *req, char *rpath, size_t rpath_len)
+{
+ /* we have full local path in {rpath,rpath_len}, and need to split it into name and path_info */
+ struct stat st;
+ size_t slash_at = self->real_path.len;
+
+ while (1) {
+ /* find the next slash (or return -1 if failed) */
+ for (++slash_at;; ++slash_at) {
+ if (slash_at >= rpath_len)
+ return -1;
+ if (rpath[slash_at] == '/')
+ break;
+ }
+ /* change the slash to '\0', and check if the file exists */
+ rpath[slash_at] = '\0';
+ if (stat(rpath, &st) != 0)
+ return -1;
+ if (!S_ISDIR(st.st_mode))
+ break;
+ /* restore slash, and continue the search */
+ rpath[slash_at] = '/';
+ }
+
+ /* file found! */
+ h2o_mimemap_type_t *mime_type = h2o_mimemap_get_type_by_extension(self->mimemap, h2o_get_filext(rpath, slash_at));
+ switch (mime_type->type) {
+ case H2O_MIMEMAP_TYPE_MIMETYPE:
+ return -1;
+ case H2O_MIMEMAP_TYPE_DYNAMIC: {
+ h2o_iovec_t script_name = h2o_iovec_init(req->path_normalized.base, self->conf_path.len + slash_at - self->real_path.len);
+ h2o_iovec_t path_info =
+ h2o_iovec_init(req->path_normalized.base + script_name.len, req->path_normalized.len - script_name.len);
+ return delegate_dynamic_request(req, script_name, path_info, rpath, slash_at, mime_type);
+ }
+ }
+ fprintf(stderr, "unknown h2o_miemmap_type_t::type (%d)\n", (int)mime_type->type);
+ abort();
+}
+
+static void send_method_not_allowed(h2o_req_t *req)
+{
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ALLOW, NULL, H2O_STRLIT("GET, HEAD"));
+ h2o_send_error_405(req, "Method Not Allowed", "method not allowed", H2O_SEND_ERROR_KEEP_HEADERS);
+}
+
+static int serve_with_generator(struct st_h2o_sendfile_generator_t *generator, h2o_req_t *req, h2o_iovec_t resolved_path,
+ const char *rpath, size_t rpath_len, h2o_mimemap_type_t *mime_type)
+{
+ enum { METHOD_IS_GET, METHOD_IS_HEAD, METHOD_IS_OTHER } method_type;
+ size_t if_modified_since_header_index, if_none_match_header_index;
+ size_t range_header_index;
+
+ /* determine the method */
+ if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) {
+ method_type = METHOD_IS_GET;
+ } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD"))) {
+ method_type = METHOD_IS_HEAD;
+ } else {
+ method_type = METHOD_IS_OTHER;
+ }
+
+ /* obtain mime type */
+ if (mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC) {
+ do_close(&generator->super, req);
+ return delegate_dynamic_request(req, resolved_path, h2o_iovec_init(NULL, 0), rpath, rpath_len, mime_type);
+ }
+ assert(mime_type->type == H2O_MIMEMAP_TYPE_MIMETYPE);
+
+ /* if-non-match and if-modified-since */
+ if ((if_none_match_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_NONE_MATCH, SIZE_MAX)) != -1) {
+ h2o_iovec_t *if_none_match = &req->headers.entries[if_none_match_header_index].value;
+ char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
+ size_t etag_len = h2o_filecache_get_etag(generator->file.ref, etag);
+ if (h2o_memis(if_none_match->base, if_none_match->len, etag, etag_len))
+ goto NotModified;
+ } else if ((if_modified_since_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_MODIFIED_SINCE, SIZE_MAX)) != -1) {
+ h2o_iovec_t *ims_vec = &req->headers.entries[if_modified_since_header_index].value;
+ struct tm ims_tm, *last_modified_tm;
+ if (h2o_time_parse_rfc1123(ims_vec->base, ims_vec->len, &ims_tm) == 0) {
+ last_modified_tm = h2o_filecache_get_last_modified(generator->file.ref, NULL);
+ if (!tm_is_lessthan(&ims_tm, last_modified_tm))
+ goto NotModified;
+ }
+ }
+
+ /* only allow GET or POST for static files */
+ if (method_type == METHOD_IS_OTHER) {
+ do_close(&generator->super, req);
+ send_method_not_allowed(req);
+ return 0;
+ }
+
+ /* if-range */
+ if ((range_header_index = h2o_find_header(&req->headers, H2O_TOKEN_RANGE, SIZE_MAX)) != -1) {
+ h2o_iovec_t *range = &req->headers.entries[range_header_index].value;
+ size_t *range_infos, range_count;
+ range_infos = process_range(&req->pool, range, generator->bytesleft, &range_count);
+ if (range_infos == NULL) {
+ h2o_iovec_t content_range;
+ content_range.base = h2o_mem_alloc_pool(&req->pool, 32);
+ content_range.len = sprintf(content_range.base, "bytes */%zu", generator->bytesleft);
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_RANGE, NULL, content_range.base, content_range.len);
+ h2o_send_error_416(req, "Request Range Not Satisfiable", "requested range not satisfiable",
+ H2O_SEND_ERROR_KEEP_HEADERS);
+ goto Close;
+ }
+ generator->ranged.range_count = range_count;
+ generator->ranged.range_infos = range_infos;
+ generator->ranged.current_range = 0;
+ generator->ranged.filesize = generator->bytesleft;
+
+ /* set content-length according to range */
+ if (range_count == 1)
+ generator->bytesleft = range_infos[1];
+ else {
+ generator->ranged.mimetype = h2o_strdup(&req->pool, mime_type->data.mimetype.base, mime_type->data.mimetype.len);
+ size_t final_content_len = 0, size_tmp = 0, size_fixed_each_part, i;
+ generator->ranged.boundary.base = h2o_mem_alloc_pool(&req->pool, BOUNDARY_SIZE + 1);
+ generator->ranged.boundary.len = BOUNDARY_SIZE;
+ gen_rand_string(&generator->ranged.boundary);
+ i = generator->bytesleft;
+ while (i) {
+ i /= 10;
+ size_tmp++;
+ }
+ size_fixed_each_part = FIXED_PART_SIZE + mime_type->data.mimetype.len + size_tmp;
+ for (i = 0; i < range_count; i++) {
+ size_tmp = *range_infos++;
+ if (size_tmp == 0)
+ final_content_len++;
+ while (size_tmp) {
+ size_tmp /= 10;
+ final_content_len++;
+ }
+
+ size_tmp = *(range_infos - 1);
+ final_content_len += *range_infos;
+
+ size_tmp += *range_infos++ - 1;
+ if (size_tmp == 0)
+ final_content_len++;
+ while (size_tmp) {
+ size_tmp /= 10;
+ final_content_len++;
+ }
+ }
+ final_content_len += sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("--\r\n") - 1 + size_fixed_each_part * range_count -
+ (sizeof("\r\n") - 1);
+ generator->bytesleft = final_content_len;
+ }
+ do_send_file(generator, req, 206, "Partial Content", mime_type->data.mimetype, &h2o_mime_attributes_as_is,
+ method_type == METHOD_IS_GET);
+ return 0;
+ }
+
+ /* return file */
+ do_send_file(generator, req, 200, "OK", mime_type->data.mimetype, &mime_type->data.attr, method_type == METHOD_IS_GET);
+ return 0;
+
+NotModified:
+ req->res.status = 304;
+ req->res.reason = "Not Modified";
+ add_headers_unconditional(generator, req);
+ h2o_send_inline(req, NULL, 0);
+Close:
+ do_close(&generator->super, req);
+ return 0;
+}
+
+static int on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ h2o_file_handler_t *self = (void *)_self;
+ char *rpath;
+ size_t rpath_len, req_path_prefix;
+ struct st_h2o_sendfile_generator_t *generator = NULL;
+ int is_dir;
+
+ if (req->path_normalized.len < self->conf_path.len) {
+ h2o_iovec_t dest = h2o_uri_escape(&req->pool, self->conf_path.base, self->conf_path.len, "/");
+ if (req->query_at != SIZE_MAX)
+ dest = h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
+ h2o_send_redirect(req, 301, "Moved Permanently", dest.base, dest.len);
+ return 0;
+ }
+
+ /* build path (still unterminated at the end of the block) */
+ req_path_prefix = self->conf_path.len;
+ rpath = alloca(self->real_path.len + (req->path_normalized.len - req_path_prefix) + self->max_index_file_len + 1);
+ rpath_len = 0;
+ memcpy(rpath + rpath_len, self->real_path.base, self->real_path.len);
+ rpath_len += self->real_path.len;
+ memcpy(rpath + rpath_len, req->path_normalized.base + req_path_prefix, req->path_normalized.len - req_path_prefix);
+ rpath_len += req->path_normalized.len - req_path_prefix;
+
+ h2o_iovec_t resolved_path = req->path_normalized;
+
+ /* build generator (as well as terminating the rpath and its length upon success) */
+ if (rpath[rpath_len - 1] == '/') {
+ h2o_iovec_t *index_file;
+ for (index_file = self->index_files; index_file->base != NULL; ++index_file) {
+ memcpy(rpath + rpath_len, index_file->base, index_file->len);
+ rpath[rpath_len + index_file->len] = '\0';
+ if ((generator = create_generator(req, rpath, rpath_len + index_file->len, &is_dir, self->flags)) != NULL) {
+ rpath_len += index_file->len;
+ resolved_path = h2o_concat(&req->pool, req->path_normalized, *index_file);
+ goto Opened;
+ }
+ if (is_dir) {
+ /* note: apache redirects "path/" to "path/index.txt/" if index.txt is a dir */
+ h2o_iovec_t dest = h2o_concat(&req->pool, req->path_normalized, *index_file, h2o_iovec_init(H2O_STRLIT("/")));
+ dest = h2o_uri_escape(&req->pool, dest.base, dest.len, "/");
+ if (req->query_at != SIZE_MAX)
+ dest =
+ h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
+ h2o_send_redirect(req, 301, "Moved Permantently", dest.base, dest.len);
+ return 0;
+ }
+ if (errno != ENOENT)
+ break;
+ }
+ if (index_file->base == NULL && (self->flags & H2O_FILE_FLAG_DIR_LISTING) != 0) {
+ rpath[rpath_len] = '\0';
+ int is_get = 0;
+ if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) {
+ is_get = 1;
+ } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD"))) {
+ /* ok */
+ } else {
+ send_method_not_allowed(req);
+ return 0;
+ }
+ if (send_dir_listing(req, rpath, rpath_len, is_get) == 0)
+ return 0;
+ }
+ } else {
+ rpath[rpath_len] = '\0';
+ if ((generator = create_generator(req, rpath, rpath_len, &is_dir, self->flags)) != NULL)
+ goto Opened;
+ if (is_dir) {
+ h2o_iovec_t dest = h2o_concat(&req->pool, req->path_normalized, h2o_iovec_init(H2O_STRLIT("/")));
+ dest = h2o_uri_escape(&req->pool, dest.base, dest.len, "/");
+ if (req->query_at != SIZE_MAX)
+ dest = h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
+ h2o_send_redirect(req, 301, "Moved Permanently", dest.base, dest.len);
+ return 0;
+ }
+ }
+ /* failed to open */
+
+ if (errno == ENFILE || errno == EMFILE) {
+ h2o_send_error_503(req, "Service Unavailable", "please try again later", 0);
+ } else {
+ if (h2o_mimemap_has_dynamic_type(self->mimemap) && try_dynamic_request(self, req, rpath, rpath_len) == 0)
+ return 0;
+ if (errno == ENOENT || errno == ENOTDIR) {
+ return -1;
+ } else {
+ h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
+ }
+ }
+ return 0;
+
+Opened:
+ return serve_with_generator(generator, req, resolved_path, rpath, rpath_len,
+ h2o_mimemap_get_type_by_extension(self->mimemap, h2o_get_filext(rpath, rpath_len)));
+}
+
+static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ h2o_file_handler_t *self = (void *)_self;
+
+ h2o_mimemap_on_context_init(self->mimemap, ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ h2o_file_handler_t *self = (void *)_self;
+
+ h2o_mimemap_on_context_dispose(self->mimemap, ctx);
+}
+
+static void on_dispose(h2o_handler_t *_self)
+{
+ h2o_file_handler_t *self = (void *)_self;
+ size_t i;
+
+ free(self->conf_path.base);
+ free(self->real_path.base);
+ h2o_mem_release_shared(self->mimemap);
+ for (i = 0; self->index_files[i].base != NULL; ++i)
+ free(self->index_files[i].base);
+}
+
+h2o_file_handler_t *h2o_file_register(h2o_pathconf_t *pathconf, const char *real_path, const char **index_files,
+ h2o_mimemap_t *mimemap, int flags)
+{
+ h2o_file_handler_t *self;
+ size_t i;
+
+ if (index_files == NULL)
+ index_files = default_index_files;
+
+ /* allocate memory */
+ for (i = 0; index_files[i] != NULL; ++i)
+ ;
+ self =
+ (void *)h2o_create_handler(pathconf, offsetof(h2o_file_handler_t, index_files[0]) + sizeof(self->index_files[0]) * (i + 1));
+
+ /* setup callbacks */
+ self->super.on_context_init = on_context_init;
+ self->super.on_context_dispose = on_context_dispose;
+ self->super.dispose = on_dispose;
+ self->super.on_req = on_req;
+
+ /* setup attributes */
+ self->conf_path = h2o_strdup_slashed(NULL, pathconf->path.base, pathconf->path.len);
+ self->real_path = h2o_strdup_slashed(NULL, real_path, SIZE_MAX);
+ if (mimemap != NULL) {
+ h2o_mem_addref_shared(mimemap);
+ self->mimemap = mimemap;
+ } else {
+ self->mimemap = h2o_mimemap_create();
+ }
+ self->flags = flags;
+ for (i = 0; index_files[i] != NULL; ++i) {
+ self->index_files[i] = h2o_strdup(NULL, index_files[i], SIZE_MAX);
+ if (self->max_index_file_len < self->index_files[i].len)
+ self->max_index_file_len = self->index_files[i].len;
+ }
+
+ return self;
+}
+
+h2o_mimemap_t *h2o_file_get_mimemap(h2o_file_handler_t *handler)
+{
+ return handler->mimemap;
+}
+
+static void specific_handler_on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct st_h2o_specific_file_handler_t *self = (void *)_self;
+
+ if (self->mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC)
+ h2o_context_init_pathconf_context(ctx, &self->mime_type->data.dynamic.pathconf);
+}
+
+static void specific_handler_on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct st_h2o_specific_file_handler_t *self = (void *)_self;
+
+ if (self->mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC)
+ h2o_context_dispose_pathconf_context(ctx, &self->mime_type->data.dynamic.pathconf);
+}
+
+static void specific_handler_on_dispose(h2o_handler_t *_self)
+{
+ struct st_h2o_specific_file_handler_t *self = (void *)_self;
+
+ free(self->real_path.base);
+ h2o_mem_release_shared(self->mime_type);
+}
+
+static int specific_handler_on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_specific_file_handler_t *self = (void *)_self;
+ struct st_h2o_sendfile_generator_t *generator;
+ int is_dir;
+
+ /* open file (or send error or return -1) */
+ if ((generator = create_generator(req, self->real_path.base, self->real_path.len, &is_dir, self->flags)) == NULL) {
+ if (is_dir) {
+ h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
+ } else if (errno == ENOENT) {
+ return -1;
+ } else if (errno == ENFILE || errno == EMFILE) {
+ h2o_send_error_503(req, "Service Unavailable", "please try again later", 0);
+ } else {
+ h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
+ }
+ return 0;
+ }
+
+ return serve_with_generator(generator, req, req->path_normalized, self->real_path.base, self->real_path.len, self->mime_type);
+}
+
+h2o_handler_t *h2o_file_register_file(h2o_pathconf_t *pathconf, const char *real_path, h2o_mimemap_type_t *mime_type, int flags)
+{
+ struct st_h2o_specific_file_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self));
+
+ self->super.on_context_init = specific_handler_on_context_init;
+ self->super.on_context_dispose = specific_handler_on_context_dispose;
+ self->super.dispose = specific_handler_on_dispose;
+ self->super.on_req = specific_handler_on_req;
+
+ self->real_path = h2o_strdup(NULL, real_path, SIZE_MAX);
+ h2o_mem_addref_shared(mime_type);
+ self->mime_type = mime_type;
+ self->flags = flags;
+
+ return &self->super;
+}
diff --git a/debian/vendor-h2o/lib/handler/file/_templates.c.h b/debian/vendor-h2o/lib/handler/file/_templates.c.h
new file mode 100644
index 0000000..11b7357
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/file/_templates.c.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * lib/file/templates.c.h is automatically generated from lib/file/_templates.h
+ * with command:
+ * picotemplate.pl --conf=misc/picotemplate-conf.pl lib/file/_templates.c.h
+ */
+
+#include <limits.h>
+
+static int cmpstrptr(const void *_x, const void *_y)
+{
+ const char *x = *(const char **)_x;
+ const char *y = *(const char **)_y;
+ return strcmp(x, y);
+}
+
+#if !defined(NAME_MAX) || defined(__linux__)
+/* readdir(3) is known to be thread-safe on Linux and should be thread-safe on a platform that does not have a predefined value for
+ NAME_MAX */
+#define FOREACH_DIRENT(dp, dent) \
+ struct dirent *dent; \
+ while ((dent = readdir(dp)) != NULL)
+#else
+#define FOREACH_DIRENT(dp, dent) \
+ struct { \
+ struct dirent d; \
+ char s[NAME_MAX + 1]; \
+ } dent_; \
+ struct dirent *dentp, *dent = &dent_.d; \
+ int ret; \
+ while ((ret = readdir_r(dp, dent, &dentp)) == 0 && dentp != NULL)
+#endif /* FOREACH_DIRENT */
+
+static h2o_buffer_t *build_dir_listing_html(h2o_mem_pool_t *pool, h2o_iovec_t path_normalized, DIR* dp)
+{
+ H2O_VECTOR(char *) files = {NULL};
+
+ { /* build list of files */
+ FOREACH_DIRENT(dp, dent)
+ {
+ if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ h2o_vector_reserve(pool, &files, files.size + 1);
+ files.entries[files.size++] = h2o_strdup(pool, dent->d_name, SIZE_MAX).base;
+ }
+ if (files.size > 1)
+ qsort(files.entries, files.size, sizeof(files.entries[0]), cmpstrptr);
+ }
+
+ h2o_buffer_t *_;
+ h2o_iovec_t path_normalized_escaped = h2o_htmlescape(pool, path_normalized.base, path_normalized.len);
+
+ h2o_buffer_init(&_, &h2o_socket_buffer_prototype);
+
+?<!DOCTYPE html>
+?<TITLE>Index of <?= path_normalized_escaped ?></TITLE>
+?<H2>Index of <?= path_normalized_escaped ?></H2>
+?<UL>
+?<LI><A HREF="..">Parent Directory</A>
+
+ size_t i;
+ for (i = 0; i != files.size; ++i) {
+ h2o_iovec_t link_escaped = h2o_uri_escape(pool, files.entries[i], strlen(files.entries[i]), NULL);
+ link_escaped = h2o_htmlescape(pool, link_escaped.base, link_escaped.len);
+ h2o_iovec_t label_escaped = h2o_htmlescape(pool, files.entries[i], strlen(files.entries[i]));
+?<LI><A HREF="<?= link_escaped ?>"><?= label_escaped ?></A>
+ }
+?</UL>
+
+ return _;
+}
diff --git a/debian/vendor-h2o/lib/handler/file/templates.c.h b/debian/vendor-h2o/lib/handler/file/templates.c.h
new file mode 100644
index 0000000..24c12da
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/file/templates.c.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * lib/file/templates.c.h is automatically generated from lib/file/_templates.h
+ * with command:
+ * picotemplate.pl --conf=misc/picotemplate-conf.pl lib/file/_templates.c.h
+ */
+
+#include <limits.h>
+
+static int cmpstrptr(const void *_x, const void *_y)
+{
+ const char *x = *(const char **)_x;
+ const char *y = *(const char **)_y;
+ return strcmp(x, y);
+}
+
+#if !defined(NAME_MAX) || defined(__linux__)
+/* readdir(3) is known to be thread-safe on Linux and should be thread-safe on a platform that does not have a predefined value for
+ NAME_MAX */
+#define FOREACH_DIRENT(dp, dent) \
+ struct dirent *dent; \
+ while ((dent = readdir(dp)) != NULL)
+#else
+#define FOREACH_DIRENT(dp, dent) \
+ struct { \
+ struct dirent d; \
+ char s[NAME_MAX + 1]; \
+ } dent_; \
+ struct dirent *dentp, *dent = &dent_.d; \
+ int ret; \
+ while ((ret = readdir_r(dp, dent, &dentp)) == 0 && dentp != NULL)
+#endif /* FOREACH_DIRENT */
+
+static h2o_buffer_t *build_dir_listing_html(h2o_mem_pool_t *pool, h2o_iovec_t path_normalized, DIR *dp)
+{
+ H2O_VECTOR(char *) files = {NULL};
+
+ { /* build list of files */
+ FOREACH_DIRENT(dp, dent)
+ {
+ if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ h2o_vector_reserve(pool, &files, files.size + 1);
+ files.entries[files.size++] = h2o_strdup(pool, dent->d_name, SIZE_MAX).base;
+ }
+ if (files.size > 1)
+ qsort(files.entries, files.size, sizeof(files.entries[0]), cmpstrptr);
+ }
+
+ h2o_buffer_t *_;
+ h2o_iovec_t path_normalized_escaped = h2o_htmlescape(pool, path_normalized.base, path_normalized.len);
+
+ h2o_buffer_init(&_, &h2o_socket_buffer_prototype);
+
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("<!DOCTYPE html>\n<TITLE>Index of ")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (path_normalized_escaped);
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("</TITLE>\n<H2>Index of ")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (path_normalized_escaped);
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("</H2>\n<UL>\n<LI><A HREF=\"..\">Parent Directory</A>\n")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+
+ size_t i;
+ for (i = 0; i != files.size; ++i) {
+ h2o_iovec_t link_escaped = h2o_uri_escape(pool, files.entries[i], strlen(files.entries[i]), NULL);
+ link_escaped = h2o_htmlescape(pool, link_escaped.base, link_escaped.len);
+ h2o_iovec_t label_escaped = h2o_htmlescape(pool, files.entries[i], strlen(files.entries[i]));
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("<LI><A HREF=\"")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (link_escaped);
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("\">")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (label_escaped);
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("</A>\n")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+ }
+ {
+ h2o_iovec_t _s = (h2o_iovec_init(H2O_STRLIT("</UL>\n")));
+ if (_s.len != 0 && _s.base[_s.len - 1] == '\n')
+ --_s.len;
+ h2o_buffer_reserve(&_, _s.len);
+ memcpy(_->bytes + _->size, _s.base, _s.len);
+ _->size += _s.len;
+ }
+
+ return _;
+}
diff --git a/debian/vendor-h2o/lib/handler/headers.c b/debian/vendor-h2o/lib/handler/headers.c
new file mode 100644
index 0000000..973976b
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/headers.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+
+struct st_headers_filter_t {
+ h2o_filter_t super;
+ h2o_headers_command_t *cmds;
+};
+
+static void on_setup_ostream(h2o_filter_t *_self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ struct st_headers_filter_t *self = (void *)_self;
+ h2o_headers_command_t *cmd;
+
+ for (cmd = self->cmds; cmd->cmd != H2O_HEADERS_CMD_NULL; ++cmd)
+ h2o_rewrite_headers(&req->pool, &req->res.headers, cmd);
+
+ h2o_setup_next_ostream(req, slot);
+}
+
+void h2o_headers_register(h2o_pathconf_t *pathconf, h2o_headers_command_t *cmds)
+{
+ struct st_headers_filter_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self));
+
+ self->super.on_setup_ostream = on_setup_ostream;
+ self->cmds = cmds;
+}
+
+int h2o_headers_is_prohibited_name(const h2o_token_t *token)
+{
+ /* prohibit connection-specific headers */
+ if (token == H2O_TOKEN_CONNECTION || token == H2O_TOKEN_CONTENT_LENGTH || token == H2O_TOKEN_TRANSFER_ENCODING)
+ return 1;
+ /* prohibit headers added at protocol layer */
+ if (token == H2O_TOKEN_DATE || token == H2O_TOKEN_SERVER)
+ return 1;
+ /* all others are permitted */
+ return 0;
+}
diff --git a/debian/vendor-h2o/lib/handler/headers_util.c b/debian/vendor-h2o/lib/handler/headers_util.c
new file mode 100644
index 0000000..5ecacfa
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/headers_util.c
@@ -0,0 +1,114 @@
+#include "h2o.h"
+
+static h2o_header_t *find_header(h2o_headers_t *headers, h2o_headers_command_t *cmd)
+{
+ size_t index;
+
+ if (h2o_iovec_is_token(cmd->name)) {
+ index = h2o_find_header(headers, (void *)cmd->name, SIZE_MAX);
+ } else {
+ index = h2o_find_header_by_str(headers, cmd->name->base, cmd->name->len, SIZE_MAX);
+ }
+ if (index == SIZE_MAX)
+ return NULL;
+ return headers->entries + index;
+}
+
+static void remove_header(h2o_headers_t *headers, h2o_headers_command_t *cmd)
+{
+ size_t src, dst = 0;
+
+ for (src = 0; src != headers->size; ++src) {
+ if (h2o_iovec_is_token(cmd->name)) {
+ if (headers->entries[src].name == cmd->name)
+ continue;
+ } else {
+ if (h2o_memis(headers->entries[src].name->base, headers->entries[src].name->len, cmd->name->base, cmd->name->len))
+ continue;
+ }
+ /* not matched */
+ if (dst != src)
+ headers->entries[dst] = headers->entries[src];
+ ++dst;
+ }
+ headers->size = dst;
+}
+
+void h2o_headers_append_command(h2o_headers_command_t **cmds, int cmd, h2o_iovec_t *name, h2o_iovec_t value)
+{
+ h2o_headers_command_t *new_cmds;
+ size_t cnt;
+
+ if (*cmds != NULL) {
+ for (cnt = 0; (*cmds)[cnt].cmd != H2O_HEADERS_CMD_NULL; ++cnt)
+ ;
+ } else {
+ cnt = 0;
+ }
+
+ new_cmds = h2o_mem_alloc_shared(NULL, (cnt + 2) * sizeof(*new_cmds), NULL);
+ if (*cmds != NULL)
+ memcpy(new_cmds, *cmds, cnt * sizeof(*new_cmds));
+ new_cmds[cnt] = (h2o_headers_command_t){cmd, name, value};
+ new_cmds[cnt + 1] = (h2o_headers_command_t){H2O_HEADERS_CMD_NULL};
+
+ if (*cmds != NULL)
+ h2o_mem_release_shared(*cmds);
+ *cmds = new_cmds;
+}
+
+void h2o_rewrite_headers(h2o_mem_pool_t *pool, h2o_headers_t *headers, h2o_headers_command_t *cmd)
+{
+ h2o_header_t *target;
+
+ switch (cmd->cmd) {
+ case H2O_HEADERS_CMD_ADD:
+ goto AddHeader;
+ case H2O_HEADERS_CMD_APPEND:
+ if ((target = find_header(headers, cmd)) == NULL)
+ goto AddHeader;
+ goto AppendToken;
+ case H2O_HEADERS_CMD_MERGE:
+ if ((target = find_header(headers, cmd)) == NULL)
+ goto AddHeader;
+ if (h2o_contains_token(target->value.base, target->value.len, cmd->value.base, cmd->value.len, ','))
+ return;
+ goto AppendToken;
+ case H2O_HEADERS_CMD_SET:
+ remove_header(headers, cmd);
+ goto AddHeader;
+ case H2O_HEADERS_CMD_SETIFEMPTY:
+ if (find_header(headers, cmd) != NULL)
+ return;
+ goto AddHeader;
+ case H2O_HEADERS_CMD_UNSET:
+ remove_header(headers, cmd);
+ return;
+ }
+
+ assert(!"FIXME");
+ return;
+
+AddHeader:
+ if (h2o_iovec_is_token(cmd->name)) {
+ h2o_add_header(pool, headers, (void *)cmd->name, NULL, cmd->value.base, cmd->value.len);
+ } else {
+ h2o_add_header_by_str(pool, headers, cmd->name->base, cmd->name->len, 0, NULL, cmd->value.base, cmd->value.len);
+ }
+ return;
+
+AppendToken:
+ if (target->value.len != 0) {
+ h2o_iovec_t v;
+ v.len = target->value.len + 2 + cmd->value.len;
+ v.base = h2o_mem_alloc_pool(pool, v.len);
+ memcpy(v.base, target->value.base, target->value.len);
+ v.base[target->value.len] = ',';
+ v.base[target->value.len + 1] = ' ';
+ memcpy(v.base + target->value.len + 2, cmd->value.base, cmd->value.len);
+ target->value = v;
+ } else {
+ target->value = cmd->value;
+ }
+ return;
+}
diff --git a/debian/vendor-h2o/lib/handler/http2_debug_state.c b/debian/vendor-h2o/lib/handler/http2_debug_state.c
new file mode 100644
index 0000000..0aa0bd1
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/http2_debug_state.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Ichito Nagata
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <inttypes.h>
+#include "h2o.h"
+
+struct st_h2o_http2_debug_state_handler_t {
+ h2o_handler_t super;
+ int hpack_enabled;
+};
+
+static int on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_http2_debug_state_handler_t *self = (void *)_self;
+
+ static h2o_generator_t generator = {NULL, NULL};
+
+ if (req->conn->callbacks->get_debug_state == NULL) {
+ return -1;
+ }
+
+ h2o_http2_debug_state_t *debug_state = req->conn->callbacks->get_debug_state(req, self->hpack_enabled);
+
+ // stringify these variables to embed in Debug Header
+ h2o_iovec_t conn_flow_in, conn_flow_out;
+ conn_flow_in.base = h2o_mem_alloc_pool(&req->pool, sizeof(H2O_INT64_LONGEST_STR));
+ conn_flow_in.len = sprintf(conn_flow_in.base, "%zd", debug_state->conn_flow_in);
+ conn_flow_out.base = h2o_mem_alloc_pool(&req->pool, sizeof(H2O_INT64_LONGEST_STR));
+ conn_flow_out.len = sprintf(conn_flow_out.base, "%zd", debug_state->conn_flow_out);
+
+ req->res.status = 200;
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("application/json; charset=utf-8"));
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("no-cache, no-store"));
+ h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("conn-flow-in"), 0, NULL, conn_flow_in.base, conn_flow_in.len);
+ h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("conn-flow-out"), 0, NULL, conn_flow_out.base,
+ conn_flow_out.len);
+
+ h2o_start_response(req, &generator);
+ h2o_send(req, debug_state->json.entries,
+ h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : debug_state->json.size,
+ H2O_SEND_STATE_FINAL);
+ return 0;
+}
+
+void h2o_http2_debug_state_register(h2o_hostconf_t *conf, int hpack_enabled)
+{
+ h2o_pathconf_t *pathconf = h2o_config_register_path(conf, "/.well-known/h2/state", 0);
+ struct st_h2o_http2_debug_state_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self));
+ self->super.on_req = on_req;
+ self->hpack_enabled = hpack_enabled;
+}
diff --git a/debian/vendor-h2o/lib/handler/mimemap.c b/debian/vendor-h2o/lib/handler/mimemap.c
new file mode 100644
index 0000000..764362d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mimemap.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2014 DeNA Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "khash.h"
+#include "h2o.h"
+
+KHASH_MAP_INIT_STR(extmap, h2o_mimemap_type_t *)
+
+static inline khint_t hash_mimemap_type(h2o_mimemap_type_t *mimetype)
+{
+ khint_t h = 0;
+ size_t i;
+ for (i = 0; i != mimetype->data.mimetype.len; ++i)
+ h = (h << 5) - h + (khint_t)mimetype->data.mimetype.base[i];
+ return h;
+}
+
+static inline int mimemap_type_equals(h2o_mimemap_type_t *x, h2o_mimemap_type_t *y)
+{
+ return h2o_memis(x->data.mimetype.base, x->data.mimetype.len, y->data.mimetype.base, y->data.mimetype.len);
+}
+
+KHASH_INIT(typeset, h2o_mimemap_type_t *, char, 0, hash_mimemap_type, mimemap_type_equals)
+
+h2o_mime_attributes_t h2o_mime_attributes_as_is;
+
+struct st_h2o_mimemap_t {
+ khash_t(extmap) * extmap;
+ khash_t(typeset) * typeset; /* refs point to the entries in extmap */
+ h2o_mimemap_type_t *default_type;
+ size_t num_dynamic;
+};
+
+static h2o_iovec_t dupref(const char *s)
+{
+ h2o_iovec_t ret;
+ ret.len = strlen(s);
+ ret.base = h2o_mem_alloc_shared(NULL, ret.len + 1, NULL);
+ memcpy(ret.base, s, ret.len + 1);
+ return ret;
+}
+
+static void on_dispose(void *_mimemap)
+{
+ h2o_mimemap_t *mimemap = _mimemap;
+ const char *ext;
+ h2o_mimemap_type_t *type;
+
+ kh_destroy(typeset, mimemap->typeset);
+ kh_foreach(mimemap->extmap, ext, type, {
+ h2o_mem_release_shared((char *)ext);
+ h2o_mem_release_shared(type);
+ });
+ kh_destroy(extmap, mimemap->extmap);
+ h2o_mem_release_shared(mimemap->default_type);
+}
+
+static void on_unlink(h2o_mimemap_t *mimemap, h2o_mimemap_type_t *type)
+{
+ switch (type->type) {
+ case H2O_MIMEMAP_TYPE_MIMETYPE:
+ break;
+ case H2O_MIMEMAP_TYPE_DYNAMIC:
+ --mimemap->num_dynamic;
+ break;
+ }
+}
+
+static void on_link(h2o_mimemap_t *mimemap, h2o_mimemap_type_t *type)
+{
+ switch (type->type) {
+ case H2O_MIMEMAP_TYPE_MIMETYPE:
+ break;
+ case H2O_MIMEMAP_TYPE_DYNAMIC:
+ ++mimemap->num_dynamic;
+ break;
+ }
+}
+
+static void rebuild_typeset(h2o_mimemap_t *mimemap)
+{
+ kh_clear(typeset, mimemap->typeset);
+
+ const char *ext;
+ h2o_mimemap_type_t *mime;
+ kh_foreach(mimemap->extmap, ext, mime, {
+ if (mime->type == H2O_MIMEMAP_TYPE_MIMETYPE) {
+ khiter_t iter = kh_get(typeset, mimemap->typeset, mime);
+ if (iter == kh_end(mimemap->typeset)) {
+ int r;
+ kh_put(typeset, mimemap->typeset, mime, &r);
+ }
+ }
+ });
+}
+
+static h2o_mimemap_type_t *create_extension_type(const char *mime, h2o_mime_attributes_t *attr)
+{
+ h2o_mimemap_type_t *type = h2o_mem_alloc_shared(NULL, sizeof(*type) + strlen(mime) + 1, NULL);
+ size_t i;
+
+ memset(type, 0, sizeof(*type));
+
+ type->type = H2O_MIMEMAP_TYPE_MIMETYPE;
+
+ /* normalize-copy type->data.mimetype */
+ type->data.mimetype.base = (char *)type + sizeof(*type);
+ for (i = 0; mime[i] != '\0' && mime[i] != ';'; ++i)
+ type->data.mimetype.base[i] = h2o_tolower(mime[i]);
+ for (; mime[i] != '\0'; ++i)
+ type->data.mimetype.base[i] = mime[i];
+ type->data.mimetype.base[i] = '\0';
+ type->data.mimetype.len = i;
+
+ if (attr != NULL) {
+ type->data.attr = *attr;
+ } else {
+ h2o_mimemap_get_default_attributes(mime, &type->data.attr);
+ }
+
+ return type;
+}
+
+static void dispose_dynamic_type(h2o_mimemap_type_t *type)
+{
+ h2o_config_dispose_pathconf(&type->data.dynamic.pathconf);
+}
+
+static h2o_mimemap_type_t *create_dynamic_type(h2o_globalconf_t *globalconf, h2o_mimemap_t *mimemap)
+{
+ h2o_mimemap_type_t *type = h2o_mem_alloc_shared(NULL, sizeof(*type), (void (*)(void *))dispose_dynamic_type);
+
+ type->type = H2O_MIMEMAP_TYPE_DYNAMIC;
+ memset(&type->data.dynamic, 0, sizeof(type->data.dynamic));
+ h2o_config_init_pathconf(&type->data.dynamic.pathconf, globalconf, NULL, mimemap);
+
+ return type;
+}
+
+h2o_mimemap_t *h2o_mimemap_create()
+{
+ h2o_mimemap_t *mimemap = h2o_mem_alloc_shared(NULL, sizeof(*mimemap), on_dispose);
+
+ mimemap->extmap = kh_init(extmap);
+ mimemap->typeset = kh_init(typeset);
+ mimemap->default_type = create_extension_type("application/octet-stream", NULL);
+ mimemap->num_dynamic = 0;
+ on_link(mimemap, mimemap->default_type);
+
+ { /* setup the tiny default */
+ static const char *default_types[] = {
+#define MIMEMAP(ext, mime) ext, mime,
+#include "mimemap/defaults.c.h"
+#undef MIMEMAP
+ NULL};
+ const char **p;
+ for (p = default_types; *p != NULL; p += 2)
+ h2o_mimemap_define_mimetype(mimemap, p[0], p[1], NULL);
+ }
+ rebuild_typeset(mimemap);
+
+ return mimemap;
+}
+
+h2o_mimemap_t *h2o_mimemap_clone(h2o_mimemap_t *src)
+{
+ h2o_mimemap_t *dst = h2o_mem_alloc_shared(NULL, sizeof(*dst), on_dispose);
+ const char *ext;
+ h2o_mimemap_type_t *type;
+
+ dst->extmap = kh_init(extmap);
+ dst->typeset = kh_init(typeset);
+ kh_foreach(src->extmap, ext, type, {
+ int r;
+ khiter_t iter = kh_put(extmap, dst->extmap, ext, &r);
+ kh_val(dst->extmap, iter) = type;
+ h2o_mem_addref_shared((char *)ext);
+ h2o_mem_addref_shared(type);
+ on_link(dst, type);
+ });
+ dst->default_type = src->default_type;
+ h2o_mem_addref_shared(dst->default_type);
+ on_link(dst, dst->default_type);
+ rebuild_typeset(dst);
+
+ return dst;
+}
+
+void h2o_mimemap_on_context_init(h2o_mimemap_t *mimemap, h2o_context_t *ctx)
+{
+ const char *ext;
+ h2o_mimemap_type_t *type;
+
+ kh_foreach(mimemap->extmap, ext, type, {
+ switch (type->type) {
+ case H2O_MIMEMAP_TYPE_DYNAMIC:
+ h2o_context_init_pathconf_context(ctx, &type->data.dynamic.pathconf);
+ break;
+ case H2O_MIMEMAP_TYPE_MIMETYPE:
+ break;
+ }
+ });
+}
+
+void h2o_mimemap_on_context_dispose(h2o_mimemap_t *mimemap, h2o_context_t *ctx)
+{
+ const char *ext;
+ h2o_mimemap_type_t *type;
+
+ kh_foreach(mimemap->extmap, ext, type, {
+ switch (type->type) {
+ case H2O_MIMEMAP_TYPE_DYNAMIC:
+ h2o_context_dispose_pathconf_context(ctx, &type->data.dynamic.pathconf);
+ break;
+ case H2O_MIMEMAP_TYPE_MIMETYPE:
+ break;
+ }
+ });
+}
+
+int h2o_mimemap_has_dynamic_type(h2o_mimemap_t *mimemap)
+{
+ return mimemap->num_dynamic != 0;
+}
+
+void h2o_mimemap_set_default_type(h2o_mimemap_t *mimemap, const char *mime, h2o_mime_attributes_t *attr)
+{
+ h2o_mimemap_type_t *new_type;
+
+ /* obtain or create new type */
+ if ((new_type = h2o_mimemap_get_type_by_mimetype(mimemap, h2o_iovec_init(mime, strlen(mime)), 1)) != NULL &&
+ (attr == NULL || memcmp(&new_type->data.attr, attr, sizeof(*attr)) == 0)) {
+ h2o_mem_addref_shared(new_type);
+ } else {
+ new_type = create_extension_type(mime, attr);
+ }
+
+ /* unlink the old one */
+ on_unlink(mimemap, mimemap->default_type);
+ h2o_mem_release_shared(mimemap->default_type);
+
+ /* update */
+ mimemap->default_type = new_type;
+ on_link(mimemap, new_type);
+ rebuild_typeset(mimemap);
+}
+
+static void set_type(h2o_mimemap_t *mimemap, const char *ext, h2o_mimemap_type_t *type)
+{
+ /* obtain key, and remove the old value */
+ khiter_t iter = kh_get(extmap, mimemap->extmap, ext);
+ if (iter != kh_end(mimemap->extmap)) {
+ h2o_mimemap_type_t *oldtype = kh_val(mimemap->extmap, iter);
+ on_unlink(mimemap, oldtype);
+ h2o_mem_release_shared(oldtype);
+ } else {
+ int ret;
+ iter = kh_put(extmap, mimemap->extmap, dupref(ext).base, &ret);
+ assert(iter != kh_end(mimemap->extmap));
+ }
+
+ /* update */
+ h2o_mem_addref_shared(type);
+ kh_val(mimemap->extmap, iter) = type;
+ on_link(mimemap, type);
+ rebuild_typeset(mimemap);
+}
+
+void h2o_mimemap_define_mimetype(h2o_mimemap_t *mimemap, const char *ext, const char *mime, h2o_mime_attributes_t *attr)
+{
+ h2o_mimemap_type_t *new_type;
+
+ if ((new_type = h2o_mimemap_get_type_by_mimetype(mimemap, h2o_iovec_init(mime, strlen(mime)), 1)) != NULL &&
+ (attr == NULL || memcmp(&new_type->data.attr, attr, sizeof(*attr)) == 0)) {
+ h2o_mem_addref_shared(new_type);
+ } else {
+ new_type = create_extension_type(mime, attr);
+ }
+ set_type(mimemap, ext, new_type);
+ h2o_mem_release_shared(new_type);
+}
+
+h2o_mimemap_type_t *h2o_mimemap_define_dynamic(h2o_mimemap_t *mimemap, const char **exts, h2o_globalconf_t *globalconf)
+{
+ /* FIXME: fix memory leak introduced by this a cyclic link (mimemap -> new_type -> mimemap)
+ * note also that we may want to update the reference from the dynamic type to the mimemap as we clone the mimemap,
+ * but doing so naively would cause unnecessary copies of fastcgi.spawns... */
+ h2o_mimemap_type_t *new_type = create_dynamic_type(globalconf, mimemap);
+ size_t i;
+
+ for (i = 0; exts[i] != NULL; ++i)
+ set_type(mimemap, exts[i], new_type);
+ h2o_mem_release_shared(new_type);
+ return new_type;
+}
+
+void h2o_mimemap_remove_type(h2o_mimemap_t *mimemap, const char *ext)
+{
+ khiter_t iter = kh_get(extmap, mimemap->extmap, ext);
+ if (iter != kh_end(mimemap->extmap)) {
+ const char *key = kh_key(mimemap->extmap, iter);
+ h2o_mimemap_type_t *type = kh_val(mimemap->extmap, iter);
+ on_unlink(mimemap, type);
+ h2o_mem_release_shared(type);
+ kh_del(extmap, mimemap->extmap, iter);
+ h2o_mem_release_shared((char *)key);
+ rebuild_typeset(mimemap);
+ }
+}
+
+void h2o_mimemap_clear_types(h2o_mimemap_t *mimemap)
+{
+ khiter_t iter;
+
+ for (iter = kh_begin(mimemap->extmap); iter != kh_end(mimemap->extmap); ++iter) {
+ if (!kh_exist(mimemap->extmap, iter)) continue;
+ const char *key = kh_key(mimemap->extmap, iter);
+ h2o_mimemap_type_t *type = kh_val(mimemap->extmap, iter);
+ on_unlink(mimemap, type);
+ h2o_mem_release_shared(type);
+ kh_del(extmap, mimemap->extmap, iter);
+ h2o_mem_release_shared((char *)key);
+ }
+ rebuild_typeset(mimemap);
+}
+
+h2o_mimemap_type_t *h2o_mimemap_get_default_type(h2o_mimemap_t *mimemap)
+{
+ return mimemap->default_type;
+}
+
+h2o_mimemap_type_t *h2o_mimemap_get_type_by_extension(h2o_mimemap_t *mimemap, h2o_iovec_t ext)
+{
+ char lcbuf[256];
+
+ if (0 < ext.len && ext.len < sizeof(lcbuf)) {
+ memcpy(lcbuf, ext.base, ext.len);
+ h2o_strtolower(lcbuf, ext.len);
+ lcbuf[ext.len] = '\0';
+ khiter_t iter = kh_get(extmap, mimemap->extmap, lcbuf);
+ if (iter != kh_end(mimemap->extmap))
+ return kh_val(mimemap->extmap, iter);
+ }
+ return mimemap->default_type;
+}
+
+h2o_mimemap_type_t *h2o_mimemap_get_type_by_mimetype(h2o_mimemap_t *mimemap, h2o_iovec_t mime, int exact_match_only)
+{
+ h2o_mimemap_type_t key = {H2O_MIMEMAP_TYPE_MIMETYPE};
+ khiter_t iter;
+ size_t type_end_at;
+
+ /* exact match */
+ key.data.mimetype = mime;
+ if ((iter = kh_get(typeset, mimemap->typeset, &key)) != kh_end(mimemap->typeset))
+ return kh_key(mimemap->typeset, iter);
+
+ if (!exact_match_only) {
+ /* determine the end of the type */
+ for (type_end_at = 0; type_end_at != mime.len; ++type_end_at)
+ if (mime.base[type_end_at] == ';' || mime.base[type_end_at] == ' ')
+ goto HasAttributes;
+ }
+ return NULL;
+
+HasAttributes:
+ /* perform search without attributes */
+ key.data.mimetype.len = type_end_at;
+ if ((iter = kh_get(typeset, mimemap->typeset, &key)) != kh_end(mimemap->typeset))
+ return kh_key(mimemap->typeset, iter);
+
+ return NULL;
+}
+
+void h2o_mimemap_get_default_attributes(const char *_mime, h2o_mime_attributes_t *attr)
+{
+ char *mime = alloca(strlen(_mime) + 1);
+ strcpy(mime, _mime);
+
+ const char *type_end_at;
+
+ if ((type_end_at = strchr(mime, ';')) == NULL)
+ type_end_at = mime + strlen(mime);
+
+ *attr = (h2o_mime_attributes_t){0};
+
+ if (h2o_memis(mime, type_end_at - mime, H2O_STRLIT("text/css")) ||
+ h2o_memis(mime, type_end_at - mime, H2O_STRLIT("application/ecmascript")) ||
+ h2o_memis(mime, type_end_at - mime, H2O_STRLIT("application/javascript")) ||
+ h2o_memis(mime, type_end_at - mime, H2O_STRLIT("text/ecmascript")) ||
+ h2o_memis(mime, type_end_at - mime, H2O_STRLIT("text/javascript"))) {
+ attr->is_compressible = 1;
+ attr->priority = H2O_MIME_ATTRIBUTE_PRIORITY_HIGHEST;
+ } else if (h2o_memis(mime, type_end_at - mime, H2O_STRLIT("application/json")) || strncmp(mime, "text/", 5) == 0 ||
+ h2o_strstr(mime, type_end_at - mime, H2O_STRLIT("+xml")) != SIZE_MAX) {
+ attr->is_compressible = 1;
+ }
+}
diff --git a/debian/vendor-h2o/lib/handler/mimemap/defaults.c.h b/debian/vendor-h2o/lib/handler/mimemap/defaults.c.h
new file mode 100644
index 0000000..a395e0c
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mimemap/defaults.c.h
@@ -0,0 +1,109 @@
+MIMEMAP("3gp", "video/3gpp")
+MIMEMAP("3gpp", "video/3gpp")
+MIMEMAP("7z", "application/x-7z-compressed")
+MIMEMAP("ai", "application/postscript")
+MIMEMAP("apng", "image/apng")
+MIMEMAP("asf", "video/x-ms-asf")
+MIMEMAP("asx", "video/x-ms-asf")
+MIMEMAP("atom", "application/atom+xml")
+MIMEMAP("avi", "video/x-msvideo")
+MIMEMAP("bin", "application/octet-stream")
+MIMEMAP("bmp", "image/x-ms-bmp")
+MIMEMAP("cco", "application/x-cocoa")
+MIMEMAP("crt", "application/x-x509-ca-cert")
+MIMEMAP("css", "text/css")
+MIMEMAP("csv", "text/csv")
+MIMEMAP("deb", "application/octet-stream")
+MIMEMAP("der", "application/x-x509-ca-cert")
+MIMEMAP("dll", "application/octet-stream")
+MIMEMAP("dmg", "application/octet-stream")
+MIMEMAP("doc", "application/msword")
+MIMEMAP("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
+MIMEMAP("ear", "application/java-archive")
+MIMEMAP("eot", "application/vnd.ms-fontobject")
+MIMEMAP("eps", "application/postscript")
+MIMEMAP("exe", "application/octet-stream")
+MIMEMAP("flv", "video/x-flv")
+MIMEMAP("gif", "image/gif")
+MIMEMAP("hqx", "application/mac-binhex40")
+MIMEMAP("htc", "text/x-component")
+MIMEMAP("htm", "text/html")
+MIMEMAP("html", "text/html")
+MIMEMAP("ico", "image/x-icon")
+MIMEMAP("img", "application/octet-stream")
+MIMEMAP("iso", "application/octet-stream")
+MIMEMAP("jad", "text/vnd.sun.j2me.app-descriptor")
+MIMEMAP("jar", "application/java-archive")
+MIMEMAP("jardiff", "application/x-java-archive-diff")
+MIMEMAP("jng", "image/x-jng")
+MIMEMAP("jnlp", "application/x-java-jnlp-file")
+MIMEMAP("jpeg", "image/jpeg")
+MIMEMAP("jpg", "image/jpeg")
+MIMEMAP("jxr", "image/jxr")
+MIMEMAP("js", "application/javascript")
+MIMEMAP("json", "application/json")
+MIMEMAP("kar", "audio/midi")
+MIMEMAP("kml", "application/vnd.google-earth.kml+xml")
+MIMEMAP("kmz", "application/vnd.google-earth.kmz")
+MIMEMAP("m3u8", "application/vnd.apple.mpegurl")
+MIMEMAP("m4a", "audio/x-m4a")
+MIMEMAP("m4v", "video/x-m4v")
+MIMEMAP("md", "text/markdown")
+MIMEMAP("mid", "audio/midi")
+MIMEMAP("midi", "audio/midi")
+MIMEMAP("mml", "text/mathml")
+MIMEMAP("mng", "video/x-mng")
+MIMEMAP("mov", "video/quicktime")
+MIMEMAP("mp3", "audio/mpeg")
+MIMEMAP("mp4", "video/mp4")
+MIMEMAP("mpeg", "video/mpeg")
+MIMEMAP("mpg", "video/mpeg")
+MIMEMAP("msi", "application/octet-stream")
+MIMEMAP("msm", "application/octet-stream")
+MIMEMAP("msp", "application/octet-stream")
+MIMEMAP("ogg", "audio/ogg")
+MIMEMAP("opus", "audio/ogg")
+MIMEMAP("pdb", "application/x-pilot")
+MIMEMAP("pdf", "application/pdf")
+MIMEMAP("pem", "application/x-x509-ca-cert")
+MIMEMAP("pl", "application/x-perl")
+MIMEMAP("pm", "application/x-perl")
+MIMEMAP("png", "image/png")
+MIMEMAP("ppt", "application/vnd.ms-powerpoint")
+MIMEMAP("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
+MIMEMAP("prc", "application/x-pilot")
+MIMEMAP("ps", "application/postscript")
+MIMEMAP("ra", "audio/x-realaudio")
+MIMEMAP("rar", "application/x-rar-compressed")
+MIMEMAP("rpm", "application/x-redhat-package-manager")
+MIMEMAP("rss", "application/rss+xml")
+MIMEMAP("rtf", "application/rtf")
+MIMEMAP("run", "application/x-makeself")
+MIMEMAP("sea", "application/x-sea")
+MIMEMAP("shtml", "text/html")
+MIMEMAP("sit", "application/x-stuffit")
+MIMEMAP("svg", "image/svg+xml")
+MIMEMAP("svgz", "image/svg+xml")
+MIMEMAP("swf", "application/x-shockwave-flash")
+MIMEMAP("tcl", "application/x-tcl")
+MIMEMAP("tif", "image/tiff")
+MIMEMAP("tiff", "image/tiff")
+MIMEMAP("tk", "application/x-tcl")
+MIMEMAP("ts", "video/mp2t")
+MIMEMAP("txt", "text/plain")
+MIMEMAP("war", "application/java-archive")
+MIMEMAP("wbmp", "image/vnd.wap.wbmp")
+MIMEMAP("webm", "video/webm")
+MIMEMAP("webp", "image/webp")
+MIMEMAP("wml", "text/vnd.wap.wml")
+MIMEMAP("wmlc", "application/vnd.wap.wmlc")
+MIMEMAP("wmv", "video/x-ms-wmv")
+MIMEMAP("woff", "application/font-woff")
+MIMEMAP("woff2", "font/woff2")
+MIMEMAP("xhtml", "application/xhtml+xml")
+MIMEMAP("xls", "application/vnd.ms-excel")
+MIMEMAP("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+MIMEMAP("xml", "text/xml")
+MIMEMAP("xpi", "application/x-xpinstall")
+MIMEMAP("xspf", "application/xspf+xml")
+MIMEMAP("zip", "application/zip")
diff --git a/debian/vendor-h2o/lib/handler/mruby.c b/debian/vendor-h2o/lib/handler/mruby.c
new file mode 100644
index 0000000..af2af53
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby.c
@@ -0,0 +1,938 @@
+/*
+ * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Ryosuke Matsumoto,
+ * Masayoshi Takahashi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <mruby.h>
+#include <mruby/proc.h>
+#include <mruby/array.h>
+#include <mruby/class.h>
+#include <mruby/compile.h>
+#include <mruby/error.h>
+#include <mruby/hash.h>
+#include <mruby/string.h>
+#include <mruby/throw.h>
+#include <mruby/variable.h>
+#include <mruby_input_stream.h>
+#include "h2o.h"
+#include "h2o/mruby_.h"
+#include "mruby/embedded.c.h"
+
+#define STATUS_FALLTHRU 399
+#define FALLTHRU_SET_PREFIX "x-fallthru-set-"
+
+#define FREEZE_STRING(v) MRB_SET_FROZEN_FLAG(mrb_obj_ptr(v))
+
+__thread h2o_mruby_generator_t *h2o_mruby_current_generator = NULL;
+
+void h2o_mruby__assert_failed(mrb_state *mrb, const char *file, int line)
+{
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ fprintf(stderr, "unexpected ruby error at file: \"%s\", line %d: %s", file, line, error->as.heap.ptr);
+ abort();
+}
+
+void h2o_mruby_setup_globals(mrb_state *mrb)
+{
+ const char *root = getenv("H2O_ROOT");
+ if (root == NULL)
+ root = H2O_TO_STR(H2O_ROOT);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$H2O_ROOT"), mrb_str_new(mrb, root, strlen(root)));
+
+ h2o_mruby_eval_expr(mrb, "$LOAD_PATH << \"#{$H2O_ROOT}/share/h2o/mruby\"");
+ h2o_mruby_assert(mrb);
+
+ /* require core modules and include built-in libraries */
+ h2o_mruby_eval_expr(mrb, "require \"#{$H2O_ROOT}/share/h2o/mruby/preloads.rb\"");
+ if (mrb->exc != NULL) {
+ if (mrb_obj_is_instance_of(mrb, mrb_obj_value(mrb->exc), mrb_class_get(mrb, "LoadError"))) {
+ fprintf(stderr, "file \"%s/%s\" not found. Did you forget to run `make install` ?", root,
+ "share/h2o/mruby/preloads.rb");
+ } else {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ fprintf(stderr, "an error occurred while loading %s/%s: %s", root, "share/h2o/mruby/preloads.rb", error->as.heap.ptr);
+ }
+ abort();
+ }
+}
+
+mrb_value h2o_mruby_to_str(mrb_state *mrb, mrb_value v)
+{
+ if (!mrb_string_p(v))
+ H2O_MRUBY_EXEC_GUARD({ v = mrb_str_to_str(mrb, v); });
+ return v;
+}
+
+mrb_value h2o_mruby_eval_expr(mrb_state *mrb, const char *expr)
+{
+ return mrb_funcall(mrb, mrb_top_self(mrb), "eval", 1, mrb_str_new_cstr(mrb, expr));
+}
+
+void h2o_mruby_define_callback(mrb_state *mrb, const char *name, int id)
+{
+ mrb_value args[2];
+ args[0] = mrb_str_new_cstr(mrb, name);
+ args[1] = mrb_fixnum_value(id);
+ mrb_funcall_argv(mrb, mrb_top_self(mrb), mrb_intern_lit(mrb, "_h2o_define_callback"), 2, args);
+
+ if (mrb->exc != NULL) {
+ fprintf(stderr, "failed to define mruby function: %s\n", name);
+ h2o_mruby_assert(mrb);
+ }
+}
+
+mrb_value h2o_mruby_create_data_instance(mrb_state *mrb, mrb_value class_obj, void *ptr, const mrb_data_type *type)
+{
+ struct RClass *klass = mrb_class_ptr(class_obj);
+ struct RData *data = mrb_data_object_alloc(mrb, klass, ptr, type);
+ return mrb_obj_value(data);
+}
+
+mrb_value h2o_mruby_compile_code(mrb_state *mrb, h2o_mruby_config_vars_t *config, char *errbuf)
+{
+ mrbc_context *cxt;
+ struct mrb_parser_state *parser;
+ struct RProc *proc = NULL;
+ mrb_value result = mrb_nil_value();
+
+ /* parse */
+ if ((cxt = mrbc_context_new(mrb)) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ if (config->path != NULL)
+ mrbc_filename(mrb, cxt, config->path);
+ cxt->capture_errors = 1;
+ cxt->lineno = config->lineno;
+ if ((parser = mrb_parse_nstring(mrb, config->source.base, (int)config->source.len, cxt)) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ /* return erro if errbuf is supplied, or abort */
+ if (parser->nerr != 0) {
+ if (errbuf == NULL) {
+ fprintf(stderr, "%s: internal error (unexpected state)\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ snprintf(errbuf, 256, "line %d:%s", parser->error_buffer[0].lineno, parser->error_buffer[0].message);
+ strcat(errbuf, "\n\n");
+ if (h2o_str_at_position(errbuf + strlen(errbuf), config->source.base, config->source.len,
+ parser->error_buffer[0].lineno - config->lineno + 1, parser->error_buffer[0].column) != 0) {
+ /* remove trailing "\n\n" in case we failed to append the source code at the error location */
+ errbuf[strlen(errbuf) - 2] = '\0';
+ }
+ goto Exit;
+ }
+ /* generate code */
+ if ((proc = mrb_generate_code(mrb, parser)) == NULL) {
+ fprintf(stderr, "%s: internal error (mrb_generate_code failed)\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+
+ /* adjust stack length of toplevel environment (see https://github.com/h2o/h2o/issues/1464#issuecomment-337880408) */
+ if (mrb->c->cibase->env) {
+ struct REnv *e = mrb->c->cibase->env;
+ if (MRB_ENV_STACK_LEN(e) < proc->body.irep->nlocals)
+ MRB_SET_ENV_STACK_LEN(e, proc->body.irep->nlocals);
+ }
+
+ /* reset configuration context */
+ h2o_mruby_eval_expr(mrb, "H2O::ConfigurationContext.reset");
+ h2o_mruby_assert(mrb);
+
+ /* run code and generate handler */
+ result = mrb_run(mrb, proc, mrb_top_self(mrb));
+ if (mrb->exc != NULL) {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ snprintf(errbuf, 256, "%s", error->as.heap.ptr);
+ mrb->exc = 0;
+ result = mrb_nil_value();
+ goto Exit;
+ } else if (mrb_nil_p(result)) {
+ snprintf(errbuf, 256, "returned value is not callable");
+ goto Exit;
+ }
+
+ /* call post_handler_generation hooks */
+ mrb_funcall_argv(mrb, h2o_mruby_eval_expr(mrb, "H2O::ConfigurationContext.instance"),
+ mrb_intern_lit(mrb, "call_post_handler_generation_hooks"), 1, &result);
+ if (mrb->exc != NULL) {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ snprintf(errbuf, 256, "%s", error->as.heap.ptr);
+ mrb->exc = 0;
+ result = mrb_nil_value();
+ goto Exit;
+ }
+
+Exit:
+ mrb_parser_free(parser);
+ mrbc_context_free(mrb, cxt);
+ return result;
+}
+
+static h2o_iovec_t convert_header_name_to_env(h2o_mem_pool_t *pool, const char *name, size_t len)
+{
+#define KEY_PREFIX "HTTP_"
+#define KEY_PREFIX_LEN (sizeof(KEY_PREFIX) - 1)
+
+ h2o_iovec_t ret;
+
+ ret.len = len + KEY_PREFIX_LEN;
+ ret.base = h2o_mem_alloc_pool(pool, ret.len);
+
+ memcpy(ret.base, KEY_PREFIX, KEY_PREFIX_LEN);
+
+ char *d = ret.base + KEY_PREFIX_LEN;
+ for (; len != 0; ++name, --len)
+ *d++ = *name == '-' ? '_' : h2o_toupper(*name);
+
+ return ret;
+
+#undef KEY_PREFIX
+#undef KEY_PREFIX_LEN
+}
+
+static mrb_value build_constants(mrb_state *mrb, const char *server_name, size_t server_name_len)
+{
+ mrb_value ary = mrb_ary_new_capa(mrb, H2O_MRUBY_NUM_CONSTANTS);
+ mrb_int i;
+
+ int gc_arena = mrb_gc_arena_save(mrb);
+
+ {
+ h2o_mem_pool_t pool;
+ h2o_mem_init_pool(&pool);
+ for (i = 0; i != H2O_MAX_TOKENS; ++i) {
+ const h2o_token_t *token = h2o__tokens + i;
+ mrb_value lit = mrb_nil_value();
+ if (token == H2O_TOKEN_CONTENT_TYPE) {
+ lit = mrb_str_new_lit(mrb, "CONTENT_TYPE");
+ } else if (token->buf.len != 0) {
+ h2o_iovec_t n = convert_header_name_to_env(&pool, token->buf.base, token->buf.len);
+ lit = mrb_str_new(mrb, n.base, n.len);
+ }
+ if (mrb_string_p(lit)) {
+ FREEZE_STRING(lit);
+ mrb_ary_set(mrb, ary, i, lit);
+ }
+ }
+ h2o_mem_clear_pool(&pool);
+ }
+
+#define SET_STRING(idx, value) \
+ do { \
+ mrb_value lit = (value); \
+ FREEZE_STRING(lit); \
+ mrb_ary_set(mrb, ary, idx, lit); \
+ } while (0)
+#define SET_LITERAL(idx, str) SET_STRING(idx, mrb_str_new_lit(mrb, str))
+
+ SET_LITERAL(H2O_MRUBY_LIT_REQUEST_METHOD, "REQUEST_METHOD");
+ SET_LITERAL(H2O_MRUBY_LIT_SCRIPT_NAME, "SCRIPT_NAME");
+ SET_LITERAL(H2O_MRUBY_LIT_PATH_INFO, "PATH_INFO");
+ SET_LITERAL(H2O_MRUBY_LIT_QUERY_STRING, "QUERY_STRING");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_NAME, "SERVER_NAME");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_ADDR, "SERVER_ADDR");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_PORT, "SERVER_PORT");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_PROTOCOL, "SERVER_PROTOCOL");
+ SET_LITERAL(H2O_MRUBY_LIT_CONTENT_LENGTH, "CONTENT_LENGTH");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_ADDR, "REMOTE_ADDR");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_PORT, "REMOTE_PORT");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_USER, "REMOTE_USER");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_URL_SCHEME, "rack.url_scheme");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_MULTITHREAD, "rack.multithread");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_MULTIPROCESS, "rack.multiprocess");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_RUN_ONCE, "rack.run_once");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_HIJACK_, "rack.hijack?");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_INPUT, "rack.input");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_ERRORS, "rack.errors");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_SOFTWARE, "SERVER_SOFTWARE");
+ SET_STRING(H2O_MRUBY_LIT_SERVER_SOFTWARE_VALUE, mrb_str_new(mrb, server_name, server_name_len));
+ SET_LITERAL(H2O_MRUBY_LIT_SEPARATOR_COMMA, ", ");
+ SET_LITERAL(H2O_MRUBY_LIT_SEPARATOR_SEMICOLON, "; ");
+
+#undef SET_LITERAL
+#undef SET_STRING
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_CORE);
+ h2o_mruby_assert(mrb);
+
+ mrb_ary_set(mrb, ary, H2O_MRUBY_PROC_EACH_TO_ARRAY,
+ mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "_h2o_proc_each_to_array", 0));
+ h2o_mruby_assert(mrb);
+
+ /* sends exception using H2O_MRUBY_CALLBACK_ID_EXCEPTION_RAISED */
+ mrb_ary_set(mrb, ary, H2O_MRUBY_PROC_APP_TO_FIBER,
+ mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "_h2o_proc_app_to_fiber", 0));
+ h2o_mruby_assert(mrb);
+
+ mrb_gc_arena_restore(mrb, gc_arena);
+ return ary;
+}
+
+static h2o_mruby_shared_context_t *create_shared_context(h2o_context_t *ctx)
+{
+ /* init mruby in every thread */
+ h2o_mruby_shared_context_t *shared_ctx = h2o_mem_alloc(sizeof(*shared_ctx));
+ if ((shared_ctx->mrb = mrb_open()) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ h2o_mruby_setup_globals(shared_ctx->mrb);
+ shared_ctx->constants = build_constants(shared_ctx->mrb, ctx->globalconf->server_name.base, ctx->globalconf->server_name.len);
+ shared_ctx->symbols.sym_call = mrb_intern_lit(shared_ctx->mrb, "call");
+ shared_ctx->symbols.sym_close = mrb_intern_lit(shared_ctx->mrb, "close");
+ shared_ctx->symbols.sym_method = mrb_intern_lit(shared_ctx->mrb, "method");
+ shared_ctx->symbols.sym_headers = mrb_intern_lit(shared_ctx->mrb, "headers");
+ shared_ctx->symbols.sym_body = mrb_intern_lit(shared_ctx->mrb, "body");
+ shared_ctx->symbols.sym_async = mrb_intern_lit(shared_ctx->mrb, "async");
+
+ h2o_mruby_send_chunked_init_context(shared_ctx);
+ h2o_mruby_http_request_init_context(shared_ctx);
+
+ return shared_ctx;
+}
+
+static void dispose_shared_context(void *data)
+{
+ if (data == NULL)
+ return;
+ h2o_mruby_shared_context_t *shared_ctx = (h2o_mruby_shared_context_t *)data;
+ mrb_close(shared_ctx->mrb);
+ free(shared_ctx);
+}
+
+static h2o_mruby_shared_context_t *get_shared_context(h2o_context_t *ctx)
+{
+ static size_t key = SIZE_MAX;
+ void **data = h2o_context_get_storage(ctx, &key, dispose_shared_context);
+ if (*data == NULL) {
+ *data = create_shared_context(ctx);
+ }
+ return *data;
+}
+
+static void on_context_init(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_context_t *handler_ctx = h2o_mem_alloc(sizeof(*handler_ctx));
+
+ handler_ctx->handler = handler;
+ handler_ctx->shared = get_shared_context(ctx);
+
+ /* compile code (must be done for each thread) */
+ int arena = mrb_gc_arena_save(handler_ctx->shared->mrb);
+ mrb_value proc = h2o_mruby_compile_code(handler_ctx->shared->mrb, &handler->config, NULL);
+
+ handler_ctx->proc =
+ mrb_funcall_argv(handler_ctx->shared->mrb, mrb_ary_entry(handler_ctx->shared->constants, H2O_MRUBY_PROC_APP_TO_FIBER),
+ handler_ctx->shared->symbols.sym_call, 1, &proc);
+ h2o_mruby_assert(handler_ctx->shared->mrb);
+ mrb_gc_arena_restore(handler_ctx->shared->mrb, arena);
+ mrb_gc_protect(handler_ctx->shared->mrb, handler_ctx->proc);
+
+ h2o_context_set_handler_context(ctx, &handler->super, handler_ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_context_t *handler_ctx = h2o_context_get_handler_context(ctx, &handler->super);
+
+ if (handler_ctx == NULL)
+ return;
+
+ free(handler_ctx);
+}
+
+static void on_handler_dispose(h2o_handler_t *_handler)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+
+ free(handler->config.source.base);
+ free(handler->config.path);
+ free(handler);
+}
+
+static void report_exception(h2o_req_t *req, mrb_state *mrb)
+{
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ h2o_req_log_error(req, H2O_MRUBY_MODULE_NAME, "mruby raised: %s\n", error->as.heap.ptr);
+ mrb->exc = NULL;
+}
+
+static void stringify_address(h2o_conn_t *conn, socklen_t (*cb)(h2o_conn_t *conn, struct sockaddr *), mrb_state *mrb,
+ mrb_value *host, mrb_value *port)
+{
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+ char buf[NI_MAXHOST];
+
+ *host = mrb_nil_value();
+ *port = mrb_nil_value();
+
+ if ((sslen = cb(conn, (void *)&ss)) == 0)
+ return;
+ size_t l = h2o_socket_getnumerichost((void *)&ss, sslen, buf);
+ if (l != SIZE_MAX)
+ *host = mrb_str_new(mrb, buf, l);
+ int32_t p = h2o_socket_getport((void *)&ss);
+ if (p != -1) {
+ l = (int)sprintf(buf, "%" PRIu16, (uint16_t)p);
+ *port = mrb_str_new(mrb, buf, l);
+ }
+}
+
+static void on_rack_input_free(mrb_state *mrb, const char *base, mrb_int len, void *_input_stream)
+{
+ /* reset ref to input_stream */
+ mrb_value *input_stream = _input_stream;
+ *input_stream = mrb_nil_value();
+}
+
+static int build_env_sort_header_cb(const void *_x, const void *_y)
+{
+ const h2o_header_t *x = *(const h2o_header_t **)_x, *y = *(const h2o_header_t **)_y;
+ if (x->name->len < y->name->len)
+ return -1;
+ if (x->name->len > y->name->len)
+ return 1;
+ if (x->name->base != y->name->base) {
+ int r = memcmp(x->name->base, y->name->base, x->name->len);
+ if (r != 0)
+ return r;
+ }
+ assert(x != y);
+ /* the order of the headers having the same name needs to be retained */
+ return x < y ? -1 : 1;
+}
+
+static mrb_value build_path_info(mrb_state *mrb, h2o_req_t *req, size_t confpath_len_wo_slash)
+{
+ if (req->path_normalized.len == confpath_len_wo_slash)
+ return mrb_str_new_lit(mrb, "");
+
+ assert(req->path_normalized.len > confpath_len_wo_slash);
+
+ size_t path_info_start, path_info_end = req->query_at != SIZE_MAX ? req->query_at : req->path.len;
+
+ if (req->norm_indexes == NULL) {
+ path_info_start = confpath_len_wo_slash;
+ } else if (req->norm_indexes[0] == 0 && confpath_len_wo_slash == 0) {
+ /* path without leading slash */
+ path_info_start = 0;
+ } else {
+ path_info_start = req->norm_indexes[confpath_len_wo_slash] - 1;
+ }
+
+ return mrb_str_new(mrb, req->path.base + path_info_start, path_info_end - path_info_start);
+}
+
+static mrb_value build_env(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_shared_context_t *shared = generator->ctx->shared;
+ mrb_state *mrb = shared->mrb;
+ mrb_value env = mrb_hash_new_capa(mrb, 16);
+ char http_version[sizeof("HTTP/1.0")];
+ size_t http_version_sz;
+
+ /* environment */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REQUEST_METHOD),
+ mrb_str_new(mrb, generator->req->method.base, generator->req->method.len));
+
+ size_t confpath_len_wo_slash = generator->req->pathconf->path.len;
+ if (generator->req->pathconf->path.base[generator->req->pathconf->path.len - 1] == '/')
+ --confpath_len_wo_slash;
+ assert(confpath_len_wo_slash <= generator->req->path_normalized.len);
+
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SCRIPT_NAME),
+ mrb_str_new(mrb, generator->req->pathconf->path.base, confpath_len_wo_slash));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_PATH_INFO), build_path_info(mrb, generator->req, confpath_len_wo_slash));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_QUERY_STRING),
+ generator->req->query_at != SIZE_MAX ? mrb_str_new(mrb, generator->req->path.base + generator->req->query_at + 1,
+ generator->req->path.len - (generator->req->query_at + 1))
+ : mrb_str_new_lit(mrb, ""));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_NAME),
+ mrb_str_new(mrb, generator->req->hostconf->authority.host.base, generator->req->hostconf->authority.host.len));
+ http_version_sz = h2o_stringify_protocol_version(http_version, generator->req->version);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_PROTOCOL),
+ mrb_str_new(mrb, http_version, http_version_sz));
+ {
+ mrb_value h, p;
+ stringify_address(generator->req->conn, generator->req->conn->callbacks->get_sockname, mrb, &h, &p);
+ if (!mrb_nil_p(h))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_ADDR), h);
+ if (!mrb_nil_p(p))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_PORT), p);
+ }
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_TOKEN_HOST - h2o__tokens),
+ mrb_str_new(mrb, generator->req->authority.base, generator->req->authority.len));
+ if (generator->req->entity.base != NULL) {
+ char buf[32];
+ int l = sprintf(buf, "%zu", generator->req->entity.len);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_CONTENT_LENGTH), mrb_str_new(mrb, buf, l));
+ generator->rack_input = mrb_input_stream_value(mrb, NULL, 0);
+ mrb_input_stream_set_data(mrb, generator->rack_input, generator->req->entity.base, (mrb_int)generator->req->entity.len, 0,
+ on_rack_input_free, &generator->rack_input);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_INPUT), generator->rack_input);
+ }
+ {
+ mrb_value h, p;
+ stringify_address(generator->req->conn, generator->req->conn->callbacks->get_peername, mrb, &h, &p);
+ if (!mrb_nil_p(h))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REMOTE_ADDR), h);
+ if (!mrb_nil_p(p))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REMOTE_PORT), p);
+ }
+ {
+ size_t i;
+ for (i = 0; i != generator->req->env.size; i += 2) {
+ h2o_iovec_t *name = generator->req->env.entries + i, *value = name + 1;
+ mrb_hash_set(mrb, env, mrb_str_new(mrb, name->base, name->len), mrb_str_new(mrb, value->base, value->len));
+ }
+ }
+
+ { /* headers */
+ h2o_header_t **headers_sorted = alloca(sizeof(*headers_sorted) * generator->req->headers.size);
+ size_t i;
+ for (i = 0; i != generator->req->headers.size; ++i)
+ headers_sorted[i] = generator->req->headers.entries + i;
+ qsort(headers_sorted, generator->req->headers.size, sizeof(*headers_sorted), build_env_sort_header_cb);
+ for (i = 0; i != generator->req->headers.size; ++i) {
+ const h2o_header_t *header = headers_sorted[i];
+ mrb_value n, v;
+ if (h2o_iovec_is_token(header->name)) {
+ const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, header->name);
+ if (token == H2O_TOKEN_TRANSFER_ENCODING)
+ continue;
+ n = mrb_ary_entry(shared->constants, (mrb_int)(token - h2o__tokens));
+ } else {
+ h2o_iovec_t vec = convert_header_name_to_env(&generator->req->pool, header->name->base, header->name->len);
+ n = mrb_str_new(mrb, vec.base, vec.len);
+ }
+ v = mrb_str_new(mrb, header->value.base, header->value.len);
+ while (i < generator->req->headers.size - 1) {
+ if (!h2o_memis(headers_sorted[i + 1]->name->base, headers_sorted[i + 1]->name->len, header->name->base,
+ header->name->len))
+ break;
+ header = headers_sorted[++i];
+ v = mrb_str_append(mrb, v, mrb_ary_entry(shared->constants,
+ header->name == &H2O_TOKEN_COOKIE->buf ? H2O_MRUBY_LIT_SEPARATOR_SEMICOLON
+ : H2O_MRUBY_LIT_SEPARATOR_COMMA));
+ v = mrb_str_append(mrb, v, mrb_str_new(mrb, header->value.base, header->value.len));
+ }
+ mrb_hash_set(mrb, env, n, v);
+ }
+ }
+
+ /* rack.* */
+ /* TBD rack.version? */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_URL_SCHEME),
+ mrb_str_new(mrb, generator->req->scheme->name.base, generator->req->scheme->name.len));
+ /* we are using shared-none architecture, and therefore declare ourselves as multiprocess */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_MULTITHREAD), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_MULTIPROCESS), mrb_true_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_RUN_ONCE), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_HIJACK_), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_ERRORS),
+ mrb_gv_get(mrb, mrb_intern_lit(mrb, "$stderr")));
+
+ /* server name */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_SOFTWARE),
+ mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_SOFTWARE_VALUE));
+
+ return env;
+}
+
+static int handle_response_header(h2o_mruby_context_t *handler_ctx, h2o_iovec_t name, h2o_iovec_t value, void *_req)
+{
+ h2o_req_t *req = _req;
+ const h2o_token_t *token;
+ static const h2o_iovec_t fallthru_set_prefix = {H2O_STRLIT(FALLTHRU_SET_PREFIX)};
+
+ /* convert name to lowercase */
+ name = h2o_strdup(&req->pool, name.base, name.len);
+ h2o_strtolower(name.base, name.len);
+
+ if ((token = h2o_lookup_token(name.base, name.len)) != NULL) {
+ if (token->proxy_should_drop_for_res) {
+ /* skip */
+ } else if (token == H2O_TOKEN_CONTENT_LENGTH) {
+ req->res.content_length = h2o_strtosize(value.base, value.len);
+ } else {
+ value = h2o_strdup(&req->pool, value.base, value.len);
+ if (token == H2O_TOKEN_LINK) {
+ h2o_iovec_t new_value = h2o_push_path_in_link_header(req, value.base, value.len);
+ if (new_value.len)
+ h2o_add_header(&req->pool, &req->res.headers, token, NULL, new_value.base, new_value.len);
+ } else {
+ h2o_add_header(&req->pool, &req->res.headers, token, NULL, value.base, value.len);
+ }
+ }
+ } else if (name.len > fallthru_set_prefix.len &&
+ h2o_memis(name.base, fallthru_set_prefix.len, fallthru_set_prefix.base, fallthru_set_prefix.len)) {
+ /* register environment variables (with the name converted to uppercase, and using `_`) */
+ size_t i;
+ name.base += fallthru_set_prefix.len;
+ name.len -= fallthru_set_prefix.len;
+ for (i = 0; i != name.len; ++i)
+ name.base[i] = name.base[i] == '-' ? '_' : h2o_toupper(name.base[i]);
+ h2o_iovec_t *slot = h2o_req_getenv(req, name.base, name.len, 1);
+ *slot = h2o_strdup(&req->pool, value.base, value.len);
+ } else {
+ value = h2o_strdup(&req->pool, value.base, value.len);
+ h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, NULL, value.base, value.len);
+ }
+
+ return 0;
+}
+
+static void clear_rack_input(h2o_mruby_generator_t *generator)
+{
+ if (!mrb_nil_p(generator->rack_input))
+ mrb_input_stream_set_data(generator->ctx->shared->mrb, generator->rack_input, NULL, -1, 0, NULL, NULL);
+}
+
+static void on_generator_dispose(void *_generator)
+{
+ h2o_mruby_generator_t *generator = _generator;
+
+ clear_rack_input(generator);
+ generator->req = NULL;
+
+ if (generator->chunked != NULL)
+ h2o_mruby_send_chunked_dispose(generator);
+}
+
+static int on_req(h2o_handler_t *_handler, h2o_req_t *req)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_shared_context_t *shared = get_shared_context(req->conn->ctx);
+ int gc_arena = mrb_gc_arena_save(shared->mrb);
+
+ h2o_mruby_generator_t *generator = h2o_mem_alloc_shared(&req->pool, sizeof(*generator), on_generator_dispose);
+ generator->super.proceed = NULL;
+ generator->super.stop = NULL;
+ generator->req = req;
+ generator->ctx = h2o_context_get_handler_context(req->conn->ctx, &handler->super);
+ generator->rack_input = mrb_nil_value();
+ generator->chunked = NULL;
+
+ mrb_value env = build_env(generator);
+
+ int is_delegate = 0;
+ h2o_mruby_run_fiber(generator, generator->ctx->proc, env, &is_delegate);
+
+ mrb_gc_arena_restore(shared->mrb, gc_arena);
+ if (is_delegate)
+ return -1;
+ return 0;
+}
+
+static void send_response(h2o_mruby_generator_t *generator, mrb_int status, mrb_value resp, int *is_delegate)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ mrb_value body;
+ h2o_iovec_t content = {NULL};
+
+ /* set status */
+ generator->req->res.status = (int)status;
+
+ /* set headers */
+ if (h2o_mruby_iterate_headers(generator->ctx, mrb_ary_entry(resp, 1), handle_response_header, generator->req) != 0) {
+ assert(mrb->exc != NULL);
+ goto GotException;
+ }
+
+ /* return without processing body, if status is fallthru */
+ if (generator->req->res.status == STATUS_FALLTHRU) {
+ if (is_delegate != NULL)
+ *is_delegate = 1;
+ else
+ h2o_delegate_request_deferred(generator->req, &generator->ctx->handler->super);
+ return;
+ }
+
+ /* obtain body */
+ body = mrb_ary_entry(resp, 2);
+
+ /* flatten body if possible */
+ if (mrb_array_p(body)) {
+ mrb_int i, len = RARRAY_LEN(body);
+ /* calculate the length of the output, while at the same time converting the elements of the output array to string */
+ content.len = 0;
+ for (i = 0; i != len; ++i) {
+ mrb_value e = mrb_ary_entry(body, i);
+ if (!mrb_string_p(e)) {
+ e = h2o_mruby_to_str(mrb, e);
+ if (mrb->exc != NULL)
+ goto GotException;
+ mrb_ary_set(mrb, body, i, e);
+ }
+ content.len += RSTRING_LEN(e);
+ }
+ /* allocate memory, and copy the response */
+ char *dst = content.base = h2o_mem_alloc_pool(&generator->req->pool, content.len);
+ for (i = 0; i != len; ++i) {
+ mrb_value e = mrb_ary_entry(body, i);
+ assert(mrb_string_p(e));
+ memcpy(dst, RSTRING_PTR(e), RSTRING_LEN(e));
+ dst += RSTRING_LEN(e);
+ }
+ /* reset body to nil, now that we have read all data */
+ body = mrb_nil_value();
+ }
+
+ /* use fiber in case we need to call #each */
+ if (!mrb_nil_p(body)) {
+ h2o_start_response(generator->req, &generator->super);
+ mrb_value receiver = h2o_mruby_send_chunked_init(generator, body);
+ if (!mrb_nil_p(receiver))
+ h2o_mruby_run_fiber(generator, receiver, body, 0);
+ return;
+ }
+
+ /* send the entire response immediately */
+ if (status == 101 || status == 204 || status == 304 ||
+ h2o_memis(generator->req->input.method.base, generator->req->input.method.len, H2O_STRLIT("HEAD"))) {
+ h2o_start_response(generator->req, &generator->super);
+ h2o_send(generator->req, NULL, 0, H2O_SEND_STATE_FINAL);
+ } else {
+ if (content.len < generator->req->res.content_length) {
+ generator->req->res.content_length = content.len;
+ } else {
+ content.len = generator->req->res.content_length;
+ }
+ h2o_start_response(generator->req, &generator->super);
+ h2o_send(generator->req, &content, 1, H2O_SEND_STATE_FINAL);
+ }
+ return;
+
+GotException:
+ report_exception(generator->req, mrb);
+ h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0);
+}
+
+void h2o_mruby_run_fiber(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value input, int *is_delegate)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ mrb_value output;
+ mrb_int status;
+
+ if (!mrb_obj_eq(mrb, generator->ctx->proc, receiver)) {
+ mrb_gc_unregister(mrb, receiver);
+ mrb_gc_protect(mrb, receiver);
+ }
+
+ h2o_mruby_current_generator = generator;
+
+ while (1) {
+ /* send input to fiber */
+ output = mrb_funcall_argv(mrb, receiver, generator->ctx->shared->symbols.sym_call, 1, &input);
+ if (mrb->exc != NULL)
+ goto GotException;
+ if (!mrb_array_p(output)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "rack app did not return an array"));
+ goto GotException;
+ }
+ /* fetch status */
+ mrb_value v = mrb_to_int(mrb, mrb_ary_entry(output, 0));
+ if (mrb->exc != NULL)
+ goto GotException;
+ status = mrb_fixnum(v);
+ /* take special action depending on the status code */
+ if (status < 0) {
+ if (status == H2O_MRUBY_CALLBACK_ID_EXCEPTION_RAISED) {
+ mrb->exc = mrb_obj_ptr(mrb_ary_entry(output, 1));
+ goto GotException;
+ }
+ receiver = mrb_ary_entry(output, 1);
+ int next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_IMMEDIATE;
+ mrb_value args = mrb_ary_entry(output, 2);
+ if (mrb_array_p(args)) {
+ switch (status) {
+ case H2O_MRUBY_CALLBACK_ID_SEND_CHUNKED_EOS:
+ input = h2o_mruby_send_chunked_eos_callback(generator, receiver, args, &next_action);
+ break;
+ case H2O_MRUBY_CALLBACK_ID_HTTP_JOIN_RESPONSE:
+ input = h2o_mruby_http_join_response_callback(generator, receiver, args, &next_action);
+ break;
+ case H2O_MRUBY_CALLBACK_ID_HTTP_FETCH_CHUNK:
+ input = h2o_mruby_http_fetch_chunk_callback(generator, receiver, args, &next_action);
+ break;
+ default:
+ input = mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "unexpected callback id sent from rack app");
+ break;
+ }
+ } else {
+ input = mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "callback from rack app did not receive an array arg");
+ }
+ switch (next_action) {
+ case H2O_MRUBY_CALLBACK_NEXT_ACTION_STOP:
+ return;
+ case H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC:
+ goto Async;
+ default:
+ assert(next_action == H2O_MRUBY_CALLBACK_NEXT_ACTION_IMMEDIATE);
+ break;
+ }
+ goto Next;
+ }
+ /* if no special actions were necessary, then the output is a rack response */
+ break;
+ Next:
+ mrb_gc_protect(mrb, receiver);
+ mrb_gc_protect(mrb, input);
+ }
+
+ h2o_mruby_current_generator = NULL;
+
+ if (!(100 <= status && status <= 999)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "status returned from rack app is out of range"));
+ goto GotException;
+ }
+
+ /* send the response (unless req is already closed) */
+ if (generator->req == NULL)
+ return;
+ if (generator->req->_generator != NULL) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "unexpectedly received a rack response"));
+ goto GotException;
+ }
+ send_response(generator, status, output, is_delegate);
+ return;
+
+GotException:
+ h2o_mruby_current_generator = NULL;
+ if (generator->req != NULL) {
+ report_exception(generator->req, mrb);
+ if (generator->req->_generator == NULL) {
+ h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0);
+ } else {
+ h2o_mruby_send_chunked_close(generator);
+ }
+ }
+ return;
+
+Async:
+ h2o_mruby_current_generator = NULL;
+ if (!mrb_obj_eq(mrb, generator->ctx->proc, receiver))
+ mrb_gc_register(mrb, receiver);
+ return;
+}
+
+h2o_mruby_handler_t *h2o_mruby_register(h2o_pathconf_t *pathconf, h2o_mruby_config_vars_t *vars)
+{
+ h2o_mruby_handler_t *handler = (void *)h2o_create_handler(pathconf, sizeof(*handler));
+
+ handler->super.on_context_init = on_context_init;
+ handler->super.on_context_dispose = on_context_dispose;
+ handler->super.dispose = on_handler_dispose;
+ handler->super.on_req = on_req;
+ handler->config.source = h2o_strdup(NULL, vars->source.base, vars->source.len);
+ if (vars->path != NULL)
+ handler->config.path = h2o_strdup(NULL, vars->path, SIZE_MAX).base;
+ handler->config.lineno = vars->lineno;
+
+ return handler;
+}
+
+mrb_value h2o_mruby_each_to_array(h2o_mruby_context_t *handler_ctx, mrb_value src)
+{
+ return mrb_funcall_argv(handler_ctx->shared->mrb, mrb_ary_entry(handler_ctx->shared->constants, H2O_MRUBY_PROC_EACH_TO_ARRAY),
+ handler_ctx->shared->symbols.sym_call, 1, &src);
+}
+
+static int iterate_headers_handle_pair(h2o_mruby_context_t *handler_ctx, mrb_value name, mrb_value value,
+ int (*cb)(h2o_mruby_context_t *, h2o_iovec_t, h2o_iovec_t, void *), void *cb_data)
+{
+ mrb_state *mrb = handler_ctx->shared->mrb;
+
+ /* convert name and value to string */
+ name = h2o_mruby_to_str(mrb, name);
+ if (mrb->exc != NULL)
+ return -1;
+ value = h2o_mruby_to_str(mrb, value);
+ if (mrb->exc != NULL)
+ return -1;
+
+ /* call the callback, splitting the values with '\n' */
+ const char *vstart = RSTRING_PTR(value), *vend = vstart + RSTRING_LEN(value), *eol;
+ while (1) {
+ for (eol = vstart; eol != vend; ++eol)
+ if (*eol == '\n')
+ break;
+ if (cb(handler_ctx, h2o_iovec_init(RSTRING_PTR(name), RSTRING_LEN(name)), h2o_iovec_init(vstart, eol - vstart), cb_data) !=
+ 0)
+ return -1;
+ if (eol == vend)
+ break;
+ vstart = eol + 1;
+ }
+
+ return 0;
+}
+
+int h2o_mruby_iterate_headers(h2o_mruby_context_t *handler_ctx, mrb_value headers,
+ int (*cb)(h2o_mruby_context_t *, h2o_iovec_t, h2o_iovec_t, void *), void *cb_data)
+{
+ mrb_state *mrb = handler_ctx->shared->mrb;
+
+ if (!(mrb_hash_p(headers) || mrb_array_p(headers))) {
+ headers = h2o_mruby_each_to_array(handler_ctx, headers);
+ if (mrb->exc != NULL)
+ return -1;
+ assert(mrb_array_p(headers));
+ }
+
+ if (mrb_hash_p(headers)) {
+ mrb_value keys = mrb_hash_keys(mrb, headers);
+ mrb_int i, len = RARRAY_LEN(keys);
+ for (i = 0; i != len; ++i) {
+ mrb_value k = mrb_ary_entry(keys, i);
+ mrb_value v = mrb_hash_get(mrb, headers, k);
+ if (iterate_headers_handle_pair(handler_ctx, k, v, cb, cb_data) != 0)
+ return -1;
+ }
+ } else {
+ assert(mrb_array_p(headers));
+ mrb_int i, len = RARRAY_LEN(headers);
+ for (i = 0; i != len; ++i) {
+ mrb_value pair = mrb_ary_entry(headers, i);
+ if (!mrb_array_p(pair)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "array element of headers MUST by an array"));
+ return -1;
+ }
+ if (iterate_headers_handle_pair(handler_ctx, mrb_ary_entry(pair, 0), mrb_ary_entry(pair, 1), cb, cb_data) != 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/debian/vendor-h2o/lib/handler/mruby/chunked.c b/debian/vendor-h2o/lib/handler/mruby/chunked.c
new file mode 100644
index 0000000..28e3ae4
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/chunked.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/error.h>
+#include <mruby/string.h>
+#include "h2o/mruby_.h"
+#include "embedded.c.h"
+
+struct st_h2o_mruby_chunked_t {
+ h2o_doublebuffer_t sending;
+ size_t bytes_left; /* SIZE_MAX indicates that the number is undermined */
+ enum { H2O_MRUBY_CHUNKED_TYPE_CALLBACK, H2O_MRUBY_CHUNKED_TYPE_SHORTCUT } type;
+ union {
+ struct {
+ h2o_buffer_t *receiving;
+ mrb_value body_obj; /* becomes nil on eos */
+ } callback;
+ struct {
+ h2o_mruby_http_request_context_t *client;
+ h2o_buffer_t *remaining;
+ } shortcut;
+ };
+};
+
+static void do_send(h2o_mruby_generator_t *generator, h2o_buffer_t **input, int is_final)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ assert(chunked->sending.bytes_inflight == 0);
+
+ h2o_iovec_t buf = h2o_doublebuffer_prepare(&chunked->sending, input, generator->req->preferred_chunk_size);
+ size_t bufcnt = 1;
+
+ if (is_final && buf.len == chunked->sending.buf->size && (*input)->size == 0) {
+ if (buf.len == 0)
+ --bufcnt;
+ /* terminate the H1 connection if the length of content served did not match the value sent in content-length header */
+ if (chunked->bytes_left != SIZE_MAX && chunked->bytes_left != 0)
+ generator->req->http1_is_persistent = 0;
+ } else {
+ if (buf.len == 0)
+ return;
+ is_final = 0;
+ }
+
+ h2o_send(generator->req, &buf, bufcnt, is_final ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS);
+}
+
+static void do_proceed(h2o_generator_t *_generator, h2o_req_t *req)
+{
+ h2o_mruby_generator_t *generator = (void *)_generator;
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ h2o_buffer_t **input;
+ int is_final;
+
+ h2o_doublebuffer_consume(&chunked->sending);
+
+ switch (chunked->type) {
+ case H2O_MRUBY_CHUNKED_TYPE_CALLBACK:
+ input = &chunked->callback.receiving;
+ is_final = mrb_nil_p(chunked->callback.body_obj);
+ break;
+ case H2O_MRUBY_CHUNKED_TYPE_SHORTCUT:
+ if (chunked->shortcut.client != NULL) {
+ input = h2o_mruby_http_peek_content(chunked->shortcut.client, &is_final);
+ assert(!is_final);
+ } else {
+ input = &chunked->shortcut.remaining;
+ is_final = 1;
+ }
+ break;
+ default:
+ h2o_fatal("unexpected type");
+ break;
+ }
+
+ do_send(generator, input, is_final);
+}
+
+static void on_shortcut_notify(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ int is_final;
+ h2o_buffer_t **input = h2o_mruby_http_peek_content(chunked->shortcut.client, &is_final);
+
+ if (chunked->bytes_left != SIZE_MAX) {
+ if (chunked->bytes_left < (*input)->size)
+ (*input)->size = chunked->bytes_left; /* trim data too long */
+ chunked->bytes_left -= (*input)->size;
+ }
+
+ /* if final, steal socket input buffer to shortcut.remaining, and reset pointer to client */
+ if (is_final) {
+ chunked->shortcut.remaining = *input;
+ h2o_buffer_init(input, &h2o_socket_buffer_prototype);
+ input = &chunked->shortcut.remaining;
+ chunked->shortcut.client = NULL;
+ }
+
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, input, is_final);
+}
+
+static void close_body_obj(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ mrb_state *mrb = generator->ctx->shared->mrb;
+
+ if (!mrb_nil_p(chunked->callback.body_obj)) {
+ /* call close and throw away error */
+ if (mrb_respond_to(mrb, chunked->callback.body_obj, generator->ctx->shared->symbols.sym_close))
+ mrb_funcall_argv(mrb, chunked->callback.body_obj, generator->ctx->shared->symbols.sym_close, 0, NULL);
+ mrb->exc = NULL;
+ mrb_gc_unregister(mrb, chunked->callback.body_obj);
+ chunked->callback.body_obj = mrb_nil_value();
+ }
+}
+
+mrb_value h2o_mruby_send_chunked_init(h2o_mruby_generator_t *generator, mrb_value body)
+{
+ h2o_mruby_chunked_t *chunked = h2o_mem_alloc_pool(&generator->req->pool, sizeof(*chunked));
+ h2o_doublebuffer_init(&chunked->sending, &h2o_socket_buffer_prototype);
+ chunked->bytes_left = h2o_memis(generator->req->method.base, generator->req->method.len, H2O_STRLIT("HEAD"))
+ ? 0
+ : generator->req->res.content_length;
+ generator->super.proceed = do_proceed;
+ generator->chunked = chunked;
+
+ if ((chunked->shortcut.client = h2o_mruby_http_set_shortcut(generator->ctx->shared->mrb, body, on_shortcut_notify)) != NULL) {
+ chunked->type = H2O_MRUBY_CHUNKED_TYPE_SHORTCUT;
+ chunked->shortcut.remaining = NULL;
+ on_shortcut_notify(generator);
+ return mrb_nil_value();
+ } else {
+ chunked->type = H2O_MRUBY_CHUNKED_TYPE_CALLBACK;
+ h2o_buffer_init(&chunked->callback.receiving, &h2o_socket_buffer_prototype);
+ mrb_gc_register(generator->ctx->shared->mrb, body);
+ chunked->callback.body_obj = body;
+ return mrb_ary_entry(generator->ctx->shared->constants, H2O_MRUBY_CHUNKED_PROC_EACH_TO_FIBER);
+ }
+}
+
+void h2o_mruby_send_chunked_dispose(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ h2o_doublebuffer_dispose(&chunked->sending);
+
+ switch (chunked->type) {
+ case H2O_MRUBY_CHUNKED_TYPE_CALLBACK:
+ h2o_buffer_dispose(&chunked->callback.receiving);
+ close_body_obj(generator);
+ break;
+ case H2O_MRUBY_CHUNKED_TYPE_SHORTCUT:
+ /* note: no need to free reference from chunked->client, since it is disposed at the same moment */
+ if (chunked->shortcut.remaining != NULL)
+ h2o_buffer_dispose(&chunked->shortcut.remaining);
+ break;
+ }
+}
+
+static mrb_value check_precond(mrb_state *mrb, h2o_mruby_generator_t *generator)
+{
+ if (generator == NULL || generator->req == NULL)
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "downstream HTTP closed");
+ if (generator->req->_generator == NULL)
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "cannot send chunk before sending headers");
+ return mrb_nil_value();
+}
+
+static mrb_value send_chunked_method(mrb_state *mrb, mrb_value self)
+{
+ h2o_mruby_generator_t *generator = h2o_mruby_current_generator;
+ const char *s;
+ mrb_int len;
+
+ /* parse args */
+ mrb_get_args(mrb, "s", &s, &len);
+
+ { /* precond check */
+ mrb_value exc = check_precond(mrb, generator);
+ if (!mrb_nil_p(exc))
+ mrb_exc_raise(mrb, exc);
+ }
+
+ /* append to send buffer, and send out immediately if necessary */
+ if (len != 0) {
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ if (chunked->bytes_left != SIZE_MAX) {
+ if (len > chunked->bytes_left)
+ len = chunked->bytes_left;
+ chunked->bytes_left -= len;
+ }
+ if (len != 0) {
+ h2o_buffer_reserve(&chunked->callback.receiving, len);
+ memcpy(chunked->callback.receiving->bytes + chunked->callback.receiving->size, s, len);
+ chunked->callback.receiving->size += len;
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, &chunked->callback.receiving, 0);
+ }
+ }
+
+ return mrb_nil_value();
+}
+
+mrb_value h2o_mruby_send_chunked_eos_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value input,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+
+ { /* precond check */
+ mrb_value exc = check_precond(mrb, generator);
+ if (!mrb_nil_p(exc))
+ return exc;
+ }
+
+ h2o_mruby_send_chunked_close(generator);
+
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_STOP;
+ return mrb_nil_value();
+}
+
+void h2o_mruby_send_chunked_close(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ /* run_fiber will never be called once we enter the fast path, and therefore this function will never get called in that case */
+ assert(chunked->type == H2O_MRUBY_CHUNKED_TYPE_CALLBACK);
+
+ close_body_obj(generator);
+
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, &chunked->callback.receiving, 1);
+}
+
+void h2o_mruby_send_chunked_init_context(h2o_mruby_shared_context_t *shared_ctx)
+{
+ mrb_state *mrb = shared_ctx->mrb;
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_CHUNKED);
+ h2o_mruby_assert(mrb);
+
+ mrb_define_method(mrb, mrb->kernel_module, "_h2o_send_chunk", send_chunked_method, MRB_ARGS_ARG(1, 0));
+ h2o_mruby_define_callback(mrb, "_h2o_send_chunk_eos", H2O_MRUBY_CALLBACK_ID_SEND_CHUNKED_EOS);
+
+ mrb_ary_set(mrb, shared_ctx->constants, H2O_MRUBY_CHUNKED_PROC_EACH_TO_FIBER,
+ mrb_funcall(mrb, mrb_top_self(mrb), "_h2o_chunked_proc_each_to_fiber", 0));
+ h2o_mruby_assert(mrb);
+}
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded.c.h b/debian/vendor-h2o/lib/handler/mruby/embedded.c.h
new file mode 100644
index 0000000..db4bb23
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded.c.h
@@ -0,0 +1,111 @@
+/*
+ * DO NOT EDIT! generated by embed_mruby_code.pl
+ * Please refer to the respective source files for copyright information.
+ */
+
+/* lib/handler/mruby/embedded/core.rb */
+#define H2O_MRUBY_CODE_CORE \
+ "module Kernel\n" \
+ " def _h2o_define_callback(name, id)\n" \
+ " Kernel.define_method(name) do |*args|\n" \
+ " ret = Fiber.yield([ id, _h2o_create_resumer(), args ])\n" \
+ " if ret.kind_of? Exception\n" \
+ " raise ret\n" \
+ " end\n" \
+ " ret\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_create_resumer()\n" \
+ " me = Fiber.current\n" \
+ " Proc.new do |v|\n" \
+ " me.resume(v)\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_proc_each_to_array()\n" \
+ " Proc.new do |o|\n" \
+ " a = []\n" \
+ " o.each do |x|\n" \
+ " a << x\n" \
+ " end\n" \
+ " a\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_proc_app_to_fiber()\n" \
+ " Proc.new do |app|\n" \
+ " cached = nil\n" \
+ " Proc.new do |req|\n" \
+ " fiber = cached\n" \
+ " cached = nil\n" \
+ " if !fiber\n" \
+ " fiber = Fiber.new do\n" \
+ " self_fiber = Fiber.current\n" \
+ " req = Fiber.yield\n" \
+ " while 1\n" \
+ " begin\n" \
+ " while 1\n" \
+ " resp = app.call(req)\n" \
+ " cached = self_fiber\n" \
+ " req = Fiber.yield(resp)\n" \
+ " end\n" \
+ " rescue => e\n" \
+ " cached = self_fiber\n" \
+ " req = Fiber.yield([-1, e])\n" \
+ " end\n" \
+ " end\n" \
+ " end\n" \
+ " fiber.resume\n" \
+ " end\n" \
+ " fiber.resume(req)\n" \
+ " end\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
+
+/* lib/handler/mruby/embedded/http_request.rb */
+#define H2O_MRUBY_CODE_HTTP_REQUEST \
+ "module H2O\n" \
+ " class HttpRequest\n" \
+ " def join\n" \
+ " if !@resp\n" \
+ " @resp = _h2o__http_join_response(self)\n" \
+ " end\n" \
+ " @resp\n" \
+ " end\n" \
+ " def _set_response(resp)\n" \
+ " @resp = resp\n" \
+ " end\n" \
+ " end\n" \
+ " class HttpInputStream\n" \
+ " def each\n" \
+ " while c = _h2o__http_fetch_chunk(self)\n" \
+ " yield c\n" \
+ " end\n" \
+ " end\n" \
+ " def join\n" \
+ " s = \"\"\n" \
+ " each do |c|\n" \
+ " s << c\n" \
+ " end\n" \
+ " s\n" \
+ " end\n" \
+ " class Empty < HttpInputStream\n" \
+ " def each; end\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
+
+/* lib/handler/mruby/embedded/chunked.rb */
+#define H2O_MRUBY_CODE_CHUNKED \
+ "module Kernel\n" \
+ " def _h2o_chunked_proc_each_to_fiber()\n" \
+ " Proc.new do |src|\n" \
+ " fiber = Fiber.new do\n" \
+ " src.each do |chunk|\n" \
+ " _h2o_send_chunk(chunk)\n" \
+ " end\n" \
+ " _h2o_send_chunk_eos()\n" \
+ " end\n" \
+ " fiber.resume\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb
new file mode 100644
index 0000000..ff4e578
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb
@@ -0,0 +1,35 @@
+# Copyright (c) 2014 DeNA Co., Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module Kernel
+
+ def _h2o_chunked_proc_each_to_fiber()
+ Proc.new do |src|
+ fiber = Fiber.new do
+ src.each do |chunk|
+ _h2o_send_chunk(chunk)
+ end
+ _h2o_send_chunk_eos()
+ end
+ fiber.resume
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb
new file mode 100644
index 0000000..e62583d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb
@@ -0,0 +1,81 @@
+# Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Ryosuke Matsumoto,
+# Masayoshi Takahashi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module Kernel
+
+ def _h2o_define_callback(name, id)
+ Kernel.define_method(name) do |*args|
+ ret = Fiber.yield([ id, _h2o_create_resumer(), args ])
+ if ret.kind_of? Exception
+ raise ret
+ end
+ ret
+ end
+ end
+
+ def _h2o_create_resumer()
+ me = Fiber.current
+ Proc.new do |v|
+ me.resume(v)
+ end
+ end
+
+ def _h2o_proc_each_to_array()
+ Proc.new do |o|
+ a = []
+ o.each do |x|
+ a << x
+ end
+ a
+ end
+ end
+
+ def _h2o_proc_app_to_fiber()
+ Proc.new do |app|
+ cached = nil
+ Proc.new do |req|
+ fiber = cached
+ cached = nil
+ if !fiber
+ fiber = Fiber.new do
+ self_fiber = Fiber.current
+ req = Fiber.yield
+ while 1
+ begin
+ while 1
+ resp = app.call(req)
+ cached = self_fiber
+ req = Fiber.yield(resp)
+ end
+ rescue => e
+ cached = self_fiber
+ req = Fiber.yield([-1, e])
+ end
+ end
+ end
+ fiber.resume
+ end
+ fiber.resume(req)
+ end
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb
new file mode 100644
index 0000000..3f3247a
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb
@@ -0,0 +1,54 @@
+# Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module H2O
+
+ class HttpRequest
+ def join
+ if !@resp
+ @resp = _h2o__http_join_response(self)
+ end
+ @resp
+ end
+ def _set_response(resp)
+ @resp = resp
+ end
+ end
+
+ class HttpInputStream
+ def each
+ while c = _h2o__http_fetch_chunk(self)
+ yield c
+ end
+ end
+ def join
+ s = ""
+ each do |c|
+ s << c
+ end
+ s
+ end
+
+ class Empty < HttpInputStream
+ def each; end
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/http_request.c b/debian/vendor-h2o/lib/handler/mruby/http_request.c
new file mode 100644
index 0000000..964d03f
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/http_request.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/error.h>
+#include <mruby/hash.h>
+#include <mruby/string.h>
+#include <mruby_input_stream.h>
+#include "h2o/mruby_.h"
+#include "embedded.c.h"
+
+struct st_h2o_mruby_http_request_context_t {
+ h2o_mruby_generator_t *generator;
+ h2o_http1client_t *client;
+ mrb_value receiver;
+ struct {
+ h2o_buffer_t *buf;
+ h2o_iovec_t body; /* body.base != NULL indicates that post content exists (and the length MAY be zero) */
+ unsigned method_is_head : 1;
+ unsigned has_transfer_encoding : 1;
+ } req;
+ struct {
+ h2o_buffer_t *after_closed; /* when client becomes NULL, rest of the data will be stored to this pointer */
+ int has_content;
+ } resp;
+ struct {
+ mrb_value request;
+ mrb_value input_stream;
+ } refs;
+ void (*shortcut_notify_cb)(h2o_mruby_generator_t *generator);
+};
+
+static void on_gc_dispose_request(mrb_state *mrb, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+ if (ctx != NULL)
+ ctx->refs.request = mrb_nil_value();
+}
+
+const static struct mrb_data_type request_type = {"http_request", on_gc_dispose_request};
+
+static void on_gc_dispose_input_stream(mrb_state *mrb, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+ if (ctx != NULL)
+ ctx->refs.input_stream = mrb_nil_value();
+}
+
+const static struct mrb_data_type input_stream_type = {"http_input_stream", on_gc_dispose_input_stream};
+
+static mrb_value create_downstream_closed_exception(mrb_state *mrb)
+{
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "downstream HTTP closed");
+}
+
+static mrb_value detach_receiver(struct st_h2o_mruby_http_request_context_t *ctx)
+{
+ mrb_value ret = ctx->receiver;
+ assert(!mrb_nil_p(ret));
+ ctx->receiver = mrb_nil_value();
+ return ret;
+}
+
+static void on_dispose(void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+
+ /* clear the refs */
+ if (ctx->client != NULL) {
+ h2o_http1client_cancel(ctx->client);
+ ctx->client = NULL;
+ }
+ if (!mrb_nil_p(ctx->refs.request))
+ DATA_PTR(ctx->refs.request) = NULL;
+ if (!mrb_nil_p(ctx->refs.input_stream))
+ DATA_PTR(ctx->refs.input_stream) = NULL;
+
+ /* clear bufs */
+ h2o_buffer_dispose(&ctx->req.buf);
+ h2o_buffer_dispose(&ctx->resp.after_closed);
+
+ /* notify the app, if it is waiting to hear from us */
+ if (!mrb_nil_p(ctx->receiver)) {
+ mrb_state *mrb = ctx->generator->ctx->shared->mrb;
+ int gc_arena = mrb_gc_arena_save(mrb);
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), create_downstream_closed_exception(mrb), NULL);
+ mrb_gc_arena_restore(mrb, gc_arena);
+ }
+}
+
+static void post_response(struct st_h2o_mruby_http_request_context_t *ctx, int status, const h2o_header_t *headers_sorted,
+ size_t num_headers)
+{
+ mrb_state *mrb = ctx->generator->ctx->shared->mrb;
+ int gc_arena = mrb_gc_arena_save(mrb);
+ size_t i;
+
+ mrb_value resp = mrb_ary_new_capa(mrb, 3);
+
+ /* set status */
+ mrb_ary_set(mrb, resp, 0, mrb_fixnum_value(status));
+
+ /* set headers */
+ mrb_value headers_hash = mrb_hash_new_capa(mrb, (int)num_headers);
+ for (i = 0; i < num_headers; ++i) {
+ /* skip the headers, we determine the eos! */
+ if (h2o_memis(headers_sorted[i].name, headers_sorted[i].name->len, H2O_STRLIT("content-length")) ||
+ h2o_memis(headers_sorted[i].name, headers_sorted[i].name->len, H2O_STRLIT("transfer-encoding")))
+ continue;
+ /* build and set the hash entry */
+ mrb_value k = mrb_str_new(mrb, headers_sorted[i].name->base, headers_sorted[i].name->len);
+ mrb_value v = mrb_str_new(mrb, headers_sorted[i].value.base, headers_sorted[i].value.len);
+ while (i + 1 < num_headers && h2o_memis(headers_sorted[i].name->base, headers_sorted[i].name->len,
+ headers_sorted[i + 1].name->base, headers_sorted[i + 1].name->len)) {
+ ++i;
+ v = mrb_str_cat_lit(mrb, v, "\n");
+ v = mrb_str_cat(mrb, v, headers_sorted[i].value.base, headers_sorted[i].value.len);
+ }
+ mrb_hash_set(mrb, headers_hash, k, v);
+ }
+ mrb_ary_set(mrb, resp, 1, headers_hash);
+
+ /* set input stream */
+ assert(mrb_nil_p(ctx->refs.input_stream));
+ mrb_value input_stream_class;
+ if (ctx->req.method_is_head || status == 101 || status == 204 || status == 304) {
+ input_stream_class = mrb_ary_entry(ctx->generator->ctx->shared->constants, H2O_MRUBY_HTTP_EMPTY_INPUT_STREAM_CLASS);
+ } else {
+ input_stream_class = mrb_ary_entry(ctx->generator->ctx->shared->constants, H2O_MRUBY_HTTP_INPUT_STREAM_CLASS);
+ }
+ ctx->refs.input_stream = h2o_mruby_create_data_instance(mrb, input_stream_class, ctx, &input_stream_type);
+ mrb_ary_set(mrb, resp, 2, ctx->refs.input_stream);
+
+ if (mrb_nil_p(ctx->receiver)) {
+ /* is async */
+ mrb_funcall(mrb, ctx->refs.request, "_set_response", 1, resp);
+ if (mrb->exc != NULL) {
+ fprintf(stderr, "_set_response failed\n");
+ abort();
+ }
+ } else {
+ /* send response to the waiting receiver */
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), resp, NULL);
+ }
+
+ mrb_gc_arena_restore(mrb, gc_arena);
+}
+
+static void post_error(struct st_h2o_mruby_http_request_context_t *ctx, const char *errstr)
+{
+ static const h2o_header_t headers_sorted[] = {
+ {&H2O_TOKEN_CONTENT_TYPE->buf, NULL, {H2O_STRLIT("text/plain; charset=utf-8")}},
+ };
+
+ ctx->client = NULL;
+ size_t errstr_len = strlen(errstr);
+ h2o_buffer_reserve(&ctx->resp.after_closed, errstr_len);
+ memcpy(ctx->resp.after_closed->bytes + ctx->resp.after_closed->size, errstr, errstr_len);
+ ctx->resp.after_closed->size += errstr_len;
+ ctx->resp.has_content = 1;
+
+ post_response(ctx, 500, headers_sorted, sizeof(headers_sorted) / sizeof(headers_sorted[0]));
+}
+
+static mrb_value build_chunk(struct st_h2o_mruby_http_request_context_t *ctx)
+{
+ mrb_value chunk;
+
+ assert(ctx->resp.has_content);
+
+ if (ctx->client != NULL) {
+ assert(ctx->client->sock->input->size != 0);
+ chunk = mrb_str_new(ctx->generator->ctx->shared->mrb, ctx->client->sock->input->bytes, ctx->client->sock->input->size);
+ h2o_buffer_consume(&ctx->client->sock->input, ctx->client->sock->input->size);
+ ctx->resp.has_content = 0;
+ } else {
+ if (ctx->resp.after_closed->size == 0) {
+ chunk = mrb_nil_value();
+ } else {
+ chunk = mrb_str_new(ctx->generator->ctx->shared->mrb, ctx->resp.after_closed->bytes, ctx->resp.after_closed->size);
+ h2o_buffer_consume(&ctx->resp.after_closed, ctx->resp.after_closed->size);
+ }
+ /* has_content is retained as true, so that repeated calls will return nil immediately */
+ }
+
+ return chunk;
+}
+
+static int on_body(h2o_http1client_t *client, const char *errstr)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ h2o_buffer_t *tmp = ctx->resp.after_closed;
+ ctx->resp.after_closed = client->sock->input;
+ client->sock->input = tmp;
+ ctx->client = NULL;
+ ctx->resp.has_content = 1;
+ } else if (client->sock->input->size != 0) {
+ ctx->resp.has_content = 1;
+ }
+
+ if (ctx->resp.has_content) {
+ if (ctx->shortcut_notify_cb != NULL) {
+ ctx->shortcut_notify_cb(ctx->generator);
+ } else if (!mrb_nil_p(ctx->receiver)) {
+ int gc_arena = mrb_gc_arena_save(ctx->generator->ctx->shared->mrb);
+ mrb_value chunk = build_chunk(ctx);
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), chunk, NULL);
+ mrb_gc_arena_restore(ctx->generator->ctx->shared->mrb, gc_arena);
+ }
+ }
+ return 0;
+}
+
+static int headers_sort_cb(const void *_x, const void *_y)
+{
+ const h2o_header_t *x = _x, *y = _y;
+
+ if (x->name->len < y->name->len)
+ return -1;
+ if (x->name->len > y->name->len)
+ return 1;
+ return memcmp(x->name->base, y->name->base, x->name->len);
+}
+
+static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *errstr, int minor_version, int status,
+ h2o_iovec_t msg, h2o_header_t *headers, size_t num_headers, int rlen)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ if (errstr != h2o_http1client_error_is_eos) {
+ /* error */
+ post_error(ctx, errstr);
+ return NULL;
+ }
+ /* closed without body */
+ ctx->client = NULL;
+ }
+
+ qsort(headers, num_headers, sizeof(headers[0]), headers_sort_cb);
+ post_response(ctx, status, headers, num_headers);
+ return on_body;
+}
+
+static h2o_http1client_head_cb on_connect(h2o_http1client_t *client, const char *errstr, h2o_iovec_t **reqbufs, size_t *reqbufcnt,
+ int *method_is_head)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ post_error(ctx, errstr);
+ return NULL;
+ }
+
+ *reqbufs = h2o_mem_alloc_pool(&ctx->generator->req->pool, sizeof(**reqbufs) * 2);
+ **reqbufs = h2o_iovec_init(ctx->req.buf->bytes, ctx->req.buf->size);
+ *reqbufcnt = 1;
+ if (ctx->req.body.base != NULL)
+ (*reqbufs)[(*reqbufcnt)++] = ctx->req.body;
+ *method_is_head = ctx->req.method_is_head;
+ return on_head;
+}
+
+static inline void append_to_buffer(h2o_buffer_t **buf, const void *src, size_t len)
+{
+ memcpy((*buf)->bytes + (*buf)->size, src, len);
+ (*buf)->size += len;
+}
+
+static int flatten_request_header(h2o_mruby_context_t *handler_ctx, h2o_iovec_t name, h2o_iovec_t value, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+
+ /* ignore certain headers */
+ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("content-length")) ||
+ h2o_lcstris(name.base, name.len, H2O_STRLIT("connection")) || h2o_lcstris(name.base, name.len, H2O_STRLIT("host")))
+ return 0;
+
+ /* mark the existence of transfer-encoding in order to prevent us from adding content-length header */
+ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("transfer-encoding")))
+ ctx->req.has_transfer_encoding = 1;
+
+ h2o_buffer_reserve(&ctx->req.buf, name.len + value.len + sizeof(": \r\n") - 1);
+ append_to_buffer(&ctx->req.buf, name.base, name.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(": "));
+ append_to_buffer(&ctx->req.buf, value.base, value.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ return 0;
+}
+
+static mrb_value http_request_method(mrb_state *mrb, mrb_value self)
+{
+ h2o_mruby_generator_t *generator;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+ const char *arg_url;
+ mrb_int arg_url_len;
+ mrb_value arg_hash;
+ h2o_iovec_t method;
+ h2o_url_t url;
+
+ /* parse args */
+ arg_hash = mrb_nil_value();
+ mrb_get_args(mrb, "s|H", &arg_url, &arg_url_len, &arg_hash);
+
+ /* precond check */
+ if ((generator = h2o_mruby_current_generator) == NULL || generator->req == NULL)
+ mrb_exc_raise(mrb, create_downstream_closed_exception(mrb));
+
+ /* allocate context and initialize */
+ ctx = h2o_mem_alloc_shared(&generator->req->pool, sizeof(*ctx), on_dispose);
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->generator = generator;
+ ctx->receiver = mrb_nil_value();
+ h2o_buffer_init(&ctx->req.buf, &h2o_socket_buffer_prototype);
+ h2o_buffer_init(&ctx->resp.after_closed, &h2o_socket_buffer_prototype);
+ ctx->refs.request = mrb_nil_value();
+ ctx->refs.input_stream = mrb_nil_value();
+
+ /* uri */
+ if (h2o_url_parse(arg_url, arg_url_len, &url) != 0)
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "invaild URL");
+
+ /* method */
+ method = h2o_iovec_init(H2O_STRLIT("GET"));
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value t = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_method));
+ if (!mrb_nil_p(t)) {
+ t = mrb_str_to_str(mrb, t);
+ method = h2o_iovec_init(RSTRING_PTR(t), RSTRING_LEN(t));
+ if (h2o_memis(method.base, method.len, H2O_STRLIT("HEAD"))) {
+ ctx->req.method_is_head = 1;
+ }
+ }
+ }
+
+ /* start building the request */
+ h2o_buffer_reserve(&ctx->req.buf, method.len + 1);
+ append_to_buffer(&ctx->req.buf, method.base, method.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(" "));
+ h2o_buffer_reserve(&ctx->req.buf,
+ url.path.len + url.authority.len + sizeof(" HTTP/1.1\r\nConnection: close\r\nHost: \r\n") - 1);
+ append_to_buffer(&ctx->req.buf, url.path.base, url.path.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(" HTTP/1.1\r\nConnection: close\r\nHost: "));
+ append_to_buffer(&ctx->req.buf, url.authority.base, url.authority.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ /* headers */
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value headers = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_headers));
+ if (!mrb_nil_p(headers)) {
+ if (h2o_mruby_iterate_headers(generator->ctx, headers, flatten_request_header, ctx) != 0) {
+ mrb_value exc = mrb_obj_value(mrb->exc);
+ mrb->exc = NULL;
+ mrb_exc_raise(mrb, exc);
+ }
+ }
+ }
+ /* body */
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value body = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_body));
+ if (!mrb_nil_p(body)) {
+ if (mrb_obj_eq(mrb, body, generator->rack_input)) {
+ /* fast path */
+ mrb_int pos;
+ mrb_input_stream_get_data(mrb, body, NULL, NULL, &pos, NULL, NULL);
+ ctx->req.body = generator->req->entity;
+ ctx->req.body.base += pos;
+ ctx->req.body.len -= pos;
+ } else {
+ if (!mrb_string_p(body)) {
+ body = mrb_funcall(mrb, body, "read", 0);
+ if (!mrb_string_p(body))
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "body.read did not return string");
+ }
+ ctx->req.body = h2o_strdup(&ctx->generator->req->pool, RSTRING_PTR(body), RSTRING_LEN(body));
+ }
+ if (!ctx->req.has_transfer_encoding) {
+ char buf[64];
+ size_t l = (size_t)sprintf(buf, "content-length: %zu\r\n", ctx->req.body.len);
+ h2o_buffer_reserve(&ctx->req.buf, l);
+ append_to_buffer(&ctx->req.buf, buf, l);
+ }
+ }
+ }
+
+ h2o_buffer_reserve(&ctx->req.buf, 2);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ /* build request and connect */
+ ctx->refs.request = h2o_mruby_create_data_instance(
+ mrb, mrb_ary_entry(generator->ctx->shared->constants, H2O_MRUBY_HTTP_REQUEST_CLASS), ctx, &request_type);
+ h2o_http1client_connect(&ctx->client, ctx, &generator->req->conn->ctx->proxy.client_ctx, url.host, h2o_url_get_port(&url),
+ url.scheme == &H2O_URL_SCHEME_HTTPS, on_connect);
+
+ return ctx->refs.request;
+}
+
+mrb_value h2o_mruby_http_join_response_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value args,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+
+ if (generator->req == NULL)
+ return create_downstream_closed_exception(mrb);
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, mrb_ary_entry(args, 0), &request_type)) == NULL)
+ return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "HttpRequest#join wrong self");
+
+ ctx->receiver = receiver;
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC;
+ return mrb_nil_value();
+}
+
+mrb_value h2o_mruby_http_fetch_chunk_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value args,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+ mrb_value ret;
+
+ if (generator->req == NULL)
+ return create_downstream_closed_exception(mrb);
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, mrb_ary_entry(args, 0), &input_stream_type)) == NULL)
+ return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "_HttpInputStream#each wrong self");
+
+ if (ctx->resp.has_content) {
+ ret = build_chunk(ctx);
+ } else {
+ ctx->receiver = receiver;
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC;
+ ret = mrb_nil_value();
+ }
+
+ return ret;
+}
+
+h2o_mruby_http_request_context_t *h2o_mruby_http_set_shortcut(mrb_state *mrb, mrb_value obj, void (*cb)(h2o_mruby_generator_t *))
+{
+ struct st_h2o_mruby_http_request_context_t *ctx;
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, obj, &input_stream_type)) == NULL)
+ return NULL;
+ ctx->shortcut_notify_cb = cb;
+ return ctx;
+}
+
+h2o_buffer_t **h2o_mruby_http_peek_content(h2o_mruby_http_request_context_t *ctx, int *is_final)
+{
+ *is_final = ctx->client == NULL;
+ return ctx->client != NULL && ctx->resp.has_content ? &ctx->client->sock->input : &ctx->resp.after_closed;
+}
+
+void h2o_mruby_http_request_init_context(h2o_mruby_shared_context_t *ctx)
+{
+ mrb_state *mrb = ctx->mrb;
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_HTTP_REQUEST);
+ h2o_mruby_assert(mrb);
+
+ struct RClass *module, *klass;
+ module = mrb_define_module(mrb, "H2O");
+
+ mrb_define_method(mrb, mrb->kernel_module, "http_request", http_request_method, MRB_ARGS_ARG(1, 2));
+
+ klass = mrb_class_get_under(mrb, module, "HttpRequest");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_REQUEST_CLASS, mrb_obj_value(klass));
+
+ klass = mrb_class_get_under(mrb, module, "HttpInputStream");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_INPUT_STREAM_CLASS, mrb_obj_value(klass));
+
+ klass = mrb_class_get_under(mrb, klass, "Empty");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_EMPTY_INPUT_STREAM_CLASS, mrb_obj_value(klass));
+
+ h2o_mruby_define_callback(mrb, "_h2o__http_join_response", H2O_MRUBY_CALLBACK_ID_HTTP_JOIN_RESPONSE);
+ h2o_mruby_define_callback(mrb, "_h2o__http_fetch_chunk", H2O_MRUBY_CALLBACK_ID_HTTP_FETCH_CHUNK);
+}
diff --git a/debian/vendor-h2o/lib/handler/proxy.c b/debian/vendor-h2o/lib/handler/proxy.c
new file mode 100644
index 0000000..1d87225
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/proxy.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2014,2015 DeNA Co., Ltd., Masahiro Nagano
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <sys/un.h>
+#include "h2o.h"
+#include "h2o/socketpool.h"
+
+struct rp_handler_t {
+ h2o_handler_t super;
+ h2o_url_t upstream; /* host should be NULL-terminated */
+ h2o_socketpool_t *sockpool; /* non-NULL if config.use_keepalive == 1 */
+ h2o_proxy_config_vars_t config;
+};
+
+static int on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ struct rp_handler_t *self = (void *)_self;
+ h2o_req_overrides_t *overrides = h2o_mem_alloc_pool(&req->pool, sizeof(*overrides));
+ const h2o_url_scheme_t *scheme;
+ h2o_iovec_t *authority;
+
+ /* setup overrides */
+ *overrides = (h2o_req_overrides_t){NULL};
+ if (self->sockpool != NULL) {
+ overrides->socketpool = self->sockpool;
+ } else if (self->config.preserve_host) {
+ overrides->hostport.host = self->upstream.host;
+ overrides->hostport.port = h2o_url_get_port(&self->upstream);
+ }
+ overrides->location_rewrite.match = &self->upstream;
+ overrides->location_rewrite.path_prefix = req->pathconf->path;
+ overrides->use_proxy_protocol = self->config.use_proxy_protocol;
+ overrides->client_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super);
+ overrides->headers_cmds = self->config.headers_cmds;
+
+ /* determine the scheme and authority */
+ if (self->config.preserve_host) {
+ scheme = req->scheme;
+ authority = &req->authority;
+ } else {
+ scheme = self->upstream.scheme;
+ authority = &self->upstream.authority;
+ }
+
+ /* request reprocess */
+ h2o_reprocess_request(req, req->method, scheme, *authority,
+ h2o_build_destination(req, self->upstream.path.base, self->upstream.path.len, 0), overrides, 0);
+
+ return 0;
+}
+
+static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct rp_handler_t *self = (void *)_self;
+
+ /* use the loop of first context for handling socketpool timeouts */
+ if (self->sockpool != NULL && self->sockpool->timeout == UINT64_MAX)
+ h2o_socketpool_set_timeout(self->sockpool, ctx->loop, self->config.keepalive_timeout);
+
+ /* setup a specific client context only if we need to */
+ if (ctx->globalconf->proxy.io_timeout == self->config.io_timeout && !self->config.websocket.enabled &&
+ self->config.ssl_ctx == ctx->globalconf->proxy.ssl_ctx)
+ return;
+
+ h2o_http1client_ctx_t *client_ctx = h2o_mem_alloc(sizeof(*ctx));
+ client_ctx->loop = ctx->loop;
+ client_ctx->getaddr_receiver = &ctx->receivers.hostinfo_getaddr;
+ if (ctx->globalconf->proxy.io_timeout == self->config.io_timeout) {
+ client_ctx->io_timeout = &ctx->proxy.io_timeout;
+ } else {
+ client_ctx->io_timeout = h2o_mem_alloc(sizeof(*client_ctx->io_timeout));
+ h2o_timeout_init(client_ctx->loop, client_ctx->io_timeout, self->config.io_timeout);
+ }
+ if (self->config.websocket.enabled) {
+ /* FIXME avoid creating h2o_timeout_t for every path-level context in case the timeout values are the same */
+ client_ctx->websocket_timeout = h2o_mem_alloc(sizeof(*client_ctx->websocket_timeout));
+ h2o_timeout_init(client_ctx->loop, client_ctx->websocket_timeout, self->config.websocket.timeout);
+ } else {
+ client_ctx->websocket_timeout = NULL;
+ }
+ client_ctx->ssl_ctx = self->config.ssl_ctx;
+
+ h2o_context_set_handler_context(ctx, &self->super, client_ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct rp_handler_t *self = (void *)_self;
+ h2o_http1client_ctx_t *client_ctx = h2o_context_get_handler_context(ctx, &self->super);
+
+ if (client_ctx == NULL)
+ return;
+
+ if (client_ctx->io_timeout != &ctx->proxy.io_timeout) {
+ h2o_timeout_dispose(client_ctx->loop, client_ctx->io_timeout);
+ free(client_ctx->io_timeout);
+ }
+ if (client_ctx->websocket_timeout != NULL) {
+ h2o_timeout_dispose(client_ctx->loop, client_ctx->websocket_timeout);
+ free(client_ctx->websocket_timeout);
+ }
+ free(client_ctx);
+}
+
+static void on_handler_dispose(h2o_handler_t *_self)
+{
+ struct rp_handler_t *self = (void *)_self;
+
+ if (self->config.ssl_ctx != NULL)
+ SSL_CTX_free(self->config.ssl_ctx);
+ free(self->upstream.host.base);
+ free(self->upstream.path.base);
+ if (self->sockpool != NULL) {
+ h2o_socketpool_dispose(self->sockpool);
+ free(self->sockpool);
+ }
+}
+
+void h2o_proxy_register_reverse_proxy(h2o_pathconf_t *pathconf, h2o_url_t *upstream, h2o_proxy_config_vars_t *config)
+{
+ struct sockaddr_un sa;
+ const char *to_sa_err;
+ struct rp_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self));
+ self->super.on_context_init = on_context_init;
+ self->super.on_context_dispose = on_context_dispose;
+ self->super.dispose = on_handler_dispose;
+ self->super.on_req = on_req;
+ to_sa_err = h2o_url_host_to_sun(upstream->host, &sa);
+ if (config->keepalive_timeout != 0) {
+ self->sockpool = h2o_mem_alloc(sizeof(*self->sockpool));
+ int is_ssl = upstream->scheme == &H2O_URL_SCHEME_HTTPS;
+ if (to_sa_err == h2o_url_host_to_sun_err_is_not_unix_socket) {
+ h2o_socketpool_init_by_hostport(self->sockpool, upstream->host, h2o_url_get_port(upstream), is_ssl,
+ SIZE_MAX /* FIXME */);
+ } else {
+ assert(to_sa_err == NULL);
+ h2o_socketpool_init_by_address(self->sockpool, (void *)&sa, sizeof(sa), is_ssl, SIZE_MAX /* FIXME */);
+ }
+ }
+ h2o_url_copy(NULL, &self->upstream, upstream);
+ if (to_sa_err) {
+ h2o_strtolower(self->upstream.host.base, self->upstream.host.len);
+ }
+ self->config = *config;
+ if (self->config.ssl_ctx != NULL)
+ SSL_CTX_up_ref(self->config.ssl_ctx);
+}
diff --git a/debian/vendor-h2o/lib/handler/redirect.c b/debian/vendor-h2o/lib/handler/redirect.c
new file mode 100644
index 0000000..c8b9c50
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/redirect.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include "h2o.h"
+
+#define MODULE_NAME "lib/handler/redirect.c"
+
+struct st_h2o_redirect_handler_t {
+ h2o_handler_t super;
+ int internal;
+ int status;
+ h2o_iovec_t prefix;
+};
+
+static void on_dispose(h2o_handler_t *_self)
+{
+ h2o_redirect_handler_t *self = (void *)_self;
+ free(self->prefix.base);
+}
+
+static void redirect_internally(h2o_redirect_handler_t *self, h2o_req_t *req, h2o_iovec_t dest)
+{
+ h2o_iovec_t method;
+ h2o_url_t input, resolved;
+
+ /* resolve the URL */
+ if (h2o_url_parse_relative(dest.base, dest.len, &input) != 0) {
+ h2o_req_log_error(req, MODULE_NAME, "invalid destination:%.*s", (int)dest.len, dest.base);
+ goto SendInternalError;
+ }
+ if (input.scheme != NULL && input.authority.base != NULL) {
+ resolved = input;
+ } else {
+ h2o_url_t base;
+ /* we MUST to set authority to that of hostconf, or internal redirect might create a TCP connection */
+ if (h2o_url_init(&base, req->scheme, req->hostconf->authority.hostport, req->path) != 0) {
+ h2o_req_log_error(req, MODULE_NAME, "failed to parse current authority:%.*s", (int)req->authority.len,
+ req->authority.base);
+ goto SendInternalError;
+ }
+ h2o_url_resolve(&req->pool, &base, &input, &resolved);
+ }
+
+ /* determine the method */
+ switch (self->status) {
+ case 307:
+ case 308:
+ method = req->method;
+ break;
+ default:
+ method = h2o_iovec_init(H2O_STRLIT("GET"));
+ req->entity = (h2o_iovec_t){NULL};
+ break;
+ }
+
+ h2o_reprocess_request_deferred(req, method, resolved.scheme, resolved.authority, resolved.path, NULL, 1);
+ return;
+
+SendInternalError:
+ h2o_send_error_503(req, "Internal Server Error", "internal server error", 0);
+}
+
+static int on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ h2o_redirect_handler_t *self = (void *)_self;
+ h2o_iovec_t dest = h2o_build_destination(req, self->prefix.base, self->prefix.len, 1);
+
+ /* redirect */
+ if (self->internal) {
+ redirect_internally(self, req, dest);
+ } else {
+ h2o_send_redirect(req, self->status, "Redirected", dest.base, dest.len);
+ }
+
+ return 0;
+}
+
+h2o_redirect_handler_t *h2o_redirect_register(h2o_pathconf_t *pathconf, int internal, int status, const char *prefix)
+{
+ h2o_redirect_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self));
+ self->super.dispose = on_dispose;
+ self->super.on_req = on_req;
+ self->internal = internal;
+ self->status = status;
+ self->prefix = h2o_strdup(NULL, prefix, SIZE_MAX);
+ return self;
+}
diff --git a/debian/vendor-h2o/lib/handler/reproxy.c b/debian/vendor-h2o/lib/handler/reproxy.c
new file mode 100644
index 0000000..369386d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/reproxy.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014,2015 DeNA Co., Ltd., Kazuho Oku, Daisuke Maki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+
+static void on_send(h2o_ostream_t *self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ /* nothing to do */
+}
+
+static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ h2o_iovec_t dest, method;
+ ssize_t xru_index;
+
+ /* obtain x-reproxy-url header, or skip to next ostream */
+ if ((xru_index = h2o_find_header(&req->res.headers, H2O_TOKEN_X_REPROXY_URL, -1)) == -1) {
+ h2o_setup_next_ostream(req, slot);
+ return;
+ }
+ dest = req->res.headers.entries[xru_index].value;
+ h2o_delete_header(&req->res.headers, xru_index);
+
+ /* setup params */
+ switch (req->res.status) {
+ case 307:
+ case 308:
+ method = req->method;
+ break;
+ default:
+ method = h2o_iovec_init(H2O_STRLIT("GET"));
+ req->entity = (h2o_iovec_t){NULL};
+ break;
+ }
+
+ /* request internal redirect (is deferred) */
+ h2o_send_redirect_internal(req, method, dest.base, dest.len, 0);
+
+ /* setup filter (that swallows the response until the timeout gets fired) */
+ h2o_ostream_t *ostream = h2o_add_ostream(req, sizeof(*ostream), slot);
+ ostream->do_send = on_send;
+}
+
+void h2o_reproxy_register(h2o_pathconf_t *pathconf)
+{
+ h2o_filter_t *self = h2o_create_filter(pathconf, sizeof(*self));
+ self->on_setup_ostream = on_setup_ostream;
+}
diff --git a/debian/vendor-h2o/lib/handler/status.c b/debian/vendor-h2o/lib/handler/status.c
new file mode 100644
index 0000000..93befed
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/status.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "h2o.h"
+
+extern h2o_status_handler_t events_status_handler;
+extern h2o_status_handler_t requests_status_handler;
+extern h2o_status_handler_t durations_status_handler;
+
+struct st_h2o_status_logger_t {
+ h2o_logger_t super;
+};
+
+struct st_h2o_root_status_handler_t {
+ h2o_handler_t super;
+ H2O_VECTOR(h2o_multithread_receiver_t *) receivers;
+};
+
+struct st_h2o_status_context_t {
+ h2o_context_t *ctx;
+ h2o_multithread_receiver_t receiver;
+};
+
+struct st_status_ctx_t {
+ int active;
+ void *ctx;
+};
+struct st_h2o_status_collector_t {
+ struct {
+ h2o_req_t *req;
+ h2o_multithread_receiver_t *receiver;
+ } src;
+ size_t num_remaining_threads_atomic;
+ H2O_VECTOR(struct st_status_ctx_t) status_ctx;
+};
+
+struct st_h2o_status_message_t {
+ h2o_multithread_message_t super;
+ struct st_h2o_status_collector_t *collector;
+};
+
+static void collect_reqs_of_context(struct st_h2o_status_collector_t *collector, h2o_context_t *ctx)
+{
+ int i;
+
+ for (i = 0; i < ctx->globalconf->statuses.size; i++) {
+ struct st_status_ctx_t *sc = collector->status_ctx.entries + i;
+ h2o_status_handler_t *sh = ctx->globalconf->statuses.entries + i;
+ if (sc->active && sh->per_thread != NULL)
+ sh->per_thread(sc->ctx, ctx);
+ }
+
+ if (__sync_sub_and_fetch(&collector->num_remaining_threads_atomic, 1) == 0) {
+ struct st_h2o_status_message_t *message = h2o_mem_alloc(sizeof(*message));
+ message->super = (h2o_multithread_message_t){{NULL}};
+ message->collector = collector;
+ h2o_multithread_send_message(collector->src.receiver, &message->super);
+ }
+}
+
+static void send_response(struct st_h2o_status_collector_t *collector)
+{
+ static h2o_generator_t generator = {NULL, NULL};
+ h2o_req_t *req;
+ size_t nr_statuses;
+ int i;
+ int cur_resp = 0;
+
+ req = collector->src.req;
+ if (!req) {
+ h2o_mem_release_shared(collector);
+ return;
+ }
+
+ nr_statuses = req->conn->ctx->globalconf->statuses.size;
+ size_t nr_resp = nr_statuses + 2; // 2 for the footer and header
+ h2o_iovec_t resp[nr_resp];
+
+ memset(resp, 0, sizeof(resp[0]) * nr_resp);
+ resp[cur_resp++] = (h2o_iovec_t){H2O_STRLIT("{\n")};
+
+ int coma_removed = 0;
+ for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) {
+ h2o_status_handler_t *sh = &req->conn->ctx->globalconf->statuses.entries[i];
+ if (!collector->status_ctx.entries[i].active) {
+ continue;
+ }
+ resp[cur_resp++] = sh->final(collector->status_ctx.entries[i].ctx, req->conn->ctx->globalconf, req);
+ if (resp[cur_resp - 1].len > 0 && !coma_removed) {
+ /* requests come in with a leading coma, replace if with a space */
+ resp[cur_resp - 1].base[0] = ' ';
+ coma_removed = 1;
+ }
+ }
+ resp[cur_resp++] = (h2o_iovec_t){H2O_STRLIT("\n}\n")};
+
+ req->res.status = 200;
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/plain; charset=utf-8"));
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("no-cache, no-store"));
+ h2o_start_response(req, &generator);
+ h2o_send(req, resp, h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : nr_resp,
+ H2O_SEND_STATE_FINAL);
+ h2o_mem_release_shared(collector);
+}
+
+static void on_collect_notify(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages)
+{
+ struct st_h2o_status_context_t *status_ctx = H2O_STRUCT_FROM_MEMBER(struct st_h2o_status_context_t, receiver, receiver);
+
+ while (!h2o_linklist_is_empty(messages)) {
+ struct st_h2o_status_message_t *message = H2O_STRUCT_FROM_MEMBER(struct st_h2o_status_message_t, super, messages->next);
+ struct st_h2o_status_collector_t *collector = message->collector;
+ h2o_linklist_unlink(&message->super.link);
+ free(message);
+
+ if (__sync_add_and_fetch(&collector->num_remaining_threads_atomic, 0) != 0) {
+ collect_reqs_of_context(collector, status_ctx->ctx);
+ } else {
+ send_response(collector);
+ }
+ }
+}
+
+static void on_collector_dispose(void *_collector)
+{
+}
+
+static void on_req_close(void *p)
+{
+ struct st_h2o_status_collector_t *collector = *(void **)p;
+ collector->src.req = NULL;
+ h2o_mem_release_shared(collector);
+}
+
+static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req, h2o_iovec_t status_list)
+{
+ { /* construct collector and send request to every thread */
+ struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super);
+ struct st_h2o_status_collector_t *collector = h2o_mem_alloc_shared(NULL, sizeof(*collector), on_collector_dispose);
+ size_t i;
+
+ memset(collector, 0, sizeof(*collector));
+ for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) {
+ h2o_status_handler_t *sh;
+
+ h2o_vector_reserve(&req->pool, &collector->status_ctx, collector->status_ctx.size + 1);
+ sh = &req->conn->ctx->globalconf->statuses.entries[i];
+
+ if (status_list.base) {
+ if (!h2o_contains_token(status_list.base, status_list.len, sh->name.base, sh->name.len, ',')) {
+ collector->status_ctx.entries[collector->status_ctx.size].active = 0;
+ goto Skip;
+ }
+ }
+ if (sh->init) {
+ collector->status_ctx.entries[collector->status_ctx.size].ctx = sh->init();
+ }
+ collector->status_ctx.entries[collector->status_ctx.size].active = 1;
+ Skip:
+ collector->status_ctx.size++;
+ }
+ collector->src.req = req;
+ collector->src.receiver = &status_ctx->receiver;
+ collector->num_remaining_threads_atomic = self->receivers.size;
+
+ for (i = 0; i != self->receivers.size; ++i) {
+ struct st_h2o_status_message_t *message = h2o_mem_alloc(sizeof(*message));
+ *message = (struct st_h2o_status_message_t){{{NULL}}, collector};
+ h2o_multithread_send_message(self->receivers.entries[i], &message->super);
+ }
+
+ /* collector is also retained by the on_req_close callback */
+ *(struct st_h2o_status_collector_t **)h2o_mem_alloc_shared(&req->pool, sizeof(collector), on_req_close) = collector;
+ h2o_mem_addref_shared(collector);
+ }
+
+ return 0;
+}
+
+static int on_req(h2o_handler_t *_self, h2o_req_t *req)
+{
+ struct st_h2o_root_status_handler_t *self = (void *)_self;
+ size_t prefix_len = req->pathconf->path.len - (req->pathconf->path.base[req->pathconf->path.len - 1] == '/');
+ h2o_iovec_t local_path = h2o_iovec_init(req->path_normalized.base + prefix_len, req->path_normalized.len - prefix_len);
+
+ if (local_path.len == 0 || h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/"))) {
+ /* root of the handler returns HTML that renders the status */
+ h2o_iovec_t fn;
+ const char *root = getenv("H2O_ROOT");
+ if (root == NULL)
+ root = H2O_TO_STR(H2O_ROOT);
+ fn = h2o_concat(&req->pool, h2o_iovec_init(root, strlen(root)), h2o_iovec_init(H2O_STRLIT("/share/h2o/status/index.html")));
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("no-cache"));
+ return h2o_file_send(req, 200, "OK", fn.base, h2o_iovec_init(H2O_STRLIT("text/html; charset=utf-8")), 0);
+ } else if (h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/json"))) {
+ int ret;
+ /* "/json" maps to the JSON API */
+ h2o_iovec_t status_list = {NULL, 0}; /* NULL means we'll show all statuses */
+ if (req->query_at != SIZE_MAX && (req->path.len - req->query_at > 6)) {
+ if (h2o_memis(&req->path.base[req->query_at], 6, "?show=", 6)) {
+ status_list = h2o_iovec_init(&req->path.base[req->query_at + 6], req->path.len - req->query_at - 6);
+ }
+ }
+ ret = on_req_json(self, req, status_list);
+ return ret;
+ }
+
+ return -1;
+}
+
+static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct st_h2o_root_status_handler_t *self = (void *)_self;
+ struct st_h2o_status_context_t *status_ctx = h2o_mem_alloc(sizeof(*status_ctx));
+
+ status_ctx->ctx = ctx;
+ h2o_multithread_register_receiver(ctx->queue, &status_ctx->receiver, on_collect_notify);
+
+ h2o_vector_reserve(NULL, &self->receivers, self->receivers.size + 1);
+ self->receivers.entries[self->receivers.size++] = &status_ctx->receiver;
+
+ h2o_context_set_handler_context(ctx, &self->super, status_ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
+{
+ struct st_h2o_root_status_handler_t *self = (void *)_self;
+ struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(ctx, &self->super);
+ size_t i;
+
+ for (i = 0; i != self->receivers.size; ++i)
+ if (self->receivers.entries[i] == &status_ctx->receiver)
+ break;
+ assert(i != self->receivers.size);
+ memmove(self->receivers.entries + i + 1, self->receivers.entries + i, self->receivers.size - i - 1);
+ --self->receivers.size;
+
+ h2o_multithread_unregister_receiver(ctx->queue, &status_ctx->receiver);
+
+ free(status_ctx);
+}
+
+void h2o_status_register(h2o_pathconf_t *conf)
+{
+ struct st_h2o_root_status_handler_t *self = (void *)h2o_create_handler(conf, sizeof(*self));
+ self->super.on_context_init = on_context_init;
+ self->super.on_context_dispose = on_context_dispose;
+ self->super.on_req = on_req;
+ h2o_config_register_status_handler(conf->global, requests_status_handler);
+ h2o_config_register_status_handler(conf->global, events_status_handler);
+ h2o_config_register_status_handler(conf->global, durations_status_handler);
+}
diff --git a/debian/vendor-h2o/lib/handler/status/durations.c b/debian/vendor-h2o/lib/handler/status/durations.c
new file mode 100644
index 0000000..f011107
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/status/durations.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2016 Fastly
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "h2o.h"
+#include "gkc.h"
+#include <inttypes.h>
+#include <pthread.h>
+
+#define GK_EPSILON 0.01
+
+struct st_duration_stats_t {
+ struct gkc_summary *connect_time;
+ struct gkc_summary *header_time;
+ struct gkc_summary *body_time;
+ struct gkc_summary *request_total_time;
+ struct gkc_summary *process_time;
+ struct gkc_summary *response_time;
+ struct gkc_summary *duration;
+};
+
+struct st_duration_agg_stats_t {
+ struct st_duration_stats_t stats;
+ pthread_mutex_t mutex;
+};
+
+static h2o_logger_t *durations_logger;
+static void durations_status_per_thread(void *priv, h2o_context_t *ctx)
+{
+ struct st_duration_agg_stats_t *agg_stats = priv;
+ if (durations_logger) {
+ struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(ctx, durations_logger);
+ pthread_mutex_lock(&agg_stats->mutex);
+#define ADD_DURATION(x) \
+ do { \
+ struct gkc_summary *tmp; \
+ tmp = gkc_combine(agg_stats->stats.x, ctx_stats->x); \
+ gkc_summary_free(agg_stats->stats.x); \
+ agg_stats->stats.x = tmp; \
+ } while (0)
+ ADD_DURATION(connect_time);
+ ADD_DURATION(header_time);
+ ADD_DURATION(body_time);
+ ADD_DURATION(request_total_time);
+ ADD_DURATION(process_time);
+ ADD_DURATION(response_time);
+ ADD_DURATION(duration);
+#undef ADD_DURATION
+ pthread_mutex_unlock(&agg_stats->mutex);
+ }
+}
+
+static void duration_stats_init(struct st_duration_stats_t *stats)
+{
+ stats->connect_time = gkc_summary_alloc(GK_EPSILON);
+ stats->header_time = gkc_summary_alloc(GK_EPSILON);
+ stats->body_time = gkc_summary_alloc(GK_EPSILON);
+ stats->request_total_time = gkc_summary_alloc(GK_EPSILON);
+ stats->process_time = gkc_summary_alloc(GK_EPSILON);
+ stats->response_time = gkc_summary_alloc(GK_EPSILON);
+ stats->duration = gkc_summary_alloc(GK_EPSILON);
+}
+
+static void *durations_status_init(void)
+{
+ struct st_duration_agg_stats_t *agg_stats;
+
+ agg_stats = h2o_mem_alloc(sizeof(*agg_stats));
+
+ duration_stats_init(&agg_stats->stats);
+ pthread_mutex_init(&agg_stats->mutex, NULL);
+
+ return agg_stats;
+}
+
+static void duration_stats_free(struct st_duration_stats_t *stats)
+{
+ gkc_summary_free(stats->connect_time);
+ gkc_summary_free(stats->header_time);
+ gkc_summary_free(stats->body_time);
+ gkc_summary_free(stats->request_total_time);
+ gkc_summary_free(stats->process_time);
+ gkc_summary_free(stats->response_time);
+ gkc_summary_free(stats->duration);
+}
+
+static h2o_iovec_t durations_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req)
+{
+ struct st_duration_agg_stats_t *agg_stats = priv;
+ h2o_iovec_t ret;
+
+#define BUFSIZE 16384
+#define DURATION_FMT(x) \
+ " \"" x "-0\": %lu,\n" \
+ " \"" x "-25\": %lu,\n" \
+ " \"" x "-50\": %lu,\n" \
+ " \"" x "-75\": %lu,\n" \
+ " \"" x "-99\": %lu\n"
+#define DURATION_VALS(x) \
+ gkc_query(agg_stats->stats.x, 0), gkc_query(agg_stats->stats.x, 0.25), gkc_query(agg_stats->stats.x, 0.5), \
+ gkc_query(agg_stats->stats.x, 0.75), gkc_query(agg_stats->stats.x, 0.99)
+
+ ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE);
+ ret.len = snprintf(
+ ret.base, BUFSIZE,
+ ",\n" DURATION_FMT("connect-time") "," DURATION_FMT("header-time") "," DURATION_FMT("body-time") "," DURATION_FMT(
+ "request-total-time") "," DURATION_FMT("process-time") "," DURATION_FMT("response-time") "," DURATION_FMT("duration"),
+ DURATION_VALS(connect_time), DURATION_VALS(header_time), DURATION_VALS(body_time), DURATION_VALS(request_total_time),
+ DURATION_VALS(process_time), DURATION_VALS(response_time), DURATION_VALS(duration));
+
+#undef BUFSIZE
+#undef DURATION_FMT
+#undef DURATION_VALS
+
+ duration_stats_free(&agg_stats->stats);
+ pthread_mutex_destroy(&agg_stats->mutex);
+
+ free(agg_stats);
+ return ret;
+}
+
+static void stat_access(h2o_logger_t *_self, h2o_req_t *req)
+{
+ struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(req->conn->ctx, _self);
+#define ADD_OBSERVATION(x, from, until) \
+ do { \
+ int64_t dur; \
+ if (h2o_time_compute_##x(req, &dur)) { \
+ gkc_insert_value(ctx_stats->x, dur); \
+ } \
+ } while (0)
+
+ ADD_OBSERVATION(connect_time, &req->conn->connected_at, &req->timestamps.request_begin_at);
+ ADD_OBSERVATION(header_time, &req->timestamps.request_begin_at, h2o_timeval_is_null(&req->timestamps.request_body_begin_at)
+ ? &req->processed_at.at
+ : &req->timestamps.request_body_begin_at);
+ ADD_OBSERVATION(body_time, h2o_timeval_is_null(&req->timestamps.request_body_begin_at) ? &req->processed_at.at
+ : &req->timestamps.request_body_begin_at,
+ &req->processed_at.at);
+ ADD_OBSERVATION(request_total_time, &req->timestamps.request_begin_at, &req->processed_at.at);
+ ADD_OBSERVATION(process_time, &req->processed_at.at, &req->timestamps.response_start_at);
+ ADD_OBSERVATION(response_time, &req->timestamps.response_start_at, &req->timestamps.response_end_at);
+ ADD_OBSERVATION(duration, &req->timestamps.request_begin_at, &req->timestamps.response_end_at);
+#undef ADD_OBSERVATION
+}
+
+void on_context_init(struct st_h2o_logger_t *self, h2o_context_t *ctx)
+{
+ struct st_duration_stats_t *duration_stats = h2o_mem_alloc(sizeof(struct st_duration_stats_t));
+ duration_stats_init(duration_stats);
+ h2o_context_set_logger_context(ctx, self, duration_stats);
+}
+
+void on_context_dispose(struct st_h2o_logger_t *self, h2o_context_t *ctx)
+{
+ struct st_duration_stats_t *duration_stats;
+ duration_stats = h2o_context_get_logger_context(ctx, self);
+ duration_stats_free(duration_stats);
+}
+
+void h2o_duration_stats_register(h2o_globalconf_t *conf)
+{
+ int i, k;
+ h2o_logger_t *logger;
+ h2o_hostconf_t *hconf;
+
+ durations_logger = logger = h2o_mem_alloc(sizeof(*logger));
+ memset(logger, 0, sizeof(*logger));
+ logger->_config_slot = conf->_num_config_slots++;
+ logger->log_access = stat_access;
+ logger->on_context_init = on_context_init;
+ logger->on_context_dispose = on_context_dispose;
+
+ for (k = 0; conf->hosts[k]; k++) {
+ hconf = conf->hosts[k];
+ for (i = 0; i < hconf->paths.size; i++) {
+ int j;
+ for (j = 0; j < hconf->paths.entries[i].handlers.size; j++) {
+ h2o_pathconf_t *pathconf = &hconf->paths.entries[i];
+ h2o_vector_reserve(NULL, &pathconf->loggers, pathconf->loggers.size + 1);
+ pathconf->loggers.entries[pathconf->loggers.size++] = (void *)logger;
+ }
+ }
+ }
+}
+
+h2o_status_handler_t durations_status_handler = {
+ {H2O_STRLIT("durations")}, durations_status_init, durations_status_per_thread, durations_status_final,
+};
diff --git a/debian/vendor-h2o/lib/handler/status/events.c b/debian/vendor-h2o/lib/handler/status/events.c
new file mode 100644
index 0000000..e6ed0b7
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/status/events.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2016 Fastly
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "h2o.h"
+#include <inttypes.h>
+
+struct st_events_status_ctx_t {
+ uint64_t emitted_status_errors[H2O_STATUS_ERROR_MAX];
+ uint64_t h2_protocol_level_errors[H2O_HTTP2_ERROR_MAX];
+ uint64_t h2_read_closed;
+ uint64_t h2_write_closed;
+ pthread_mutex_t mutex;
+};
+
+static void events_status_per_thread(void *priv, h2o_context_t *ctx)
+{
+ size_t i;
+ struct st_events_status_ctx_t *esc = priv;
+
+ pthread_mutex_lock(&esc->mutex);
+
+ for (i = 0; i < H2O_STATUS_ERROR_MAX; i++) {
+ esc->emitted_status_errors[i] += ctx->emitted_error_status[i];
+ }
+ for (i = 0; i < H2O_HTTP2_ERROR_MAX; i++) {
+ esc->h2_protocol_level_errors[i] += ctx->http2.events.protocol_level_errors[i];
+ }
+ esc->h2_read_closed += ctx->http2.events.read_closed;
+ esc->h2_write_closed += ctx->http2.events.write_closed;
+
+ pthread_mutex_unlock(&esc->mutex);
+}
+
+static void *events_status_init(void)
+{
+ struct st_events_status_ctx_t *ret;
+
+ ret = h2o_mem_alloc(sizeof(*ret));
+ memset(ret, 0, sizeof(*ret));
+ pthread_mutex_init(&ret->mutex, NULL);
+
+ return ret;
+}
+
+static h2o_iovec_t events_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req)
+{
+ struct st_events_status_ctx_t *esc = priv;
+ h2o_iovec_t ret;
+
+#define H1_AGG_ERR(status_) esc->emitted_status_errors[H2O_STATUS_ERROR_##status_]
+#define H2_AGG_ERR(err_) esc->h2_protocol_level_errors[-H2O_HTTP2_ERROR_##err_]
+#define BUFSIZE (2 * 1024)
+ ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE);
+ ret.len = snprintf(ret.base, BUFSIZE, ",\n"
+ " \"status-errors.400\": %" PRIu64 ",\n"
+ " \"status-errors.403\": %" PRIu64 ",\n"
+ " \"status-errors.404\": %" PRIu64 ",\n"
+ " \"status-errors.405\": %" PRIu64 ",\n"
+ " \"status-errors.416\": %" PRIu64 ",\n"
+ " \"status-errors.417\": %" PRIu64 ",\n"
+ " \"status-errors.500\": %" PRIu64 ",\n"
+ " \"status-errors.502\": %" PRIu64 ",\n"
+ " \"status-errors.503\": %" PRIu64 ",\n"
+ " \"http2-errors.protocol\": %" PRIu64 ", \n"
+ " \"http2-errors.internal\": %" PRIu64 ", \n"
+ " \"http2-errors.flow-control\": %" PRIu64 ", \n"
+ " \"http2-errors.settings-timeout\": %" PRIu64 ", \n"
+ " \"http2-errors.stream-closed\": %" PRIu64 ", \n"
+ " \"http2-errors.frame-size\": %" PRIu64 ", \n"
+ " \"http2-errors.refused-stream\": %" PRIu64 ", \n"
+ " \"http2-errors.cancel\": %" PRIu64 ", \n"
+ " \"http2-errors.compression\": %" PRIu64 ", \n"
+ " \"http2-errors.connect\": %" PRIu64 ", \n"
+ " \"http2-errors.enhance-your-calm\": %" PRIu64 ", \n"
+ " \"http2-errors.inadequate-security\": %" PRIu64 ", \n"
+ " \"http2.read-closed\": %" PRIu64 ", \n"
+ " \"http2.write-closed\": %" PRIu64 "\n",
+ H1_AGG_ERR(400), H1_AGG_ERR(403), H1_AGG_ERR(404), H1_AGG_ERR(405), H1_AGG_ERR(416), H1_AGG_ERR(417),
+ H1_AGG_ERR(500), H1_AGG_ERR(502), H1_AGG_ERR(503), H2_AGG_ERR(PROTOCOL), H2_AGG_ERR(INTERNAL),
+ H2_AGG_ERR(FLOW_CONTROL), H2_AGG_ERR(SETTINGS_TIMEOUT), H2_AGG_ERR(STREAM_CLOSED), H2_AGG_ERR(FRAME_SIZE),
+ H2_AGG_ERR(REFUSED_STREAM), H2_AGG_ERR(CANCEL), H2_AGG_ERR(COMPRESSION), H2_AGG_ERR(CONNECT),
+ H2_AGG_ERR(ENHANCE_YOUR_CALM), H2_AGG_ERR(INADEQUATE_SECURITY), esc->h2_read_closed, esc->h2_write_closed);
+ pthread_mutex_destroy(&esc->mutex);
+ free(esc);
+ return ret;
+#undef BUFSIZE
+#undef H1_AGG_ERR
+#undef H2_AGG_ERR
+}
+
+h2o_status_handler_t events_status_handler = {
+ {H2O_STRLIT("events")}, events_status_init, events_status_per_thread, events_status_final,
+};
diff --git a/debian/vendor-h2o/lib/handler/status/requests.c b/debian/vendor-h2o/lib/handler/status/requests.c
new file mode 100644
index 0000000..4854e4a
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/status/requests.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "h2o.h"
+
+struct st_requests_status_ctx_t {
+ h2o_logconf_t *logconf;
+ h2o_iovec_t req_data;
+ pthread_mutex_t mutex;
+};
+
+struct st_collect_req_status_cbdata_t {
+ h2o_logconf_t *logconf;
+ h2o_buffer_t *buffer;
+};
+
+static int collect_req_status(h2o_req_t *req, void *_cbdata)
+{
+ struct st_collect_req_status_cbdata_t *cbdata = _cbdata;
+
+ /* collect log */
+ char buf[4096];
+ size_t len = sizeof(buf);
+ char *logline = h2o_log_request(cbdata->logconf, req, &len, buf);
+ assert(len != 0);
+ --len; /* omit trailing LF */
+
+ /* append to buffer */
+ h2o_buffer_reserve(&cbdata->buffer, len + 3);
+ memcpy(cbdata->buffer->bytes + cbdata->buffer->size, logline, len);
+ cbdata->buffer->size += len;
+
+ if (logline != buf)
+ free(logline);
+
+ return 0;
+}
+
+static void requests_status_per_thread(void *priv, h2o_context_t *ctx)
+{
+ struct st_requests_status_ctx_t *rsc = priv;
+ struct st_collect_req_status_cbdata_t cbdata = {rsc->logconf};
+
+ /* we encountered an error at init() time, return early */
+ if (rsc->logconf == NULL)
+ return;
+
+ h2o_buffer_init(&cbdata.buffer, &h2o_socket_buffer_prototype);
+ ctx->globalconf->http1.callbacks.foreach_request(ctx, collect_req_status, &cbdata);
+ ctx->globalconf->http2.callbacks.foreach_request(ctx, collect_req_status, &cbdata);
+
+ /* concat JSON elements */
+ if (cbdata.buffer->size != 0) {
+ pthread_mutex_lock(&rsc->mutex);
+ if (rsc->req_data.len == 0)
+ h2o_buffer_consume(&cbdata.buffer, 1); /* skip preceeding comma */
+ rsc->req_data.base = h2o_mem_realloc(rsc->req_data.base, rsc->req_data.len + cbdata.buffer->size);
+ memcpy(rsc->req_data.base + rsc->req_data.len, cbdata.buffer->bytes, cbdata.buffer->size);
+ rsc->req_data.len += cbdata.buffer->size;
+ pthread_mutex_unlock(&rsc->mutex);
+ }
+
+ h2o_buffer_dispose(&cbdata.buffer);
+}
+
+static void *requests_status_init(void)
+{
+ struct st_requests_status_ctx_t *rsc = h2o_mem_alloc(sizeof(*rsc));
+ char errbuf[256];
+
+#define ELEMENT(key, expr) "\"" key "\": \"" expr "\""
+#define X_ELEMENT(id) ELEMENT(id, "%{" id "}x")
+#define SEPARATOR ", "
+ const char *fmt = ",\n {"
+ /* combined_log */
+ ELEMENT("host", "%h") SEPARATOR ELEMENT("user", "%u") SEPARATOR ELEMENT("at", "%{%Y%m%dT%H%M%S}t.%{usec_frac}t%{%z}t")
+ SEPARATOR ELEMENT("method", "%m") SEPARATOR ELEMENT("path", "%U") SEPARATOR ELEMENT("query", "%q")
+ SEPARATOR ELEMENT("protocol", "%H") SEPARATOR ELEMENT("referer", "%{Referer}i")
+ SEPARATOR ELEMENT("user-agent", "%{User-agent}i") SEPARATOR
+ /* time */
+ X_ELEMENT("connect-time") SEPARATOR X_ELEMENT("request-header-time") SEPARATOR X_ELEMENT("request-body-time")
+ SEPARATOR X_ELEMENT("request-total-time") SEPARATOR X_ELEMENT("process-time") SEPARATOR X_ELEMENT("response-time")
+ SEPARATOR
+ /* connection */
+ X_ELEMENT("connection-id") SEPARATOR X_ELEMENT("ssl.protocol-version") SEPARATOR X_ELEMENT("ssl.session-reused")
+ SEPARATOR X_ELEMENT("ssl.cipher") SEPARATOR X_ELEMENT("ssl.cipher-bits") SEPARATOR X_ELEMENT("ssl.session-ticket")
+ SEPARATOR
+ /* http1 */
+ X_ELEMENT("http1.request-index") SEPARATOR
+ /* http2 */
+ X_ELEMENT("http2.stream-id") SEPARATOR X_ELEMENT("http2.priority.received.exclusive")
+ SEPARATOR X_ELEMENT("http2.priority.received.parent") SEPARATOR X_ELEMENT("http2.priority.received.weight")
+ SEPARATOR X_ELEMENT("http2.priority.actual.parent") SEPARATOR X_ELEMENT("http2.priority.actual.weight") SEPARATOR
+ /* misc */
+ ELEMENT("authority", "%V")
+ /* end */
+ "}";
+#undef ELEMENT
+#undef X_ELEMENT
+#undef SEPARATOR
+
+ /* compile logconf */
+ if ((rsc->logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL)
+ /* log format compilation error is an internal logic flaw, therefore we need not send the details to the client */
+ fprintf(stderr, "[lib/handler/status/requests.c] failed to compile log format: %s", errbuf);
+
+ rsc->req_data = (h2o_iovec_t){NULL};
+ pthread_mutex_init(&rsc->mutex, NULL);
+
+ return rsc;
+}
+
+static h2o_iovec_t requests_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req)
+{
+ h2o_iovec_t ret = {NULL};
+ struct st_requests_status_ctx_t *rsc = priv;
+
+ if (rsc->logconf != NULL) {
+ ret = h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT(",\n \"requests\": [")), rsc->req_data,
+ h2o_iovec_init(H2O_STRLIT("\n ]")));
+ h2o_logconf_dispose(rsc->logconf);
+ }
+ free(rsc->req_data.base);
+ pthread_mutex_destroy(&rsc->mutex);
+
+ free(rsc);
+ return ret;
+}
+
+h2o_status_handler_t requests_status_handler = {
+ {H2O_STRLIT("requests")}, requests_status_init, requests_status_per_thread, requests_status_final,
+};
diff --git a/debian/vendor-h2o/lib/handler/throttle_resp.c b/debian/vendor-h2o/lib/handler/throttle_resp.c
new file mode 100644
index 0000000..a0028b5
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/throttle_resp.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2016 Justin Zhu
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include "h2o.h"
+
+#ifndef HUNDRED_MS
+#define HUNDRED_MS 100
+#endif
+
+#ifndef ONE_SECOND
+#define ONE_SECOND 1000
+#endif
+
+typedef H2O_VECTOR(h2o_iovec_t) iovec_vector_t;
+
+typedef struct st_throttle_resp_t {
+ h2o_ostream_t super;
+ h2o_timeout_entry_t timeout_entry;
+ int64_t tokens;
+ size_t token_inc;
+ h2o_context_t *ctx;
+ h2o_req_t *req;
+ struct {
+ iovec_vector_t bufs;
+ h2o_send_state_t stream_state;
+ } state;
+} throttle_resp_t;
+
+static void real_send(throttle_resp_t *self)
+{
+ /* a really simple token bucket implementation */
+ assert(self->tokens > 0);
+ size_t i, token_consume;
+
+ token_consume = 0;
+
+ for (i = 0; i < self->state.bufs.size; i++) {
+ token_consume += self->state.bufs.entries[i].len;
+ }
+
+ self->tokens -= token_consume;
+
+ h2o_ostream_send_next(&self->super, self->req, self->state.bufs.entries, self->state.bufs.size, self->state.stream_state);
+ if (!h2o_send_state_is_in_progress(self->state.stream_state))
+ h2o_timeout_unlink(&self->timeout_entry);
+}
+
+static void add_token(h2o_timeout_entry_t *entry)
+{
+ throttle_resp_t *self = H2O_STRUCT_FROM_MEMBER(throttle_resp_t, timeout_entry, entry);
+
+ h2o_timeout_link(self->ctx->loop, &self->ctx->hundred_ms_timeout, &self->timeout_entry);
+ self->tokens += self->token_inc;
+
+ if (self->tokens > 0)
+ real_send(self);
+}
+
+static void on_send(h2o_ostream_t *_self, h2o_req_t *req, h2o_iovec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
+{
+ throttle_resp_t *self = (void *)_self;
+ size_t i;
+
+ /* I don't know if this is a proper way. */
+ h2o_vector_reserve(&req->pool, &self->state.bufs, inbufcnt);
+ /* start to save state */
+ for (i = 0; i < inbufcnt; ++i) {
+ self->state.bufs.entries[i] = inbufs[i];
+ }
+ self->state.bufs.size = inbufcnt;
+ self->state.stream_state = state;
+
+ /* if there's token, we try to send */
+ if (self->tokens > 0)
+ real_send(self);
+}
+
+static void on_stop(h2o_ostream_t *_self, h2o_req_t *req)
+{
+ throttle_resp_t *self = (void *)_self;
+ if (h2o_timeout_is_linked(&self->timeout_entry)) {
+ h2o_timeout_unlink(&self->timeout_entry);
+ }
+}
+
+static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
+{
+ throttle_resp_t *throttle;
+ h2o_iovec_t traffic_header_value;
+ size_t traffic_limit;
+
+ if (req->res.status != 200)
+ goto Next;
+ if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")))
+ goto Next;
+
+ ssize_t xt_index;
+ if ((xt_index = h2o_find_header(&req->res.headers, H2O_TOKEN_X_TRAFFIC, -1)) == -1)
+ goto Next;
+
+ traffic_header_value = req->res.headers.entries[xt_index].value;
+ char *buf = traffic_header_value.base;
+
+ if (H2O_UNLIKELY((traffic_limit = h2o_strtosizefwd(&buf, traffic_header_value.len)) == SIZE_MAX))
+ goto Next;
+
+ throttle = (void *)h2o_add_ostream(req, sizeof(throttle_resp_t), slot);
+
+ /* calculate the token increment per 100ms */
+ throttle->token_inc = traffic_limit * HUNDRED_MS / ONE_SECOND;
+ if (req->preferred_chunk_size > throttle->token_inc)
+ req->preferred_chunk_size = throttle->token_inc;
+
+ h2o_delete_header(&req->res.headers, xt_index);
+
+ throttle->super.do_send = on_send;
+ throttle->super.stop = on_stop;
+ throttle->ctx = req->conn->ctx;
+ throttle->req = req;
+ throttle->state.bufs.capacity = 0;
+ throttle->state.bufs.size = 0;
+ throttle->timeout_entry = (h2o_timeout_entry_t){0};
+ throttle->timeout_entry.cb = add_token;
+ throttle->tokens = throttle->token_inc;
+ slot = &throttle->super.next;
+
+ h2o_timeout_link(throttle->ctx->loop, &throttle->ctx->hundred_ms_timeout, &throttle->timeout_entry);
+
+Next:
+ h2o_setup_next_ostream(req, slot);
+}
+
+void h2o_throttle_resp_register(h2o_pathconf_t *pathconf)
+{
+ h2o_filter_t *self = h2o_create_filter(pathconf, sizeof(*self));
+ self->on_setup_ostream = on_setup_ostream;
+}