1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 */
31
32 #include "third_party/blink/renderer/core/loader/ping_loader.h"
33
34 #include "base/feature_list.h"
35 #include "third_party/blink/public/common/features.h"
36 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
37 #include "third_party/blink/public/platform/web_url_request.h"
38 #include "third_party/blink/renderer/core/fileapi/file.h"
39 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
40 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
41 #include "third_party/blink/renderer/core/frame/local_frame.h"
42 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
43 #include "third_party/blink/renderer/core/html/forms/form_data.h"
44 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
45 #include "third_party/blink/renderer/core/url/url_search_params.h"
46 #include "third_party/blink/renderer/platform/bindings/script_state.h"
47 #include "third_party/blink/renderer/platform/loader/cors/cors.h"
48 #include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h"
49 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
50 #include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h"
51 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
52 #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
53 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
54 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
55 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
56 #include "third_party/blink/renderer/platform/network/encoded_form_data.h"
57 #include "third_party/blink/renderer/platform/network/parsed_content_type.h"
58 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
59 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
60 #include "third_party/blink/renderer/platform/wtf/functional.h"
61
62 namespace blink {
63
64 namespace {
65
66 class Beacon {
67 STACK_ALLOCATED();
68
69 public:
70 virtual void Serialize(ResourceRequest&) const = 0;
71 virtual uint64_t size() const = 0;
72 virtual const AtomicString GetContentType() const = 0;
73 };
74
75 class BeaconString final : public Beacon {
76 public:
BeaconString(const String & data)77 explicit BeaconString(const String& data) : data_(data) {}
78
size() const79 uint64_t size() const override { return data_.CharactersSizeInBytes(); }
80
Serialize(ResourceRequest & request) const81 void Serialize(ResourceRequest& request) const override {
82 scoped_refptr<EncodedFormData> entity_body =
83 EncodedFormData::Create(data_.Utf8());
84 request.SetHttpBody(entity_body);
85 request.SetHTTPContentType(GetContentType());
86 }
87
GetContentType() const88 const AtomicString GetContentType() const override {
89 return AtomicString("text/plain;charset=UTF-8");
90 }
91
92 private:
93 const String data_;
94 };
95
96 class BeaconBlob final : public Beacon {
97 public:
BeaconBlob(Blob * data)98 explicit BeaconBlob(Blob* data) : data_(data) {
99 const String& blob_type = data_->type();
100 if (!blob_type.IsEmpty() && ParsedContentType(blob_type).IsValid())
101 content_type_ = AtomicString(blob_type);
102 }
103
size() const104 uint64_t size() const override { return data_->size(); }
105
Serialize(ResourceRequest & request) const106 void Serialize(ResourceRequest& request) const override {
107 DCHECK(data_);
108
109 scoped_refptr<EncodedFormData> entity_body = EncodedFormData::Create();
110 if (data_->HasBackingFile()) {
111 entity_body->AppendFile(To<File>(data_)->GetPath(),
112 To<File>(data_)->LastModifiedTime());
113 } else {
114 entity_body->AppendBlob(data_->Uuid(), data_->GetBlobDataHandle());
115 }
116
117 request.SetHttpBody(std::move(entity_body));
118
119 if (!content_type_.IsEmpty()) {
120 if (!cors::IsCorsSafelistedContentType(content_type_)) {
121 request.SetMode(network::mojom::blink::RequestMode::kCors);
122 }
123 request.SetHTTPContentType(content_type_);
124 }
125 }
126
GetContentType() const127 const AtomicString GetContentType() const override { return content_type_; }
128
129 private:
130 Blob* const data_;
131 AtomicString content_type_;
132 };
133
134 class BeaconDOMArrayBufferView final : public Beacon {
135 public:
BeaconDOMArrayBufferView(DOMArrayBufferView * data)136 explicit BeaconDOMArrayBufferView(DOMArrayBufferView* data) : data_(data) {
137 CHECK(base::CheckedNumeric<wtf_size_t>(data->byteLength()).IsValid())
138 << "EncodedFormData::Create cannot deal with huge ArrayBuffers.";
139 }
140
size() const141 uint64_t size() const override { return data_->byteLength(); }
142
Serialize(ResourceRequest & request) const143 void Serialize(ResourceRequest& request) const override {
144 DCHECK(data_);
145
146 scoped_refptr<EncodedFormData> entity_body = EncodedFormData::Create(
147 data_->BaseAddress(),
148 base::checked_cast<wtf_size_t>(data_->byteLength()));
149 request.SetHttpBody(std::move(entity_body));
150 }
151
GetContentType() const152 const AtomicString GetContentType() const override { return g_null_atom; }
153
154 private:
155 DOMArrayBufferView* const data_;
156 };
157
158 class BeaconDOMArrayBuffer final : public Beacon {
159 public:
BeaconDOMArrayBuffer(DOMArrayBuffer * data)160 explicit BeaconDOMArrayBuffer(DOMArrayBuffer* data) : data_(data) {
161 CHECK(base::CheckedNumeric<wtf_size_t>(data->ByteLength()).IsValid())
162 << "EncodedFormData::Create cannot deal with huge ArrayBuffers.";
163 }
164
size() const165 uint64_t size() const override { return data_->ByteLength(); }
166
Serialize(ResourceRequest & request) const167 void Serialize(ResourceRequest& request) const override {
168 DCHECK(data_);
169
170 scoped_refptr<EncodedFormData> entity_body = EncodedFormData::Create(
171 data_->Data(), base::checked_cast<wtf_size_t>(data_->ByteLength()));
172 request.SetHttpBody(std::move(entity_body));
173 }
174
GetContentType() const175 const AtomicString GetContentType() const override { return g_null_atom; }
176
177 private:
178 DOMArrayBuffer* const data_;
179 };
180
181 class BeaconURLSearchParams final : public Beacon {
182 public:
BeaconURLSearchParams(URLSearchParams * data)183 explicit BeaconURLSearchParams(URLSearchParams* data) : data_(data) {}
184
size() const185 uint64_t size() const override {
186 return data_->toString().CharactersSizeInBytes();
187 }
188
Serialize(ResourceRequest & request) const189 void Serialize(ResourceRequest& request) const override {
190 DCHECK(data_);
191
192 request.SetHttpBody(data_->ToEncodedFormData());
193 request.SetHTTPContentType(GetContentType());
194 }
195
GetContentType() const196 const AtomicString GetContentType() const override {
197 return AtomicString("application/x-www-form-urlencoded;charset=UTF-8");
198 }
199
200 private:
201 URLSearchParams* const data_;
202 };
203
204 class BeaconFormData final : public Beacon {
205 public:
BeaconFormData(FormData * data)206 explicit BeaconFormData(FormData* data)
207 : data_(data), entity_body_(data_->EncodeMultiPartFormData()) {
208 content_type_ = AtomicString("multipart/form-data; boundary=") +
209 entity_body_->Boundary().data();
210 }
211
size() const212 uint64_t size() const override { return entity_body_->SizeInBytes(); }
213
Serialize(ResourceRequest & request) const214 void Serialize(ResourceRequest& request) const override {
215 request.SetHttpBody(entity_body_.get());
216 request.SetHTTPContentType(content_type_);
217 }
218
GetContentType() const219 const AtomicString GetContentType() const override { return content_type_; }
220
221 private:
222 FormData* const data_;
223 scoped_refptr<EncodedFormData> entity_body_;
224 AtomicString content_type_;
225 };
226
SendBeaconCommon(const ScriptState & state,LocalFrame * frame,const KURL & url,const Beacon & beacon)227 bool SendBeaconCommon(const ScriptState& state,
228 LocalFrame* frame,
229 const KURL& url,
230 const Beacon& beacon) {
231 if (!frame->DomWindow()
232 ->GetContentSecurityPolicyForWorld(&state.World())
233 ->AllowConnectToSource(url, url, RedirectStatus::kNoRedirect)) {
234 // We're simulating a network failure here, so we return 'true'.
235 return true;
236 }
237
238 ResourceRequest request(url);
239 request.SetHttpMethod(http_names::kPOST);
240 request.SetKeepalive(true);
241 request.SetRequestContext(mojom::blink::RequestContextType::BEACON);
242 beacon.Serialize(request);
243 FetchParameters params(std::move(request), &state.World());
244 // The spec says:
245 // - If mimeType is not null:
246 // - If mimeType value is a CORS-safelisted request-header value for the
247 // Content-Type header, set corsMode to "no-cors".
248 // As we don't support requests with non CORS-safelisted Content-Type, the
249 // mode should always be "no-cors".
250 params.MutableOptions().initiator_info.name =
251 fetch_initiator_type_names::kBeacon;
252
253 frame->Client()->DidDispatchPingLoader(url);
254 Resource* resource =
255 RawResource::Fetch(params, frame->DomWindow()->Fetcher(), nullptr);
256 return resource->GetStatus() != ResourceStatus::kLoadError;
257 }
258
259 } // namespace
260
261 // http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#hyperlink-auditing
SendLinkAuditPing(LocalFrame * frame,const KURL & ping_url,const KURL & destination_url)262 void PingLoader::SendLinkAuditPing(LocalFrame* frame,
263 const KURL& ping_url,
264 const KURL& destination_url) {
265 if (!ping_url.ProtocolIsInHTTPFamily())
266 return;
267
268 ResourceRequest request(ping_url);
269 request.SetHttpMethod(http_names::kPOST);
270 request.SetHTTPContentType("text/ping");
271 request.SetHttpBody(EncodedFormData::Create("PING"));
272 request.SetHttpHeaderField(http_names::kCacheControl, "max-age=0");
273 request.SetHttpHeaderField(http_names::kPingTo,
274 AtomicString(destination_url.GetString()));
275 scoped_refptr<const SecurityOrigin> ping_origin =
276 SecurityOrigin::Create(ping_url);
277 if (ProtocolIs(frame->DomWindow()->Url().GetString(), "http") ||
278 frame->DomWindow()->GetSecurityOrigin()->CanAccess(ping_origin.get())) {
279 request.SetHttpHeaderField(
280 http_names::kPingFrom,
281 AtomicString(frame->DomWindow()->Url().GetString()));
282 }
283
284 request.SetKeepalive(true);
285 request.SetReferrerString(Referrer::NoReferrer());
286 request.SetReferrerPolicy(network::mojom::ReferrerPolicy::kNever);
287 request.SetRequestContext(mojom::blink::RequestContextType::PING);
288 FetchParameters params(std::move(request),
289 frame->DomWindow()->GetCurrentWorld());
290 params.MutableOptions().initiator_info.name =
291 fetch_initiator_type_names::kPing;
292
293 frame->Client()->DidDispatchPingLoader(ping_url);
294 RawResource::Fetch(params, frame->DomWindow()->Fetcher(), nullptr);
295 }
296
SendViolationReport(LocalFrame * frame,const KURL & report_url,scoped_refptr<EncodedFormData> report)297 void PingLoader::SendViolationReport(LocalFrame* frame,
298 const KURL& report_url,
299 scoped_refptr<EncodedFormData> report) {
300 ResourceRequest request(report_url);
301 request.SetHttpMethod(http_names::kPOST);
302 request.SetHTTPContentType("application/csp-report");
303 request.SetKeepalive(true);
304 request.SetHttpBody(std::move(report));
305 request.SetCredentialsMode(network::mojom::CredentialsMode::kSameOrigin);
306 request.SetRequestContext(mojom::blink::RequestContextType::CSP_REPORT);
307 request.SetRequestDestination(network::mojom::RequestDestination::kReport);
308 request.SetRequestorOrigin(frame->DomWindow()->GetSecurityOrigin());
309 request.SetRedirectMode(network::mojom::RedirectMode::kError);
310 FetchParameters params(std::move(request),
311 frame->DomWindow()->GetCurrentWorld());
312 params.MutableOptions().initiator_info.name =
313 fetch_initiator_type_names::kViolationreport;
314
315 frame->Client()->DidDispatchPingLoader(report_url);
316 RawResource::Fetch(params, frame->DomWindow()->Fetcher(), nullptr);
317 }
318
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,const String & data)319 bool PingLoader::SendBeacon(const ScriptState& state,
320 LocalFrame* frame,
321 const KURL& beacon_url,
322 const String& data) {
323 BeaconString beacon(data);
324 return SendBeaconCommon(state, frame, beacon_url, beacon);
325 }
326
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,DOMArrayBufferView * data)327 bool PingLoader::SendBeacon(const ScriptState& state,
328 LocalFrame* frame,
329 const KURL& beacon_url,
330 DOMArrayBufferView* data) {
331 BeaconDOMArrayBufferView beacon(data);
332 return SendBeaconCommon(state, frame, beacon_url, beacon);
333 }
334
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,DOMArrayBuffer * data)335 bool PingLoader::SendBeacon(const ScriptState& state,
336 LocalFrame* frame,
337 const KURL& beacon_url,
338 DOMArrayBuffer* data) {
339 BeaconDOMArrayBuffer beacon(data);
340 return SendBeaconCommon(state, frame, beacon_url, beacon);
341 }
342
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,URLSearchParams * data)343 bool PingLoader::SendBeacon(const ScriptState& state,
344 LocalFrame* frame,
345 const KURL& beacon_url,
346 URLSearchParams* data) {
347 BeaconURLSearchParams beacon(data);
348 return SendBeaconCommon(state, frame, beacon_url, beacon);
349 }
350
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,FormData * data)351 bool PingLoader::SendBeacon(const ScriptState& state,
352 LocalFrame* frame,
353 const KURL& beacon_url,
354 FormData* data) {
355 BeaconFormData beacon(data);
356 return SendBeaconCommon(state, frame, beacon_url, beacon);
357 }
358
SendBeacon(const ScriptState & state,LocalFrame * frame,const KURL & beacon_url,Blob * data)359 bool PingLoader::SendBeacon(const ScriptState& state,
360 LocalFrame* frame,
361 const KURL& beacon_url,
362 Blob* data) {
363 BeaconBlob beacon(data);
364 return SendBeaconCommon(state, frame, beacon_url, beacon);
365 }
366
367 } // namespace blink
368