1/*
2 * Copyright (C) 2014 Igalia S.L
3 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "MediaResourceLoader.h"
29
30#if ENABLE(VIDEO)
31
32#include "CachedRawResource.h"
33#include "CachedResourceLoader.h"
34#include "CachedResourceRequest.h"
35#include "CrossOriginAccessControl.h"
36#include "Document.h"
37#include "HTMLMediaElement.h"
38#include "InspectorInstrumentation.h"
39#include "SecurityOrigin.h"
40#include <wtf/NeverDestroyed.h>
41
42namespace WebCore {
43
44MediaResourceLoader::MediaResourceLoader(Document& document, HTMLMediaElement& mediaElement, const String& crossOriginMode)
45 : ContextDestructionObserver(&document)
46 , m_document(makeWeakPtr(document))
47 , m_mediaElement(makeWeakPtr(mediaElement))
48 , m_crossOriginMode(crossOriginMode)
49{
50}
51
52MediaResourceLoader::~MediaResourceLoader()
53{
54 ASSERT(m_resources.isEmpty());
55}
56
57void MediaResourceLoader::contextDestroyed()
58{
59 ContextDestructionObserver::contextDestroyed();
60 m_document = nullptr;
61 m_mediaElement = nullptr;
62}
63
64RefPtr<PlatformMediaResource> MediaResourceLoader::requestResource(ResourceRequest&& request, LoadOptions options)
65{
66 if (!m_document)
67 return nullptr;
68
69 DataBufferingPolicy bufferingPolicy = options & LoadOption::BufferData ? DataBufferingPolicy::BufferData : DataBufferingPolicy::DoNotBufferData;
70 auto cachingPolicy = options & LoadOption::DisallowCaching ? CachingPolicy::DisallowCaching : CachingPolicy::AllowCaching;
71
72 request.setRequester(ResourceRequest::Requester::Media);
73
74 if (m_mediaElement)
75 request.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(*m_mediaElement));
76
77#if HAVE(AVFOUNDATION_LOADER_DELEGATE) && PLATFORM(MAC)
78 // FIXME: Workaround for <rdar://problem/26071607>. We are not able to do CORS checking on 304 responses because they are usually missing the headers we need.
79 if (!m_crossOriginMode.isNull())
80 request.makeUnconditional();
81#endif
82
83 ContentSecurityPolicyImposition contentSecurityPolicyImposition = m_mediaElement && m_mediaElement->isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
84 ResourceLoaderOptions loaderOptions {
85 SendCallbackPolicy::SendCallbacks,
86 ContentSniffingPolicy::DoNotSniffContent,
87 bufferingPolicy,
88 StoredCredentialsPolicy::Use,
89 ClientCredentialPolicy::MayAskClientForCredentials,
90 FetchOptions::Credentials::Include,
91 SecurityCheckPolicy::DoSecurityCheck,
92 FetchOptions::Mode::NoCors,
93 CertificateInfoPolicy::DoNotIncludeCertificateInfo,
94 contentSecurityPolicyImposition,
95 DefersLoadingPolicy::AllowDefersLoading,
96 cachingPolicy };
97 loaderOptions.destination = m_mediaElement && !m_mediaElement->isVideo() ? FetchOptions::Destination::Audio : FetchOptions::Destination::Video;
98 auto cachedRequest = createPotentialAccessControlRequest(WTFMove(request), *m_document, m_crossOriginMode, WTFMove(loaderOptions));
99 if (m_mediaElement)
100 cachedRequest.setInitiator(*m_mediaElement.get());
101
102 auto resource = m_document->cachedResourceLoader().requestMedia(WTFMove(cachedRequest)).value_or(nullptr);
103 if (!resource)
104 return nullptr;
105
106 Ref<MediaResource> mediaResource = MediaResource::create(*this, resource);
107 m_resources.add(mediaResource.ptr());
108
109 return mediaResource;
110}
111
112void MediaResourceLoader::removeResource(MediaResource& mediaResource)
113{
114 ASSERT(m_resources.contains(&mediaResource));
115 m_resources.remove(&mediaResource);
116}
117
118void MediaResourceLoader::addResponseForTesting(const ResourceResponse& response)
119{
120 const auto maximumResponsesForTesting = 5;
121 if (m_responsesForTesting.size() > maximumResponsesForTesting)
122 return;
123 m_responsesForTesting.append(response);
124}
125
126Ref<MediaResource> MediaResource::create(MediaResourceLoader& loader, CachedResourceHandle<CachedRawResource> resource)
127{
128 return adoptRef(*new MediaResource(loader, resource));
129}
130
131MediaResource::MediaResource(MediaResourceLoader& loader, CachedResourceHandle<CachedRawResource> resource)
132 : m_loader(loader)
133 , m_resource(resource)
134{
135 ASSERT(resource);
136 resource->addClient(*this);
137}
138
139MediaResource::~MediaResource()
140{
141 stop();
142 m_loader->removeResource(*this);
143}
144
145void MediaResource::stop()
146{
147 if (!m_resource)
148 return;
149
150 m_resource->removeClient(*this);
151 m_resource = nullptr;
152}
153
154void MediaResource::responseReceived(CachedResource& resource, const ResourceResponse& response, CompletionHandler<void()>&& completionHandler)
155{
156 ASSERT_UNUSED(resource, &resource == m_resource);
157 CompletionHandlerCallingScope completionHandlerCaller(WTFMove(completionHandler));
158
159 if (!m_loader->document())
160 return;
161
162 RefPtr<MediaResource> protectedThis(this);
163 if (m_resource->resourceError().isAccessControl()) {
164 static NeverDestroyed<const String> consoleMessage("Cross-origin media resource load denied by Cross-Origin Resource Sharing policy.");
165 m_loader->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, consoleMessage.get());
166 m_didPassAccessControlCheck = false;
167 if (m_client)
168 m_client->accessControlCheckFailed(*this, ResourceError(errorDomainWebKitInternal, 0, response.url(), consoleMessage.get()));
169 stop();
170 return;
171 }
172
173 m_didPassAccessControlCheck = m_resource->options().mode == FetchOptions::Mode::Cors;
174 if (m_client)
175 m_client->responseReceived(*this, response, [this, protectedThis = makeRef(*this), completionHandler = completionHandlerCaller.release()] (ShouldContinue shouldContinue) mutable {
176 if (completionHandler)
177 completionHandler();
178 if (shouldContinue == ShouldContinue::No)
179 stop();
180 });
181
182 m_loader->addResponseForTesting(response);
183}
184
185bool MediaResource::shouldCacheResponse(CachedResource& resource, const ResourceResponse& response)
186{
187 ASSERT_UNUSED(resource, &resource == m_resource);
188
189 RefPtr<MediaResource> protectedThis(this);
190 if (m_client)
191 return m_client->shouldCacheResponse(*this, response);
192 return true;
193}
194
195void MediaResource::redirectReceived(CachedResource& resource, ResourceRequest&& request, const ResourceResponse& response, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
196{
197 ASSERT_UNUSED(resource, &resource == m_resource);
198
199 RefPtr<MediaResource> protectedThis(this);
200 if (m_client)
201 m_client->redirectReceived(*this, WTFMove(request), response, WTFMove(completionHandler));
202 else
203 completionHandler(WTFMove(request));
204}
205
206void MediaResource::dataSent(CachedResource& resource, unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
207{
208 ASSERT_UNUSED(resource, &resource == m_resource);
209
210 RefPtr<MediaResource> protectedThis(this);
211 if (m_client)
212 m_client->dataSent(*this, bytesSent, totalBytesToBeSent);
213}
214
215void MediaResource::dataReceived(CachedResource& resource, const char* data, int dataLength)
216{
217 ASSERT_UNUSED(resource, &resource == m_resource);
218
219 RefPtr<MediaResource> protectedThis(this);
220 if (m_client)
221 m_client->dataReceived(*this, data, dataLength);
222}
223
224void MediaResource::notifyFinished(CachedResource& resource)
225{
226 ASSERT_UNUSED(resource, &resource == m_resource);
227
228 RefPtr<MediaResource> protectedThis(this);
229 if (m_client) {
230 if (m_resource->loadFailedOrCanceled())
231 m_client->loadFailed(*this, m_resource->resourceError());
232 else
233 m_client->loadFinished(*this);
234 }
235 stop();
236}
237
238} // namespace WebCore
239
240#endif
241