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