static h2o_iovec_t __get_if_match_header_value(const h2o_req_t* req, const kstr_t* key) { ssize_t header = h2o_find_header(&req->headers, H2O_TOKEN_IF_MATCH, SIZE_MAX); if (-1 == header) return (h2o_iovec_t){.base = NULL, .len = 0 }; return req->headers.entries[header].value; } /** * @return 1 if the client wants an ETag; otherwise 0 */ static int __prefers_etag(const h2o_req_t* req) { ssize_t header = h2o_find_header_by_str( &req->headers, "prefers", strlen("prefers"), SIZE_MAX); if (-1 == header) return 0; if (!strncmp(req->headers.entries[header].value.base, "ETag", min(strlen("ETag"), req->headers.entries[header].value.len))) return 1; return 0; }
static int check_header(h2o_res_t *res, const h2o_token_t *header_name, const char *expected) { size_t index = h2o_find_header(&res->headers, header_name, SIZE_MAX); if (index == SIZE_MAX) return 0; return h2o_lcstris(res->headers.entries[index].value.base, res->headers.entries[index].value.len, expected, strlen(expected)); }
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, H2O_ALIGNOF(*ostream), sizeof(*ostream), slot); ostream->do_send = on_send; }
static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot) { struct rproxy_t *rproxy; ssize_t reproxy_header_index; h2o_buf_t reproxy_url; /* do nothing unless 200 */ if (req->res.status != 200) goto SkipMe; if ((reproxy_header_index = h2o_find_header(&req->res.headers, H2O_TOKEN_X_REPROXY_URL, -1)) == -1) goto SkipMe; reproxy_url = req->res.headers.entries[reproxy_header_index].value; h2o_delete_header(&req->res.headers, reproxy_header_index); /* setup */ rproxy = (void*)h2o_add_ostream(req, sizeof(struct rproxy_t), slot); rproxy->filter = self; rproxy->super.do_send = send_chunk; rproxy->reproxy_url = h2o_strdup(&req->pool, reproxy_url.base, reproxy_url.len).base; /* next ostream is setup when send_chunk receives EOS */ return; SkipMe: h2o_setup_next_ostream(self, req, slot); }
static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot) { chunked_encoder_t *encoder; /* do nothing if content-length is known */ if (req->res.content_length != SIZE_MAX) 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->method.base, req->method.len, H2O_STRLIT("HEAD")) == 0) 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, H2O_STRLIT("chunked")); /* 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(self, req, slot); }
/* * A request without neither Content-Length or Transfer-Encoding header implies a zero-length request body (see 6th rule of RFC 7230 * 3.3.3). * OTOH, section 3.3.3 states: * * A user agent SHOULD send a Content-Length in a request message when * no Transfer-Encoding is sent and the request method defines a meaning * for an enclosed payload body. For example, a Content-Length header * field is normally sent in a POST request even when the value is 0 * (indicating an empty payload body). A user agent SHOULD NOT send a * Content-Length header field when the request message does not contain * a payload body and the method semantics do not anticipate such a * body. * * PUT and POST define a meaning for the payload body, let's emit a * Content-Length header if it doesn't exist already, since the server * might send a '411 Length Required' response. * * see also: ML thread starting at https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0580.html */ static int req_requires_content_length(h2o_req_t *req) { int is_put_or_post = (req->method.len >= 1 && req->method.base[0] == 'P' && (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) || h2o_memis(req->method.base, req->method.len, H2O_STRLIT("PUT")))); return is_put_or_post && h2o_find_header(&req->res.headers, H2O_TOKEN_TRANSFER_ENCODING, -1) == -1; }
static int is_msie(h2o_req_t *req) { ssize_t cursor = h2o_find_header(&req->headers, H2O_TOKEN_USER_AGENT, -1); if (cursor == -1) return 0; if (h2o_strstr(req->headers.entries[cursor].value.base, req->headers.entries[cursor].value.len, H2O_STRLIT("; MSIE ")) == SIZE_MAX) return 0; return 1; }
void h2o_set_header(h2o_mempool_t *pool, h2o_headers_t *headers, const h2o_token_t *token, const char *value, size_t value_len, int overwrite_if_exists) { ssize_t cursor = h2o_find_header(headers, token, -1); if (cursor != -1) { if (overwrite_if_exists) { h2o_buf_t *slot = &headers->entries[cursor].value; slot->base = (char*)value; slot->len = value_len; } } else { h2o_add_header(pool, headers, token, value, value_len); } }
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 mrb_value h2o_mrb_req_send_file(mrb_state *mrb, mrb_value self) { h2o_mruby_internal_context_t *mruby_ctx = (h2o_mruby_internal_context_t *)mrb->ud; char *fn; int status; h2o_iovec_t content_type; int content_type_header_removed = 0; if (mruby_ctx->state != H2O_MRUBY_STATE_UNDETERMINED) mrb_raise(mrb, E_RUNTIME_ERROR, "response already sent"); mrb_get_args(mrb, "z", &fn); /* determine status and reason to be used */ if ((status = mruby_ctx->req->res.status) == 0) status = 200; { /* determine content-type (removing existing header, since it is added by h2o_file_send) */ ssize_t header_index; if ((header_index = h2o_find_header(&mruby_ctx->req->res.headers, H2O_TOKEN_CONTENT_TYPE, -1)) != -1) { content_type = mruby_ctx->req->res.headers.entries[header_index].value; h2o_delete_header(&mruby_ctx->req->res.headers, header_index); content_type_header_removed = 1; } else { const char *ext = h2o_get_filext(fn, strlen(fn)); h2o_mimemap_type_t *m = h2o_mimemap_get_type_by_extension(mruby_ctx->req->pathconf->mimemap, ext); if (m == NULL || m->type != H2O_MIMEMAP_TYPE_MIMETYPE) { m = h2o_mimemap_get_default_type(mruby_ctx->req->pathconf->mimemap); assert(m->type == H2O_MIMEMAP_TYPE_MIMETYPE); } content_type = m->data.mimetype; } } if (h2o_file_send(mruby_ctx->req, status, mruby_ctx->req->res.reason, fn, content_type, H2O_FILE_FLAG_SEND_GZIP) == 0) { /* succeeded, return true */ mruby_ctx->state = H2O_MRUBY_STATE_RESPONSE_SENT; return mrb_true_value(); } else { /* failed, restore content-type header and return false */ if (content_type_header_removed) h2o_add_header(&mruby_ctx->req->pool, &mruby_ctx->req->res.headers, H2O_TOKEN_CONTENT_TYPE, content_type.base, content_type.len); return mrb_false_value(); } }
static void on_setup_ostream(h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot) { gzip_encoder_t *encoder; 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; if (req->res.mime_attr == NULL) h2o_req_fill_mime_attributes(req); if (!req->res.mime_attr->is_compressible) goto Next; /* 100 is a rough estimate */ if (req->res.content_length <= 100) goto Next; /* skip if no accept-encoding is set */ if ((i = h2o_find_header(&req->headers, H2O_TOKEN_ACCEPT_ENCODING, -1)) == -1) goto Next; if (!h2o_contains_token(req->headers.entries[i].value.base, req->headers.entries[i].value.len, H2O_STRLIT("gzip"), ',')) 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; /* adjust the response headers */ req->res.content_length = SIZE_MAX; h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, H2O_STRLIT("gzip")); h2o_add_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, H2O_STRLIT("none")); } /* setup filter */ encoder = (void *)h2o_add_ostream(req, sizeof(gzip_encoder_t), slot); encoder->super.do_send = send_gzip; encoder->super.stop = stop_gzip; slot = &encoder->super.next; encoder->bufs.capacity = 0; encoder->bufs.size = 0; encoder->zstream.zalloc = gzip_encoder_alloc; encoder->zstream.zfree = gzip_encoder_free; encoder->zstream.opaque = encoder; /* 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); }
static int serve_with_generator(struct st_h2o_sendfile_generator_t *generator, h2o_req_t *req, 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; } /* 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; } } /* obtain mime type */ if (mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC) { do_close(&generator->super, req); return delegate_dynamic_request(req, req->path_normalized.len, rpath, rpath_len, mime_type); } assert(mime_type->type == H2O_MIMEMAP_TYPE_MIMETYPE); /* 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, content_range.base, content_range.len); h2o_send_error(req, 416, "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; h2o_buf_t vpath, mime_type; char *rpath; size_t rpath_len; struct st_h2o_sendfile_generator_t *generator = NULL; size_t if_modified_since_header_index, if_none_match_header_index; int is_dir; /* only accept GET (TODO accept HEAD as well) */ if (! h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) return -1; /* prefix match */ if (req->path.len < self->virtual_path.len || memcmp(req->path.base, self->virtual_path.base, self->virtual_path.len) != 0) return -1; /* normalize path */ vpath = h2o_normalize_path(&req->pool, req->path.base + self->virtual_path.len - 1, req->path.len - self->virtual_path.len + 1); if (vpath.len > PATH_MAX) return -1; /* build path (still unterminated at the end of the block) */ rpath = alloca( self->real_path.len + (vpath.len - 1) /* exclude "/" at the head */ + 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, vpath.base + 1, vpath.len - 1); rpath_len += vpath.len - 1; /* build generator (as well as terminating the rpath and its length upon success) */ if (rpath[rpath_len - 1] == '/') { h2o_buf_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->pool, rpath, &is_dir)) != NULL) { rpath_len += index_file->len; break; } if (is_dir) { /* note: apache redirects "path/" to "path/index.txt/" if index.txt is a dir */ char *path = alloca(req->path.len + index_file->len + 1); size_t path_len = sprintf(path, "%.*s%.*s", (int)req->path.len, req->path.base, (int)index_file->len, index_file->base); return redirect_to_dir(req, path, path_len); } if (errno != ENOENT) break; } } else { rpath[rpath_len] = '\0'; generator = create_generator(&req->pool, rpath, &is_dir); if (generator == NULL && is_dir) return redirect_to_dir(req, req->path.base, req->path.len); } /* return error if failed */ if (generator == NULL) { if (errno == ENOENT) { h2o_send_error(req, 404, "File Not Found", "file not found"); } else { h2o_send_error(req, 403, "Access Forbidden", "access forbidden"); } return 0; } if ((if_none_match_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_NONE_MATCH, SIZE_MAX)) != -1) { h2o_buf_t *if_none_match = &req->headers.entries[if_none_match_header_index].value; if (h2o_memis(if_none_match->base, if_none_match->len, generator->etag_buf, generator->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_buf_t *if_modified_since = &req->headers.entries[if_modified_since_header_index].value; if (h2o_memis(if_modified_since->base, if_modified_since->len, generator->last_modified_buf, H2O_TIMESTR_RFC1123_LEN)) goto NotModified; } /* obtain mime type */ mime_type = h2o_mimemap_get_type(self->mimemap, h2o_get_filext(rpath, rpath_len)); /* return file */ do_send_file(generator, req, 200, "OK", mime_type); return 0; NotModified: req->res.status = 304; req->res.reason = "Not Modified"; h2o_send_inline(req, NULL, 0); do_close(&generator->super, req); return 0; }
static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) { h2o_timestamp_t ts; h2o_get_timestamp(conn->super.ctx, &stream->req.pool, &ts); /* cancel push with an error response */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (400 <= stream->req.res.status) goto CancelPush; if (stream->cache_digests != NULL) { ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); if (etag_index != -1) { h2o_iovec_t url = h2o_concat(&stream->req.pool, stream->req.input.scheme->name, h2o_iovec_init(H2O_STRLIT("://")), stream->req.input.authority, stream->req.input.path); h2o_iovec_t *etag = &stream->req.headers.entries[etag_index].value; if (h2o_cache_digests_lookup_by_url_and_etag(stream->cache_digests, url.base, url.len, etag->base, etag->len) == H2O_CACHE_DIGESTS_STATE_FRESH) goto CancelPush; } } } /* reset casper cookie in case cache-digests exist */ if (stream->cache_digests != NULL && stream->req.hostconf->http2.casper.capacity_bits != 0) { h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, H2O_STRLIT("h2o_casper=; Path=/; Expires=Sat, 01 Jan 2000 00:00:00 GMT")); } /* CASPER */ if (conn->casper != NULL) { /* update casper if necessary */ if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, 1)) { /* cancel if the pushed resource is already marked as cached */ if (h2o_http2_stream_is_push(stream->stream_id)) goto CancelPush; } } if (stream->cache_digests != NULL) goto SkipCookie; /* browsers might ignore push responses, or they may process the responses in a different order than they were pushed. * Therefore H2O tries to include casper cookie only in the last stream that may be received by the client, or when the * value become stable; see also: https://github.com/h2o/h2o/issues/421 */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (!(conn->num_streams.pull.open == 0 && (conn->num_streams.push.half_closed - conn->num_streams.push.send_body) == 1)) goto SkipCookie; } else { if (conn->num_streams.push.half_closed - conn->num_streams.push.send_body != 0) goto SkipCookie; } h2o_iovec_t cookie = h2o_http2_casper_get_cookie(conn->casper); h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, cookie.base, cookie.len); SkipCookie:; } if (h2o_http2_stream_is_push(stream->stream_id)) { /* for push, send the push promise */ if (!stream->push.promise_sent) h2o_http2_stream_send_push_promise(conn, stream); /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ if (is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } else { /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based * prioritization (e.g. that of Firefox) */ if (conn->num_streams.priority.open == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && h2o_http2_scheduler_get_parent(&stream->_refs.scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } /* send HEADERS, as well as start sending body */ if (h2o_http2_stream_is_push(stream->stream_id)) h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, H2O_STRLIT("pushed")); h2o_hpack_flatten_response(&conn->_write.buf, &conn->_output_header_table, stream->stream_id, conn->peer_settings.max_frame_size, &stream->req.res, &ts, &conn->super.ctx->globalconf->server_name, stream->req.res.content_length); h2o_http2_conn_request_write(conn); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); return 0; CancelPush: h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, H2O_STRLIT("cancelled")); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_refs.link); if (stream->push.promise_sent) { #ifndef _MSC_VER h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); #else h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, H2O_HTTP2_ERROR_INTERNAL); #endif h2o_http2_conn_request_write(conn); } return -1; }
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 *line, *pos, *line_end; size_t element_index; /* note: LOG_ALLOCA_SIZE should be much greater than NI_MAXHOST to avoid unnecessary reallocations */ line = alloca(LOG_ALLOCA_SIZE); pos = line; line_end = line + LOG_ALLOCA_SIZE; for (element_index = 0; element_index != fh->num_elements; ++element_index) { struct log_element_t *element = fh->elements + element_index; /* reserve capacity + suffix.len */ #define RESERVE(capacity) \ do { \ if ((capacity) + element->suffix.len > line_end - pos) { \ size_t off = pos - line; \ line = expand_line_buf(line, line_end - line, off + (capacity) + element->suffix.len); \ pos = line + off; \ } \ } while (0) switch (element->type) { case ELEMENT_TYPE_EMPTY: RESERVE(0); break; case ELEMENT_TYPE_BYTES_SENT: /* %b */ RESERVE(sizeof("18446744073709551615") - 1); pos += sprintf(pos, "%llu", (unsigned long long)req->bytes_sent); break; case ELEMENT_TYPE_PROTOCOL: /* %H */ RESERVE(sizeof("HTTP/1.1")); pos += h2o_stringify_protocol_version(pos, req->version); break; case ELEMENT_TYPE_REMOTE_ADDR: /* %h */ { struct sockaddr_storage ss; socklen_t sslen; if ((sslen = req->conn->get_peername(req->conn, (void *)&ss)) != 0) { RESERVE(NI_MAXHOST); size_t l = h2o_socket_getnumerichost((void *)&ss, sslen, pos); if (l != SIZE_MAX) pos += l; else *pos++ = '-'; } else { RESERVE(1); *pos++ = '-'; } } break; case ELEMENT_TYPE_METHOD: /* %m */ RESERVE(req->input.method.len * 4); pos = append_unsafe_string(pos, req->input.method.base, req->input.method.len); break; case ELEMENT_TYPE_QUERY: /* %q */ if (req->input.query_at != SIZE_MAX) { size_t len = req->input.path.len - req->input.query_at; RESERVE(len * 4); pos = append_unsafe_string(pos, req->input.path.base + req->input.query_at, len); } break; case ELEMENT_TYPE_REQUEST_LINE: /* %r */ RESERVE((req->input.method.len + req->input.path.len) * 4 + sizeof(" HTTP/1.1")); pos = append_unsafe_string(pos, req->input.method.base, req->input.method.len); *pos++ = ' '; pos = append_unsafe_string(pos, req->input.path.base, req->input.path.len); *pos++ = ' '; pos += h2o_stringify_protocol_version(pos, req->version); break; case ELEMENT_TYPE_STATUS: /* %s */ RESERVE(sizeof("2147483647") - 1); pos += sprintf(pos, "%d", req->res.status); break; case ELEMENT_TYPE_TIMESTAMP: /* %t */ RESERVE(H2O_TIMESTR_LOG_LEN + 2); *pos++ = '['; pos = append_safe_string(pos, req->processed_at.str->log, H2O_TIMESTR_LOG_LEN); *pos++ = ']'; break; case ELEMENT_TYPE_URL_PATH: /* %U */ { size_t path_len = req->input.query_at == SIZE_MAX ? req->input.path.len : req->input.query_at; RESERVE(req->input.scheme->name.len + (sizeof("://") - 1) + (req->input.authority.len + path_len) * 4); pos = append_safe_string(pos, req->input.scheme->name.base, req->input.scheme->name.len); pos = append_safe_string(pos, H2O_STRLIT("://")); pos = append_unsafe_string(pos, req->input.authority.base, req->input.authority.len); pos = append_unsafe_string(pos, req->input.path.base, path_len); } break; case ELEMENT_TYPE_AUTHORITY: /* %V */ RESERVE(req->input.authority.len * 4); pos = append_unsafe_string(pos, req->input.authority.base, req->input.authority.len); break; case ELEMENT_TYPE_HOSTCONF: /* %v */ RESERVE(req->hostconf->authority.hostport.len * 4); pos = append_unsafe_string(pos, req->hostconf->authority.hostport.base, req->hostconf->authority.hostport.len); break; case ELEMENT_TYPE_LOGNAME: /* %l */ case ELEMENT_TYPE_REMOTE_USER: /* %u */ RESERVE(1); *pos++ = '-'; break; #define EMIT_HEADER(headers, _index) \ do { \ ssize_t index = (_index); \ if (index != -1) { \ const h2o_header_t *header = (headers)->entries + index; \ RESERVE(header->value.len * 4); \ pos = append_unsafe_string(pos, header->value.base, header->value.len); \ } else { \ RESERVE(1); \ *pos++ = '-'; \ } \ } while (0) case ELEMENT_TYPE_IN_HEADER_TOKEN: EMIT_HEADER(&req->headers, h2o_find_header(&req->headers, element->data.header_token, SIZE_MAX)); break; case ELEMENT_TYPE_IN_HEADER_STRING: EMIT_HEADER(&req->headers, h2o_find_header_by_str(&req->headers, element->data.header_string.base, element->data.header_string.len, SIZE_MAX)); break; case ELEMENT_TYPE_OUT_HEADER_TOKEN: EMIT_HEADER(&req->res.headers, h2o_find_header(&req->res.headers, element->data.header_token, SIZE_MAX)); break; case ELEMENT_TYPE_OUT_HEADER_STRING: EMIT_HEADER(&req->res.headers, h2o_find_header_by_str(&req->res.headers, element->data.header_string.base, element->data.header_string.len, SIZE_MAX)); break; #undef EMIT_HEADER default: assert(!"unknown type"); break; } #undef RESERVE pos = append_safe_string(pos, element->suffix.base, element->suffix.len); } write(fh->fd, line, pos - line); if (line_end - line != LOG_ALLOCA_SIZE) free(line); }
static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) { h2o_timestamp_t ts; size_t num_casper_entries_before_push = 0; h2o_get_timestamp(conn->super.ctx, &stream->req.pool, &ts); /* cancel push with an error response */ if (h2o_http2_stream_is_push(stream->stream_id)) { if (400 <= stream->req.res.status) goto CancelPush; h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-pushed"), 0, H2O_STRLIT("1")); } if (stream->req.hostconf->http2.casper.capacity_bits != 0) { /* extract the client-side cache fingerprint */ if (conn->casper == NULL) h2o_http2_conn_init_casper(conn, stream->req.hostconf->http2.casper.capacity_bits); size_t header_index = -1; while ((header_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_COOKIE, header_index)) != -1) { h2o_header_t *header = stream->req.headers.entries + header_index; h2o_http2_casper_consume_cookie(conn->casper, header->value.base, header->value.len); } num_casper_entries_before_push = h2o_http2_casper_num_entries(conn->casper); /* update casper if necessary */ if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); h2o_iovec_t etag = etag_index != -1 ? stream->req.headers.entries[etag_index].value : (h2o_iovec_t){}; if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, etag.base, etag.len, 1)) { /* cancel if the pushed resource is already marked as cached */ if (h2o_http2_stream_is_push(stream->stream_id)) goto CancelPush; } } } if (h2o_http2_stream_is_push(stream->stream_id)) { /* for push, send the push promise */ if (!stream->push.promise_sent) h2o_http2_stream_send_push_promise(conn, stream); /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ if (is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } else { /* for pull, push things requested, as well as send the casper cookie if modified */ if (conn->peer_settings.enable_push) { size_t i; for (i = 0; i != stream->req.http2_push_paths.size; ++i) h2o_http2_conn_push_path(conn, stream->req.http2_push_paths.entries[i], stream); /* send casper cookie if it has been altered (due to the __stream itself__ or by some of the pushes) */ if (conn->casper != NULL && num_casper_entries_before_push != h2o_http2_casper_num_entries(conn->casper)) { h2o_iovec_t cookie = h2o_http2_casper_build_cookie(conn->casper, &stream->req.pool); h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, cookie.base, cookie.len); } } /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based * prioritization (e.g. that of Firefox) */ if (conn->num_streams.open_priority == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && h2o_http2_scheduler_get_parent(&stream->_refs.scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) h2o_http2_scheduler_rebind(&stream->_refs.scheduler, &conn->scheduler, 257, 0); } /* send HEADERS, as well as start sending body */ h2o_hpack_flatten_response(&conn->_write.buf, &conn->_output_header_table, stream->stream_id, conn->peer_settings.max_frame_size, &stream->req.res, &ts, &conn->super.ctx->globalconf->server_name); h2o_http2_conn_request_write(conn); h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); return 0; CancelPush: h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_refs.link); if (stream->push.promise_sent) { h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, H2O_HTTP2_ERROR_INTERNAL); h2o_http2_conn_request_write(conn); } return -1; }