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#if ENABLE(BLOB)
34
35#include "BlobResourceHandle.h"
36
37#include "AsyncFileStream.h"
38#include "BlobStorageData.h"
39#include "FileStream.h"
40#include "FileSystem.h"
41#include "HTTPParsers.h"
42#include "KURL.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
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*, int, 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*, int, 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(BlobStorageData* 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(BlobStorageData* 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(PassRefPtr<BlobStorageData> 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 delayedStartBlobResourceHandle(void* context)
205{
206    RefPtr<BlobResourceHandle> handle = adoptRef(static_cast<BlobResourceHandle*>(context));
207    handle->doStart();
208}
209
210void BlobResourceHandle::start()
211{
212    if (m_async) {
213        // Keep BlobResourceHandle alive until delayedStartBlobResourceHandle runs.
214        ref();
215
216        // Finish this async call quickly and return.
217        callOnMainThread(delayedStartBlobResourceHandle, this);
218        return;
219    }
220
221    doStart();
222}
223
224void BlobResourceHandle::doStart()
225{
226    // Do not continue if the request is aborted or an error occurs.
227    if (m_aborted || m_errorCode)
228        return;
229
230    // If the blob data is not found, fail now.
231    if (!m_blobData) {
232        m_errorCode = notFoundError;
233        notifyResponse();
234        return;
235    }
236
237    // Parse the "Range" header we care about.
238    String range = firstRequest().httpHeaderField("Range");
239    if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
240        m_errorCode = rangeError;
241        notifyResponse();
242        return;
243    }
244
245    if (m_async)
246        getSizeForNext();
247    else {
248        RefPtr<BlobResourceHandle> protect(this); // getSizeForNext calls the client
249        for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i)
250            getSizeForNext();
251        notifyResponse();
252    }
253}
254
255void BlobResourceHandle::getSizeForNext()
256{
257    // Do we finish validating and counting size for all items?
258    if (m_sizeItemCount >= m_blobData->items().size()) {
259        seek();
260
261        // Start reading if in asynchronous mode.
262        if (m_async) {
263            RefPtr<BlobResourceHandle> protect(this);
264            notifyResponse();
265            m_buffer.resize(bufferSize);
266            readAsync();
267        }
268        return;
269    }
270
271    const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
272    switch (item.type) {
273    case BlobDataItem::Data:
274        didGetSize(item.length);
275        break;
276    case BlobDataItem::File:
277        if (m_async)
278            m_asyncStream->getSize(item.path, item.expectedModificationTime);
279        else
280            didGetSize(m_stream->getSize(item.path, item.expectedModificationTime));
281        break;
282    default:
283        ASSERT_NOT_REACHED();
284    }
285}
286
287void BlobResourceHandle::didGetSize(long long size)
288{
289    // Do not continue if the request is aborted or an error occurs.
290    if (m_aborted || m_errorCode)
291        return;
292
293    // If the size is -1, it means the file has been moved or changed. Fail now.
294    if (size == -1) {
295        m_errorCode = notFoundError;
296        notifyResponse();
297        return;
298    }
299
300    // 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.
301    const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
302    if (item.type == BlobDataItem::File && item.length != BlobDataItem::toEndOfFile)
303        size = item.length;
304
305    // Cache the size.
306    m_itemLengthList.append(size);
307
308    // Count the size.
309    m_totalRemainingSize += size;
310    m_sizeItemCount++;
311
312    // Continue with the next item.
313    getSizeForNext();
314}
315
316void BlobResourceHandle::seek()
317{
318    // Convert from the suffix length to the range.
319    if (m_rangeSuffixLength != positionNotSpecified) {
320        m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
321        m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
322    }
323
324    // Bail out if the range is not provided.
325    if (m_rangeOffset == positionNotSpecified)
326        return;
327
328    // Skip the initial items that are not in the range.
329    long long offset = m_rangeOffset;
330    for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount)
331        offset -= m_itemLengthList[m_readItemCount];
332
333    // Set the offset that need to jump to for the first item in the range.
334    m_currentItemReadSize = offset;
335
336    // Adjust the total remaining size in order not to go beyond the range.
337    if (m_rangeEnd != positionNotSpecified) {
338        long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
339        if (m_totalRemainingSize > rangeSize)
340            m_totalRemainingSize = rangeSize;
341    } else
342        m_totalRemainingSize -= m_rangeOffset;
343}
344
345int BlobResourceHandle::readSync(char* buf, int length)
346{
347    ASSERT(!m_async);
348    RefPtr<BlobResourceHandle> protect(this);
349
350    int offset = 0;
351    int remaining = length;
352    while (remaining) {
353        // Do not continue if the request is aborted or an error occurs.
354        if (m_aborted || m_errorCode)
355            break;
356
357        // If there is no more remaining data to read, we are done.
358        if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size())
359            break;
360
361        const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
362        int bytesRead = 0;
363        if (item.type == BlobDataItem::Data)
364            bytesRead = readDataSync(item, buf + offset, remaining);
365        else if (item.type == BlobDataItem::File)
366            bytesRead = readFileSync(item, buf + offset, remaining);
367        else
368            ASSERT_NOT_REACHED();
369
370        if (bytesRead > 0) {
371            offset += bytesRead;
372            remaining -= bytesRead;
373        }
374    }
375
376    int result;
377    if (m_aborted || m_errorCode)
378        result = -1;
379    else
380        result = length - remaining;
381
382    if (result > 0)
383        notifyReceiveData(buf, result);
384
385    if (!result)
386        notifyFinish();
387
388    return result;
389}
390
391int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length)
392{
393    ASSERT(!m_async);
394
395    long long remaining = item.length - m_currentItemReadSize;
396    int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length;
397    if (bytesToRead > m_totalRemainingSize)
398        bytesToRead = static_cast<int>(m_totalRemainingSize);
399    memcpy(buf, item.data->data() + item.offset + m_currentItemReadSize, bytesToRead);
400    m_totalRemainingSize -= bytesToRead;
401
402    m_currentItemReadSize += bytesToRead;
403    if (m_currentItemReadSize == item.length) {
404        m_readItemCount++;
405        m_currentItemReadSize = 0;
406    }
407
408    return bytesToRead;
409}
410
411int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length)
412{
413    ASSERT(!m_async);
414
415    if (!m_fileOpened) {
416        long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
417        if (bytesToRead > m_totalRemainingSize)
418            bytesToRead = m_totalRemainingSize;
419        bool success = m_stream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead);
420        m_currentItemReadSize = 0;
421        if (!success) {
422            m_errorCode = notReadableError;
423            return 0;
424        }
425
426        m_fileOpened = true;
427    }
428
429    int bytesRead = m_stream->read(buf, length);
430    if (bytesRead < 0) {
431        m_errorCode = notReadableError;
432        return 0;
433    }
434    if (!bytesRead) {
435        m_stream->close();
436        m_fileOpened = false;
437        m_readItemCount++;
438    } else
439        m_totalRemainingSize -= bytesRead;
440
441    return bytesRead;
442}
443
444void BlobResourceHandle::readAsync()
445{
446    ASSERT(m_async);
447
448    // Do not continue if the request is aborted or an error occurs.
449    if (m_aborted || m_errorCode)
450        return;
451
452    // If there is no more remaining data to read, we are done.
453    if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
454        notifyFinish();
455        return;
456    }
457
458    const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
459    if (item.type == BlobDataItem::Data)
460        readDataAsync(item);
461    else if (item.type == BlobDataItem::File)
462        readFileAsync(item);
463    else
464        ASSERT_NOT_REACHED();
465}
466
467void BlobResourceHandle::readDataAsync(const BlobDataItem& item)
468{
469    ASSERT(m_async);
470    RefPtr<BlobResourceHandle> protect(this);
471
472    long long bytesToRead = item.length - m_currentItemReadSize;
473    if (bytesToRead > m_totalRemainingSize)
474        bytesToRead = m_totalRemainingSize;
475    consumeData(item.data->data() + item.offset + m_currentItemReadSize, static_cast<int>(bytesToRead));
476    m_currentItemReadSize = 0;
477}
478
479void BlobResourceHandle::readFileAsync(const BlobDataItem& item)
480{
481    ASSERT(m_async);
482
483    if (m_fileOpened) {
484        m_asyncStream->read(m_buffer.data(), m_buffer.size());
485        return;
486    }
487
488    long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
489    if (bytesToRead > m_totalRemainingSize)
490        bytesToRead = static_cast<int>(m_totalRemainingSize);
491    m_asyncStream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead);
492    m_fileOpened = true;
493    m_currentItemReadSize = 0;
494}
495
496void BlobResourceHandle::didOpen(bool success)
497{
498    ASSERT(m_async);
499
500    if (!success) {
501        failed(notReadableError);
502        return;
503    }
504
505    // Continue the reading.
506    readAsync();
507}
508
509void BlobResourceHandle::didRead(int bytesRead)
510{
511    if (bytesRead < 0) {
512        failed(notReadableError);
513        return;
514    }
515
516    consumeData(m_buffer.data(), bytesRead);
517}
518
519void BlobResourceHandle::consumeData(const char* data, int bytesRead)
520{
521    ASSERT(m_async);
522    RefPtr<BlobResourceHandle> protect(this);
523
524    m_totalRemainingSize -= bytesRead;
525
526    // Notify the client.
527    if (bytesRead)
528        notifyReceiveData(data, bytesRead);
529
530    if (m_fileOpened) {
531        // When the current item is a file item, the reading is completed only if bytesRead is 0.
532        if (!bytesRead) {
533            // Close the file.
534            m_fileOpened = false;
535            m_asyncStream->close();
536
537            // Move to the next item.
538            m_readItemCount++;
539        }
540    } else {
541        // Otherwise, we read the current text item as a whole and move to the next item.
542        m_readItemCount++;
543    }
544
545    // Continue the reading.
546    readAsync();
547}
548
549void BlobResourceHandle::failed(int errorCode)
550{
551    ASSERT(m_async);
552    RefPtr<BlobResourceHandle> protect(this);
553
554    // Notify the client.
555    notifyFail(errorCode);
556
557    // Close the file if needed.
558    if (m_fileOpened) {
559        m_fileOpened = false;
560        m_asyncStream->close();
561    }
562}
563
564void BlobResourceHandle::notifyResponse()
565{
566    if (!client())
567        return;
568
569    if (m_errorCode) {
570        RefPtr<BlobResourceHandle> protect(this);
571        notifyResponseOnError();
572        notifyFinish();
573    } else
574        notifyResponseOnSuccess();
575}
576
577void BlobResourceHandle::notifyResponseOnSuccess()
578{
579    bool isRangeRequest = m_rangeOffset != positionNotSpecified;
580    ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String(), String());
581    response.setExpectedContentLength(m_totalRemainingSize);
582    response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
583    response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
584    if (!m_blobData->contentDisposition().isEmpty())
585        response.setHTTPHeaderField("Content-Disposition", m_blobData->contentDisposition());
586
587    // BlobResourceHandle cannot be used with downloading, and doesn't even wait for continueDidReceiveResponse.
588    // It's currently client's responsibility to know that didReceiveResponseAsync cannot be used to convert a
589    // load into a download or blobs.
590    if (client()->usesAsyncCallbacks())
591        client()->didReceiveResponseAsync(this, response);
592    else
593        client()->didReceiveResponse(this, response);
594}
595
596void BlobResourceHandle::notifyResponseOnError()
597{
598    ASSERT(m_errorCode);
599
600    ResourceResponse response(firstRequest().url(), "text/plain", 0, String(), String());
601    switch (m_errorCode) {
602    case rangeError:
603        response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable);
604        response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText);
605        break;
606    case notFoundError:
607        response.setHTTPStatusCode(httpNotFound);
608        response.setHTTPStatusText(httpNotFoundText);
609        break;
610    case securityError:
611        response.setHTTPStatusCode(httpNotAllowed);
612        response.setHTTPStatusText(httpNotAllowedText);
613        break;
614    default:
615        response.setHTTPStatusCode(httpInternalError);
616        response.setHTTPStatusText(httpInternalErrorText);
617        break;
618    }
619
620    // Note that we don't wait for continueDidReceiveResponse when using didReceiveResponseAsync.
621    // This is not formally correct, but the client has to be a no-op anyway, because blobs can't be downloaded.
622    if (client()->usesAsyncCallbacks())
623        client()->didReceiveResponseAsync(this, response);
624    else
625        client()->didReceiveResponse(this, response);
626}
627
628void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead)
629{
630    if (client())
631        client()->didReceiveBuffer(this, SharedBuffer::create(data, bytesRead), bytesRead);
632}
633
634void BlobResourceHandle::notifyFail(int errorCode)
635{
636    if (client())
637        client()->didFail(this, ResourceError(webKitBlobResourceDomain, errorCode, firstRequest().url(), String()));
638}
639
640static void doNotifyFinish(void* context)
641{
642    BlobResourceHandle* handle = static_cast<BlobResourceHandle*>(context);
643    if (!handle->aborted() && handle->client())
644        handle->client()->didFinishLoading(handle, 0);
645
646    // Balance the ref() in BlobResourceHandle::notfyFinish().
647    handle->deref();
648}
649
650void BlobResourceHandle::notifyFinish()
651{
652    // Balanced in doNotifyFinish().
653    ref();
654
655    if (m_async) {
656        // Schedule to notify the client from a standalone function because the client might dispose the handle immediately from the callback function
657        // while we still have BlobResourceHandle calls in the stack.
658        callOnMainThread(doNotifyFinish, this);
659        return;
660    }
661
662    doNotifyFinish(this);
663}
664
665} // namespace WebCore
666
667#endif // ENABLE(BLOB)
668