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;
364 case NGHTTP2_TOKEN__METHOD:
365 rv = nghttp2_check_method(nv->value->base, nv->value->len);
367 case NGHTTP2_TOKEN__PATH:
368 rv = nghttp2_check_path(nv->value->base, nv->value->len);
370 case NGHTTP2_TOKEN__AUTHORITY:
371 case NGHTTP2_TOKEN_HOST:
372 rv = nghttp2_check_authority(nv->value->base, nv->value->len);
374 case NGHTTP2_TOKEN__SCHEME:
375 rv = check_scheme(nv->value->base, nv->value->len);
378 rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
382 assert(nv->name->len > 0);
383 if (nv->name->base[0] == ':') {
384 return NGHTTP2_ERR_HTTP_HEADER;
386 /* When ignoring regular headers, we set this flag so that we
387 still enforce header field ordering rule for pseudo header
389 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
390 return NGHTTP2_ERR_IGN_HTTP_HEADER;
393 if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
394 return http_request_on_header(stream, nv, trailer,
396 session->pending_enable_connect_protocol);
399 return http_response_on_header(stream, nv, trailer);
402 int nghttp2_http_on_request_headers(nghttp2_stream *stream,
403 nghttp2_frame *frame) {
404 if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
405 (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
406 if ((stream->http_flags &
407 (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) ||
408 (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
411 stream->content_length = -1;
413 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) !=
414 NGHTTP2_HTTP_FLAG_REQ_HEADERS ||
415 (stream->http_flags &
416 (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
419 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
420 ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 ||
421 (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) {
424 if (!check_path(stream)) {
429 if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
430 /* we are going to reuse data fields for upcoming response. Clear
431 them now, except for method flags. */
432 stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL;
433 stream->content_length = -1;
439 int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
440 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) {
444 if (stream->status_code / 100 == 1) {
445 /* non-final response */
447 (uint16_t)((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) |
448 NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE);
449 stream->content_length = -1;
450 stream->status_code = -1;
455 (uint16_t)(stream->http_flags & ~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE);
457 if (!expect_response_body(stream)) {
458 stream->content_length = 0;
459 } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT |
460 NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) {
461 stream->content_length = -1;
467 int nghttp2_http_on_trailer_headers(nghttp2_stream *stream,
468 nghttp2_frame *frame) {
471 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
478 int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) {
479 if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
483 if (stream->content_length != -1 &&
484 stream->content_length != stream->recv_content_length) {
491 int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) {
492 stream->recv_content_length += (int64_t)n;
494 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) ||
495 (stream->content_length != -1 &&
496 stream->recv_content_length > stream->content_length)) {
503 void nghttp2_http_record_request_method(nghttp2_stream *stream,
504 nghttp2_frame *frame) {
505 const nghttp2_nv *nva;
509 switch (frame->hd.type) {
510 case NGHTTP2_HEADERS:
511 nva = frame->headers.nva;
512 nvlen = frame->headers.nvlen;
514 case NGHTTP2_PUSH_PROMISE:
515 nva = frame->push_promise.nva;
516 nvlen = frame->push_promise.nvlen;
522 /* TODO we should do this strictly. */
523 for (i = 0; i < nvlen; ++i) {
524 const nghttp2_nv *nv = &nva[i];
525 if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
526 memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
529 if (lstreq("CONNECT", nv->value, nv->valuelen)) {
530 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
533 if (lstreq("HEAD", nv->value, nv->valuelen)) {
534 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;