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 | |
42 | namespace WebCore { |
43 | |
44 | MediaResourceLoader::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 | |
52 | MediaResourceLoader::~MediaResourceLoader() |
53 | { |
54 | ASSERT(m_resources.isEmpty()); |
55 | } |
56 | |
57 | void MediaResourceLoader::contextDestroyed() |
58 | { |
59 | ContextDestructionObserver::contextDestroyed(); |
60 | m_document = nullptr; |
61 | m_mediaElement = nullptr; |
62 | } |
63 | |
64 | RefPtr<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 | |
112 | void MediaResourceLoader::removeResource(MediaResource& mediaResource) |
113 | { |
114 | ASSERT(m_resources.contains(&mediaResource)); |
115 | m_resources.remove(&mediaResource); |
116 | } |
117 | |
118 | void 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 | |
126 | Ref<MediaResource> MediaResource::create(MediaResourceLoader& loader, CachedResourceHandle<CachedRawResource> resource) |
127 | { |
128 | return adoptRef(*new MediaResource(loader, resource)); |
129 | } |
130 | |
131 | MediaResource::MediaResource(MediaResourceLoader& loader, CachedResourceHandle<CachedRawResource> resource) |
132 | : m_loader(loader) |
133 | , m_resource(resource) |
134 | { |
135 | ASSERT(resource); |
136 | resource->addClient(*this); |
137 | } |
138 | |
139 | MediaResource::~MediaResource() |
140 | { |
141 | stop(); |
142 | m_loader->removeResource(*this); |
143 | } |
144 | |
145 | void MediaResource::stop() |
146 | { |
147 | if (!m_resource) |
148 | return; |
149 | |
150 | m_resource->removeClient(*this); |
151 | m_resource = nullptr; |
152 | } |
153 | |
154 | void 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 | |
185 | bool 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 | |
195 | void 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 | |
206 | void 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 | |
215 | void 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 | |
224 | void 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 | |