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