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