2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2015 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 #include "nghttp2_http.h"
31 #include "nghttp2_hd.h"
32 #include "nghttp2_helper.h"
34 static uint8_t downcase(uint8_t c) {
35 return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c;
38 static int memieq(const void *a, const void *b, size_t n) {
40 const uint8_t *aa = a, *bb = b;
42 for (i = 0; i < n; ++i) {
43 if (downcase(aa[i]) != downcase(bb[i])) {
50 #define lstrieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N)))
52 static int64_t parse_uint(const uint8_t *s, size_t len) {
58 for (i = 0; i < len; ++i) {
59 if ('0' <= s[i] && s[i] <= '9') {
60 if (n > INT64_MAX / 10) {
64 if (n > INT64_MAX - (s[i] - '0')) {
75 static int lws(const uint8_t *s, size_t n) {
77 for (i = 0; i < n; ++i) {
78 if (s[i] != ' ' && s[i] != '\t') {
85 static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv,
87 if (stream->http_flags & flag) {
90 if (lws(nv->value->base, nv->value->len)) {
93 stream->http_flags = (uint16_t)(stream->http_flags | flag);
97 static int expect_response_body(nghttp2_stream *stream) {
98 return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 &&
99 stream->status_code / 100 != 1 && stream->status_code != 304 &&
100 stream->status_code != 204;
103 /* For "http" or "https" URIs, OPTIONS request may have "*" in :path
104 header field to represent system-wide OPTIONS request. Otherwise,
105 :path header field value must start with "/". This function must
106 be called after ":method" header field was received. This function
107 returns nonzero if path is valid.*/
108 static int check_path(nghttp2_stream *stream) {
109 return (stream->http_flags & NGHTTP2_HTTP_FLAG_SCHEME_HTTP) == 0 ||
110 ((stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_REGULAR) ||
111 ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_OPTIONS) &&
112 (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK)));
115 static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
116 int trailer, int connect_protocol) {
117 if (nv->name->base[0] == ':') {
119 (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
120 return NGHTTP2_ERR_HTTP_HEADER;
125 case NGHTTP2_TOKEN__AUTHORITY:
126 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
127 return NGHTTP2_ERR_HTTP_HEADER;
130 case NGHTTP2_TOKEN__METHOD:
131 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
132 return NGHTTP2_ERR_HTTP_HEADER;
134 switch (nv->value->len) {
136 if (lstreq("HEAD", nv->value->base, nv->value->len)) {
137 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
141 switch (nv->value->base[6]) {
143 if (lstreq("CONNECT", nv->value->base, nv->value->len)) {
144 if (stream->stream_id % 2 == 0) {
145 /* we won't allow CONNECT for push */
146 return NGHTTP2_ERR_HTTP_HEADER;
148 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
152 if (lstreq("OPTIONS", nv->value->base, nv->value->len)) {
153 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
160 case NGHTTP2_TOKEN__PATH:
161 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
162 return NGHTTP2_ERR_HTTP_HEADER;
164 if (nv->value->base[0] == '/') {
165 stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
166 } else if (nv->value->len == 1 && nv->value->base[0] == '*') {
167 stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
170 case NGHTTP2_TOKEN__SCHEME:
171 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
172 return NGHTTP2_ERR_HTTP_HEADER;
174 if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) ||
175 (nv->value->len == 5 && memieq("https", nv->value->base, 5))) {
176 stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
179 case NGHTTP2_TOKEN__PROTOCOL:
180 if (!connect_protocol) {
181 return NGHTTP2_ERR_HTTP_HEADER;
184 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) {
185 return NGHTTP2_ERR_HTTP_HEADER;
188 case NGHTTP2_TOKEN_HOST:
189 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
190 return NGHTTP2_ERR_HTTP_HEADER;
193 case NGHTTP2_TOKEN_CONTENT_LENGTH: {
194 if (stream->content_length != -1) {
195 return NGHTTP2_ERR_HTTP_HEADER;
197 stream->content_length = parse_uint(nv->value->base, nv->value->len);
198 if (stream->content_length == -1) {
199 return NGHTTP2_ERR_HTTP_HEADER;
203 /* disallowed header fields */
204 case NGHTTP2_TOKEN_CONNECTION:
205 case NGHTTP2_TOKEN_KEEP_ALIVE:
206 case NGHTTP2_TOKEN_PROXY_CONNECTION:
207 case NGHTTP2_TOKEN_TRANSFER_ENCODING:
208 case NGHTTP2_TOKEN_UPGRADE:
209 return NGHTTP2_ERR_HTTP_HEADER;
210 case NGHTTP2_TOKEN_TE:
211 if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
212 return NGHTTP2_ERR_HTTP_HEADER;
216 if (nv->name->base[0] == ':') {
217 return NGHTTP2_ERR_HTTP_HEADER;
221 if (nv->name->base[0] != ':') {
222 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
228 static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
230 if (nv->name->base[0] == ':') {
232 (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
233 return NGHTTP2_ERR_HTTP_HEADER;
238 case NGHTTP2_TOKEN__STATUS: {
239 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
240 return NGHTTP2_ERR_HTTP_HEADER;
242 if (nv->value->len != 3) {
243 return NGHTTP2_ERR_HTTP_HEADER;
245 stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len);
246 if (stream->status_code == -1 || stream->status_code == 101) {
247 return NGHTTP2_ERR_HTTP_HEADER;
251 case NGHTTP2_TOKEN_CONTENT_LENGTH: {
252 if (stream->status_code == 204) {
253 /* content-length header field in 204 response is prohibited by
254 RFC 7230. But some widely used servers send content-length:
255 0. Until they get fixed, we ignore it. */
256 if (stream->content_length != -1) {
257 /* Found multiple content-length field */
258 return NGHTTP2_ERR_HTTP_HEADER;
260 if (!lstrieq("0", nv->value->base, nv->value->len)) {
261 return NGHTTP2_ERR_HTTP_HEADER;
263 stream->content_length = 0;
264 return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
266 if (stream->status_code / 100 == 1) {
267 return NGHTTP2_ERR_HTTP_HEADER;
269 /* https://tools.ietf.org/html/rfc7230#section-3.3.3 */
270 if (stream->status_code / 100 == 2 &&
271 (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
272 return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
274 if (stream->content_length != -1) {
275 return NGHTTP2_ERR_HTTP_HEADER;
277 stream->content_length = parse_uint(nv->value->base, nv->value->len);
278 if (stream->content_length == -1) {
279 return NGHTTP2_ERR_HTTP_HEADER;
283 /* disallowed header fields */
284 case NGHTTP2_TOKEN_CONNECTION:
285 case NGHTTP2_TOKEN_KEEP_ALIVE:
286 case NGHTTP2_TOKEN_PROXY_CONNECTION:
287 case NGHTTP2_TOKEN_TRANSFER_ENCODING:
288 case NGHTTP2_TOKEN_UPGRADE:
289 return NGHTTP2_ERR_HTTP_HEADER;
290 case NGHTTP2_TOKEN_TE:
291 if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
292 return NGHTTP2_ERR_HTTP_HEADER;
296 if (nv->name->base[0] == ':') {
297 return NGHTTP2_ERR_HTTP_HEADER;
301 if (nv->name->base[0] != ':') {
302 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
308 static int check_scheme(const uint8_t *value, size_t len) {
314 if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) {
321 for (; value != last; ++value) {
322 if (!(('A' <= *value && *value <= 'Z') ||
323 ('a' <= *value && *value <= 'z') ||
324 ('0' <= *value && *value <= '9') || *value == '+' || *value == '-' ||
332 int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
333 nghttp2_frame *frame, nghttp2_hd_nv *nv,
337 /* We are strict for pseudo header field. One bad character should
338 lead to fail. OTOH, we should be a bit forgiving for regular
339 headers, since existing public internet has so much illegal
340 headers floating around and if we kill the stream because of
341 this, we may disrupt many web sites and/or libraries. So we
342 become conservative here, and just ignore those illegal regular
344 if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) {
346 if (nv->name->len > 0 && nv->name->base[0] == ':') {
347 return NGHTTP2_ERR_HTTP_HEADER;
349 /* header field name must be lower-cased without exception */
350 for (i = 0; i < nv->name->len; ++i) {
351 uint8_t c = nv->name->base[i];
352 if ('A' <= c && c <= 'Z') {
353 return NGHTTP2_ERR_HTTP_HEADER;
356 /* When ignoring regular headers, we set this flag so that we
357 still enforce header field ordering rule for pseudo header
359 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
360 return NGHTTP2_ERR_IGN_HTTP_HEADER;
363 if (nv->token == NGHTTP2_TOKEN__AUTHORITY ||
364 nv->token == NGHTTP2_TOKEN_HOST) {
365 rv = nghttp2_check_authority(nv->value->base, nv->value->len);
366 } else if (nv->token == NGHTTP2_TOKEN__SCHEME) {
367 rv = check_scheme(nv->value->base, nv->value->len);
369 rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
373 assert(nv->name->len > 0);
374 if (nv->name->base[0] == ':') {
375 return NGHTTP2_ERR_HTTP_HEADER;
377 /* When ignoring regular headers, we set this flag so that we
378 still enforce header field ordering rule for pseudo header
380 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
381 return NGHTTP2_ERR_IGN_HTTP_HEADER;
384 if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
385 return http_request_on_header(stream, nv, trailer,
387 session->pending_enable_connect_protocol);
390 return http_response_on_header(stream, nv, trailer);
393 int nghttp2_http_on_request_headers(nghttp2_stream *stream,
394 nghttp2_frame *frame) {
395 if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
396 (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
397 if ((stream->http_flags &
398 (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) ||
399 (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
402 stream->content_length = -1;
404 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) !=
405 NGHTTP2_HTTP_FLAG_REQ_HEADERS ||
406 (stream->http_flags &
407 (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
410 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
411 ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 ||
412 (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) {
415 if (!check_path(stream)) {
420 if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
421 /* we are going to reuse data fields for upcoming response. Clear
422 them now, except for method flags. */
423 stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL;
424 stream->content_length = -1;
430 int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
431 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) {
435 if (stream->status_code / 100 == 1) {
436 /* non-final response */
438 (uint16_t)((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) |
439 NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE);
440 stream->content_length = -1;
441 stream->status_code = -1;
446 (uint16_t)(stream->http_flags & ~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE);
448 if (!expect_response_body(stream)) {
449 stream->content_length = 0;
450 } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT |
451 NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) {
452 stream->content_length = -1;
458 int nghttp2_http_on_trailer_headers(nghttp2_stream *stream,
459 nghttp2_frame *frame) {
462 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
469 int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) {
470 if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
474 if (stream->content_length != -1 &&
475 stream->content_length != stream->recv_content_length) {
482 int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) {
483 stream->recv_content_length += (int64_t)n;
485 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) ||
486 (stream->content_length != -1 &&
487 stream->recv_content_length > stream->content_length)) {
494 void nghttp2_http_record_request_method(nghttp2_stream *stream,
495 nghttp2_frame *frame) {
496 const nghttp2_nv *nva;
500 switch (frame->hd.type) {
501 case NGHTTP2_HEADERS:
502 nva = frame->headers.nva;
503 nvlen = frame->headers.nvlen;
505 case NGHTTP2_PUSH_PROMISE:
506 nva = frame->push_promise.nva;
507 nvlen = frame->push_promise.nvlen;
513 /* TODO we should do this strictly. */
514 for (i = 0; i < nvlen; ++i) {
515 const nghttp2_nv *nv = &nva[i];
516 if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
517 memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
520 if (lstreq("CONNECT", nv->value, nv->valuelen)) {
521 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
524 if (lstreq("HEAD", nv->value, nv->valuelen)) {
525 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;