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