/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_strmatch.h"

#include <ap_mmn.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_ssl.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_config.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_util.h"


h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
                              const char *scheme, const char *authority,
                              const char *path, apr_table_t *header)
{
    h2_request *req = apr_pcalloc(pool, sizeof(h2_request));

    req->method         = method;
    req->scheme         = scheme;
    req->authority      = authority;
    req->path           = path;
    req->headers        = header? header : apr_table_make(pool, 10);
    req->request_time   = apr_time_now();

    return req;
}

typedef struct {
    apr_table_t *headers;
    apr_pool_t *pool;
    apr_status_t status;
} h1_ctx;

static int set_h1_header(void *ctx, const char *key, const char *value)
{
    h1_ctx *x = ctx;
    int was_added;
    h2_req_add_header(x->headers, x->pool, key, strlen(key), value, strlen(value), 0, &was_added);
    return 1;
}

apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
                                request_rec *r)
{
    h2_request *req;
    const char *scheme, *authority, *path;
    h1_ctx x;
    
    *preq = NULL;
    scheme = apr_pstrdup(pool, r->parsed_uri.scheme? r->parsed_uri.scheme
              : ap_http_scheme(r));
    authority = apr_pstrdup(pool, r->hostname);
    path = apr_uri_unparse(pool, &r->parsed_uri, APR_URI_UNP_OMITSITEPART);
    
    if (!r->method || !scheme || !r->hostname || !path) {
        return APR_EINVAL;
    }

    /* The authority we carry in h2_request is the 'authority' part of
     * the URL for the request. r->hostname has stripped any port info that
     * might have been present. Do we need to add it?
     */
    if (!ap_strchr_c(authority, ':')) {
        if (r->parsed_uri.port_str) {
            /* Yes, it was there, add it again. */
            authority = apr_pstrcat(pool, authority, ":", r->parsed_uri.port_str, NULL);
        }
        else if (!r->parsed_uri.hostname && r->server && r->server->port) {
            /* If there was no hostname in the parsed URL, the URL was relative.
             * In that case, we restore port from our server->port, if it
             * is known and not the default port for the scheme. */
            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);
            }
        }
    }

    req = apr_pcalloc(pool, sizeof(*req));
    req->method      = apr_pstrdup(pool, r->method);
    req->scheme      = scheme;
    req->authority   = authority;
    req->path        = path;
    req->headers     = apr_table_make(pool, 10);
    req->http_status = H2_HTTP_STATUS_UNSET;

    x.pool = pool;
    x.headers = req->headers;
    x.status = APR_SUCCESS;
    apr_table_do(set_h1_header, &x, r->headers_in, NULL);

    *preq = req;
    return x.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,
                                   size_t max_field_len, int *pwas_added)
{
    apr_status_t status = APR_SUCCESS;

    *pwas_added = 0;
    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: pseudo header after request start");
            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: ignoring unknown pseudo header %s",
                          buffer);
        }
    }
    else {
        /* non-pseudo header, add to table */
        status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen,
                                   max_field_len, pwas_added);
    }

    return status;
}

apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
                                    size_t raw_bytes)
{
    /* rfc7540, ch. 8.1.2.3: without :authority, Host: must be there */
    if (req->authority && !strlen(req->authority)) {
        req->authority = NULL;
    }
    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);
    }
    req->raw_bytes += raw_bytes;

    return APR_SUCCESS;
}

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);
    return dst;
}

#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
static request_rec *my_ap_create_request(conn_rec *c)
{
    apr_pool_t *p;
    request_rec *r;

    apr_pool_create(&p, c->pool);
    apr_pool_tag(p, "request");
    r = apr_pcalloc(p, sizeof(request_rec));
    AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
    r->pool            = p;
    r->connection      = c;
    r->server          = c->base_server;

    r->user            = NULL;
    r->ap_auth_type    = NULL;

    r->allowed_methods = ap_make_method_list(p, 2);

    r->headers_in      = apr_table_make(r->pool, 5);
    r->trailers_in     = apr_table_make(r->pool, 5);
    r->subprocess_env  = apr_table_make(r->pool, 25);
    r->headers_out     = apr_table_make(r->pool, 12);
    r->err_headers_out = apr_table_make(r->pool, 5);
    r->trailers_out    = apr_table_make(r->pool, 5);
    r->notes           = apr_table_make(r->pool, 5);

    r->request_config  = ap_create_request_config(r->pool);
    /* Must be set before we run create request hook */

    r->proto_output_filters = c->output_filters;
    r->output_filters  = r->proto_output_filters;
    r->proto_input_filters = c->input_filters;
    r->input_filters   = r->proto_input_filters;
    ap_run_create_request(r);
    r->per_dir_config  = r->server->lookup_defaults;

    r->sent_bodyct     = 0;                      /* bytect isn't for body */

    r->read_length     = 0;
    r->read_body       = REQUEST_NO_BODY;

    r->status          = HTTP_OK;  /* Until further notice */
    r->header_only     = 0;
    r->the_request     = NULL;

    /* Begin by presuming any module can make its own path_info assumptions,
     * until some module interjects and changes the value.
     */
    r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;

    r->useragent_addr = c->client_addr;
    r->useragent_ip = c->client_ip;
    return r;
}
#endif

#if AP_HAS_RESPONSE_BUCKETS
apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
{
    conn_rec *c = r->connection;
    apr_table_t *headers = apr_table_clone(r->pool, req->headers);
    const char *uri = req->path;

    AP_DEBUG_ASSERT(req->authority);
    if (req->scheme && (ap_cstr_casecmp(req->scheme,
                        ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
                        || !ap_cstr_casecmp("CONNECT", req->method))) {
        /* Client sent a non-matching ':scheme' pseudo header or CONNECT.
         * In this case, we use an absolute URI.
         */
        uri = apr_psprintf(r->pool, "%s://%s%s",
                           req->scheme, req->authority, req->path ? req->path : "");
    }

    return ap_bucket_request_create(req->method, uri, "HTTP/2.0", headers,
                                    r->pool, c->bucket_alloc);
}
#endif

static void assign_headers(request_rec *r, const h2_request *req,
                           int no_body)
{
    const char *cl;

    r->headers_in = apr_table_clone(r->pool, req->headers);
    if (req->authority) {
        /* for internal handling, we have to simulate that :authority
         * came in as Host:, RFC 9113 ch. says that mismatches between
         * :authority and Host: SHOULD be rejected as malformed. However,
         * we are more lenient and just replace any Host: if we have
         * an :authority.
         */
        const char *orig_host = apr_table_get(req->headers, "Host");
        if (orig_host && strcmp(req->authority, orig_host)) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10401)
                          "overwriting 'Host: %s' with :authority: %s'",
                          orig_host, req->authority);
            apr_table_setn(r->subprocess_env, "H2_ORIGINAL_HOST", orig_host);
        }
        apr_table_setn(r->headers_in, "Host", req->authority);
        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
                      "set 'Host: %s' from :authority", req->authority);
    }

    cl = apr_table_get(req->headers, "Content-Length");
    if (no_body) {
        if (!cl && apr_table_get(req->headers, "Content-Type")) {
            /* If we have a content-type, but already seen eos, no more
             * data will come. Signal a zero content length explicitly.
             */
            apr_table_setn(req->headers, "Content-Length", "0");
        }
    }
#if !AP_HAS_RESPONSE_BUCKETS
    else if (!cl) {
        /* there may be a body and we have internal HTTP/1.1 processing.
         * If the Content-Length is unspecified, we MUST simulate
         * chunked Transfer-Encoding.
         *
         * HTTP/2 does not need a Content-Length for framing. Ideally
         * all clients set the EOS flag on the header frame if they
         * do not intent to send a body. However, forwarding proxies
         * might just no know at the time and send an empty DATA
         * frame with EOS much later.
         */
        apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
    }
#endif /* else AP_HAS_RESPONSE_BUCKETS */
}

request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
                                   int no_body)
{
    int access_status = HTTP_OK;

#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
    request_rec *r = ap_create_request(c);
#else
    request_rec *r = my_ap_create_request(c);
#endif

#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
    assign_headers(r, req, no_body);
    ap_run_pre_read_request(r, c);

    /* Time to populate r with the data we have. */
    r->request_time = req->request_time;
    AP_DEBUG_ASSERT(req->authority);
    if (req->scheme && (ap_cstr_casecmp(req->scheme,
                        ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
                        || !ap_cstr_casecmp("CONNECT", req->method))) {
        /* Client sent a non-matching ':scheme' pseudo header. Forward this
         * via an absolute URI in the request line.
         */
        r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
                                      req->method, req->scheme, req->authority,
                                      req->path ? req->path : "");
    }
    else if (req->path) {
        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
                                      req->method, req->path);
    }
    else {
        /* We should only come here on a request that is errored already.
         * create a request line that passes parsing, we'll die anyway.
         */
        AP_DEBUG_ASSERT(req->http_status != H2_HTTP_STATUS_UNSET);
        r->the_request = apr_psprintf(r->pool, "%s / HTTP/2.0", req->method);
    }

    /* Start with r->hostname = NULL, ap_check_request_header() will get it
     * form Host: header, otherwise we get complains about port numbers.
     */
    r->hostname = NULL;

    /* Validate HTTP/1 request and select vhost. */
    if (!ap_parse_request_line(r) || !ap_check_request_header(r)) {
        /* we may have switched to another server still */
        r->per_dir_config = r->server->lookup_defaults;
        if (req->http_status != H2_HTTP_STATUS_UNSET) {
            access_status = req->http_status;
            /* Be safe and close the connection */
            c->keepalive = AP_CONN_CLOSE;
        }
        else {
            access_status = r->status;
        }
        r->status = HTTP_OK;
        goto die;
    }
#else
    {
        const char *s;

        assign_headers(r, req, no_body);
        ap_run_pre_read_request(r, c);

        /* Time to populate r with the data we have. */
        r->request_time = req->request_time;
        r->method = apr_pstrdup(r->pool, 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 ? req->path : "");
        r->protocol = (char*)"HTTP/2.0";
        r->proto_num = HTTP_VERSION(2, 0);
        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
                                      r->method, req->path ? req->path : "");

        /* Start with r->hostname = NULL, ap_check_request_header() will get it
         * form 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;

         s = apr_table_get(r->headers_in, "Expect");
         if (s && s[0]) {
            if (ap_cstr_casecmp(s, "100-continue") == 0) {
                r->expecting_100 = 1;
            }
            else {
                r->status = HTTP_EXPECTATION_FAILED;
                access_status = r->status;
                goto die;
            }
         }
    }
#endif

    /* we may have switched to another server */
    r->per_dir_config = r->server->lookup_defaults;

    if (req->http_status != H2_HTTP_STATUS_UNSET) {
        access_status = req->http_status;
        r->status = HTTP_OK;
        /* Be safe and close the connection */
        c->keepalive = AP_CONN_CLOSE;
        goto die;
    }

    /*
     * 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 = ap_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: access_status=%d, request_create failed",
                      access_status);
        goto die;
    }

    AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
                            (char *)r->uri, (char *)r->server->defn_name,
                            r->status);
    return r;

die:
    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
                  "ap_die(%d) for %s", access_status, r->the_request);
    ap_die(access_status, r);

    /* ap_die() sent the response through the output filters, we must now
     * end the request with an EOR bucket for stream/pipeline accounting.
     */
    {
        apr_bucket_brigade *eor_bb;
#if AP_MODULE_MAGIC_AT_LEAST(20180905, 1)
        eor_bb = ap_acquire_brigade(c);
        APR_BRIGADE_INSERT_TAIL(eor_bb,
                                ap_bucket_eor_create(c->bucket_alloc, r));
        ap_pass_brigade(c->output_filters, eor_bb);
        ap_release_brigade(c, eor_bb);
#else
        eor_bb = apr_brigade_create(c->pool, c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(eor_bb,
                                ap_bucket_eor_create(c->bucket_alloc, r));
        ap_pass_brigade(c->output_filters, eor_bb);
        apr_brigade_destroy(eor_bb);
#endif
    }

    r = NULL;
    AP_READ_REQUEST_FAILURE((uintptr_t)r);
    return NULL;
}
