ba28f32f118b5e00dcfe7b5d14b7c08c18aa07c4
[WebKit-https.git] / Source / WebCore / platform / network / BlobRegistryImpl.cpp
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 "FileMetadata.h"
39 #include "FileSystem.h"
40 #include "ResourceError.h"
41 #include "ResourceHandle.h"
42 #include "ResourceRequest.h"
43 #include "ResourceResponse.h"
44 #include "ScopeGuard.h"
45 #include <wtf/MainThread.h>
46 #include <wtf/NeverDestroyed.h>
47 #include <wtf/StdLibExtras.h>
48 #include <wtf/WorkQueue.h>
49
50 namespace WebCore {
51
52 BlobRegistryImpl::~BlobRegistryImpl()
53 {
54 }
55
56 static RefPtr<ResourceHandle> createResourceHandle(const ResourceRequest& request, ResourceHandleClient* client)
57 {
58     return static_cast<BlobRegistryImpl&>(blobRegistry()).createResourceHandle(request, client);
59 }
60
61 static void loadResourceSynchronously(NetworkingContext*, const ResourceRequest& request, StoredCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
62 {
63     BlobData* blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(request.url());
64     BlobResourceHandle::loadResourceSynchronously(blobData, request, error, response, data);
65 }
66
67 static void registerBlobResourceHandleConstructor()
68 {
69     static bool didRegister = false;
70     if (!didRegister) {
71         ResourceHandle::registerBuiltinConstructor("blob", createResourceHandle);
72         ResourceHandle::registerBuiltinSynchronousLoader("blob", loadResourceSynchronously);
73         didRegister = true;
74     }
75 }
76
77 RefPtr<ResourceHandle> BlobRegistryImpl::createResourceHandle(const ResourceRequest& request, ResourceHandleClient* client)
78 {
79     auto handle = BlobResourceHandle::createAsync(getBlobDataFromURL(request.url()), request, client);
80     if (!handle)
81         return nullptr;
82
83     handle->start();
84     return handle;
85 }
86
87 void BlobRegistryImpl::appendStorageItems(BlobData* blobData, const BlobDataItemList& items, long long offset, long long length)
88 {
89     ASSERT(length != BlobDataItem::toEndOfFile);
90
91     BlobDataItemList::const_iterator iter = items.begin();
92     if (offset) {
93         for (; iter != items.end(); ++iter) {
94             if (offset >= iter->length())
95                 offset -= iter->length();
96             else
97                 break;
98         }
99     }
100
101     for (; iter != items.end() && length > 0; ++iter) {
102         long long currentLength = iter->length() - offset;
103         long long newLength = currentLength > length ? length : currentLength;
104         if (iter->type() == BlobDataItem::Type::Data)
105             blobData->appendData(iter->data(), iter->offset() + offset, newLength);
106         else {
107             ASSERT(iter->type() == BlobDataItem::Type::File);
108             blobData->appendFile(iter->file(), iter->offset() + offset, newLength);
109         }
110         length -= newLength;
111         offset = 0;
112     }
113     ASSERT(!length);
114 }
115
116 void BlobRegistryImpl::registerFileBlobURL(const URL& url, RefPtr<BlobDataFileReference>&& file, const String& contentType)
117 {
118     ASSERT(isMainThread());
119     registerBlobResourceHandleConstructor();
120
121     RefPtr<BlobData> blobData = BlobData::create(contentType);
122
123     blobData->appendFile(file);
124     m_blobs.set(url.string(), blobData.release());
125 }
126
127 void BlobRegistryImpl::registerBlobURL(const URL& url, Vector<BlobPart> blobParts, const String& contentType)
128 {
129     ASSERT(isMainThread());
130     registerBlobResourceHandleConstructor();
131
132     RefPtr<BlobData> blobData = BlobData::create(contentType);
133
134     // The blob data is stored in the "canonical" way. That is, it only contains a list of Data and File items.
135     // 1) The Data item is denoted by the raw data and the range.
136     // 2) The File item is denoted by the file path, the range and the expected modification time.
137     // 3) The URL item is denoted by the URL, the range and the expected modification time.
138     // All the Blob items in the passing blob data are resolved and expanded into a set of Data and File items.
139
140     for (BlobPart& part : blobParts) {
141         switch (part.type()) {
142         case BlobPart::Data: {
143             auto movedData = part.moveData();
144             auto data = ThreadSafeDataBuffer::adoptVector(movedData);
145             blobData->appendData(data);
146             break;
147         }
148         case BlobPart::Blob: {
149             if (auto blob = m_blobs.get(part.url().string())) {
150                 for (const BlobDataItem& item : blob->items())
151                     blobData->m_items.append(item);
152             }
153             break;
154         }
155         }
156     }
157
158     m_blobs.set(url.string(), blobData.release());
159 }
160
161 void BlobRegistryImpl::registerBlobURL(const URL& url, const URL& srcURL)
162 {
163     registerBlobURLOptionallyFileBacked(url, srcURL, nullptr);
164 }
165
166 void BlobRegistryImpl::registerBlobURLOptionallyFileBacked(const URL& url, const URL& srcURL, RefPtr<BlobDataFileReference>&& file)
167 {
168     ASSERT(isMainThread());
169     registerBlobResourceHandleConstructor();
170
171     BlobData* src = getBlobDataFromURL(srcURL);
172     if (src) {
173         m_blobs.set(url.string(), src);
174         return;
175     }
176
177     if (file == nullptr || file->path().isEmpty())
178         return;
179
180     RefPtr<BlobData> backingFile = BlobData::create({ });
181     backingFile->appendFile(WTFMove(file));
182
183     m_blobs.set(url.string(), backingFile.release());
184 }
185
186 void BlobRegistryImpl::registerBlobURLForSlice(const URL& url, const URL& srcURL, long long start, long long end)
187 {
188     ASSERT(isMainThread());
189     BlobData* originalData = getBlobDataFromURL(srcURL);
190     if (!originalData)
191         return;
192
193     unsigned long long originalSize = blobSize(srcURL);
194
195     // Convert the negative value that is used to select from the end.
196     if (start < 0)
197         start = start + originalSize;
198     if (end < 0)
199         end = end + originalSize;
200
201     // Clamp the range if it exceeds the size limit.
202     if (start < 0)
203         start = 0;
204     if (end < 0)
205         end = 0;
206     if (static_cast<unsigned long long>(start) >= originalSize) {
207         start = 0;
208         end = 0;
209     } else if (end < start)
210         end = start;
211     else if (static_cast<unsigned long long>(end) > originalSize)
212         end = originalSize;
213
214     unsigned long long newLength = end - start;
215     RefPtr<BlobData> newData = BlobData::create(originalData->contentType());
216
217     appendStorageItems(newData.get(), originalData->items(), start, newLength);
218
219     m_blobs.set(url.string(), newData.release());
220 }
221
222 void BlobRegistryImpl::unregisterBlobURL(const URL& url)
223 {
224     ASSERT(isMainThread());
225     m_blobs.remove(url.string());
226 }
227
228 BlobData* BlobRegistryImpl::getBlobDataFromURL(const URL& url) const
229 {
230     ASSERT(isMainThread());
231     return m_blobs.get(url.string());
232 }
233
234 unsigned long long BlobRegistryImpl::blobSize(const URL& url)
235 {
236     ASSERT(isMainThread());
237     BlobData* data = getBlobDataFromURL(url);
238     if (!data)
239         return 0;
240
241     unsigned long long result = 0;
242     for (const BlobDataItem& item : data->items())
243         result += item.length();
244
245     return result;
246 }
247
248 static WorkQueue& blobUtilityQueue()
249 {
250     static NeverDestroyed<Ref<WorkQueue>> queue(WorkQueue::create("org.webkit.BlobUtility", WorkQueue::Type::Serial, WorkQueue::QOS::Background));
251     return queue.get();
252 }
253
254 struct BlobForFileWriting {
255     String blobURL;
256     Vector<std::pair<String, ThreadSafeDataBuffer>> filePathsOrDataBuffers;
257 };
258
259 void BlobRegistryImpl::writeBlobsToTemporaryFiles(const Vector<String>& blobURLs, std::function<void (const Vector<String>& filePaths)> completionHandler)
260 {
261     Vector<BlobForFileWriting> blobsForWriting;
262     for (auto& url : blobURLs) {
263         blobsForWriting.append({ });
264         blobsForWriting.last().blobURL = url.isolatedCopy();
265
266         auto* blobData = getBlobDataFromURL({ ParsedURLString, url });
267         if (!blobData) {
268             Vector<String> filePaths;
269             completionHandler(filePaths);
270             return;
271         }
272
273         for (auto& item : blobData->items()) {
274             switch (item.type()) {
275             case BlobDataItem::Type::Data:
276                 blobsForWriting.last().filePathsOrDataBuffers.append({ { }, item.data() });
277                 break;
278             case BlobDataItem::Type::File:
279                 blobsForWriting.last().filePathsOrDataBuffers.append({ item.file()->path().isolatedCopy(), { } });
280                 break;
281             default:
282                 ASSERT_NOT_REACHED();
283             }
284         }
285     }
286
287     blobUtilityQueue().dispatch([blobsForWriting = WTFMove(blobsForWriting), completionHandler = WTFMove(completionHandler)]() mutable {
288         Vector<String> filePaths;
289
290         ScopeGuard completionCaller([completionHandler]() mutable {
291             callOnMainThread([completionHandler = WTFMove(completionHandler)]() {
292                 Vector<String> filePaths;
293                 completionHandler(filePaths);
294             });
295         });
296
297         for (auto& blob : blobsForWriting) {
298             PlatformFileHandle file;
299             String tempFilePath = openTemporaryFile(ASCIILiteral("Blob"), file);
300
301             ScopeGuard fileCloser([file]() {
302                 PlatformFileHandle handle = file;
303                 closeFile(handle);
304             });
305             
306             if (tempFilePath.isEmpty() || !isHandleValid(file)) {
307                 LOG_ERROR("Failed to open temporary file for writing a Blob to IndexedDB");
308                 return;
309             }
310
311             for (auto& part : blob.filePathsOrDataBuffers) {
312                 if (part.second.data()) {
313                     int length = part.second.data()->size();
314                     if (writeToFile(file, reinterpret_cast<const char*>(part.second.data()->data()), length) != length) {
315                         LOG_ERROR("Failed writing a Blob to temporary file for storage in IndexedDB");
316                         return;
317                     }
318                 } else {
319                     ASSERT(!part.first.isEmpty());
320                     if (!appendFileContentsToFileHandle(part.first, file)) {
321                         LOG_ERROR("Failed copying File contents to a Blob temporary file for storage in IndexedDB (%s to %s)", part.first.utf8().data(), tempFilePath.utf8().data());
322                         return;
323                     }
324                 }
325             }
326
327             filePaths.append(tempFilePath.isolatedCopy());
328         }
329
330         completionCaller.disable();
331         callOnMainThread([completionHandler = WTFMove(completionHandler), filePaths = WTFMove(filePaths)]() {
332             completionHandler(filePaths);
333         });
334     });
335 }
336
337 } // namespace WebCore