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