]> granicus.if.org Git - apache/blob - modules/http2/h2_switch.c
2b74debd3440832e4379f92c5cf8871c33fe5dc7
[apache] / modules / http2 / h2_switch.c
1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <assert.h>
17
18 #include <apr_strings.h>
19 #include <apr_optional.h>
20 #include <apr_optional_hooks.h>
21
22 #include <httpd.h>
23 #include <http_core.h>
24 #include <http_config.h>
25 #include <http_connection.h>
26 #include <http_protocol.h>
27 #include <http_log.h>
28
29 #include "h2_private.h"
30
31 #include "h2_config.h"
32 #include "h2_ctx.h"
33 #include "h2_conn.h"
34 #include "h2_h2.h"
35 #include "h2_switch.h"
36
37 /*******************************************************************************
38  * SSL var lookup
39  */
40 APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
41                         (apr_pool_t *, server_rec *,
42                          conn_rec *, request_rec *,
43                          char *));
44 static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
45                                    conn_rec *, request_rec *,
46                                    char *);
47
48 /*******************************************************************************
49  * Once per lifetime init, retrieve optional functions
50  */
51 apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s)
52 {
53     (void)pool;
54     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init");
55     opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
56
57     return APR_SUCCESS;
58 }
59
60 static int h2_protocol_propose(conn_rec *c, request_rec *r,
61                                server_rec *s,
62                                const apr_array_header_t *offers,
63                                apr_array_header_t *proposals)
64 {
65     int proposed = 0;
66     const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
67     
68     if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
69         /* We do not know how to switch from anything else but http/1.1.
70          */
71         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
72                       "protocol switch: current proto != http/1.1, declined");
73         return DECLINED;
74     }
75     
76     if (r) {
77         const char *p;
78         /* So far, this indicates an HTTP/1 Upgrade header initiated
79          * protocol switch. For that, the HTTP2-Settings header needs
80          * to be present and valid for the connection.
81          */
82         p = apr_table_get(r->headers_in, "HTTP2-Settings");
83         if (!p) {
84             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
85                           "upgrade without HTTP2-Settings declined");
86             return DECLINED;
87         }
88         
89         p = apr_table_get(r->headers_in, "Connection");
90         if (!ap_find_token(r->pool, p, "http2-settings")) {
91             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
92                           "upgrade without HTTP2-Settings declined");
93             return DECLINED;
94         }
95         
96         /* We also allow switching only for requests that have no body.
97          */
98         p = apr_table_get(r->headers_in, "Content-Length");
99         if (p && strcmp(p, "0")) {
100             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
101                           "upgrade with content-length: %s, declined", p);
102             return DECLINED;
103         }
104     }
105     
106     while (*protos) {
107         /* Add all protocols we know (tls or clear) and that
108          * are part of the offerings (if there have been any). 
109          */
110         if (!offers || ap_array_contains(offers, *protos)) {
111             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
112                           "proposing protocol '%s'", *protos);
113             APR_ARRAY_PUSH(proposals, const char*) = *protos;
114             proposed = 1;
115         }
116         ++protos;
117     }
118     return proposed? DECLINED : OK;
119 }
120
121 static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
122                               const char *protocol)
123 {
124     int found = 0;
125     const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
126     const char **p = protos;
127     
128     while (*p) {
129         if (!strcmp(*p, protocol)) {
130             found = 1;
131             break;
132         }
133         p++;
134     }
135     
136     if (found) {
137         h2_ctx *ctx = h2_ctx_get(c);
138         
139         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
140                       "switching protocol to '%s'", protocol);
141         h2_ctx_protocol_set(ctx, protocol);
142         
143         if (r != NULL) {
144             apr_status_t status;
145             /* Switching in the middle of a request means that
146              * we have to send out the response to this one in h2
147              * format. So we need to take over the connection
148              * right away.
149              */
150             ap_remove_input_filter_byhandle(r->input_filters, "http_in");
151             ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
152             
153             /* Ok, start an h2_conn on this one. */
154             status = h2_conn_rprocess(r);
155             if (status != DONE) {
156                 /* Nothing really to do about this. */
157                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
158                               "session proessed, unexpected status");
159             }
160         }
161         return DONE;
162     }
163     
164     return DECLINED;
165 }
166
167 static const char *h2_protocol_get(const conn_rec *c)
168 {
169     return h2_ctx_protocol_get(c);
170 }
171
172 void h2_switch_register_hooks(void)
173 {
174     ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
175     ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
176     ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
177 }
178