2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2012 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.
33 std::string get_status_string(unsigned int status_code) {
34 switch (status_code) {
36 return "100 Continue";
38 return "101 Switching Protocols";
44 return "202 Accepted";
46 return "203 Non-Authoritative Information";
48 return "204 No Content";
50 return "205 Reset Content";
52 return "206 Partial Content";
54 return "300 Multiple Choices";
56 return "301 Moved Permanently";
60 return "303 See Other";
62 return "304 Not Modified";
64 return "305 Use Proxy";
65 // case 306: return "306 (Unused)";
67 return "307 Temporary Redirect";
69 return "308 Permanent Redirect";
71 return "400 Bad Request";
73 return "401 Unauthorized";
75 return "402 Payment Required";
77 return "403 Forbidden";
79 return "404 Not Found";
81 return "405 Method Not Allowed";
83 return "406 Not Acceptable";
85 return "407 Proxy Authentication Required";
87 return "408 Request Timeout";
89 return "409 Conflict";
93 return "411 Length Required";
95 return "412 Precondition Failed";
97 return "413 Payload Too Large";
99 return "414 URI Too Long";
101 return "415 Unsupported Media Type";
103 return "416 Requested Range Not Satisfiable";
105 return "417 Expectation Failed";
107 return "421 Misdirected Request";
109 return "426 Upgrade Required";
111 return "428 Precondition Required";
113 return "429 Too Many Requests";
115 return "431 Request Header Fields Too Large";
117 return "500 Internal Server Error";
119 return "501 Not Implemented";
121 return "502 Bad Gateway";
123 return "503 Service Unavailable";
125 return "504 Gateway Timeout";
127 return "505 HTTP Version Not Supported";
129 return "511 Network Authentication Required";
131 return util::utos(status_code);
135 void capitalize(std::string &s, size_t offset) {
136 s[offset] = util::upcase(s[offset]);
137 for (size_t i = offset + 1, eoi = s.size(); i < eoi; ++i) {
138 if (s[i - 1] == '-') {
139 s[i] = util::upcase(s[i]);
141 s[i] = util::lowcase(s[i]);
146 bool lws(const char *value) {
147 for (; *value; ++value) {
159 void copy_url_component(std::string &dest, const http_parser_url *u, int field,
161 if (u->field_set & (1 << field)) {
162 dest.assign(url + u->field_data[field].off, u->field_data[field].len);
166 Headers::value_type to_header(const uint8_t *name, size_t namelen,
167 const uint8_t *value, size_t valuelen,
168 bool no_index, int16_t token) {
169 return Header(std::string(reinterpret_cast<const char *>(name), namelen),
170 std::string(reinterpret_cast<const char *>(value), valuelen),
174 void add_header(Headers &nva, const uint8_t *name, size_t namelen,
175 const uint8_t *value, size_t valuelen, bool no_index,
179 for (i = 0; i < valuelen && (value[i] == ' ' || value[i] == '\t'); ++i)
181 for (j = valuelen - 1; j > i && (value[j] == ' ' || value[j] == '\t'); --j)
184 valuelen -= i + (valuelen - j - 1);
186 nva.push_back(to_header(name, namelen, value, valuelen, no_index, token));
189 const Headers::value_type *get_header(const Headers &nva, const char *name) {
190 const Headers::value_type *res = nullptr;
191 for (auto &nv : nva) {
192 if (nv.name == name) {
199 std::string value_to_str(const Headers::value_type *nv) {
206 bool non_empty_value(const Headers::value_type *nv) {
207 return nv && !nv->value.empty();
210 nghttp2_nv make_nv(const std::string &name, const std::string &value,
214 flags = no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE;
216 return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
217 value.size(), flags};
220 void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
221 for (auto &kv : headers) {
222 if (kv.name.empty() || kv.name[0] == ':') {
229 case HD_HTTP2_SETTINGS:
231 case HD_PROXY_CONNECTION:
234 case HD_TRANSFER_ENCODING:
237 case HD_X_FORWARDED_FOR:
238 case HD_X_FORWARDED_PROTO:
241 nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
245 void build_http1_headers_from_headers(std::string &hdrs,
246 const Headers &headers) {
247 for (auto &kv : headers) {
248 if (kv.name.empty() || kv.name[0] == ':') {
255 case HD_HTTP2_SETTINGS:
257 case HD_PROXY_CONNECTION:
262 case HD_X_FORWARDED_FOR:
263 case HD_X_FORWARDED_PROTO:
267 capitalize(hdrs, hdrs.size() - kv.name.size());
274 int32_t determine_window_update_transmission(nghttp2_session *session,
276 int32_t recv_length, window_size;
277 if (stream_id == 0) {
278 recv_length = nghttp2_session_get_effective_recv_data_length(session);
279 window_size = nghttp2_session_get_effective_local_window_size(session);
281 recv_length = nghttp2_session_get_stream_effective_recv_data_length(
283 window_size = nghttp2_session_get_stream_effective_local_window_size(
286 if (recv_length != -1 && window_size != -1) {
287 if (recv_length >= window_size / 2) {
294 void dump_nv(FILE *out, const char **nv) {
295 for (size_t i = 0; nv[i]; i += 2) {
296 fwrite(nv[i], strlen(nv[i]), 1, out);
297 fwrite(": ", 2, 1, out);
298 fwrite(nv[i + 1], strlen(nv[i + 1]), 1, out);
299 fwrite("\n", 1, 1, out);
301 fwrite("\n", 1, 1, out);
305 void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen) {
306 auto end = nva + nvlen;
307 for (; nva != end; ++nva) {
308 fwrite(nva->name, nva->namelen, 1, out);
309 fwrite(": ", 2, 1, out);
310 fwrite(nva->value, nva->valuelen, 1, out);
311 fwrite("\n", 1, 1, out);
313 fwrite("\n", 1, 1, out);
317 void dump_nv(FILE *out, const Headers &nva) {
318 for (auto &nv : nva) {
319 fwrite(nv.name.c_str(), nv.name.size(), 1, out);
320 fwrite(": ", 2, 1, out);
321 fwrite(nv.value.c_str(), nv.value.size(), 1, out);
322 fwrite("\n", 1, 1, out);
324 fwrite("\n", 1, 1, out);
328 std::string rewrite_location_uri(const std::string &uri,
329 const http_parser_url &u,
330 const std::string &match_host,
331 const std::string &request_authority,
332 const std::string &upstream_scheme) {
333 // We just rewrite scheme and authority.
334 if ((u.field_set & (1 << UF_HOST)) == 0) {
337 auto field = &u.field_data[UF_HOST];
338 if (!util::startsWith(std::begin(match_host), std::end(match_host),
339 &uri[field->off], &uri[field->off] + field->len) ||
340 (match_host.size() != field->len && match_host[field->len] != ':')) {
344 if (!request_authority.empty()) {
345 res += upstream_scheme;
347 res += request_authority;
349 if (u.field_set & (1 << UF_PATH)) {
350 field = &u.field_data[UF_PATH];
351 res.append(&uri[field->off], field->len);
353 if (u.field_set & (1 << UF_QUERY)) {
354 field = &u.field_data[UF_QUERY];
356 res.append(&uri[field->off], field->len);
358 if (u.field_set & (1 << UF_FRAGMENT)) {
359 field = &u.field_data[UF_FRAGMENT];
361 res.append(&uri[field->off], field->len);
366 int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
368 if (!nghttp2_check_header_name(name, namelen)) {
371 if (!nghttp2_check_header_value(value, valuelen)) {
377 int parse_http_status_code(const std::string &src) {
378 if (src.size() != 3) {
398 int lookup_token(const std::string &name) {
399 return lookup_token(reinterpret_cast<const uint8_t *>(name.c_str()),
403 // This function was generated by genheaderfunc.py. Inspired by h2o
404 // header lookup. https://github.com/h2o/h2o
405 int lookup_token(const uint8_t *name, size_t namelen) {
408 switch (name[namelen - 1]) {
410 if (util::streq("t", name, 1)) {
417 switch (name[namelen - 1]) {
419 if (util::streq("vi", name, 2)) {
426 switch (name[namelen - 1]) {
428 if (util::streq("lin", name, 3)) {
433 if (util::streq("hos", name, 3)) {
440 switch (name[namelen - 1]) {
442 if (util::streq(":pat", name, 4)) {
447 if (util::streq(":hos", name, 4)) {
454 switch (name[namelen - 1]) {
456 if (util::streq("cooki", name, 5)) {
461 if (util::streq("serve", name, 5)) {
466 if (util::streq("expec", name, 5)) {
473 switch (name[namelen - 1]) {
475 if (util::streq("alt-sv", name, 6)) {
480 if (util::streq(":metho", name, 6)) {
485 if (util::streq(":schem", name, 6)) {
488 if (util::streq("upgrad", name, 6)) {
493 if (util::streq("traile", name, 6)) {
498 if (util::streq(":statu", name, 6)) {
505 switch (name[namelen - 1]) {
507 if (util::streq("locatio", name, 7)) {
514 switch (name[namelen - 1]) {
516 if (util::streq("keep-aliv", name, 9)) {
517 return HD_KEEP_ALIVE;
521 if (util::streq("connectio", name, 9)) {
522 return HD_CONNECTION;
526 if (util::streq("user-agen", name, 9)) {
527 return HD_USER_AGENT;
531 if (util::streq(":authorit", name, 9)) {
532 return HD__AUTHORITY;
538 switch (name[namelen - 1]) {
540 if (util::streq("cache-contro", name, 12)) {
541 return HD_CACHE_CONTROL;
547 switch (name[namelen - 1]) {
549 if (util::streq("content-lengt", name, 13)) {
550 return HD_CONTENT_LENGTH;
554 if (util::streq("http2-setting", name, 13)) {
555 return HD_HTTP2_SETTINGS;
561 switch (name[namelen - 1]) {
563 if (util::streq("accept-languag", name, 14)) {
564 return HD_ACCEPT_LANGUAGE;
568 if (util::streq("accept-encodin", name, 14)) {
569 return HD_ACCEPT_ENCODING;
573 if (util::streq("x-forwarded-fo", name, 14)) {
574 return HD_X_FORWARDED_FOR;
580 switch (name[namelen - 1]) {
582 if (util::streq("proxy-connectio", name, 15)) {
583 return HD_PROXY_CONNECTION;
589 switch (name[namelen - 1]) {
591 if (util::streq("if-modified-sinc", name, 16)) {
592 return HD_IF_MODIFIED_SINCE;
596 if (util::streq("transfer-encodin", name, 16)) {
597 return HD_TRANSFER_ENCODING;
601 if (util::streq("x-forwarded-prot", name, 16)) {
602 return HD_X_FORWARDED_PROTO;
611 void init_hdidx(HeaderIndex &hdidx) {
612 std::fill(std::begin(hdidx), std::end(hdidx), -1);
615 void index_header(HeaderIndex &hdidx, int16_t token, size_t idx) {
619 assert(token < HD_MAXIDX);
623 bool check_http2_request_pseudo_header(const HeaderIndex &hdidx,
630 return hdidx[token] == -1;
636 bool check_http2_response_pseudo_header(const HeaderIndex &hdidx,
640 return hdidx[token] == -1;
646 bool http2_header_allowed(int16_t token) {
650 case HD_PROXY_CONNECTION:
651 case HD_TRANSFER_ENCODING:
659 bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx) {
660 if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 ||
661 hdidx[HD__SCHEME] == -1 ||
662 (hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) {
668 const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
669 const Headers &nva) {
670 auto i = hdidx[token];
678 template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
679 for (; first != last; ++first) {
693 template <typename InputIt>
694 InputIt skip_to_next_field(InputIt first, InputIt last) {
695 for (; first != last; ++first) {
710 std::pair<LinkHeader, const char *>
711 parse_next_link_header_once(const char *first, const char *last) {
712 first = skip_to_next_field(first, last);
713 if (first == last || *first != '<') {
714 return {{{0, 0}}, last};
716 auto url_first = ++first;
717 first = std::find(first, last, '>');
719 return {{{0, 0}}, first};
721 auto url_last = first++;
723 return {{{0, 0}}, first};
725 // we expect ';' or ',' here
728 return {{{0, 0}}, ++first};
733 return {{{0, 0}}, last};
738 first = skip_lws(first, last);
740 return {{{0, 0}}, first};
742 // we expect link-param
744 // we are only interested in rel=preload parameter. Others are
746 static const char PL[] = "rel=preload";
747 static const size_t PLLEN = sizeof(PL) - 1;
748 if (first + PLLEN == last) {
749 if (std::equal(PL, PL + PLLEN, first)) {
751 // this is the end of sequence
752 return {{{url_first, url_last}}, last};
754 } else if (first + PLLEN + 1 <= last) {
755 switch (*(first + PLLEN)) {
757 if (!std::equal(PL, PL + PLLEN, first)) {
761 // skip including ','
763 return {{{url_first, url_last}}, first};
765 if (!std::equal(PL, PL + PLLEN, first)) {
769 // skip including ';'
771 // continue parse next link-param
775 auto param_first = first;
776 for (; first != last;) {
777 if (util::in_attr_char(*first)) {
781 // '*' is only allowed at the end of parameter name and must be
783 if (last - first >= 2 && first != param_first) {
784 if (*first == '*' && *(first + 1) == '=') {
789 if (*first == '=' || *first == ';' || *first == ',') {
792 return {{{0, 0}}, last};
794 if (param_first == first) {
796 return {{{0, 0}}, last};
798 // link-param without value is acceptable (see link-extension) if
799 // it is not followed by '='
800 if (first == last || *first == ',') {
805 // parse next link-param
808 // now parsing lin-param value
809 assert(*first == '=');
812 // empty value is not acceptable
813 return {{{0, 0}}, first};
817 first = std::find(first + 1, last, '"');
819 return {{{0, 0}}, first};
822 if (first == last || *first == ',') {
827 // parse next link-param
830 return {{{0, 0}}, last};
832 // not quoted-string, skip to next ',' or ';'
833 if (*first == ',' || *first == ';') {
835 return {{{0, 0}}, last};
837 for (; first != last; ++first) {
838 if (*first == ',' || *first == ';') {
842 if (first == last || *first == ',') {
845 assert(*first == ';');
847 // parse next link-param
851 assert(first == last || *first == ',');
857 return {{{url_first, url_last}}, first};
859 return {{{0, 0}}, first};
863 std::vector<LinkHeader> parse_link_header(const char *src, size_t len) {
865 auto last = src + len;
866 std::vector<LinkHeader> res;
867 for (; first != last;) {
868 auto rv = parse_next_link_header_once(first, last);
870 if (rv.first.uri.first != 0 || rv.first.uri.second != 0) {
871 res.push_back(rv.first);
878 void eat_file(std::string &path) {
883 auto p = path.size() - 1;
884 if (path[p] == '/') {
887 p = path.rfind('/', p);
888 if (p == std::string::npos) {
889 // this should not happend in normal case, where we expect path
894 path.erase(std::begin(path) + p + 1, std::end(path));
899 void eat_dir(std::string &path) {
904 auto p = path.size() - 1;
905 if (path[p] != '/') {
906 p = path.rfind('/', p);
907 if (p == std::string::npos) {
908 // this should not happend in normal case, where we expect path
914 if (path[p] == '/') {
920 p = path.rfind('/', p);
921 if (p == std::string::npos) {
922 // this should not happend in normal case, where we expect path
927 path.erase(std::begin(path) + p + 1, std::end(path));
931 std::string path_join(const char *base_path, size_t base_pathlen,
932 const char *base_query, size_t base_querylen,
933 const char *rel_path, size_t rel_pathlen,
934 const char *rel_query, size_t rel_querylen) {
936 if (rel_pathlen == 0) {
937 if (base_pathlen == 0) {
940 res.assign(base_path, base_pathlen);
942 if (rel_querylen == 0) {
945 res.append(base_query, base_querylen);
950 res.append(rel_query, rel_querylen);
954 auto first = rel_path;
955 auto last = rel_path + rel_pathlen;
957 if (rel_path[0] == '/') {
960 } else if (base_pathlen == 0) {
963 res.assign(base_path, base_pathlen);
966 for (; first != last;) {
968 if (first + 1 == last) {
971 if (*(first + 1) == '/') {
975 if (*(first + 1) == '.') {
976 if (first + 2 == last) {
980 if (*(first + 2) == '/') {
987 if (res.back() != '/') {
990 auto slash = std::find(first, last, '/');
992 res.append(first, last);
995 res.append(first, slash + 1);
997 for (; first != last && *first == '/'; ++first)
1002 res.append(rel_query, rel_querylen);
1007 } // namespace http2
1009 } // namespace nghttp2