1 | /* |
2 | * Copyright (C) 2010 Google Inc. All rights reserved. |
3 | * Copyright (C) 2013 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 are |
7 | * met: |
8 | * |
9 | * * Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * * Redistributions in binary form must reproduce the above |
12 | * copyright notice, this list of conditions and the following disclaimer |
13 | * in the documentation and/or other materials provided with the |
14 | * distribution. |
15 | * * Neither the name of Google Inc. nor the names of its |
16 | * contributors may be used to endorse or promote products derived from |
17 | * this software without specific prior written permission. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include "config.h" |
33 | #include "BlobRegistryImpl.h" |
34 | |
35 | #include "BlobData.h" |
36 | #include "BlobPart.h" |
37 | #include "BlobResourceHandle.h" |
38 | #include "ResourceError.h" |
39 | #include "ResourceHandle.h" |
40 | #include "ResourceRequest.h" |
41 | #include "ResourceResponse.h" |
42 | #include <wtf/CompletionHandler.h> |
43 | #include <wtf/FileMetadata.h> |
44 | #include <wtf/FileSystem.h> |
45 | #include <wtf/MainThread.h> |
46 | #include <wtf/NeverDestroyed.h> |
47 | #include <wtf/Scope.h> |
48 | #include <wtf/StdLibExtras.h> |
49 | #include <wtf/WorkQueue.h> |
50 | |
51 | namespace WebCore { |
52 | |
53 | BlobRegistryImpl::~BlobRegistryImpl() = default; |
54 | |
55 | static Ref<ResourceHandle> createBlobResourceHandle(const ResourceRequest& request, ResourceHandleClient* client) |
56 | { |
57 | return static_cast<BlobRegistryImpl&>(blobRegistry()).createResourceHandle(request, client); |
58 | } |
59 | |
60 | static void loadBlobResourceSynchronously(NetworkingContext*, const ResourceRequest& request, StoredCredentialsPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data) |
61 | { |
62 | BlobData* blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(request.url()); |
63 | BlobResourceHandle::loadResourceSynchronously(blobData, request, error, response, data); |
64 | } |
65 | |
66 | static void registerBlobResourceHandleConstructor() |
67 | { |
68 | static bool didRegister = false; |
69 | if (!didRegister) { |
70 | ResourceHandle::registerBuiltinConstructor("blob" , createBlobResourceHandle); |
71 | ResourceHandle::registerBuiltinSynchronousLoader("blob" , loadBlobResourceSynchronously); |
72 | didRegister = true; |
73 | } |
74 | } |
75 | |
76 | Ref<ResourceHandle> BlobRegistryImpl::createResourceHandle(const ResourceRequest& request, ResourceHandleClient* client) |
77 | { |
78 | auto handle = BlobResourceHandle::createAsync(getBlobDataFromURL(request.url()), request, client); |
79 | handle->start(); |
80 | return handle; |
81 | } |
82 | |
83 | void BlobRegistryImpl::appendStorageItems(BlobData* blobData, const BlobDataItemList& items, long long offset, long long length) |
84 | { |
85 | ASSERT(length != BlobDataItem::toEndOfFile); |
86 | |
87 | BlobDataItemList::const_iterator iter = items.begin(); |
88 | if (offset) { |
89 | for (; iter != items.end(); ++iter) { |
90 | if (offset >= iter->length()) |
91 | offset -= iter->length(); |
92 | else |
93 | break; |
94 | } |
95 | } |
96 | |
97 | for (; iter != items.end() && length > 0; ++iter) { |
98 | long long currentLength = iter->length() - offset; |
99 | long long newLength = currentLength > length ? length : currentLength; |
100 | if (iter->type() == BlobDataItem::Type::Data) |
101 | blobData->appendData(iter->data(), iter->offset() + offset, newLength); |
102 | else { |
103 | ASSERT(iter->type() == BlobDataItem::Type::File); |
104 | blobData->appendFile(iter->file(), iter->offset() + offset, newLength); |
105 | } |
106 | length -= newLength; |
107 | offset = 0; |
108 | } |
109 | ASSERT(!length); |
110 | } |
111 | |
112 | void BlobRegistryImpl::registerFileBlobURL(const URL& url, Ref<BlobDataFileReference>&& file, const String& contentType) |
113 | { |
114 | ASSERT(isMainThread()); |
115 | registerBlobResourceHandleConstructor(); |
116 | |
117 | auto blobData = BlobData::create(contentType); |
118 | blobData->appendFile(WTFMove(file)); |
119 | m_blobs.set(url.string(), WTFMove(blobData)); |
120 | } |
121 | |
122 | void BlobRegistryImpl::registerBlobURL(const URL& url, Vector<BlobPart>&& blobParts, const String& contentType) |
123 | { |
124 | ASSERT(isMainThread()); |
125 | registerBlobResourceHandleConstructor(); |
126 | |
127 | auto blobData = BlobData::create(contentType); |
128 | |
129 | // The blob data is stored in the "canonical" way. That is, it only contains a list of Data and File items. |
130 | // 1) The Data item is denoted by the raw data and the range. |
131 | // 2) The File item is denoted by the file path, the range and the expected modification time. |
132 | // 3) The URL item is denoted by the URL, the range and the expected modification time. |
133 | // All the Blob items in the passing blob data are resolved and expanded into a set of Data and File items. |
134 | |
135 | for (BlobPart& part : blobParts) { |
136 | switch (part.type()) { |
137 | case BlobPart::Data: { |
138 | auto movedData = part.moveData(); |
139 | auto data = ThreadSafeDataBuffer::create(WTFMove(movedData)); |
140 | blobData->appendData(data); |
141 | break; |
142 | } |
143 | case BlobPart::Blob: { |
144 | if (auto blob = m_blobs.get(part.url().string())) { |
145 | for (const BlobDataItem& item : blob->items()) |
146 | blobData->m_items.append(item); |
147 | } |
148 | break; |
149 | } |
150 | } |
151 | } |
152 | |
153 | m_blobs.set(url.string(), WTFMove(blobData)); |
154 | } |
155 | |
156 | void BlobRegistryImpl::registerBlobURL(const URL& url, const URL& srcURL) |
157 | { |
158 | registerBlobURLOptionallyFileBacked(url, srcURL, nullptr, { }); |
159 | } |
160 | |
161 | void BlobRegistryImpl::registerBlobURLOptionallyFileBacked(const URL& url, const URL& srcURL, RefPtr<BlobDataFileReference>&& file, const String& contentType) |
162 | { |
163 | ASSERT(isMainThread()); |
164 | registerBlobResourceHandleConstructor(); |
165 | |
166 | BlobData* src = getBlobDataFromURL(srcURL); |
167 | if (src) { |
168 | m_blobs.set(url.string(), src); |
169 | return; |
170 | } |
171 | |
172 | if (!file || file->path().isEmpty()) |
173 | return; |
174 | |
175 | auto backingFile = BlobData::create(contentType); |
176 | backingFile->appendFile(file.releaseNonNull()); |
177 | |
178 | m_blobs.set(url.string(), WTFMove(backingFile)); |
179 | } |
180 | |
181 | void BlobRegistryImpl::registerBlobURLForSlice(const URL& url, const URL& srcURL, long long start, long long end) |
182 | { |
183 | ASSERT(isMainThread()); |
184 | BlobData* originalData = getBlobDataFromURL(srcURL); |
185 | if (!originalData) |
186 | return; |
187 | |
188 | unsigned long long originalSize = blobSize(srcURL); |
189 | |
190 | // Convert the negative value that is used to select from the end. |
191 | if (start < 0) |
192 | start = start + originalSize; |
193 | if (end < 0) |
194 | end = end + originalSize; |
195 | |
196 | // Clamp the range if it exceeds the size limit. |
197 | if (start < 0) |
198 | start = 0; |
199 | if (end < 0) |
200 | end = 0; |
201 | if (static_cast<unsigned long long>(start) >= originalSize) { |
202 | start = 0; |
203 | end = 0; |
204 | } else if (end < start) |
205 | end = start; |
206 | else if (static_cast<unsigned long long>(end) > originalSize) |
207 | end = originalSize; |
208 | |
209 | unsigned long long newLength = end - start; |
210 | auto newData = BlobData::create(originalData->contentType()); |
211 | |
212 | appendStorageItems(newData.ptr(), originalData->items(), start, newLength); |
213 | |
214 | m_blobs.set(url.string(), WTFMove(newData)); |
215 | } |
216 | |
217 | void BlobRegistryImpl::unregisterBlobURL(const URL& url) |
218 | { |
219 | ASSERT(isMainThread()); |
220 | m_blobs.remove(url.string()); |
221 | } |
222 | |
223 | BlobData* BlobRegistryImpl::getBlobDataFromURL(const URL& url) const |
224 | { |
225 | ASSERT(isMainThread()); |
226 | return m_blobs.get(url.string()); |
227 | } |
228 | |
229 | unsigned long long BlobRegistryImpl::blobSize(const URL& url) |
230 | { |
231 | ASSERT(isMainThread()); |
232 | BlobData* data = getBlobDataFromURL(url); |
233 | if (!data) |
234 | return 0; |
235 | |
236 | unsigned long long result = 0; |
237 | for (const BlobDataItem& item : data->items()) |
238 | result += item.length(); |
239 | |
240 | return result; |
241 | } |
242 | |
243 | static WorkQueue& blobUtilityQueue() |
244 | { |
245 | static auto& queue = WorkQueue::create("org.webkit.BlobUtility" , WorkQueue::Type::Serial, WorkQueue::QOS::Background).leakRef(); |
246 | return queue; |
247 | } |
248 | |
249 | bool BlobRegistryImpl::populateBlobsForFileWriting(const Vector<String>& blobURLs, Vector<BlobForFileWriting>& blobsForWriting) |
250 | { |
251 | for (auto& url : blobURLs) { |
252 | blobsForWriting.append({ }); |
253 | blobsForWriting.last().blobURL = url.isolatedCopy(); |
254 | |
255 | auto* blobData = getBlobDataFromURL({ { }, url }); |
256 | if (!blobData) |
257 | return false; |
258 | |
259 | for (auto& item : blobData->items()) { |
260 | switch (item.type()) { |
261 | case BlobDataItem::Type::Data: |
262 | blobsForWriting.last().filePathsOrDataBuffers.append({ { }, item.data() }); |
263 | break; |
264 | case BlobDataItem::Type::File: |
265 | blobsForWriting.last().filePathsOrDataBuffers.append({ item.file()->path().isolatedCopy(), { } }); |
266 | break; |
267 | default: |
268 | ASSERT_NOT_REACHED(); |
269 | } |
270 | } |
271 | } |
272 | return true; |
273 | } |
274 | |
275 | static bool writeFilePathsOrDataBuffersToFile(const Vector<std::pair<String, ThreadSafeDataBuffer>>& filePathsOrDataBuffers, FileSystem::PlatformFileHandle file, const String& path) |
276 | { |
277 | auto fileCloser = WTF::makeScopeExit([file]() mutable { |
278 | FileSystem::closeFile(file); |
279 | }); |
280 | |
281 | if (path.isEmpty() || !FileSystem::isHandleValid(file)) { |
282 | LOG_ERROR("Failed to open temporary file for writing a Blob" ); |
283 | return false; |
284 | } |
285 | |
286 | for (auto& part : filePathsOrDataBuffers) { |
287 | if (part.second.data()) { |
288 | int length = part.second.data()->size(); |
289 | if (FileSystem::writeToFile(file, reinterpret_cast<const char*>(part.second.data()->data()), length) != length) { |
290 | LOG_ERROR("Failed writing a Blob to temporary file" ); |
291 | return false; |
292 | } |
293 | } else { |
294 | ASSERT(!part.first.isEmpty()); |
295 | if (!FileSystem::appendFileContentsToFileHandle(part.first, file)) { |
296 | LOG_ERROR("Failed copying File contents to a Blob temporary file (%s to %s)" , part.first.utf8().data(), path.utf8().data()); |
297 | return false; |
298 | } |
299 | } |
300 | } |
301 | return true; |
302 | } |
303 | |
304 | void BlobRegistryImpl::writeBlobsToTemporaryFiles(const Vector<String>& blobURLs, CompletionHandler<void(Vector<String>&& filePaths)>&& completionHandler) |
305 | { |
306 | Vector<BlobForFileWriting> blobsForWriting; |
307 | if (!populateBlobsForFileWriting(blobURLs, blobsForWriting)) { |
308 | completionHandler({ }); |
309 | return; |
310 | } |
311 | |
312 | blobUtilityQueue().dispatch([blobsForWriting = WTFMove(blobsForWriting), completionHandler = WTFMove(completionHandler)]() mutable { |
313 | Vector<String> filePaths; |
314 | for (auto& blob : blobsForWriting) { |
315 | FileSystem::PlatformFileHandle file; |
316 | String tempFilePath = FileSystem::openTemporaryFile("Blob"_s , file); |
317 | if (!writeFilePathsOrDataBuffersToFile(blob.filePathsOrDataBuffers, file, tempFilePath)) { |
318 | filePaths.clear(); |
319 | break; |
320 | } |
321 | filePaths.append(tempFilePath.isolatedCopy()); |
322 | } |
323 | |
324 | callOnMainThread([completionHandler = WTFMove(completionHandler), filePaths = WTFMove(filePaths)] () mutable { |
325 | completionHandler(WTFMove(filePaths)); |
326 | }); |
327 | }); |
328 | } |
329 | |
330 | void BlobRegistryImpl::writeBlobToFilePath(const URL& blobURL, const String& path, Function<void(bool success)>&& completionHandler) |
331 | { |
332 | Vector<BlobForFileWriting> blobsForWriting; |
333 | if (!populateBlobsForFileWriting({ blobURL }, blobsForWriting) || blobsForWriting.size() != 1) { |
334 | completionHandler(false); |
335 | return; |
336 | } |
337 | |
338 | blobUtilityQueue().dispatch([path, blobsForWriting = WTFMove(blobsForWriting), completionHandler = WTFMove(completionHandler)]() mutable { |
339 | bool success = writeFilePathsOrDataBuffersToFile(blobsForWriting.first().filePathsOrDataBuffers, FileSystem::openFile(path, FileSystem::FileOpenMode::Write), path); |
340 | callOnMainThread([success, completionHandler = WTFMove(completionHandler)]() { |
341 | completionHandler(success); |
342 | }); |
343 | }); |
344 | } |
345 | |
346 | } // namespace WebCore |
347 | |