| /* 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_strings.h> |
| #include <apr_optional.h> |
| #include <apr_optional_hooks.h> |
| |
| #include <httpd.h> |
| #include <http_core.h> |
| #include <http_config.h> |
| #include <http_connection.h> |
| #include <http_protocol.h> |
| #include <http_ssl.h> |
| #include <http_log.h> |
| |
| #include "h2_private.h" |
| #include "h2.h" |
| |
| #include "h2_config.h" |
| #include "h2_conn_ctx.h" |
| #include "h2_c1.h" |
| #include "h2_c2.h" |
| #include "h2_protocol.h" |
| #include "h2_switch.h" |
| |
| /******************************************************************************* |
| * Once per lifetime init, retrieve optional functions |
| */ |
| apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s) |
| { |
| (void)pool; |
| ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_switch init"); |
| |
| return APR_SUCCESS; |
| } |
| |
| static int h2_protocol_propose(conn_rec *c, request_rec *r, |
| server_rec *s, |
| const apr_array_header_t *offers, |
| apr_array_header_t *proposals) |
| { |
| int proposed = 0; |
| int is_tls = ap_ssl_conn_is_ssl(c); |
| const char **protos = is_tls? h2_protocol_ids_tls : h2_protocol_ids_clear; |
| |
| if (!h2_mpm_supported()) { |
| return DECLINED; |
| } |
| |
| if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { |
| /* We do not know how to switch from anything else but http/1.1. |
| */ |
| ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03083) |
| "protocol switch: current proto != http/1.1, declined"); |
| return DECLINED; |
| } |
| |
| if (!h2_protocol_is_acceptable_c1(c, r, 0)) { |
| ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03084) |
| "protocol propose: connection requirements not met"); |
| return DECLINED; |
| } |
| |
| if (r) { |
| /* So far, this indicates an HTTP/1 Upgrade header initiated |
| * protocol switch. For that, the HTTP2-Settings header needs |
| * to be present and valid for the connection. |
| */ |
| const char *p; |
| |
| if (!h2_c1_can_upgrade(r)) { |
| return DECLINED; |
| } |
| |
| p = apr_table_get(r->headers_in, "HTTP2-Settings"); |
| if (!p) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03085) |
| "upgrade without HTTP2-Settings declined"); |
| return DECLINED; |
| } |
| |
| p = apr_table_get(r->headers_in, "Connection"); |
| if (!ap_find_token(r->pool, p, "http2-settings")) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03086) |
| "upgrade without HTTP2-Settings declined"); |
| return DECLINED; |
| } |
| |
| /* We also allow switching only for requests that have no body. |
| */ |
| p = apr_table_get(r->headers_in, "Content-Length"); |
| if ((p && strcmp(p, "0")) |
| || (!p && apr_table_get(r->headers_in, "Transfer-Encoding"))) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03087) |
| "upgrade with body declined"); |
| return DECLINED; |
| } |
| } |
| |
| while (*protos) { |
| /* Add all protocols we know (tls or clear) and that |
| * are part of the offerings (if there have been any). |
| */ |
| if (!offers || ap_array_str_contains(offers, *protos)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "proposing protocol '%s'", *protos); |
| APR_ARRAY_PUSH(proposals, const char*) = *protos; |
| proposed = 1; |
| } |
| ++protos; |
| } |
| return proposed? DECLINED : OK; |
| } |
| |
| #if AP_HAS_RESPONSE_BUCKETS |
| static void remove_output_filters_below(ap_filter_t *f, ap_filter_type ftype) |
| { |
| ap_filter_t *fnext; |
| |
| while (f && f->frec->ftype < ftype) { |
| fnext = f->next; |
| ap_remove_output_filter(f); |
| f = fnext; |
| } |
| } |
| |
| static void remove_input_filters_below(ap_filter_t *f, ap_filter_type ftype) |
| { |
| ap_filter_t *fnext; |
| |
| while (f && f->frec->ftype < ftype) { |
| fnext = f->next; |
| ap_remove_input_filter(f); |
| f = fnext; |
| } |
| } |
| #endif |
| |
| static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, |
| const char *protocol) |
| { |
| int found = 0; |
| const char **protos = ap_ssl_conn_is_ssl(c)? h2_protocol_ids_tls : h2_protocol_ids_clear; |
| const char **p = protos; |
| |
| (void)s; |
| if (!h2_mpm_supported()) { |
| return DECLINED; |
| } |
| |
| while (*p) { |
| if (!strcmp(*p, protocol)) { |
| found = 1; |
| break; |
| } |
| p++; |
| } |
| |
| if (found) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "switching protocol to '%s'", protocol); |
| h2_conn_ctx_create_for_c1(c, s, protocol); |
| |
| if (r != NULL) { |
| apr_status_t status; |
| #if AP_HAS_RESPONSE_BUCKETS |
| /* Switching in the middle of a request means that |
| * we have to send out the response to this one in h2 |
| * format. So we need to take over the connection |
| * and remove all old filters with type up to the |
| * CONNEDCTION/NETWORK ones. |
| */ |
| remove_input_filters_below(r->input_filters, AP_FTYPE_CONNECTION); |
| remove_output_filters_below(r->output_filters, AP_FTYPE_CONNECTION); |
| #else |
| /* Switching in the middle of a request means that |
| * we have to send out the response to this one in h2 |
| * format. So we need to take over the connection |
| * right away. |
| */ |
| ap_remove_input_filter_byhandle(r->input_filters, "http_in"); |
| ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER"); |
| #endif |
| /* Ok, start an h2_conn on this one. */ |
| status = h2_c1_setup(c, r, s); |
| |
| if (status != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088) |
| "session setup"); |
| h2_conn_ctx_detach(c); |
| return !OK; |
| } |
| |
| h2_c1_run(c); |
| } |
| return OK; |
| } |
| |
| return DECLINED; |
| } |
| |
| static const char *h2_protocol_get(const conn_rec *c) |
| { |
| h2_conn_ctx_t *ctx; |
| |
| if (c->master) { |
| c = c->master; |
| } |
| ctx = h2_conn_ctx_get(c); |
| return ctx? ctx->protocol : NULL; |
| } |
| |
| void h2_switch_register_hooks(void) |
| { |
| ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE); |
| } |