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.
28 #include <sys/types.h>
29 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <netinet/tcp.h>
35 #include <arpa/inet.h>
44 #include <nghttp2/nghttp2.h>
52 const char DEFAULT_STRIP_CHARSET[] = "\r\n\t ";
54 const char UPPER_XDIGITS[] = "0123456789ABCDEF";
56 bool isAlpha(const char c) {
57 return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
60 bool isDigit(const char c) { return '0' <= c && c <= '9'; }
62 bool isHexDigit(const char c) {
63 return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
66 bool inRFC3986UnreservedChars(const char c) {
67 static const char unreserved[] = {'-', '.', '_', '~'};
68 return isAlpha(c) || isDigit(c) ||
69 std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4];
72 std::string percentEncode(const unsigned char *target, size_t len) {
74 for (size_t i = 0; i < len; ++i) {
75 unsigned char c = target[i];
77 if (inRFC3986UnreservedChars(c)) {
81 dest += UPPER_XDIGITS[c >> 4];
82 dest += UPPER_XDIGITS[(c & 0x0f)];
88 std::string percentEncode(const std::string &target) {
89 return percentEncode(reinterpret_cast<const unsigned char *>(target.c_str()),
93 bool in_token(char c) {
94 static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+',
95 '-', '.', '^', '_', '`', '|', '~'};
97 return isAlpha(c) || isDigit(c) ||
98 std::find(&extra[0], &extra[sizeof(extra)], c) !=
99 &extra[sizeof(extra)];
102 bool in_attr_char(char c) {
103 static const char bad[] = {'*', '\'', '%'};
104 return util::in_token(c) &&
105 std::find(std::begin(bad), std::end(bad) - 1, c) == std::end(bad) - 1;
108 std::string percent_encode_token(const std::string &target) {
109 auto len = target.size();
112 for (size_t i = 0; i < len; ++i) {
113 unsigned char c = target[i];
115 if (c != '%' && in_token(c)) {
119 dest += UPPER_XDIGITS[c >> 4];
120 dest += UPPER_XDIGITS[(c & 0x0f)];
126 std::string percentDecode(std::string::const_iterator first,
127 std::string::const_iterator last) {
129 for (; first != last; ++first) {
131 if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
132 isHexDigit(*(first + 2))) {
133 std::string numstr(first + 1, first + 3);
134 result += strtol(numstr.c_str(), 0, 16);
146 std::string quote_string(const std::string &target) {
147 auto cnt = std::count(std::begin(target), std::end(target), '"');
154 res.reserve(target.size() + cnt);
156 for (auto c : target) {
168 template <typename Iterator>
169 Iterator cpydig(Iterator d, uint32_t n, size_t len) {
170 auto p = d + len - 1;
173 *p-- = (n % 10) + '0';
182 const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
183 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
184 const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
187 std::string http_date(time_t t) {
191 if (gmtime_r(&t, &tms) == nullptr) {
195 /* Sat, 27 Sep 2014 06:31:15 GMT */
198 auto p = std::begin(res);
200 auto s = DAY_OF_WEEK[tms.tm_wday];
201 p = std::copy_n(s, 3, p);
204 p = cpydig(p, tms.tm_mday, 2);
206 s = MONTH[tms.tm_mon];
207 p = std::copy_n(s, 3, p);
209 p = cpydig(p, tms.tm_year + 1900, 4);
211 p = cpydig(p, tms.tm_hour, 2);
213 p = cpydig(p, tms.tm_min, 2);
215 p = cpydig(p, tms.tm_sec, 2);
217 p = std::copy_n(s, 4, p);
222 std::string common_log_date(time_t t) {
225 if (localtime_r(&t, &tms) == nullptr) {
229 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
230 // Format data like this:
231 // 03/Jul/2014:00:19:38 +0900
235 auto p = std::begin(res);
237 p = cpydig(p, tms.tm_mday, 2);
239 auto s = MONTH[tms.tm_mon];
240 p = std::copy_n(s, 3, p);
242 p = cpydig(p, tms.tm_year + 1900, 4);
244 p = cpydig(p, tms.tm_hour, 2);
246 p = cpydig(p, tms.tm_min, 2);
248 p = cpydig(p, tms.tm_sec, 2);
251 auto gmtoff = tms.tm_gmtoff;
259 p = cpydig(p, gmtoff / 3600, 2);
260 p = cpydig(p, (gmtoff % 3600) / 60, 2);
263 #else // !HAVE_STRUCT_TM_TM_GMTOFF
266 strftime(buf, sizeof(buf), "%d/%b/%Y:%T %z", &tms);
269 #endif // !HAVE_STRUCT_TM_TM_GMTOFF
272 std::string iso8601_date(int64_t ms) {
273 time_t sec = ms / 1000;
276 if (localtime_r(&sec, &tms) == nullptr) {
280 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
281 // Format data like this:
282 // 2014-11-15T12:58:24.741Z
283 // 2014-11-15T12:58:24.741+09:00
287 auto p = std::begin(res);
289 p = cpydig(p, tms.tm_year + 1900, 4);
291 p = cpydig(p, tms.tm_mon + 1, 2);
293 p = cpydig(p, tms.tm_mday, 2);
295 p = cpydig(p, tms.tm_hour, 2);
297 p = cpydig(p, tms.tm_min, 2);
299 p = cpydig(p, tms.tm_sec, 2);
301 p = cpydig(p, ms % 1000, 3);
303 auto gmtoff = tms.tm_gmtoff;
313 p = cpydig(p, gmtoff / 3600, 2);
315 p = cpydig(p, (gmtoff % 3600) / 60, 2);
318 res.resize(p - std::begin(res));
321 #else // !HAVE_STRUCT_TM_TM_GMTOFF
324 auto nwrite = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tms);
325 nwrite += snprintf(&buf[nwrite], sizeof(buf) - nwrite, ".%03d",
326 static_cast<int>(ms % 1000));
327 auto nzone = strftime(&buf[nwrite], sizeof(buf) - nwrite, "%z", &tms);
329 // %z of strftime writes +hhmm or -hhmm not Z, +hh:mm or -hh:mm. Do
330 // %nothing if nzone is not 5. we don't know how to cope with this.
332 if (memcmp(&buf[nwrite], "+0000", 5) == 0) {
334 memcpy(&buf[nwrite], "Z", 2);
336 // Move mm part to right by 1 including terminal \0
337 memmove(&buf[nwrite + 4], &buf[nwrite + 3], 3);
338 // Insert ':' between hh and mm
339 buf[nwrite + 3] = ':';
343 #endif // !HAVE_STRUCT_TM_TM_GMTOFF
346 time_t parse_http_date(const std::string &s) {
348 memset(&tm, 0, sizeof(tm));
349 char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
356 bool startsWith(const std::string &a, const std::string &b) {
357 return startsWith(a.begin(), a.end(), b.begin(), b.end());
360 bool istartsWith(const std::string &a, const std::string &b) {
361 return istartsWith(a.begin(), a.end(), b.begin(), b.end());
365 void streq_advance(const char **ap, const char **bp) {
366 for (; **ap && **bp && lowcase(**ap) == lowcase(**bp); ++*ap, ++*bp)
371 bool istartsWith(const char *a, const char *b) {
375 streq_advance(&a, &b);
379 bool istartsWith(const char *a, size_t n, const char *b) {
380 return istartsWith(a, a + n, b, b + strlen(b));
383 bool endsWith(const std::string &a, const std::string &b) {
384 return endsWith(a.begin(), a.end(), b.begin(), b.end());
387 bool strieq(const std::string &a, const std::string &b) {
388 if (a.size() != b.size()) {
391 for (size_t i = 0; i < a.size(); ++i) {
392 if (lowcase(a[i]) != lowcase(b[i])) {
399 bool strieq(const char *a, const char *b) {
403 for (; *a && *b && lowcase(*a) == lowcase(*b); ++a, ++b)
408 bool strieq(const char *a, const uint8_t *b, size_t bn) {
412 const uint8_t *blast = b + bn;
413 for (; *a && b != blast && lowcase(*a) == lowcase(*b); ++a, ++b)
415 return !*a && b == blast;
418 bool strieq(const char *a, const char *b, size_t bn) {
419 return strieq(a, reinterpret_cast<const uint8_t *>(b), bn);
422 int strcompare(const char *a, const uint8_t *b, size_t bn) {
424 const uint8_t *blast = b + bn;
425 for (; *a && b != blast; ++a, ++b) {
428 } else if (*a > *b) {
432 if (!*a && b == blast) {
434 } else if (b == blast) {
441 bool strifind(const char *a, const char *b) {
445 for (size_t i = 0; a[i]; ++i) {
446 const char *ap = &a[i], *bp = b;
447 for (; *ap && *bp && lowcase(*ap) == lowcase(*bp); ++ap, ++bp)
456 char upcase(char c) {
457 if ('a' <= c && c <= 'z') {
458 return c - 'a' + 'A';
465 const char LOWER_XDIGITS[] = "0123456789abcdef";
468 std::string format_hex(const unsigned char *s, size_t len) {
472 for (size_t i = 0; i < len; ++i) {
473 unsigned char c = s[i];
475 res[i * 2] = LOWER_XDIGITS[c >> 4];
476 res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f];
481 void to_token68(std::string &base64str) {
482 for (auto i = std::begin(base64str); i != std::end(base64str); ++i) {
491 base64str.erase(i, std::end(base64str));
498 void to_base64(std::string &token68str) {
499 for (auto i = std::begin(token68str); i != std::end(token68str); ++i) {
509 if (token68str.size() & 0x3) {
510 token68str.append(4 - (token68str.size() & 0x3), '=');
515 void inp_strlower(std::string &s) {
516 for (auto i = std::begin(s); i != std::end(s); ++i) {
517 if ('A' <= *i && *i <= 'Z') {
518 *i = (*i) - 'A' + 'a';
524 // Calculates Damerau–Levenshtein distance between c-string a and b
525 // with given costs. swapcost, subcost, addcost and delcost are cost
526 // to swap 2 adjacent characters, substitute characters, add character
527 // and delete character respectively.
528 int levenshtein(const char *a, int alen, const char *b, int blen, int swapcost,
529 int subcost, int addcost, int delcost) {
530 auto dp = std::vector<std::vector<int>>(3, std::vector<int>(blen + 1));
531 for (int i = 0; i <= blen; ++i) {
534 for (int i = 1; i <= alen; ++i) {
536 for (int j = 1; j <= blen; ++j) {
537 dp[0][j] = dp[1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : subcost);
538 if (i >= 2 && j >= 2 && a[i - 1] != b[j - 1] && a[i - 2] == b[j - 1] &&
539 a[i - 1] == b[j - 2]) {
540 dp[0][j] = std::min(dp[0][j], dp[2][j - 2] + swapcost);
542 dp[0][j] = std::min(dp[0][j],
543 std::min(dp[1][j] + delcost, dp[0][j - 1] + addcost));
545 std::rotate(std::begin(dp), std::begin(dp) + 2, std::end(dp));
551 void show_candidates(const char *unkopt, option *options) {
552 for (; *unkopt == '-'; ++unkopt)
554 if (*unkopt == '\0') {
557 auto unkoptend = unkopt;
558 for (; *unkoptend && *unkoptend != '='; ++unkoptend)
560 auto unkoptlen = unkoptend - unkopt;
561 if (unkoptlen == 0) {
564 int prefix_match = 0;
565 auto cands = std::vector<std::pair<int, const char *>>();
566 for (size_t i = 0; options[i].name != nullptr; ++i) {
567 auto optnamelen = strlen(options[i].name);
568 // Use cost 0 for prefix match
569 if (istartsWith(options[i].name, options[i].name + optnamelen, unkopt,
570 unkopt + unkoptlen)) {
571 if (optnamelen == static_cast<size_t>(unkoptlen)) {
572 // Exact match, then we don't show any condidates.
576 cands.emplace_back(0, options[i].name);
579 // Use cost 0 for suffix match, but match at least 3 characters
580 if (unkoptlen >= 3 &&
581 iendsWith(options[i].name, options[i].name + optnamelen, unkopt,
582 unkopt + unkoptlen)) {
583 cands.emplace_back(0, options[i].name);
586 // cost values are borrowed from git, help.c.
588 levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3);
589 cands.emplace_back(sim, options[i].name);
591 if (prefix_match == 1 || cands.empty()) {
594 std::sort(std::begin(cands), std::end(cands));
595 int threshold = cands[0].first;
596 // threshold value is a magic value.
600 std::cerr << "\nDid you mean:\n";
601 for (auto &item : cands) {
602 if (item.first > threshold) {
605 std::cerr << "\t--" << item.second << "\n";
609 bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) {
610 return u.field_set & (1 << field);
613 bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2,
614 const http_parser_url &u2, http_parser_url_fields field) {
615 if (!has_uri_field(u1, field)) {
616 if (!has_uri_field(u2, field)) {
621 } else if (!has_uri_field(u2, field)) {
624 if (u1.field_data[field].len != u2.field_data[field].len) {
627 return memcmp(uri1 + u1.field_data[field].off,
628 uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0;
631 bool fieldeq(const char *uri, const http_parser_url &u,
632 http_parser_url_fields field, const char *t) {
633 if (!has_uri_field(u, field)) {
642 int i, len = u.field_data[field].len;
643 const char *p = uri + u.field_data[field].off;
644 for (i = 0; i < len && t[i] && p[i] == t[i]; ++i)
646 return i == len && !t[i];
649 std::string get_uri_field(const char *uri, const http_parser_url &u,
650 http_parser_url_fields field) {
651 if (util::has_uri_field(u, field)) {
652 return std::string(uri + u.field_data[field].off, u.field_data[field].len);
658 uint16_t get_default_port(const char *uri, const http_parser_url &u) {
659 if (util::fieldeq(uri, u, UF_SCHEMA, "https")) {
661 } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) {
668 bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2,
669 const http_parser_url &u2) {
670 uint16_t port1, port2;
672 util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1);
674 util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2);
675 return port1 == port2;
678 void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
679 http_parser_url_fields field) {
680 if (util::has_uri_field(u, field)) {
681 o.write(uri + u.field_data[field].off, u.field_data[field].len);
685 bool numeric_host(const char *hostname) {
686 struct addrinfo hints;
687 struct addrinfo *res;
688 memset(&hints, 0, sizeof(hints));
689 hints.ai_family = AF_UNSPEC;
690 hints.ai_flags = AI_NUMERICHOST;
691 if (getaddrinfo(hostname, nullptr, &hints, &res)) {
698 int reopen_log_file(const char *path) {
699 #if defined(__ANDROID__) || defined(ANDROID)
702 if (strcmp("/proc/self/fd/1", path) == 0 ||
703 strcmp("/proc/self/fd/2", path) == 0) {
705 // We will get permission denied error when O_APPEND is used for
708 open(path, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
710 fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
711 S_IRUSR | S_IWUSR | S_IRGRP);
713 #elif defined O_CLOEXEC
715 auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
716 S_IRUSR | S_IWUSR | S_IRGRP);
720 open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
722 // We get race condition if execve is called at the same time.
724 make_socket_closeonexec(fd);
736 std::string ascii_dump(const uint8_t *data, size_t len) {
739 for (size_t i = 0; i < len; ++i) {
742 if (c >= 0x20 && c < 0x7f) {
752 char *get_exec_path(int argc, char **const argv, const char *cwd) {
753 if (argc == 0 || cwd == nullptr) {
757 auto argv0 = argv[0];
758 auto len = strlen(argv0);
762 if (argv0[0] == '/') {
763 path = static_cast<char *>(malloc(len + 1));
764 memcpy(path, argv0, len + 1);
766 auto cwdlen = strlen(cwd);
767 path = static_cast<char *>(malloc(len + 1 + cwdlen + 1));
768 memcpy(path, cwd, cwdlen);
770 memcpy(path + cwdlen + 1, argv0, len + 1);
776 bool check_path(const std::string &path) {
777 // We don't like '\' in path.
778 return !path.empty() && path[0] == '/' &&
779 path.find('\\') == std::string::npos &&
780 path.find("/../") == std::string::npos &&
781 path.find("/./") == std::string::npos &&
782 !util::endsWith(path, "/..") && !util::endsWith(path, "/.");
785 int64_t to_time64(const timeval &tv) {
786 return tv.tv_sec * 1000000 + tv.tv_usec;
789 bool check_h2_is_selected(const unsigned char *proto, size_t len) {
790 return streq(NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN, proto,
792 streq(NGHTTP2_H2_16_ID, NGHTTP2_H2_16_ID_LEN, proto, len);
796 bool select_h2(const unsigned char **out, unsigned char *outlen,
797 const unsigned char *in, unsigned int inlen, const char *key,
798 unsigned int keylen) {
799 for (auto p = in, end = in + inlen; p + keylen <= end; p += *p + 1) {
800 if (memcmp(key, p, keylen) == 0) {
810 bool select_h2(const unsigned char **out, unsigned char *outlen,
811 const unsigned char *in, unsigned int inlen) {
812 return select_h2(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN,
813 NGHTTP2_H2_16_ALPN_LEN) ||
814 select_h2(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
815 NGHTTP2_PROTO_ALPN_LEN);
818 std::vector<unsigned char> get_default_alpn() {
819 auto res = std::vector<unsigned char>(NGHTTP2_PROTO_ALPN_LEN +
820 NGHTTP2_H2_16_ALPN_LEN);
821 auto p = std::begin(res);
823 p = std::copy_n(NGHTTP2_H2_16_ALPN, NGHTTP2_H2_16_ALPN_LEN, p);
824 p = std::copy_n(NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN, p);
829 int make_socket_closeonexec(int fd) {
832 while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR)
834 while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR)
839 int make_socket_nonblocking(int fd) {
842 while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
844 while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
849 int make_socket_nodelay(int fd) {
851 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
852 sizeof(val)) == -1) {
858 int create_nonblock_socket(int family) {
860 auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
865 #else // !SOCK_NONBLOCK
866 auto fd = socket(family, SOCK_STREAM, 0);
872 make_socket_nonblocking(fd);
873 make_socket_closeonexec(fd);
874 #endif // !SOCK_NONBLOCK
876 make_socket_nodelay(fd);
881 bool check_socket_connected(int fd) {
883 socklen_t len = sizeof(error);
884 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
892 bool ipv6_numeric_addr(const char *host) {
894 return inet_pton(AF_INET6, host, dst) == 1;
898 std::pair<int64_t, size_t> parse_uint_digits(const void *ss, size_t len) {
899 const uint8_t *s = static_cast<const uint8_t *>(ss);
905 constexpr int64_t max = std::numeric_limits<int64_t>::max();
906 for (i = 0; i < len; ++i) {
907 if ('0' <= s[i] && s[i] <= '9') {
912 if (n > max - (s[i] - '0')) {
927 int64_t parse_uint_with_unit(const char *s) {
930 auto len = strlen(s);
931 std::tie(n, i) = parse_uint_digits(s, len);
958 constexpr int64_t max = std::numeric_limits<int64_t>::max();
965 int64_t parse_uint(const char *s) {
966 return parse_uint(reinterpret_cast<const uint8_t *>(s), strlen(s));
969 int64_t parse_uint(const std::string &s) {
970 return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size());
973 int64_t parse_uint(const uint8_t *s, size_t len) {
976 std::tie(n, i) = parse_uint_digits(s, len);
977 if (n == -1 || i != len) {
983 double parse_duration_with_unit(const char *s) {
986 auto len = strlen(s);
987 std::tie(n, i) = parse_uint_digits(s, len);
992 return static_cast<double>(n);
1000 return static_cast<double>(n);
1004 if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
1007 return static_cast<double>(n) / 1000.;
1010 return std::numeric_limits<double>::infinity();
1013 std::string duration_str(double t) {
1017 auto frac = static_cast<int64_t>(t * 1000) % 1000;
1019 return utos(static_cast<int64_t>(t * 1000)) + "ms";
1021 return utos(static_cast<int64_t>(t)) + "s";
1024 std::string format_duration(const std::chrono::microseconds &u) {
1025 const char *unit = "us";
1031 } else if (t >= 1000) {
1035 return utos(t) + unit;
1037 return dtos(static_cast<double>(t) / d) + unit;
1040 std::string dtos(double n) {
1041 auto f = utos(static_cast<int64_t>(round(100. * n)) % 100);
1042 return utos(static_cast<int64_t>(n)) + "." + (f.size() == 1 ? "0" : "") + f;
1047 } // namespace nghttp2