| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | // A binary wrapper for QuicClient. | 
 | // Connects to a host using QUIC, sends a request to the provided URL, and | 
 | // displays the response. | 
 | // | 
 | // Some usage examples: | 
 | // | 
 | //   TODO(rtenneti): make --host optional by getting IP Address of URL's host. | 
 | // | 
 | //   Get IP address of the www.google.com | 
 | //   IP=`dig www.google.com +short | head -1` | 
 | // | 
 | // Standard request/response: | 
 | //   quic_client http://www.google.com  --host=${IP} | 
 | //   quic_client http://www.google.com --quiet  --host=${IP} | 
 | //   quic_client https://www.google.com --port=443  --host=${IP} | 
 | // | 
 | // Use a specific version: | 
 | //   quic_client http://www.google.com --quic_version=23  --host=${IP} | 
 | // | 
 | // Send a POST instead of a GET: | 
 | //   quic_client http://www.google.com --body="this is a POST body" --host=${IP} | 
 | // | 
 | // Append additional headers to the request: | 
 | //   quic_client http://www.google.com  --host=${IP} | 
 | //               --headers="Header-A: 1234; Header-B: 5678" | 
 | // | 
 | // Connect to a host different to the URL being requested: | 
 | //   Get IP address of the www.google.com | 
 | //   IP=`dig www.google.com +short | head -1` | 
 | //   quic_client mail.google.com --host=${IP} | 
 | // | 
 | // Try to connect to a host which does not speak QUIC: | 
 | //   Get IP address of the www.example.com | 
 | //   IP=`dig www.example.com +short | head -1` | 
 | //   quic_client http://www.example.com --host=${IP} | 
 |  | 
 | #include <iostream> | 
 |  | 
 | #include "base/at_exit.h" | 
 | #include "base/command_line.h" | 
 | #include "base/logging.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/task/task_scheduler/task_scheduler.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/base/privacy_mode.h" | 
 | #include "net/cert/cert_verifier.h" | 
 | #include "net/cert/ct_log_verifier.h" | 
 | #include "net/cert/ct_policy_enforcer.h" | 
 | #include "net/cert/multi_log_ct_verifier.h" | 
 | #include "net/http/transport_security_state.h" | 
 | #include "net/quic/crypto/proof_verifier_chromium.h" | 
 | #include "net/spdy/spdy_http_utils.h" | 
 | #include "net/third_party/quic/core/quic_error_codes.h" | 
 | #include "net/third_party/quic/core/quic_packets.h" | 
 | #include "net/third_party/quic/core/quic_server_id.h" | 
 | #include "net/third_party/quic/platform/api/quic_socket_address.h" | 
 | #include "net/third_party/quic/platform/api/quic_str_cat.h" | 
 | #include "net/third_party/quic/platform/api/quic_string_piece.h" | 
 | #include "net/third_party/quic/platform/api/quic_text_utils.h" | 
 | #include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" | 
 | #include "net/tools/quic/quic_simple_client.h" | 
 | #include "net/tools/quic/synchronous_host_resolver.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | using net::CertVerifier; | 
 | using net::CTVerifier; | 
 | using net::MultiLogCTVerifier; | 
 | using quic::ProofVerifier; | 
 | using net::ProofVerifierChromium; | 
 | using quic::QuicStringPiece; | 
 | using quic::QuicTextUtils; | 
 | using net::TransportSecurityState; | 
 | using spdy::SpdyHeaderBlock; | 
 | using std::cout; | 
 | using std::cerr; | 
 | using std::endl; | 
 | using std::string; | 
 |  | 
 | // The IP or hostname the quic client will connect to. | 
 | string FLAGS_host = ""; | 
 | // The port to connect to. | 
 | int32_t FLAGS_port = 0; | 
 | // If set, send a POST with this body. | 
 | string FLAGS_body = ""; | 
 | // If set, contents are converted from hex to ascii, before sending as body of | 
 | // a POST. e.g. --body_hex=\"68656c6c6f\" | 
 | string FLAGS_body_hex = ""; | 
 | // A semicolon separated list of key:value pairs to add to request headers. | 
 | string FLAGS_headers = ""; | 
 | // Set to true for a quieter output experience. | 
 | bool FLAGS_quiet = false; | 
 | // QUIC version to speak, e.g. 21. If not set, then all available versions are | 
 | // offered in the handshake. | 
 | int32_t FLAGS_quic_version = -1; | 
 | // If true, a version mismatch in the handshake is not considered a failure. | 
 | // Useful for probing a server to determine if it speaks any version of QUIC. | 
 | bool FLAGS_version_mismatch_ok = false; | 
 | // If true, an HTTP response code of 3xx is considered to be a successful | 
 | // response, otherwise a failure. | 
 | bool FLAGS_redirect_is_success = true; | 
 | // Initial MTU of the connection. | 
 | int32_t FLAGS_initial_mtu = 0; | 
 |  | 
 | class FakeProofVerifier : public quic::ProofVerifier { | 
 |  public: | 
 |   quic::QuicAsyncStatus VerifyProof( | 
 |       const string& hostname, | 
 |       const uint16_t port, | 
 |       const string& server_config, | 
 |       quic::QuicTransportVersion quic_version, | 
 |       quic::QuicStringPiece chlo_hash, | 
 |       const std::vector<string>& certs, | 
 |       const string& cert_sct, | 
 |       const string& signature, | 
 |       const quic::ProofVerifyContext* context, | 
 |       string* error_details, | 
 |       std::unique_ptr<quic::ProofVerifyDetails>* details, | 
 |       std::unique_ptr<quic::ProofVerifierCallback> callback) override { | 
 |     return quic::QUIC_SUCCESS; | 
 |   } | 
 |  | 
 |   quic::QuicAsyncStatus VerifyCertChain( | 
 |       const std::string& hostname, | 
 |       const std::vector<std::string>& certs, | 
 |       const quic::ProofVerifyContext* verify_context, | 
 |       std::string* error_details, | 
 |       std::unique_ptr<quic::ProofVerifyDetails>* verify_details, | 
 |       std::unique_ptr<quic::ProofVerifierCallback> callback) override { | 
 |     return quic::QUIC_SUCCESS; | 
 |   } | 
 |  | 
 |   std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override { | 
 |     return nullptr; | 
 |   } | 
 | }; | 
 |  | 
 | int main(int argc, char* argv[]) { | 
 |   base::CommandLine::Init(argc, argv); | 
 |   base::CommandLine* line = base::CommandLine::ForCurrentProcess(); | 
 |   const base::CommandLine::StringVector& urls = line->GetArgs(); | 
 |   base::TaskScheduler::CreateAndStartWithDefaultParams("quic_client"); | 
 |  | 
 |   logging::LoggingSettings settings; | 
 |   settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; | 
 |   CHECK(logging::InitLogging(settings)); | 
 |  | 
 |   if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) { | 
 |     const char* help_str = | 
 |         "Usage: quic_client [options] <url>\n" | 
 |         "\n" | 
 |         "<url> with scheme must be provided (e.g. http://www.google.com)\n\n" | 
 |         "Options:\n" | 
 |         "-h, --help                  show this help message and exit\n" | 
 |         "--host=<host>               specify the IP address of the hostname to " | 
 |         "connect to\n" | 
 |         "--port=<port>               specify the port to connect to\n" | 
 |         "--body=<body>               specify the body to post\n" | 
 |         "--body_hex=<body_hex>       specify the body_hex to be printed out\n" | 
 |         "--headers=<headers>         specify a semicolon separated list of " | 
 |         "key:value pairs to add to request headers\n" | 
 |         "--quiet                     specify for a quieter output experience\n" | 
 |         "--quic-version=<quic version> specify QUIC version to speak\n" | 
 |         "--version_mismatch_ok       if specified a version mismatch in the " | 
 |         "handshake is not considered a failure\n" | 
 |         "--redirect_is_success       if specified an HTTP response code of 3xx " | 
 |         "is considered to be a successful response, otherwise a failure\n" | 
 |         "--initial_mtu=<initial_mtu> specify the initial MTU of the connection" | 
 |         "\n" | 
 |         "--disable-certificate-verification do not verify certificates\n"; | 
 |     cout << help_str; | 
 |     exit(0); | 
 |   } | 
 |   if (line->HasSwitch("host")) { | 
 |     FLAGS_host = line->GetSwitchValueASCII("host"); | 
 |   } | 
 |   if (line->HasSwitch("port")) { | 
 |     if (!base::StringToInt(line->GetSwitchValueASCII("port"), &FLAGS_port)) { | 
 |       std::cerr << "--port must be an integer\n"; | 
 |       return 1; | 
 |     } | 
 |   } | 
 |   if (line->HasSwitch("body")) { | 
 |     FLAGS_body = line->GetSwitchValueASCII("body"); | 
 |   } | 
 |   if (line->HasSwitch("body_hex")) { | 
 |     FLAGS_body_hex = line->GetSwitchValueASCII("body_hex"); | 
 |   } | 
 |   if (line->HasSwitch("headers")) { | 
 |     FLAGS_headers = line->GetSwitchValueASCII("headers"); | 
 |   } | 
 |   if (line->HasSwitch("quiet")) { | 
 |     FLAGS_quiet = true; | 
 |   } | 
 |   if (line->HasSwitch("quic-version")) { | 
 |     int quic_version; | 
 |     if (base::StringToInt(line->GetSwitchValueASCII("quic-version"), | 
 |                           &quic_version)) { | 
 |       FLAGS_quic_version = quic_version; | 
 |     } | 
 |   } | 
 |   if (line->HasSwitch("version_mismatch_ok")) { | 
 |     FLAGS_version_mismatch_ok = true; | 
 |   } | 
 |   if (line->HasSwitch("redirect_is_success")) { | 
 |     FLAGS_redirect_is_success = true; | 
 |   } | 
 |   if (line->HasSwitch("initial_mtu")) { | 
 |     if (!base::StringToInt(line->GetSwitchValueASCII("initial_mtu"), | 
 |                            &FLAGS_initial_mtu)) { | 
 |       std::cerr << "--initial_mtu must be an integer\n"; | 
 |       return 1; | 
 |     } | 
 |   } | 
 |  | 
 |   VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port | 
 |           << " body: " << FLAGS_body << " headers: " << FLAGS_headers | 
 |           << " quiet: " << FLAGS_quiet | 
 |           << " quic-version: " << FLAGS_quic_version | 
 |           << " version_mismatch_ok: " << FLAGS_version_mismatch_ok | 
 |           << " redirect_is_success: " << FLAGS_redirect_is_success | 
 |           << " initial_mtu: " << FLAGS_initial_mtu; | 
 |  | 
 |   base::AtExitManager exit_manager; | 
 |   base::MessageLoopForIO message_loop; | 
 |  | 
 |   // Determine IP address to connect to from supplied hostname. | 
 |   quic::QuicIpAddress ip_addr; | 
 |  | 
 |   GURL url(urls[0]); | 
 |   string host = FLAGS_host; | 
 |   if (host.empty()) { | 
 |     host = url.host(); | 
 |   } | 
 |   int port = FLAGS_port; | 
 |   if (port == 0) { | 
 |     port = url.EffectiveIntPort(); | 
 |   } | 
 |   if (!ip_addr.FromString(host)) { | 
 |     net::AddressList addresses; | 
 |     int rv = net::SynchronousHostResolver::Resolve(host, &addresses); | 
 |     if (rv != net::OK) { | 
 |       LOG(ERROR) << "Unable to resolve '" << host | 
 |                  << "' : " << net::ErrorToShortString(rv); | 
 |       return 1; | 
 |     } | 
 |     ip_addr = | 
 |         quic::QuicIpAddress(quic::QuicIpAddressImpl(addresses[0].address())); | 
 |   } | 
 |  | 
 |   string host_port = quic::QuicStrCat(ip_addr.ToString(), ":", port); | 
 |   VLOG(1) << "Resolved " << host << " to " << host_port << endl; | 
 |  | 
 |   // Build the client, and try to connect. | 
 |   quic::QuicServerId server_id(url.host(), url.EffectiveIntPort(), | 
 |                                net::PRIVACY_MODE_DISABLED); | 
 |   quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions(); | 
 |   if (FLAGS_quic_version != -1) { | 
 |     versions.clear(); | 
 |     versions.push_back(quic::ParsedQuicVersion( | 
 |         quic::PROTOCOL_QUIC_CRYPTO, | 
 |         static_cast<quic::QuicTransportVersion>(FLAGS_quic_version))); | 
 |   } | 
 |   // For secure QUIC we need to verify the cert chain. | 
 |   std::unique_ptr<CertVerifier> cert_verifier(CertVerifier::CreateDefault()); | 
 |   std::unique_ptr<TransportSecurityState> transport_security_state( | 
 |       new TransportSecurityState); | 
 |   std::unique_ptr<MultiLogCTVerifier> ct_verifier(new MultiLogCTVerifier()); | 
 |   std::unique_ptr<net::CTPolicyEnforcer> ct_policy_enforcer( | 
 |       new net::DefaultCTPolicyEnforcer()); | 
 |   std::unique_ptr<quic::ProofVerifier> proof_verifier; | 
 |   if (line->HasSwitch("disable-certificate-verification")) { | 
 |     proof_verifier.reset(new FakeProofVerifier()); | 
 |   } else { | 
 |     proof_verifier.reset(new ProofVerifierChromium( | 
 |         cert_verifier.get(), ct_policy_enforcer.get(), | 
 |         transport_security_state.get(), ct_verifier.get())); | 
 |   } | 
 |   net::QuicSimpleClient client(quic::QuicSocketAddress(ip_addr, port), | 
 |                                server_id, versions, std::move(proof_verifier)); | 
 |   client.set_initial_max_packet_length( | 
 |       FLAGS_initial_mtu != 0 ? FLAGS_initial_mtu : quic::kDefaultMaxPacketSize); | 
 |   if (!client.Initialize()) { | 
 |     cerr << "Failed to initialize client." << endl; | 
 |     return 1; | 
 |   } | 
 |   if (!client.Connect()) { | 
 |     quic::QuicErrorCode error = client.session()->error(); | 
 |     if (FLAGS_version_mismatch_ok && error == quic::QUIC_INVALID_VERSION) { | 
 |       cout << "Server talks QUIC, but none of the versions supported by " | 
 |            << "this client: " << ParsedQuicVersionVectorToString(versions) | 
 |            << endl; | 
 |       // Version mismatch is not deemed a failure. | 
 |       return 0; | 
 |     } | 
 |     cerr << "Failed to connect to " << host_port | 
 |          << ". Error: " << quic::QuicErrorCodeToString(error) << endl; | 
 |     return 1; | 
 |   } | 
 |   cout << "Connected to " << host_port << endl; | 
 |  | 
 |   // Construct the string body from flags, if provided. | 
 |   string body = FLAGS_body; | 
 |   if (!FLAGS_body_hex.empty()) { | 
 |     DCHECK(FLAGS_body.empty()) << "Only set one of --body and --body_hex."; | 
 |     body = quic::QuicTextUtils::HexDecode(FLAGS_body_hex); | 
 |   } | 
 |  | 
 |   // Construct a GET or POST request for supplied URL. | 
 |   SpdyHeaderBlock header_block; | 
 |   header_block[":method"] = body.empty() ? "GET" : "POST"; | 
 |   header_block[":scheme"] = url.scheme(); | 
 |   header_block[":authority"] = url.host(); | 
 |   header_block[":path"] = url.path(); | 
 |  | 
 |   // Append any additional headers supplied on the command line. | 
 |   for (quic::QuicStringPiece sp : | 
 |        quic::QuicTextUtils::Split(FLAGS_headers, ';')) { | 
 |     quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp); | 
 |     if (sp.empty()) { | 
 |       continue; | 
 |     } | 
 |     std::vector<quic::QuicStringPiece> kv = quic::QuicTextUtils::Split(sp, ':'); | 
 |     quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]); | 
 |     quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]); | 
 |     header_block[kv[0]] = kv[1]; | 
 |   } | 
 |  | 
 |   // Make sure to store the response, for later output. | 
 |   client.set_store_response(true); | 
 |  | 
 |   // Send the request. | 
 |   client.SendRequestAndWaitForResponse(header_block, body, /*fin=*/true); | 
 |  | 
 |   // Print request and response details. | 
 |   if (!FLAGS_quiet) { | 
 |     cout << "Request:" << endl; | 
 |     cout << "headers:" << header_block.DebugString(); | 
 |     if (!FLAGS_body_hex.empty()) { | 
 |       // Print the user provided hex, rather than binary body. | 
 |       cout << "body:\n" | 
 |            << quic::QuicTextUtils::HexDump( | 
 |                   quic::QuicTextUtils::HexDecode(FLAGS_body_hex)) | 
 |            << endl; | 
 |     } else { | 
 |       cout << "body: " << body << endl; | 
 |     } | 
 |     cout << endl; | 
 |     cout << "Response:" << endl; | 
 |     cout << "headers: " << client.latest_response_headers() << endl; | 
 |     string response_body = client.latest_response_body(); | 
 |     if (!FLAGS_body_hex.empty()) { | 
 |       // Assume response is binary data. | 
 |       cout << "body:\n" << quic::QuicTextUtils::HexDump(response_body) << endl; | 
 |     } else { | 
 |       cout << "body: " << response_body << endl; | 
 |     } | 
 |     cout << "trailers: " << client.latest_response_trailers() << endl; | 
 |   } | 
 |  | 
 |   size_t response_code = client.latest_response_code(); | 
 |   if (response_code >= 200 && response_code < 300) { | 
 |     cout << "Request succeeded (" << response_code << ")." << endl; | 
 |     return 0; | 
 |   } else if (response_code >= 300 && response_code < 400) { | 
 |     if (FLAGS_redirect_is_success) { | 
 |       cout << "Request succeeded (redirect " << response_code << ")." << endl; | 
 |       return 0; | 
 |     } else { | 
 |       cout << "Request failed (redirect " << response_code << ")." << endl; | 
 |       return 1; | 
 |     } | 
 |   } else { | 
 |     cerr << "Request failed (" << response_code << ")." << endl; | 
 |     return 1; | 
 |   } | 
 | } |