Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option to skip supplementation of a Date header #1495

Merged
merged 13 commits into from Nov 24, 2017
Merged
4 changes: 3 additions & 1 deletion doc/configure/cgi.html
Expand Up @@ -79,7 +79,8 @@ <h2>
</div>


The gateway also provides options to for tuning the behavior. A full list of options can be obtained by running the gateway directly with <code>--help</code> option.
The gateway also provides options to for tuning the behavior.
A full list of options can be obtained by running the gateway directly with <code>--help</code> option.

<div class="example">
<div class="caption">Example. Output of <code>share/h2o/fastcgi-cgi --help</code></div>
Expand All @@ -95,6 +96,7 @@ <h2>
connections.
--max-workers=nnn maximum number of CGI processes (default: unlimited)
--pass-authz if set, preserves HTTP_AUTHORIZATION parameter
--verbose verbose mode
</code></pre>
</div>

Expand Down
20 changes: 20 additions & 0 deletions doc/configure/proxy_directives.html
Expand Up @@ -227,6 +227,26 @@ <h3><a href="configure/proxy_directives.html#proxy.emit-via-header"><code>"proxy
</dl>


<div id="proxy.emit-missing-date-header" class="directive-head">
<div class="directive-since">since v2.3</div>
<h3><a href="configure/proxy_directives.html#proxy.emit-missing-date-header"><code>"proxy.emit-missing-date-header"</code></a></h3>
</div>

<dl class="directive-desc">
<dt>Description:</dt>
<dd>
<p>
A boolean flag (<code>ON</code> or <code>OFF</code>) indicating if H2O should add a <code>date</date> header to the response, if that header is missing from the upstream response.
</p>

</dd>
<dt><a href="configure/syntax_and_structure.html#config_levels">Level</a>:</dt>
<dd>global</dd>
<dt>Default:</dt>
<dd><code><pre>proxy.emit-missing-date-header: ON</pre></code>
</dl>


<div id="proxy.header.add" class="directive-head">
<div class="directive-since">since v2.2</div>
<h3><a href="configure/proxy_directives.html#proxy.header.add"><code>"proxy.header.add"</code></a></h3>
Expand Down
14 changes: 13 additions & 1 deletion doc/h2o.conf.5
Expand Up @@ -2617,6 +2617,16 @@ proxy.emit-x-forwarded-headers
.RE


.SS proxy.emit-missing-date-header
(since v2.3)
A boolean flag (ON or OFF) indicating if H2O should add a date header to the response, if that header is missing from the upstream response.

.PP

.RE
.RE


.SS proxy.header.add
(since v2.2)
Modifies the request headers sent to the application server.
Expand Down Expand Up @@ -3113,7 +3123,8 @@ file.custom-handler:
.PP


The gateway also provides options to for tuning the behavior. A full list of options can be obtained by running the gateway directly with --help option.
The gateway also provides options to for tuning the behavior.
A full list of options can be obtained by running the gateway directly with --help option.

.PP
.BR Example:\
Expand All @@ -3133,6 +3144,7 @@ Options:
connections.
--max-workers=nnn maximum number of CGI processes (default: unlimited)
--pass-authz if set, preserves HTTP_AUTHORIZATION parameter
--verbose verbose mode

.RE
.fi
Expand Down
2 changes: 1 addition & 1 deletion doc/search/searchindex.js

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions include/h2o.h
Expand Up @@ -420,6 +420,10 @@ struct st_h2o_globalconf_t {
* a boolean flag if set to true, instructs the proxy to emit a via header
*/
unsigned emit_via_header : 1;
/**
* a boolean flag if set to true, instructs the proxy to emit a date header, if it's missing from the upstream response
*/
unsigned emit_missing_date_header : 1;
/**
* global socketpool
*/
Expand Down Expand Up @@ -1509,6 +1513,10 @@ enum {
H2O_SEND_ERROR_KEEP_HEADERS = 0x2
};

/**
* Add a `date:` header to the response
*/
void h2o_resp_add_date_header(h2o_req_t *req);
/**
* sends the given string as the response
*/
Expand Down
3 changes: 1 addition & 2 deletions include/h2o/http2_internal.h
Expand Up @@ -69,8 +69,7 @@ size_t h2o_hpack_encode_string(uint8_t *dst, const char *s, size_t len);
void h2o_hpack_flatten_request(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t stream_id,
size_t max_frame_size, h2o_req_t *req, uint32_t parent_stream_id);
void h2o_hpack_flatten_response(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t stream_id,
size_t max_frame_size, h2o_res_t *res, h2o_timestamp_t *ts, const h2o_iovec_t *server_name,
size_t content_length);
size_t max_frame_size, h2o_res_t *res, const h2o_iovec_t *server_name, size_t content_length);
static h2o_hpack_header_table_entry_t *h2o_hpack_header_table_get(h2o_hpack_header_table_t *table, size_t index);

/* frames */
Expand Down
1 change: 1 addition & 0 deletions lib/core/config.c
Expand Up @@ -186,6 +186,7 @@ void h2o_config_init(h2o_globalconf_t *config)
config->proxy.first_byte_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT;
config->proxy.emit_x_forwarded_headers = 1;
config->proxy.emit_via_header = 1;
config->proxy.emit_missing_date_header = 1;
config->http2.max_concurrent_requests_per_connection = H2O_HTTP2_SETTINGS_HOST.max_concurrent_streams;
config->http2.max_streams_for_priority = 16;
config->http2.latency_optimization.min_rtt = 50; // milliseconds
Expand Down
7 changes: 7 additions & 0 deletions lib/core/proxy.c
Expand Up @@ -444,6 +444,8 @@ static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *er
struct rp_generator_t *self = client->data;
h2o_req_t *req = self->src_req;
size_t i;
int emit_missing_date_header = req->conn->ctx->globalconf->proxy.emit_missing_date_header;
int seen_date_header = 0;

if (errstr != NULL && errstr != h2o_http1client_error_is_eos) {
self->client = NULL;
Expand Down Expand Up @@ -499,6 +501,8 @@ static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *er
} else if (token == H2O_TOKEN_X_COMPRESS_HINT) {
req->compress_hint = compress_hint_to_enum(headers[i].value.base, headers[i].value.len);
goto Skip;
} else if (token == H2O_TOKEN_DATE) {
seen_date_header = 1;
}
/* default behaviour, transfer the header downstream */
AddHeaderDuped:
Expand All @@ -514,6 +518,9 @@ static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *er
}
}

if (!seen_date_header && emit_missing_date_header)
h2o_resp_add_date_header(req);

if (self->is_websocket_handshake && req->res.status == 101) {
h2o_http1client_ctx_t *client_ctx = get_client_ctx(req);
assert(client_ctx->websocket_timeout != NULL);
Expand Down
7 changes: 7 additions & 0 deletions lib/core/request.c
Expand Up @@ -709,3 +709,10 @@ h2o_iovec_t h2o_push_path_in_link_header(h2o_req_t *req, const char *value, size

return ret;
}

void h2o_resp_add_date_header(h2o_req_t *req)
{
h2o_timestamp_t ts;
h2o_get_timestamp(req->conn->ctx, &req->pool, &ts);
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_DATE, NULL, ts.str->rfc1123, strlen(ts.str->rfc1123));
}
2 changes: 1 addition & 1 deletion lib/core/token_table.h
Expand Up @@ -46,7 +46,7 @@ h2o_token_t h2o__tokens[] = {{{H2O_STRLIT(":authority")}, 1, 0, 0, 0, 0, 0, 0},
{{H2O_STRLIT("content-range")}, 30, 0, 0, 0, 0, 0, 0},
{{H2O_STRLIT("content-type")}, 31, 0, 0, 0, 0, 0, 0},
{{H2O_STRLIT("cookie")}, 32, 0, 0, 0, 0, 0, 1},
{{H2O_STRLIT("date")}, 33, 0, 1, 0, 0, 0, 0},
{{H2O_STRLIT("date")}, 33, 0, 0, 0, 0, 0, 0},
{{H2O_STRLIT("etag")}, 34, 0, 0, 0, 0, 0, 0},
{{H2O_STRLIT("expect")}, 35, 0, 0, 1, 0, 0, 0},
{{H2O_STRLIT("expires")}, 36, 0, 0, 0, 0, 0, 0},
Expand Down
11 changes: 11 additions & 0 deletions lib/handler/configurator/proxy.c
Expand Up @@ -328,6 +328,15 @@ static int on_config_emit_via_header(h2o_configurator_command_t *cmd, h2o_config
return 0;
}

static int on_config_emit_missing_date_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_missing_date_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");
Expand Down Expand Up @@ -458,6 +467,8 @@ void h2o_proxy_register_configurator(h2o_globalconf_t *conf)
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_command(&c->super, "proxy.emit-missing-date-header",
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_emit_missing_date_header);
h2o_configurator_define_headers_commands(conf, &c->super, "proxy.header", get_headers_commands);
h2o_configurator_define_command(&c->super, "proxy.max-buffer-size",
H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
Expand Down
4 changes: 4 additions & 0 deletions lib/handler/fastcgi.c
Expand Up @@ -548,6 +548,10 @@ static int fill_headers(h2o_req_t *req, struct phr_header *headers, size_t num_h
}
}

/* add date: if it's missing from the response */
if (h2o_find_header(&req->res.headers, H2O_TOKEN_DATE, 0) == -1)
h2o_resp_add_date_header(req);

return 0;
}

Expand Down
2 changes: 2 additions & 0 deletions lib/handler/file.c
Expand Up @@ -752,6 +752,8 @@ static int on_req(h2o_handler_t *_self, h2o_req_t *req)
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_resp_add_date_header(req);

/* build generator (as well as terminating the rpath and its length upon success) */
if (rpath[rpath_len - 1] == '/') {
h2o_iovec_t *index_file;
Expand Down
3 changes: 3 additions & 0 deletions lib/handler/mruby.c
Expand Up @@ -669,6 +669,9 @@ static void send_response(h2o_mruby_generator_t *generator, mrb_int status, mrb_
assert(mrb->exc != NULL);
goto GotException;
}
/* add date: if it's missing from the response */
if (h2o_find_header(&generator->req->res.headers, H2O_TOKEN_DATE, 0) == -1)
h2o_resp_add_date_header(generator->req);

/* return without processing body, if status is fallthru */
if (generator->req->res.status == STATUS_FALLTHRU) {
Expand Down
14 changes: 5 additions & 9 deletions lib/http1.c
Expand Up @@ -587,8 +587,8 @@ static void on_upgrade_complete(h2o_socket_t *socket, const char *err)

static size_t flatten_headers_estimate_size(h2o_req_t *req, size_t server_name_and_connection_len)
{
size_t len = sizeof("HTTP/1.1 \r\ndate: \r\nserver: \r\nconnection: \r\ncontent-length: \r\n\r\n") + 3 +
strlen(req->res.reason) + H2O_TIMESTR_RFC1123_LEN + server_name_and_connection_len +
size_t len = sizeof("HTTP/1.1 \r\nserver: \r\nconnection: \r\ncontent-length: \r\n\r\n") + 3 +
strlen(req->res.reason) + server_name_and_connection_len +
sizeof(H2O_UINT64_LONGEST_STR) - 1 + sizeof("cache-control: private") - 1;
const h2o_header_t *header, *end;

Expand All @@ -601,20 +601,16 @@ static size_t flatten_headers_estimate_size(h2o_req_t *req, size_t server_name_a
static size_t flatten_headers(char *buf, h2o_req_t *req, const char *connection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should tweak flatten_headers_estimate_size too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in ed0786d

{
h2o_context_t *ctx = req->conn->ctx;
h2o_timestamp_t ts;
char *dst = buf;

h2o_get_timestamp(ctx, &req->pool, &ts);

assert(req->res.status <= 999);

/* send essential headers with the first chars uppercased for max. interoperability (#72) */
if (req->res.content_length != SIZE_MAX) {
dst += sprintf(dst, "HTTP/1.1 %d %s\r\nDate: %s\r\nConnection: %s\r\nContent-Length: %zu\r\n", req->res.status,
req->res.reason, ts.str->rfc1123, connection, req->res.content_length);
dst += sprintf(dst, "HTTP/1.1 %d %s\r\nConnection: %s\r\nContent-Length: %zu\r\n", req->res.status, req->res.reason,
connection, req->res.content_length);
} else {
dst += sprintf(dst, "HTTP/1.1 %d %s\r\nDate: %s\r\nConnection: %s\r\n", req->res.status, req->res.reason, ts.str->rfc1123,
connection);
dst += sprintf(dst, "HTTP/1.1 %d %s\r\nConnection: %s\r\n", req->res.status, req->res.reason, connection);
}
if (ctx->globalconf->server_name.len) {
dst += sprintf(dst, "Server: %s\r\n", ctx->globalconf->server_name.base);
Expand Down
8 changes: 2 additions & 6 deletions lib/http2/hpack.c
Expand Up @@ -878,14 +878,12 @@ void h2o_hpack_flatten_request(h2o_buffer_t **buf, h2o_hpack_header_table_t *hea
}

void h2o_hpack_flatten_response(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t stream_id,
size_t max_frame_size, h2o_res_t *res, h2o_timestamp_t *ts, const h2o_iovec_t *server_name,
size_t content_length)
size_t max_frame_size, h2o_res_t *res, const h2o_iovec_t *server_name, size_t content_length)
{
size_t capacity = calc_headers_capacity(res->headers.entries, res->headers.size);
capacity += H2O_HTTP2_FRAME_HEADER_SIZE; /* for the first header */
capacity += STATUS_HEADER_MAX_SIZE; /* for :status: */
#ifndef H2O_UNITTEST
capacity += 2 + H2O_TIMESTR_RFC1123_LEN; /* for Date: */
if (server_name->len) {
capacity += 5 + server_name->len; /* for Server: */
}
Expand All @@ -899,12 +897,10 @@ void h2o_hpack_flatten_response(h2o_buffer_t **buf, h2o_hpack_header_table_t *he
/* encode */
dst = encode_status(dst, res->status);
#ifndef H2O_UNITTEST
/* TODO keep some kind of reference to the indexed headers of Server and Date, and reuse them */
/* TODO keep some kind of reference to the indexed Server header, and reuse it */
if (server_name->len) {
dst = encode_header(header_table, dst, &H2O_TOKEN_SERVER->buf, server_name);
}
h2o_iovec_t date_value = {ts->str->rfc1123, H2O_TIMESTR_RFC1123_LEN};
dst = encode_header(header_table, dst, &H2O_TOKEN_DATE->buf, &date_value);
#endif
size_t i;
for (i = 0; i != res->headers.size; ++i)
Expand Down
5 changes: 1 addition & 4 deletions lib/http2/stream.c
Expand Up @@ -211,9 +211,6 @@ static int is_blocking_asset(h2o_req_t *req)

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)) {
Expand Down Expand Up @@ -287,7 +284,7 @@ static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream)
h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, NULL,
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,
conn->peer_settings.max_frame_size, &stream->req.res, &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);
Expand Down
2 changes: 1 addition & 1 deletion misc/tokens.pl
Expand Up @@ -193,7 +193,7 @@ sub normalize_name {
30 0 0 0 0 0 0 content-range
31 0 0 0 0 0 0 content-type
32 0 0 0 0 0 1 cookie
33 0 1 0 0 0 0 date
33 0 0 0 0 0 0 date
34 0 0 0 0 0 0 etag
35 0 0 1 0 0 0 expect
36 0 0 0 0 0 0 expires
Expand Down
10 changes: 10 additions & 0 deletions srcdoc/configure/proxy_directives.mt
Expand Up @@ -112,6 +112,16 @@ EOT
)->(sub {})
?>

<?
$ctx->{directive}->(
name => "proxy.emit-missing-date-header",
levels => [ qw(global) ],
since => "2.3",
default => q{proxy.emit-missing-date-header: ON},
desc => "A boolean flag (<code>ON</code> or <code>OFF</code>) indicating if H2O should add a <code>date</date> header to the response, if that header is missing from the upstream response.",
)->(sub {})
?>

<?
for my $action (qw(add append merge set setifempty unset)) {
$ctx->{directive}->(
Expand Down
6 changes: 3 additions & 3 deletions t/00unit/lib/http2/hpack.c
Expand Up @@ -99,7 +99,7 @@ static void check_flatten(h2o_hpack_header_table_t *header_table, h2o_res_t *res
const char *err_desc;

h2o_buffer_init(&buf, &h2o_socket_buffer_prototype);
h2o_hpack_flatten_response(&buf, header_table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, res, NULL, NULL, SIZE_MAX);
h2o_hpack_flatten_response(&buf, header_table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, res, NULL, SIZE_MAX);

ok(h2o_http2_decode_frame(&frame, (uint8_t *)buf->bytes, buf->size, &H2O_HTTP2_SETTINGS_DEFAULT, &err_desc) > 0);
ok(h2o_memis(frame.payload, frame.length, expected, expected_len));
Expand Down Expand Up @@ -486,14 +486,14 @@ void test_token_wo_hpack_id(void)
res.reason = "OK";
h2o_add_header(&pool, &res.headers, H2O_TOKEN_TE, NULL, H2O_STRLIT("test"));

h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, NULL, SIZE_MAX);
h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, SIZE_MAX);
ok(h2o_memis(buf->bytes + 9, buf->size - 9, H2O_STRLIT("\x88" /* :status:200 */
"\x40\x02" /* literal header w. incremental indexing, raw, TE */
"te"
"\x83" /* header value, huffman */
"IP\x9f" /* test */)));
h2o_buffer_consume(&buf, buf->size);
h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, NULL, SIZE_MAX);
h2o_hpack_flatten_response(&buf, &table, 1, H2O_HTTP2_SETTINGS_DEFAULT.max_frame_size, &res, NULL, SIZE_MAX);
ok(h2o_memis(buf->bytes + 9, buf->size - 9, H2O_STRLIT("\x88" /* :status:200 */
"\xbe" /* te: test, indexed */)));

Expand Down