Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / Modules / beacon / NavigatorBeacon.cpp
1 /*
2  * Copyright (C) 2017 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NavigatorBeacon.h"
28
29 #include "CachedRawResource.h"
30 #include "CachedResourceLoader.h"
31 #include "Document.h"
32 #include "Frame.h"
33 #include "HTTPParsers.h"
34 #include "Navigator.h"
35 #include <wtf/URL.h>
36
37 namespace WebCore {
38
39 NavigatorBeacon::NavigatorBeacon(Navigator& navigator)
40     : m_navigator(navigator)
41 {
42 }
43
44 NavigatorBeacon::~NavigatorBeacon()
45 {
46     for (auto& beacon : m_inflightBeacons)
47         beacon->removeClient(*this);
48 }
49
50 NavigatorBeacon* NavigatorBeacon::from(Navigator& navigator)
51 {
52     auto* supplement = static_cast<NavigatorBeacon*>(Supplement<Navigator>::from(&navigator, supplementName()));
53     if (!supplement) {
54         auto newSupplement = std::make_unique<NavigatorBeacon>(navigator);
55         supplement = newSupplement.get();
56         provideTo(&navigator, supplementName(), WTFMove(newSupplement));
57     }
58     return supplement;
59 }
60
61 const char* NavigatorBeacon::supplementName()
62 {
63     return "NavigatorBeacon";
64 }
65
66 void NavigatorBeacon::notifyFinished(CachedResource& resource)
67 {
68     if (!resource.resourceError().isNull())
69         logError(resource.resourceError());
70
71     resource.removeClient(*this);
72     bool wasRemoved = m_inflightBeacons.removeFirst(&resource);
73     ASSERT_UNUSED(wasRemoved, wasRemoved);
74     ASSERT(!m_inflightBeacons.contains(&resource));
75 }
76
77 void NavigatorBeacon::logError(const ResourceError& error)
78 {
79     ASSERT(!error.isNull());
80
81     auto* frame = m_navigator.frame();
82     if (!frame)
83         return;
84
85     auto* document = frame->document();
86     if (!document)
87         return;
88
89     ASCIILiteral messageMiddle { ". "_s };
90     String description = error.localizedDescription();
91     if (description.isEmpty()) {
92         if (error.isAccessControl())
93             messageMiddle = " due to access control checks."_s;
94         else
95             messageMiddle = "."_s;
96     }
97
98     document->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString("Beacon API cannot load "_s, error.failingURL().string(), messageMiddle, description));
99 }
100
101 ExceptionOr<bool> NavigatorBeacon::sendBeacon(Document& document, const String& url, std::optional<FetchBody::Init>&& body)
102 {
103     URL parsedUrl = document.completeURL(url);
104
105     // Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if
106     // parsedUrl's scheme is not "http" or "https", throw a "TypeError" exception and terminate these steps.
107     if (!parsedUrl.isValid())
108         return Exception { TypeError, "This URL is invalid"_s };
109     if (!parsedUrl.protocolIsInHTTPFamily())
110         return Exception { TypeError, "Beacons can only be sent over HTTP(S)"_s };
111
112     if (!document.frame())
113         return false;
114
115     auto& contentSecurityPolicy = *document.contentSecurityPolicy();
116     if (!document.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(parsedUrl)) {
117         // We simulate a network error so we return true here. This is consistent with Blink.
118         return true;
119     }
120
121     ResourceRequest request(parsedUrl);
122     request.setHTTPMethod("POST"_s);
123
124     FetchOptions options;
125     options.credentials = FetchOptions::Credentials::Include;
126     options.cache = FetchOptions::Cache::NoCache;
127     options.keepAlive = true;
128     if (body) {
129         options.mode = FetchOptions::Mode::Cors;
130         String mimeType;
131         auto fetchBody = FetchBody::extract(document, WTFMove(body.value()), mimeType);
132
133         if (fetchBody.hasReadableStream())
134             return Exception { TypeError, "Beacons cannot send ReadableStream body"_s };
135
136         request.setHTTPBody(fetchBody.bodyAsFormData(document));
137         if (!mimeType.isEmpty()) {
138             request.setHTTPContentType(mimeType);
139             if (isCrossOriginSafeRequestHeader(HTTPHeaderName::ContentType, mimeType))
140                 options.mode = FetchOptions::Mode::NoCors;
141         }
142     }
143
144     auto cachedResource = document.cachedResourceLoader().requestBeaconResource({ WTFMove(request), options });
145     if (!cachedResource) {
146         logError(cachedResource.error());
147         return false;
148     }
149
150     ASSERT(!m_inflightBeacons.contains(cachedResource.value().get()));
151     m_inflightBeacons.append(cachedResource.value().get());
152     cachedResource.value()->addClient(*this);
153     return true;
154 }
155
156 ExceptionOr<bool> NavigatorBeacon::sendBeacon(Navigator& navigator, Document& document, const String& url, std::optional<FetchBody::Init>&& body)
157 {
158     return NavigatorBeacon::from(navigator)->sendBeacon(document, url, WTFMove(body));
159 }
160
161 }
162