1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <apr_strings.h>
22 #include <http_core.h>
24 #include <http_connection.h>
26 #include "h2_private.h"
28 #include "h2_response.h"
34 h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
35 apr_bucket_alloc_t *bucket_alloc,
38 const char *authority,
44 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
46 "h2_to_h1: header start but :method missing");
50 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
52 "h2_to_h1: header start but :path missing");
56 to_h1 = apr_pcalloc(pool, sizeof(h2_to_h1));
58 to_h1->stream_id = stream_id;
60 to_h1->method = method;
61 to_h1->scheme = scheme;
62 to_h1->authority = authority;
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;
73 void h2_to_h1_destroy(h2_to_h1 *to_h1)
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)
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,
88 "h2_to_h1: 'transfer-encoding: chunked' received");
92 else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
94 to_h1->content_len = apr_strtoi64(value, &end, 10);
96 ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
98 "h2_request(%d): content-length value not parsed: %s",
99 to_h1->stream_id, value);
102 to_h1->remain_len = to_h1->content_len;
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);
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)) {
120 else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
121 const char *existing = apr_table_get(to_h1->headers, "cookie");
125 /* Cookie headers come separately in HTTP/2, but need
126 * to be merged by "; " (instead of default ", ")
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);
134 else if (H2_HD_MATCH_LIT("host", name, nlen)) {
135 to_h1->seen_host = 1;
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);
146 static int set_header(void *ctx, const char *key, const char *value)
148 h2_to_h1 *to_h1 = (h2_to_h1*)ctx;
149 h2_to_h1_add_header(to_h1, key, strlen(key), value, strlen(value));
153 apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers)
155 apr_table_do(set_header, to_h1, headers, NULL);
159 apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
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);
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) {
175 apr_table_set(to_h1->headers, "Host", to_h1->authority);
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.
183 apr_table_setn(to_h1->headers, "Content-Length", "0");
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.
189 apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
192 h2_task_set_request(task, to_h1->method,
196 to_h1->headers, 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,
204 "h2_to_h1(%ld-%d): end headers, eos=%d",
205 to_h1->m->id, to_h1->stream_id, eos);
212 static apr_status_t flush(apr_bucket_brigade *bb, void *ctx)
215 return h2_to_h1_flush((h2_to_h1*)ctx);
218 static apr_status_t h2_to_h1_add_data_raw(h2_to_h1 *to_h1,
219 const char *data, size_t len)
221 apr_status_t status = APR_SUCCESS;
223 if (to_h1->eos || !to_h1->eoh) {
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);
235 apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
236 const char *data, size_t len)
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);
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.
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");
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,
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);
266 return h2_to_h1_add_data_raw(to_h1, data, len);
270 apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
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);
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",
288 apr_status_t h2_to_h1_close(h2_to_h1 *to_h1)
290 apr_status_t status = APR_SUCCESS;
292 if (to_h1->chunked) {
293 status = h2_to_h1_add_data_raw(to_h1, "0\r\n\r\n", 5);
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);
300 status = h2_mplx_in_close(to_h1->m, to_h1->stream_id);