/* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2015 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_http.h" #include #include #include static int memeq(const void *a, const void *b, size_t n) { return memcmp(a, b, n) == 0; } #define streq(A, B, N) ((sizeof((A)) - 1) == (N) && memeq((A), (B), (N))) static char downcase(char c) { return 'A' <= c && c <= 'Z' ? (c - 'A' + 'a') : c; } static int memieq(const void *a, const void *b, size_t n) { size_t i; const uint8_t *aa = a, *bb = b; for (i = 0; i < n; ++i) { if (downcase(aa[i]) != downcase(bb[i])) { return 0; } } return 1; } #define strieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N))) typedef enum { NGHTTP2_TOKEN__AUTHORITY, NGHTTP2_TOKEN__METHOD, NGHTTP2_TOKEN__PATH, NGHTTP2_TOKEN__SCHEME, NGHTTP2_TOKEN__STATUS, NGHTTP2_TOKEN_CONNECTION, NGHTTP2_TOKEN_CONTENT_LENGTH, NGHTTP2_TOKEN_HOST, NGHTTP2_TOKEN_KEEP_ALIVE, NGHTTP2_TOKEN_PROXY_CONNECTION, NGHTTP2_TOKEN_TE, NGHTTP2_TOKEN_TRANSFER_ENCODING, NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN_MAXIDX, } nghttp2_token; /* * This function was generated by genlibtokenlookup.py. Inspired by * h2o header lookup. https://github.com/h2o/h2o */ static int lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) { case 2: switch (name[namelen - 1]) { case 'e': if (streq("t", name, 1)) { return NGHTTP2_TOKEN_TE; } break; } break; case 4: switch (name[namelen - 1]) { case 't': if (streq("hos", name, 3)) { return NGHTTP2_TOKEN_HOST; } break; } break; case 5: switch (name[namelen - 1]) { case 'h': if (streq(":pat", name, 4)) { return NGHTTP2_TOKEN__PATH; } break; } break; case 7: switch (name[namelen - 1]) { case 'd': if (streq(":metho", name, 6)) { return NGHTTP2_TOKEN__METHOD; } break; case 'e': if (streq(":schem", name, 6)) { return NGHTTP2_TOKEN__SCHEME; } if (streq("upgrad", name, 6)) { return NGHTTP2_TOKEN_UPGRADE; } break; case 's': if (streq(":statu", name, 6)) { return NGHTTP2_TOKEN__STATUS; } break; } break; case 10: switch (name[namelen - 1]) { case 'e': if (streq("keep-aliv", name, 9)) { return NGHTTP2_TOKEN_KEEP_ALIVE; } break; case 'n': if (streq("connectio", name, 9)) { return NGHTTP2_TOKEN_CONNECTION; } break; case 'y': if (streq(":authorit", name, 9)) { return NGHTTP2_TOKEN__AUTHORITY; } break; } break; case 14: switch (name[namelen - 1]) { case 'h': if (streq("content-lengt", name, 13)) { return NGHTTP2_TOKEN_CONTENT_LENGTH; } break; } break; case 16: switch (name[namelen - 1]) { case 'n': if (streq("proxy-connectio", name, 15)) { return NGHTTP2_TOKEN_PROXY_CONNECTION; } break; } break; case 17: switch (name[namelen - 1]) { case 'g': if (streq("transfer-encodin", name, 16)) { return NGHTTP2_TOKEN_TRANSFER_ENCODING; } break; } break; } return -1; } static int64_t parse_uint(const uint8_t *s, size_t len) { int64_t n = 0; size_t i; if (len == 0) { return -1; } for (i = 0; i < len; ++i) { if ('0' <= s[i] && s[i] <= '9') { if (n > INT64_MAX / 10) { return -1; } n *= 10; if (n > INT64_MAX - (s[i] - '0')) { return -1; } n += s[i] - '0'; continue; } return -1; } return n; } static int lws(const uint8_t *s, size_t n) { size_t i; for (i = 0; i < n; ++i) { if (s[i] != ' ' && s[i] != '\t') { return 0; } } return 1; } static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_nv *nv, int flag) { if (stream->http_flags & flag) { return 0; } if (lws(nv->value, nv->valuelen)) { return 0; } stream->http_flags |= flag; return 1; } static int expect_response_body(nghttp2_stream *stream) { return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 && stream->status_code / 100 != 1 && stream->status_code != 304 && stream->status_code != 204; } static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, int trailer) { int token; if (nv->name[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { return -1; } } token = lookup_token(nv->name, nv->namelen); switch (token) { case NGHTTP2_TOKEN__AUTHORITY: if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) { return -1; } break; case NGHTTP2_TOKEN__METHOD: if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) { return -1; } if (streq("HEAD", nv->value, nv->valuelen)) { stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; } else if (streq("CONNECT", nv->value, nv->valuelen)) { if (stream->stream_id % 2 == 0) { /* we won't allow CONNECT for push */ return -1; } stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; if (stream->http_flags & (NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) { return -1; } } break; case NGHTTP2_TOKEN__PATH: if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { return -1; } if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { return -1; } break; case NGHTTP2_TOKEN__SCHEME: if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { return -1; } if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { return -1; } break; case NGHTTP2_TOKEN_HOST: if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) { return -1; } break; case NGHTTP2_TOKEN_CONTENT_LENGTH: { if (stream->content_length != -1) { return -1; } stream->content_length = parse_uint(nv->value, nv->valuelen); if (stream->content_length == -1) { return -1; } break; } /* disallowed header fields */ case NGHTTP2_TOKEN_CONNECTION: case NGHTTP2_TOKEN_KEEP_ALIVE: case NGHTTP2_TOKEN_PROXY_CONNECTION: case NGHTTP2_TOKEN_TRANSFER_ENCODING: case NGHTTP2_TOKEN_UPGRADE: return -1; case NGHTTP2_TOKEN_TE: if (!strieq("trailers", nv->value, nv->valuelen)) { return -1; } break; default: if (nv->name[0] == ':') { return -1; } } if (nv->name[0] != ':') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; } return 0; } static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, int trailer) { int token; if (nv->name[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { return -1; } } token = lookup_token(nv->name, nv->namelen); switch (token) { case NGHTTP2_TOKEN__STATUS: { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) { return -1; } if (nv->valuelen != 3) { return -1; } stream->status_code = parse_uint(nv->value, nv->valuelen); if (stream->status_code == -1) { return -1; } break; } case NGHTTP2_TOKEN_CONTENT_LENGTH: { if (stream->content_length != -1) { return -1; } stream->content_length = parse_uint(nv->value, nv->valuelen); if (stream->content_length == -1) { return -1; } break; } /* disallowed header fields */ case NGHTTP2_TOKEN_CONNECTION: case NGHTTP2_TOKEN_KEEP_ALIVE: case NGHTTP2_TOKEN_PROXY_CONNECTION: case NGHTTP2_TOKEN_TRANSFER_ENCODING: case NGHTTP2_TOKEN_UPGRADE: return -1; case NGHTTP2_TOKEN_TE: if (!strieq("trailers", nv->value, nv->valuelen)) { return -1; } break; default: if (nv->name[0] == ':') { return -1; } } if (nv->name[0] != ':') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; } return 0; } int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, nghttp2_frame *frame, nghttp2_nv *nv, int trailer) { if (!nghttp2_check_header_name(nv->name, nv->namelen) || !nghttp2_check_header_value(nv->value, nv->valuelen)) { return -1; } if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { return http_request_on_header(stream, nv, trailer); } return http_response_on_header(stream, nv, trailer); } int nghttp2_http_on_request_headers(nghttp2_stream *stream, nghttp2_frame *frame) { if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { if ((stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) { return -1; } stream->content_length = -1; } else if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) != NGHTTP2_HTTP_FLAG_REQ_HEADERS || (stream->http_flags & (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) { return -1; } if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { /* we are going to reuse data fields for upcoming response. Clear them now, except for method flags. */ stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL; stream->content_length = -1; } return 0; } int nghttp2_http_on_response_headers(nghttp2_stream *stream) { if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) { return -1; } if (stream->status_code / 100 == 1) { /* non-final response */ stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) | NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; stream->content_length = -1; stream->status_code = -1; return 0; } stream->http_flags &= ~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; if (!expect_response_body(stream)) { stream->content_length = 0; } else if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { stream->content_length = -1; } return 0; } int nghttp2_http_on_trailer_headers(nghttp2_stream *stream _U_, nghttp2_frame *frame) { if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { return -1; } return 0; } int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) { if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { return -1; } if (stream->content_length != -1 && stream->content_length != stream->recv_content_length) { return -1; } return 0; } int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) { stream->recv_content_length += n; if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) || (stream->content_length != -1 && stream->recv_content_length > stream->content_length)) { return -1; } return 0; } void nghttp2_http_record_request_method(nghttp2_stream *stream, nghttp2_frame *frame) { const nghttp2_nv *nva; size_t nvlen; size_t i; switch (frame->hd.type) { case NGHTTP2_HEADERS: nva = frame->headers.nva; nvlen = frame->headers.nvlen; break; case NGHTTP2_PUSH_PROMISE: nva = frame->push_promise.nva; nvlen = frame->push_promise.nvlen; break; default: return; } /* TODO we should do this strictly. */ for (i = 0; i < nvlen; ++i) { const nghttp2_nv *nv = &nva[i]; if (lookup_token(nv->name, nv->namelen) != NGHTTP2_TOKEN__METHOD) { continue; } if (streq("CONNECT", nv->value, nv->valuelen)) { stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; return; } if (streq("HEAD", nv->value, nv->valuelen)) { stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; return; } return; } }