callOnMainThread() should not copy captured lambda variables
[WebKit.git] / Source / WebCore / platform / network / BlobResourceHandle.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32
33 #include "BlobResourceHandle.h"
34
35 #include "AsyncFileStream.h"
36 #include "BlobData.h"
37 #include "FileStream.h"
38 #include "FileSystem.h"
39 #include "HTTPHeaderNames.h"
40 #include "HTTPParsers.h"
41 #include "ParsedContentRange.h"
42 #include "URL.h"
43 #include "ResourceError.h"
44 #include "ResourceHandleClient.h"
45 #include "ResourceRequest.h"
46 #include "ResourceResponse.h"
47 #include "SharedBuffer.h"
48 #include <wtf/MainThread.h>
49 #include <wtf/Ref.h>
50
51 namespace WebCore {
52
53 static const unsigned bufferSize = 512 * 1024;
54
55 static const int httpOK = 200;
56 static const int httpPartialContent = 206;
57 static const int httpNotAllowed = 403;
58 static const int httpNotFound = 404;
59 static const int httpRequestedRangeNotSatisfiable = 416;
60 static const int httpInternalError = 500;
61 static const char* httpOKText = "OK";
62 static const char* httpPartialContentText = "Partial Content";
63 static const char* httpNotAllowedText = "Not Allowed";
64 static const char* httpNotFoundText = "Not Found";
65 static const char* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable";
66 static const char* httpInternalErrorText = "Internal Server Error";
67
68 static const char* const webKitBlobResourceDomain = "WebKitBlobResource";
69 enum {
70     notFoundError = 1,
71     securityError = 2,
72     rangeError = 3,
73     notReadableError = 4,
74     methodNotAllowed = 5
75 };
76
77 ///////////////////////////////////////////////////////////////////////////////
78 // BlobResourceSynchronousLoader
79
80 namespace {
81
82 class BlobResourceSynchronousLoader : public ResourceHandleClient {
83 public:
84     BlobResourceSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&);
85
86     void didReceiveResponse(ResourceHandle*, const ResourceResponse&) override;
87     void didReceiveData(ResourceHandle*, const char*, unsigned, int /*encodedDataLength*/) override;
88     void didFinishLoading(ResourceHandle*, double /*finishTime*/) override;
89     void didFail(ResourceHandle*, const ResourceError&) override;
90
91 private:
92     ResourceError& m_error;
93     ResourceResponse& m_response;
94     Vector<char>& m_data;
95 };
96
97 BlobResourceSynchronousLoader::BlobResourceSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data)
98     : m_error(error)
99     , m_response(response)
100     , m_data(data)
101 {
102 }
103
104 void BlobResourceSynchronousLoader::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
105 {
106     // We cannot handle the size that is more than maximum integer.
107     if (response.expectedContentLength() > INT_MAX) {
108         m_error = ResourceError(webKitBlobResourceDomain, notReadableError, response.url(), "File is too large");
109         return;
110     }
111
112     m_response = response;
113
114     // Read all the data.
115     m_data.resize(static_cast<size_t>(response.expectedContentLength()));
116     static_cast<BlobResourceHandle*>(handle)->readSync(m_data.data(), static_cast<int>(m_data.size()));
117 }
118
119 void BlobResourceSynchronousLoader::didReceiveData(ResourceHandle*, const char*, unsigned, int)
120 {
121 }
122
123 void BlobResourceSynchronousLoader::didFinishLoading(ResourceHandle*, double)
124 {
125 }
126
127 void BlobResourceSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
128 {
129     m_error = error;
130 }
131
132 }
133
134 ///////////////////////////////////////////////////////////////////////////////
135 // BlobResourceHandle
136
137 PassRefPtr<BlobResourceHandle> BlobResourceHandle::createAsync(BlobData* blobData, const ResourceRequest& request, ResourceHandleClient* client)
138 {
139     return adoptRef(new BlobResourceHandle(blobData, request, client, true));
140 }
141
142 void BlobResourceHandle::loadResourceSynchronously(BlobData* blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
143 {
144     if (!equalLettersIgnoringASCIICase(request.httpMethod(), "get")) {
145         error = ResourceError(webKitBlobResourceDomain, methodNotAllowed, response.url(), "Request method must be GET");
146         return;
147     }
148
149     BlobResourceSynchronousLoader loader(error, response, data);
150     RefPtr<BlobResourceHandle> handle = adoptRef(new BlobResourceHandle(blobData, request, &loader, false));
151     handle->start();
152 }
153
154 BlobResourceHandle::BlobResourceHandle(BlobData* blobData, const ResourceRequest& request, ResourceHandleClient* client, bool async)
155     : ResourceHandle(nullptr, request, client, false, false)
156     , m_blobData(blobData)
157     , m_async(async)
158 {
159     if (m_async)
160         m_asyncStream = std::make_unique<AsyncFileStream>(*this);
161     else
162         m_stream = std::make_unique<FileStream>();
163 }
164
165 BlobResourceHandle::~BlobResourceHandle()
166 {
167 }
168
169 void BlobResourceHandle::cancel()
170 {
171     m_asyncStream = nullptr;
172
173     m_aborted = true;
174
175     ResourceHandle::cancel();
176 }
177
178 void BlobResourceHandle::continueDidReceiveResponse()
179 {
180     // BlobResourceHandle doesn't wait for didReceiveResponse, and it currently cannot be used for downloading.
181 }
182
183 void BlobResourceHandle::start()
184 {
185     if (!m_async) {
186         doStart();
187         return;
188     }
189
190     // Finish this async call quickly and return.
191     callOnMainThread([protectedThis = Ref<BlobResourceHandle>(*this)]() mutable {
192         protectedThis->doStart();
193     });
194 }
195
196 void BlobResourceHandle::doStart()
197 {
198     ASSERT(isMainThread());
199
200     // Do not continue if the request is aborted or an error occurs.
201     if (m_aborted || m_errorCode)
202         return;
203
204     if (!equalLettersIgnoringASCIICase(firstRequest().httpMethod(), "get")) {
205         m_errorCode = methodNotAllowed;
206         notifyResponse();
207         return;
208     }
209
210     // If the blob data is not found, fail now.
211     if (!m_blobData) {
212         m_errorCode = notFoundError;
213         notifyResponse();
214         return;
215     }
216
217     // Parse the "Range" header we care about.
218     String range = firstRequest().httpHeaderField(HTTPHeaderName::Range);
219     if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
220         m_errorCode = rangeError;
221         notifyResponse();
222         return;
223     }
224
225     if (m_async)
226         getSizeForNext();
227     else {
228         Ref<BlobResourceHandle> protectedThis(*this); // getSizeForNext calls the client
229         for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i)
230             getSizeForNext();
231         notifyResponse();
232     }
233 }
234
235 void BlobResourceHandle::getSizeForNext()
236 {
237     ASSERT(isMainThread());
238
239     // Do we finish validating and counting size for all items?
240     if (m_sizeItemCount >= m_blobData->items().size()) {
241         seek();
242
243         // Start reading if in asynchronous mode.
244         if (m_async) {
245             Ref<BlobResourceHandle> protectedThis(*this);
246             notifyResponse();
247             m_buffer.resize(bufferSize);
248             readAsync();
249         }
250         return;
251     }
252
253     const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
254     switch (item.type()) {
255     case BlobDataItem::Type::Data:
256         didGetSize(item.length());
257         break;
258     case BlobDataItem::Type::File:
259         // Files know their sizes, but asking the stream to verify that the file wasn't modified.
260         if (m_async)
261             m_asyncStream->getSize(item.file()->path(), item.file()->expectedModificationTime());
262         else
263             didGetSize(m_stream->getSize(item.file()->path(), item.file()->expectedModificationTime()));
264         break;
265     default:
266         ASSERT_NOT_REACHED();
267     }
268 }
269
270 void BlobResourceHandle::didGetSize(long long size)
271 {
272     ASSERT(isMainThread());
273
274     // Do not continue if the request is aborted or an error occurs.
275     if (m_aborted || m_errorCode)
276         return;
277
278     // If the size is -1, it means the file has been moved or changed. Fail now.
279     if (size == -1) {
280         m_errorCode = notFoundError;
281         notifyResponse();
282         return;
283     }
284
285     // The size passed back is the size of the whole file. If the underlying item is a sliced file, we need to use the slice length.
286     const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
287     size = item.length();
288
289     // Cache the size.
290     m_itemLengthList.append(size);
291
292     // Count the size.
293     m_totalSize += size;
294     m_totalRemainingSize += size;
295     m_sizeItemCount++;
296
297     // Continue with the next item.
298     getSizeForNext();
299 }
300
301 void BlobResourceHandle::seek()
302 {
303     ASSERT(isMainThread());
304
305     // Convert from the suffix length to the range.
306     if (m_rangeSuffixLength != kPositionNotSpecified) {
307         m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
308         m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
309     }
310
311     // Bail out if the range is not provided.
312     if (m_rangeOffset == kPositionNotSpecified)
313         return;
314
315     // Skip the initial items that are not in the range.
316     long long offset = m_rangeOffset;
317     for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount)
318         offset -= m_itemLengthList[m_readItemCount];
319
320     // Set the offset that need to jump to for the first item in the range.
321     m_currentItemReadSize = offset;
322
323     // Adjust the total remaining size in order not to go beyond the range.
324     if (m_rangeEnd != kPositionNotSpecified) {
325         long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
326         if (m_totalRemainingSize > rangeSize)
327             m_totalRemainingSize = rangeSize;
328     } else
329         m_totalRemainingSize -= m_rangeOffset;
330 }
331
332 int BlobResourceHandle::readSync(char* buf, int length)
333 {
334     ASSERT(isMainThread());
335
336     ASSERT(!m_async);
337     Ref<BlobResourceHandle> protectedThis(*this);
338
339     int offset = 0;
340     int remaining = length;
341     while (remaining) {
342         // Do not continue if the request is aborted or an error occurs.
343         if (m_aborted || m_errorCode)
344             break;
345
346         // If there is no more remaining data to read, we are done.
347         if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size())
348             break;
349         
350         const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
351         int bytesRead = 0;
352         if (item.type() == BlobDataItem::Type::Data)
353             bytesRead = readDataSync(item, buf + offset, remaining);
354         else if (item.type() == BlobDataItem::Type::File)
355             bytesRead = readFileSync(item, buf + offset, remaining);
356         else
357             ASSERT_NOT_REACHED();
358
359         if (bytesRead > 0) {
360             offset += bytesRead;
361             remaining -= bytesRead;
362         }
363     }
364
365     int result;
366     if (m_aborted || m_errorCode)
367         result = -1;
368     else
369         result = length - remaining;
370
371     if (result > 0)
372         notifyReceiveData(buf, result);
373
374     if (!result)
375         notifyFinish();
376
377     return result;
378 }
379
380 int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length)
381 {
382     ASSERT(isMainThread());
383
384     ASSERT(!m_async);
385
386     long long remaining = item.length() - m_currentItemReadSize;
387     int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length;
388     if (bytesToRead > m_totalRemainingSize)
389         bytesToRead = static_cast<int>(m_totalRemainingSize);
390     memcpy(buf, item.data().data() + item.offset() + m_currentItemReadSize, bytesToRead);
391     m_totalRemainingSize -= bytesToRead;
392
393     m_currentItemReadSize += bytesToRead;
394     if (m_currentItemReadSize == item.length()) {
395         m_readItemCount++;
396         m_currentItemReadSize = 0;
397     }
398
399     return bytesToRead;
400 }
401
402 int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length)
403 {
404     ASSERT(isMainThread());
405
406     ASSERT(!m_async);
407
408     if (!m_fileOpened) {
409         long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
410         if (bytesToRead > m_totalRemainingSize)
411             bytesToRead = m_totalRemainingSize;
412         bool success = m_stream->openForRead(item.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead);
413         m_currentItemReadSize = 0;
414         if (!success) {
415             m_errorCode = notReadableError;
416             return 0;
417         }
418
419         m_fileOpened = true;
420     }
421
422     int bytesRead = m_stream->read(buf, length);
423     if (bytesRead < 0) {
424         m_errorCode = notReadableError;
425         return 0;
426     }
427     if (!bytesRead) {
428         m_stream->close();
429         m_fileOpened = false;
430         m_readItemCount++;
431     } else
432         m_totalRemainingSize -= bytesRead;
433
434     return bytesRead;
435 }
436
437 void BlobResourceHandle::readAsync()
438 {
439     ASSERT(isMainThread());
440     ASSERT(m_async);
441
442     // Do not continue if the request is aborted or an error occurs.
443     if (m_aborted || m_errorCode)
444         return;
445
446     // If there is no more remaining data to read, we are done.
447     if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
448         notifyFinish();
449         return;
450     }
451
452     const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
453     if (item.type() == BlobDataItem::Type::Data)
454         readDataAsync(item);
455     else if (item.type() == BlobDataItem::Type::File)
456         readFileAsync(item);
457     else
458         ASSERT_NOT_REACHED();
459 }
460
461 void BlobResourceHandle::readDataAsync(const BlobDataItem& item)
462 {
463     ASSERT(isMainThread());
464     ASSERT(m_async);
465     ASSERT(item.data().data());
466
467     Ref<BlobResourceHandle> protectedThis(*this);
468
469     long long bytesToRead = item.length() - m_currentItemReadSize;
470     if (bytesToRead > m_totalRemainingSize)
471         bytesToRead = m_totalRemainingSize;
472     consumeData(reinterpret_cast<const char*>(item.data().data()->data()) + item.offset() + m_currentItemReadSize, static_cast<int>(bytesToRead));
473     m_currentItemReadSize = 0;
474 }
475
476 void BlobResourceHandle::readFileAsync(const BlobDataItem& item)
477 {
478     ASSERT(isMainThread());
479     ASSERT(m_async);
480
481     if (m_fileOpened) {
482         m_asyncStream->read(m_buffer.data(), m_buffer.size());
483         return;
484     }
485
486     long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
487     if (bytesToRead > m_totalRemainingSize)
488         bytesToRead = static_cast<int>(m_totalRemainingSize);
489     m_asyncStream->openForRead(item.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead);
490     m_fileOpened = true;
491     m_currentItemReadSize = 0;
492 }
493
494 void BlobResourceHandle::didOpen(bool success)
495 {
496     ASSERT(m_async);
497
498     if (!success) {
499         failed(notReadableError);
500         return;
501     }
502
503     // Continue the reading.
504     readAsync();
505 }
506
507 void BlobResourceHandle::didRead(int bytesRead)
508 {
509     if (bytesRead < 0) {
510         failed(notReadableError);
511         return;
512     }
513
514     consumeData(m_buffer.data(), bytesRead);
515 }
516
517 void BlobResourceHandle::consumeData(const char* data, int bytesRead)
518 {
519     ASSERT(m_async);
520     Ref<BlobResourceHandle> protectedThis(*this);
521
522     m_totalRemainingSize -= bytesRead;
523
524     // Notify the client.
525     if (bytesRead)
526         notifyReceiveData(data, bytesRead);
527
528     if (m_fileOpened) {
529         // When the current item is a file item, the reading is completed only if bytesRead is 0.
530         if (!bytesRead) {
531             // Close the file.
532             m_fileOpened = false;
533             m_asyncStream->close();
534
535             // Move to the next item.
536             m_readItemCount++;
537         }
538     } else {
539         // Otherwise, we read the current text item as a whole and move to the next item.
540         m_readItemCount++;
541     }
542
543     // Continue the reading.
544     readAsync();
545 }
546
547 void BlobResourceHandle::failed(int errorCode)
548 {
549     ASSERT(m_async);
550     Ref<BlobResourceHandle> protectedThis(*this);
551
552     // Notify the client.
553     notifyFail(errorCode);
554
555     // Close the file if needed.
556     if (m_fileOpened) {
557         m_fileOpened = false;
558         m_asyncStream->close();
559     }
560 }
561
562 void BlobResourceHandle::notifyResponse()
563 {
564     if (!client())
565         return;
566
567     if (m_errorCode) {
568         Ref<BlobResourceHandle> protectedThis(*this);
569         notifyResponseOnError();
570         notifyFinish();
571     } else
572         notifyResponseOnSuccess();
573 }
574
575 void BlobResourceHandle::notifyResponseOnSuccess()
576 {
577     ASSERT(isMainThread());
578
579     bool isRangeRequest = m_rangeOffset != kPositionNotSpecified;
580     ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String());
581     response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
582     response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
583
584     response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_blobData->contentType());
585     response.setHTTPHeaderField(HTTPHeaderName::ContentLength, String::number(m_totalRemainingSize));
586
587     if (isRangeRequest)
588         response.setHTTPHeaderField(HTTPHeaderName::ContentRange, ParsedContentRange(m_rangeOffset, m_rangeEnd, m_totalSize).headerValue());
589     // FIXME: If a resource identified with a blob: URL is a File object, user agents must use that file's name attribute,
590     // as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute.
591     // Notably, this will affect a name suggested in "File Save As".
592
593     // BlobResourceHandle cannot be used with downloading, and doesn't even wait for continueDidReceiveResponse.
594     // It's currently client's responsibility to know that didReceiveResponseAsync cannot be used to convert a
595     // load into a download or blobs.
596     if (usesAsyncCallbacks())
597         client()->didReceiveResponseAsync(this, response);
598     else
599         client()->didReceiveResponse(this, response);
600 }
601
602 void BlobResourceHandle::notifyResponseOnError()
603 {
604     ASSERT(m_errorCode);
605
606     ResourceResponse response(firstRequest().url(), "text/plain", 0, String());
607     switch (m_errorCode) {
608     case rangeError:
609         response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable);
610         response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText);
611         break;
612     case notFoundError:
613         response.setHTTPStatusCode(httpNotFound);
614         response.setHTTPStatusText(httpNotFoundText);
615         break;
616     case securityError:
617         response.setHTTPStatusCode(httpNotAllowed);
618         response.setHTTPStatusText(httpNotAllowedText);
619         break;
620     default:
621         response.setHTTPStatusCode(httpInternalError);
622         response.setHTTPStatusText(httpInternalErrorText);
623         break;
624     }
625
626     // Note that we don't wait for continueDidReceiveResponse when using didReceiveResponseAsync.
627     // This is not formally correct, but the client has to be a no-op anyway, because blobs can't be downloaded.
628     if (usesAsyncCallbacks())
629         client()->didReceiveResponseAsync(this, response);
630     else
631         client()->didReceiveResponse(this, response);
632 }
633
634 void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead)
635 {
636     if (client())
637         client()->didReceiveBuffer(this, SharedBuffer::create(data, bytesRead), bytesRead);
638 }
639
640 void BlobResourceHandle::notifyFail(int errorCode)
641 {
642     if (client())
643         client()->didFail(this, ResourceError(webKitBlobResourceDomain, errorCode, firstRequest().url(), String()));
644 }
645
646 static void doNotifyFinish(BlobResourceHandle& handle)
647 {
648     if (handle.aborted())
649         return;
650
651     if (!handle.client())
652         return;
653
654     handle.client()->didFinishLoading(&handle, 0);
655 }
656
657 void BlobResourceHandle::notifyFinish()
658 {
659     if (!m_async) {
660         doNotifyFinish(*this);
661         return;
662     }
663
664     // Schedule to notify the client from a standalone function because the client might dispose the handle immediately from the callback function
665     // while we still have BlobResourceHandle calls in the stack.
666     callOnMainThread([protectedThis = Ref<BlobResourceHandle>(*this)]() mutable {
667         doNotifyFinish(protectedThis);
668     });
669
670 }
671
672 } // namespace WebCore