| // Copyright (c) 2015 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. | 
 |  | 
 | #include "quic/tools/quic_spdy_client_base.h" | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "absl/strings/numbers.h" | 
 | #include "absl/strings/string_view.h" | 
 | #include "quic/core/crypto/quic_random.h" | 
 | #include "quic/core/http/spdy_utils.h" | 
 | #include "quic/core/quic_server_id.h" | 
 | #include "quic/platform/api/quic_flags.h" | 
 | #include "quic/platform/api/quic_logging.h" | 
 | #include "common/platform/api/quiche_text_utils.h" | 
 |  | 
 | using spdy::Http2HeaderBlock; | 
 |  | 
 | namespace quic { | 
 |  | 
 | void QuicSpdyClientBase::ClientQuicDataToResend::Resend() { | 
 |   client_->SendRequest(*headers_, body_, fin_); | 
 |   headers_ = nullptr; | 
 | } | 
 |  | 
 | QuicSpdyClientBase::QuicDataToResend::QuicDataToResend( | 
 |     std::unique_ptr<Http2HeaderBlock> headers, | 
 |     absl::string_view body, | 
 |     bool fin) | 
 |     : headers_(std::move(headers)), body_(body), fin_(fin) {} | 
 |  | 
 | QuicSpdyClientBase::QuicDataToResend::~QuicDataToResend() = default; | 
 |  | 
 | QuicSpdyClientBase::QuicSpdyClientBase( | 
 |     const QuicServerId& server_id, | 
 |     const ParsedQuicVersionVector& supported_versions, | 
 |     const QuicConfig& config, | 
 |     QuicConnectionHelperInterface* helper, | 
 |     QuicAlarmFactory* alarm_factory, | 
 |     std::unique_ptr<NetworkHelper> network_helper, | 
 |     std::unique_ptr<ProofVerifier> proof_verifier, | 
 |     std::unique_ptr<SessionCache> session_cache) | 
 |     : QuicClientBase(server_id, | 
 |                      supported_versions, | 
 |                      config, | 
 |                      helper, | 
 |                      alarm_factory, | 
 |                      std::move(network_helper), | 
 |                      std::move(proof_verifier), | 
 |                      std::move(session_cache)), | 
 |       store_response_(false), | 
 |       latest_response_code_(-1), | 
 |       max_allowed_push_id_(0) {} | 
 |  | 
 | QuicSpdyClientBase::~QuicSpdyClientBase() { | 
 |   // We own the push promise index. We need to explicitly kill | 
 |   // the session before the push promise index goes out of scope. | 
 |   ResetSession(); | 
 | } | 
 |  | 
 | QuicSpdyClientSession* QuicSpdyClientBase::client_session() { | 
 |   return static_cast<QuicSpdyClientSession*>(QuicClientBase::session()); | 
 | } | 
 |  | 
 | const QuicSpdyClientSession* QuicSpdyClientBase::client_session() const { | 
 |   return static_cast<const QuicSpdyClientSession*>(QuicClientBase::session()); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::InitializeSession() { | 
 |   client_session()->Initialize(); | 
 |   client_session()->CryptoConnect(); | 
 |   if (max_allowed_push_id_ > 0 && | 
 |       VersionUsesHttp3(client_session()->transport_version())) { | 
 |     client_session()->SetMaxPushId(max_allowed_push_id_); | 
 |   } | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::OnClose(QuicSpdyStream* stream) { | 
 |   QUICHE_DCHECK(stream != nullptr); | 
 |   QuicSpdyClientStream* client_stream = | 
 |       static_cast<QuicSpdyClientStream*>(stream); | 
 |  | 
 |   const Http2HeaderBlock& response_headers = client_stream->response_headers(); | 
 |   if (response_listener_ != nullptr) { | 
 |     response_listener_->OnCompleteResponse(stream->id(), response_headers, | 
 |                                            client_stream->data()); | 
 |   } | 
 |  | 
 |   // Store response headers and body. | 
 |   if (store_response_) { | 
 |     auto status = response_headers.find(":status"); | 
 |     if (status == response_headers.end()) { | 
 |       QUIC_LOG(ERROR) << "Missing :status response header"; | 
 |     } else if (!absl::SimpleAtoi(status->second, &latest_response_code_)) { | 
 |       QUIC_LOG(ERROR) << "Invalid :status response header: " << status->second; | 
 |     } | 
 |     latest_response_headers_ = response_headers.DebugString(); | 
 |     preliminary_response_headers_ = | 
 |         client_stream->preliminary_headers().DebugString(); | 
 |     latest_response_header_block_ = response_headers.Clone(); | 
 |     latest_response_body_ = client_stream->data(); | 
 |     latest_response_trailers_ = | 
 |         client_stream->received_trailers().DebugString(); | 
 |   } | 
 | } | 
 |  | 
 | std::unique_ptr<QuicSession> QuicSpdyClientBase::CreateQuicClientSession( | 
 |     const quic::ParsedQuicVersionVector& supported_versions, | 
 |     QuicConnection* connection) { | 
 |   return std::make_unique<QuicSpdyClientSession>( | 
 |       *config(), supported_versions, connection, server_id(), crypto_config(), | 
 |       &push_promise_index_); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::SendRequest(const Http2HeaderBlock& headers, | 
 |                                      absl::string_view body, | 
 |                                      bool fin) { | 
 |   if (GetQuicFlag(FLAGS_quic_client_convert_http_header_name_to_lowercase)) { | 
 |     QUIC_CODE_COUNT(quic_client_convert_http_header_name_to_lowercase); | 
 |     Http2HeaderBlock sanitized_headers; | 
 |     for (const auto& p : headers) { | 
 |       sanitized_headers[quiche::QuicheTextUtils::ToLower(p.first)] = p.second; | 
 |     } | 
 |  | 
 |     SendRequestInternal(std::move(sanitized_headers), body, fin); | 
 |   } else { | 
 |     SendRequestInternal(headers.Clone(), body, fin); | 
 |   } | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::SendRequestInternal(Http2HeaderBlock sanitized_headers, | 
 |                                              absl::string_view body, | 
 |                                              bool fin) { | 
 |   QuicClientPushPromiseIndex::TryHandle* handle; | 
 |   QuicAsyncStatus rv = | 
 |       push_promise_index()->Try(sanitized_headers, this, &handle); | 
 |   if (rv == QUIC_SUCCESS) | 
 |     return; | 
 |  | 
 |   if (rv == QUIC_PENDING) { | 
 |     // May need to retry request if asynchronous rendezvous fails. | 
 |     AddPromiseDataToResend(sanitized_headers, body, fin); | 
 |     return; | 
 |   } | 
 |  | 
 |   QuicSpdyClientStream* stream = CreateClientStream(); | 
 |   if (stream == nullptr) { | 
 |     QUIC_BUG << "stream creation failed!"; | 
 |     return; | 
 |   } | 
 |   stream->SendRequest(std::move(sanitized_headers), body, fin); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::SendRequestAndWaitForResponse( | 
 |     const Http2HeaderBlock& headers, | 
 |     absl::string_view body, | 
 |     bool fin) { | 
 |   SendRequest(headers, body, fin); | 
 |   while (WaitForEvents()) { | 
 |   } | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::SendRequestsAndWaitForResponse( | 
 |     const std::vector<std::string>& url_list) { | 
 |   for (size_t i = 0; i < url_list.size(); ++i) { | 
 |     Http2HeaderBlock headers; | 
 |     if (!SpdyUtils::PopulateHeaderBlockFromUrl(url_list[i], &headers)) { | 
 |       QUIC_BUG << "Unable to create request"; | 
 |       continue; | 
 |     } | 
 |     SendRequest(headers, "", true); | 
 |   } | 
 |   while (WaitForEvents()) { | 
 |   } | 
 | } | 
 |  | 
 | QuicSpdyClientStream* QuicSpdyClientBase::CreateClientStream() { | 
 |   if (!connected()) { | 
 |     return nullptr; | 
 |   } | 
 |   if (VersionHasIetfQuicFrames(client_session()->transport_version())) { | 
 |     // Process MAX_STREAMS from peer or wait for liveness testing succeeds. | 
 |     while (!client_session()->CanOpenNextOutgoingBidirectionalStream()) { | 
 |       network_helper()->RunEventLoop(); | 
 |     } | 
 |   } | 
 |   auto* stream = static_cast<QuicSpdyClientStream*>( | 
 |       client_session()->CreateOutgoingBidirectionalStream()); | 
 |   if (stream) { | 
 |     stream->set_visitor(this); | 
 |   } | 
 |   return stream; | 
 | } | 
 |  | 
 | bool QuicSpdyClientBase::goaway_received() const { | 
 |   return client_session() && client_session()->goaway_received(); | 
 | } | 
 |  | 
 | bool QuicSpdyClientBase::EarlyDataAccepted() { | 
 |   return client_session()->EarlyDataAccepted(); | 
 | } | 
 |  | 
 | bool QuicSpdyClientBase::ReceivedInchoateReject() { | 
 |   return client_session()->ReceivedInchoateReject(); | 
 | } | 
 |  | 
 | int QuicSpdyClientBase::GetNumSentClientHellosFromSession() { | 
 |   return client_session()->GetNumSentClientHellos(); | 
 | } | 
 |  | 
 | int QuicSpdyClientBase::GetNumReceivedServerConfigUpdatesFromSession() { | 
 |   return client_session()->GetNumReceivedServerConfigUpdates(); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::MaybeAddQuicDataToResend( | 
 |     std::unique_ptr<QuicDataToResend> data_to_resend) { | 
 |   data_to_resend_on_connect_.push_back(std::move(data_to_resend)); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::ClearDataToResend() { | 
 |   data_to_resend_on_connect_.clear(); | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::ResendSavedData() { | 
 |   // Calling Resend will re-enqueue the data, so swap out | 
 |   //  data_to_resend_on_connect_ before iterating. | 
 |   std::vector<std::unique_ptr<QuicDataToResend>> old_data; | 
 |   old_data.swap(data_to_resend_on_connect_); | 
 |   for (const auto& data : old_data) { | 
 |     data->Resend(); | 
 |   } | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::AddPromiseDataToResend(const Http2HeaderBlock& headers, | 
 |                                                 absl::string_view body, | 
 |                                                 bool fin) { | 
 |   std::unique_ptr<Http2HeaderBlock> new_headers( | 
 |       new Http2HeaderBlock(headers.Clone())); | 
 |   push_promise_data_to_resend_.reset( | 
 |       new ClientQuicDataToResend(std::move(new_headers), body, fin, this)); | 
 | } | 
 |  | 
 | bool QuicSpdyClientBase::CheckVary( | 
 |     const Http2HeaderBlock& /*client_request*/, | 
 |     const Http2HeaderBlock& /*promise_request*/, | 
 |     const Http2HeaderBlock& /*promise_response*/) { | 
 |   return true; | 
 | } | 
 |  | 
 | void QuicSpdyClientBase::OnRendezvousResult(QuicSpdyStream* stream) { | 
 |   std::unique_ptr<ClientQuicDataToResend> data_to_resend = | 
 |       std::move(push_promise_data_to_resend_); | 
 |   if (stream) { | 
 |     stream->set_visitor(this); | 
 |     stream->OnBodyAvailable(); | 
 |   } else if (data_to_resend) { | 
 |     data_to_resend->Resend(); | 
 |   } | 
 | } | 
 |  | 
 | int QuicSpdyClientBase::latest_response_code() const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return latest_response_code_; | 
 | } | 
 |  | 
 | const std::string& QuicSpdyClientBase::latest_response_headers() const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return latest_response_headers_; | 
 | } | 
 |  | 
 | const std::string& QuicSpdyClientBase::preliminary_response_headers() const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return preliminary_response_headers_; | 
 | } | 
 |  | 
 | const Http2HeaderBlock& QuicSpdyClientBase::latest_response_header_block() | 
 |     const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return latest_response_header_block_; | 
 | } | 
 |  | 
 | const std::string& QuicSpdyClientBase::latest_response_body() const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return latest_response_body_; | 
 | } | 
 |  | 
 | const std::string& QuicSpdyClientBase::latest_response_trailers() const { | 
 |   QUIC_BUG_IF(!store_response_) << "Response not stored!"; | 
 |   return latest_response_trailers_; | 
 | } | 
 |  | 
 | bool QuicSpdyClientBase::HasActiveRequests() { | 
 |   return client_session()->HasActiveRequestStreams(); | 
 | } | 
 |  | 
 | }  // namespace quic |