• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2013 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 "nghttp.h"
26 
27 #include <sys/stat.h>
28 #ifdef HAVE_UNISTD_H
29 #  include <unistd.h>
30 #endif // HAVE_UNISTD_H
31 #ifdef HAVE_FCNTL_H
32 #  include <fcntl.h>
33 #endif // HAVE_FCNTL_H
34 #ifdef HAVE_NETINET_IN_H
35 #  include <netinet/in.h>
36 #endif // HAVE_NETINET_IN_H
37 #include <netinet/tcp.h>
38 #include <getopt.h>
39 
40 #include <cassert>
41 #include <cstdio>
42 #include <cerrno>
43 #include <cstdlib>
44 #include <cstring>
45 #include <iostream>
46 #include <iomanip>
47 #include <sstream>
48 #include <tuple>
49 
50 #include "ssl_compat.h"
51 
52 #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL
53 #  include <wolfssl/options.h>
54 #  include <wolfssl/openssl/err.h>
55 #else // !NGHTTP2_OPENSSL_IS_WOLFSSL
56 #  include <openssl/err.h>
57 #endif // !NGHTTP2_OPENSSL_IS_WOLFSSL
58 
59 #ifdef HAVE_JANSSON
60 #  include <jansson.h>
61 #endif // HAVE_JANSSON
62 
63 #include "app_helper.h"
64 #include "HtmlParser.h"
65 #include "util.h"
66 #include "base64.h"
67 #include "tls.h"
68 #include "template.h"
69 
70 #ifndef O_BINARY
71 #  define O_BINARY (0)
72 #endif // O_BINARY
73 
74 namespace nghttp2 {
75 
76 // The anchor stream nodes when --no-dep is not used.  The stream ID =
77 // 1 is excluded since it is used as first stream in upgrade case.  We
78 // follows the same dependency anchor nodes as Firefox does.
79 struct Anchor {
80   int32_t stream_id;
81   // stream ID this anchor depends on
82   int32_t dep_stream_id;
83   // .. with this weight.
84   int32_t weight;
85 };
86 
87 // This is index into anchors.  Firefox uses ANCHOR_FOLLOWERS for html
88 // file.
89 enum {
90   ANCHOR_LEADERS,
91   ANCHOR_UNBLOCKED,
92   ANCHOR_BACKGROUND,
93   ANCHOR_SPECULATIVE,
94   ANCHOR_FOLLOWERS,
95 };
96 
97 namespace {
98 constexpr auto anchors = std::to_array<Anchor>({
99   {3, 0, 201},
100   {5, 0, 101},
101   {7, 0, 1},
102   {9, 7, 1},
103   {11, 3, 1},
104 });
105 } // namespace
106 
Config()107 Config::Config()
108   : header_table_size(-1),
109     min_header_table_size(std::numeric_limits<uint32_t>::max()),
110     encoder_header_table_size(-1),
111     padding(0),
112     max_concurrent_streams(100),
113     peer_max_concurrent_streams(100),
114     multiply(1),
115     timeout(0.),
116     window_bits(-1),
117     connection_window_bits(-1),
118     verbose(0),
119     port_override(0),
120     null_out(false),
121     remote_name(false),
122     get_assets(false),
123     stat(false),
124     upgrade(false),
125     continuation(false),
126     no_content_length(false),
127     no_dep(false),
128     hexdump(false),
129     no_push(false),
130     expect_continue(false),
131     verify_peer(true),
132     ktls(false),
133     no_rfc7540_pri(false) {
134   nghttp2_option_new(&http2_option);
135   nghttp2_option_set_peer_max_concurrent_streams(http2_option,
136                                                  peer_max_concurrent_streams);
137   nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC);
138   nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN);
139 }
140 
~Config()141 Config::~Config() { nghttp2_option_del(http2_option); }
142 
143 namespace {
144 Config config;
145 } // namespace
146 
147 namespace {
print_protocol_nego_error()148 void print_protocol_nego_error() {
149   std::cerr << "[ERROR] HTTP/2 protocol was not selected."
150             << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
151             << std::endl;
152 }
153 } // namespace
154 
155 namespace {
strip_fragment(const char * raw_uri)156 std::string strip_fragment(const char *raw_uri) {
157   const char *end;
158   for (end = raw_uri; *end && *end != '#'; ++end)
159     ;
160   size_t len = end - raw_uri;
161   return std::string(raw_uri, len);
162 }
163 } // namespace
164 
Request(const std::string & uri,const http_parser_url & u,const nghttp2_data_provider2 * data_prd,int64_t data_length,const nghttp2_priority_spec & pri_spec,int level)165 Request::Request(const std::string &uri, const http_parser_url &u,
166                  const nghttp2_data_provider2 *data_prd, int64_t data_length,
167                  const nghttp2_priority_spec &pri_spec, int level)
168   : uri(uri),
169     u(u),
170     pri_spec(pri_spec),
171     data_length(data_length),
172     data_offset(0),
173     response_len(0),
174     inflater(nullptr),
175     data_prd(data_prd),
176     header_buffer_size(0),
177     stream_id(-1),
178     status(0),
179     level(level),
180     expect_final_response(false) {
181   http2::init_hdidx(res_hdidx);
182   http2::init_hdidx(req_hdidx);
183 }
184 
~Request()185 Request::~Request() { nghttp2_gzip_inflate_del(inflater); }
186 
init_inflater()187 void Request::init_inflater() {
188   int rv;
189   // This is required with --disable-assert.
190   (void)rv;
191   rv = nghttp2_gzip_inflate_new(&inflater);
192   assert(rv == 0);
193 }
194 
get_real_scheme() const195 StringRef Request::get_real_scheme() const {
196   return config.scheme_override.empty()
197            ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA)
198            : StringRef{config.scheme_override};
199 }
200 
get_real_host() const201 StringRef Request::get_real_host() const {
202   return config.host_override.empty()
203            ? util::get_uri_field(uri.c_str(), u, UF_HOST)
204            : StringRef{config.host_override};
205 }
206 
get_real_port() const207 uint16_t Request::get_real_port() const {
208   auto scheme = get_real_scheme();
209   return config.host_override.empty() ? util::has_uri_field(u, UF_PORT) ? u.port
210                                         : scheme == "https"_sr          ? 443
211                                                                         : 80
212          : config.port_override == 0  ? scheme == "https"_sr ? 443 : 80
213                                       : config.port_override;
214 }
215 
init_html_parser()216 void Request::init_html_parser() {
217   // We crawl HTML using overridden scheme, host, and port.
218   auto scheme = get_real_scheme();
219   auto host = get_real_host();
220   auto port = get_real_port();
221   auto ipv6_lit =
222     std::find(std::begin(host), std::end(host), ':') != std::end(host);
223 
224   auto base_uri = std::string{scheme};
225   base_uri += "://";
226   if (ipv6_lit) {
227     base_uri += '[';
228   }
229   base_uri += host;
230   if (ipv6_lit) {
231     base_uri += ']';
232   }
233   if (!((scheme == "https"_sr && port == 443) ||
234         (scheme == "http"_sr && port == 80))) {
235     base_uri += ':';
236     base_uri += util::utos(port);
237   }
238   base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH);
239   if (util::has_uri_field(u, UF_QUERY)) {
240     base_uri += '?';
241     base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY);
242   }
243 
244   html_parser = std::make_unique<HtmlParser>(base_uri);
245 }
246 
update_html_parser(const uint8_t * data,size_t len,int fin)247 int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
248   if (!html_parser) {
249     return 0;
250   }
251   return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
252                                   fin);
253 }
254 
make_reqpath() const255 std::string Request::make_reqpath() const {
256   auto path = util::has_uri_field(u, UF_PATH)
257                 ? std::string{util::get_uri_field(uri.c_str(), u, UF_PATH)}
258                 : "/"s;
259   if (util::has_uri_field(u, UF_QUERY)) {
260     path += '?';
261     path.append(uri.c_str() + u.field_data[UF_QUERY].off,
262                 u.field_data[UF_QUERY].len);
263   }
264   return path;
265 }
266 
267 namespace {
268 // Perform special handling |host| if it is IPv6 literal and includes
269 // zone ID per RFC 6874.
decode_host(const StringRef & host)270 std::string decode_host(const StringRef &host) {
271   auto zone_start = std::find(std::begin(host), std::end(host), '%');
272   if (zone_start == std::end(host) ||
273       !util::ipv6_numeric_addr(
274         std::string(std::begin(host), zone_start).c_str())) {
275     return std::string{host};
276   }
277   // case: ::1%
278   if (zone_start + 1 == std::end(host)) {
279     return {host.data(), host.size() - 1};
280   }
281   // case: ::1%12 or ::1%1
282   if (zone_start + 3 >= std::end(host)) {
283     return std::string{host};
284   }
285   // If we see "%25", followed by more characters, then decode %25 as
286   // '%'.
287   auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
288                        ? zone_start + 3
289                        : zone_start + 1;
290   auto zone_id = util::percent_decode(zone_id_src, std::end(host));
291   auto res = std::string(std::begin(host), zone_start + 1);
292   res += zone_id;
293   return res;
294 }
295 } // namespace
296 
297 namespace {
resolve_dep(int res_type)298 nghttp2_priority_spec resolve_dep(int res_type) {
299   nghttp2_priority_spec pri_spec;
300 
301   if (config.no_dep) {
302     nghttp2_priority_spec_default_init(&pri_spec);
303 
304     return pri_spec;
305   }
306 
307   int32_t anchor_id;
308   int32_t weight;
309   switch (res_type) {
310   case REQ_CSS:
311   case REQ_JS:
312     anchor_id = anchors[ANCHOR_LEADERS].stream_id;
313     weight = 32;
314     break;
315   case REQ_UNBLOCK_JS:
316     anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
317     weight = 32;
318     break;
319   case REQ_IMG:
320     anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
321     weight = 12;
322     break;
323   default:
324     anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
325     weight = 32;
326   }
327 
328   nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
329   return pri_spec;
330 }
331 } // namespace
332 
is_ipv6_literal_addr() const333 bool Request::is_ipv6_literal_addr() const {
334   if (util::has_uri_field(u, UF_HOST)) {
335     return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
336                   u.field_data[UF_HOST].len);
337   } else {
338     return false;
339   }
340 }
341 
get_res_header(int32_t token)342 Headers::value_type *Request::get_res_header(int32_t token) {
343   auto idx = res_hdidx[token];
344   if (idx == -1) {
345     return nullptr;
346   }
347   return &res_nva[idx];
348 }
349 
get_req_header(int32_t token)350 Headers::value_type *Request::get_req_header(int32_t token) {
351   auto idx = req_hdidx[token];
352   if (idx == -1) {
353     return nullptr;
354   }
355   return &req_nva[idx];
356 }
357 
record_request_start_time()358 void Request::record_request_start_time() {
359   timing.state = RequestState::ON_REQUEST;
360   timing.request_start_time = get_time();
361 }
362 
record_response_start_time()363 void Request::record_response_start_time() {
364   timing.state = RequestState::ON_RESPONSE;
365   timing.response_start_time = get_time();
366 }
367 
record_response_end_time()368 void Request::record_response_end_time() {
369   timing.state = RequestState::ON_COMPLETE;
370   timing.response_end_time = get_time();
371 }
372 
373 namespace {
continue_timeout_cb(struct ev_loop * loop,ev_timer * w,int revents)374 void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
375   auto client = static_cast<HttpClient *>(ev_userdata(loop));
376   auto req = static_cast<Request *>(w->data);
377   int error;
378 
379   error = nghttp2_submit_data2(client->session, NGHTTP2_FLAG_END_STREAM,
380                                req->stream_id, req->data_prd);
381 
382   if (error) {
383     std::cerr << "[ERROR] nghttp2_submit_data2() returned error: "
384               << nghttp2_strerror(error) << std::endl;
385     nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE,
386                               req->stream_id, NGHTTP2_INTERNAL_ERROR);
387   }
388 
389   client->signal_write();
390 }
391 } // namespace
392 
ContinueTimer(struct ev_loop * loop,Request * req)393 ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) {
394   ev_timer_init(&timer, continue_timeout_cb, 1., 0.);
395   timer.data = req;
396 }
397 
~ContinueTimer()398 ContinueTimer::~ContinueTimer() { stop(); }
399 
start()400 void ContinueTimer::start() { ev_timer_start(loop, &timer); }
401 
stop()402 void ContinueTimer::stop() { ev_timer_stop(loop, &timer); }
403 
dispatch_continue()404 void ContinueTimer::dispatch_continue() {
405   // Only dispatch the timeout callback if it hasn't already been called.
406   if (ev_is_active(&timer)) {
407     ev_feed_event(loop, &timer, 0);
408   }
409 }
410 
411 namespace {
htp_msg_begincb(llhttp_t * htp)412 int htp_msg_begincb(llhttp_t *htp) {
413   if (config.verbose) {
414     print_timer();
415     std::cout << " HTTP Upgrade response" << std::endl;
416   }
417   return 0;
418 }
419 } // namespace
420 
421 namespace {
htp_msg_completecb(llhttp_t * htp)422 int htp_msg_completecb(llhttp_t *htp) {
423   auto client = static_cast<HttpClient *>(htp->data);
424   client->upgrade_response_status_code = htp->status_code;
425   client->upgrade_response_complete = true;
426   return 0;
427 }
428 } // namespace
429 
430 namespace {
431 constexpr llhttp_settings_t htp_hooks = {
432   htp_msg_begincb,    // llhttp_cb      on_message_begin;
433   nullptr,            // llhttp_data_cb on_url;
434   nullptr,            // llhttp_data_cb on_status;
435   nullptr,            // llhttp_data_cb on_method;
436   nullptr,            // llhttp_data_cb on_version;
437   nullptr,            // llhttp_data_cb on_header_field;
438   nullptr,            // llhttp_data_cb on_header_value;
439   nullptr,            // llhttp_data_cb on_chunk_extension_name;
440   nullptr,            // llhttp_data_cb on_chunk_extension_value;
441   nullptr,            // llhttp_cb      on_headers_complete;
442   nullptr,            // llhttp_data_cb on_body;
443   htp_msg_completecb, // llhttp_cb      on_message_complete;
444   nullptr,            // llhttp_cb      on_url_complete;
445   nullptr,            // llhttp_cb      on_status_complete;
446   nullptr,            // llhttp_cb      on_method_complete;
447   nullptr,            // llhttp_cb      on_version_complete;
448   nullptr,            // llhttp_cb      on_header_field_complete;
449   nullptr,            // llhttp_cb      on_header_value_complete;
450   nullptr,            // llhttp_cb      on_chunk_extension_name_complete;
451   nullptr,            // llhttp_cb      on_chunk_extension_value_complete;
452   nullptr,            // llhttp_cb      on_chunk_header;
453   nullptr,            // llhttp_cb      on_chunk_complete;
454   nullptr,            // llhttp_cb      on_reset;
455 };
456 } // namespace
457 
458 namespace {
submit_request(HttpClient * client,const Headers & headers,Request * req)459 int submit_request(HttpClient *client, const Headers &headers, Request *req) {
460   auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
461   auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
462                                {":path", req->make_reqpath()},
463                                {":scheme", std::string{scheme}},
464                                {":authority", client->hostport},
465                                {"accept", "*/*"},
466                                {"accept-encoding", "gzip, deflate"},
467                                {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
468   bool expect_continue = false;
469 
470   if (config.continuation) {
471     for (size_t i = 0; i < 6; ++i) {
472       build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
473                                  std::string(4_k, '-'));
474     }
475   }
476 
477   auto num_initial_headers = build_headers.size();
478 
479   if (req->data_prd) {
480     if (!config.no_content_length) {
481       build_headers.emplace_back("content-length",
482                                  util::utos(req->data_length));
483     }
484     if (config.expect_continue) {
485       expect_continue = true;
486       build_headers.emplace_back("expect", "100-continue");
487     }
488   }
489 
490   for (auto &kv : headers) {
491     size_t i;
492     for (i = 0; i < num_initial_headers; ++i) {
493       if (kv.name == build_headers[i].name) {
494         build_headers[i].value = kv.value;
495         break;
496       }
497     }
498     if (i < num_initial_headers) {
499       continue;
500     }
501 
502     build_headers.emplace_back(kv.name, kv.value, kv.no_index);
503   }
504 
505   auto nva = std::vector<nghttp2_nv>();
506   nva.reserve(build_headers.size());
507 
508   for (auto &kv : build_headers) {
509     nva.push_back(
510       http2::make_field_nv(kv.name, kv.value, http2::no_index(kv.no_index)));
511   }
512 
513   auto method = http2::get_header(build_headers, ":method");
514   assert(method);
515 
516   req->method = method->value;
517 
518   std::string trailer_names;
519   if (!config.trailer.empty()) {
520     trailer_names = config.trailer[0].name;
521     for (size_t i = 1; i < config.trailer.size(); ++i) {
522       trailer_names += ", ";
523       trailer_names += config.trailer[i].name;
524     }
525     nva.push_back(http2::make_field_v("trailer"_sr, trailer_names));
526   }
527 
528   int32_t stream_id;
529 
530   if (expect_continue) {
531     stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec,
532                                        nva.data(), nva.size(), req);
533   } else {
534     stream_id =
535       nghttp2_submit_request2(client->session, &req->pri_spec, nva.data(),
536                               nva.size(), req->data_prd, req);
537   }
538 
539   if (stream_id < 0) {
540     std::cerr << "[ERROR] nghttp2_submit_"
541               << (expect_continue ? "headers" : "request2")
542               << "() returned error: " << nghttp2_strerror(stream_id)
543               << std::endl;
544     return -1;
545   }
546 
547   req->stream_id = stream_id;
548   client->request_done(req);
549 
550   req->req_nva = std::move(build_headers);
551 
552   if (expect_continue) {
553     auto timer = std::make_unique<ContinueTimer>(client->loop, req);
554     req->continue_timer = std::move(timer);
555   }
556 
557   return 0;
558 }
559 } // namespace
560 
561 namespace {
readcb(struct ev_loop * loop,ev_io * w,int revents)562 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
563   auto client = static_cast<HttpClient *>(w->data);
564   if (client->do_read() != 0) {
565     client->disconnect();
566   }
567 }
568 } // namespace
569 
570 namespace {
writecb(struct ev_loop * loop,ev_io * w,int revents)571 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
572   auto client = static_cast<HttpClient *>(w->data);
573   auto rv = client->do_write();
574   if (rv == HttpClient::ERR_CONNECT_FAIL) {
575     client->connect_fail();
576     return;
577   }
578   if (rv != 0) {
579     client->disconnect();
580   }
581 }
582 } // namespace
583 
584 namespace {
timeoutcb(struct ev_loop * loop,ev_timer * w,int revents)585 void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
586   auto client = static_cast<HttpClient *>(w->data);
587   std::cerr << "[ERROR] Timeout" << std::endl;
588   client->disconnect();
589 }
590 } // namespace
591 
592 namespace {
settings_timeout_cb(struct ev_loop * loop,ev_timer * w,int revents)593 void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
594   auto client = static_cast<HttpClient *>(w->data);
595   ev_timer_stop(loop, w);
596 
597   nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
598 
599   client->signal_write();
600 }
601 } // namespace
602 
HttpClient(const nghttp2_session_callbacks * callbacks,struct ev_loop * loop,SSL_CTX * ssl_ctx)603 HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
604                        struct ev_loop *loop, SSL_CTX *ssl_ctx)
605   : wb(&mcpool),
606     session(nullptr),
607     callbacks(callbacks),
608     loop(loop),
609     ssl_ctx(ssl_ctx),
610     ssl(nullptr),
611     addrs(nullptr),
612     next_addr(nullptr),
613     cur_addr(nullptr),
614     complete(0),
615     success(0),
616     settings_payloadlen(0),
617     state(ClientState::IDLE),
618     upgrade_response_status_code(0),
619     fd(-1),
620     upgrade_response_complete(false) {
621   ev_io_init(&wev, writecb, 0, EV_WRITE);
622   ev_io_init(&rev, readcb, 0, EV_READ);
623 
624   wev.data = this;
625   rev.data = this;
626 
627   ev_timer_init(&wt, timeoutcb, 0., config.timeout);
628   ev_timer_init(&rt, timeoutcb, 0., config.timeout);
629 
630   wt.data = this;
631   rt.data = this;
632 
633   ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
634 
635   settings_timer.data = this;
636 }
637 
~HttpClient()638 HttpClient::~HttpClient() {
639   disconnect();
640 
641   if (addrs) {
642     freeaddrinfo(addrs);
643     addrs = nullptr;
644     next_addr = nullptr;
645   }
646 }
647 
need_upgrade() const648 bool HttpClient::need_upgrade() const {
649   return config.upgrade && scheme == "http";
650 }
651 
resolve_host(const std::string & host,uint16_t port)652 int HttpClient::resolve_host(const std::string &host, uint16_t port) {
653   int rv;
654   this->host = host;
655   addrinfo hints{};
656   hints.ai_family = AF_UNSPEC;
657   hints.ai_socktype = SOCK_STREAM;
658   hints.ai_protocol = 0;
659   hints.ai_flags = AI_ADDRCONFIG;
660   rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
661   if (rv != 0) {
662     std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
663               << std::endl;
664     return -1;
665   }
666   if (addrs == nullptr) {
667     std::cerr << "[ERROR] No address returned" << std::endl;
668     return -1;
669   }
670   next_addr = addrs;
671   return 0;
672 }
673 
674 namespace {
675 // Just returns 1 to continue handshake.
verify_cb(int preverify_ok,X509_STORE_CTX * ctx)676 int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
677 } // namespace
678 
initiate_connection()679 int HttpClient::initiate_connection() {
680   int rv;
681 
682   cur_addr = nullptr;
683   while (next_addr) {
684     cur_addr = next_addr;
685     next_addr = next_addr->ai_next;
686     fd = util::create_nonblock_socket(cur_addr->ai_family);
687     if (fd == -1) {
688       continue;
689     }
690 
691     if (ssl_ctx) {
692       // We are establishing TLS connection.
693       ssl = SSL_new(ssl_ctx);
694       if (!ssl) {
695         std::cerr << "[ERROR] SSL_new() failed: "
696                   << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
697         return -1;
698       }
699 
700       SSL_set_connect_state(ssl);
701 
702       // If the user overrode the :authority or host header, use that
703       // value for the SNI extension
704       const auto &host_string =
705         config.host_override.empty() ? host : config.host_override;
706 
707       auto param = SSL_get0_param(ssl);
708       X509_VERIFY_PARAM_set_hostflags(param, 0);
709       X509_VERIFY_PARAM_set1_host(param, host_string.c_str(),
710                                   host_string.size());
711       SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb);
712 
713       if (!util::numeric_host(host_string.c_str())) {
714         SSL_set_tlsext_host_name(ssl, host_string.c_str());
715       }
716     }
717 
718     rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
719 
720     if (rv != 0 && errno != EINPROGRESS) {
721       if (ssl) {
722         SSL_free(ssl);
723         ssl = nullptr;
724       }
725       close(fd);
726       fd = -1;
727       continue;
728     }
729     break;
730   }
731 
732   if (fd == -1) {
733     return -1;
734   }
735 
736   writefn = &HttpClient::connected;
737 
738   if (need_upgrade()) {
739     on_readfn = &HttpClient::on_upgrade_read;
740     on_writefn = &HttpClient::on_upgrade_connect;
741   } else {
742     on_readfn = &HttpClient::on_read;
743     on_writefn = &HttpClient::on_write;
744   }
745 
746   ev_io_set(&rev, fd, EV_READ);
747   ev_io_set(&wev, fd, EV_WRITE);
748 
749   ev_io_start(loop, &wev);
750 
751   ev_timer_again(loop, &wt);
752 
753   return 0;
754 }
755 
disconnect()756 void HttpClient::disconnect() {
757   state = ClientState::IDLE;
758 
759   for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) {
760     if ((*req)->continue_timer) {
761       (*req)->continue_timer->stop();
762     }
763   }
764 
765   ev_timer_stop(loop, &settings_timer);
766 
767   ev_timer_stop(loop, &rt);
768   ev_timer_stop(loop, &wt);
769 
770   ev_io_stop(loop, &rev);
771   ev_io_stop(loop, &wev);
772 
773   nghttp2_session_del(session);
774   session = nullptr;
775 
776   if (ssl) {
777     SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
778     ERR_clear_error();
779     SSL_shutdown(ssl);
780     SSL_free(ssl);
781     ssl = nullptr;
782   }
783 
784   if (fd != -1) {
785     shutdown(fd, SHUT_WR);
786     close(fd);
787     fd = -1;
788   }
789 }
790 
read_clear()791 int HttpClient::read_clear() {
792   ev_timer_again(loop, &rt);
793 
794   std::array<uint8_t, 8_k> buf;
795 
796   for (;;) {
797     ssize_t nread;
798     while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
799       ;
800     if (nread == -1) {
801       if (errno == EAGAIN || errno == EWOULDBLOCK) {
802         return 0;
803       }
804       return -1;
805     }
806 
807     if (nread == 0) {
808       return -1;
809     }
810 
811     if (on_readfn(*this, buf.data(), nread) != 0) {
812       return -1;
813     }
814   }
815 
816   return 0;
817 }
818 
write_clear()819 int HttpClient::write_clear() {
820   ev_timer_again(loop, &rt);
821 
822   std::array<struct iovec, 2> iov;
823 
824   for (;;) {
825     if (on_writefn(*this) != 0) {
826       return -1;
827     }
828 
829     auto iovcnt = wb.riovec(iov.data(), iov.size());
830 
831     if (iovcnt == 0) {
832       break;
833     }
834 
835     ssize_t nwrite;
836     while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
837       ;
838     if (nwrite == -1) {
839       if (errno == EAGAIN || errno == EWOULDBLOCK) {
840         ev_io_start(loop, &wev);
841         ev_timer_again(loop, &wt);
842         return 0;
843       }
844       return -1;
845     }
846 
847     wb.drain(nwrite);
848   }
849 
850   ev_io_stop(loop, &wev);
851   ev_timer_stop(loop, &wt);
852 
853   return 0;
854 }
855 
noop()856 int HttpClient::noop() { return 0; }
857 
connect_fail()858 void HttpClient::connect_fail() {
859   if (state == ClientState::IDLE) {
860     std::cerr << "[ERROR] Could not connect to the address "
861               << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
862               << std::endl;
863   }
864   auto cur_state = state;
865   disconnect();
866   if (cur_state == ClientState::IDLE) {
867     if (initiate_connection() == 0) {
868       std::cerr << "Trying next address "
869                 << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
870                 << std::endl;
871     }
872   }
873 }
874 
connected()875 int HttpClient::connected() {
876   if (!util::check_socket_connected(fd)) {
877     return ERR_CONNECT_FAIL;
878   }
879 
880   if (config.verbose) {
881     print_timer();
882     std::cout << " Connected" << std::endl;
883   }
884 
885   state = ClientState::CONNECTED;
886 
887   ev_io_start(loop, &rev);
888   ev_io_stop(loop, &wev);
889 
890   ev_timer_again(loop, &rt);
891   ev_timer_stop(loop, &wt);
892 
893   if (ssl) {
894     SSL_set_fd(ssl, fd);
895 
896     readfn = &HttpClient::tls_handshake;
897     writefn = &HttpClient::tls_handshake;
898 
899     return do_write();
900   }
901 
902   readfn = &HttpClient::read_clear;
903   writefn = &HttpClient::write_clear;
904 
905   if (need_upgrade()) {
906     htp = std::make_unique<llhttp_t>();
907     llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks);
908     htp->data = this;
909 
910     return do_write();
911   }
912 
913   if (connection_made() != 0) {
914     return -1;
915   }
916 
917   return 0;
918 }
919 
920 namespace {
populate_settings(nghttp2_settings_entry * iv)921 size_t populate_settings(nghttp2_settings_entry *iv) {
922   size_t niv = 2;
923 
924   iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
925   iv[0].value = config.max_concurrent_streams;
926 
927   iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
928   if (config.window_bits != -1) {
929     iv[1].value = (1 << config.window_bits) - 1;
930   } else {
931     iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
932   }
933 
934   if (config.header_table_size >= 0) {
935     if (config.min_header_table_size < config.header_table_size) {
936       iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
937       iv[niv].value = config.min_header_table_size;
938       ++niv;
939     }
940 
941     iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
942     iv[niv].value = config.header_table_size;
943     ++niv;
944   }
945 
946   if (config.no_push) {
947     iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
948     iv[niv].value = 0;
949     ++niv;
950   }
951 
952   if (config.no_rfc7540_pri) {
953     iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
954     iv[niv].value = 1;
955     ++niv;
956   }
957 
958   return niv;
959 }
960 } // namespace
961 
on_upgrade_connect()962 int HttpClient::on_upgrade_connect() {
963   nghttp2_ssize rv;
964   record_connect_end_time();
965   assert(!reqvec.empty());
966   std::array<nghttp2_settings_entry, 16> iv;
967   size_t niv = populate_settings(iv.data());
968   assert(settings_payload.size() >= 8 * niv);
969   rv = nghttp2_pack_settings_payload2(settings_payload.data(),
970                                       settings_payload.size(), iv.data(), niv);
971   if (rv < 0) {
972     return -1;
973   }
974   settings_payloadlen = rv;
975   auto token68 =
976     base64::encode(std::begin(settings_payload),
977                    std::begin(settings_payload) + settings_payloadlen);
978   util::to_token68(token68);
979 
980   std::string req;
981   if (reqvec[0]->data_prd) {
982     // If the request contains upload data, use OPTIONS * to upgrade
983     req = "OPTIONS *";
984   } else {
985     auto meth =
986       std::find_if(std::begin(config.headers), std::end(config.headers),
987                    [](const auto &kv) { return ":method"_sr == kv.name; });
988 
989     if (meth == std::end(config.headers)) {
990       req = "GET ";
991       reqvec[0]->method = "GET";
992     } else {
993       req = (*meth).value;
994       req += ' ';
995       reqvec[0]->method = (*meth).value;
996     }
997     req += reqvec[0]->make_reqpath();
998   }
999 
1000   auto headers = Headers{{"host", hostport},
1001                          {"connection", "Upgrade, HTTP2-Settings"},
1002                          {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
1003                          {"http2-settings", std::move(token68)},
1004                          {"accept", "*/*"},
1005                          {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
1006   auto initial_headerslen = headers.size();
1007 
1008   for (auto &kv : config.headers) {
1009     size_t i;
1010     if (kv.name.empty() || kv.name[0] == ':') {
1011       continue;
1012     }
1013     for (i = 0; i < initial_headerslen; ++i) {
1014       if (kv.name == headers[i].name) {
1015         headers[i].value = kv.value;
1016         break;
1017       }
1018     }
1019     if (i < initial_headerslen) {
1020       continue;
1021     }
1022     headers.emplace_back(kv.name, kv.value, kv.no_index);
1023   }
1024 
1025   req += " HTTP/1.1\r\n";
1026 
1027   for (auto &kv : headers) {
1028     req += kv.name;
1029     req += ": ";
1030     req += kv.value;
1031     req += "\r\n";
1032   }
1033   req += "\r\n";
1034 
1035   wb.append(req);
1036 
1037   if (config.verbose) {
1038     print_timer();
1039     std::cout << " HTTP Upgrade request\n" << req << std::endl;
1040   }
1041 
1042   if (!reqvec[0]->data_prd) {
1043     // record request time if this is a part of real request.
1044     reqvec[0]->record_request_start_time();
1045     reqvec[0]->req_nva = std::move(headers);
1046   }
1047 
1048   on_writefn = &HttpClient::noop;
1049 
1050   signal_write();
1051 
1052   return 0;
1053 }
1054 
on_upgrade_read(const uint8_t * data,size_t len)1055 int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
1056   int rv;
1057 
1058   auto htperr =
1059     llhttp_execute(htp.get(), reinterpret_cast<const char *>(data), len);
1060   auto nread = htperr == HPE_OK
1061                  ? len
1062                  : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
1063                                          llhttp_get_error_pos(htp.get())) -
1064                                        data);
1065 
1066   if (config.verbose) {
1067     std::cout.write(reinterpret_cast<const char *>(data), nread);
1068   }
1069 
1070   if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) {
1071     std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
1072               << "(" << llhttp_errno_name(htperr) << ") "
1073               << llhttp_get_error_reason(htp.get()) << std::endl;
1074     return -1;
1075   }
1076 
1077   if (!upgrade_response_complete) {
1078     return 0;
1079   }
1080 
1081   if (config.verbose) {
1082     std::cout << std::endl;
1083   }
1084 
1085   if (upgrade_response_status_code != 101) {
1086     std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
1087 
1088     return -1;
1089   }
1090 
1091   if (config.verbose) {
1092     print_timer();
1093     std::cout << " HTTP Upgrade success" << std::endl;
1094   }
1095 
1096   on_readfn = &HttpClient::on_read;
1097   on_writefn = &HttpClient::on_write;
1098 
1099   rv = connection_made();
1100   if (rv != 0) {
1101     return rv;
1102   }
1103 
1104   // Read remaining data in the buffer because it is not notified
1105   // callback anymore.
1106   rv = on_readfn(*this, data + nread, len - nread);
1107   if (rv != 0) {
1108     return rv;
1109   }
1110 
1111   return 0;
1112 }
1113 
do_read()1114 int HttpClient::do_read() { return readfn(*this); }
do_write()1115 int HttpClient::do_write() { return writefn(*this); }
1116 
connection_made()1117 int HttpClient::connection_made() {
1118   int rv;
1119 
1120   if (!need_upgrade()) {
1121     record_connect_end_time();
1122   }
1123 
1124   if (ssl) {
1125     // Check ALPN result
1126     const unsigned char *next_proto = nullptr;
1127     unsigned int next_proto_len;
1128 
1129     SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
1130     if (next_proto) {
1131       auto proto = StringRef{next_proto, next_proto_len};
1132       if (config.verbose) {
1133         std::cout << "The negotiated protocol: " << proto << std::endl;
1134       }
1135       if (!util::check_h2_is_selected(proto)) {
1136         next_proto = nullptr;
1137       }
1138     }
1139     if (!next_proto) {
1140       print_protocol_nego_error();
1141       return -1;
1142     }
1143   }
1144 
1145   rv =
1146     nghttp2_session_client_new2(&session, callbacks, this, config.http2_option);
1147 
1148   if (rv != 0) {
1149     return -1;
1150   }
1151   if (need_upgrade()) {
1152     // Adjust stream user-data depending on the existence of upload
1153     // data
1154     Request *stream_user_data = nullptr;
1155     if (!reqvec[0]->data_prd) {
1156       stream_user_data = reqvec[0].get();
1157     }
1158     // If HEAD is used, that is only when user specified it with -H
1159     // option.
1160     auto head_request = stream_user_data && stream_user_data->method == "HEAD";
1161     rv = nghttp2_session_upgrade2(session, settings_payload.data(),
1162                                   settings_payloadlen, head_request,
1163                                   stream_user_data);
1164     if (rv != 0) {
1165       std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
1166                 << nghttp2_strerror(rv) << std::endl;
1167       return -1;
1168     }
1169     if (stream_user_data) {
1170       stream_user_data->stream_id = 1;
1171       request_done(stream_user_data);
1172     }
1173   }
1174   // If upgrade succeeds, the SETTINGS value sent with
1175   // HTTP2-Settings header field has already been submitted to
1176   // session object.
1177   if (!need_upgrade()) {
1178     std::array<nghttp2_settings_entry, 16> iv;
1179     auto niv = populate_settings(iv.data());
1180     rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
1181     if (rv != 0) {
1182       return -1;
1183     }
1184   }
1185   if (!config.no_dep) {
1186     // Create anchor stream nodes
1187     nghttp2_priority_spec pri_spec;
1188 
1189     for (auto &anchor : anchors) {
1190       nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
1191                                  0);
1192       rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
1193                                    &pri_spec);
1194       if (rv != 0) {
1195         return -1;
1196       }
1197     }
1198 
1199     rv = nghttp2_session_set_next_stream_id(
1200       session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
1201     if (rv != 0) {
1202       return -1;
1203     }
1204 
1205     if (need_upgrade() && !reqvec[0]->data_prd) {
1206       // Amend the priority because we cannot send priority in
1207       // HTTP/1.1 Upgrade.
1208       auto &anchor = anchors[ANCHOR_FOLLOWERS];
1209       nghttp2_priority_spec_init(&pri_spec, anchor.stream_id,
1210                                  reqvec[0]->pri_spec.weight, 0);
1211 
1212       rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1213       if (rv != 0) {
1214         return -1;
1215       }
1216     }
1217   } else if (need_upgrade() && !reqvec[0]->data_prd &&
1218              reqvec[0]->pri_spec.weight != NGHTTP2_DEFAULT_WEIGHT) {
1219     // Amend the priority because we cannot send priority in HTTP/1.1
1220     // Upgrade.
1221     nghttp2_priority_spec pri_spec;
1222 
1223     nghttp2_priority_spec_init(&pri_spec, 0, reqvec[0]->pri_spec.weight, 0);
1224 
1225     rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1226     if (rv != 0) {
1227       return -1;
1228     }
1229   }
1230 
1231   ev_timer_again(loop, &settings_timer);
1232 
1233   if (config.connection_window_bits != -1) {
1234     int32_t window_size = (1 << config.connection_window_bits) - 1;
1235     rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
1236                                                window_size);
1237     if (rv != 0) {
1238       return -1;
1239     }
1240   }
1241   // Adjust first request depending on the existence of the upload
1242   // data
1243   for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
1244        i != std::end(reqvec); ++i) {
1245     if (submit_request(this, config.headers, (*i).get()) != 0) {
1246       return -1;
1247     }
1248   }
1249 
1250   signal_write();
1251 
1252   return 0;
1253 }
1254 
on_read(const uint8_t * data,size_t len)1255 int HttpClient::on_read(const uint8_t *data, size_t len) {
1256   if (config.hexdump) {
1257     util::hexdump(stdout, data, len);
1258   }
1259 
1260   auto rv = nghttp2_session_mem_recv2(session, data, len);
1261   if (rv < 0) {
1262     std::cerr << "[ERROR] nghttp2_session_mem_recv2() returned error: "
1263               << nghttp2_strerror(rv) << std::endl;
1264     return -1;
1265   }
1266 
1267   assert(static_cast<size_t>(rv) == len);
1268 
1269   if (nghttp2_session_want_read(session) == 0 &&
1270       nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1271     return -1;
1272   }
1273 
1274   signal_write();
1275 
1276   return 0;
1277 }
1278 
on_write()1279 int HttpClient::on_write() {
1280   for (;;) {
1281     if (wb.rleft() >= 16384) {
1282       return 0;
1283     }
1284 
1285     const uint8_t *data;
1286     auto len = nghttp2_session_mem_send2(session, &data);
1287     if (len < 0) {
1288       std::cerr << "[ERROR] nghttp2_session_send2() returned error: "
1289                 << nghttp2_strerror(len) << std::endl;
1290       return -1;
1291     }
1292 
1293     if (len == 0) {
1294       break;
1295     }
1296 
1297     wb.append(data, len);
1298   }
1299 
1300   if (nghttp2_session_want_read(session) == 0 &&
1301       nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1302     return -1;
1303   }
1304 
1305   return 0;
1306 }
1307 
tls_handshake()1308 int HttpClient::tls_handshake() {
1309   ev_timer_again(loop, &rt);
1310 
1311   ERR_clear_error();
1312 
1313   auto rv = SSL_do_handshake(ssl);
1314 
1315   if (rv <= 0) {
1316     auto err = SSL_get_error(ssl, rv);
1317     switch (err) {
1318     case SSL_ERROR_WANT_READ:
1319       ev_io_stop(loop, &wev);
1320       ev_timer_stop(loop, &wt);
1321       return 0;
1322     case SSL_ERROR_WANT_WRITE:
1323       ev_io_start(loop, &wev);
1324       ev_timer_again(loop, &wt);
1325       return 0;
1326     default:
1327       return -1;
1328     }
1329   }
1330 
1331   ev_io_stop(loop, &wev);
1332   ev_timer_stop(loop, &wt);
1333 
1334   readfn = &HttpClient::read_tls;
1335   writefn = &HttpClient::write_tls;
1336 
1337   if (config.verify_peer) {
1338     auto verify_res = SSL_get_verify_result(ssl);
1339     if (verify_res != X509_V_OK) {
1340       std::cerr << "[WARNING] Certificate verification failed: "
1341                 << X509_verify_cert_error_string(verify_res) << std::endl;
1342     }
1343   }
1344 
1345   if (connection_made() != 0) {
1346     return -1;
1347   }
1348 
1349   return 0;
1350 }
1351 
read_tls()1352 int HttpClient::read_tls() {
1353   ev_timer_again(loop, &rt);
1354 
1355   ERR_clear_error();
1356 
1357   std::array<uint8_t, 8_k> buf;
1358   for (;;) {
1359     auto rv = SSL_read(ssl, buf.data(), buf.size());
1360 
1361     if (rv <= 0) {
1362       auto err = SSL_get_error(ssl, rv);
1363       switch (err) {
1364       case SSL_ERROR_WANT_READ:
1365         return 0;
1366       case SSL_ERROR_WANT_WRITE:
1367         // renegotiation started
1368         return -1;
1369       default:
1370         return -1;
1371       }
1372     }
1373 
1374     if (on_readfn(*this, buf.data(), rv) != 0) {
1375       return -1;
1376     }
1377   }
1378 }
1379 
write_tls()1380 int HttpClient::write_tls() {
1381   ev_timer_again(loop, &rt);
1382 
1383   ERR_clear_error();
1384 
1385   struct iovec iov;
1386 
1387   for (;;) {
1388     if (on_writefn(*this) != 0) {
1389       return -1;
1390     }
1391 
1392     auto iovcnt = wb.riovec(&iov, 1);
1393 
1394     if (iovcnt == 0) {
1395       break;
1396     }
1397 
1398     auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
1399 
1400     if (rv <= 0) {
1401       auto err = SSL_get_error(ssl, rv);
1402       switch (err) {
1403       case SSL_ERROR_WANT_READ:
1404         // renegotiation started
1405         return -1;
1406       case SSL_ERROR_WANT_WRITE:
1407         ev_io_start(loop, &wev);
1408         ev_timer_again(loop, &wt);
1409         return 0;
1410       default:
1411         return -1;
1412       }
1413     }
1414 
1415     wb.drain(rv);
1416   }
1417 
1418   ev_io_stop(loop, &wev);
1419   ev_timer_stop(loop, &wt);
1420 
1421   return 0;
1422 }
1423 
signal_write()1424 void HttpClient::signal_write() { ev_io_start(loop, &wev); }
1425 
all_requests_processed() const1426 bool HttpClient::all_requests_processed() const {
1427   return complete == reqvec.size();
1428 }
1429 
update_hostport()1430 void HttpClient::update_hostport() {
1431   if (reqvec.empty()) {
1432     return;
1433   }
1434   scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
1435   std::stringstream ss;
1436   if (reqvec[0]->is_ipv6_literal_addr()) {
1437     // we may have zone ID, which must start with "%25", or "%".  RFC
1438     // 6874 defines "%25" only, and just "%" is allowed for just
1439     // convenience to end-user input.
1440     auto host =
1441       util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1442     auto end = std::find(std::begin(host), std::end(host), '%');
1443     ss << "[";
1444     ss.write(host.data(), end - std::begin(host));
1445     ss << "]";
1446   } else {
1447     util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1448   }
1449   if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
1450       reqvec[0]->u.port !=
1451         util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
1452     ss << ":" << reqvec[0]->u.port;
1453   }
1454   hostport = ss.str();
1455 }
1456 
add_request(const std::string & uri,const nghttp2_data_provider2 * data_prd,int64_t data_length,const nghttp2_priority_spec & pri_spec,int level)1457 bool HttpClient::add_request(const std::string &uri,
1458                              const nghttp2_data_provider2 *data_prd,
1459                              int64_t data_length,
1460                              const nghttp2_priority_spec &pri_spec, int level) {
1461   http_parser_url u{};
1462   if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1463     return false;
1464   }
1465   if (path_cache.count(uri)) {
1466     return false;
1467   }
1468 
1469   if (config.multiply == 1) {
1470     path_cache.insert(uri);
1471   }
1472 
1473   reqvec.push_back(
1474     std::make_unique<Request>(uri, u, data_prd, data_length, pri_spec, level));
1475   return true;
1476 }
1477 
record_start_time()1478 void HttpClient::record_start_time() {
1479   timing.system_start_time = std::chrono::system_clock::now();
1480   timing.start_time = get_time();
1481 }
1482 
record_domain_lookup_end_time()1483 void HttpClient::record_domain_lookup_end_time() {
1484   timing.domain_lookup_end_time = get_time();
1485 }
1486 
record_connect_end_time()1487 void HttpClient::record_connect_end_time() {
1488   timing.connect_end_time = get_time();
1489 }
1490 
request_done(Request * req)1491 void HttpClient::request_done(Request *req) {
1492   if (req->stream_id % 2 == 0) {
1493     return;
1494   }
1495 }
1496 
1497 #ifdef HAVE_JANSSON
output_har(FILE * outfile)1498 void HttpClient::output_har(FILE *outfile) {
1499   static auto PAGE_ID = "page_0";
1500 
1501   auto root = json_object();
1502   auto log = json_object();
1503   json_object_set_new(root, "log", log);
1504   json_object_set_new(log, "version", json_string("1.2"));
1505 
1506   auto creator = json_object();
1507   json_object_set_new(log, "creator", creator);
1508 
1509   json_object_set_new(creator, "name", json_string("nghttp"));
1510   json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
1511 
1512   auto pages = json_array();
1513   json_object_set_new(log, "pages", pages);
1514 
1515   auto page = json_object();
1516   json_array_append_new(pages, page);
1517 
1518   json_object_set_new(
1519     page, "startedDateTime",
1520     json_string(util::format_iso8601(timing.system_start_time).c_str()));
1521   json_object_set_new(page, "id", json_string(PAGE_ID));
1522   json_object_set_new(page, "title", json_string(""));
1523 
1524   json_object_set_new(page, "pageTimings", json_object());
1525 
1526   auto entries = json_array();
1527   json_object_set_new(log, "entries", entries);
1528 
1529   auto dns_delta = std::chrono::duration_cast<std::chrono::microseconds>(
1530                      timing.domain_lookup_end_time - timing.start_time)
1531                      .count() /
1532                    1000.0;
1533   auto connect_delta =
1534     std::chrono::duration_cast<std::chrono::microseconds>(
1535       timing.connect_end_time - timing.domain_lookup_end_time)
1536       .count() /
1537     1000.0;
1538 
1539   for (size_t i = 0; i < reqvec.size(); ++i) {
1540     auto &req = reqvec[i];
1541 
1542     if (req->timing.state != RequestState::ON_COMPLETE) {
1543       continue;
1544     }
1545 
1546     auto entry = json_object();
1547     json_array_append_new(entries, entry);
1548 
1549     auto &req_timing = req->timing;
1550     auto request_time =
1551       (i == 0)
1552         ? timing.system_start_time
1553         : timing.system_start_time +
1554             std::chrono::duration_cast<std::chrono::system_clock::duration>(
1555               req_timing.request_start_time - timing.start_time);
1556 
1557     auto wait_delta =
1558       std::chrono::duration_cast<std::chrono::microseconds>(
1559         req_timing.response_start_time - req_timing.request_start_time)
1560         .count() /
1561       1000.0;
1562     auto receive_delta =
1563       std::chrono::duration_cast<std::chrono::microseconds>(
1564         req_timing.response_end_time - req_timing.response_start_time)
1565         .count() /
1566       1000.0;
1567 
1568     auto time_sum =
1569       std::chrono::duration_cast<std::chrono::microseconds>(
1570         (i == 0)
1571           ? (req_timing.response_end_time - timing.start_time)
1572           : (req_timing.response_end_time - req_timing.request_start_time))
1573         .count() /
1574       1000.0;
1575 
1576     json_object_set_new(
1577       entry, "startedDateTime",
1578       json_string(util::format_iso8601(request_time).c_str()));
1579     json_object_set_new(entry, "time", json_real(time_sum));
1580 
1581     auto pushed = req->stream_id % 2 == 0;
1582 
1583     json_object_set_new(entry, "comment",
1584                         json_string(pushed ? "Pushed Object" : ""));
1585 
1586     auto request = json_object();
1587     json_object_set_new(entry, "request", request);
1588 
1589     auto req_headers = json_array();
1590     json_object_set_new(request, "headers", req_headers);
1591 
1592     for (auto &nv : req->req_nva) {
1593       auto hd = json_object();
1594       json_array_append_new(req_headers, hd);
1595 
1596       json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1597       json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1598     }
1599 
1600     json_object_set_new(request, "method", json_string(req->method.c_str()));
1601     json_object_set_new(request, "url", json_string(req->uri.c_str()));
1602     json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
1603     json_object_set_new(request, "cookies", json_array());
1604     json_object_set_new(request, "queryString", json_array());
1605     json_object_set_new(request, "headersSize", json_integer(-1));
1606     json_object_set_new(request, "bodySize", json_integer(-1));
1607 
1608     auto response = json_object();
1609     json_object_set_new(entry, "response", response);
1610 
1611     auto res_headers = json_array();
1612     json_object_set_new(response, "headers", res_headers);
1613 
1614     for (auto &nv : req->res_nva) {
1615       auto hd = json_object();
1616       json_array_append_new(res_headers, hd);
1617 
1618       json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1619       json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1620     }
1621 
1622     json_object_set_new(response, "status", json_integer(req->status));
1623     json_object_set_new(response, "statusText", json_string(""));
1624     json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
1625     json_object_set_new(response, "cookies", json_array());
1626 
1627     auto content = json_object();
1628     json_object_set_new(response, "content", content);
1629 
1630     json_object_set_new(content, "size", json_integer(req->response_len));
1631 
1632     auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
1633 
1634     const char *content_type = "";
1635     if (content_type_ptr) {
1636       content_type = content_type_ptr->value.c_str();
1637     }
1638 
1639     json_object_set_new(content, "mimeType", json_string(content_type));
1640 
1641     json_object_set_new(response, "redirectURL", json_string(""));
1642     json_object_set_new(response, "headersSize", json_integer(-1));
1643     json_object_set_new(response, "bodySize", json_integer(-1));
1644     json_object_set_new(entry, "cache", json_object());
1645 
1646     auto timings = json_object();
1647     json_object_set_new(entry, "timings", timings);
1648 
1649     auto dns_timing = (i == 0) ? dns_delta : 0;
1650     auto connect_timing = (i == 0) ? connect_delta : 0;
1651 
1652     json_object_set_new(timings, "dns", json_real(dns_timing));
1653     json_object_set_new(timings, "connect", json_real(connect_timing));
1654 
1655     json_object_set_new(timings, "blocked", json_real(0.0));
1656     json_object_set_new(timings, "send", json_real(0.0));
1657     json_object_set_new(timings, "wait", json_real(wait_delta));
1658     json_object_set_new(timings, "receive", json_real(receive_delta));
1659 
1660     json_object_set_new(entry, "pageref", json_string(PAGE_ID));
1661     json_object_set_new(entry, "connection",
1662                         json_string(util::utos(req->stream_id).c_str()));
1663   }
1664 
1665   json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
1666   json_decref(root);
1667 }
1668 #endif // HAVE_JANSSON
1669 
1670 namespace {
update_html_parser(HttpClient * client,Request * req,const uint8_t * data,size_t len,int fin)1671 void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
1672                         size_t len, int fin) {
1673   if (!req->html_parser) {
1674     return;
1675   }
1676   req->update_html_parser(data, len, fin);
1677 
1678   auto scheme = req->get_real_scheme();
1679   auto host = req->get_real_host();
1680   auto port = req->get_real_port();
1681 
1682   for (auto &p : req->html_parser->get_links()) {
1683     auto uri = strip_fragment(p.first.c_str());
1684     auto res_type = p.second;
1685 
1686     http_parser_url u{};
1687     if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1688       continue;
1689     }
1690 
1691     if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) ||
1692         !util::fieldeq(uri.c_str(), u, UF_HOST, host)) {
1693       continue;
1694     }
1695 
1696     auto link_port = util::has_uri_field(u, UF_PORT) ? u.port
1697                      : scheme == "https"_sr          ? 443
1698                                                      : 80;
1699 
1700     if (port != link_port) {
1701       continue;
1702     }
1703 
1704     // No POST data for assets
1705     auto pri_spec = resolve_dep(res_type);
1706 
1707     if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
1708       submit_request(client, config.headers, client->reqvec.back().get());
1709     }
1710   }
1711   req->html_parser->clear_links();
1712 }
1713 } // namespace
1714 
1715 namespace {
get_client(void * user_data)1716 HttpClient *get_client(void *user_data) {
1717   return static_cast<HttpClient *>(user_data);
1718 }
1719 } // namespace
1720 
1721 namespace {
on_data_chunk_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)1722 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1723                                 int32_t stream_id, const uint8_t *data,
1724                                 size_t len, void *user_data) {
1725   auto client = get_client(user_data);
1726   auto req = static_cast<Request *>(
1727     nghttp2_session_get_stream_user_data(session, stream_id));
1728 
1729   if (!req) {
1730     return 0;
1731   }
1732 
1733   if (config.verbose >= 2) {
1734     verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1735                                         user_data);
1736   }
1737 
1738   req->response_len += len;
1739 
1740   if (req->inflater) {
1741     while (len > 0) {
1742       const size_t MAX_OUTLEN = 4_k;
1743       std::array<uint8_t, MAX_OUTLEN> out;
1744       size_t outlen = MAX_OUTLEN;
1745       size_t tlen = len;
1746       int rv =
1747         nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1748       if (rv != 0) {
1749         nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1750                                   NGHTTP2_INTERNAL_ERROR);
1751         break;
1752       }
1753 
1754       if (!config.null_out) {
1755         std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1756       }
1757 
1758       update_html_parser(client, req, out.data(), outlen, 0);
1759       data += tlen;
1760       len -= tlen;
1761     }
1762 
1763     return 0;
1764   }
1765 
1766   if (!config.null_out) {
1767     std::cout.write(reinterpret_cast<const char *>(data), len);
1768   }
1769 
1770   update_html_parser(client, req, data, len, 0);
1771 
1772   return 0;
1773 }
1774 } // namespace
1775 
1776 namespace {
select_padding_callback(nghttp2_session * session,const nghttp2_frame * frame,size_t max_payload,void * user_data)1777 nghttp2_ssize select_padding_callback(nghttp2_session *session,
1778                                       const nghttp2_frame *frame,
1779                                       size_t max_payload, void *user_data) {
1780   return std::min(max_payload, frame->hd.length + config.padding);
1781 }
1782 } // namespace
1783 
1784 namespace {
check_response_header(nghttp2_session * session,Request * req)1785 void check_response_header(nghttp2_session *session, Request *req) {
1786   bool gzip = false;
1787 
1788   req->expect_final_response = false;
1789 
1790   auto status_hd = req->get_res_header(http2::HD__STATUS);
1791 
1792   if (!status_hd) {
1793     nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1794                               NGHTTP2_PROTOCOL_ERROR);
1795     return;
1796   }
1797 
1798   auto status = http2::parse_http_status_code(StringRef{status_hd->value});
1799   if (status == -1) {
1800     nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1801                               NGHTTP2_PROTOCOL_ERROR);
1802     return;
1803   }
1804 
1805   req->status = status;
1806 
1807   for (auto &nv : req->res_nva) {
1808     if ("content-encoding" == nv.name) {
1809       gzip = util::strieq("gzip"_sr, nv.value) ||
1810              util::strieq("deflate"_sr, nv.value);
1811       continue;
1812     }
1813   }
1814 
1815   if (req->status / 100 == 1) {
1816     if (req->continue_timer && (req->status == 100)) {
1817       // If the request is waiting for a 100 Continue, complete the handshake.
1818       req->continue_timer->dispatch_continue();
1819     }
1820 
1821     req->expect_final_response = true;
1822     req->status = 0;
1823     req->res_nva.clear();
1824     http2::init_hdidx(req->res_hdidx);
1825     return;
1826   } else if (req->continue_timer) {
1827     // A final response stops any pending Expect/Continue handshake.
1828     req->continue_timer->stop();
1829   }
1830 
1831   if (gzip) {
1832     if (!req->inflater) {
1833       req->init_inflater();
1834     }
1835   }
1836   if (config.get_assets && req->level == 0) {
1837     if (!req->html_parser) {
1838       req->init_html_parser();
1839     }
1840   }
1841 }
1842 } // namespace
1843 
1844 namespace {
on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)1845 int on_begin_headers_callback(nghttp2_session *session,
1846                               const nghttp2_frame *frame, void *user_data) {
1847   auto client = get_client(user_data);
1848   switch (frame->hd.type) {
1849   case NGHTTP2_HEADERS: {
1850     auto req = static_cast<Request *>(
1851       nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1852     if (!req) {
1853       break;
1854     }
1855 
1856     switch (frame->headers.cat) {
1857     case NGHTTP2_HCAT_RESPONSE:
1858     case NGHTTP2_HCAT_PUSH_RESPONSE:
1859       req->record_response_start_time();
1860       break;
1861     default:
1862       break;
1863     }
1864 
1865     break;
1866   }
1867   case NGHTTP2_PUSH_PROMISE: {
1868     auto stream_id = frame->push_promise.promised_stream_id;
1869     http_parser_url u{};
1870     // TODO Set pri and level
1871     nghttp2_priority_spec pri_spec;
1872 
1873     nghttp2_priority_spec_default_init(&pri_spec);
1874 
1875     auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
1876     req->stream_id = stream_id;
1877 
1878     nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1879 
1880     client->request_done(req.get());
1881     req->record_request_start_time();
1882     client->reqvec.push_back(std::move(req));
1883 
1884     break;
1885   }
1886   }
1887   return 0;
1888 }
1889 } // namespace
1890 
1891 namespace {
on_header_callback(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * user_data)1892 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1893                        const uint8_t *name, size_t namelen,
1894                        const uint8_t *value, size_t valuelen, uint8_t flags,
1895                        void *user_data) {
1896   if (config.verbose) {
1897     verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1898                                flags, user_data);
1899   }
1900 
1901   switch (frame->hd.type) {
1902   case NGHTTP2_HEADERS: {
1903     auto req = static_cast<Request *>(
1904       nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1905 
1906     if (!req) {
1907       break;
1908     }
1909 
1910     /* ignore trailer header */
1911     if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
1912         !req->expect_final_response) {
1913       break;
1914     }
1915 
1916     if (req->header_buffer_size + namelen + valuelen > 64_k) {
1917       nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1918                                 NGHTTP2_INTERNAL_ERROR);
1919       return 0;
1920     }
1921 
1922     req->header_buffer_size += namelen + valuelen;
1923 
1924     auto nameref = StringRef{name, namelen};
1925     auto valueref = StringRef{value, valuelen};
1926     auto token = http2::lookup_token(nameref);
1927 
1928     http2::index_header(req->res_hdidx, token, req->res_nva.size());
1929     http2::add_header(req->res_nva, nameref, valueref,
1930                       flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1931     break;
1932   }
1933   case NGHTTP2_PUSH_PROMISE: {
1934     auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1935       session, frame->push_promise.promised_stream_id));
1936 
1937     if (!req) {
1938       break;
1939     }
1940 
1941     if (req->header_buffer_size + namelen + valuelen > 64_k) {
1942       nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1943                                 frame->push_promise.promised_stream_id,
1944                                 NGHTTP2_INTERNAL_ERROR);
1945       return 0;
1946     }
1947 
1948     req->header_buffer_size += namelen + valuelen;
1949 
1950     auto nameref = StringRef{name, namelen};
1951     auto valueref = StringRef{value, valuelen};
1952     auto token = http2::lookup_token(nameref);
1953 
1954     http2::index_header(req->req_hdidx, token, req->req_nva.size());
1955     http2::add_header(req->req_nva, nameref, valueref,
1956                       flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1957     break;
1958   }
1959   }
1960   return 0;
1961 }
1962 } // namespace
1963 
1964 namespace {
on_frame_recv_callback2(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)1965 int on_frame_recv_callback2(nghttp2_session *session,
1966                             const nghttp2_frame *frame, void *user_data) {
1967   int rv = 0;
1968 
1969   if (config.verbose) {
1970     verbose_on_frame_recv_callback(session, frame, user_data);
1971   }
1972 
1973   auto client = get_client(user_data);
1974   switch (frame->hd.type) {
1975   case NGHTTP2_DATA: {
1976     auto req = static_cast<Request *>(
1977       nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1978     if (!req) {
1979       return 0;
1980       ;
1981     }
1982 
1983     if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1984       req->record_response_end_time();
1985       ++client->success;
1986     }
1987 
1988     break;
1989   }
1990   case NGHTTP2_HEADERS: {
1991     auto req = static_cast<Request *>(
1992       nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1993     // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1994     // req is nullptr.
1995     if (!req) {
1996       return 0;
1997       ;
1998     }
1999 
2000     switch (frame->headers.cat) {
2001     case NGHTTP2_HCAT_RESPONSE:
2002     case NGHTTP2_HCAT_PUSH_RESPONSE:
2003       check_response_header(session, req);
2004       break;
2005     case NGHTTP2_HCAT_HEADERS:
2006       if (req->expect_final_response) {
2007         check_response_header(session, req);
2008         break;
2009       }
2010       if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
2011         nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2012                                   frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
2013         return 0;
2014       }
2015       break;
2016     default:
2017       assert(0);
2018     }
2019 
2020     if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
2021       req->record_response_end_time();
2022       ++client->success;
2023     }
2024 
2025     break;
2026   }
2027   case NGHTTP2_SETTINGS:
2028     if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
2029       break;
2030     }
2031     ev_timer_stop(client->loop, &client->settings_timer);
2032     break;
2033   case NGHTTP2_PUSH_PROMISE: {
2034     auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
2035       session, frame->push_promise.promised_stream_id));
2036     if (!req) {
2037       break;
2038     }
2039 
2040     // Reset for response header field reception
2041     req->header_buffer_size = 0;
2042 
2043     auto scheme = req->get_req_header(http2::HD__SCHEME);
2044     auto authority = req->get_req_header(http2::HD__AUTHORITY);
2045     auto path = req->get_req_header(http2::HD__PATH);
2046 
2047     if (!authority) {
2048       authority = req->get_req_header(http2::HD_HOST);
2049     }
2050 
2051     // libnghttp2 guarantees :scheme, :method, :path and (:authority |
2052     // host) exist and non-empty.
2053     if (path->value[0] != '/') {
2054       nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2055                                 frame->push_promise.promised_stream_id,
2056                                 NGHTTP2_PROTOCOL_ERROR);
2057       break;
2058     }
2059     std::string uri = scheme->value;
2060     uri += "://";
2061     uri += authority->value;
2062     uri += path->value;
2063     http_parser_url u{};
2064     if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2065       nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2066                                 frame->push_promise.promised_stream_id,
2067                                 NGHTTP2_PROTOCOL_ERROR);
2068       break;
2069     }
2070     req->uri = uri;
2071     req->u = u;
2072 
2073     if (client->path_cache.count(uri)) {
2074       nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2075                                 frame->push_promise.promised_stream_id,
2076                                 NGHTTP2_CANCEL);
2077       break;
2078     }
2079 
2080     if (config.multiply == 1) {
2081       client->path_cache.insert(uri);
2082     }
2083 
2084     break;
2085   }
2086   }
2087   return rv;
2088 }
2089 } // namespace
2090 
2091 namespace {
before_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)2092 int before_frame_send_callback(nghttp2_session *session,
2093                                const nghttp2_frame *frame, void *user_data) {
2094   if (frame->hd.type != NGHTTP2_HEADERS ||
2095       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2096     return 0;
2097   }
2098   auto req = static_cast<Request *>(
2099     nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2100   assert(req);
2101   req->record_request_start_time();
2102   return 0;
2103 }
2104 
2105 } // namespace
2106 
2107 namespace {
on_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)2108 int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
2109                            void *user_data) {
2110   if (config.verbose) {
2111     verbose_on_frame_send_callback(session, frame, user_data);
2112   }
2113 
2114   if (frame->hd.type != NGHTTP2_HEADERS ||
2115       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2116     return 0;
2117   }
2118 
2119   auto req = static_cast<Request *>(
2120     nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2121   if (!req) {
2122     return 0;
2123   }
2124 
2125   // If this request is using Expect/Continue, start its ContinueTimer.
2126   if (req->continue_timer) {
2127     req->continue_timer->start();
2128   }
2129 
2130   return 0;
2131 }
2132 } // namespace
2133 
2134 namespace {
on_frame_not_send_callback(nghttp2_session * session,const nghttp2_frame * frame,int lib_error_code,void * user_data)2135 int on_frame_not_send_callback(nghttp2_session *session,
2136                                const nghttp2_frame *frame, int lib_error_code,
2137                                void *user_data) {
2138   if (frame->hd.type != NGHTTP2_HEADERS ||
2139       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2140     return 0;
2141   }
2142 
2143   auto req = static_cast<Request *>(
2144     nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2145   if (!req) {
2146     return 0;
2147   }
2148 
2149   std::cerr << "[ERROR] request " << req->uri
2150             << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
2151 
2152   return 0;
2153 }
2154 } // namespace
2155 
2156 namespace {
on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)2157 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
2158                              uint32_t error_code, void *user_data) {
2159   auto client = get_client(user_data);
2160   auto req = static_cast<Request *>(
2161     nghttp2_session_get_stream_user_data(session, stream_id));
2162 
2163   if (!req) {
2164     return 0;
2165   }
2166 
2167   // If this request is using Expect/Continue, stop its ContinueTimer.
2168   if (req->continue_timer) {
2169     req->continue_timer->stop();
2170   }
2171 
2172   update_html_parser(client, req, nullptr, 0, 1);
2173   ++client->complete;
2174 
2175   if (client->all_requests_processed()) {
2176     nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
2177   }
2178 
2179   return 0;
2180 }
2181 } // namespace
2182 
2183 struct RequestResult {
2184   std::chrono::microseconds time;
2185 };
2186 
2187 namespace {
print_stats(const HttpClient & client)2188 void print_stats(const HttpClient &client) {
2189   std::cout << "***** Statistics *****" << std::endl;
2190 
2191   std::vector<Request *> reqs;
2192   reqs.reserve(client.reqvec.size());
2193   for (const auto &req : client.reqvec) {
2194     if (req->timing.state == RequestState::ON_COMPLETE) {
2195       reqs.push_back(req.get());
2196     }
2197   }
2198 
2199   std::sort(std::begin(reqs), std::end(reqs),
2200             [](const Request *lhs, const Request *rhs) {
2201               const auto &ltiming = lhs->timing;
2202               const auto &rtiming = rhs->timing;
2203               return ltiming.response_end_time < rtiming.response_end_time ||
2204                      (ltiming.response_end_time == rtiming.response_end_time &&
2205                       ltiming.request_start_time < rtiming.request_start_time);
2206             });
2207 
2208   std::cout << R"(
2209 Request timing:
2210   responseEnd: the  time  when  last  byte of  response  was  received
2211                relative to connectEnd
2212  requestStart: the time  just before  first byte  of request  was sent
2213                relative  to connectEnd.   If  '*' is  shown, this  was
2214                pushed by server.
2215       process: responseEnd - requestStart
2216          code: HTTP status code
2217          size: number  of  bytes  received as  response  body  without
2218                inflation.
2219           URI: request URI
2220 
2221 see http://www.w3.org/TR/resource-timing/#processing-model
2222 
2223 sorted by 'complete'
2224 
2225 id  responseEnd requestStart  process code size request path)"
2226             << std::endl;
2227 
2228   const auto &base = client.timing.connect_end_time;
2229   for (const auto &req : reqs) {
2230     auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
2231       req->timing.response_end_time - base);
2232     auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
2233       req->timing.request_start_time - base);
2234     auto total = std::chrono::duration_cast<std::chrono::microseconds>(
2235       req->timing.response_end_time - req->timing.request_start_time);
2236     auto pushed = req->stream_id % 2 == 0;
2237 
2238     std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
2239               << ("+" + util::format_duration(response_end)) << " "
2240               << (pushed ? "*" : " ") << std::setw(11)
2241               << ("+" + util::format_duration(request_start)) << " "
2242               << std::setw(8) << util::format_duration(total) << " "
2243               << std::setw(4) << req->status << " " << std::setw(4)
2244               << util::utos_unit(req->response_len) << " "
2245               << req->make_reqpath() << std::endl;
2246   }
2247 }
2248 } // namespace
2249 
2250 namespace {
communicate(const std::string & scheme,const std::string & host,uint16_t port,std::vector<std::tuple<std::string,nghttp2_data_provider2 *,int64_t,int32_t>> requests,const nghttp2_session_callbacks * callbacks)2251 int communicate(
2252   const std::string &scheme, const std::string &host, uint16_t port,
2253   std::vector<
2254     std::tuple<std::string, nghttp2_data_provider2 *, int64_t, int32_t>>
2255     requests,
2256   const nghttp2_session_callbacks *callbacks) {
2257   int result = 0;
2258   auto loop = EV_DEFAULT;
2259   SSL_CTX *ssl_ctx = nullptr;
2260   if (scheme == "https") {
2261     ssl_ctx = SSL_CTX_new(TLS_client_method());
2262     if (!ssl_ctx) {
2263       std::cerr << "[ERROR] Failed to create SSL_CTX: "
2264                 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2265       result = -1;
2266       goto fin;
2267     }
2268 
2269     auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2270                     SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2271                     SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2272 
2273 #ifdef SSL_OP_ENABLE_KTLS
2274     if (config.ktls) {
2275       ssl_opts |= SSL_OP_ENABLE_KTLS;
2276     }
2277 #endif // SSL_OP_ENABLE_KTLS
2278 
2279     SSL_CTX_set_options(ssl_ctx, ssl_opts);
2280     SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2281     SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2282 
2283     if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
2284       std::cerr << "[WARNING] Could not load system trusted CA certificates: "
2285                 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2286     }
2287 
2288     if (nghttp2::tls::ssl_ctx_set_proto_versions(
2289           ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2290           nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2291       std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
2292       result = -1;
2293       goto fin;
2294     }
2295 
2296     if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST.data()) ==
2297         0) {
2298       std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2299                 << std::endl;
2300       result = -1;
2301       goto fin;
2302     }
2303 
2304 #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL
2305     if (SSL_CTX_set_ciphersuites(ssl_ctx,
2306                                  tls::DEFAULT_TLS13_CIPHER_LIST.data()) == 0) {
2307       std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2308                 << std::endl;
2309       result = -1;
2310       goto fin;
2311     }
2312 #endif // NGHTTP2_OPENSSL_IS_WOLFSSL
2313 
2314     if (!config.keyfile.empty()) {
2315       if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
2316                                       SSL_FILETYPE_PEM) != 1) {
2317         std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2318                   << std::endl;
2319         result = -1;
2320         goto fin;
2321       }
2322     }
2323     if (!config.certfile.empty()) {
2324       if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
2325                                              config.certfile.c_str()) != 1) {
2326         std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2327                   << std::endl;
2328         result = -1;
2329         goto fin;
2330       }
2331     }
2332 
2333     auto proto_list = util::get_default_alpn();
2334 
2335     SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2336 
2337 #if defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && defined(HAVE_LIBBROTLI)
2338     if (!SSL_CTX_add_cert_compression_alg(
2339           ssl_ctx, nghttp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI,
2340           nghttp2::tls::cert_compress, nghttp2::tls::cert_decompress)) {
2341       std::cerr << "[ERROR] SSL_CTX_add_cert_compression_alg failed."
2342                 << std::endl;
2343       result = -1;
2344       goto fin;
2345     }
2346 #endif // NGHTTP2_OPENSSL_IS_BORINGSSL && HAVE_LIBBROTLI
2347 
2348     if (tls::setup_keylog_callback(ssl_ctx) != 0) {
2349       std::cerr << "[ERROR] Failed to setup keylog" << std::endl;
2350 
2351       result = -1;
2352 
2353       goto fin;
2354     }
2355   }
2356   {
2357     HttpClient client{callbacks, loop, ssl_ctx};
2358 
2359     int32_t dep_stream_id = 0;
2360 
2361     if (!config.no_dep) {
2362       dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
2363     }
2364 
2365     for (auto &req : requests) {
2366       nghttp2_priority_spec pri_spec;
2367 
2368       nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
2369 
2370       for (int i = 0; i < config.multiply; ++i) {
2371         client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2372                            pri_spec);
2373       }
2374     }
2375     client.update_hostport();
2376 
2377     client.record_start_time();
2378 
2379     if (client.resolve_host(host, port) != 0) {
2380       goto fin;
2381     }
2382 
2383     client.record_domain_lookup_end_time();
2384 
2385     if (client.initiate_connection() != 0) {
2386       std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
2387                 << std::endl;
2388       goto fin;
2389     }
2390 
2391     ev_set_userdata(loop, &client);
2392     ev_run(loop, 0);
2393     ev_set_userdata(loop, nullptr);
2394 
2395 #ifdef HAVE_JANSSON
2396     if (!config.harfile.empty()) {
2397       FILE *outfile;
2398       if (config.harfile == "-") {
2399         outfile = stdout;
2400       } else {
2401         outfile = fopen(config.harfile.c_str(), "wb");
2402       }
2403 
2404       if (outfile) {
2405         client.output_har(outfile);
2406 
2407         if (outfile != stdout) {
2408           fclose(outfile);
2409         }
2410       } else {
2411         std::cerr << "Cannot open file " << config.harfile << ". "
2412                   << "har file could not be created." << std::endl;
2413       }
2414     }
2415 #endif // HAVE_JANSSON
2416 
2417     if (client.success != client.reqvec.size()) {
2418       std::cerr << "Some requests were not processed. total="
2419                 << client.reqvec.size() << ", processed=" << client.success
2420                 << std::endl;
2421     }
2422     if (config.stat) {
2423       print_stats(client);
2424     }
2425   }
2426 fin:
2427   if (ssl_ctx) {
2428     SSL_CTX_free(ssl_ctx);
2429   }
2430   return result;
2431 }
2432 } // namespace
2433 
2434 namespace {
file_read_callback(nghttp2_session * session,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * user_data)2435 nghttp2_ssize file_read_callback(nghttp2_session *session, int32_t stream_id,
2436                                  uint8_t *buf, size_t length,
2437                                  uint32_t *data_flags,
2438                                  nghttp2_data_source *source, void *user_data) {
2439   int rv;
2440   auto req = static_cast<Request *>(
2441     nghttp2_session_get_stream_user_data(session, stream_id));
2442   assert(req);
2443   int fd = source->fd;
2444   ssize_t nread;
2445 
2446   while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2447          errno == EINTR)
2448     ;
2449 
2450   if (nread == -1) {
2451     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2452   }
2453 
2454   req->data_offset += nread;
2455 
2456   if (req->data_offset == req->data_length) {
2457     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2458     if (!config.trailer.empty()) {
2459       std::vector<nghttp2_nv> nva;
2460       nva.reserve(config.trailer.size());
2461       for (auto &kv : config.trailer) {
2462         nva.push_back(http2::make_field_nv(kv.name, kv.value,
2463                                            http2::no_index(kv.no_index)));
2464       }
2465       rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
2466       if (rv != 0) {
2467         if (nghttp2_is_fatal(rv)) {
2468           return NGHTTP2_ERR_CALLBACK_FAILURE;
2469         }
2470       } else {
2471         *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2472       }
2473     }
2474 
2475     return static_cast<nghttp2_ssize>(nread);
2476   }
2477 
2478   if (req->data_offset > req->data_length || nread == 0) {
2479     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2480   }
2481 
2482   return static_cast<nghttp2_ssize>(nread);
2483 }
2484 } // namespace
2485 
2486 namespace {
run(char ** uris,int n)2487 int run(char **uris, int n) {
2488   nghttp2_session_callbacks *callbacks;
2489 
2490   nghttp2_session_callbacks_new(&callbacks);
2491   auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2492 
2493   nghttp2_session_callbacks_set_on_stream_close_callback(
2494     callbacks, on_stream_close_callback);
2495 
2496   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2497                                                        on_frame_recv_callback2);
2498 
2499   if (config.verbose) {
2500     nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2501       callbacks, verbose_on_invalid_frame_recv_callback);
2502 
2503     nghttp2_session_callbacks_set_error_callback2(callbacks,
2504                                                   verbose_error_callback);
2505   }
2506 
2507   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2508     callbacks, on_data_chunk_recv_callback);
2509 
2510   nghttp2_session_callbacks_set_on_begin_headers_callback(
2511     callbacks, on_begin_headers_callback);
2512 
2513   nghttp2_session_callbacks_set_on_header_callback(callbacks,
2514                                                    on_header_callback);
2515 
2516   nghttp2_session_callbacks_set_before_frame_send_callback(
2517     callbacks, before_frame_send_callback);
2518 
2519   nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
2520                                                        on_frame_send_callback);
2521 
2522   nghttp2_session_callbacks_set_on_frame_not_send_callback(
2523     callbacks, on_frame_not_send_callback);
2524 
2525   if (config.padding) {
2526     nghttp2_session_callbacks_set_select_padding_callback2(
2527       callbacks, select_padding_callback);
2528   }
2529 
2530   std::string prev_scheme;
2531   std::string prev_host;
2532   uint16_t prev_port = 0;
2533   int failures = 0;
2534   int data_fd = -1;
2535   nghttp2_data_provider2 data_prd;
2536   struct stat data_stat;
2537 
2538   if (!config.datafile.empty()) {
2539     if (config.datafile == "-") {
2540       if (fstat(0, &data_stat) == 0 &&
2541           (data_stat.st_mode & S_IFMT) == S_IFREG) {
2542         // use STDIN if it is a regular file
2543         data_fd = 0;
2544       } else {
2545         // copy the contents of STDIN to a temporary file
2546         char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2547         data_fd = mkstemp(tempfn);
2548         if (data_fd == -1) {
2549           std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2550                     << std::endl;
2551           return 1;
2552         }
2553         if (unlink(tempfn) != 0) {
2554           std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2555                     << std::endl;
2556         }
2557         while (1) {
2558           std::array<char, 1_k> buf;
2559           ssize_t rret, wret;
2560           while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2561                  errno == EINTR)
2562             ;
2563           if (rret == 0)
2564             break;
2565           if (rret == -1) {
2566             std::cerr << "[ERROR] I/O error while reading from STDIN"
2567                       << std::endl;
2568             return 1;
2569           }
2570           while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2571                  errno == EINTR)
2572             ;
2573           if (wret != rret) {
2574             std::cerr << "[ERROR] I/O error while writing to temporary file"
2575                       << std::endl;
2576             return 1;
2577           }
2578         }
2579         if (fstat(data_fd, &data_stat) == -1) {
2580           close(data_fd);
2581           std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2582           return 1;
2583         }
2584       }
2585     } else {
2586       data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2587       if (data_fd == -1) {
2588         std::cerr << "[ERROR] Could not open file " << config.datafile
2589                   << std::endl;
2590         return 1;
2591       }
2592       if (fstat(data_fd, &data_stat) == -1) {
2593         close(data_fd);
2594         std::cerr << "[ERROR] Could not stat file " << config.datafile
2595                   << std::endl;
2596         return 1;
2597       }
2598     }
2599     data_prd.source.fd = data_fd;
2600     data_prd.read_callback = file_read_callback;
2601   }
2602   std::vector<
2603     std::tuple<std::string, nghttp2_data_provider2 *, int64_t, int32_t>>
2604     requests;
2605 
2606   size_t next_weight_idx = 0;
2607 
2608   for (int i = 0; i < n; ++i) {
2609     http_parser_url u{};
2610     auto uri = strip_fragment(uris[i]);
2611     if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2612       ++next_weight_idx;
2613       std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
2614       continue;
2615     }
2616     if (!util::has_uri_field(u, UF_SCHEMA)) {
2617       ++next_weight_idx;
2618       std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
2619                 << std::endl;
2620       continue;
2621     }
2622     auto port = util::has_uri_field(u, UF_PORT)
2623                   ? u.port
2624                   : util::get_default_port(uri.c_str(), u);
2625     auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
2626     if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2627         host != prev_host || port != prev_port) {
2628       if (!requests.empty()) {
2629         if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2630                         callbacks) != 0) {
2631           ++failures;
2632         }
2633         requests.clear();
2634       }
2635       prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
2636       prev_host = std::move(host);
2637       prev_port = port;
2638     }
2639     requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2640                           data_stat.st_size, config.weight[next_weight_idx++]);
2641   }
2642   if (!requests.empty()) {
2643     if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2644                     callbacks) != 0) {
2645       ++failures;
2646     }
2647   }
2648   return failures;
2649 }
2650 } // namespace
2651 
2652 namespace {
print_version(std::ostream & out)2653 void print_version(std::ostream &out) {
2654   out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2655 }
2656 } // namespace
2657 
2658 namespace {
print_usage(std::ostream & out)2659 void print_usage(std::ostream &out) {
2660   out << R"(Usage: nghttp [OPTIONS]... <URI>...
2661 HTTP/2 client)"
2662       << std::endl;
2663 }
2664 } // namespace
2665 
2666 namespace {
print_help(std::ostream & out)2667 void print_help(std::ostream &out) {
2668   print_usage(out);
2669   out << R"(
2670   <URI>       Specify URI to access.
2671 Options:
2672   -v, --verbose
2673               Print   debug   information   such  as   reception   and
2674               transmission of frames and name/value pairs.  Specifying
2675               this option multiple times increases verbosity.
2676   -n, --null-out
2677               Discard downloaded data.
2678   -O, --remote-name
2679               Save  download  data  in  the  current  directory.   The
2680               filename is  derived from  URI.  If  URI ends  with '/',
2681               'index.html'  is used  as a  filename.  Not  implemented
2682               yet.
2683   -t, --timeout=<DURATION>
2684               Timeout each request after <DURATION>.  Set 0 to disable
2685               timeout.
2686   -w, --window-bits=<N>
2687               Sets the stream level initial window size to 2**<N>-1.
2688   -W, --connection-window-bits=<N>
2689               Sets  the  connection  level   initial  window  size  to
2690               2**<N>-1.
2691   -a, --get-assets
2692               Download assets  such as stylesheets, images  and script
2693               files linked  from the downloaded resource.   Only links
2694               whose  origins are  the same  with the  linking resource
2695               will be downloaded.   nghttp prioritizes resources using
2696               HTTP/2 dependency  based priority.  The  priority order,
2697               from highest to lowest,  is html itself, css, javascript
2698               and images.
2699   -s, --stat  Print statistics.
2700   -H, --header=<HEADER>
2701               Add a header to the requests.  Example: -H':method: PUT'
2702   --trailer=<HEADER>
2703               Add a trailer header to the requests.  <HEADER> must not
2704               include pseudo header field  (header field name starting
2705               with ':').  To  send trailer, one must use  -d option to
2706               send request body.  Example: --trailer 'foo: bar'.
2707   --cert=<CERT>
2708               Use  the specified  client certificate  file.  The  file
2709               must be in PEM format.
2710   --key=<KEY> Use the  client private key  file.  The file must  be in
2711               PEM format.
2712   -d, --data=<PATH>
2713               Post FILE to server. If '-'  is given, data will be read
2714               from stdin.
2715   -m, --multiply=<N>
2716               Request each URI <N> times.  By default, same URI is not
2717               requested twice.  This option disables it too.
2718   -u, --upgrade
2719               Perform HTTP Upgrade for HTTP/2.  This option is ignored
2720               if the request URI has https scheme.  If -d is used, the
2721               HTTP upgrade request is performed with OPTIONS method.
2722   -p, --weight=<WEIGHT>
2723               Sets  weight of  given  URI.  This  option  can be  used
2724               multiple times, and  N-th -p option sets  weight of N-th
2725               URI in the command line.  If  the number of -p option is
2726               less than the number of URI, the last -p option value is
2727               repeated.  If there is no -p option, default weight, 16,
2728               is assumed.  The valid value range is
2729               [)"
2730       << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
2731   -M, --peer-max-concurrent-streams=<N>
2732               Use  <N>  as  SETTINGS_MAX_CONCURRENT_STREAMS  value  of
2733               remote endpoint as if it  is received in SETTINGS frame.
2734               Default: 100
2735   -c, --header-table-size=<SIZE>
2736               Specify decoder  header table  size.  If this  option is
2737               used  multiple times,  and the  minimum value  among the
2738               given values except  for last one is  strictly less than
2739               the last  value, that minimum  value is set  in SETTINGS
2740               frame  payload  before  the   last  value,  to  simulate
2741               multiple header table size change.
2742   --encoder-header-table-size=<SIZE>
2743               Specify encoder header table size.  The decoder (server)
2744               specifies  the maximum  dynamic table  size it  accepts.
2745               Then the negotiated dynamic table size is the minimum of
2746               this option value and the value which server specified.
2747   -b, --padding=<N>
2748               Add at  most <N>  bytes to a  frame payload  as padding.
2749               Specify 0 to disable padding.
2750   -r, --har=<PATH>
2751               Output HTTP  transactions <PATH> in HAR  format.  If '-'
2752               is given, data is written to stdout.
2753   --color     Force colored log output.
2754   --continuation
2755               Send large header to test CONTINUATION.
2756   --no-content-length
2757               Don't send content-length header field.
2758   --no-dep    Don't send dependency based priority hint to server.
2759   --hexdump   Display the  incoming traffic in  hexadecimal (Canonical
2760               hex+ASCII display).  If SSL/TLS  is used, decrypted data
2761               are used.
2762   --no-push   Disable server push.
2763   --max-concurrent-streams=<N>
2764               The  number of  concurrent  pushed  streams this  client
2765               accepts.
2766   --expect-continue
2767               Perform an Expect/Continue handshake:  wait to send DATA
2768               (up to  a short  timeout)  until the server sends  a 100
2769               Continue interim response. This option is ignored unless
2770               combined with the -d option.
2771   -y, --no-verify-peer
2772               Suppress  warning  on  server  certificate  verification
2773               failure.
2774   --ktls      Enable ktls.
2775   --no-rfc7540-pri
2776               Disable RFC7540 priorities.
2777   --version   Display version information and exit.
2778   -h, --help  Display this help and exit.
2779 
2780 --
2781 
2782   The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2783   10 * 1024).  Units are K, M and G (powers of 1024).
2784 
2785   The <DURATION> argument is an integer and an optional unit (e.g., 1s
2786   is 1 second and 500ms is 500 milliseconds).  Units are h, m, s or ms
2787   (hours, minutes, seconds and milliseconds, respectively).  If a unit
2788   is omitted, a second is used as unit.)"
2789       << std::endl;
2790 }
2791 } // namespace
2792 
main(int argc,char ** argv)2793 int main(int argc, char **argv) {
2794   bool color = false;
2795   while (1) {
2796     static int flag = 0;
2797     constexpr static option long_options[] = {
2798       {"verbose", no_argument, nullptr, 'v'},
2799       {"null-out", no_argument, nullptr, 'n'},
2800       {"remote-name", no_argument, nullptr, 'O'},
2801       {"timeout", required_argument, nullptr, 't'},
2802       {"window-bits", required_argument, nullptr, 'w'},
2803       {"connection-window-bits", required_argument, nullptr, 'W'},
2804       {"get-assets", no_argument, nullptr, 'a'},
2805       {"stat", no_argument, nullptr, 's'},
2806       {"help", no_argument, nullptr, 'h'},
2807       {"header", required_argument, nullptr, 'H'},
2808       {"data", required_argument, nullptr, 'd'},
2809       {"multiply", required_argument, nullptr, 'm'},
2810       {"upgrade", no_argument, nullptr, 'u'},
2811       {"weight", required_argument, nullptr, 'p'},
2812       {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2813       {"header-table-size", required_argument, nullptr, 'c'},
2814       {"padding", required_argument, nullptr, 'b'},
2815       {"har", required_argument, nullptr, 'r'},
2816       {"no-verify-peer", no_argument, nullptr, 'y'},
2817       {"cert", required_argument, &flag, 1},
2818       {"key", required_argument, &flag, 2},
2819       {"color", no_argument, &flag, 3},
2820       {"continuation", no_argument, &flag, 4},
2821       {"version", no_argument, &flag, 5},
2822       {"no-content-length", no_argument, &flag, 6},
2823       {"no-dep", no_argument, &flag, 7},
2824       {"trailer", required_argument, &flag, 9},
2825       {"hexdump", no_argument, &flag, 10},
2826       {"no-push", no_argument, &flag, 11},
2827       {"max-concurrent-streams", required_argument, &flag, 12},
2828       {"expect-continue", no_argument, &flag, 13},
2829       {"encoder-header-table-size", required_argument, &flag, 14},
2830       {"ktls", no_argument, &flag, 15},
2831       {"no-rfc7540-pri", no_argument, &flag, 16},
2832       {nullptr, 0, nullptr, 0}};
2833     int option_index = 0;
2834     int c =
2835       getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
2836                   &option_index);
2837     if (c == -1) {
2838       break;
2839     }
2840     switch (c) {
2841     case 'M': {
2842       // peer-max-concurrent-streams option
2843       auto n = util::parse_uint(optarg);
2844       if (!n) {
2845         std::cerr << "-M: Bad option value: " << optarg << std::endl;
2846         exit(EXIT_FAILURE);
2847       }
2848       config.peer_max_concurrent_streams = *n;
2849       break;
2850     }
2851     case 'O':
2852       config.remote_name = true;
2853       break;
2854     case 'h':
2855       print_help(std::cout);
2856       exit(EXIT_SUCCESS);
2857     case 'b': {
2858       auto n = util::parse_uint(optarg);
2859       if (!n) {
2860         std::cerr << "-b: Bad option value: " << optarg << std::endl;
2861         exit(EXIT_FAILURE);
2862       }
2863       config.padding = *n;
2864       break;
2865     }
2866     case 'n':
2867       config.null_out = true;
2868       break;
2869     case 'p': {
2870       auto n = util::parse_uint(optarg);
2871       if (!n || NGHTTP2_MIN_WEIGHT > n || n > NGHTTP2_MAX_WEIGHT) {
2872         std::cerr << "-p: specify the integer in the range ["
2873                   << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2874                   << "], inclusive" << std::endl;
2875         exit(EXIT_FAILURE);
2876       }
2877       config.weight.push_back(*n);
2878       break;
2879     }
2880     case 'r':
2881 #ifdef HAVE_JANSSON
2882       config.harfile = optarg;
2883 #else  // !HAVE_JANSSON
2884       std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2885                 << "the binary was not compiled with libjansson." << std::endl;
2886 #endif // !HAVE_JANSSON
2887       break;
2888     case 'v':
2889       ++config.verbose;
2890       break;
2891     case 't': {
2892       auto d = util::parse_duration_with_unit(optarg);
2893       if (!d) {
2894         std::cerr << "-t: bad timeout value: " << optarg << std::endl;
2895         exit(EXIT_FAILURE);
2896       }
2897       config.timeout = *d;
2898       break;
2899     }
2900     case 'u':
2901       config.upgrade = true;
2902       break;
2903     case 'w':
2904     case 'W': {
2905       auto n = util::parse_uint(optarg);
2906       if (!n || n > 30) {
2907         std::cerr << "-" << static_cast<char>(c)
2908                   << ": specify the integer in the range [0, 30], inclusive"
2909                   << std::endl;
2910         exit(EXIT_FAILURE);
2911       }
2912       if (c == 'w') {
2913         config.window_bits = *n;
2914       } else {
2915         config.connection_window_bits = *n;
2916       }
2917       break;
2918     }
2919     case 'H': {
2920       char *header = optarg;
2921       // Skip first possible ':' in the header name
2922       char *value = strchr(optarg + 1, ':');
2923       if (!value || (header[0] == ':' && header + 1 == value)) {
2924         std::cerr << "-H: invalid header: " << optarg << std::endl;
2925         exit(EXIT_FAILURE);
2926       }
2927       *value = 0;
2928       value++;
2929       while (isspace(*value)) {
2930         value++;
2931       }
2932       if (*value == 0) {
2933         // This could also be a valid case for suppressing a header
2934         // similar to curl
2935         std::cerr << "-H: invalid header - value missing: " << optarg
2936                   << std::endl;
2937         exit(EXIT_FAILURE);
2938       }
2939       config.headers.emplace_back(header, value, false);
2940       util::inp_strlower(config.headers.back().name);
2941       break;
2942     }
2943     case 'a':
2944 #ifdef HAVE_LIBXML2
2945       config.get_assets = true;
2946 #else  // !HAVE_LIBXML2
2947       std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2948                 << "the binary was not compiled with libxml2." << std::endl;
2949 #endif // !HAVE_LIBXML2
2950       break;
2951     case 's':
2952       config.stat = true;
2953       break;
2954     case 'd':
2955       config.datafile = optarg;
2956       break;
2957     case 'm': {
2958       auto n = util::parse_uint(optarg);
2959       if (!n) {
2960         std::cerr << "-m: Bad option value: " << optarg << std::endl;
2961         exit(EXIT_FAILURE);
2962       }
2963       config.multiply = *n;
2964       break;
2965     }
2966     case 'c': {
2967       auto n = util::parse_uint_with_unit(optarg);
2968       if (!n) {
2969         std::cerr << "-c: Bad option value: " << optarg << std::endl;
2970         exit(EXIT_FAILURE);
2971       }
2972       if (n > std::numeric_limits<uint32_t>::max()) {
2973         std::cerr << "-c: Value too large.  It should be less than or equal to "
2974                   << std::numeric_limits<uint32_t>::max() << std::endl;
2975         exit(EXIT_FAILURE);
2976       }
2977       config.header_table_size = *n;
2978       config.min_header_table_size = std::min(config.min_header_table_size, *n);
2979       break;
2980     }
2981     case 'y':
2982       config.verify_peer = false;
2983       break;
2984     case '?':
2985       util::show_candidates(argv[optind - 1], long_options);
2986       exit(EXIT_FAILURE);
2987     case 0:
2988       switch (flag) {
2989       case 1:
2990         // cert option
2991         config.certfile = optarg;
2992         break;
2993       case 2:
2994         // key option
2995         config.keyfile = optarg;
2996         break;
2997       case 3:
2998         // color option
2999         color = true;
3000         break;
3001       case 4:
3002         // continuation option
3003         config.continuation = true;
3004         break;
3005       case 5:
3006         // version option
3007         print_version(std::cout);
3008         exit(EXIT_SUCCESS);
3009       case 6:
3010         // no-content-length option
3011         config.no_content_length = true;
3012         break;
3013       case 7:
3014         // no-dep option
3015         config.no_dep = true;
3016         break;
3017       case 9: {
3018         // trailer option
3019         auto header = optarg;
3020         auto value = strchr(optarg, ':');
3021         if (!value) {
3022           std::cerr << "--trailer: invalid header: " << optarg << std::endl;
3023           exit(EXIT_FAILURE);
3024         }
3025         *value = 0;
3026         value++;
3027         while (isspace(*value)) {
3028           value++;
3029         }
3030         if (*value == 0) {
3031           // This could also be a valid case for suppressing a header
3032           // similar to curl
3033           std::cerr << "--trailer: invalid header - value missing: " << optarg
3034                     << std::endl;
3035           exit(EXIT_FAILURE);
3036         }
3037         config.trailer.emplace_back(header, value, false);
3038         util::inp_strlower(config.trailer.back().name);
3039         break;
3040       }
3041       case 10:
3042         // hexdump option
3043         config.hexdump = true;
3044         break;
3045       case 11:
3046         // no-push option
3047         config.no_push = true;
3048         break;
3049       case 12: {
3050         // max-concurrent-streams option
3051         auto n = util::parse_uint(optarg);
3052         if (!n) {
3053           std::cerr << "--max-concurrent-streams: Bad option value: " << optarg
3054                     << std::endl;
3055           exit(EXIT_FAILURE);
3056         }
3057         config.max_concurrent_streams = *n;
3058         break;
3059       }
3060       case 13:
3061         // expect-continue option
3062         config.expect_continue = true;
3063         break;
3064       case 14: {
3065         // encoder-header-table-size option
3066         auto n = util::parse_uint_with_unit(optarg);
3067         if (!n) {
3068           std::cerr << "--encoder-header-table-size: Bad option value: "
3069                     << optarg << std::endl;
3070           exit(EXIT_FAILURE);
3071         }
3072         if (n > std::numeric_limits<uint32_t>::max()) {
3073           std::cerr << "--encoder-header-table-size: Value too large.  It "
3074                        "should be less than or equal to "
3075                     << std::numeric_limits<uint32_t>::max() << std::endl;
3076           exit(EXIT_FAILURE);
3077         }
3078         config.encoder_header_table_size = *n;
3079         break;
3080       }
3081       case 15:
3082         // ktls option
3083         config.ktls = true;
3084         break;
3085       case 16:
3086         // no-rfc7540-pri option
3087         config.no_rfc7540_pri = true;
3088         break;
3089       }
3090       break;
3091     default:
3092       break;
3093     }
3094   }
3095 
3096   int32_t weight_to_fill;
3097   if (config.weight.empty()) {
3098     weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
3099   } else {
3100     weight_to_fill = config.weight.back();
3101   }
3102   config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
3103 
3104   // Find scheme overridden by extra header fields.
3105   auto scheme_it =
3106     std::find_if(std::begin(config.headers), std::end(config.headers),
3107                  [](const Header &nv) { return nv.name == ":scheme"; });
3108   if (scheme_it != std::end(config.headers)) {
3109     config.scheme_override = (*scheme_it).value;
3110   }
3111 
3112   // Find host and port overridden by extra header fields.
3113   auto authority_it =
3114     std::find_if(std::begin(config.headers), std::end(config.headers),
3115                  [](const Header &nv) { return nv.name == ":authority"; });
3116   if (authority_it == std::end(config.headers)) {
3117     authority_it =
3118       std::find_if(std::begin(config.headers), std::end(config.headers),
3119                    [](const Header &nv) { return nv.name == "host"; });
3120   }
3121 
3122   if (authority_it != std::end(config.headers)) {
3123     // authority_it may looks like "host:port".
3124     auto uri = "https://" + (*authority_it).value;
3125     http_parser_url u{};
3126     if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
3127       std::cerr << "[ERROR] Could not parse authority in "
3128                 << (*authority_it).name << ": " << (*authority_it).value
3129                 << std::endl;
3130       exit(EXIT_FAILURE);
3131     }
3132 
3133     config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST);
3134     if (util::has_uri_field(u, UF_PORT)) {
3135       config.port_override = u.port;
3136     }
3137   }
3138 
3139   set_color_output(color || isatty(fileno(stdout)));
3140 
3141   nghttp2_option_set_peer_max_concurrent_streams(
3142     config.http2_option, config.peer_max_concurrent_streams);
3143 
3144   if (config.encoder_header_table_size != -1) {
3145     nghttp2_option_set_max_deflate_dynamic_table_size(
3146       config.http2_option, config.encoder_header_table_size);
3147   }
3148 
3149   struct sigaction act {};
3150   act.sa_handler = SIG_IGN;
3151   sigaction(SIGPIPE, &act, nullptr);
3152   reset_timer();
3153   return run(argv + optind, argc - optind);
3154 }
3155 
3156 } // namespace nghttp2
3157 
main(int argc,char ** argv)3158 int main(int argc, char **argv) {
3159   return nghttp2::run_app(nghttp2::main, argc, argv);
3160 }
3161