]> granicus.if.org Git - apache/blob - modules/http2/h2_to_h1.c
eliminating some stricter compiler warnings in mod_h2
[apache] / modules / http2 / h2_to_h1.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 #include <stdio.h>
18
19 #include <apr_strings.h>
20
21 #include <httpd.h>
22 #include <http_core.h>
23 #include <http_log.h>
24 #include <http_connection.h>
25
26 #include "h2_private.h"
27 #include "h2_mplx.h"
28 #include "h2_response.h"
29 #include "h2_task.h"
30 #include "h2_to_h1.h"
31 #include "h2_util.h"
32
33
34 h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool, 
35                           apr_bucket_alloc_t *bucket_alloc, 
36                           const char *method, 
37                           const char *scheme, 
38                           const char *authority, 
39                           const char *path,
40                           struct h2_mplx *m)
41 {
42     h2_to_h1 *to_h1;
43     if (!method) {
44         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
45                       APLOGNO(02943) 
46                       "h2_to_h1: header start but :method missing");
47         return NULL;
48     }
49     if (!path) {
50         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
51                       APLOGNO(02944) 
52                       "h2_to_h1: header start but :path missing");
53         return NULL;
54     }
55     
56     to_h1 = apr_pcalloc(pool, sizeof(h2_to_h1));
57     if (to_h1) {
58         to_h1->stream_id = stream_id;
59         to_h1->pool = pool;
60         to_h1->method = method;
61         to_h1->scheme = scheme;
62         to_h1->authority = authority;
63         to_h1->path = path;
64         to_h1->m = m;
65         to_h1->headers = apr_table_make(to_h1->pool, 10);
66         to_h1->bb = apr_brigade_create(pool, bucket_alloc);
67         to_h1->chunked = 0; /* until we see a content-type and no length */
68         to_h1->content_len = -1;
69     }
70     return to_h1;
71 }
72
73 void h2_to_h1_destroy(h2_to_h1 *to_h1)
74 {
75     to_h1->bb = NULL;
76 }
77
78 apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
79                                  const char *name, size_t nlen,
80                                  const char *value, size_t vlen)
81 {
82     char *hname, *hvalue;
83     if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
84         if (!apr_strnatcasecmp("chunked", value)) {
85             /* This should never arrive here in a HTTP/2 request */
86             ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
87                           APLOGNO(02945) 
88                           "h2_to_h1: 'transfer-encoding: chunked' received");
89             return APR_BADARG;
90         }
91     }
92     else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
93         char *end;
94         to_h1->content_len = apr_strtoi64(value, &end, 10);
95         if (value == end) {
96             ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
97                           APLOGNO(02959) 
98                           "h2_request(%d): content-length value not parsed: %s",
99                           to_h1->stream_id, value);
100             return APR_EINVAL;
101         }
102         to_h1->remain_len = to_h1->content_len;
103         to_h1->chunked = 0;
104     }
105     else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
106         /* If we see a content-type and have no length (yet),
107          * we need to chunk. */
108         to_h1->chunked = (to_h1->content_len == -1);
109     }
110     else if ((to_h1->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
111              || H2_HD_MATCH_LIT("expect", name, nlen)
112              || H2_HD_MATCH_LIT("upgrade", name, nlen)
113              || H2_HD_MATCH_LIT("connection", name, nlen)
114              || H2_HD_MATCH_LIT("proxy-connection", name, nlen)
115              || H2_HD_MATCH_LIT("keep-alive", name, nlen)
116              || H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
117         // ignore these.
118         return APR_SUCCESS;
119     }
120     else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
121         const char *existing = apr_table_get(to_h1->headers, "cookie");
122         if (existing) {
123             char *nval;
124             
125             /* Cookie headers come separately in HTTP/2, but need
126              * to be merged by "; " (instead of default ", ")
127              */
128             hvalue = apr_pstrndup(to_h1->pool, value, vlen);
129             nval = apr_psprintf(to_h1->pool, "%s; %s", existing, hvalue);
130             apr_table_setn(to_h1->headers, "Cookie", nval);
131             return APR_SUCCESS;
132         }
133     }
134     else if (H2_HD_MATCH_LIT("host", name, nlen)) {
135         to_h1->seen_host = 1;
136     }
137     
138     hname = apr_pstrndup(to_h1->pool, name, nlen);
139     hvalue = apr_pstrndup(to_h1->pool, value, vlen);
140     h2_util_camel_case_header(hname, nlen);
141     apr_table_mergen(to_h1->headers, hname, hvalue);
142     
143     return APR_SUCCESS;
144 }
145
146 static int set_header(void *ctx, const char *key, const char *value)
147 {
148     h2_to_h1 *to_h1 = (h2_to_h1*)ctx;
149     h2_to_h1_add_header(to_h1, key, strlen(key), value, strlen(value));
150     return 1;
151 }
152
153 apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers)
154 {
155     apr_table_do(set_header, to_h1, headers, NULL);
156     return APR_SUCCESS;
157 }
158
159 apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
160 {
161     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
162                   "h2_to_h1(%ld-%d): end headers", 
163                   to_h1->m->id, to_h1->stream_id);
164     
165     if (to_h1->eoh) {
166         return APR_EINVAL;
167     }
168     
169     if (!to_h1->seen_host) {
170         /* Need to add a "Host" header if not already there to
171          * make virtual hosts work correctly. */
172         if (!to_h1->authority) {
173             return APR_BADARG;
174         }
175         apr_table_set(to_h1->headers, "Host", to_h1->authority);
176     }
177
178     if (eos && to_h1->chunked) {
179         /* We had chunking figured out, but the EOS is already there.
180          * unmark chunking and set a definitive content-length.
181          */
182         to_h1->chunked = 0;
183         apr_table_setn(to_h1->headers, "Content-Length", "0");
184     }
185     else if (to_h1->chunked) {
186         /* We have not seen a content-length. We therefore must
187          * pass any request content in chunked form.
188          */
189         apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
190     }
191     
192     h2_task_set_request(task, to_h1->method, 
193                         to_h1->scheme, 
194                         to_h1->authority, 
195                         to_h1->path, 
196                         to_h1->headers, eos);
197     to_h1->eoh = 1;
198     
199     if (eos) {
200         apr_status_t status = h2_to_h1_close(to_h1);
201         if (status != APR_SUCCESS) {
202             ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
203                           APLOGNO(02960) 
204                           "h2_to_h1(%ld-%d): end headers, eos=%d", 
205                           to_h1->m->id, to_h1->stream_id, eos);
206         }
207         return status;
208     }
209     return APR_SUCCESS;
210 }
211
212 static apr_status_t flush(apr_bucket_brigade *bb, void *ctx) 
213 {
214     (void)bb;
215     return h2_to_h1_flush((h2_to_h1*)ctx);
216 }
217
218 static apr_status_t h2_to_h1_add_data_raw(h2_to_h1 *to_h1,
219                                           const char *data, size_t len)
220 {
221     apr_status_t status = APR_SUCCESS;
222
223     if (to_h1->eos || !to_h1->eoh) {
224         return APR_EINVAL;
225     }
226     
227     status = apr_brigade_write(to_h1->bb, flush, to_h1, data, len);
228     if (status == APR_SUCCESS) {
229         status = h2_to_h1_flush(to_h1);
230     }
231     return status;
232 }
233
234
235 apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
236                                const char *data, size_t len)
237 {
238     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
239                   "h2_to_h1(%ld-%d): add %ld data bytes", 
240                   to_h1->m->id, to_h1->stream_id, (long)len);
241     
242     if (to_h1->chunked) {
243         /* if input may have a body and we have not seen any
244          * content-length header, we need to chunk the input data.
245          */
246         apr_status_t status = apr_brigade_printf(to_h1->bb, NULL, NULL,
247                                                  "%lx\r\n", (unsigned long)len);
248         if (status == APR_SUCCESS) {
249             status = h2_to_h1_add_data_raw(to_h1, data, len);
250             if (status == APR_SUCCESS) {
251                 status = apr_brigade_puts(to_h1->bb, NULL, NULL, "\r\n");
252             }
253         }
254         return status;
255     }
256     else {
257         to_h1->remain_len -= len;
258         if (to_h1->remain_len < 0) {
259             ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c,
260                           APLOGNO(02961) 
261                           "h2_to_h1(%ld-%d): got %ld more content bytes than announced "
262                           "in content-length header: %ld", 
263                           to_h1->m->id, to_h1->stream_id, 
264                           (long)to_h1->content_len, -(long)to_h1->remain_len);
265         }
266         return h2_to_h1_add_data_raw(to_h1, data, len);
267     }
268 }
269
270 apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
271 {
272     apr_status_t status = APR_SUCCESS;
273     if (!APR_BRIGADE_EMPTY(to_h1->bb)) {
274         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
275                       "h2_to_h1(%ld-%d): flush request bytes", 
276                       to_h1->m->id, to_h1->stream_id);
277         
278         status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
279         if (status != APR_SUCCESS) {
280             ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c,
281                           APLOGNO(02946) "h2_request(%d): pushing request data",
282                           to_h1->stream_id);
283         }
284     }
285     return status;
286 }
287
288 apr_status_t h2_to_h1_close(h2_to_h1 *to_h1)
289 {
290     apr_status_t status = APR_SUCCESS;
291     if (!to_h1->eos) {
292         if (to_h1->chunked) {
293             status = h2_to_h1_add_data_raw(to_h1, "0\r\n\r\n", 5);
294         }
295         to_h1->eos = 1;
296         status = h2_to_h1_flush(to_h1);
297         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
298                       "h2_to_h1(%d): close", to_h1->stream_id);
299         
300         status = h2_mplx_in_close(to_h1->m, to_h1->stream_id);
301     }
302     return status;
303 }
304
305