[Fetch API] Add basic loading of resources
[WebKit-https.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     RefPtr<BlobResourceHandle> handle(this);
191
192     // Finish this async call quickly and return.
193     callOnMainThread([handle] {
194         handle->doStart();
195     });
196 }
197
198 void BlobResourceHandle::doStart()
199 {
200     ASSERT(isMainThread());
201
202     // Do not continue if the request is aborted or an error occurs.
203     if (m_aborted || m_errorCode)
204         return;
205
206     if (!equalLettersIgnoringASCIICase(firstRequest().httpMethod(), "get")) {
207         m_errorCode = methodNotAllowed;
208         notifyResponse();
209         return;
210     }
211
212     // If the blob data is not found, fail now.
213     if (!m_blobData) {
214         m_errorCode = notFoundError;
215         notifyResponse();
216         return;
217     }
218
219     // Parse the "Range" header we care about.
220     String range = firstRequest().httpHeaderField(HTTPHeaderName::Range);
221     if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
222         m_errorCode = rangeError;
223         notifyResponse();
224         return;
225     }
226
227     if (m_async)
228         getSizeForNext();
229     else {
230         Ref<BlobResourceHandle> protect(*this); // getSizeForNext calls the client
231         for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i)
232             getSizeForNext();
233         notifyResponse();
234     }
235 }
236
237 void BlobResourceHandle::getSizeForNext()
238 {
239     ASSERT(isMainThread());
240
241     // Do we finish validating and counting size for all items?
242     if (m_sizeItemCount >= m_blobData->items().size()) {
243         seek();
244
245         // Start reading if in asynchronous mode.
246         if (m_async) {
247             Ref<BlobResourceHandle> protect(*this);
248             notifyResponse();
249             m_buffer.resize(bufferSize);
250             readAsync();
251         }
252         return;
253     }
254
255     const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
256     switch (item.type) {
257     case BlobDataItem::Data:
258         didGetSize(item.length());
259         break;
260     case BlobDataItem::File:
261         // Files know their sizes, but asking the stream to verify that the file wasn't modified.
262         if (m_async)
263             m_asyncStream->getSize(item.file->path(), item.file->expectedModificationTime());
264         else
265             didGetSize(m_stream->getSize(item.file->path(), item.file->expectedModificationTime()));
266         break;
267     default:
268         ASSERT_NOT_REACHED();
269     }
270 }
271
272 void BlobResourceHandle::didGetSize(long long size)
273 {
274     ASSERT(isMainThread());
275
276     // Do not continue if the request is aborted or an error occurs.
277     if (m_aborted || m_errorCode)
278         return;
279
280     // If the size is -1, it means the file has been moved or changed. Fail now.
281     if (size == -1) {
282         m_errorCode = notFoundError;
283         notifyResponse();
284         return;
285     }
286
287     // 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.
288     const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
289     size = item.length();
290
291     // Cache the size.
292     m_itemLengthList.append(size);
293
294     // Count the size.
295     m_totalSize += size;
296     m_totalRemainingSize += size;
297     m_sizeItemCount++;
298
299     // Continue with the next item.
300     getSizeForNext();
301 }
302
303 void BlobResourceHandle::seek()
304 {
305     ASSERT(isMainThread());
306
307     // Convert from the suffix length to the range.
308     if (m_rangeSuffixLength != kPositionNotSpecified) {
309         m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
310         m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
311     }
312
313     // Bail out if the range is not provided.
314     if (m_rangeOffset == kPositionNotSpecified)
315         return;
316
317     // Skip the initial items that are not in the range.
318     long long offset = m_rangeOffset;
319     for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount)
320         offset -= m_itemLengthList[m_readItemCount];
321
322     // Set the offset that need to jump to for the first item in the range.
323     m_currentItemReadSize = offset;
324
325     // Adjust the total remaining size in order not to go beyond the range.
326     if (m_rangeEnd != kPositionNotSpecified) {
327         long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
328         if (m_totalRemainingSize > rangeSize)
329             m_totalRemainingSize = rangeSize;
330     } else
331         m_totalRemainingSize -= m_rangeOffset;
332 }
333
334 int BlobResourceHandle::readSync(char* buf, int length)
335 {
336     ASSERT(isMainThread());
337
338     ASSERT(!m_async);
339     Ref<BlobResourceHandle> protect(*this);
340
341     int offset = 0;
342     int remaining = length;
343     while (remaining) {
344         // Do not continue if the request is aborted or an error occurs.
345         if (m_aborted || m_errorCode)
346             break;
347
348         // If there is no more remaining data to read, we are done.
349         if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size())
350             break;
351         
352         const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
353         int bytesRead = 0;
354         if (item.type == BlobDataItem::Data)
355             bytesRead = readDataSync(item, buf + offset, remaining);
356         else if (item.type == BlobDataItem::File)
357             bytesRead = readFileSync(item, buf + offset, remaining);
358         else
359             ASSERT_NOT_REACHED();
360
361         if (bytesRead > 0) {
362             offset += bytesRead;
363             remaining -= bytesRead;
364         }
365     }
366
367     int result;
368     if (m_aborted || m_errorCode)
369         result = -1;
370     else
371         result = length - remaining;
372
373     if (result > 0)
374         notifyReceiveData(buf, result);
375
376     if (!result)
377         notifyFinish();
378
379     return result;
380 }
381
382 int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length)
383 {
384     ASSERT(isMainThread());
385
386     ASSERT(!m_async);
387
388     long long remaining = item.length() - m_currentItemReadSize;
389     int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length;
390     if (bytesToRead > m_totalRemainingSize)
391         bytesToRead = static_cast<int>(m_totalRemainingSize);
392     memcpy(buf, item.data->data() + item.offset() + m_currentItemReadSize, bytesToRead);
393     m_totalRemainingSize -= bytesToRead;
394
395     m_currentItemReadSize += bytesToRead;
396     if (m_currentItemReadSize == item.length()) {
397         m_readItemCount++;
398         m_currentItemReadSize = 0;
399     }
400
401     return bytesToRead;
402 }
403
404 int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length)
405 {
406     ASSERT(isMainThread());
407
408     ASSERT(!m_async);
409
410     if (!m_fileOpened) {
411         long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
412         if (bytesToRead > m_totalRemainingSize)
413             bytesToRead = m_totalRemainingSize;
414         bool success = m_stream->openForRead(item.file->path(), item.offset() + m_currentItemReadSize, bytesToRead);
415         m_currentItemReadSize = 0;
416         if (!success) {
417             m_errorCode = notReadableError;
418             return 0;
419         }
420
421         m_fileOpened = true;
422     }
423
424     int bytesRead = m_stream->read(buf, length);
425     if (bytesRead < 0) {
426         m_errorCode = notReadableError;
427         return 0;
428     }
429     if (!bytesRead) {
430         m_stream->close();
431         m_fileOpened = false;
432         m_readItemCount++;
433     } else
434         m_totalRemainingSize -= bytesRead;
435
436     return bytesRead;
437 }
438
439 void BlobResourceHandle::readAsync()
440 {
441     ASSERT(isMainThread());
442     ASSERT(m_async);
443
444     // Do not continue if the request is aborted or an error occurs.
445     if (m_aborted || m_errorCode)
446         return;
447
448     // If there is no more remaining data to read, we are done.
449     if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
450         notifyFinish();
451         return;
452     }
453
454     const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
455     if (item.type == BlobDataItem::Data)
456         readDataAsync(item);
457     else if (item.type == BlobDataItem::File)
458         readFileAsync(item);
459     else
460         ASSERT_NOT_REACHED();
461 }
462
463 void BlobResourceHandle::readDataAsync(const BlobDataItem& item)
464 {
465     ASSERT(isMainThread());
466     ASSERT(m_async);
467     Ref<BlobResourceHandle> protect(*this);
468
469     long long bytesToRead = item.length() - m_currentItemReadSize;
470     if (bytesToRead > m_totalRemainingSize)
471         bytesToRead = m_totalRemainingSize;
472     consumeData(item.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> protect(*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> protect(*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> protect(*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     RefPtr<BlobResourceHandle> handle(this);
667     callOnMainThread([handle] {
668         doNotifyFinish(*handle);
669     });
670
671 }
672
673 } // namespace WebCore