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