| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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 "core/loader/FrameFetchContext.h" |
| |
| #include "bindings/core/v8/ScriptController.h" |
| #include "core/dom/Document.h" |
| #include "core/fetch/ClientHintsPreferences.h" |
| #include "core/fetch/ResourceLoader.h" |
| #include "core/fetch/UniqueIdentifier.h" |
| #include "core/frame/FrameConsole.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/imports/HTMLImportsController.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/inspector/InspectorResourceAgent.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/inspector/InstrumentingAgents.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/FrameLoaderClient.h" |
| #include "core/loader/LinkLoader.h" |
| #include "core/loader/MixedContentChecker.h" |
| #include "core/loader/NetworkHintsInterface.h" |
| #include "core/loader/PingLoader.h" |
| #include "core/loader/ProgressTracker.h" |
| #include "core/loader/appcache/ApplicationCacheHost.h" |
| #include "core/page/Page.h" |
| #include "core/svg/graphics/SVGImageChromeClient.h" |
| #include "core/timing/DOMWindowPerformance.h" |
| #include "core/timing/Performance.h" |
| #include "platform/Logging.h" |
| #include "platform/mhtml/MHTMLArchive.h" |
| #include "platform/network/ResourceTimingInfo.h" |
| #include "platform/weborigin/SchemeRegistry.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "public/platform/WebFrameScheduler.h" |
| |
| #include <algorithm> |
| |
| namespace blink { |
| |
| FrameFetchContext::FrameFetchContext(DocumentLoader* loader) |
| : m_document(nullptr) |
| , m_documentLoader(loader) |
| , m_imageFetched(false) |
| { |
| } |
| |
| FrameFetchContext::~FrameFetchContext() |
| { |
| m_document = nullptr; |
| m_documentLoader = nullptr; |
| } |
| |
| LocalFrame* FrameFetchContext::frame() const |
| { |
| LocalFrame* frame = nullptr; |
| if (m_documentLoader) |
| frame = m_documentLoader->frame(); |
| else if (m_document && m_document->importsController()) |
| frame = m_document->importsController()->master()->frame(); |
| ASSERT(frame); |
| return frame; |
| } |
| |
| void FrameFetchContext::addAdditionalRequestHeaders(ResourceRequest& request, FetchResourceType type) |
| { |
| bool isMainResource = type == FetchMainResource; |
| if (!isMainResource) { |
| RefPtr<SecurityOrigin> outgoingOrigin; |
| if (!request.didSetHTTPReferrer()) { |
| ASSERT(m_document); |
| outgoingOrigin = m_document->securityOrigin(); |
| request.setHTTPReferrer(SecurityPolicy::generateReferrer(m_document->referrerPolicy(), request.url(), m_document->outgoingReferrer())); |
| } else { |
| RELEASE_ASSERT(SecurityPolicy::generateReferrer(request.referrerPolicy(), request.url(), request.httpReferrer()).referrer == request.httpReferrer()); |
| outgoingOrigin = SecurityOrigin::createFromString(request.httpReferrer()); |
| } |
| |
| request.addHTTPOriginIfNeeded(outgoingOrigin); |
| } |
| |
| if (m_document) |
| request.setOriginatesFromReservedIPRange(m_document->isHostedInReservedIPRange()); |
| |
| // The remaining modifications are only necessary for HTTP and HTTPS. |
| if (!request.url().isEmpty() && !request.url().protocolIsInHTTPFamily()) |
| return; |
| |
| if (frame()->settings() && frame()->settings()->dataSaverEnabled()) |
| request.addHTTPHeaderField("Save-Data", "on"); |
| |
| frame()->loader().applyUserAgent(request); |
| } |
| |
| void FrameFetchContext::setFirstPartyForCookies(ResourceRequest& request) |
| { |
| if (frame()->tree().top()->isLocalFrame()) |
| request.setFirstPartyForCookies(toLocalFrame(frame()->tree().top())->document()->firstPartyForCookies()); |
| } |
| |
| CachePolicy FrameFetchContext::cachePolicy() const |
| { |
| if (m_document && m_document->loadEventFinished()) |
| return CachePolicyVerify; |
| |
| FrameLoadType loadType = frame()->loader().loadType(); |
| if (loadType == FrameLoadTypeReloadFromOrigin) |
| return CachePolicyReload; |
| |
| Frame* parentFrame = frame()->tree().parent(); |
| if (parentFrame && parentFrame->isLocalFrame()) { |
| CachePolicy parentCachePolicy = toLocalFrame(parentFrame)->document()->fetcher()->context().cachePolicy(); |
| if (parentCachePolicy != CachePolicyVerify) |
| return parentCachePolicy; |
| } |
| |
| if (loadType == FrameLoadTypeReload) |
| return CachePolicyRevalidate; |
| |
| if (m_documentLoader && m_documentLoader->request().cachePolicy() == ReturnCacheDataElseLoad) |
| return CachePolicyHistoryBuffer; |
| return CachePolicyVerify; |
| |
| } |
| |
| static ResourceRequestCachePolicy memoryCachePolicyToResourceRequestCachePolicy( |
| const CachePolicy policy) { |
| if (policy == CachePolicyVerify) |
| return UseProtocolCachePolicy; |
| if (policy == CachePolicyRevalidate) |
| return ReloadIgnoringCacheData; |
| if (policy == CachePolicyReload) |
| return ReloadBypassingCache; |
| if (policy == CachePolicyHistoryBuffer) |
| return ReturnCacheDataElseLoad; |
| return UseProtocolCachePolicy; |
| } |
| |
| ResourceRequestCachePolicy FrameFetchContext::resourceRequestCachePolicy(const ResourceRequest& request, Resource::Type type) const |
| { |
| ASSERT(frame()); |
| if (type == Resource::MainResource) { |
| FrameLoadType frameLoadType = frame()->loader().loadType(); |
| if (request.httpMethod() == "POST" && frameLoadType == FrameLoadTypeBackForward) |
| return ReturnCacheDataDontLoad; |
| if (!frame()->host()->overrideEncoding().isEmpty()) |
| return ReturnCacheDataElseLoad; |
| if (frameLoadType == FrameLoadTypeSame || request.isConditional() || request.httpMethod() == "POST") |
| return ReloadIgnoringCacheData; |
| |
| for (Frame* f = frame(); f; f = f->tree().parent()) { |
| if (!f->isLocalFrame()) |
| continue; |
| frameLoadType = toLocalFrame(f)->loader().loadType(); |
| if (frameLoadType == FrameLoadTypeBackForward) |
| return ReturnCacheDataElseLoad; |
| if (frameLoadType == FrameLoadTypeReloadFromOrigin) |
| return ReloadBypassingCache; |
| if (frameLoadType == FrameLoadTypeReload) |
| return ReloadIgnoringCacheData; |
| } |
| return UseProtocolCachePolicy; |
| } |
| |
| // For users on slow connections, we want to avoid blocking the parser in |
| // the main frame on script loads inserted via document.write, since it can |
| // add significant delays before page content is displayed on the screen. |
| // For now, as a prototype, we block fetches for main frame scripts |
| // inserted via document.write as long as the |
| // disallowFetchForDocWrittenScriptsInMainFrame setting is enabled. In the |
| // future, we'll extend this logic to only block if estimated network RTT |
| // is above some threshold. |
| if (type == Resource::Script && isMainFrame()) { |
| const bool isInDocumentWrite = m_document && m_document->isInDocumentWrite(); |
| const bool disallowFetchForDocWriteScripts = frame()->settings() && frame()->settings()->disallowFetchForDocWrittenScriptsInMainFrame(); |
| if (isInDocumentWrite && disallowFetchForDocWriteScripts) |
| return ReturnCacheDataDontLoad; |
| } |
| |
| if (request.isConditional()) |
| return ReloadIgnoringCacheData; |
| |
| if (m_documentLoader && m_document && !m_document->loadEventFinished()) { |
| // For POST requests, we mutate the main resource's cache policy to avoid form resubmission. |
| // This policy should not be inherited by subresources. |
| ResourceRequestCachePolicy mainResourceCachePolicy = m_documentLoader->request().cachePolicy(); |
| if (m_documentLoader->request().httpMethod() == "POST") { |
| if (mainResourceCachePolicy == ReturnCacheDataDontLoad) |
| return ReturnCacheDataElseLoad; |
| return UseProtocolCachePolicy; |
| } |
| return memoryCachePolicyToResourceRequestCachePolicy(cachePolicy()); |
| } |
| return UseProtocolCachePolicy; |
| } |
| |
| // FIXME(http://crbug.com/274173): |
| // |loader| can be null if the resource is loaded from imported document. |
| // This means inspector, which uses DocumentLoader as an grouping entity, |
| // cannot see imported documents. |
| inline DocumentLoader* FrameFetchContext::ensureLoaderForNotifications() const |
| { |
| return m_documentLoader ? m_documentLoader.get() : frame()->loader().documentLoader(); |
| } |
| |
| void FrameFetchContext::dispatchDidChangeResourcePriority(unsigned long identifier, ResourceLoadPriority loadPriority, int intraPriorityValue) |
| { |
| frame()->loader().client()->dispatchDidChangeResourcePriority(identifier, loadPriority, intraPriorityValue); |
| } |
| |
| void FrameFetchContext::dispatchWillSendRequest(unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& initiatorInfo) |
| { |
| frame()->loader().applyUserAgent(request); |
| frame()->loader().client()->dispatchWillSendRequest(m_documentLoader, identifier, request, redirectResponse); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceSendRequest", TRACE_EVENT_SCOPE_THREAD, "data", InspectorSendRequestEvent::data(identifier, frame(), request)); |
| InspectorInstrumentation::willSendRequest(frame(), identifier, ensureLoaderForNotifications(), request, redirectResponse, initiatorInfo); |
| } |
| |
| void FrameFetchContext::dispatchDidReceiveResponse(unsigned long identifier, const ResourceResponse& response, WebURLRequest::FrameType frameType, WebURLRequest::RequestContext requestContext, ResourceLoader* resourceLoader) |
| { |
| LinkLoader::CanLoadResources resourceLoadingPolicy = LinkLoader::LoadResourcesAndPreconnect; |
| MixedContentChecker::checkMixedPrivatePublic(frame(), response.remoteIPAddress()); |
| if (m_documentLoader == frame()->loader().provisionalDocumentLoader()) { |
| ResourceFetcher* fetcher = nullptr; |
| if (frame()->document()) |
| fetcher = frame()->document()->fetcher(); |
| m_documentLoader->clientHintsPreferences().updateFromAcceptClientHintsHeader(response.httpHeaderField(HTTPNames::Accept_CH), fetcher); |
| // When response is received with a provisional docloader, the resource haven't committed yet, and we cannot load resources, only preconnect. |
| resourceLoadingPolicy = LinkLoader::DoNotLoadResources; |
| } |
| LinkLoader::loadLinkFromHeader(response.httpHeaderField(HTTPNames::Link), response.url(), frame()->document(), NetworkHintsInterfaceImpl(), resourceLoadingPolicy); |
| |
| if (response.hasMajorCertificateErrors()) |
| MixedContentChecker::handleCertificateError(frame(), response, frameType, requestContext); |
| |
| frame()->loader().progress().incrementProgress(identifier, response); |
| frame()->loader().client()->dispatchDidReceiveResponse(m_documentLoader, identifier, response); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceReceiveResponse", TRACE_EVENT_SCOPE_THREAD, "data", InspectorReceiveResponseEvent::data(identifier, frame(), response)); |
| DocumentLoader* documentLoader = ensureLoaderForNotifications(); |
| InspectorInstrumentation::didReceiveResourceResponse(frame(), identifier, documentLoader, response, resourceLoader); |
| // It is essential that inspector gets resource response BEFORE console. |
| frame()->console().reportResourceResponseReceived(documentLoader, identifier, response); |
| } |
| |
| void FrameFetchContext::dispatchDidReceiveData(unsigned long identifier, const char* data, int dataLength, int encodedDataLength) |
| { |
| frame()->loader().progress().incrementProgress(identifier, dataLength); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceReceivedData", TRACE_EVENT_SCOPE_THREAD, "data", InspectorReceiveDataEvent::data(identifier, frame(), encodedDataLength)); |
| InspectorInstrumentation::didReceiveData(frame(), identifier, data, dataLength, encodedDataLength); |
| } |
| |
| void FrameFetchContext::dispatchDidDownloadData(unsigned long identifier, int dataLength, int encodedDataLength) |
| { |
| frame()->loader().progress().incrementProgress(identifier, dataLength); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceReceivedData", TRACE_EVENT_SCOPE_THREAD, "data", InspectorReceiveDataEvent::data(identifier, frame(), encodedDataLength)); |
| InspectorInstrumentation::didReceiveData(frame(), identifier, 0, dataLength, encodedDataLength); |
| } |
| |
| void FrameFetchContext::dispatchDidFinishLoading(unsigned long identifier, double finishTime, int64_t encodedDataLength) |
| { |
| frame()->loader().progress().completeProgress(identifier); |
| frame()->loader().client()->dispatchDidFinishLoading(m_documentLoader, identifier); |
| |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceFinish", TRACE_EVENT_SCOPE_THREAD, "data", InspectorResourceFinishEvent::data(identifier, finishTime, false)); |
| InspectorInstrumentation::didFinishLoading(frame(), identifier, finishTime, encodedDataLength); |
| } |
| |
| void FrameFetchContext::dispatchDidFail(unsigned long identifier, const ResourceError& error, bool isInternalRequest) |
| { |
| frame()->loader().progress().completeProgress(identifier); |
| frame()->loader().client()->dispatchDidFinishLoading(m_documentLoader, identifier); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceFinish", TRACE_EVENT_SCOPE_THREAD, "data", InspectorResourceFinishEvent::data(identifier, 0, true)); |
| InspectorInstrumentation::didFailLoading(frame(), identifier, error); |
| // Notification to FrameConsole should come AFTER InspectorInstrumentation call, DevTools front-end relies on this. |
| if (!isInternalRequest) |
| frame()->console().didFailLoading(identifier, error); |
| } |
| |
| void FrameFetchContext::dispatchDidLoadResourceFromMemoryCache(const Resource* resource, WebURLRequest::FrameType frameType, WebURLRequest::RequestContext requestContext) |
| { |
| ResourceRequest request(resource->url()); |
| unsigned long identifier = createUniqueIdentifier(); |
| frame()->loader().client()->dispatchDidLoadResourceFromMemoryCache(request, resource->response()); |
| dispatchWillSendRequest(identifier, request, ResourceResponse(), resource->options().initiatorInfo); |
| |
| InspectorInstrumentation::markResourceAsCached(frame(), identifier); |
| if (!resource->response().isNull()) |
| dispatchDidReceiveResponse(identifier, resource->response(), frameType, requestContext); |
| |
| if (resource->encodedSize() > 0) |
| dispatchDidReceiveData(identifier, 0, resource->encodedSize(), 0); |
| |
| dispatchDidFinishLoading(identifier, 0, 0); |
| } |
| |
| bool FrameFetchContext::shouldLoadNewResource(Resource::Type type) const |
| { |
| if (!m_documentLoader) |
| return true; |
| if (type == Resource::MainResource) |
| return m_documentLoader == frame()->loader().provisionalDocumentLoader(); |
| return m_documentLoader == frame()->loader().documentLoader(); |
| } |
| |
| void FrameFetchContext::willStartLoadingResource(ResourceRequest& request) |
| { |
| if (m_documentLoader) |
| m_documentLoader->applicationCacheHost()->willStartLoadingResource(request); |
| } |
| |
| void FrameFetchContext::didLoadResource(Resource* resource) |
| { |
| if (resource->isLoadEventBlockingResourceType()) |
| frame()->loader().checkCompleted(); |
| } |
| |
| void FrameFetchContext::addResourceTiming(const ResourceTimingInfo& info) |
| { |
| Document* initiatorDocument = m_document && info.isMainResource() ? m_document->parentDocument() : m_document.get(); |
| if (!initiatorDocument || !initiatorDocument->domWindow()) |
| return; |
| DOMWindowPerformance::performance(*initiatorDocument->domWindow())->addResourceTiming(info); |
| } |
| |
| bool FrameFetchContext::allowImage(bool imagesEnabled, const KURL& url) const |
| { |
| return frame()->loader().client()->allowImage(imagesEnabled, url); |
| } |
| |
| void FrameFetchContext::printAccessDeniedMessage(const KURL& url) const |
| { |
| if (url.isNull()) |
| return; |
| |
| String message; |
| if (!m_document || m_document->url().isNull()) |
| message = "Unsafe attempt to load URL " + url.elidedString() + '.'; |
| else if (url.isLocalFile() || m_document->url().isLocalFile()) |
| message = "Unsafe attempt to load URL " + url.elidedString() + " from frame with URL " + m_document->url().elidedString() + ". 'file:' URLs are treated as unique security origins.\n"; |
| else |
| message = "Unsafe attempt to load URL " + url.elidedString() + " from frame with URL " + m_document->url().elidedString() + ". Domains, protocols and ports must match.\n"; |
| |
| frame()->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message)); |
| } |
| |
| bool FrameFetchContext::canRequest(Resource::Type type, const ResourceRequest& resourceRequest, const KURL& url, const ResourceLoaderOptions& options, bool forPreload, FetchRequest::OriginRestriction originRestriction) const |
| { |
| // As of CSP2, for requests that are the results of redirects, the match |
| // algorithm should ignore the path component of the URL. |
| ContentSecurityPolicy::RedirectStatus redirectStatus = resourceRequest.followedRedirect() ? ContentSecurityPolicy::DidRedirect : ContentSecurityPolicy::DidNotRedirect; |
| |
| ResourceRequestBlockedReason reason = canRequestInternal(type, resourceRequest, url, options, forPreload, originRestriction, redirectStatus); |
| if (reason != ResourceRequestBlockedReasonNone) { |
| if (!forPreload) |
| InspectorInstrumentation::didBlockRequest(frame(), resourceRequest, ensureLoaderForNotifications(), options.initiatorInfo, reason); |
| return false; |
| } |
| return true; |
| } |
| |
| bool FrameFetchContext::allowResponse(Resource::Type type, const ResourceRequest& resourceRequest, const KURL& url, const ResourceLoaderOptions& options) const |
| { |
| ResourceRequestBlockedReason reason = canRequestInternal(type, resourceRequest, url, options, false, FetchRequest::UseDefaultOriginRestrictionForType, ContentSecurityPolicy::DidRedirect); |
| if (reason != ResourceRequestBlockedReasonNone) { |
| InspectorInstrumentation::didBlockRequest(frame(), resourceRequest, ensureLoaderForNotifications(), options.initiatorInfo, reason); |
| return false; |
| } |
| return true; |
| } |
| |
| ResourceRequestBlockedReason FrameFetchContext::canRequestInternal(Resource::Type type, const ResourceRequest& resourceRequest, const KURL& url, const ResourceLoaderOptions& options, bool forPreload, FetchRequest::OriginRestriction originRestriction, ContentSecurityPolicy::RedirectStatus redirectStatus) const |
| { |
| InstrumentingAgents* agents = InspectorInstrumentation::instrumentingAgentsFor(frame()); |
| if (agents && agents->inspectorResourceAgent()) { |
| if (agents->inspectorResourceAgent()->shouldBlockRequest(resourceRequest)) |
| return ResourceRequestBlockedReasonInspector; |
| } |
| |
| SecurityOrigin* securityOrigin = options.securityOrigin.get(); |
| if (!securityOrigin && m_document) |
| securityOrigin = m_document->securityOrigin(); |
| |
| if (originRestriction != FetchRequest::NoOriginRestriction && securityOrigin && !securityOrigin->canDisplay(url)) { |
| if (!forPreload) |
| FrameLoader::reportLocalLoadFailed(frame(), url.elidedString()); |
| WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource URL was not allowed by SecurityOrigin::canDisplay"); |
| return ResourceRequestBlockedReasonOther; |
| } |
| |
| // Some types of resources can be loaded only from the same origin. Other |
| // types of resources, like Images, Scripts, and CSS, can be loaded from |
| // any URL. |
| switch (type) { |
| case Resource::MainResource: |
| case Resource::Image: |
| case Resource::CSSStyleSheet: |
| case Resource::Script: |
| case Resource::Font: |
| case Resource::Raw: |
| case Resource::LinkPrefetch: |
| case Resource::LinkPreload: |
| case Resource::TextTrack: |
| case Resource::ImportResource: |
| case Resource::Media: |
| case Resource::Manifest: |
| // By default these types of resources can be loaded from any origin. |
| // FIXME: Are we sure about Resource::Font? |
| if (originRestriction == FetchRequest::RestrictToSameOrigin && !securityOrigin->canRequest(url)) { |
| printAccessDeniedMessage(url); |
| return ResourceRequestBlockedReasonOrigin; |
| } |
| break; |
| case Resource::XSLStyleSheet: |
| ASSERT(RuntimeEnabledFeatures::xsltEnabled()); |
| case Resource::SVGDocument: |
| if (!securityOrigin->canRequest(url)) { |
| printAccessDeniedMessage(url); |
| return ResourceRequestBlockedReasonOrigin; |
| } |
| break; |
| } |
| |
| // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. |
| bool shouldBypassMainWorldCSP = frame()->script().shouldBypassMainWorldCSP() || options.contentSecurityPolicyOption == DoNotCheckContentSecurityPolicy; |
| |
| // Don't send CSP messages for preloads, we might never actually display those items. |
| ContentSecurityPolicy::ReportingStatus cspReporting = forPreload ? |
| ContentSecurityPolicy::SuppressReport : ContentSecurityPolicy::SendReport; |
| |
| // m_document can be null, but not in any of the cases where csp is actually used below. |
| // ImageResourceTest.MultipartImage crashes w/o the m_document null check. |
| // I believe it's the Resource::Raw case. |
| const ContentSecurityPolicy* csp = m_document ? m_document->contentSecurityPolicy() : nullptr; |
| |
| // TODO(mkwst): This would be cleaner if moved this switch into an allowFromSource() |
| // helper on this object which took a Resource::Type, then this block would |
| // collapse to about 10 lines for handling Raw and Script special cases. |
| switch (type) { |
| case Resource::XSLStyleSheet: |
| ASSERT(RuntimeEnabledFeatures::xsltEnabled()); |
| ASSERT(ContentSecurityPolicy::isScriptResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowScriptFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| break; |
| case Resource::Script: |
| case Resource::ImportResource: |
| ASSERT(ContentSecurityPolicy::isScriptResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowScriptFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| ASSERT(frame()); |
| if (!frame()->loader().client()->allowScriptFromSource(!frame()->settings() || frame()->settings()->scriptEnabled(), url)) { |
| frame()->loader().client()->didNotAllowScript(); |
| return ResourceRequestBlockedReasonCSP; |
| } |
| break; |
| case Resource::CSSStyleSheet: |
| ASSERT(ContentSecurityPolicy::isStyleResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowStyleFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| break; |
| case Resource::SVGDocument: |
| case Resource::Image: |
| ASSERT(ContentSecurityPolicy::isImageResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowImageFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| break; |
| case Resource::Font: { |
| ASSERT(ContentSecurityPolicy::isFontResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowFontFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| break; |
| } |
| case Resource::LinkPreload: |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowConnectToSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| break; |
| case Resource::MainResource: |
| case Resource::Raw: |
| case Resource::LinkPrefetch: |
| case Resource::Manifest: |
| break; |
| case Resource::Media: |
| case Resource::TextTrack: |
| ASSERT(ContentSecurityPolicy::isMediaResource(resourceRequest)); |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowMediaFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| |
| if (!frame()->loader().client()->allowMedia(url)) |
| return ResourceRequestBlockedReasonOther; |
| break; |
| } |
| |
| // SVG Images have unique security rules that prevent all subresource requests |
| // except for data urls. |
| if (type != Resource::MainResource && frame()->chromeClient().isSVGImageChromeClient() && !url.protocolIsData()) |
| return ResourceRequestBlockedReasonOrigin; |
| |
| // FIXME: Once we use RequestContext for CSP (http://crbug.com/390497), remove this extra check. |
| if (resourceRequest.requestContext() == WebURLRequest::RequestContextManifest) { |
| ASSERT(csp); |
| if (!shouldBypassMainWorldCSP && !csp->allowManifestFromSource(url, redirectStatus, cspReporting)) |
| return ResourceRequestBlockedReasonCSP; |
| } |
| |
| // Measure the number of legacy URL schemes ('ftp://') and the number of embedded-credential |
| // ('http://user:password@...') resources embedded as subresources. in the hopes that we can |
| // block them at some point in the future. |
| if (resourceRequest.frameType() != WebURLRequest::FrameTypeTopLevel) { |
| ASSERT(frame()->document()); |
| if (SchemeRegistry::shouldTreatURLSchemeAsLegacy(url.protocol()) && !SchemeRegistry::shouldTreatURLSchemeAsLegacy(frame()->document()->securityOrigin()->protocol())) |
| UseCounter::count(frame()->document(), UseCounter::LegacyProtocolEmbeddedAsSubresource); |
| if (!url.user().isEmpty() || !url.pass().isEmpty()) |
| UseCounter::count(frame()->document(), UseCounter::RequestedSubresourceWithEmbeddedCredentials); |
| } |
| |
| // Measure the number of pages that load resources after a redirect |
| // when a CSP is active, to see if implementing CSP |
| // 'unsafe-redirect' is feasible. |
| if (csp && csp->isActive() && resourceRequest.frameType() != WebURLRequest::FrameTypeTopLevel && resourceRequest.frameType() != WebURLRequest::FrameTypeAuxiliary && redirectStatus == ContentSecurityPolicy::DidRedirect) { |
| ASSERT(frame()->document()); |
| UseCounter::count(frame()->document(), UseCounter::ResourceLoadedAfterRedirectWithCSP); |
| } |
| |
| // Last of all, check for mixed content. We do this last so that when |
| // folks block mixed content with a CSP policy, they don't get a warning. |
| // They'll still get a warning in the console about CSP blocking the load. |
| MixedContentChecker::ReportingStatus mixedContentReporting = forPreload ? |
| MixedContentChecker::SuppressReport : MixedContentChecker::SendReport; |
| if (MixedContentChecker::shouldBlockFetch(frame(), resourceRequest, url, mixedContentReporting)) |
| return ResourceRequestBlockedReasonMixedContent; |
| |
| return ResourceRequestBlockedReasonNone; |
| } |
| |
| bool FrameFetchContext::isControlledByServiceWorker() const |
| { |
| ASSERT(m_documentLoader || frame()->loader().documentLoader()); |
| if (m_documentLoader) |
| return frame()->loader().client()->isControlledByServiceWorker(*m_documentLoader); |
| // m_documentLoader is null while loading resources from an HTML import. |
| // In such cases whether the request is controlled by ServiceWorker or not |
| // is determined by the document loader of the frame. |
| return frame()->loader().client()->isControlledByServiceWorker(*frame()->loader().documentLoader()); |
| } |
| |
| int64_t FrameFetchContext::serviceWorkerID() const |
| { |
| ASSERT(m_documentLoader || frame()->loader().documentLoader()); |
| if (m_documentLoader) |
| return frame()->loader().client()->serviceWorkerID(*m_documentLoader); |
| // m_documentLoader is null while loading resources from an HTML import. |
| // In such cases a service worker ID could be retrieved from the document |
| // loader of the frame. |
| return frame()->loader().client()->serviceWorkerID(*frame()->loader().documentLoader()); |
| } |
| |
| bool FrameFetchContext::isMainFrame() const |
| { |
| return frame()->isMainFrame(); |
| } |
| |
| bool FrameFetchContext::defersLoading() const |
| { |
| return frame()->page()->defersLoading(); |
| } |
| |
| bool FrameFetchContext::isLoadComplete() const |
| { |
| return m_document && m_document->loadEventFinished(); |
| } |
| |
| bool FrameFetchContext::pageDismissalEventBeingDispatched() const |
| { |
| return m_document && m_document->pageDismissalEventBeingDispatched() != Document::NoDismissal; |
| } |
| |
| bool FrameFetchContext::updateTimingInfoForIFrameNavigation(ResourceTimingInfo* info) |
| { |
| // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations. |
| // FIXME: Resource timing is broken when the parent is a remote frame. |
| if (!frame()->deprecatedLocalOwner() || frame()->deprecatedLocalOwner()->loadedNonEmptyDocument()) |
| return false; |
| frame()->deprecatedLocalOwner()->didLoadNonEmptyDocument(); |
| // Do not report iframe navigation that restored from history, since its |
| // location may have been changed after initial navigation. |
| if (frame()->loader().loadType() == FrameLoadTypeInitialHistoryLoad) |
| return false; |
| info->setInitiatorType(frame()->deprecatedLocalOwner()->localName()); |
| return true; |
| } |
| |
| void FrameFetchContext::sendImagePing(const KURL& url) |
| { |
| PingLoader::loadImage(frame(), url); |
| } |
| |
| void FrameFetchContext::addConsoleMessage(const String& message) const |
| { |
| if (frame()->document()) |
| frame()->document()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message)); |
| } |
| |
| SecurityOrigin* FrameFetchContext::securityOrigin() const |
| { |
| return m_document ? m_document->securityOrigin() : nullptr; |
| } |
| |
| void FrameFetchContext::upgradeInsecureRequest(FetchRequest& fetchRequest) |
| { |
| KURL url = fetchRequest.resourceRequest().url(); |
| |
| // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational requests, as described in |
| // https://w3c.github.io/webappsec/specs/upgrade/#feature-detect |
| if (fetchRequest.resourceRequest().frameType() != WebURLRequest::FrameTypeNone) |
| fetchRequest.mutableResourceRequest().addHTTPHeaderField("Upgrade-Insecure-Requests", "1"); |
| |
| if (m_document && m_document->insecureRequestsPolicy() == SecurityContext::InsecureRequestsUpgrade && url.protocolIs("http")) { |
| ASSERT(m_document->insecureNavigationsToUpgrade()); |
| |
| // We always upgrade requests that meet any of the following criteria: |
| // |
| // 1. Are for subresources (including nested frames). |
| // 2. Are form submissions. |
| // 3. Whose hosts are contained in the document's InsecureNavigationSet. |
| const ResourceRequest& request = fetchRequest.resourceRequest(); |
| if (request.frameType() == WebURLRequest::FrameTypeNone |
| || request.frameType() == WebURLRequest::FrameTypeNested |
| || request.requestContext() == WebURLRequest::RequestContextForm |
| || (!url.host().isNull() && m_document->insecureNavigationsToUpgrade()->contains(url.host().impl()->hash()))) |
| { |
| UseCounter::count(m_document, UseCounter::UpgradeInsecureRequestsUpgradedRequest); |
| url.setProtocol("https"); |
| if (url.port() == 80) |
| url.setPort(443); |
| fetchRequest.mutableResourceRequest().setURL(url); |
| } |
| } |
| } |
| |
| void FrameFetchContext::addClientHintsIfNecessary(FetchRequest& fetchRequest) |
| { |
| if (!RuntimeEnabledFeatures::clientHintsEnabled() || !m_document) |
| return; |
| |
| bool shouldSendDPR = m_document->clientHintsPreferences().shouldSendDPR() || fetchRequest.clientHintsPreferences().shouldSendDPR(); |
| bool shouldSendResourceWidth = m_document->clientHintsPreferences().shouldSendResourceWidth() || fetchRequest.clientHintsPreferences().shouldSendResourceWidth(); |
| bool shouldSendViewportWidth = m_document->clientHintsPreferences().shouldSendViewportWidth() || fetchRequest.clientHintsPreferences().shouldSendViewportWidth(); |
| |
| if (shouldSendDPR) |
| fetchRequest.mutableResourceRequest().addHTTPHeaderField("DPR", AtomicString(String::number(m_document->devicePixelRatio()))); |
| |
| if (shouldSendResourceWidth) { |
| FetchRequest::ResourceWidth resourceWidth = fetchRequest.resourceWidth(); |
| if (resourceWidth.isSet) { |
| float physicalWidth = resourceWidth.width * m_document->devicePixelRatio(); |
| fetchRequest.mutableResourceRequest().addHTTPHeaderField("Width", AtomicString(String::number(ceil(physicalWidth)))); |
| } |
| } |
| |
| if (shouldSendViewportWidth && frame()->view()) |
| fetchRequest.mutableResourceRequest().addHTTPHeaderField("Viewport-Width", AtomicString(String::number(frame()->view()->viewportWidth()))); |
| } |
| |
| void FrameFetchContext::addCSPHeaderIfNecessary(Resource::Type type, FetchRequest& fetchRequest) |
| { |
| if (!m_document) |
| return; |
| |
| const ContentSecurityPolicy* csp = m_document->contentSecurityPolicy(); |
| if (csp->shouldSendCSPHeader(type)) |
| fetchRequest.mutableResourceRequest().addHTTPHeaderField("CSP", "active"); |
| } |
| |
| MHTMLArchive* FrameFetchContext::archive() const |
| { |
| ASSERT(!isMainFrame()); |
| // TODO(nasko): How should this work with OOPIF? |
| // The MHTMLArchive is parsed as a whole, but can be constructed from |
| // frames in mutliple processes. In that case, which process should parse |
| // it and how should the output be spread back across multiple processes? |
| if (!frame()->tree().parent()->isLocalFrame()) |
| return nullptr; |
| return toLocalFrame(frame()->tree().parent())->loader().documentLoader()->fetcher()->archive(); |
| } |
| |
| void FrameFetchContext::countClientHintsDPR() |
| { |
| UseCounter::count(frame(), UseCounter::ClientHintsDPR); |
| } |
| |
| void FrameFetchContext::countClientHintsResourceWidth() |
| { |
| UseCounter::count(frame(), UseCounter::ClientHintsResourceWidth); |
| } |
| |
| void FrameFetchContext::countClientHintsViewportWidth() |
| { |
| UseCounter::count(frame(), UseCounter::ClientHintsViewportWidth); |
| } |
| |
| ResourceLoadPriority FrameFetchContext::modifyPriorityForExperiments(ResourceLoadPriority priority, Resource::Type type, const FetchRequest& request, ResourcePriority::VisibilityStatus visibility) |
| { |
| // An image fetch is used to distinguish between "early" and "late" scripts in a document |
| if (type == Resource::Image) |
| m_imageFetched = true; |
| |
| // If Settings is null, we can't verify any experiments are in force. |
| if (!frame()->settings()) |
| return priority; |
| |
| // If enabled, drop the priority of all resources in a subframe. |
| if (frame()->settings()->lowPriorityIframes() && !frame()->isMainFrame()) |
| return ResourceLoadPriorityVeryLow; |
| |
| // Async/Defer scripts. |
| if (type == Resource::Script && FetchRequest::LazyLoad == request.defer()) |
| return frame()->settings()->fetchIncreaseAsyncScriptPriority() ? ResourceLoadPriorityMedium : ResourceLoadPriorityLow; |
| |
| // Runtime experiment that change how we prioritize resources. |
| // The toggles do not depend on each other and can be flipped individually |
| // though the cumulative result will depend on the interaction between them. |
| // Background doc: https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit?usp=sharing |
| |
| // Increases the priorities for CSS, Scripts, Fonts and Images all by one level |
| // and parser-blocking scripts and visible images by 2. |
| // This is used in conjunction with logic on the Chrome side to raise the threshold |
| // of "layout-blocking" resources and provide a boost to resources that are needed |
| // as soon as possible for something currently on the screen. |
| int modifiedPriority = static_cast<int>(priority); |
| if (frame()->settings()->fetchIncreasePriorities()) { |
| if (type == Resource::CSSStyleSheet || type == Resource::Script || type == Resource::Font || type == Resource::Image) |
| modifiedPriority++; |
| } |
| |
| // Always give visible resources a bump, and an additional bump if generally increasing priorities. |
| if (visibility == ResourcePriority::Visible) { |
| modifiedPriority++; |
| if (frame()->settings()->fetchIncreasePriorities()) |
| modifiedPriority++; |
| } |
| |
| if (frame()->settings()->fetchIncreaseFontPriority() && type == Resource::Font) |
| modifiedPriority++; |
| |
| if (type == Resource::Script) { |
| // Reduce the priority of late-body scripts. |
| if (frame()->settings()->fetchDeferLateScripts() && request.forPreload() && m_imageFetched) |
| modifiedPriority--; |
| // Parser-blocking scripts. |
| if (frame()->settings()->fetchIncreasePriorities() && !request.forPreload()) |
| modifiedPriority++; |
| } |
| |
| // Clamp priority |
| modifiedPriority = std::min(static_cast<int>(ResourceLoadPriorityHighest), std::max(static_cast<int>(ResourceLoadPriorityLowest), modifiedPriority)); |
| return static_cast<ResourceLoadPriority>(modifiedPriority); |
| } |
| |
| WebTaskRunner* FrameFetchContext::loadingTaskRunner() const |
| { |
| return frame()->frameScheduler()->loadingTaskRunner(); |
| } |
| |
| DEFINE_TRACE(FrameFetchContext) |
| { |
| visitor->trace(m_document); |
| visitor->trace(m_documentLoader); |
| FetchContext::trace(visitor); |
| } |
| |
| } // namespace blink |