1/*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2015 Tatsuhiro Tsujikawa
5 *
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:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
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.
24 */
25#include "nghttp2_http.h"
26
27#include <string.h>
28#include <assert.h>
29#include <stdio.h>
30
31#include "nghttp2_hd.h"
32#include "nghttp2_helper.h"
33#include "nghttp2_extpri.h"
34#include "sfparse.h"
35
36static uint8_t downcase(uint8_t c) {
37  return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c;
38}
39
40static int memieq(const void *a, const void *b, size_t n) {
41  size_t i;
42  const uint8_t *aa = a, *bb = b;
43
44  for (i = 0; i < n; ++i) {
45    if (downcase(aa[i]) != downcase(bb[i])) {
46      return 0;
47    }
48  }
49  return 1;
50}
51
52#define lstrieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N)))
53
54static int64_t parse_uint(const uint8_t *s, size_t len) {
55  int64_t n = 0;
56  size_t i;
57  if (len == 0) {
58    return -1;
59  }
60  for (i = 0; i < len; ++i) {
61    if ('0' <= s[i] && s[i] <= '9') {
62      if (n > INT64_MAX / 10) {
63        return -1;
64      }
65      n *= 10;
66      if (n > INT64_MAX - (s[i] - '0')) {
67        return -1;
68      }
69      n += s[i] - '0';
70      continue;
71    }
72    return -1;
73  }
74  return n;
75}
76
77static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv,
78                               uint32_t flag) {
79  if ((stream->http_flags & flag) || nv->value->len == 0) {
80    return 0;
81  }
82  stream->http_flags = stream->http_flags | flag;
83  return 1;
84}
85
86static int expect_response_body(nghttp2_stream *stream) {
87  return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 &&
88         stream->status_code / 100 != 1 && stream->status_code != 304 &&
89         stream->status_code != 204;
90}
91
92/* For "http" or "https" URIs, OPTIONS request may have "*" in :path
93   header field to represent system-wide OPTIONS request.  Otherwise,
94   :path header field value must start with "/".  This function must
95   be called after ":method" header field was received.  This function
96   returns nonzero if path is valid.*/
97static int check_path(nghttp2_stream *stream) {
98  return (stream->http_flags & NGHTTP2_HTTP_FLAG_SCHEME_HTTP) == 0 ||
99         ((stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_REGULAR) ||
100          ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_OPTIONS) &&
101           (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK)));
102}
103
104static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
105                                  int trailer, int connect_protocol) {
106  nghttp2_extpri extpri;
107
108  if (nv->name->base[0] == ':') {
109    if (trailer ||
110        (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
111      return NGHTTP2_ERR_HTTP_HEADER;
112    }
113  }
114
115  switch (nv->token) {
116  case NGHTTP2_TOKEN__AUTHORITY:
117    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
118      return NGHTTP2_ERR_HTTP_HEADER;
119    }
120    break;
121  case NGHTTP2_TOKEN__METHOD:
122    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
123      return NGHTTP2_ERR_HTTP_HEADER;
124    }
125    switch (nv->value->len) {
126    case 4:
127      if (lstreq("HEAD", nv->value->base, nv->value->len)) {
128        stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
129      }
130      break;
131    case 7:
132      switch (nv->value->base[6]) {
133      case 'T':
134        if (lstreq("CONNECT", nv->value->base, nv->value->len)) {
135          if (stream->stream_id % 2 == 0) {
136            /* we won't allow CONNECT for push */
137            return NGHTTP2_ERR_HTTP_HEADER;
138          }
139          stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
140        }
141        break;
142      case 'S':
143        if (lstreq("OPTIONS", nv->value->base, nv->value->len)) {
144          stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
145        }
146        break;
147      }
148      break;
149    }
150    break;
151  case NGHTTP2_TOKEN__PATH:
152    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
153      return NGHTTP2_ERR_HTTP_HEADER;
154    }
155    if (nv->value->base[0] == '/') {
156      stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
157    } else if (nv->value->len == 1 && nv->value->base[0] == '*') {
158      stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
159    }
160    break;
161  case NGHTTP2_TOKEN__SCHEME:
162    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
163      return NGHTTP2_ERR_HTTP_HEADER;
164    }
165    if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) ||
166        (nv->value->len == 5 && memieq("https", nv->value->base, 5))) {
167      stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
168    }
169    break;
170  case NGHTTP2_TOKEN__PROTOCOL:
171    if (!connect_protocol) {
172      return NGHTTP2_ERR_HTTP_HEADER;
173    }
174
175    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) {
176      return NGHTTP2_ERR_HTTP_HEADER;
177    }
178    break;
179  case NGHTTP2_TOKEN_HOST:
180    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
181      return NGHTTP2_ERR_HTTP_HEADER;
182    }
183    break;
184  case NGHTTP2_TOKEN_CONTENT_LENGTH: {
185    if (stream->content_length != -1) {
186      return NGHTTP2_ERR_HTTP_HEADER;
187    }
188    stream->content_length = parse_uint(nv->value->base, nv->value->len);
189    if (stream->content_length == -1) {
190      return NGHTTP2_ERR_HTTP_HEADER;
191    }
192    break;
193  }
194  /* disallowed header fields */
195  case NGHTTP2_TOKEN_CONNECTION:
196  case NGHTTP2_TOKEN_KEEP_ALIVE:
197  case NGHTTP2_TOKEN_PROXY_CONNECTION:
198  case NGHTTP2_TOKEN_TRANSFER_ENCODING:
199  case NGHTTP2_TOKEN_UPGRADE:
200    return NGHTTP2_ERR_HTTP_HEADER;
201  case NGHTTP2_TOKEN_TE:
202    if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
203      return NGHTTP2_ERR_HTTP_HEADER;
204    }
205    break;
206  case NGHTTP2_TOKEN_PRIORITY:
207    if (!trailer &&
208        /* Do not parse the header field in PUSH_PROMISE. */
209        (stream->stream_id & 1) &&
210        (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) &&
211        !(stream->http_flags & NGHTTP2_HTTP_FLAG_BAD_PRIORITY)) {
212      nghttp2_extpri_from_uint8(&extpri, stream->http_extpri);
213      if (nghttp2_http_parse_priority(&extpri, nv->value->base,
214                                      nv->value->len) == 0) {
215        stream->http_extpri = nghttp2_extpri_to_uint8(&extpri);
216        stream->http_flags |= NGHTTP2_HTTP_FLAG_PRIORITY;
217      } else {
218        stream->http_flags &= (uint32_t)~NGHTTP2_HTTP_FLAG_PRIORITY;
219        stream->http_flags |= NGHTTP2_HTTP_FLAG_BAD_PRIORITY;
220      }
221    }
222    break;
223  default:
224    if (nv->name->base[0] == ':') {
225      return NGHTTP2_ERR_HTTP_HEADER;
226    }
227  }
228
229  if (nv->name->base[0] != ':') {
230    stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
231  }
232
233  return 0;
234}
235
236static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
237                                   int trailer) {
238  if (nv->name->base[0] == ':') {
239    if (trailer ||
240        (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
241      return NGHTTP2_ERR_HTTP_HEADER;
242    }
243  }
244
245  switch (nv->token) {
246  case NGHTTP2_TOKEN__STATUS: {
247    if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
248      return NGHTTP2_ERR_HTTP_HEADER;
249    }
250    if (nv->value->len != 3) {
251      return NGHTTP2_ERR_HTTP_HEADER;
252    }
253    stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len);
254    if (stream->status_code == -1 || stream->status_code == 101) {
255      return NGHTTP2_ERR_HTTP_HEADER;
256    }
257    break;
258  }
259  case NGHTTP2_TOKEN_CONTENT_LENGTH: {
260    if (stream->status_code == 204) {
261      /* content-length header field in 204 response is prohibited by
262         RFC 7230.  But some widely used servers send content-length:
263         0.  Until they get fixed, we ignore it. */
264      if (stream->content_length != -1) {
265        /* Found multiple content-length field */
266        return NGHTTP2_ERR_HTTP_HEADER;
267      }
268      if (!lstrieq("0", nv->value->base, nv->value->len)) {
269        return NGHTTP2_ERR_HTTP_HEADER;
270      }
271      stream->content_length = 0;
272      return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
273    }
274    if (stream->status_code / 100 == 1) {
275      return NGHTTP2_ERR_HTTP_HEADER;
276    }
277    /* https://tools.ietf.org/html/rfc7230#section-3.3.3 */
278    if (stream->status_code / 100 == 2 &&
279        (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
280      return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
281    }
282    if (stream->content_length != -1) {
283      return NGHTTP2_ERR_HTTP_HEADER;
284    }
285    stream->content_length = parse_uint(nv->value->base, nv->value->len);
286    if (stream->content_length == -1) {
287      return NGHTTP2_ERR_HTTP_HEADER;
288    }
289    break;
290  }
291  /* disallowed header fields */
292  case NGHTTP2_TOKEN_CONNECTION:
293  case NGHTTP2_TOKEN_KEEP_ALIVE:
294  case NGHTTP2_TOKEN_PROXY_CONNECTION:
295  case NGHTTP2_TOKEN_TRANSFER_ENCODING:
296  case NGHTTP2_TOKEN_UPGRADE:
297    return NGHTTP2_ERR_HTTP_HEADER;
298  case NGHTTP2_TOKEN_TE:
299    if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
300      return NGHTTP2_ERR_HTTP_HEADER;
301    }
302    break;
303  default:
304    if (nv->name->base[0] == ':') {
305      return NGHTTP2_ERR_HTTP_HEADER;
306    }
307  }
308
309  if (nv->name->base[0] != ':') {
310    stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
311  }
312
313  return 0;
314}
315
316static int check_scheme(const uint8_t *value, size_t len) {
317  const uint8_t *last;
318  if (len == 0) {
319    return 0;
320  }
321
322  if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) {
323    return 0;
324  }
325
326  last = value + len;
327  ++value;
328
329  for (; value != last; ++value) {
330    if (!(('A' <= *value && *value <= 'Z') ||
331          ('a' <= *value && *value <= 'z') ||
332          ('0' <= *value && *value <= '9') || *value == '+' || *value == '-' ||
333          *value == '.')) {
334      return 0;
335    }
336  }
337  return 1;
338}
339
340static int lws(const uint8_t *s, size_t n) {
341  size_t i;
342  for (i = 0; i < n; ++i) {
343    if (s[i] != ' ' && s[i] != '\t') {
344      return 0;
345    }
346  }
347  return 1;
348}
349
350int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
351                           nghttp2_frame *frame, nghttp2_hd_nv *nv,
352                           int trailer) {
353  int rv;
354
355  /* We are strict for pseudo header field.  One bad character should
356     lead to fail.  OTOH, we should be a bit forgiving for regular
357     headers, since existing public internet has so much illegal
358     headers floating around and if we kill the stream because of
359     this, we may disrupt many web sites and/or libraries.  So we
360     become conservative here, and just ignore those illegal regular
361     headers. */
362  if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) {
363    size_t i;
364    if (nv->name->len > 0 && nv->name->base[0] == ':') {
365      return NGHTTP2_ERR_HTTP_HEADER;
366    }
367    /* header field name must be lower-cased without exception */
368    for (i = 0; i < nv->name->len; ++i) {
369      uint8_t c = nv->name->base[i];
370      if ('A' <= c && c <= 'Z') {
371        return NGHTTP2_ERR_HTTP_HEADER;
372      }
373    }
374    /* When ignoring regular headers, we set this flag so that we
375       still enforce header field ordering rule for pseudo header
376       fields. */
377    stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
378    return NGHTTP2_ERR_IGN_HTTP_HEADER;
379  }
380
381  switch (nv->token) {
382  case NGHTTP2_TOKEN__METHOD:
383    rv = nghttp2_check_method(nv->value->base, nv->value->len);
384    break;
385  case NGHTTP2_TOKEN__PATH:
386    rv = nghttp2_check_path(nv->value->base, nv->value->len);
387    break;
388  case NGHTTP2_TOKEN__AUTHORITY:
389  case NGHTTP2_TOKEN_HOST:
390    if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
391      rv = nghttp2_check_authority(nv->value->base, nv->value->len);
392    } else if (
393        stream->flags &
394        NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
395      rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
396    } else {
397      rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
398    }
399    break;
400  case NGHTTP2_TOKEN__SCHEME:
401    rv = check_scheme(nv->value->base, nv->value->len);
402    break;
403  case NGHTTP2_TOKEN__PROTOCOL:
404    /* Check the value consists of just white spaces, which was done
405       in check_pseudo_header before
406       nghttp2_check_header_value_rfc9113 has been introduced. */
407    if ((stream->flags &
408         NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) &&
409        lws(nv->value->base, nv->value->len)) {
410      rv = 0;
411      break;
412    }
413    /* fall through */
414  default:
415    if (stream->flags &
416        NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
417      rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
418    } else {
419      rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
420    }
421  }
422
423  if (rv == 0) {
424    assert(nv->name->len > 0);
425    if (nv->name->base[0] == ':') {
426      return NGHTTP2_ERR_HTTP_HEADER;
427    }
428    /* When ignoring regular headers, we set this flag so that we
429       still enforce header field ordering rule for pseudo header
430       fields. */
431    stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
432    return NGHTTP2_ERR_IGN_HTTP_HEADER;
433  }
434
435  if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
436    return http_request_on_header(stream, nv, trailer,
437                                  session->server &&
438                                      session->pending_enable_connect_protocol);
439  }
440
441  return http_response_on_header(stream, nv, trailer);
442}
443
444int nghttp2_http_on_request_headers(nghttp2_stream *stream,
445                                    nghttp2_frame *frame) {
446  if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
447      (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
448    if ((stream->http_flags &
449         (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) ||
450        (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
451      return -1;
452    }
453    stream->content_length = -1;
454  } else {
455    if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) !=
456            NGHTTP2_HTTP_FLAG_REQ_HEADERS ||
457        (stream->http_flags &
458         (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
459      return -1;
460    }
461    if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
462        ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 ||
463         (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) {
464      return -1;
465    }
466    if (!check_path(stream)) {
467      return -1;
468    }
469  }
470
471  if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
472    /* we are going to reuse data fields for upcoming response.  Clear
473       them now, except for method flags. */
474    stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL;
475    stream->content_length = -1;
476  }
477
478  return 0;
479}
480
481int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
482  if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) {
483    return -1;
484  }
485
486  if (stream->status_code / 100 == 1) {
487    /* non-final response */
488    stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) |
489                         NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
490    stream->content_length = -1;
491    stream->status_code = -1;
492    return 0;
493  }
494
495  stream->http_flags =
496      stream->http_flags & (uint32_t)~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
497
498  if (!expect_response_body(stream)) {
499    stream->content_length = 0;
500  } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT |
501                                   NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) {
502    stream->content_length = -1;
503  }
504
505  return 0;
506}
507
508int nghttp2_http_on_trailer_headers(nghttp2_stream *stream,
509                                    nghttp2_frame *frame) {
510  (void)stream;
511
512  if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
513    return -1;
514  }
515
516  return 0;
517}
518
519int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) {
520  if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
521    return -1;
522  }
523
524  if (stream->content_length != -1 &&
525      stream->content_length != stream->recv_content_length) {
526    return -1;
527  }
528
529  return 0;
530}
531
532int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) {
533  stream->recv_content_length += (int64_t)n;
534
535  if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) ||
536      (stream->content_length != -1 &&
537       stream->recv_content_length > stream->content_length)) {
538    return -1;
539  }
540
541  return 0;
542}
543
544void nghttp2_http_record_request_method(nghttp2_stream *stream,
545                                        nghttp2_frame *frame) {
546  const nghttp2_nv *nva;
547  size_t nvlen;
548  size_t i;
549
550  switch (frame->hd.type) {
551  case NGHTTP2_HEADERS:
552    nva = frame->headers.nva;
553    nvlen = frame->headers.nvlen;
554    break;
555  case NGHTTP2_PUSH_PROMISE:
556    nva = frame->push_promise.nva;
557    nvlen = frame->push_promise.nvlen;
558    break;
559  default:
560    return;
561  }
562
563  /* TODO we should do this strictly. */
564  for (i = 0; i < nvlen; ++i) {
565    const nghttp2_nv *nv = &nva[i];
566    if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
567          memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
568      continue;
569    }
570    if (lstreq("CONNECT", nv->value, nv->valuelen)) {
571      stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
572      return;
573    }
574    if (lstreq("HEAD", nv->value, nv->valuelen)) {
575      stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
576      return;
577    }
578    return;
579  }
580}
581
582int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value,
583                                size_t valuelen) {
584  nghttp2_extpri pri = *dest;
585  sf_parser sfp;
586  sf_vec key;
587  sf_value val;
588  int rv;
589
590  sf_parser_init(&sfp, value, valuelen);
591
592  for (;;) {
593    rv = sf_parser_dict(&sfp, &key, &val);
594    if (rv != 0) {
595      if (rv == SF_ERR_EOF) {
596        break;
597      }
598
599      return NGHTTP2_ERR_INVALID_ARGUMENT;
600    }
601
602    if (key.len != 1) {
603      continue;
604    }
605
606    switch (key.base[0]) {
607    case 'i':
608      if (val.type != SF_TYPE_BOOLEAN) {
609        return NGHTTP2_ERR_INVALID_ARGUMENT;
610      }
611
612      pri.inc = val.boolean;
613
614      break;
615    case 'u':
616      if (val.type != SF_TYPE_INTEGER ||
617          val.integer < NGHTTP2_EXTPRI_URGENCY_HIGH ||
618          NGHTTP2_EXTPRI_URGENCY_LOW < val.integer) {
619        return NGHTTP2_ERR_INVALID_ARGUMENT;
620      }
621
622      pri.urgency = (uint32_t)val.integer;
623
624      break;
625    }
626  }
627
628  *dest = pri;
629
630  return 0;
631}
632