| // Copyright (c) 2016 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 "quiche/quic/core/http/quic_client_promised_info.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "quiche/quic/core/http/spdy_server_push_utils.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/spdy/core/spdy_protocol.h" |
| |
| using spdy::Http2HeaderBlock; |
| |
| namespace quic { |
| |
| QuicClientPromisedInfo::QuicClientPromisedInfo( |
| QuicSpdyClientSessionBase* session, QuicStreamId id, std::string url) |
| : session_(session), |
| id_(id), |
| url_(std::move(url)), |
| client_request_delegate_(nullptr) {} |
| |
| QuicClientPromisedInfo::~QuicClientPromisedInfo() { |
| if (cleanup_alarm_ != nullptr) { |
| cleanup_alarm_->PermanentCancel(); |
| } |
| } |
| |
| void QuicClientPromisedInfo::CleanupAlarm::OnAlarm() { |
| QUIC_DVLOG(1) << "self GC alarm for stream " << promised_->id_; |
| promised_->session()->OnPushStreamTimedOut(promised_->id_); |
| promised_->Reset(QUIC_PUSH_STREAM_TIMED_OUT); |
| } |
| |
| void QuicClientPromisedInfo::Init() { |
| cleanup_alarm_.reset(session_->connection()->alarm_factory()->CreateAlarm( |
| new QuicClientPromisedInfo::CleanupAlarm(this))); |
| cleanup_alarm_->Set( |
| session_->connection()->helper()->GetClock()->ApproximateNow() + |
| QuicTime::Delta::FromSeconds(kPushPromiseTimeoutSecs)); |
| } |
| |
| bool QuicClientPromisedInfo::OnPromiseHeaders(const Http2HeaderBlock& headers) { |
| // RFC7540, Section 8.2, requests MUST be safe [RFC7231], Section |
| // 4.2.1. GET and HEAD are the methods that are safe and required. |
| Http2HeaderBlock::const_iterator it = headers.find(spdy::kHttp2MethodHeader); |
| if (it == headers.end()) { |
| QUIC_DVLOG(1) << "Promise for stream " << id_ << " has no method"; |
| Reset(QUIC_INVALID_PROMISE_METHOD); |
| return false; |
| } |
| if (!(it->second == "GET" || it->second == "HEAD")) { |
| QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid method " |
| << it->second; |
| Reset(QUIC_INVALID_PROMISE_METHOD); |
| return false; |
| } |
| if (!SpdyServerPushUtils::PromisedUrlIsValid(headers)) { |
| QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL " |
| << url_; |
| Reset(QUIC_INVALID_PROMISE_URL); |
| return false; |
| } |
| if (!session_->IsAuthorized( |
| SpdyServerPushUtils::GetPromisedHostNameFromHeaders(headers))) { |
| Reset(QUIC_UNAUTHORIZED_PROMISE_URL); |
| return false; |
| } |
| request_headers_ = headers.Clone(); |
| return true; |
| } |
| |
| void QuicClientPromisedInfo::OnResponseHeaders( |
| const Http2HeaderBlock& headers) { |
| response_headers_ = std::make_unique<Http2HeaderBlock>(headers.Clone()); |
| if (client_request_delegate_) { |
| // We already have a client request waiting. |
| FinalValidation(); |
| } |
| } |
| |
| void QuicClientPromisedInfo::Reset(QuicRstStreamErrorCode error_code) { |
| QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_; |
| session_->ResetPromised(id_, error_code); |
| session_->DeletePromised(this); |
| if (delegate) { |
| delegate->OnRendezvousResult(nullptr); |
| } |
| } |
| |
| QuicAsyncStatus QuicClientPromisedInfo::FinalValidation() { |
| if (!client_request_delegate_->CheckVary( |
| client_request_headers_, request_headers_, *response_headers_)) { |
| Reset(QUIC_PROMISE_VARY_MISMATCH); |
| return QUIC_FAILURE; |
| } |
| QuicSpdyStream* stream = session_->GetPromisedStream(id_); |
| if (!stream) { |
| // This shouldn't be possible, as |ClientRequest| guards against |
| // closed stream for the synchronous case. And in the |
| // asynchronous case, a RST can only be caught by |OnAlarm()|. |
| QUIC_BUG(quic_bug_10378_1) << "missing promised stream" << id_; |
| } |
| QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_; |
| session_->DeletePromised(this); |
| // Stream can start draining now |
| if (delegate) { |
| delegate->OnRendezvousResult(stream); |
| } |
| return QUIC_SUCCESS; |
| } |
| |
| QuicAsyncStatus QuicClientPromisedInfo::HandleClientRequest( |
| const Http2HeaderBlock& request_headers, |
| QuicClientPushPromiseIndex::Delegate* delegate) { |
| if (session_->IsClosedStream(id_)) { |
| // There was a RST on the response stream. |
| session_->DeletePromised(this); |
| return QUIC_FAILURE; |
| } |
| |
| if (is_validating()) { |
| // The push promise has already been matched to another request though |
| // pending for validation. Returns QUIC_FAILURE to the caller as it couldn't |
| // match a new request any more. This will not affect the validation of the |
| // other request. |
| return QUIC_FAILURE; |
| } |
| |
| client_request_delegate_ = delegate; |
| client_request_headers_ = request_headers.Clone(); |
| if (response_headers_ == nullptr) { |
| return QUIC_PENDING; |
| } |
| return FinalValidation(); |
| } |
| |
| void QuicClientPromisedInfo::Cancel() { |
| // Don't fire OnRendezvousResult() for client initiated cancel. |
| client_request_delegate_ = nullptr; |
| Reset(QUIC_STREAM_CANCELLED); |
| } |
| |
| } // namespace quic |