blob: 2f232080622d287ceb9dd177f64ae4bfa9239336 [file] [log] [blame]
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <apr_strings.h>
#include <httpd.h>
#include <http_core.h>
#include <http_connection.h>
#include <http_protocol.h>
#include <http_request.h>
#include <http_log.h>
#include <http_vhost.h>
#include <util_filter.h>
#include <ap_mpm.h>
#include <mod_core.h>
#include <scoreboard.h>
#include "h2_private.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_util.h"
static apr_status_t inspect_clen(h2_request *req, const char *s)
{
char *end;
req->content_length = apr_strtoi64(s, &end, 10);
return (s == end)? APR_EINVAL : APR_SUCCESS;
}
apr_status_t h2_request_rwrite(h2_request *req, apr_pool_t *pool,
request_rec *r)
{
apr_status_t status;
const char *scheme, *authority;
scheme = apr_pstrdup(pool, r->parsed_uri.scheme? r->parsed_uri.scheme
: ap_http_scheme(r));
authority = apr_pstrdup(pool, r->hostname);
if (!ap_strchr_c(authority, ':') && r->server && r->server->port) {
apr_port_t defport = apr_uri_port_of_scheme(scheme);
if (defport != r->server->port) {
/* port info missing and port is not default for scheme: append */
authority = apr_psprintf(pool, "%s:%d", authority,
(int)r->server->port);
}
}
status = h2_req_make(req, pool, apr_pstrdup(pool, r->method), scheme,
authority, apr_uri_unparse(pool, &r->parsed_uri,
APR_URI_UNP_OMITSITEPART),
r->headers_in);
return status;
}
apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
const char *name, size_t nlen,
const char *value, size_t vlen)
{
apr_status_t status = APR_SUCCESS;
if (nlen <= 0) {
return status;
}
if (name[0] == ':') {
/* pseudo header, see ch. 8.1.2.3, always should come first */
if (!apr_is_empty_table(req->headers)) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
APLOGNO(02917)
"h2_request(%d): pseudo header after request start",
req->id);
return APR_EGENERAL;
}
if (H2_HEADER_METHOD_LEN == nlen
&& !strncmp(H2_HEADER_METHOD, name, nlen)) {
req->method = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_SCHEME_LEN == nlen
&& !strncmp(H2_HEADER_SCHEME, name, nlen)) {
req->scheme = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_PATH_LEN == nlen
&& !strncmp(H2_HEADER_PATH, name, nlen)) {
req->path = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_AUTH_LEN == nlen
&& !strncmp(H2_HEADER_AUTH, name, nlen)) {
req->authority = apr_pstrndup(pool, value, vlen);
}
else {
char buffer[32];
memset(buffer, 0, 32);
strncpy(buffer, name, (nlen > 31)? 31 : nlen);
ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
APLOGNO(02954)
"h2_request(%d): ignoring unknown pseudo header %s",
req->id, buffer);
}
}
else {
/* non-pseudo header, append to work bucket of stream */
status = h2_headers_add_h1(req->headers, pool, name, nlen, value, vlen);
}
return status;
}
apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
int eos, int push)
{
const char *s;
if (req->eoh) {
/* already done */
return APR_SUCCESS;
}
/* rfc7540, ch. 8.1.2.3:
* - if we have :authority, it overrides any Host header
* - :authority MUST be ommited when converting h1->h2, so we
* might get a stream without, but then Host needs to be there */
if (!req->authority) {
const char *host = apr_table_get(req->headers, "Host");
if (!host) {
return APR_BADARG;
}
req->authority = host;
}
else {
apr_table_setn(req->headers, "Host", req->authority);
}
s = apr_table_get(req->headers, "Content-Length");
if (s) {
if (inspect_clen(req, s) != APR_SUCCESS) {
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
APLOGNO(02959)
"h2_request(%d): content-length value not parsed: %s",
req->id, s);
return APR_EINVAL;
}
}
else {
/* no content-length given */
req->content_length = -1;
if (!eos) {
/* We have not seen a content-length and have no eos,
* simulate a chunked encoding for our HTTP/1.1 infrastructure,
* in case we have "H2SerializeHeaders on" here
*/
req->chunked = 1;
apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
}
else if (apr_table_get(req->headers, "Content-Type")) {
/* If we have a content-type, but already see eos, no more
* data will come. Signal a zero content length explicitly.
*/
apr_table_setn(req->headers, "Content-Length", "0");
}
}
req->eoh = 1;
h2_push_policy_determine(req, pool, push);
/* In the presence of trailers, force behaviour of chunked encoding */
s = apr_table_get(req->headers, "Trailer");
if (s && s[0]) {
req->trailers = apr_table_make(pool, 5);
if (!req->chunked) {
req->chunked = 1;
apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
}
}
return APR_SUCCESS;
}
static apr_status_t add_h1_trailer(h2_request *req, apr_pool_t *pool,
const char *name, size_t nlen,
const char *value, size_t vlen)
{
char *hname, *hvalue;
if (h2_req_ignore_trailer(name, nlen)) {
return APR_SUCCESS;
}
hname = apr_pstrndup(pool, name, nlen);
hvalue = apr_pstrndup(pool, value, vlen);
h2_util_camel_case_header(hname, nlen);
apr_table_mergen(req->trailers, hname, hvalue);
return APR_SUCCESS;
}
apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
const char *name, size_t nlen,
const char *value, size_t vlen)
{
if (!req->trailers) {
ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, APLOGNO(03059)
"h2_request(%d): unanounced trailers",
req->id);
return APR_EINVAL;
}
if (nlen == 0 || name[0] == ':') {
ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, APLOGNO(03060)
"h2_request(%d): pseudo header in trailer",
req->id);
return APR_EINVAL;
}
return add_h1_trailer(req, pool, name, nlen, value, vlen);
}
h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
{
h2_request *dst = apr_pmemdup(p, src, sizeof(*dst));
dst->method = apr_pstrdup(p, src->method);
dst->scheme = apr_pstrdup(p, src->scheme);
dst->authority = apr_pstrdup(p, src->authority);
dst->path = apr_pstrdup(p, src->path);
dst->headers = apr_table_clone(p, src->headers);
if (src->trailers) {
dst->trailers = apr_table_clone(p, src->trailers);
}
return dst;
}
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
{
int access_status = HTTP_OK;
request_rec *r = ap_create_request(c);
r->headers_in = apr_table_clone(r->pool, req->headers);
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
r->request_time = req->request_time;
r->method = req->method;
/* Provide quick information about the request method as soon as known */
r->method_number = ap_method_number_of(r->method);
if (r->method_number == M_GET && r->method[0] == 'H') {
r->header_only = 1;
}
ap_parse_uri(r, req->path);
r->protocol = "HTTP/2.0";
r->proto_num = HTTP_VERSION(2, 0);
r->the_request = apr_psprintf(r->pool, "%s %s %s",
r->method, req->path, r->protocol);
/* update what we think the virtual host is based on the headers we've
* now read. may update status.
* Leave r->hostname empty, vhost will parse if form our Host: header,
* otherwise we get complains about port numbers.
*/
r->hostname = NULL;
ap_update_vhost_from_headers(r);
/* we may have switched to another server */
r->per_dir_config = r->server->lookup_defaults;
/*
* Add the HTTP_IN filter here to ensure that ap_discard_request_body
* called by ap_die and by ap_send_error_response works correctly on
* status codes that do not cause the connection to be dropped and
* in situations where the connection should be kept alive.
*/
ap_add_input_filter_handle(ap_http_input_filter_handle,
NULL, r, r->connection);
if (access_status != HTTP_OK
|| (access_status = ap_run_post_read_request(r))) {
/* Request check post hooks failed. An example of this would be a
* request for a vhost where h2 is disabled --> 421.
*/
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
"h2_request(%d): access_status=%d, request_create failed",
req->id, access_status);
ap_die(access_status, r);
ap_update_child_status(c->sbh, SERVER_BUSY_LOG, r);
ap_run_log_transaction(r);
r = NULL;
goto traceout;
}
AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
(char *)r->uri, (char *)r->server->defn_name,
r->status);
return r;
traceout:
AP_READ_REQUEST_FAILURE((uintptr_t)r);
return r;
}