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()
202 ? util::has_uri_field(u, UF_PORT) ? u.port
203 : scheme == "https" ? 443 : 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 =
1685 util::has_uri_field(u, UF_PORT) ? u.port : scheme == "https" ? 443 : 80;
1686
1687 if (port != link_port) {
1688 continue;
1689 }
1690
1691 // No POST data for assets
1692 auto pri_spec = resolve_dep(res_type);
1693
1694 if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
1695 submit_request(client, config.headers, client->reqvec.back().get());
1696 }
1697 }
1698 req->html_parser->clear_links();
1699 }
1700 } // namespace
1701
1702 namespace {
get_client(void * user_data)1703 HttpClient *get_client(void *user_data) {
1704 return static_cast<HttpClient *>(user_data);
1705 }
1706 } // namespace
1707
1708 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)1709 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1710 int32_t stream_id, const uint8_t *data,
1711 size_t len, void *user_data) {
1712 auto client = get_client(user_data);
1713 auto req = static_cast<Request *>(
1714 nghttp2_session_get_stream_user_data(session, stream_id));
1715
1716 if (!req) {
1717 return 0;
1718 }
1719
1720 if (config.verbose >= 2) {
1721 verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1722 user_data);
1723 }
1724
1725 req->response_len += len;
1726
1727 if (req->inflater) {
1728 while (len > 0) {
1729 const size_t MAX_OUTLEN = 4_k;
1730 std::array<uint8_t, MAX_OUTLEN> out;
1731 size_t outlen = MAX_OUTLEN;
1732 size_t tlen = len;
1733 int rv =
1734 nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1735 if (rv != 0) {
1736 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1737 NGHTTP2_INTERNAL_ERROR);
1738 break;
1739 }
1740
1741 if (!config.null_out) {
1742 std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1743 }
1744
1745 update_html_parser(client, req, out.data(), outlen, 0);
1746 data += tlen;
1747 len -= tlen;
1748 }
1749
1750 return 0;
1751 }
1752
1753 if (!config.null_out) {
1754 std::cout.write(reinterpret_cast<const char *>(data), len);
1755 }
1756
1757 update_html_parser(client, req, data, len, 0);
1758
1759 return 0;
1760 }
1761 } // namespace
1762
1763 namespace {
select_padding_callback(nghttp2_session * session,const nghttp2_frame * frame,size_t max_payload,void * user_data)1764 ssize_t select_padding_callback(nghttp2_session *session,
1765 const nghttp2_frame *frame, size_t max_payload,
1766 void *user_data) {
1767 return std::min(max_payload, frame->hd.length + config.padding);
1768 }
1769 } // namespace
1770
1771 namespace {
check_response_header(nghttp2_session * session,Request * req)1772 void check_response_header(nghttp2_session *session, Request *req) {
1773 bool gzip = false;
1774
1775 req->expect_final_response = false;
1776
1777 auto status_hd = req->get_res_header(http2::HD__STATUS);
1778
1779 if (!status_hd) {
1780 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1781 NGHTTP2_PROTOCOL_ERROR);
1782 return;
1783 }
1784
1785 auto status = http2::parse_http_status_code(StringRef{status_hd->value});
1786 if (status == -1) {
1787 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1788 NGHTTP2_PROTOCOL_ERROR);
1789 return;
1790 }
1791
1792 req->status = status;
1793
1794 for (auto &nv : req->res_nva) {
1795 if ("content-encoding" == nv.name) {
1796 gzip = util::strieq_l("gzip", nv.value) ||
1797 util::strieq_l("deflate", nv.value);
1798 continue;
1799 }
1800 }
1801
1802 if (req->status / 100 == 1) {
1803 if (req->continue_timer && (req->status == 100)) {
1804 // If the request is waiting for a 100 Continue, complete the handshake.
1805 req->continue_timer->dispatch_continue();
1806 }
1807
1808 req->expect_final_response = true;
1809 req->status = 0;
1810 req->res_nva.clear();
1811 http2::init_hdidx(req->res_hdidx);
1812 return;
1813 } else if (req->continue_timer) {
1814 // A final response stops any pending Expect/Continue handshake.
1815 req->continue_timer->stop();
1816 }
1817
1818 if (gzip) {
1819 if (!req->inflater) {
1820 req->init_inflater();
1821 }
1822 }
1823 if (config.get_assets && req->level == 0) {
1824 if (!req->html_parser) {
1825 req->init_html_parser();
1826 }
1827 }
1828 }
1829 } // namespace
1830
1831 namespace {
on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)1832 int on_begin_headers_callback(nghttp2_session *session,
1833 const nghttp2_frame *frame, void *user_data) {
1834 auto client = get_client(user_data);
1835 switch (frame->hd.type) {
1836 case NGHTTP2_HEADERS: {
1837 auto req = static_cast<Request *>(
1838 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1839 if (!req) {
1840 break;
1841 }
1842
1843 switch (frame->headers.cat) {
1844 case NGHTTP2_HCAT_RESPONSE:
1845 case NGHTTP2_HCAT_PUSH_RESPONSE:
1846 req->record_response_start_time();
1847 break;
1848 default:
1849 break;
1850 }
1851
1852 break;
1853 }
1854 case NGHTTP2_PUSH_PROMISE: {
1855 auto stream_id = frame->push_promise.promised_stream_id;
1856 http_parser_url u{};
1857 // TODO Set pri and level
1858 nghttp2_priority_spec pri_spec;
1859
1860 nghttp2_priority_spec_default_init(&pri_spec);
1861
1862 auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
1863 req->stream_id = stream_id;
1864
1865 nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1866
1867 client->request_done(req.get());
1868 req->record_request_start_time();
1869 client->reqvec.push_back(std::move(req));
1870
1871 break;
1872 }
1873 }
1874 return 0;
1875 }
1876 } // namespace
1877
1878 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)1879 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1880 const uint8_t *name, size_t namelen,
1881 const uint8_t *value, size_t valuelen, uint8_t flags,
1882 void *user_data) {
1883 if (config.verbose) {
1884 verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1885 flags, user_data);
1886 }
1887
1888 switch (frame->hd.type) {
1889 case NGHTTP2_HEADERS: {
1890 auto req = static_cast<Request *>(
1891 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1892
1893 if (!req) {
1894 break;
1895 }
1896
1897 /* ignore trailer header */
1898 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
1899 !req->expect_final_response) {
1900 break;
1901 }
1902
1903 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1904 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1905 NGHTTP2_INTERNAL_ERROR);
1906 return 0;
1907 }
1908
1909 req->header_buffer_size += namelen + valuelen;
1910
1911 auto token = http2::lookup_token(name, namelen);
1912
1913 http2::index_header(req->res_hdidx, token, req->res_nva.size());
1914 http2::add_header(req->res_nva, name, namelen, value, valuelen,
1915 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1916 break;
1917 }
1918 case NGHTTP2_PUSH_PROMISE: {
1919 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1920 session, frame->push_promise.promised_stream_id));
1921
1922 if (!req) {
1923 break;
1924 }
1925
1926 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1927 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1928 frame->push_promise.promised_stream_id,
1929 NGHTTP2_INTERNAL_ERROR);
1930 return 0;
1931 }
1932
1933 req->header_buffer_size += namelen + valuelen;
1934
1935 auto token = http2::lookup_token(name, namelen);
1936
1937 http2::index_header(req->req_hdidx, token, req->req_nva.size());
1938 http2::add_header(req->req_nva, name, namelen, value, valuelen,
1939 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1940 break;
1941 }
1942 }
1943 return 0;
1944 }
1945 } // namespace
1946
1947 namespace {
on_frame_recv_callback2(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)1948 int on_frame_recv_callback2(nghttp2_session *session,
1949 const nghttp2_frame *frame, void *user_data) {
1950 int rv = 0;
1951
1952 if (config.verbose) {
1953 verbose_on_frame_recv_callback(session, frame, user_data);
1954 }
1955
1956 auto client = get_client(user_data);
1957 switch (frame->hd.type) {
1958 case NGHTTP2_DATA: {
1959 auto req = static_cast<Request *>(
1960 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1961 if (!req) {
1962 return 0;
1963 ;
1964 }
1965
1966 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1967 req->record_response_end_time();
1968 ++client->success;
1969 }
1970
1971 break;
1972 }
1973 case NGHTTP2_HEADERS: {
1974 auto req = static_cast<Request *>(
1975 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1976 // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1977 // req is nullptr.
1978 if (!req) {
1979 return 0;
1980 ;
1981 }
1982
1983 switch (frame->headers.cat) {
1984 case NGHTTP2_HCAT_RESPONSE:
1985 case NGHTTP2_HCAT_PUSH_RESPONSE:
1986 check_response_header(session, req);
1987 break;
1988 case NGHTTP2_HCAT_HEADERS:
1989 if (req->expect_final_response) {
1990 check_response_header(session, req);
1991 break;
1992 }
1993 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
1994 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1995 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
1996 return 0;
1997 }
1998 break;
1999 default:
2000 assert(0);
2001 }
2002
2003 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
2004 req->record_response_end_time();
2005 ++client->success;
2006 }
2007
2008 break;
2009 }
2010 case NGHTTP2_SETTINGS:
2011 if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
2012 break;
2013 }
2014 ev_timer_stop(client->loop, &client->settings_timer);
2015 break;
2016 case NGHTTP2_PUSH_PROMISE: {
2017 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
2018 session, frame->push_promise.promised_stream_id));
2019 if (!req) {
2020 break;
2021 }
2022
2023 // Reset for response header field reception
2024 req->header_buffer_size = 0;
2025
2026 auto scheme = req->get_req_header(http2::HD__SCHEME);
2027 auto authority = req->get_req_header(http2::HD__AUTHORITY);
2028 auto path = req->get_req_header(http2::HD__PATH);
2029
2030 if (!authority) {
2031 authority = req->get_req_header(http2::HD_HOST);
2032 }
2033
2034 // libnghttp2 guarantees :scheme, :method, :path and (:authority |
2035 // host) exist and non-empty.
2036 if (path->value[0] != '/') {
2037 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2038 frame->push_promise.promised_stream_id,
2039 NGHTTP2_PROTOCOL_ERROR);
2040 break;
2041 }
2042 std::string uri = scheme->value;
2043 uri += "://";
2044 uri += authority->value;
2045 uri += path->value;
2046 http_parser_url u{};
2047 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2048 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2049 frame->push_promise.promised_stream_id,
2050 NGHTTP2_PROTOCOL_ERROR);
2051 break;
2052 }
2053 req->uri = uri;
2054 req->u = u;
2055
2056 if (client->path_cache.count(uri)) {
2057 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2058 frame->push_promise.promised_stream_id,
2059 NGHTTP2_CANCEL);
2060 break;
2061 }
2062
2063 if (config.multiply == 1) {
2064 client->path_cache.insert(uri);
2065 }
2066
2067 break;
2068 }
2069 }
2070 return rv;
2071 }
2072 } // namespace
2073
2074 namespace {
before_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)2075 int before_frame_send_callback(nghttp2_session *session,
2076 const nghttp2_frame *frame, void *user_data) {
2077 if (frame->hd.type != NGHTTP2_HEADERS ||
2078 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2079 return 0;
2080 }
2081 auto req = static_cast<Request *>(
2082 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2083 assert(req);
2084 req->record_request_start_time();
2085 return 0;
2086 }
2087
2088 } // namespace
2089
2090 namespace {
on_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)2091 int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
2092 void *user_data) {
2093 if (config.verbose) {
2094 verbose_on_frame_send_callback(session, frame, user_data);
2095 }
2096
2097 if (frame->hd.type != NGHTTP2_HEADERS ||
2098 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2099 return 0;
2100 }
2101
2102 auto req = static_cast<Request *>(
2103 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2104 if (!req) {
2105 return 0;
2106 }
2107
2108 // If this request is using Expect/Continue, start its ContinueTimer.
2109 if (req->continue_timer) {
2110 req->continue_timer->start();
2111 }
2112
2113 return 0;
2114 }
2115 } // namespace
2116
2117 namespace {
on_frame_not_send_callback(nghttp2_session * session,const nghttp2_frame * frame,int lib_error_code,void * user_data)2118 int on_frame_not_send_callback(nghttp2_session *session,
2119 const nghttp2_frame *frame, int lib_error_code,
2120 void *user_data) {
2121 if (frame->hd.type != NGHTTP2_HEADERS ||
2122 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2123 return 0;
2124 }
2125
2126 auto req = static_cast<Request *>(
2127 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2128 if (!req) {
2129 return 0;
2130 }
2131
2132 std::cerr << "[ERROR] request " << req->uri
2133 << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
2134
2135 return 0;
2136 }
2137 } // namespace
2138
2139 namespace {
on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)2140 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
2141 uint32_t error_code, void *user_data) {
2142 auto client = get_client(user_data);
2143 auto req = static_cast<Request *>(
2144 nghttp2_session_get_stream_user_data(session, stream_id));
2145
2146 if (!req) {
2147 return 0;
2148 }
2149
2150 // If this request is using Expect/Continue, stop its ContinueTimer.
2151 if (req->continue_timer) {
2152 req->continue_timer->stop();
2153 }
2154
2155 update_html_parser(client, req, nullptr, 0, 1);
2156 ++client->complete;
2157
2158 if (client->all_requests_processed()) {
2159 nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
2160 }
2161
2162 return 0;
2163 }
2164 } // namespace
2165
2166 struct RequestResult {
2167 std::chrono::microseconds time;
2168 };
2169
2170 namespace {
print_stats(const HttpClient & client)2171 void print_stats(const HttpClient &client) {
2172 std::cout << "***** Statistics *****" << std::endl;
2173
2174 std::vector<Request *> reqs;
2175 reqs.reserve(client.reqvec.size());
2176 for (const auto &req : client.reqvec) {
2177 if (req->timing.state == RequestState::ON_COMPLETE) {
2178 reqs.push_back(req.get());
2179 }
2180 }
2181
2182 std::sort(std::begin(reqs), std::end(reqs),
2183 [](const Request *lhs, const Request *rhs) {
2184 const auto <iming = lhs->timing;
2185 const auto &rtiming = rhs->timing;
2186 return ltiming.response_end_time < rtiming.response_end_time ||
2187 (ltiming.response_end_time == rtiming.response_end_time &&
2188 ltiming.request_start_time < rtiming.request_start_time);
2189 });
2190
2191 std::cout << R"(
2192 Request timing:
2193 responseEnd: the time when last byte of response was received
2194 relative to connectEnd
2195 requestStart: the time just before first byte of request was sent
2196 relative to connectEnd. If '*' is shown, this was
2197 pushed by server.
2198 process: responseEnd - requestStart
2199 code: HTTP status code
2200 size: number of bytes received as response body without
2201 inflation.
2202 URI: request URI
2203
2204 see http://www.w3.org/TR/resource-timing/#processing-model
2205
2206 sorted by 'complete'
2207
2208 id responseEnd requestStart process code size request path)"
2209 << std::endl;
2210
2211 const auto &base = client.timing.connect_end_time;
2212 for (const auto &req : reqs) {
2213 auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
2214 req->timing.response_end_time - base);
2215 auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
2216 req->timing.request_start_time - base);
2217 auto total = std::chrono::duration_cast<std::chrono::microseconds>(
2218 req->timing.response_end_time - req->timing.request_start_time);
2219 auto pushed = req->stream_id % 2 == 0;
2220
2221 std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
2222 << ("+" + util::format_duration(response_end)) << " "
2223 << (pushed ? "*" : " ") << std::setw(11)
2224 << ("+" + util::format_duration(request_start)) << " "
2225 << std::setw(8) << util::format_duration(total) << " "
2226 << std::setw(4) << req->status << " " << std::setw(4)
2227 << util::utos_unit(req->response_len) << " "
2228 << req->make_reqpath() << std::endl;
2229 }
2230 }
2231 } // namespace
2232
2233 #ifndef OPENSSL_NO_NEXTPROTONEG
2234 namespace {
client_select_next_proto_cb(SSL * ssl,unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)2235 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
2236 unsigned char *outlen, const unsigned char *in,
2237 unsigned int inlen, void *arg) {
2238 if (config.verbose) {
2239 print_timer();
2240 std::cout << "[NPN] server offers:" << std::endl;
2241 }
2242 for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
2243 if (config.verbose) {
2244 std::cout << " * ";
2245 std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
2246 std::cout << std::endl;
2247 }
2248 }
2249 if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
2250 inlen)) {
2251 print_protocol_nego_error();
2252 return SSL_TLSEXT_ERR_NOACK;
2253 }
2254 return SSL_TLSEXT_ERR_OK;
2255 }
2256 } // namespace
2257 #endif // !OPENSSL_NO_NEXTPROTONEG
2258
2259 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)2260 int communicate(
2261 const std::string &scheme, const std::string &host, uint16_t port,
2262 std::vector<
2263 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2264 requests,
2265 const nghttp2_session_callbacks *callbacks) {
2266 int result = 0;
2267 auto loop = EV_DEFAULT;
2268 SSL_CTX *ssl_ctx = nullptr;
2269 if (scheme == "https") {
2270 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
2271 if (!ssl_ctx) {
2272 std::cerr << "[ERROR] Failed to create SSL_CTX: "
2273 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2274 result = -1;
2275 goto fin;
2276 }
2277
2278 auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2279 SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2280 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2281
2282 SSL_CTX_set_options(ssl_ctx, ssl_opts);
2283 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2284 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2285
2286 if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
2287 std::cerr << "[WARNING] Could not load system trusted CA certificates: "
2288 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2289 }
2290
2291 if (nghttp2::tls::ssl_ctx_set_proto_versions(
2292 ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2293 nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2294 std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
2295 result = -1;
2296 goto fin;
2297 }
2298
2299 if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) {
2300 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2301 << std::endl;
2302 result = -1;
2303 goto fin;
2304 }
2305 if (!config.keyfile.empty()) {
2306 if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
2307 SSL_FILETYPE_PEM) != 1) {
2308 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2309 << std::endl;
2310 result = -1;
2311 goto fin;
2312 }
2313 }
2314 if (!config.certfile.empty()) {
2315 if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
2316 config.certfile.c_str()) != 1) {
2317 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2318 << std::endl;
2319 result = -1;
2320 goto fin;
2321 }
2322 }
2323 #ifndef OPENSSL_NO_NEXTPROTONEG
2324 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2325 nullptr);
2326 #endif // !OPENSSL_NO_NEXTPROTONEG
2327
2328 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2329 auto proto_list = util::get_default_alpn();
2330
2331 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2332 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2333 }
2334 {
2335 HttpClient client{callbacks, loop, ssl_ctx};
2336
2337 int32_t dep_stream_id = 0;
2338
2339 if (!config.no_dep) {
2340 dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
2341 }
2342
2343 for (auto &req : requests) {
2344 nghttp2_priority_spec pri_spec;
2345
2346 nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
2347
2348 for (int i = 0; i < config.multiply; ++i) {
2349 client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2350 pri_spec);
2351 }
2352 }
2353 client.update_hostport();
2354
2355 client.record_start_time();
2356
2357 if (client.resolve_host(host, port) != 0) {
2358 goto fin;
2359 }
2360
2361 client.record_domain_lookup_end_time();
2362
2363 if (client.initiate_connection() != 0) {
2364 std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
2365 << std::endl;
2366 goto fin;
2367 }
2368
2369 ev_set_userdata(loop, &client);
2370 ev_run(loop, 0);
2371 ev_set_userdata(loop, nullptr);
2372
2373 #ifdef HAVE_JANSSON
2374 if (!config.harfile.empty()) {
2375 FILE *outfile;
2376 if (config.harfile == "-") {
2377 outfile = stdout;
2378 } else {
2379 outfile = fopen(config.harfile.c_str(), "wb");
2380 }
2381
2382 if (outfile) {
2383 client.output_har(outfile);
2384
2385 if (outfile != stdout) {
2386 fclose(outfile);
2387 }
2388 } else {
2389 std::cerr << "Cannot open file " << config.harfile << ". "
2390 << "har file could not be created." << std::endl;
2391 }
2392 }
2393 #endif // HAVE_JANSSON
2394
2395 if (client.success != client.reqvec.size()) {
2396 std::cerr << "Some requests were not processed. total="
2397 << client.reqvec.size() << ", processed=" << client.success
2398 << std::endl;
2399 }
2400 if (config.stat) {
2401 print_stats(client);
2402 }
2403 }
2404 fin:
2405 if (ssl_ctx) {
2406 SSL_CTX_free(ssl_ctx);
2407 }
2408 return result;
2409 }
2410 } // namespace
2411
2412 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)2413 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
2414 uint8_t *buf, size_t length, uint32_t *data_flags,
2415 nghttp2_data_source *source, void *user_data) {
2416 int rv;
2417 auto req = static_cast<Request *>(
2418 nghttp2_session_get_stream_user_data(session, stream_id));
2419 assert(req);
2420 int fd = source->fd;
2421 ssize_t nread;
2422
2423 while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2424 errno == EINTR)
2425 ;
2426
2427 if (nread == -1) {
2428 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2429 }
2430
2431 req->data_offset += nread;
2432
2433 if (req->data_offset == req->data_length) {
2434 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2435 if (!config.trailer.empty()) {
2436 std::vector<nghttp2_nv> nva;
2437 nva.reserve(config.trailer.size());
2438 for (auto &kv : config.trailer) {
2439 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
2440 }
2441 rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
2442 if (rv != 0) {
2443 if (nghttp2_is_fatal(rv)) {
2444 return NGHTTP2_ERR_CALLBACK_FAILURE;
2445 }
2446 } else {
2447 *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2448 }
2449 }
2450
2451 return nread;
2452 }
2453
2454 if (req->data_offset > req->data_length || nread == 0) {
2455 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2456 }
2457
2458 return nread;
2459 }
2460 } // namespace
2461
2462 namespace {
run(char ** uris,int n)2463 int run(char **uris, int n) {
2464 nghttp2_session_callbacks *callbacks;
2465
2466 nghttp2_session_callbacks_new(&callbacks);
2467 auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2468
2469 nghttp2_session_callbacks_set_on_stream_close_callback(
2470 callbacks, on_stream_close_callback);
2471
2472 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2473 on_frame_recv_callback2);
2474
2475 if (config.verbose) {
2476 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2477 callbacks, verbose_on_invalid_frame_recv_callback);
2478
2479 nghttp2_session_callbacks_set_error_callback2(callbacks,
2480 verbose_error_callback);
2481 }
2482
2483 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2484 callbacks, on_data_chunk_recv_callback);
2485
2486 nghttp2_session_callbacks_set_on_begin_headers_callback(
2487 callbacks, on_begin_headers_callback);
2488
2489 nghttp2_session_callbacks_set_on_header_callback(callbacks,
2490 on_header_callback);
2491
2492 nghttp2_session_callbacks_set_before_frame_send_callback(
2493 callbacks, before_frame_send_callback);
2494
2495 nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
2496 on_frame_send_callback);
2497
2498 nghttp2_session_callbacks_set_on_frame_not_send_callback(
2499 callbacks, on_frame_not_send_callback);
2500
2501 if (config.padding) {
2502 nghttp2_session_callbacks_set_select_padding_callback(
2503 callbacks, select_padding_callback);
2504 }
2505
2506 std::string prev_scheme;
2507 std::string prev_host;
2508 uint16_t prev_port = 0;
2509 int failures = 0;
2510 int data_fd = -1;
2511 nghttp2_data_provider data_prd;
2512 struct stat data_stat;
2513
2514 if (!config.datafile.empty()) {
2515 if (config.datafile == "-") {
2516 if (fstat(0, &data_stat) == 0 &&
2517 (data_stat.st_mode & S_IFMT) == S_IFREG) {
2518 // use STDIN if it is a regular file
2519 data_fd = 0;
2520 } else {
2521 // copy the contents of STDIN to a temporary file
2522 char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2523 data_fd = mkstemp(tempfn);
2524 if (data_fd == -1) {
2525 std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2526 << std::endl;
2527 return 1;
2528 }
2529 if (unlink(tempfn) != 0) {
2530 std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2531 << std::endl;
2532 }
2533 while (1) {
2534 std::array<char, 1_k> buf;
2535 ssize_t rret, wret;
2536 while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2537 errno == EINTR)
2538 ;
2539 if (rret == 0)
2540 break;
2541 if (rret == -1) {
2542 std::cerr << "[ERROR] I/O error while reading from STDIN"
2543 << std::endl;
2544 return 1;
2545 }
2546 while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2547 errno == EINTR)
2548 ;
2549 if (wret != rret) {
2550 std::cerr << "[ERROR] I/O error while writing to temporary file"
2551 << std::endl;
2552 return 1;
2553 }
2554 }
2555 if (fstat(data_fd, &data_stat) == -1) {
2556 close(data_fd);
2557 std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2558 return 1;
2559 }
2560 }
2561 } else {
2562 data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2563 if (data_fd == -1) {
2564 std::cerr << "[ERROR] Could not open file " << config.datafile
2565 << std::endl;
2566 return 1;
2567 }
2568 if (fstat(data_fd, &data_stat) == -1) {
2569 close(data_fd);
2570 std::cerr << "[ERROR] Could not stat file " << config.datafile
2571 << std::endl;
2572 return 1;
2573 }
2574 }
2575 data_prd.source.fd = data_fd;
2576 data_prd.read_callback = file_read_callback;
2577 }
2578 std::vector<
2579 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2580 requests;
2581
2582 size_t next_weight_idx = 0;
2583
2584 for (int i = 0; i < n; ++i) {
2585 http_parser_url u{};
2586 auto uri = strip_fragment(uris[i]);
2587 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2588 ++next_weight_idx;
2589 std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
2590 continue;
2591 }
2592 if (!util::has_uri_field(u, UF_SCHEMA)) {
2593 ++next_weight_idx;
2594 std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
2595 << std::endl;
2596 continue;
2597 }
2598 auto port = util::has_uri_field(u, UF_PORT)
2599 ? u.port
2600 : util::get_default_port(uri.c_str(), u);
2601 auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
2602 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2603 host != prev_host || port != prev_port) {
2604 if (!requests.empty()) {
2605 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2606 callbacks) != 0) {
2607 ++failures;
2608 }
2609 requests.clear();
2610 }
2611 prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str();
2612 prev_host = std::move(host);
2613 prev_port = port;
2614 }
2615 requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2616 data_stat.st_size, config.weight[next_weight_idx++]);
2617 }
2618 if (!requests.empty()) {
2619 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2620 callbacks) != 0) {
2621 ++failures;
2622 }
2623 }
2624 return failures;
2625 }
2626 } // namespace
2627
2628 namespace {
print_version(std::ostream & out)2629 void print_version(std::ostream &out) {
2630 out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2631 }
2632 } // namespace
2633
2634 namespace {
print_usage(std::ostream & out)2635 void print_usage(std::ostream &out) {
2636 out << R"(Usage: nghttp [OPTIONS]... <URI>...
2637 HTTP/2 client)"
2638 << std::endl;
2639 }
2640 } // namespace
2641
2642 namespace {
print_help(std::ostream & out)2643 void print_help(std::ostream &out) {
2644 print_usage(out);
2645 out << R"(
2646 <URI> Specify URI to access.
2647 Options:
2648 -v, --verbose
2649 Print debug information such as reception and
2650 transmission of frames and name/value pairs. Specifying
2651 this option multiple times increases verbosity.
2652 -n, --null-out
2653 Discard downloaded data.
2654 -O, --remote-name
2655 Save download data in the current directory. The
2656 filename is derived from URI. If URI ends with '/',
2657 'index.html' is used as a filename. Not implemented
2658 yet.
2659 -t, --timeout=<DURATION>
2660 Timeout each request after <DURATION>. Set 0 to disable
2661 timeout.
2662 -w, --window-bits=<N>
2663 Sets the stream level initial window size to 2**<N>-1.
2664 -W, --connection-window-bits=<N>
2665 Sets the connection level initial window size to
2666 2**<N>-1.
2667 -a, --get-assets
2668 Download assets such as stylesheets, images and script
2669 files linked from the downloaded resource. Only links
2670 whose origins are the same with the linking resource
2671 will be downloaded. nghttp prioritizes resources using
2672 HTTP/2 dependency based priority. The priority order,
2673 from highest to lowest, is html itself, css, javascript
2674 and images.
2675 -s, --stat Print statistics.
2676 -H, --header=<HEADER>
2677 Add a header to the requests. Example: -H':method: PUT'
2678 --trailer=<HEADER>
2679 Add a trailer header to the requests. <HEADER> must not
2680 include pseudo header field (header field name starting
2681 with ':'). To send trailer, one must use -d option to
2682 send request body. Example: --trailer 'foo: bar'.
2683 --cert=<CERT>
2684 Use the specified client certificate file. The file
2685 must be in PEM format.
2686 --key=<KEY> Use the client private key file. The file must be in
2687 PEM format.
2688 -d, --data=<PATH>
2689 Post FILE to server. If '-' is given, data will be read
2690 from stdin.
2691 -m, --multiply=<N>
2692 Request each URI <N> times. By default, same URI is not
2693 requested twice. This option disables it too.
2694 -u, --upgrade
2695 Perform HTTP Upgrade for HTTP/2. This option is ignored
2696 if the request URI has https scheme. If -d is used, the
2697 HTTP upgrade request is performed with OPTIONS method.
2698 -p, --weight=<WEIGHT>
2699 Sets weight of given URI. This option can be used
2700 multiple times, and N-th -p option sets weight of N-th
2701 URI in the command line. If the number of -p option is
2702 less than the number of URI, the last -p option value is
2703 repeated. If there is no -p option, default weight, 16,
2704 is assumed. The valid value range is
2705 [)"
2706 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
2707 -M, --peer-max-concurrent-streams=<N>
2708 Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
2709 remote endpoint as if it is received in SETTINGS frame.
2710 Default: 100
2711 -c, --header-table-size=<SIZE>
2712 Specify decoder header table size. If this option is
2713 used multiple times, and the minimum value among the
2714 given values except for last one is strictly less than
2715 the last value, that minimum value is set in SETTINGS
2716 frame payload before the last value, to simulate
2717 multiple header table size change.
2718 --encoder-header-table-size=<SIZE>
2719 Specify encoder header table size. The decoder (server)
2720 specifies the maximum dynamic table size it accepts.
2721 Then the negotiated dynamic table size is the minimum of
2722 this option value and the value which server specified.
2723 -b, --padding=<N>
2724 Add at most <N> bytes to a frame payload as padding.
2725 Specify 0 to disable padding.
2726 -r, --har=<PATH>
2727 Output HTTP transactions <PATH> in HAR format. If '-'
2728 is given, data is written to stdout.
2729 --color Force colored log output.
2730 --continuation
2731 Send large header to test CONTINUATION.
2732 --no-content-length
2733 Don't send content-length header field.
2734 --no-dep Don't send dependency based priority hint to server.
2735 --hexdump Display the incoming traffic in hexadecimal (Canonical
2736 hex+ASCII display). If SSL/TLS is used, decrypted data
2737 are used.
2738 --no-push Disable server push.
2739 --max-concurrent-streams=<N>
2740 The number of concurrent pushed streams this client
2741 accepts.
2742 --expect-continue
2743 Perform an Expect/Continue handshake: wait to send DATA
2744 (up to a short timeout) until the server sends a 100
2745 Continue interim response. This option is ignored unless
2746 combined with the -d option.
2747 -y, --no-verify-peer
2748 Suppress warning on server certificate verification
2749 failure.
2750 --version Display version information and exit.
2751 -h, --help Display this help and exit.
2752
2753 --
2754
2755 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2756 10 * 1024). Units are K, M and G (powers of 1024).
2757
2758 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2759 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2760 (hours, minutes, seconds and milliseconds, respectively). If a unit
2761 is omitted, a second is used as unit.)"
2762 << std::endl;
2763 }
2764 } // namespace
2765
main(int argc,char ** argv)2766 int main(int argc, char **argv) {
2767 tls::libssl_init();
2768
2769 bool color = false;
2770 while (1) {
2771 static int flag = 0;
2772 constexpr static option long_options[] = {
2773 {"verbose", no_argument, nullptr, 'v'},
2774 {"null-out", no_argument, nullptr, 'n'},
2775 {"remote-name", no_argument, nullptr, 'O'},
2776 {"timeout", required_argument, nullptr, 't'},
2777 {"window-bits", required_argument, nullptr, 'w'},
2778 {"connection-window-bits", required_argument, nullptr, 'W'},
2779 {"get-assets", no_argument, nullptr, 'a'},
2780 {"stat", no_argument, nullptr, 's'},
2781 {"help", no_argument, nullptr, 'h'},
2782 {"header", required_argument, nullptr, 'H'},
2783 {"data", required_argument, nullptr, 'd'},
2784 {"multiply", required_argument, nullptr, 'm'},
2785 {"upgrade", no_argument, nullptr, 'u'},
2786 {"weight", required_argument, nullptr, 'p'},
2787 {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2788 {"header-table-size", required_argument, nullptr, 'c'},
2789 {"padding", required_argument, nullptr, 'b'},
2790 {"har", required_argument, nullptr, 'r'},
2791 {"no-verify-peer", no_argument, nullptr, 'y'},
2792 {"cert", required_argument, &flag, 1},
2793 {"key", required_argument, &flag, 2},
2794 {"color", no_argument, &flag, 3},
2795 {"continuation", no_argument, &flag, 4},
2796 {"version", no_argument, &flag, 5},
2797 {"no-content-length", no_argument, &flag, 6},
2798 {"no-dep", no_argument, &flag, 7},
2799 {"trailer", required_argument, &flag, 9},
2800 {"hexdump", no_argument, &flag, 10},
2801 {"no-push", no_argument, &flag, 11},
2802 {"max-concurrent-streams", required_argument, &flag, 12},
2803 {"expect-continue", no_argument, &flag, 13},
2804 {"encoder-header-table-size", required_argument, &flag, 14},
2805 {nullptr, 0, nullptr, 0}};
2806 int option_index = 0;
2807 int c =
2808 getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
2809 &option_index);
2810 if (c == -1) {
2811 break;
2812 }
2813 switch (c) {
2814 case 'M':
2815 // peer-max-concurrent-streams option
2816 config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
2817 break;
2818 case 'O':
2819 config.remote_name = true;
2820 break;
2821 case 'h':
2822 print_help(std::cout);
2823 exit(EXIT_SUCCESS);
2824 case 'b':
2825 config.padding = strtol(optarg, nullptr, 10);
2826 break;
2827 case 'n':
2828 config.null_out = true;
2829 break;
2830 case 'p': {
2831 errno = 0;
2832 auto n = strtoul(optarg, nullptr, 10);
2833 if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2834 config.weight.push_back(n);
2835 } else {
2836 std::cerr << "-p: specify the integer in the range ["
2837 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2838 << "], inclusive" << std::endl;
2839 exit(EXIT_FAILURE);
2840 }
2841 break;
2842 }
2843 case 'r':
2844 #ifdef HAVE_JANSSON
2845 config.harfile = optarg;
2846 #else // !HAVE_JANSSON
2847 std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2848 << "the binary was not compiled with libjansson." << std::endl;
2849 #endif // !HAVE_JANSSON
2850 break;
2851 case 'v':
2852 ++config.verbose;
2853 break;
2854 case 't':
2855 config.timeout = util::parse_duration_with_unit(optarg);
2856 if (config.timeout == std::numeric_limits<double>::infinity()) {
2857 std::cerr << "-t: bad timeout value: " << optarg << std::endl;
2858 exit(EXIT_FAILURE);
2859 }
2860 break;
2861 case 'u':
2862 config.upgrade = true;
2863 break;
2864 case 'w':
2865 case 'W': {
2866 errno = 0;
2867 char *endptr = nullptr;
2868 unsigned long int n = strtoul(optarg, &endptr, 10);
2869 if (errno == 0 && *endptr == '\0' && n < 31) {
2870 if (c == 'w') {
2871 config.window_bits = n;
2872 } else {
2873 config.connection_window_bits = n;
2874 }
2875 } else {
2876 std::cerr << "-" << static_cast<char>(c)
2877 << ": specify the integer in the range [0, 30], inclusive"
2878 << std::endl;
2879 exit(EXIT_FAILURE);
2880 }
2881 break;
2882 }
2883 case 'H': {
2884 char *header = optarg;
2885 // Skip first possible ':' in the header name
2886 char *value = strchr(optarg + 1, ':');
2887 if (!value || (header[0] == ':' && header + 1 == value)) {
2888 std::cerr << "-H: invalid header: " << optarg << std::endl;
2889 exit(EXIT_FAILURE);
2890 }
2891 *value = 0;
2892 value++;
2893 while (isspace(*value)) {
2894 value++;
2895 }
2896 if (*value == 0) {
2897 // This could also be a valid case for suppressing a header
2898 // similar to curl
2899 std::cerr << "-H: invalid header - value missing: " << optarg
2900 << std::endl;
2901 exit(EXIT_FAILURE);
2902 }
2903 config.headers.emplace_back(header, value, false);
2904 util::inp_strlower(config.headers.back().name);
2905 break;
2906 }
2907 case 'a':
2908 #ifdef HAVE_LIBXML2
2909 config.get_assets = true;
2910 #else // !HAVE_LIBXML2
2911 std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2912 << "the binary was not compiled with libxml2." << std::endl;
2913 #endif // !HAVE_LIBXML2
2914 break;
2915 case 's':
2916 config.stat = true;
2917 break;
2918 case 'd':
2919 config.datafile = optarg;
2920 break;
2921 case 'm':
2922 config.multiply = strtoul(optarg, nullptr, 10);
2923 break;
2924 case 'c': {
2925 auto n = util::parse_uint_with_unit(optarg);
2926 if (n == -1) {
2927 std::cerr << "-c: Bad option value: " << optarg << std::endl;
2928 exit(EXIT_FAILURE);
2929 }
2930 if (n > std::numeric_limits<uint32_t>::max()) {
2931 std::cerr << "-c: Value too large. It should be less than or equal to "
2932 << std::numeric_limits<uint32_t>::max() << std::endl;
2933 exit(EXIT_FAILURE);
2934 }
2935 config.header_table_size = n;
2936 config.min_header_table_size = std::min(config.min_header_table_size, n);
2937 break;
2938 }
2939 case 'y':
2940 config.verify_peer = false;
2941 break;
2942 case '?':
2943 util::show_candidates(argv[optind - 1], long_options);
2944 exit(EXIT_FAILURE);
2945 case 0:
2946 switch (flag) {
2947 case 1:
2948 // cert option
2949 config.certfile = optarg;
2950 break;
2951 case 2:
2952 // key option
2953 config.keyfile = optarg;
2954 break;
2955 case 3:
2956 // color option
2957 color = true;
2958 break;
2959 case 4:
2960 // continuation option
2961 config.continuation = true;
2962 break;
2963 case 5:
2964 // version option
2965 print_version(std::cout);
2966 exit(EXIT_SUCCESS);
2967 case 6:
2968 // no-content-length option
2969 config.no_content_length = true;
2970 break;
2971 case 7:
2972 // no-dep option
2973 config.no_dep = true;
2974 break;
2975 case 9: {
2976 // trailer option
2977 auto header = optarg;
2978 auto value = strchr(optarg, ':');
2979 if (!value) {
2980 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
2981 exit(EXIT_FAILURE);
2982 }
2983 *value = 0;
2984 value++;
2985 while (isspace(*value)) {
2986 value++;
2987 }
2988 if (*value == 0) {
2989 // This could also be a valid case for suppressing a header
2990 // similar to curl
2991 std::cerr << "--trailer: invalid header - value missing: " << optarg
2992 << std::endl;
2993 exit(EXIT_FAILURE);
2994 }
2995 config.trailer.emplace_back(header, value, false);
2996 util::inp_strlower(config.trailer.back().name);
2997 break;
2998 }
2999 case 10:
3000 // hexdump option
3001 config.hexdump = true;
3002 break;
3003 case 11:
3004 // no-push option
3005 config.no_push = true;
3006 break;
3007 case 12:
3008 // max-concurrent-streams option
3009 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
3010 break;
3011 case 13:
3012 // expect-continue option
3013 config.expect_continue = true;
3014 break;
3015 case 14: {
3016 // encoder-header-table-size option
3017 auto n = util::parse_uint_with_unit(optarg);
3018 if (n == -1) {
3019 std::cerr << "--encoder-header-table-size: Bad option value: "
3020 << optarg << std::endl;
3021 exit(EXIT_FAILURE);
3022 }
3023 if (n > std::numeric_limits<uint32_t>::max()) {
3024 std::cerr << "--encoder-header-table-size: Value too large. It "
3025 "should be less than or equal to "
3026 << std::numeric_limits<uint32_t>::max() << std::endl;
3027 exit(EXIT_FAILURE);
3028 }
3029 config.encoder_header_table_size = n;
3030 break;
3031 }
3032 }
3033 break;
3034 default:
3035 break;
3036 }
3037 }
3038
3039 int32_t weight_to_fill;
3040 if (config.weight.empty()) {
3041 weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
3042 } else {
3043 weight_to_fill = config.weight.back();
3044 }
3045 config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
3046
3047 // Find scheme overridden by extra header fields.
3048 auto scheme_it =
3049 std::find_if(std::begin(config.headers), std::end(config.headers),
3050 [](const Header &nv) { return nv.name == ":scheme"; });
3051 if (scheme_it != std::end(config.headers)) {
3052 config.scheme_override = (*scheme_it).value;
3053 }
3054
3055 // Find host and port overridden by extra header fields.
3056 auto authority_it =
3057 std::find_if(std::begin(config.headers), std::end(config.headers),
3058 [](const Header &nv) { return nv.name == ":authority"; });
3059 if (authority_it == std::end(config.headers)) {
3060 authority_it =
3061 std::find_if(std::begin(config.headers), std::end(config.headers),
3062 [](const Header &nv) { return nv.name == "host"; });
3063 }
3064
3065 if (authority_it != std::end(config.headers)) {
3066 // authority_it may looks like "host:port".
3067 auto uri = "https://" + (*authority_it).value;
3068 http_parser_url u{};
3069 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
3070 std::cerr << "[ERROR] Could not parse authority in "
3071 << (*authority_it).name << ": " << (*authority_it).value
3072 << std::endl;
3073 exit(EXIT_FAILURE);
3074 }
3075
3076 config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str();
3077 if (util::has_uri_field(u, UF_PORT)) {
3078 config.port_override = u.port;
3079 }
3080 }
3081
3082 set_color_output(color || isatty(fileno(stdout)));
3083
3084 nghttp2_option_set_peer_max_concurrent_streams(
3085 config.http2_option, config.peer_max_concurrent_streams);
3086
3087 if (config.encoder_header_table_size != -1) {
3088 nghttp2_option_set_max_deflate_dynamic_table_size(
3089 config.http2_option, config.encoder_header_table_size);
3090 }
3091
3092 struct sigaction act {};
3093 act.sa_handler = SIG_IGN;
3094 sigaction(SIGPIPE, &act, nullptr);
3095 reset_timer();
3096 return run(argv + optind, argc - optind);
3097 }
3098
3099 } // namespace nghttp2
3100
main(int argc,char ** argv)3101 int main(int argc, char **argv) {
3102 return nghttp2::run_app(nghttp2::main, argc, argv);
3103 }
3104