1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2016 Igalia S.L.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "NetworkDataTaskBlob.h"
34
35#include "DataReference.h"
36#include "Download.h"
37#include "Logging.h"
38#include "NetworkProcess.h"
39#include "NetworkSession.h"
40#include "WebErrors.h"
41#include <WebCore/AsyncFileStream.h>
42#include <WebCore/BlobRegistryImpl.h>
43#include <WebCore/HTTPParsers.h>
44#include <WebCore/ParsedContentRange.h>
45#include <WebCore/ResourceError.h>
46#include <WebCore/ResourceResponse.h>
47#include <WebCore/SharedBuffer.h>
48#include <wtf/RunLoop.h>
49
50namespace WebKit {
51using namespace WebCore;
52
53static const unsigned bufferSize = 512 * 1024;
54
55static const int httpOK = 200;
56static const int httpPartialContent = 206;
57static const int httpNotAllowed = 403;
58static const int httpRequestedRangeNotSatisfiable = 416;
59static const int httpInternalError = 500;
60static const char* httpOKText = "OK";
61static const char* httpPartialContentText = "Partial Content";
62static const char* httpNotAllowedText = "Not Allowed";
63static const char* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable";
64static const char* httpInternalErrorText = "Internal Server Error";
65
66static const char* const webKitBlobResourceDomain = "WebKitBlobResource";
67
68NetworkDataTaskBlob::NetworkDataTaskBlob(NetworkSession& session, BlobRegistryImpl& blobRegistry, NetworkDataTaskClient& client, const ResourceRequest& request, ContentSniffingPolicy shouldContentSniff, const Vector<RefPtr<WebCore::BlobDataFileReference>>& fileReferences)
69 : NetworkDataTask(session, client, request, StoredCredentialsPolicy::DoNotUse, false, false)
70 , m_stream(std::make_unique<AsyncFileStream>(*this))
71 , m_fileReferences(fileReferences)
72 , m_networkProcess(session.networkProcess())
73{
74 for (auto& fileReference : m_fileReferences)
75 fileReference->prepareForFileAccess();
76
77 m_blobData = blobRegistry.getBlobDataFromURL(request.url());
78
79 m_session->registerNetworkDataTask(*this);
80 LOG(NetworkSession, "%p - Created NetworkDataTaskBlob for %s", this, request.url().string().utf8().data());
81}
82
83NetworkDataTaskBlob::~NetworkDataTaskBlob()
84{
85 for (auto& fileReference : m_fileReferences)
86 fileReference->revokeFileAccess();
87
88 clearStream();
89 m_session->unregisterNetworkDataTask(*this);
90}
91
92void NetworkDataTaskBlob::clearStream()
93{
94 if (m_state == State::Completed)
95 return;
96
97 m_state = State::Completed;
98
99 if (m_fileOpened) {
100 m_fileOpened = false;
101 m_stream->close();
102 }
103 m_stream = nullptr;
104}
105
106void NetworkDataTaskBlob::resume()
107{
108 ASSERT(m_state != State::Running);
109 if (m_state == State::Canceling || m_state == State::Completed)
110 return;
111
112 m_state = State::Running;
113
114 if (m_scheduledFailureType != NoFailure) {
115 ASSERT(m_failureTimer.isActive());
116 return;
117 }
118
119 RunLoop::main().dispatch([this, protectedThis = makeRef(*this)] {
120 if (m_state == State::Canceling || m_state == State::Completed || !m_client) {
121 clearStream();
122 return;
123 }
124
125 if (!equalLettersIgnoringASCIICase(m_firstRequest.httpMethod(), "get")) {
126 didFail(Error::MethodNotAllowed);
127 return;
128 }
129
130 // If the blob data is not found, fail now.
131 if (!m_blobData) {
132 didFail(Error::NotFoundError);
133 return;
134 }
135
136 // Parse the "Range" header we care about.
137 String range = m_firstRequest.httpHeaderField(HTTPHeaderName::Range);
138 if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
139 dispatchDidReceiveResponse(Error::RangeError);
140 return;
141 }
142
143 getSizeForNext();
144 });
145}
146
147void NetworkDataTaskBlob::cancel()
148{
149 if (m_state == State::Canceling || m_state == State::Completed)
150 return;
151
152 m_state = State::Canceling;
153
154 if (m_fileOpened) {
155 m_fileOpened = false;
156 m_stream->close();
157 }
158
159 if (isDownload())
160 cleanDownloadFiles();
161}
162
163void NetworkDataTaskBlob::invalidateAndCancel()
164{
165 cancel();
166 clearStream();
167}
168
169void NetworkDataTaskBlob::getSizeForNext()
170{
171 ASSERT(RunLoop::isMain());
172
173 // Do we finish validating and counting size for all items?
174 if (m_sizeItemCount >= m_blobData->items().size()) {
175 seek();
176 dispatchDidReceiveResponse();
177 return;
178 }
179
180 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
181 switch (item.type()) {
182 case BlobDataItem::Type::Data:
183 didGetSize(item.length());
184 break;
185 case BlobDataItem::Type::File:
186 // Files know their sizes, but asking the stream to verify that the file wasn't modified.
187 m_stream->getSize(item.file()->path(), item.file()->expectedModificationTime());
188 break;
189 default:
190 ASSERT_NOT_REACHED();
191 }
192}
193
194void NetworkDataTaskBlob::didGetSize(long long size)
195{
196 ASSERT(RunLoop::isMain());
197
198 if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) {
199 clearStream();
200 return;
201 }
202
203 // If the size is -1, it means the file has been moved or changed. Fail now.
204 if (size == -1) {
205 didFail(Error::NotFoundError);
206 return;
207 }
208
209 // 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.
210 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
211 size = item.length();
212
213 // Cache the size.
214 m_itemLengthList.append(size);
215
216 // Count the size.
217 m_totalSize += size;
218 m_totalRemainingSize += size;
219 m_sizeItemCount++;
220
221 // Continue with the next item.
222 getSizeForNext();
223}
224
225void NetworkDataTaskBlob::seek()
226{
227 ASSERT(RunLoop::isMain());
228
229 // Convert from the suffix length to the range.
230 if (m_rangeSuffixLength != kPositionNotSpecified) {
231 m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
232 m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
233 }
234
235 // Bail out if the range is not provided.
236 if (m_rangeOffset == kPositionNotSpecified)
237 return;
238
239 // Skip the initial items that are not in the range.
240 long long offset = m_rangeOffset;
241 for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount)
242 offset -= m_itemLengthList[m_readItemCount];
243
244 // Set the offset that need to jump to for the first item in the range.
245 m_currentItemReadSize = offset;
246
247 // Adjust the total remaining size in order not to go beyond the range.
248 if (m_rangeEnd != kPositionNotSpecified) {
249 long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
250 if (m_totalRemainingSize > rangeSize)
251 m_totalRemainingSize = rangeSize;
252 } else
253 m_totalRemainingSize -= m_rangeOffset;
254}
255
256void NetworkDataTaskBlob::dispatchDidReceiveResponse(Error errorCode)
257{
258 LOG(NetworkSession, "%p - NetworkDataTaskBlob::dispatchDidReceiveResponse(%u)", this, static_cast<unsigned>(errorCode));
259
260 Ref<NetworkDataTaskBlob> protectedThis(*this);
261 ResourceResponse response(m_firstRequest.url(), errorCode != Error::NoError ? "text/plain" : m_blobData->contentType(), errorCode != Error::NoError ? 0 : m_totalRemainingSize, String());
262 switch (errorCode) {
263 case Error::NoError: {
264 bool isRangeRequest = m_rangeOffset != kPositionNotSpecified;
265 response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
266 response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
267
268 response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_blobData->contentType());
269 response.setHTTPHeaderField(HTTPHeaderName::ContentLength, String::number(m_totalRemainingSize));
270
271 if (isRangeRequest)
272 response.setHTTPHeaderField(HTTPHeaderName::ContentRange, ParsedContentRange(m_rangeOffset, m_rangeEnd, m_totalSize).headerValue());
273 // FIXME: If a resource identified with a blob: URL is a File object, user agents must use that file's name attribute,
274 // as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute.
275 // Notably, this will affect a name suggested in "File Save As".
276 break;
277 }
278 case Error::RangeError:
279 response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable);
280 response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText);
281 break;
282 case Error::SecurityError:
283 response.setHTTPStatusCode(httpNotAllowed);
284 response.setHTTPStatusText(httpNotAllowedText);
285 break;
286 default:
287 response.setHTTPStatusCode(httpInternalError);
288 response.setHTTPStatusText(httpInternalErrorText);
289 break;
290 }
291
292 didReceiveResponse(WTFMove(response), [this, protectedThis = WTFMove(protectedThis), errorCode](PolicyAction policyAction) {
293 LOG(NetworkSession, "%p - NetworkDataTaskBlob::didReceiveResponse completionHandler (%u)", this, static_cast<unsigned>(policyAction));
294
295 if (m_state == State::Canceling || m_state == State::Completed) {
296 clearStream();
297 return;
298 }
299
300 if (errorCode != Error::NoError) {
301 didFinish();
302 return;
303 }
304
305 switch (policyAction) {
306 case PolicyAction::Use:
307 m_buffer.resize(bufferSize);
308 read();
309 break;
310 case PolicyAction::StopAllLoads:
311 ASSERT_NOT_REACHED();
312 break;
313 case PolicyAction::Ignore:
314 break;
315 case PolicyAction::Download:
316 download();
317 break;
318 }
319 });
320}
321
322void NetworkDataTaskBlob::read()
323{
324 ASSERT(RunLoop::isMain());
325
326 // If there is no more remaining data to read, we are done.
327 if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
328 didFinish();
329 return;
330 }
331
332 const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
333 if (item.type() == BlobDataItem::Type::Data)
334 readData(item);
335 else if (item.type() == BlobDataItem::Type::File)
336 readFile(item);
337 else
338 ASSERT_NOT_REACHED();
339}
340
341void NetworkDataTaskBlob::readData(const BlobDataItem& item)
342{
343 ASSERT(item.data().data());
344
345 long long bytesToRead = item.length() - m_currentItemReadSize;
346 if (bytesToRead > m_totalRemainingSize)
347 bytesToRead = m_totalRemainingSize;
348 consumeData(reinterpret_cast<const char*>(item.data().data()->data()) + item.offset() + m_currentItemReadSize, static_cast<int>(bytesToRead));
349 m_currentItemReadSize = 0;
350}
351
352void NetworkDataTaskBlob::readFile(const BlobDataItem& item)
353{
354 ASSERT(m_stream);
355
356 if (m_fileOpened) {
357 m_stream->read(m_buffer.data(), m_buffer.size());
358 return;
359 }
360
361 long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
362 if (bytesToRead > m_totalRemainingSize)
363 bytesToRead = static_cast<int>(m_totalRemainingSize);
364 m_stream->openForRead(item.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead);
365 m_fileOpened = true;
366 m_currentItemReadSize = 0;
367}
368
369void NetworkDataTaskBlob::didOpen(bool success)
370{
371 if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) {
372 clearStream();
373 return;
374 }
375
376 if (!success) {
377 didFail(Error::NotReadableError);
378 return;
379 }
380
381 Ref<NetworkDataTaskBlob> protectedThis(*this);
382 read();
383}
384
385void NetworkDataTaskBlob::didRead(int bytesRead)
386{
387 if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) {
388 clearStream();
389 return;
390 }
391
392 if (bytesRead < 0) {
393 didFail(Error::NotReadableError);
394 return;
395 }
396
397 Ref<NetworkDataTaskBlob> protectedThis(*this);
398 consumeData(m_buffer.data(), bytesRead);
399}
400
401void NetworkDataTaskBlob::consumeData(const char* data, int bytesRead)
402{
403 m_totalRemainingSize -= bytesRead;
404
405 if (bytesRead) {
406 if (m_downloadFile != FileSystem::invalidPlatformFileHandle) {
407 if (!writeDownload(data, bytesRead))
408 return;
409 } else {
410 ASSERT(m_client);
411 m_client->didReceiveData(SharedBuffer::create(data, bytesRead));
412 }
413 }
414
415 if (m_fileOpened) {
416 // When the current item is a file item, the reading is completed only if bytesRead is 0.
417 if (!bytesRead) {
418 // Close the file.
419 m_fileOpened = false;
420 m_stream->close();
421
422 // Move to the next item.
423 m_readItemCount++;
424 }
425 } else {
426 // Otherwise, we read the current text item as a whole and move to the next item.
427 m_readItemCount++;
428 }
429
430 read();
431}
432
433void NetworkDataTaskBlob::setPendingDownloadLocation(const String& filename, SandboxExtension::Handle&& sandboxExtensionHandle, bool allowOverwrite)
434{
435 NetworkDataTask::setPendingDownloadLocation(filename, { }, allowOverwrite);
436
437 ASSERT(!m_sandboxExtension);
438 m_sandboxExtension = SandboxExtension::create(WTFMove(sandboxExtensionHandle));
439 if (m_sandboxExtension)
440 m_sandboxExtension->consume();
441
442 if (allowOverwrite && FileSystem::fileExists(m_pendingDownloadLocation))
443 FileSystem::deleteFile(m_pendingDownloadLocation);
444}
445
446String NetworkDataTaskBlob::suggestedFilename() const
447{
448 if (!m_suggestedFilename.isEmpty())
449 return m_suggestedFilename;
450
451 return "unknown"_s;
452}
453
454void NetworkDataTaskBlob::download()
455{
456 ASSERT(isDownload());
457 ASSERT(m_pendingDownloadLocation);
458
459 LOG(NetworkSession, "%p - NetworkDataTaskBlob::download to %s", this, m_pendingDownloadLocation.utf8().data());
460
461 m_downloadFile = FileSystem::openFile(m_pendingDownloadLocation, FileSystem::FileOpenMode::Write);
462 if (m_downloadFile == FileSystem::invalidPlatformFileHandle) {
463 didFailDownload(cancelledError(m_firstRequest));
464 return;
465 }
466
467 auto& downloadManager = m_networkProcess->downloadManager();
468 auto download = std::make_unique<Download>(downloadManager, m_pendingDownloadID, *this, m_session->sessionID(), suggestedFilename());
469 auto* downloadPtr = download.get();
470 downloadManager.dataTaskBecameDownloadTask(m_pendingDownloadID, WTFMove(download));
471 downloadPtr->didCreateDestination(m_pendingDownloadLocation);
472
473 ASSERT(!m_client);
474
475 m_buffer.resize(bufferSize);
476 read();
477}
478
479bool NetworkDataTaskBlob::writeDownload(const char* data, int bytesRead)
480{
481 ASSERT(isDownload());
482 int bytesWritten = FileSystem::writeToFile(m_downloadFile, data, bytesRead);
483 if (bytesWritten == -1) {
484 didFailDownload(cancelledError(m_firstRequest));
485 return false;
486 }
487
488 ASSERT(bytesWritten == bytesRead);
489 auto* download = m_networkProcess->downloadManager().download(m_pendingDownloadID);
490 ASSERT(download);
491 download->didReceiveData(bytesWritten);
492 return true;
493}
494
495void NetworkDataTaskBlob::cleanDownloadFiles()
496{
497 if (m_downloadFile != FileSystem::invalidPlatformFileHandle) {
498 FileSystem::closeFile(m_downloadFile);
499 m_downloadFile = FileSystem::invalidPlatformFileHandle;
500 }
501 FileSystem::deleteFile(m_pendingDownloadLocation);
502}
503
504void NetworkDataTaskBlob::didFailDownload(const ResourceError& error)
505{
506 LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFailDownload", this);
507
508 clearStream();
509 cleanDownloadFiles();
510
511 if (m_sandboxExtension) {
512 m_sandboxExtension->revoke();
513 m_sandboxExtension = nullptr;
514 }
515
516 if (m_client)
517 m_client->didCompleteWithError(error);
518 else {
519 auto* download = m_networkProcess->downloadManager().download(m_pendingDownloadID);
520 ASSERT(download);
521 download->didFail(error, IPC::DataReference());
522 }
523}
524
525void NetworkDataTaskBlob::didFinishDownload()
526{
527 LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFinishDownload", this);
528
529 ASSERT(isDownload());
530 FileSystem::closeFile(m_downloadFile);
531 m_downloadFile = FileSystem::invalidPlatformFileHandle;
532
533 if (m_sandboxExtension) {
534 m_sandboxExtension->revoke();
535 m_sandboxExtension = nullptr;
536 }
537
538 clearStream();
539 auto* download = m_networkProcess->downloadManager().download(m_pendingDownloadID);
540 ASSERT(download);
541 download->didFinish();
542}
543
544void NetworkDataTaskBlob::didFail(Error errorCode)
545{
546 ASSERT(!m_sandboxExtension);
547
548 Ref<NetworkDataTaskBlob> protectedThis(*this);
549 if (isDownload()) {
550 didFailDownload(ResourceError(webKitBlobResourceDomain, static_cast<int>(errorCode), m_firstRequest.url(), String()));
551 return;
552 }
553
554 LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFail", this);
555
556 clearStream();
557 ASSERT(m_client);
558 m_client->didCompleteWithError(ResourceError(webKitBlobResourceDomain, static_cast<int>(errorCode), m_firstRequest.url(), String()));
559}
560
561void NetworkDataTaskBlob::didFinish()
562{
563 if (m_downloadFile != FileSystem::invalidPlatformFileHandle) {
564 didFinishDownload();
565 return;
566 }
567
568 ASSERT(!m_sandboxExtension);
569
570 LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFinish", this);
571
572 clearStream();
573 ASSERT(m_client);
574 m_client->didCompleteWithError({ });
575}
576
577} // namespace WebKit
578