• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "quiche/quic/core/http/quic_client_promised_info.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "quiche/quic/core/http/spdy_server_push_utils.h"
11 #include "quiche/quic/platform/api/quic_logging.h"
12 #include "quiche/spdy/core/spdy_protocol.h"
13 
14 using spdy::Http2HeaderBlock;
15 
16 namespace quic {
17 
QuicClientPromisedInfo(QuicSpdyClientSessionBase * session,QuicStreamId id,std::string url)18 QuicClientPromisedInfo::QuicClientPromisedInfo(
19     QuicSpdyClientSessionBase* session, QuicStreamId id, std::string url)
20     : session_(session),
21       id_(id),
22       url_(std::move(url)),
23       client_request_delegate_(nullptr) {}
24 
~QuicClientPromisedInfo()25 QuicClientPromisedInfo::~QuicClientPromisedInfo() {
26   if (cleanup_alarm_ != nullptr) {
27     cleanup_alarm_->PermanentCancel();
28   }
29 }
30 
OnAlarm()31 void QuicClientPromisedInfo::CleanupAlarm::OnAlarm() {
32   QUIC_DVLOG(1) << "self GC alarm for stream " << promised_->id_;
33   promised_->session()->OnPushStreamTimedOut(promised_->id_);
34   promised_->Reset(QUIC_PUSH_STREAM_TIMED_OUT);
35 }
36 
Init()37 void QuicClientPromisedInfo::Init() {
38   cleanup_alarm_.reset(session_->connection()->alarm_factory()->CreateAlarm(
39       new QuicClientPromisedInfo::CleanupAlarm(this)));
40   cleanup_alarm_->Set(
41       session_->connection()->helper()->GetClock()->ApproximateNow() +
42       QuicTime::Delta::FromSeconds(kPushPromiseTimeoutSecs));
43 }
44 
OnPromiseHeaders(const Http2HeaderBlock & headers)45 bool QuicClientPromisedInfo::OnPromiseHeaders(const Http2HeaderBlock& headers) {
46   // RFC7540, Section 8.2, requests MUST be safe [RFC7231], Section
47   // 4.2.1.  GET and HEAD are the methods that are safe and required.
48   Http2HeaderBlock::const_iterator it = headers.find(spdy::kHttp2MethodHeader);
49   if (it == headers.end()) {
50     QUIC_DVLOG(1) << "Promise for stream " << id_ << " has no method";
51     Reset(QUIC_INVALID_PROMISE_METHOD);
52     return false;
53   }
54   if (!(it->second == "GET" || it->second == "HEAD")) {
55     QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid method "
56                   << it->second;
57     Reset(QUIC_INVALID_PROMISE_METHOD);
58     return false;
59   }
60   if (!SpdyServerPushUtils::PromisedUrlIsValid(headers)) {
61     QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL "
62                   << url_;
63     Reset(QUIC_INVALID_PROMISE_URL);
64     return false;
65   }
66   if (!session_->IsAuthorized(
67           SpdyServerPushUtils::GetPromisedHostNameFromHeaders(headers))) {
68     Reset(QUIC_UNAUTHORIZED_PROMISE_URL);
69     return false;
70   }
71   request_headers_ = headers.Clone();
72   return true;
73 }
74 
OnResponseHeaders(const Http2HeaderBlock & headers)75 void QuicClientPromisedInfo::OnResponseHeaders(
76     const Http2HeaderBlock& headers) {
77   response_headers_ = std::make_unique<Http2HeaderBlock>(headers.Clone());
78   if (client_request_delegate_) {
79     // We already have a client request waiting.
80     FinalValidation();
81   }
82 }
83 
Reset(QuicRstStreamErrorCode error_code)84 void QuicClientPromisedInfo::Reset(QuicRstStreamErrorCode error_code) {
85   QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
86   session_->ResetPromised(id_, error_code);
87   session_->DeletePromised(this);
88   if (delegate) {
89     delegate->OnRendezvousResult(nullptr);
90   }
91 }
92 
FinalValidation()93 QuicAsyncStatus QuicClientPromisedInfo::FinalValidation() {
94   if (!client_request_delegate_->CheckVary(
95           client_request_headers_, request_headers_, *response_headers_)) {
96     Reset(QUIC_PROMISE_VARY_MISMATCH);
97     return QUIC_FAILURE;
98   }
99   QuicSpdyStream* stream = session_->GetPromisedStream(id_);
100   if (!stream) {
101     // This shouldn't be possible, as |ClientRequest| guards against
102     // closed stream for the synchronous case.  And in the
103     // asynchronous case, a RST can only be caught by |OnAlarm()|.
104     QUIC_BUG(quic_bug_10378_1) << "missing promised stream" << id_;
105   }
106   QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
107   session_->DeletePromised(this);
108   // Stream can start draining now
109   if (delegate) {
110     delegate->OnRendezvousResult(stream);
111   }
112   return QUIC_SUCCESS;
113 }
114 
HandleClientRequest(const Http2HeaderBlock & request_headers,QuicClientPushPromiseIndex::Delegate * delegate)115 QuicAsyncStatus QuicClientPromisedInfo::HandleClientRequest(
116     const Http2HeaderBlock& request_headers,
117     QuicClientPushPromiseIndex::Delegate* delegate) {
118   if (session_->IsClosedStream(id_)) {
119     // There was a RST on the response stream.
120     session_->DeletePromised(this);
121     return QUIC_FAILURE;
122   }
123 
124   if (is_validating()) {
125     // The push promise has already been matched to another request though
126     // pending for validation. Returns QUIC_FAILURE to the caller as it couldn't
127     // match a new request any more. This will not affect the validation of the
128     // other request.
129     return QUIC_FAILURE;
130   }
131 
132   client_request_delegate_ = delegate;
133   client_request_headers_ = request_headers.Clone();
134   if (response_headers_ == nullptr) {
135     return QUIC_PENDING;
136   }
137   return FinalValidation();
138 }
139 
Cancel()140 void QuicClientPromisedInfo::Cancel() {
141   // Don't fire OnRendezvousResult() for client initiated cancel.
142   client_request_delegate_ = nullptr;
143   Reset(QUIC_STREAM_CANCELLED);
144 }
145 
146 }  // namespace quic
147