| /* |
| * Copyright (C) 2010, 2011, 2012 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 "web/WebAssociatedURLLoaderImpl.h" |
| |
| #include "core/dom/ContextLifecycleObserver.h" |
| #include "core/fetch/CrossOriginAccessControl.h" |
| #include "core/fetch/FetchUtils.h" |
| #include "core/loader/DocumentThreadableLoader.h" |
| #include "core/loader/DocumentThreadableLoaderClient.h" |
| #include "platform/Timer.h" |
| #include "platform/exported/WrappedResourceRequest.h" |
| #include "platform/exported/WrappedResourceResponse.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/network/ResourceError.h" |
| #include "public/platform/WebHTTPHeaderVisitor.h" |
| #include "public/platform/WebString.h" |
| #include "public/platform/WebURLError.h" |
| #include "public/platform/WebURLRequest.h" |
| #include "public/web/WebAssociatedURLLoaderClient.h" |
| #include "public/web/WebDataSource.h" |
| #include "web/WebLocalFrameImpl.h" |
| #include "wtf/HashSet.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/text/WTFString.h" |
| #include <limits.h> |
| #include <memory> |
| |
| namespace blink { |
| |
| namespace { |
| |
| class HTTPRequestHeaderValidator : public WebHTTPHeaderVisitor { |
| WTF_MAKE_NONCOPYABLE(HTTPRequestHeaderValidator); |
| |
| public: |
| HTTPRequestHeaderValidator() : m_isSafe(true) {} |
| ~HTTPRequestHeaderValidator() override {} |
| |
| void visitHeader(const WebString& name, const WebString& value) override; |
| bool isSafe() const { return m_isSafe; } |
| |
| private: |
| bool m_isSafe; |
| }; |
| |
| void HTTPRequestHeaderValidator::visitHeader(const WebString& name, |
| const WebString& value) { |
| m_isSafe = m_isSafe && isValidHTTPToken(name) && |
| !FetchUtils::isForbiddenHeaderName(name) && |
| isValidHTTPHeaderValue(value); |
| } |
| |
| } // namespace |
| |
| // This class bridges the interface differences between WebCore and WebKit |
| // loader clients. |
| // It forwards its ThreadableLoaderClient notifications to a |
| // WebAssociatedURLLoaderClient. |
| class WebAssociatedURLLoaderImpl::ClientAdapter final |
| : public DocumentThreadableLoaderClient { |
| WTF_MAKE_NONCOPYABLE(ClientAdapter); |
| |
| public: |
| static std::unique_ptr<ClientAdapter> create( |
| WebAssociatedURLLoaderImpl*, |
| WebAssociatedURLLoaderClient*, |
| const WebAssociatedURLLoaderOptions&); |
| |
| // ThreadableLoaderClient |
| void didSendData(unsigned long long /*bytesSent*/, |
| unsigned long long /*totalBytesToBeSent*/) override; |
| void didReceiveResponse(unsigned long, |
| const ResourceResponse&, |
| std::unique_ptr<WebDataConsumerHandle>) override; |
| void didDownloadData(int /*dataLength*/) override; |
| void didReceiveData(const char*, unsigned /*dataLength*/) override; |
| void didReceiveCachedMetadata(const char*, int /*dataLength*/) override; |
| void didFinishLoading(unsigned long /*identifier*/, |
| double /*finishTime*/) override; |
| void didFail(const ResourceError&) override; |
| void didFailRedirectCheck() override; |
| |
| // DocumentThreadableLoaderClient |
| bool willFollowRedirect( |
| const ResourceRequest& /*newRequest*/, |
| const ResourceResponse& /*redirectResponse*/) override; |
| |
| // Sets an error to be reported back to the client, asychronously. |
| void setDelayedError(const ResourceError&); |
| |
| // Enables forwarding of error notifications to the |
| // WebAssociatedURLLoaderClient. These |
| // must be deferred until after the call to |
| // WebAssociatedURLLoader::loadAsynchronously() completes. |
| void enableErrorNotifications(); |
| |
| // Stops loading and releases the DocumentThreadableLoader as early as |
| // possible. |
| WebAssociatedURLLoaderClient* releaseClient() { |
| WebAssociatedURLLoaderClient* client = m_client; |
| m_client = nullptr; |
| return client; |
| } |
| |
| private: |
| ClientAdapter(WebAssociatedURLLoaderImpl*, |
| WebAssociatedURLLoaderClient*, |
| const WebAssociatedURLLoaderOptions&); |
| |
| void notifyError(TimerBase*); |
| |
| WebAssociatedURLLoaderImpl* m_loader; |
| WebAssociatedURLLoaderClient* m_client; |
| WebAssociatedURLLoaderOptions m_options; |
| WebURLError m_error; |
| |
| Timer<ClientAdapter> m_errorTimer; |
| bool m_enableErrorNotifications; |
| bool m_didFail; |
| }; |
| |
| std::unique_ptr<WebAssociatedURLLoaderImpl::ClientAdapter> |
| WebAssociatedURLLoaderImpl::ClientAdapter::create( |
| WebAssociatedURLLoaderImpl* loader, |
| WebAssociatedURLLoaderClient* client, |
| const WebAssociatedURLLoaderOptions& options) { |
| return wrapUnique(new ClientAdapter(loader, client, options)); |
| } |
| |
| WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter( |
| WebAssociatedURLLoaderImpl* loader, |
| WebAssociatedURLLoaderClient* client, |
| const WebAssociatedURLLoaderOptions& options) |
| : m_loader(loader), |
| m_client(client), |
| m_options(options), |
| m_errorTimer(this, &ClientAdapter::notifyError), |
| m_enableErrorNotifications(false), |
| m_didFail(false) { |
| DCHECK(m_loader); |
| DCHECK(m_client); |
| } |
| |
| bool WebAssociatedURLLoaderImpl::ClientAdapter::willFollowRedirect( |
| const ResourceRequest& newRequest, |
| const ResourceResponse& redirectResponse) { |
| if (!m_client) |
| return true; |
| |
| WrappedResourceRequest wrappedNewRequest(newRequest); |
| WrappedResourceResponse wrappedRedirectResponse(redirectResponse); |
| return m_client->willFollowRedirect(wrappedNewRequest, |
| wrappedRedirectResponse); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didSendData( |
| unsigned long long bytesSent, |
| unsigned long long totalBytesToBeSent) { |
| if (!m_client) |
| return; |
| |
| m_client->didSendData(bytesSent, totalBytesToBeSent); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didReceiveResponse( |
| unsigned long, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| ALLOW_UNUSED_LOCAL(handle); |
| DCHECK(!handle); |
| if (!m_client) |
| return; |
| |
| if (m_options.exposeAllResponseHeaders || |
| m_options.crossOriginRequestPolicy != |
| WebAssociatedURLLoaderOptions:: |
| CrossOriginRequestPolicyUseAccessControl) { |
| // Use the original ResourceResponse. |
| m_client->didReceiveResponse(WrappedResourceResponse(response)); |
| return; |
| } |
| |
| HTTPHeaderSet exposedHeaders; |
| extractCorsExposedHeaderNamesList(response, exposedHeaders); |
| HTTPHeaderSet blockedHeaders; |
| for (const auto& header : response.httpHeaderFields()) { |
| if (FetchUtils::isForbiddenResponseHeaderName(header.key) || |
| (!isOnAccessControlResponseHeaderWhitelist(header.key) && |
| !exposedHeaders.contains(header.key))) |
| blockedHeaders.add(header.key); |
| } |
| |
| if (blockedHeaders.isEmpty()) { |
| // Use the original ResourceResponse. |
| m_client->didReceiveResponse(WrappedResourceResponse(response)); |
| return; |
| } |
| |
| // If there are blocked headers, copy the response so we can remove them. |
| WebURLResponse validatedResponse = WrappedResourceResponse(response); |
| for (const auto& header : blockedHeaders) |
| validatedResponse.clearHTTPHeaderField(header); |
| m_client->didReceiveResponse(validatedResponse); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didDownloadData( |
| int dataLength) { |
| if (!m_client) |
| return; |
| |
| m_client->didDownloadData(dataLength); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didReceiveData( |
| const char* data, |
| unsigned dataLength) { |
| if (!m_client) |
| return; |
| |
| CHECK_LE(dataLength, static_cast<unsigned>(std::numeric_limits<int>::max())); |
| |
| m_client->didReceiveData(data, dataLength); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didReceiveCachedMetadata( |
| const char* data, |
| int dataLength) { |
| if (!m_client) |
| return; |
| |
| m_client->didReceiveCachedMetadata(data, dataLength); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didFinishLoading( |
| unsigned long identifier, |
| double finishTime) { |
| if (!m_client) |
| return; |
| |
| m_loader->clientAdapterDone(); |
| |
| releaseClient()->didFinishLoading(finishTime); |
| // |this| may be dead here. |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didFail( |
| const ResourceError& error) { |
| if (!m_client) |
| return; |
| |
| m_loader->clientAdapterDone(); |
| |
| m_didFail = true; |
| m_error = WebURLError(error); |
| if (m_enableErrorNotifications) |
| notifyError(&m_errorTimer); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::didFailRedirectCheck() { |
| didFail(ResourceError()); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::enableErrorNotifications() { |
| m_enableErrorNotifications = true; |
| // If an error has already been received, start a timer to report it to the |
| // client after WebAssociatedURLLoader::loadAsynchronously has returned to the |
| // caller. |
| if (m_didFail) |
| m_errorTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void WebAssociatedURLLoaderImpl::ClientAdapter::notifyError(TimerBase* timer) { |
| DCHECK_EQ(timer, &m_errorTimer); |
| |
| if (m_client) |
| releaseClient()->didFail(m_error); |
| // |this| may be dead here. |
| } |
| |
| class WebAssociatedURLLoaderImpl::Observer final |
| : public GarbageCollected<Observer>, |
| public ContextLifecycleObserver { |
| USING_GARBAGE_COLLECTED_MIXIN(Observer); |
| |
| public: |
| Observer(WebAssociatedURLLoaderImpl* parent, Document* document) |
| : ContextLifecycleObserver(document), m_parent(parent) {} |
| |
| void dispose() { |
| m_parent = nullptr; |
| clearContext(); |
| } |
| |
| void contextDestroyed() override { |
| if (m_parent) |
| m_parent->documentDestroyed(); |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { ContextLifecycleObserver::trace(visitor); } |
| |
| WebAssociatedURLLoaderImpl* m_parent; |
| }; |
| |
| WebAssociatedURLLoaderImpl::WebAssociatedURLLoaderImpl( |
| WebLocalFrameImpl* frameImpl, |
| const WebAssociatedURLLoaderOptions& options) |
| : m_client(nullptr), |
| m_options(options), |
| m_observer(new Observer(this, frameImpl->frame()->document())) {} |
| |
| WebAssociatedURLLoaderImpl::~WebAssociatedURLLoaderImpl() { |
| cancel(); |
| } |
| |
| #define STATIC_ASSERT_ENUM(a, b) \ |
| static_assert(static_cast<int>(a) == static_cast<int>(b), \ |
| "mismatching enum: " #a) |
| |
| STATIC_ASSERT_ENUM(WebAssociatedURLLoaderOptions::CrossOriginRequestPolicyDeny, |
| DenyCrossOriginRequests); |
| STATIC_ASSERT_ENUM( |
| WebAssociatedURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl, |
| UseAccessControl); |
| STATIC_ASSERT_ENUM(WebAssociatedURLLoaderOptions::CrossOriginRequestPolicyAllow, |
| AllowCrossOriginRequests); |
| |
| STATIC_ASSERT_ENUM(WebAssociatedURLLoaderOptions::ConsiderPreflight, |
| ConsiderPreflight); |
| STATIC_ASSERT_ENUM(WebAssociatedURLLoaderOptions::ForcePreflight, |
| ForcePreflight); |
| STATIC_ASSERT_ENUM(WebAssociatedURLLoaderOptions::PreventPreflight, |
| PreventPreflight); |
| |
| void WebAssociatedURLLoaderImpl::loadAsynchronously( |
| const WebURLRequest& request, |
| WebAssociatedURLLoaderClient* client) { |
| DCHECK(!m_client); |
| DCHECK(!m_loader); |
| DCHECK(!m_clientAdapter); |
| |
| DCHECK(client); |
| |
| bool allowLoad = true; |
| WebURLRequest newRequest(request); |
| if (m_options.untrustedHTTP) { |
| WebString method = newRequest.httpMethod(); |
| allowLoad = m_observer && isValidHTTPToken(method) && |
| FetchUtils::isUsefulMethod(method); |
| if (allowLoad) { |
| newRequest.setHTTPMethod(FetchUtils::normalizeMethod(method)); |
| HTTPRequestHeaderValidator validator; |
| newRequest.visitHTTPHeaderFields(&validator); |
| allowLoad = validator.isSafe(); |
| } |
| } |
| |
| m_client = client; |
| m_clientAdapter = ClientAdapter::create(this, client, m_options); |
| |
| if (allowLoad) { |
| ThreadableLoaderOptions options; |
| options.preflightPolicy = |
| static_cast<PreflightPolicy>(m_options.preflightPolicy); |
| options.crossOriginRequestPolicy = static_cast<CrossOriginRequestPolicy>( |
| m_options.crossOriginRequestPolicy); |
| |
| ResourceLoaderOptions resourceLoaderOptions; |
| resourceLoaderOptions.allowCredentials = m_options.allowCredentials |
| ? AllowStoredCredentials |
| : DoNotAllowStoredCredentials; |
| resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData; |
| |
| const ResourceRequest& webcoreRequest = newRequest.toResourceRequest(); |
| if (webcoreRequest.requestContext() == |
| WebURLRequest::RequestContextUnspecified) { |
| // FIXME: We load URLs without setting a TargetType (and therefore a |
| // request context) in several places in content/ |
| // (P2PPortAllocatorSession::AllocateLegacyRelaySession, for example). |
| // Remove this once those places are patched up. |
| newRequest.setRequestContext(WebURLRequest::RequestContextInternal); |
| } |
| |
| Document* document = toDocument(m_observer->lifecycleContext()); |
| DCHECK(document); |
| m_loader = DocumentThreadableLoader::create( |
| *document, m_clientAdapter.get(), options, resourceLoaderOptions); |
| m_loader->start(webcoreRequest); |
| } |
| |
| if (!m_loader) { |
| // FIXME: return meaningful error codes. |
| m_clientAdapter->didFail(ResourceError()); |
| } |
| m_clientAdapter->enableErrorNotifications(); |
| } |
| |
| void WebAssociatedURLLoaderImpl::cancel() { |
| disposeObserver(); |
| cancelLoader(); |
| releaseClient(); |
| } |
| |
| void WebAssociatedURLLoaderImpl::clientAdapterDone() { |
| disposeObserver(); |
| releaseClient(); |
| } |
| |
| void WebAssociatedURLLoaderImpl::cancelLoader() { |
| if (!m_clientAdapter) |
| return; |
| |
| // Prevent invocation of the WebAssociatedURLLoaderClient methods. |
| m_clientAdapter->releaseClient(); |
| |
| if (m_loader) { |
| m_loader->cancel(); |
| m_loader = nullptr; |
| } |
| m_clientAdapter.reset(); |
| } |
| |
| void WebAssociatedURLLoaderImpl::setDefersLoading(bool defersLoading) { |
| if (m_loader) |
| m_loader->setDefersLoading(defersLoading); |
| } |
| |
| void WebAssociatedURLLoaderImpl::setLoadingTaskRunner(blink::WebTaskRunner*) { |
| // TODO(alexclarke): Maybe support this one day if it proves worthwhile. |
| } |
| |
| void WebAssociatedURLLoaderImpl::documentDestroyed() { |
| disposeObserver(); |
| cancelLoader(); |
| |
| if (!m_client) |
| return; |
| |
| releaseClient()->didFail(ResourceError()); |
| // |this| may be dead here. |
| } |
| |
| void WebAssociatedURLLoaderImpl::disposeObserver() { |
| if (!m_observer) |
| return; |
| |
| // TODO(tyoshino): Remove this assert once Document is fixed so that |
| // contextDestroyed() is invoked for all kinds of Documents. |
| // |
| // Currently, the method of detecting Document destruction implemented here |
| // doesn't work for all kinds of Documents. In case we reached here after |
| // the Oilpan is destroyed, we just crash the renderer process to prevent |
| // UaF. |
| // |
| // We could consider just skipping the rest of code in case |
| // ThreadState::current() is null. However, the fact we reached here |
| // without cancelling the loader means that it's possible there're some |
| // non-Blink non-on-heap objects still facing on-heap Blink objects. E.g. |
| // there could be a WebURLLoader instance behind the |
| // DocumentThreadableLoader instance. So, for safety, we chose to just |
| // crash here. |
| CHECK(ThreadState::current()); |
| |
| m_observer->dispose(); |
| m_observer = nullptr; |
| } |
| |
| } // namespace blink |