1 // Copyright 2019 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 "third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.h"
6 
7 #include "base/callback.h"
8 #include "base/memory/weak_ptr.h"
9 #include "base/optional.h"
10 #include "mojo/public/cpp/bindings/remote.h"
11 #include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
12 #include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
13 #include "third_party/blink/public/platform/platform.h"
14 #include "third_party/blink/public/platform/task_type.h"
15 #include "third_party/blink/public/platform/web_url_loader.h"
16 #include "third_party/blink/public/platform/web_url_loader_client.h"
17 #include "third_party/blink/public/platform/web_url_loader_factory.h"
18 #include "third_party/blink/public/platform/web_url_response.h"
19 #include "third_party/blink/renderer/core/dom/document.h"
20 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
21 #include "third_party/blink/renderer/core/frame/local_frame.h"
22 #include "third_party/blink/renderer/core/frame/navigator.h"
23 #include "third_party/blink/renderer/core/inspector/console_message.h"
24 #include "third_party/blink/renderer/core/loader/alternate_signed_exchange_resource_info.h"
25 #include "third_party/blink/renderer/platform/heap/heap.h"
26 #include "third_party/blink/renderer/platform/loader/link_header.h"
27 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
28 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
29 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
30 #include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
31 #include "third_party/blink/renderer/platform/wtf/functional.h"
32 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
33 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
34 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
35 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
36 #include "third_party/blink/renderer/platform/wtf/vector.h"
37 
38 namespace blink {
39 
40 class PrefetchedSignedExchangeManager::PrefetchedSignedExchangeLoader
41     : public WebURLLoader {
42  public:
PrefetchedSignedExchangeLoader(const WebURLRequest & request,scoped_refptr<base::SingleThreadTaskRunner> task_runner)43   PrefetchedSignedExchangeLoader(
44       const WebURLRequest& request,
45       scoped_refptr<base::SingleThreadTaskRunner> task_runner)
46       : task_runner_(std::move(task_runner)) {
47     request_.CopyFrom(request);
48   }
49 
~PrefetchedSignedExchangeLoader()50   ~PrefetchedSignedExchangeLoader() override {}
51 
GetWeakPtr()52   base::WeakPtr<PrefetchedSignedExchangeLoader> GetWeakPtr() {
53     return weak_ptr_factory_.GetWeakPtr();
54   }
55 
SetURLLoader(std::unique_ptr<WebURLLoader> url_loader)56   void SetURLLoader(std::unique_ptr<WebURLLoader> url_loader) {
57     DCHECK(!url_loader_);
58     url_loader_ = std::move(url_loader);
59     ExecutePendingMethodCalls();
60   }
61 
request() const62   const WebURLRequest& request() const { return request_; }
63 
64   // WebURLLoader methods:
LoadSynchronously(std::unique_ptr<network::ResourceRequest> request,scoped_refptr<WebURLRequest::ExtraData> request_extra_data,int requestor_id,bool download_to_network_cache_only,bool pass_response_pipe_to_client,bool no_mime_sniffing,base::TimeDelta timeout_interval,WebURLLoaderClient * client,WebURLResponse & response,base::Optional<WebURLError> & error,WebData & data,int64_t & encoded_data_length,int64_t & encoded_body_length,WebBlobInfo & downloaded_blob)65   void LoadSynchronously(
66       std::unique_ptr<network::ResourceRequest> request,
67       scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
68       int requestor_id,
69       bool download_to_network_cache_only,
70       bool pass_response_pipe_to_client,
71       bool no_mime_sniffing,
72       base::TimeDelta timeout_interval,
73       WebURLLoaderClient* client,
74       WebURLResponse& response,
75       base::Optional<WebURLError>& error,
76       WebData& data,
77       int64_t& encoded_data_length,
78       int64_t& encoded_body_length,
79       WebBlobInfo& downloaded_blob) override {
80     NOTREACHED();
81   }
LoadAsynchronously(std::unique_ptr<network::ResourceRequest> request,scoped_refptr<WebURLRequest::ExtraData> request_extra_data,int requestor_id,bool download_to_network_cache_only,bool no_mime_sniffing,WebURLLoaderClient * client)82   void LoadAsynchronously(
83       std::unique_ptr<network::ResourceRequest> request,
84       scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
85       int requestor_id,
86       bool download_to_network_cache_only,
87       bool no_mime_sniffing,
88       WebURLLoaderClient* client) override {
89     if (url_loader_) {
90       url_loader_->LoadAsynchronously(
91           std::move(request), std::move(request_extra_data), requestor_id,
92           download_to_network_cache_only, no_mime_sniffing, client);
93       return;
94     }
95     // It is safe to use Unretained(client), because |client| is a
96     // ResourceLoader which owns |this|, and we are binding with weak ptr of
97     // |this| here.
98     pending_method_calls_.push(WTF::Bind(
99         &PrefetchedSignedExchangeLoader::LoadAsynchronously, GetWeakPtr(),
100         std::move(request), std::move(request_extra_data), requestor_id,
101         download_to_network_cache_only, no_mime_sniffing,
102         WTF::Unretained(client)));
103   }
SetDefersLoading(bool value)104   void SetDefersLoading(bool value) override {
105     if (url_loader_) {
106       url_loader_->SetDefersLoading(value);
107       return;
108     }
109     pending_method_calls_.push(
110         WTF::Bind(&PrefetchedSignedExchangeLoader::SetDefersLoading,
111                   GetWeakPtr(), value));
112   }
DidChangePriority(WebURLRequest::Priority new_priority,int intra_priority_value)113   void DidChangePriority(WebURLRequest::Priority new_priority,
114                          int intra_priority_value) override {
115     if (url_loader_) {
116       url_loader_->DidChangePriority(new_priority, intra_priority_value);
117       return;
118     }
119     pending_method_calls_.push(
120         WTF::Bind(&PrefetchedSignedExchangeLoader::DidChangePriority,
121                   GetWeakPtr(), new_priority, intra_priority_value));
122   }
GetTaskRunner()123   scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() override {
124     return task_runner_;
125   }
126 
127  private:
ExecutePendingMethodCalls()128   void ExecutePendingMethodCalls() {
129     std::queue<base::OnceClosure> pending_calls =
130         std::move(pending_method_calls_);
131     while (!pending_calls.empty()) {
132       std::move(pending_calls.front()).Run();
133       pending_calls.pop();
134     }
135   }
136 
137   WebURLRequest request_;
138   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
139   std::unique_ptr<WebURLLoader> url_loader_;
140   std::queue<base::OnceClosure> pending_method_calls_;
141 
142   base::WeakPtrFactory<PrefetchedSignedExchangeLoader> weak_ptr_factory_{this};
143 
144   DISALLOW_COPY_AND_ASSIGN(PrefetchedSignedExchangeLoader);
145 };
146 
147 // static
MaybeCreate(LocalFrame * frame,const String & outer_link_header,const String & inner_link_header,WebVector<std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>> prefetched_signed_exchanges)148 PrefetchedSignedExchangeManager* PrefetchedSignedExchangeManager::MaybeCreate(
149     LocalFrame* frame,
150     const String& outer_link_header,
151     const String& inner_link_header,
152     WebVector<std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
153         prefetched_signed_exchanges) {
154   if (prefetched_signed_exchanges.empty())
155     return nullptr;
156   std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources =
157       AlternateSignedExchangeResourceInfo::CreateIfValid(outer_link_header,
158                                                          inner_link_header);
159   if (!alternative_resources) {
160     // There is no "allowed-alt-sxg" link header for this resource.
161     return nullptr;
162   }
163 
164   HashMap<KURL, std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
165       prefetched_exchanges_map;
166   for (auto& exchange : prefetched_signed_exchanges) {
167     const KURL outer_url = exchange->outer_url;
168     prefetched_exchanges_map.Set(outer_url, std::move(exchange));
169   }
170 
171   return MakeGarbageCollected<PrefetchedSignedExchangeManager>(
172       frame, std::move(alternative_resources),
173       std::move(prefetched_exchanges_map));
174 }
175 
PrefetchedSignedExchangeManager(LocalFrame * frame,std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources,HashMap<KURL,std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>> prefetched_exchanges_map)176 PrefetchedSignedExchangeManager::PrefetchedSignedExchangeManager(
177     LocalFrame* frame,
178     std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources,
179     HashMap<KURL,
180             std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
181         prefetched_exchanges_map)
182     : frame_(frame),
183       alternative_resources_(std::move(alternative_resources)),
184       prefetched_exchanges_map_(std::move(prefetched_exchanges_map)) {
185 }
186 
~PrefetchedSignedExchangeManager()187 PrefetchedSignedExchangeManager::~PrefetchedSignedExchangeManager() {}
188 
Trace(Visitor * visitor)189 void PrefetchedSignedExchangeManager::Trace(Visitor* visitor) {
190   visitor->Trace(frame_);
191 }
192 
StartPrefetchedLinkHeaderPreloads()193 void PrefetchedSignedExchangeManager::StartPrefetchedLinkHeaderPreloads() {
194   DCHECK(!started_);
195   started_ = true;
196   TriggerLoad();
197   // Clears |prefetched_exchanges_map_| to release URLLoaderFactory in the
198   // browser process.
199   prefetched_exchanges_map_.clear();
200   // Clears |alternative_resources_| which will not be used forever.
201   alternative_resources_.reset();
202 }
203 
204 std::unique_ptr<WebURLLoader>
MaybeCreateURLLoader(const WebURLRequest & request)205 PrefetchedSignedExchangeManager::MaybeCreateURLLoader(
206     const WebURLRequest& request) {
207   if (started_)
208     return nullptr;
209   const auto* matching_resource = alternative_resources_->FindMatchingEntry(
210       request.Url(), request.GetRequestContext(),
211       frame_->DomWindow()->navigator()->languages());
212   if (!matching_resource)
213     return nullptr;
214 
215   std::unique_ptr<PrefetchedSignedExchangeLoader> loader =
216       std::make_unique<PrefetchedSignedExchangeLoader>(
217           request, frame_->GetFrameScheduler()->GetTaskRunner(
218                        TaskType::kInternalLoading));
219   loaders_.emplace_back(loader->GetWeakPtr());
220   return loader;
221 }
222 
223 std::unique_ptr<WebURLLoader>
CreateDefaultURLLoader(const WebURLRequest & request)224 PrefetchedSignedExchangeManager::CreateDefaultURLLoader(
225     const WebURLRequest& request) {
226   return frame_->GetURLLoaderFactory()->CreateURLLoader(
227       request,
228       frame_->GetFrameScheduler()->CreateResourceLoadingTaskRunnerHandle());
229 }
230 
231 std::unique_ptr<WebURLLoader>
CreatePrefetchedSignedExchangeURLLoader(const WebURLRequest & request,mojo::PendingRemote<network::mojom::blink::URLLoaderFactory> loader_factory)232 PrefetchedSignedExchangeManager::CreatePrefetchedSignedExchangeURLLoader(
233     const WebURLRequest& request,
234     mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
235         loader_factory) {
236   return Platform::Current()
237       ->WrapURLLoaderFactory(loader_factory.PassPipe())
238       ->CreateURLLoader(
239           request,
240           frame_->GetFrameScheduler()->CreateResourceLoadingTaskRunnerHandle());
241 }
242 
TriggerLoad()243 void PrefetchedSignedExchangeManager::TriggerLoad() {
244   Vector<WebNavigationParams::PrefetchedSignedExchange*>
245       maching_prefetched_exchanges;
246   for (auto loader : loaders_) {
247     if (!loader) {
248       // The loader has been canceled.
249       maching_prefetched_exchanges.emplace_back(nullptr);
250       // We can continue the matching, because the distributor can't send
251       // arbitrary information to the publisher using this resource.
252       continue;
253     }
254     const auto* matching_resource = alternative_resources_->FindMatchingEntry(
255         loader->request().Url(), loader->request().GetRequestContext(),
256         frame_->DomWindow()->navigator()->languages());
257     const auto alternative_url = matching_resource->alternative_url();
258     if (!alternative_url.IsValid()) {
259       // There is no matching "alternate" link header in outer response header.
260       break;
261     }
262     const auto exchange_it = prefetched_exchanges_map_.find(alternative_url);
263     if (exchange_it == prefetched_exchanges_map_.end()) {
264       // There is no matching prefetched exchange.
265       break;
266     }
267     if (String(exchange_it->value->header_integrity) !=
268         matching_resource->header_integrity()) {
269       // The header integrity doesn't match.
270       break;
271     }
272     if (KURL(exchange_it->value->inner_url) !=
273         matching_resource->anchor_url()) {
274       // The inner URL doesn't match.
275       break;
276     }
277     maching_prefetched_exchanges.emplace_back(exchange_it->value.get());
278   }
279   if (loaders_.size() != maching_prefetched_exchanges.size()) {
280     // Need to load the all original resources in this case to prevent the
281     // distributor from sending arbitrary information to the publisher.
282     String message =
283         "Failed to match prefetched alternative signed exchange subresources. "
284         "Requesting the all original resources ignoreing all alternative signed"
285         " exchange responses.";
286     frame_->GetDocument()->AddConsoleMessage(
287         MakeGarbageCollected<ConsoleMessage>(
288             mojom::ConsoleMessageSource::kNetwork,
289             mojom::ConsoleMessageLevel::kError, message));
290     for (auto loader : loaders_) {
291       if (!loader)
292         continue;
293       loader->SetURLLoader(CreateDefaultURLLoader(loader->request()));
294     }
295     return;
296   }
297   for (wtf_size_t i = 0; i < loaders_.size(); ++i) {
298     auto loader = loaders_.at(i);
299     if (!loader)
300       continue;
301     auto* prefetched_exchange = maching_prefetched_exchanges.at(i);
302     mojo::Remote<network::mojom::blink::URLLoaderFactory> loader_factory(
303         mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>(
304             std::move(prefetched_exchange->loader_factory_handle),
305             network::mojom::URLLoaderFactory::Version_));
306     mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
307         loader_factory_clone;
308     loader_factory->Clone(
309         loader_factory_clone.InitWithNewPipeAndPassReceiver());
310     // Reset loader_factory_handle to support loading the same resource again.
311     prefetched_exchange->loader_factory_handle =
312         loader_factory_clone.PassPipe();
313     loader->SetURLLoader(CreatePrefetchedSignedExchangeURLLoader(
314         loader->request(), loader_factory.Unbind()));
315   }
316 }
317 
318 }  // namespace blink
319