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