169 lines
6.0 KiB
C++
169 lines
6.0 KiB
C++
/*
|
|
* Copyright (C) 2017 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "NavigatorBeacon.h"
|
|
|
|
#include "CachedRawResource.h"
|
|
#include "CachedResourceLoader.h"
|
|
#include "Document.h"
|
|
#include "Frame.h"
|
|
#include "HTTPParsers.h"
|
|
#include "Navigator.h"
|
|
#include "Page.h"
|
|
#include <wtf/URL.h>
|
|
|
|
namespace WebCore {
|
|
|
|
NavigatorBeacon::NavigatorBeacon(Navigator& navigator)
|
|
: m_navigator(navigator)
|
|
{
|
|
}
|
|
|
|
NavigatorBeacon::~NavigatorBeacon()
|
|
{
|
|
for (auto& beacon : m_inflightBeacons)
|
|
beacon->removeClient(*this);
|
|
}
|
|
|
|
NavigatorBeacon* NavigatorBeacon::from(Navigator& navigator)
|
|
{
|
|
auto* supplement = static_cast<NavigatorBeacon*>(Supplement<Navigator>::from(&navigator, supplementName()));
|
|
if (!supplement) {
|
|
auto newSupplement = makeUnique<NavigatorBeacon>(navigator);
|
|
supplement = newSupplement.get();
|
|
provideTo(&navigator, supplementName(), WTFMove(newSupplement));
|
|
}
|
|
return supplement;
|
|
}
|
|
|
|
const char* NavigatorBeacon::supplementName()
|
|
{
|
|
return "NavigatorBeacon";
|
|
}
|
|
|
|
void NavigatorBeacon::notifyFinished(CachedResource& resource, const NetworkLoadMetrics&)
|
|
{
|
|
if (!resource.resourceError().isNull())
|
|
logError(resource.resourceError());
|
|
|
|
resource.removeClient(*this);
|
|
bool wasRemoved = m_inflightBeacons.removeFirst(&resource);
|
|
ASSERT_UNUSED(wasRemoved, wasRemoved);
|
|
ASSERT(!m_inflightBeacons.contains(&resource));
|
|
}
|
|
|
|
void NavigatorBeacon::logError(const ResourceError& error)
|
|
{
|
|
ASSERT(!error.isNull());
|
|
|
|
auto* frame = m_navigator.frame();
|
|
if (!frame)
|
|
return;
|
|
|
|
auto* document = frame->document();
|
|
if (!document)
|
|
return;
|
|
|
|
ASCIILiteral messageMiddle { ". "_s };
|
|
String description = error.localizedDescription();
|
|
if (description.isEmpty()) {
|
|
if (error.isAccessControl())
|
|
messageMiddle = " due to access control checks."_s;
|
|
else
|
|
messageMiddle = "."_s;
|
|
}
|
|
|
|
document->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString("Beacon API cannot load "_s, error.failingURL().string(), messageMiddle, description));
|
|
}
|
|
|
|
ExceptionOr<bool> NavigatorBeacon::sendBeacon(Document& document, const String& url, std::optional<FetchBody::Init>&& body)
|
|
{
|
|
URL parsedUrl = document.completeURL(url);
|
|
|
|
// Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if
|
|
// parsedUrl's scheme is not "http" or "https", throw a "TypeError" exception and terminate these steps.
|
|
if (!parsedUrl.isValid())
|
|
return Exception { TypeError, "This URL is invalid"_s };
|
|
if (!parsedUrl.protocolIsInHTTPFamily())
|
|
return Exception { TypeError, "Beacons can only be sent over HTTP(S)"_s };
|
|
|
|
if (!document.frame())
|
|
return false;
|
|
|
|
auto& contentSecurityPolicy = *document.contentSecurityPolicy();
|
|
if (!document.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(parsedUrl)) {
|
|
// We simulate a network error so we return true here. This is consistent with Blink.
|
|
return true;
|
|
}
|
|
|
|
ResourceRequest request(parsedUrl);
|
|
request.setHTTPMethod("POST"_s);
|
|
request.setRequester(ResourceRequest::Requester::Beacon);
|
|
|
|
ResourceLoaderOptions options;
|
|
options.credentials = FetchOptions::Credentials::Include;
|
|
options.cache = FetchOptions::Cache::NoCache;
|
|
options.keepAlive = true;
|
|
options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks;
|
|
|
|
if (body) {
|
|
options.mode = FetchOptions::Mode::NoCors;
|
|
String mimeType;
|
|
auto result = FetchBody::extract(WTFMove(body.value()), mimeType);
|
|
if (result.hasException())
|
|
return result.releaseException();
|
|
auto fetchBody = result.releaseReturnValue();
|
|
if (fetchBody.hasReadableStream())
|
|
return Exception { TypeError, "Beacons cannot send ReadableStream body"_s };
|
|
|
|
request.setHTTPBody(fetchBody.bodyAsFormData());
|
|
if (!mimeType.isEmpty()) {
|
|
request.setHTTPContentType(mimeType);
|
|
if (!isCrossOriginSafeRequestHeader(HTTPHeaderName::ContentType, mimeType))
|
|
options.mode = FetchOptions::Mode::Cors;
|
|
}
|
|
}
|
|
|
|
auto cachedResource = document.cachedResourceLoader().requestBeaconResource({ WTFMove(request), options });
|
|
if (!cachedResource) {
|
|
logError(cachedResource.error());
|
|
return false;
|
|
}
|
|
|
|
ASSERT(!m_inflightBeacons.contains(cachedResource.value().get()));
|
|
m_inflightBeacons.append(cachedResource.value().get());
|
|
cachedResource.value()->addClient(*this);
|
|
return true;
|
|
}
|
|
|
|
ExceptionOr<bool> NavigatorBeacon::sendBeacon(Navigator& navigator, Document& document, const String& url, std::optional<FetchBody::Init>&& body)
|
|
{
|
|
return NavigatorBeacon::from(navigator)->sendBeacon(document, url, WTFMove(body));
|
|
}
|
|
|
|
}
|
|
|