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