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 char downcase(char c) {
35 return 'A' <= c && c <= 'Z' ? (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_nv *nv,
87 if (stream->http_flags & flag) {
90 if (lws(nv->value, nv->valuelen)) {
93 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_nv *nv,
116 int token, int trailer) {
117 if (nv->name[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->valuelen) {
136 if (lstreq("HEAD", nv->value, nv->valuelen)) {
137 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
141 switch (nv->value[6]) {
143 if (lstreq("CONNECT", nv->value, nv->valuelen)) {
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;
149 if (stream->http_flags &
150 (NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) {
151 return NGHTTP2_ERR_HTTP_HEADER;
156 if (lstreq("OPTIONS", nv->value, nv->valuelen)) {
157 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
164 case NGHTTP2_TOKEN__PATH:
165 if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
166 return NGHTTP2_ERR_HTTP_HEADER;
168 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
169 return NGHTTP2_ERR_HTTP_HEADER;
171 if (nv->value[0] == '/') {
172 stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
173 } else if (nv->valuelen == 1 && nv->value[0] == '*') {
174 stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
177 case NGHTTP2_TOKEN__SCHEME:
178 if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
179 return NGHTTP2_ERR_HTTP_HEADER;
181 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
182 return NGHTTP2_ERR_HTTP_HEADER;
184 if ((nv->valuelen == 4 && memieq("http", nv->value, 4)) ||
185 (nv->valuelen == 5 && memieq("https", nv->value, 5))) {
186 stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
189 case NGHTTP2_TOKEN_HOST:
190 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
191 return NGHTTP2_ERR_HTTP_HEADER;
194 case NGHTTP2_TOKEN_CONTENT_LENGTH: {
195 if (stream->content_length != -1) {
196 return NGHTTP2_ERR_HTTP_HEADER;
198 stream->content_length = parse_uint(nv->value, nv->valuelen);
199 if (stream->content_length == -1) {
200 return NGHTTP2_ERR_HTTP_HEADER;
204 /* disallowed header fields */
205 case NGHTTP2_TOKEN_CONNECTION:
206 case NGHTTP2_TOKEN_KEEP_ALIVE:
207 case NGHTTP2_TOKEN_PROXY_CONNECTION:
208 case NGHTTP2_TOKEN_TRANSFER_ENCODING:
209 case NGHTTP2_TOKEN_UPGRADE:
210 return NGHTTP2_ERR_HTTP_HEADER;
211 case NGHTTP2_TOKEN_TE:
212 if (!lstrieq("trailers", nv->value, nv->valuelen)) {
213 return NGHTTP2_ERR_HTTP_HEADER;
217 if (nv->name[0] == ':') {
218 return NGHTTP2_ERR_HTTP_HEADER;
222 if (nv->name[0] != ':') {
223 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
229 static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
230 int token, int trailer) {
231 if (nv->name[0] == ':') {
233 (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
234 return NGHTTP2_ERR_HTTP_HEADER;
239 case NGHTTP2_TOKEN__STATUS: {
240 if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
241 return NGHTTP2_ERR_HTTP_HEADER;
243 if (nv->valuelen != 3) {
244 return NGHTTP2_ERR_HTTP_HEADER;
246 stream->status_code = parse_uint(nv->value, nv->valuelen);
247 if (stream->status_code == -1) {
248 return NGHTTP2_ERR_HTTP_HEADER;
252 case NGHTTP2_TOKEN_CONTENT_LENGTH: {
253 if (stream->content_length != -1) {
254 return NGHTTP2_ERR_HTTP_HEADER;
256 stream->content_length = parse_uint(nv->value, nv->valuelen);
257 if (stream->content_length == -1) {
258 return NGHTTP2_ERR_HTTP_HEADER;
262 /* disallowed header fields */
263 case NGHTTP2_TOKEN_CONNECTION:
264 case NGHTTP2_TOKEN_KEEP_ALIVE:
265 case NGHTTP2_TOKEN_PROXY_CONNECTION:
266 case NGHTTP2_TOKEN_TRANSFER_ENCODING:
267 case NGHTTP2_TOKEN_UPGRADE:
268 return NGHTTP2_ERR_HTTP_HEADER;
269 case NGHTTP2_TOKEN_TE:
270 if (!lstrieq("trailers", nv->value, nv->valuelen)) {
271 return NGHTTP2_ERR_HTTP_HEADER;
275 if (nv->name[0] == ':') {
276 return NGHTTP2_ERR_HTTP_HEADER;
280 if (nv->name[0] != ':') {
281 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
287 int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
288 nghttp2_frame *frame, nghttp2_nv *nv, int token,
290 /* We are strict for pseudo header field. One bad character should
291 lead to fail. OTOH, we should be a bit forgiving for regular
292 headers, since existing public internet has so much illegal
293 headers floating around and if we kill the stream because of
294 this, we may disrupt many web sites and/or libraries. So we
295 become conservative here, and just ignore those illegal regular
297 if (!nghttp2_check_header_name(nv->name, nv->namelen)) {
299 if (nv->namelen > 0 && nv->name[0] == ':') {
300 return NGHTTP2_ERR_HTTP_HEADER;
302 /* header field name must be lower-cased without exception */
303 for (i = 0; i < nv->namelen; ++i) {
304 char c = nv->name[i];
305 if ('A' <= c && c <= 'Z') {
306 return NGHTTP2_ERR_HTTP_HEADER;
309 /* When ignoring regular headers, we set this flag so that we
310 still enforce header field ordering rule for pseudo header
312 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
313 return NGHTTP2_ERR_IGN_HTTP_HEADER;
316 if (!nghttp2_check_header_value(nv->value, nv->valuelen)) {
317 assert(nv->namelen > 0);
318 if (nv->name[0] == ':') {
319 return NGHTTP2_ERR_HTTP_HEADER;
321 /* When ignoring regular headers, we set this flag so that we
322 still enforce header field ordering rule for pseudo header
324 stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
325 return NGHTTP2_ERR_IGN_HTTP_HEADER;
328 if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
329 return http_request_on_header(stream, nv, token, trailer);
332 return http_response_on_header(stream, nv, token, trailer);
335 int nghttp2_http_on_request_headers(nghttp2_stream *stream,
336 nghttp2_frame *frame) {
337 if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
338 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
341 stream->content_length = -1;
343 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) !=
344 NGHTTP2_HTTP_FLAG_REQ_HEADERS ||
345 (stream->http_flags &
346 (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
349 if (!check_path(stream)) {
354 if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
355 /* we are going to reuse data fields for upcoming response. Clear
356 them now, except for method flags. */
357 stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL;
358 stream->content_length = -1;
364 int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
365 if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) {
369 if (stream->status_code / 100 == 1) {
370 /* non-final response */
371 stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) |
372 NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
373 stream->content_length = -1;
374 stream->status_code = -1;
378 stream->http_flags &= ~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
380 if (!expect_response_body(stream)) {
381 stream->content_length = 0;
382 } else if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
383 stream->content_length = -1;
389 int nghttp2_http_on_trailer_headers(nghttp2_stream *stream _U_,
390 nghttp2_frame *frame) {
391 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
398 int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) {
399 if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
403 if (stream->content_length != -1 &&
404 stream->content_length != stream->recv_content_length) {
411 int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) {
412 stream->recv_content_length += n;
414 if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) ||
415 (stream->content_length != -1 &&
416 stream->recv_content_length > stream->content_length)) {
423 void nghttp2_http_record_request_method(nghttp2_stream *stream,
424 nghttp2_frame *frame) {
425 const nghttp2_nv *nva;
429 switch (frame->hd.type) {
430 case NGHTTP2_HEADERS:
431 nva = frame->headers.nva;
432 nvlen = frame->headers.nvlen;
434 case NGHTTP2_PUSH_PROMISE:
435 nva = frame->push_promise.nva;
436 nvlen = frame->push_promise.nvlen;
442 /* TODO we should do this strictly. */
443 for (i = 0; i < nvlen; ++i) {
444 const nghttp2_nv *nv = &nva[i];
445 if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
446 memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
449 if (lstreq("CONNECT", nv->value, nv->valuelen)) {
450 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
453 if (lstreq("HEAD", nv->value, nv->valuelen)) {
454 stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;