diff options
Diffstat (limited to '')
46 files changed, 9416 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/lib/handler/access_log.c b/web/server/h2o/libh2o/lib/handler/access_log.c new file mode 100644 index 000000000..4a7704174 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/chunked.c b/web/server/h2o/libh2o/lib/handler/chunked.c new file mode 100644 index 000000000..b6ad4346b --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/compress.c b/web/server/h2o/libh2o/lib/handler/compress.c new file mode 100644 index 000000000..615977131 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/compress/brotli.cc b/web/server/h2o/libh2o/lib/handler/compress/brotli.cc new file mode 100644 index 000000000..d4d06e9c4 --- /dev/null +++ b/web/server/h2o/libh2o/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 ¶ms, 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 ¶ms, 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/web/server/h2o/libh2o/lib/handler/compress/gzip.c b/web/server/h2o/libh2o/lib/handler/compress/gzip.c new file mode 100644 index 000000000..c12260df3 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/access_log.c b/web/server/h2o/libh2o/lib/handler/configurator/access_log.c new file mode 100644 index 000000000..de6380882 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/compress.c b/web/server/h2o/libh2o/lib/handler/configurator/compress.c new file mode 100644 index 000000000..c023dd5cf --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/errordoc.c b/web/server/h2o/libh2o/lib/handler/configurator/errordoc.c new file mode 100644 index 000000000..ab24923ee --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/expires.c b/web/server/h2o/libh2o/lib/handler/configurator/expires.c new file mode 100644 index 000000000..01d404dd5 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/fastcgi.c b/web/server/h2o/libh2o/lib/handler/configurator/fastcgi.c new file mode 100644 index 000000000..bf89b7b26 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/file.c b/web/server/h2o/libh2o/lib/handler/configurator/file.c new file mode 100644 index 000000000..c1c779c68 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/headers.c b/web/server/h2o/libh2o/lib/handler/configurator/headers.c new file mode 100644 index 000000000..68536c052 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/headers_util.c b/web/server/h2o/libh2o/lib/handler/configurator/headers_util.c new file mode 100644 index 000000000..c05b9b7c2 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/http2_debug_state.c b/web/server/h2o/libh2o/lib/handler/configurator/http2_debug_state.c new file mode 100644 index 000000000..419da9de8 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/mruby.c b/web/server/h2o/libh2o/lib/handler/configurator/mruby.c new file mode 100644 index 000000000..1cef8f499 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/proxy.c b/web/server/h2o/libh2o/lib/handler/configurator/proxy.c new file mode 100644 index 000000000..cfc9cbf40 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/redirect.c b/web/server/h2o/libh2o/lib/handler/configurator/redirect.c new file mode 100644 index 000000000..4ebbb99c0 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/reproxy.c b/web/server/h2o/libh2o/lib/handler/configurator/reproxy.c new file mode 100644 index 000000000..21650e4fb --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/status.c b/web/server/h2o/libh2o/lib/handler/configurator/status.c new file mode 100644 index 000000000..8645aa33d --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/configurator/throttle_resp.c b/web/server/h2o/libh2o/lib/handler/configurator/throttle_resp.c new file mode 100644 index 000000000..61431f756 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/errordoc.c b/web/server/h2o/libh2o/lib/handler/errordoc.c new file mode 100644 index 000000000..72744f358 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/expires.c b/web/server/h2o/libh2o/lib/handler/expires.c new file mode 100644 index 000000000..c77225649 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/fastcgi.c b/web/server/h2o/libh2o/lib/handler/fastcgi.c new file mode 100644 index 000000000..d6bcf9ede --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/file.c b/web/server/h2o/libh2o/lib/handler/file.c new file mode 100644 index 000000000..5d7c7a2a4 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/file/_templates.c.h b/web/server/h2o/libh2o/lib/handler/file/_templates.c.h new file mode 100644 index 000000000..11b73577c --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/file/templates.c.h b/web/server/h2o/libh2o/lib/handler/file/templates.c.h new file mode 100644 index 000000000..24c12dae5 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/headers.c b/web/server/h2o/libh2o/lib/handler/headers.c new file mode 100644 index 000000000..973976b35 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/headers_util.c b/web/server/h2o/libh2o/lib/handler/headers_util.c new file mode 100644 index 000000000..5ecacfa54 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/http2_debug_state.c b/web/server/h2o/libh2o/lib/handler/http2_debug_state.c new file mode 100644 index 000000000..0aa0bd1b1 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mimemap.c b/web/server/h2o/libh2o/lib/handler/mimemap.c new file mode 100644 index 000000000..764362da0 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mimemap/defaults.c.h b/web/server/h2o/libh2o/lib/handler/mimemap/defaults.c.h new file mode 100644 index 000000000..a395e0ccb --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby.c b/web/server/h2o/libh2o/lib/handler/mruby.c new file mode 100644 index 000000000..af2af53fd --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/chunked.c b/web/server/h2o/libh2o/lib/handler/mruby/chunked.c new file mode 100644 index 000000000..28e3ae433 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/embedded.c.h b/web/server/h2o/libh2o/lib/handler/mruby/embedded.c.h new file mode 100644 index 000000000..db4bb2321 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/embedded/chunked.rb b/web/server/h2o/libh2o/lib/handler/mruby/embedded/chunked.rb new file mode 100644 index 000000000..ff4e578f8 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/embedded/core.rb b/web/server/h2o/libh2o/lib/handler/mruby/embedded/core.rb new file mode 100644 index 000000000..e62583df4 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/embedded/http_request.rb b/web/server/h2o/libh2o/lib/handler/mruby/embedded/http_request.rb new file mode 100644 index 000000000..3f3247a3c --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/mruby/http_request.c b/web/server/h2o/libh2o/lib/handler/mruby/http_request.c new file mode 100644 index 000000000..964d03f19 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/proxy.c b/web/server/h2o/libh2o/lib/handler/proxy.c new file mode 100644 index 000000000..1d87225e1 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/redirect.c b/web/server/h2o/libh2o/lib/handler/redirect.c new file mode 100644 index 000000000..c8b9c5086 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/reproxy.c b/web/server/h2o/libh2o/lib/handler/reproxy.c new file mode 100644 index 000000000..369386d27 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/status.c b/web/server/h2o/libh2o/lib/handler/status.c new file mode 100644 index 000000000..93befed3b --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/status/durations.c b/web/server/h2o/libh2o/lib/handler/status/durations.c new file mode 100644 index 000000000..f011107bf --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/status/events.c b/web/server/h2o/libh2o/lib/handler/status/events.c new file mode 100644 index 000000000..e6ed0b7c6 --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/status/requests.c b/web/server/h2o/libh2o/lib/handler/status/requests.c new file mode 100644 index 000000000..4854e4a1f --- /dev/null +++ b/web/server/h2o/libh2o/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/web/server/h2o/libh2o/lib/handler/throttle_resp.c b/web/server/h2o/libh2o/lib/handler/throttle_resp.c new file mode 100644 index 000000000..a0028b502 --- /dev/null +++ b/web/server/h2o/libh2o/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; +} |