| // 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 "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h" | |
| #include <string> | |
| #include <utility> | |
| #include "net/third_party/quiche/src/quic/core/http/spdy_server_push_utils.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" | |
| #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" | |
| using spdy::SpdyHeaderBlock; | |
| 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() {} | |
| 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 SpdyHeaderBlock& 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. | |
| SpdyHeaderBlock::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 SpdyHeaderBlock& headers) { | |
| response_headers_ = std::make_unique<SpdyHeaderBlock>(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 << "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 SpdyHeaderBlock& 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 |