예제 #1
0
파일: http1.c 프로젝트: ifzz/h2o
static ssize_t fixup_request(struct st_h2o_http1_conn_t *conn, struct phr_header *headers, size_t num_headers, int minor_version,
                             h2o_iovec_t *expect)
{
    ssize_t entity_header_index;
    h2o_iovec_t connection = {NULL, 0}, host = {NULL, 0}, upgrade = {NULL, 0};

    expect->base = NULL;
    expect->len = 0;

    conn->req.input.scheme = conn->sock->ssl != NULL ? &H2O_URL_SCHEME_HTTPS : &H2O_URL_SCHEME_HTTP;
    conn->req.version = 0x100 | (minor_version != 0);

    /* init headers */
    entity_header_index =
        init_headers(&conn->req.pool, &conn->req.headers, headers, num_headers, &connection, &host, &upgrade, expect);

    /* copy the values to pool, since the buffer pointed by the headers may get realloced */
    if (entity_header_index != -1) {
        size_t i;
        conn->req.input.method = h2o_strdup(&conn->req.pool, conn->req.input.method.base, conn->req.input.method.len);
        conn->req.input.path = h2o_strdup(&conn->req.pool, conn->req.input.path.base, conn->req.input.path.len);
        for (i = 0; i != conn->req.headers.size; ++i) {
            h2o_header_t *header = conn->req.headers.entries + i;
            if (!h2o_iovec_is_token(header->name)) {
                *header->name = h2o_strdup(&conn->req.pool, header->name->base, header->name->len);
            }
            header->value = h2o_strdup(&conn->req.pool, header->value.base, header->value.len);
        }
        if (host.base != NULL)
            host = h2o_strdup(&conn->req.pool, host.base, host.len);
        if (upgrade.base != NULL)
            upgrade = h2o_strdup(&conn->req.pool, upgrade.base, upgrade.len);
    }

    /* move host header to req->authority */
    if (host.base != NULL)
        conn->req.input.authority = host;

    /* setup persistent flag (and upgrade info) */
    if (connection.base != NULL) {
        /* TODO contains_token function can be faster */
        if (h2o_contains_token(connection.base, connection.len, H2O_STRLIT("keep-alive"), ',')) {
            conn->req.http1_is_persistent = 1;
        }
        if (upgrade.base != NULL && h2o_contains_token(connection.base, connection.len, H2O_STRLIT("upgrade"), ',')) {
            conn->req.upgrade = upgrade;
        }
    } else if (conn->req.version >= 0x101) {
        /* defaults to keep-alive if >= HTTP/1.1 */
        conn->req.http1_is_persistent = 1;
    }
    /* disable keep-alive if shutdown is requested */
    if (conn->req.http1_is_persistent && conn->super.ctx->shutdown_requested)
        conn->req.http1_is_persistent = 0;

    return entity_header_index;
}
예제 #2
0
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;
}
예제 #3
0
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, char, 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 {
예제 #4
0
파일: proxy.c 프로젝트: bowlofstew/h2o
static h2o_iovec_t build_request(h2o_req_t *req, h2o_proxy_location_t *upstream, int keepalive)
{
    h2o_iovec_t buf;
    size_t bufsz;
    const h2o_header_t *h, * h_end;
    char *p;

    /* calc buffer length */
    bufsz = sizeof("  HTTP/1.1\r\nhost: :65535\r\nconnection: keep-alive\r\ncontent-length: 18446744073709551615\r\n\r\n")
        + req->method.len
        + req->path.len - req->pathconf->path.len + upstream->path.len
        + upstream->host.len;
    for (h = req->headers.entries, h_end = h + req->headers.size; h != h_end; ++h)
        bufsz += h->name->len + h->value.len + 4;

    /* allocate */
    buf.base = h2o_mem_alloc_pool(&req->pool, bufsz);

    /* build response */
    p = buf.base;
    p += sprintf(p, "%.*s %.*s%.*s HTTP/1.1\r\nconnection: %s\r\n",
        (int)req->method.len, req->method.base,
        (int)upstream->path.len, upstream->path.base,
        (int)(req->path.len - req->pathconf->path.len), req->path.base + req->pathconf->path.len,
        keepalive ? "keep-alive" : "close");
    if (upstream->port == 80)
        p += sprintf(p, "host: %.*s\r\n", (int)upstream->host.len, upstream->host.base);
    else
        p += sprintf(p, "host: %.*s:%u\r\n", (int)upstream->host.len, upstream->host.base, (unsigned)upstream->port);
    if (req->entity.base != NULL) {
        p += sprintf(p, "content-length: %zu\r\n", req->entity.len);
    }
    for (h = req->headers.entries, h_end = h + req->headers.size; h != h_end; ++h) {
        if (h2o_iovec_is_token(h->name) && ((h2o_token_t*)h->name)->is_connection_specific)
            continue;
        p += sprintf(p, "%.*s: %.*s\r\n",
            (int)h->name->len, h->name->base,
            (int)h->value.len, h->value.base);
    }
    *p++ = '\r';
    *p++ = '\n';

    /* set the length */
    buf.len = p - buf.base;
    assert(buf.len < bufsz);

    return buf;
}
예제 #5
0
파일: headers.c 프로젝트: zhangjinde/h2o
static int add_cmd(h2o_configurator_command_t *cmd, yoml_t *node, int cmd_id, h2o_iovec_t *name, h2o_iovec_t value)
{
    struct headers_configurator_t *self = (void *)cmd->configurator;

    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_vector_reserve(NULL, (h2o_vector_t *)self->cmds, sizeof(self->cmds->entries[0]), self->cmds->size + 1);
    self->cmds->entries[self->cmds->size++] = (h2o_headers_command_t){cmd_id, name, value};
    return 0;
}
예제 #6
0
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;
}
예제 #7
0
파일: proxy.c 프로젝트: firewood/h2o
static h2o_iovec_t build_request(h2o_req_t *req, int keepalive, int is_websocket_handshake)
{
    h2o_iovec_t buf;
    size_t offset = 0, remote_addr_len = SIZE_MAX;
    char remote_addr[NI_MAXHOST];
    struct sockaddr_storage ss;
    socklen_t sslen;
    h2o_iovec_t cookie_buf = {}, xff_buf = {}, via_buf = {};

    /* for x-f-f */
    if ((sslen = req->conn->callbacks->get_peername(req->conn, (void *)&ss)) != 0)
        remote_addr_len = h2o_socket_getnumerichost((void *)&ss, sslen, remote_addr);

    /* build response */
    buf.len = req->method.len + req->path.len + req->authority.len + 512;
    buf.base = h2o_mem_alloc_pool(&req->pool, buf.len);

#define RESERVE(sz)                                                                                                                \
    do {                                                                                                                           \
        size_t required = offset + sz + 4 /* for "\r\n\r\n" */;                                                                    \
        if (required > buf.len) {                                                                                                  \
            do {                                                                                                                   \
                buf.len *= 2;                                                                                                      \
            } while (required > buf.len);                                                                                          \
            char *newp = h2o_mem_alloc_pool(&req->pool, buf.len);                                                                  \
            memcpy(newp, buf.base, offset);                                                                                        \
            buf.base = newp;                                                                                                       \
        }                                                                                                                          \
    } while (0)
#define APPEND(s, l)                                                                                                               \
    do {                                                                                                                           \
        memcpy(buf.base + offset, (s), (l));                                                                                       \
        offset += (l);                                                                                                             \
    } while (0)
#define APPEND_STRLIT(lit) APPEND((lit), sizeof(lit) - 1)
#define FLATTEN_PREFIXED_VALUE(prefix, value, add_size)                                                                            \
    do {                                                                                                                           \
        RESERVE(sizeof(prefix) - 1 + value.len + 2 + add_size);                                                                    \
        APPEND_STRLIT(prefix);                                                                                                     \
        if (value.len != 0) {                                                                                                      \
            APPEND(value.base, value.len);                                                                                         \
            if (add_size != 0) {                                                                                                   \
                buf.base[offset++] = ',';                                                                                          \
                buf.base[offset++] = ' ';                                                                                          \
            }                                                                                                                      \
        }                                                                                                                          \
    } while (0)

    APPEND(req->method.base, req->method.len);
    buf.base[offset++] = ' ';
    APPEND(req->path.base, req->path.len);
    APPEND_STRLIT(" HTTP/1.1\r\nconnection: ");
    if (is_websocket_handshake) {
        APPEND_STRLIT("upgrade\r\nupgrade: websocket\r\nhost: ");
    } else if (keepalive) {
        APPEND_STRLIT("keep-alive\r\nhost: ");
    } else {
        APPEND_STRLIT("close\r\nhost: ");
    }
    APPEND(req->authority.base, req->authority.len);
    buf.base[offset++] = '\r';
    buf.base[offset++] = '\n';
    assert(offset <= buf.len);
    if (req->entity.base != NULL) {
        RESERVE(sizeof("content-length: 18446744073709551615") - 1);
        offset += sprintf(buf.base + offset, "content-length: %zu\r\n", req->entity.len);
    }
    {
        const h2o_header_t *h, *h_end;
        for (h = req->headers.entries, h_end = h + req->headers.size; h != h_end; ++h) {
            if (h2o_iovec_is_token(h->name)) {
                const h2o_token_t *token = (void *)h->name;
                if (token->proxy_should_drop) {
                    continue;
                } else if (token == H2O_TOKEN_COOKIE) {
                    /* merge the cookie headers; see HTTP/2 8.1.2.5 and HTTP/1 (RFC6265 5.4) */
                    /* FIXME current algorithm is O(n^2) against the number of cookie headers */
                    cookie_buf = build_request_merge_headers(&req->pool, cookie_buf, h->value, ';');
                    continue;
                } else if (token == H2O_TOKEN_VIA) {
                    via_buf = build_request_merge_headers(&req->pool, via_buf, h->value, ',');
                    continue;
                } else if (token == H2O_TOKEN_X_FORWARDED_FOR) {
                    xff_buf = build_request_merge_headers(&req->pool, xff_buf, h->value, ',');
                    continue;
                }
            }
            if (h2o_lcstris(h->name->base, h->name->len, H2O_STRLIT("x-forwarded-proto")))
                continue;
            RESERVE(h->name->len + h->value.len + 2);
            APPEND(h->name->base, h->name->len);
            buf.base[offset++] = ':';
            buf.base[offset++] = ' ';
            APPEND(h->value.base, h->value.len);
            buf.base[offset++] = '\r';
            buf.base[offset++] = '\n';
        }
    }
    if (cookie_buf.len != 0) {
        FLATTEN_PREFIXED_VALUE("cookie: ", cookie_buf, 0);
        buf.base[offset++] = '\r';
        buf.base[offset++] = '\n';
    }
    FLATTEN_PREFIXED_VALUE("x-forwarded-proto: ", req->input.scheme->name, 0);
    buf.base[offset++] = '\r';
    buf.base[offset++] = '\n';
    if (remote_addr_len != SIZE_MAX) {
        FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, remote_addr_len);
        APPEND(remote_addr, remote_addr_len);
    } else {
        FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, 0);
    }
    buf.base[offset++] = '\r';
    buf.base[offset++] = '\n';
    FLATTEN_PREFIXED_VALUE("via: ", via_buf, sizeof("1.1 ") - 1 + req->input.authority.len);
    if (req->version < 0x200) {
        buf.base[offset++] = '1';
        buf.base[offset++] = '.';
        buf.base[offset++] = '0' + (0x100 <= req->version && req->version <= 0x109 ? req->version - 0x100 : 0);
    } else {
        buf.base[offset++] = '2';
    }
    buf.base[offset++] = ' ';
    APPEND(req->input.authority.base, req->input.authority.len);
    APPEND_STRLIT("\r\n\r\n");

#undef RESERVE
#undef APPEND
#undef APPEND_STRLIT
#undef FLATTEN_PREFIXED_VALUE

    /* set the length */
    assert(offset <= buf.len);
    buf.len = offset;

    return buf;
}
예제 #8
0
파일: proxy.c 프로젝트: cubicdaiya/h2o
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 rp_generator_t *self = client->data;
    h2o_req_t *req = self->src_req;
    size_t i;

    if (errstr != NULL && errstr != h2o_http1client_error_is_eos) {
        self->client = NULL;
        h2o_req_log_error(req, "lib/core/proxy.c", "%s", errstr);
        h2o_send_error_502(req, "Gateway Error", errstr, 0);
        return NULL;
    }

    /* copy the response (note: all the headers must be copied; http1client discards the input once we return from this callback) */
    req->res.status = status;
    req->res.reason = h2o_strdup(&req->pool, msg.base, msg.len).base;
    for (i = 0; i != num_headers; ++i) {
        if (h2o_iovec_is_token(headers[i].name)) {
            const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, headers[i].name);
            h2o_iovec_t value;
            if (token->proxy_should_drop) {
                goto Skip;
            }
            if (token == H2O_TOKEN_CONTENT_LENGTH) {
                if (req->res.content_length != SIZE_MAX ||
                    (req->res.content_length = h2o_strtosize(headers[i].value.base, headers[i].value.len)) == SIZE_MAX) {
                    self->client = NULL;
                    h2o_req_log_error(req, "lib/core/proxy.c", "%s", "invalid response from upstream (malformed content-length)");
                    h2o_send_error_502(req, "Gateway Error", "invalid response from upstream", 0);
                    return NULL;
                }
                goto Skip;
            } else if (token == H2O_TOKEN_LOCATION) {
                if (req->res_is_delegated && (300 <= status && status <= 399) && status != 304) {
                    self->client = NULL;
                    h2o_iovec_t method = h2o_get_redirect_method(req->method, status);
                    h2o_send_redirect_internal(req, method, headers[i].value.base, headers[i].value.len, 1);
                    return NULL;
                }
                if (req->overrides != NULL && req->overrides->location_rewrite.match != NULL) {
                    value = rewrite_location(&req->pool, headers[i].value.base, headers[i].value.len,
                                             req->overrides->location_rewrite.match, req->input.scheme, req->input.authority,
                                             req->overrides->location_rewrite.path_prefix);
                    if (value.base != NULL)
                        goto AddHeader;
                }
                goto AddHeaderDuped;
            } else if (token == H2O_TOKEN_LINK) {
                h2o_iovec_t new_value;
                new_value = h2o_push_path_in_link_header(req, headers[i].value.base, headers[i].value.len);
                if (!new_value.len)
                    goto Skip;
                headers[i].value.base = new_value.base;
                headers[i].value.len = new_value.len;
            } else if (token == H2O_TOKEN_SERVER) {
                if (!req->conn->ctx->globalconf->proxy.preserve_server_header)
                    goto Skip;
            } 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;
            }
        /* default behaviour, transfer the header downstream */
        AddHeaderDuped:
            value = h2o_strdup(&req->pool, headers[i].value.base, headers[i].value.len);
        AddHeader:
            h2o_add_header(&req->pool, &req->res.headers, token, headers[i].orig_name, value.base, value.len);
        Skip:;
        } else {
            h2o_iovec_t name = h2o_strdup(&req->pool, headers[i].name->base, headers[i].name->len);
            h2o_iovec_t value = h2o_strdup(&req->pool, headers[i].value.base, headers[i].value.len);
            h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, headers[i].orig_name, value.base,
                                  value.len);
        }
    }

    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);
        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_UPGRADE, NULL, H2O_STRLIT("websocket"));
        on_websocket_upgrade(self, client_ctx->websocket_timeout, rlen);
        self->client = NULL;
        return NULL;
    }
    /* declare the start of the response */
    h2o_start_response(req, &self->super);

    if (errstr == h2o_http1client_error_is_eos) {
        self->client = NULL;
        h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL);
        return NULL;
    }

    return on_body;
}
예제 #9
0
파일: proxy.c 프로젝트: cubicdaiya/h2o
static h2o_iovec_t build_request(h2o_req_t *req, int keepalive, int is_websocket_handshake, int use_proxy_protocol)
{
    h2o_iovec_t buf;
    size_t offset = 0, remote_addr_len = SIZE_MAX;
    char remote_addr[NI_MAXHOST];
    struct sockaddr_storage ss;
    socklen_t sslen;
    h2o_iovec_t cookie_buf = {NULL}, xff_buf = {NULL}, via_buf = {NULL};
    int preserve_x_forwarded_proto = req->conn->ctx->globalconf->proxy.preserve_x_forwarded_proto;
    int emit_x_forwarded_headers = req->conn->ctx->globalconf->proxy.emit_x_forwarded_headers;
    int emit_via_header = req->conn->ctx->globalconf->proxy.emit_via_header;

    /* for x-f-f */
    if ((sslen = req->conn->callbacks->get_peername(req->conn, (void *)&ss)) != 0)
        remote_addr_len = h2o_socket_getnumerichost((void *)&ss, sslen, remote_addr);

    /* build response */
    buf.len = req->method.len + req->path.len + req->authority.len + 512;
    if (use_proxy_protocol)
        buf.len += H2O_PROXY_HEADER_MAX_LENGTH;
    buf.base = h2o_mem_alloc_pool(&req->pool, buf.len);

#define RESERVE(sz)                                                                                                                \
    do {                                                                                                                           \
        size_t required = offset + sz + 4 /* for "\r\n\r\n" */;                                                                    \
        if (required > buf.len) {                                                                                                  \
            do {                                                                                                                   \
                buf.len *= 2;                                                                                                      \
            } while (required > buf.len);                                                                                          \
            char *newp = h2o_mem_alloc_pool(&req->pool, buf.len);                                                                  \
            memcpy(newp, buf.base, offset);                                                                                        \
            buf.base = newp;                                                                                                       \
        }                                                                                                                          \
    } while (0)
#define APPEND(s, l)                                                                                                               \
    do {                                                                                                                           \
        memcpy(buf.base + offset, (s), (l));                                                                                       \
        offset += (l);                                                                                                             \
    } while (0)
#define APPEND_STRLIT(lit) APPEND((lit), sizeof(lit) - 1)
#define FLATTEN_PREFIXED_VALUE(prefix, value, add_size)                                                                            \
    do {                                                                                                                           \
        RESERVE(sizeof(prefix) - 1 + value.len + 2 + add_size);                                                                    \
        APPEND_STRLIT(prefix);                                                                                                     \
        if (value.len != 0) {                                                                                                      \
            APPEND(value.base, value.len);                                                                                         \
            if (add_size != 0) {                                                                                                   \
                buf.base[offset++] = ',';                                                                                          \
                buf.base[offset++] = ' ';                                                                                          \
            }                                                                                                                      \
        }                                                                                                                          \
    } while (0)

    if (use_proxy_protocol)
        offset += h2o_stringify_proxy_header(req->conn, buf.base + offset);

    APPEND(req->method.base, req->method.len);
    buf.base[offset++] = ' ';
    APPEND(req->path.base, req->path.len);
    APPEND_STRLIT(" HTTP/1.1\r\nconnection: ");
    if (is_websocket_handshake) {
        APPEND_STRLIT("upgrade\r\nupgrade: websocket\r\nhost: ");
    } else if (keepalive) {
        APPEND_STRLIT("keep-alive\r\nhost: ");
    } else {
        APPEND_STRLIT("close\r\nhost: ");
    }
    APPEND(req->authority.base, req->authority.len);
    buf.base[offset++] = '\r';
    buf.base[offset++] = '\n';
    assert(offset <= buf.len);
    if (req->entity.base != NULL || req_requires_content_length(req)) {
        RESERVE(sizeof("content-length: " H2O_UINT64_LONGEST_STR) - 1);
        offset += sprintf(buf.base + offset, "content-length: %zu\r\n", req->entity.len);
    }

    /* rewrite headers if necessary */
    h2o_headers_t req_headers = req->headers;
    if (req->overrides != NULL && req->overrides->headers_cmds != NULL) {
        req_headers.entries = NULL;
        req_headers.size = 0;
        req_headers.capacity = 0;
        h2o_headers_command_t *cmd;
        h2o_vector_reserve(&req->pool, &req_headers, req->headers.capacity);
        memcpy(req_headers.entries, req->headers.entries, sizeof(req->headers.entries[0]) * req->headers.size);
        req_headers.size = req->headers.size;
        for (cmd = req->overrides->headers_cmds; cmd->cmd != H2O_HEADERS_CMD_NULL; ++cmd)
            h2o_rewrite_headers(&req->pool, &req_headers, cmd);
    }

    {
        const h2o_header_t *h, *h_end;
        for (h = req_headers.entries, h_end = h + req_headers.size; h != h_end; ++h) {
            if (h2o_iovec_is_token(h->name)) {
                const h2o_token_t *token = (void *)h->name;
                if (token->proxy_should_drop) {
                    continue;
                } else if (token == H2O_TOKEN_COOKIE) {
                    /* merge the cookie headers; see HTTP/2 8.1.2.5 and HTTP/1 (RFC6265 5.4) */
                    /* FIXME current algorithm is O(n^2) against the number of cookie headers */
                    cookie_buf = build_request_merge_headers(&req->pool, cookie_buf, h->value, ';');
                    continue;
                } else if (token == H2O_TOKEN_VIA) {
                    if (!emit_via_header) {
                        goto AddHeader;
                    }
                    via_buf = build_request_merge_headers(&req->pool, via_buf, h->value, ',');
                    continue;
                } else if (token == H2O_TOKEN_X_FORWARDED_FOR) {
                    if (!emit_x_forwarded_headers) {
                        goto AddHeader;
                    }
                    xff_buf = build_request_merge_headers(&req->pool, xff_buf, h->value, ',');
                    continue;
                }
            }
            if (!preserve_x_forwarded_proto && h2o_lcstris(h->name->base, h->name->len, H2O_STRLIT("x-forwarded-proto")))
                continue;
        AddHeader:
            RESERVE(h->name->len + h->value.len + 2);
            APPEND(h->orig_name ? h->orig_name : h->name->base, h->name->len);
            buf.base[offset++] = ':';
            buf.base[offset++] = ' ';
            APPEND(h->value.base, h->value.len);
            buf.base[offset++] = '\r';
            buf.base[offset++] = '\n';
        }
    }
    if (cookie_buf.len != 0) {
        FLATTEN_PREFIXED_VALUE("cookie: ", cookie_buf, 0);
        buf.base[offset++] = '\r';
        buf.base[offset++] = '\n';
    }
    if (emit_x_forwarded_headers) {
        if (!preserve_x_forwarded_proto) {
            FLATTEN_PREFIXED_VALUE("x-forwarded-proto: ", req->input.scheme->name, 0);
            buf.base[offset++] = '\r';
            buf.base[offset++] = '\n';
        }
        if (remote_addr_len != SIZE_MAX) {
            FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, remote_addr_len);
            APPEND(remote_addr, remote_addr_len);
        } else {
            FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, 0);
        }
        buf.base[offset++] = '\r';
        buf.base[offset++] = '\n';
    }
    if (emit_via_header) {
        FLATTEN_PREFIXED_VALUE("via: ", via_buf, sizeof("1.1 ") - 1 + req->input.authority.len);
        if (req->version < 0x200) {
            buf.base[offset++] = '1';
            buf.base[offset++] = '.';
            buf.base[offset++] = '0' + (0x100 <= req->version && req->version <= 0x109 ? req->version - 0x100 : 0);
        } else {
            buf.base[offset++] = '2';
        }
        buf.base[offset++] = ' ';
        APPEND(req->input.authority.base, req->input.authority.len);
        buf.base[offset++] = '\r';
        buf.base[offset++] = '\n';
    }
    APPEND_STRLIT("\r\n");

#undef RESERVE
#undef APPEND
#undef APPEND_STRLIT
#undef FLATTEN_PREFIXED_VALUE

    /* set the length */
    assert(offset <= buf.len);
    buf.len = offset;

    return buf;
}