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
51namespace WebCore {
52
53BlobRegistryImpl::~BlobRegistryImpl() = default;
54
55static Ref<ResourceHandle> createBlobResourceHandle(const ResourceRequest& request, ResourceHandleClient* client)
56{
57 return static_cast<BlobRegistryImpl&>(blobRegistry()).createResourceHandle(request, client);
58}
59
60static 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
66static 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
76Ref<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
83void 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
112void 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
122void 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
156void BlobRegistryImpl::registerBlobURL(const URL& url, const URL& srcURL)
157{
158 registerBlobURLOptionallyFileBacked(url, srcURL, nullptr, { });
159}
160
161void 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
181void 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
217void BlobRegistryImpl::unregisterBlobURL(const URL& url)
218{
219 ASSERT(isMainThread());
220 m_blobs.remove(url.string());
221}
222
223BlobData* BlobRegistryImpl::getBlobDataFromURL(const URL& url) const
224{
225 ASSERT(isMainThread());
226 return m_blobs.get(url.string());
227}
228
229unsigned 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
243static WorkQueue& blobUtilityQueue()
244{
245 static auto& queue = WorkQueue::create("org.webkit.BlobUtility", WorkQueue::Type::Serial, WorkQueue::QOS::Background).leakRef();
246 return queue;
247}
248
249bool 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
275static 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
304void 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
330void 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