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 <iming = 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