/* * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "NetworkDataTaskBlob.h" #if USE(NETWORK_SESSION) #include "DataReference.h" #include "Download.h" #include "Logging.h" #include "NetworkProcess.h" #include "NetworkSession.h" #include "WebErrors.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace WebCore; namespace WebKit { static const unsigned bufferSize = 512 * 1024; static const int httpOK = 200; static const int httpPartialContent = 206; static const int httpNotAllowed = 403; static const int httpRequestedRangeNotSatisfiable = 416; static const int httpInternalError = 500; static const char* httpOKText = "OK"; static const char* httpPartialContentText = "Partial Content"; static const char* httpNotAllowedText = "Not Allowed"; static const char* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable"; static const char* httpInternalErrorText = "Internal Server Error"; static const char* const webKitBlobResourceDomain = "WebKitBlobResource"; NetworkDataTaskBlob::NetworkDataTaskBlob(NetworkSession& session, NetworkDataTaskClient& client, const ResourceRequest& request, ContentSniffingPolicy shouldContentSniff, const Vector>& fileReferences) : NetworkDataTask(session, client, request, DoNotAllowStoredCredentials, false) , m_stream(std::make_unique(*this)) , m_fileReferences(fileReferences) { for (auto& fileReference : m_fileReferences) fileReference->prepareForFileAccess(); m_blobData = static_cast(blobRegistry()).getBlobDataFromURL(request.url()); m_session->registerNetworkDataTask(*this); LOG(NetworkSession, "%p - Created NetworkDataTaskBlob for %s", this, request.url().string().utf8().data()); } NetworkDataTaskBlob::~NetworkDataTaskBlob() { for (auto& fileReference : m_fileReferences) fileReference->revokeFileAccess(); clearStream(); m_session->unregisterNetworkDataTask(*this); } void NetworkDataTaskBlob::clearStream() { if (m_state == State::Completed) return; m_state = State::Completed; if (m_fileOpened) { m_fileOpened = false; m_stream->close(); } m_stream = nullptr; } void NetworkDataTaskBlob::resume() { ASSERT(m_state != State::Running); if (m_state == State::Canceling || m_state == State::Completed) return; m_state = State::Running; if (m_scheduledFailureType != NoFailure) { ASSERT(m_failureTimer.isActive()); return; } RunLoop::main().dispatch([this, protectedThis = makeRef(*this)] { if (m_state == State::Canceling || m_state == State::Completed || !m_client) { clearStream(); return; } if (!equalLettersIgnoringASCIICase(m_firstRequest.httpMethod(), "get")) { didFail(Error::MethodNotAllowed); return; } // If the blob data is not found, fail now. if (!m_blobData) { didFail(Error::NotFoundError); return; } // Parse the "Range" header we care about. String range = m_firstRequest.httpHeaderField(HTTPHeaderName::Range); if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) { dispatchDidReceiveResponse(Error::RangeError); return; } getSizeForNext(); }); } void NetworkDataTaskBlob::suspend() { // FIXME: can this happen? } void NetworkDataTaskBlob::cancel() { if (m_state == State::Canceling || m_state == State::Completed) return; m_state = State::Canceling; if (m_fileOpened) { m_fileOpened = false; m_stream->close(); } if (isDownload()) cleanDownloadFiles(); } void NetworkDataTaskBlob::invalidateAndCancel() { cancel(); clearStream(); } void NetworkDataTaskBlob::getSizeForNext() { ASSERT(isMainThread()); // Do we finish validating and counting size for all items? if (m_sizeItemCount >= m_blobData->items().size()) { seek(); dispatchDidReceiveResponse(); return; } const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); switch (item.type()) { case BlobDataItem::Type::Data: didGetSize(item.length()); break; case BlobDataItem::Type::File: // Files know their sizes, but asking the stream to verify that the file wasn't modified. m_stream->getSize(item.file()->path(), item.file()->expectedModificationTime()); break; default: ASSERT_NOT_REACHED(); } } void NetworkDataTaskBlob::didGetSize(long long size) { ASSERT(isMainThread()); if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) { clearStream(); return; } // If the size is -1, it means the file has been moved or changed. Fail now. if (size == -1) { didFail(Error::NotFoundError); return; } // 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. const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); size = item.length(); // Cache the size. m_itemLengthList.append(size); // Count the size. m_totalSize += size; m_totalRemainingSize += size; m_sizeItemCount++; // Continue with the next item. getSizeForNext(); } void NetworkDataTaskBlob::seek() { ASSERT(isMainThread()); // Convert from the suffix length to the range. if (m_rangeSuffixLength != kPositionNotSpecified) { m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength; m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1; } // Bail out if the range is not provided. if (m_rangeOffset == kPositionNotSpecified) return; // Skip the initial items that are not in the range. long long offset = m_rangeOffset; for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount) offset -= m_itemLengthList[m_readItemCount]; // Set the offset that need to jump to for the first item in the range. m_currentItemReadSize = offset; // Adjust the total remaining size in order not to go beyond the range. if (m_rangeEnd != kPositionNotSpecified) { long long rangeSize = m_rangeEnd - m_rangeOffset + 1; if (m_totalRemainingSize > rangeSize) m_totalRemainingSize = rangeSize; } else m_totalRemainingSize -= m_rangeOffset; } void NetworkDataTaskBlob::dispatchDidReceiveResponse(Error errorCode) { LOG(NetworkSession, "%p - NetworkDataTaskBlob::dispatchDidReceiveResponse(%u)", this, static_cast(errorCode)); Ref protectedThis(*this); ResourceResponse response(m_firstRequest.url(), errorCode != Error::NoError ? "text/plain" : m_blobData->contentType(), errorCode != Error::NoError ? 0 : m_totalRemainingSize, String()); switch (errorCode) { case Error::NoError: { bool isRangeRequest = m_rangeOffset != kPositionNotSpecified; response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK); response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText); response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_blobData->contentType()); response.setHTTPHeaderField(HTTPHeaderName::ContentLength, String::number(m_totalRemainingSize)); if (isRangeRequest) response.setHTTPHeaderField(HTTPHeaderName::ContentRange, ParsedContentRange(m_rangeOffset, m_rangeEnd, m_totalSize).headerValue()); // FIXME: If a resource identified with a blob: URL is a File object, user agents must use that file's name attribute, // as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute. // Notably, this will affect a name suggested in "File Save As". break; } case Error::RangeError: response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable); response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText); break; case Error::SecurityError: response.setHTTPStatusCode(httpNotAllowed); response.setHTTPStatusText(httpNotAllowedText); break; default: response.setHTTPStatusCode(httpInternalError); response.setHTTPStatusText(httpInternalErrorText); break; } didReceiveResponse(WTFMove(response), [this, protectedThis = WTFMove(protectedThis), errorCode](PolicyAction policyAction) { LOG(NetworkSession, "%p - NetworkDataTaskBlob::didReceiveResponse completionHandler (%u)", this, static_cast(policyAction)); if (m_state == State::Canceling || m_state == State::Completed) { clearStream(); return; } if (errorCode != Error::NoError) { didFinish(); return; } switch (policyAction) { case PolicyAction::PolicyUse: m_buffer.resize(bufferSize); read(); break; case PolicyAction::PolicyIgnore: break; case PolicyAction::PolicyDownload: download(); break; } }); } void NetworkDataTaskBlob::read() { ASSERT(isMainThread()); // If there is no more remaining data to read, we are done. if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) { didFinish(); return; } const BlobDataItem& item = m_blobData->items().at(m_readItemCount); if (item.type() == BlobDataItem::Type::Data) readData(item); else if (item.type() == BlobDataItem::Type::File) readFile(item); else ASSERT_NOT_REACHED(); } void NetworkDataTaskBlob::readData(const BlobDataItem& item) { ASSERT(item.data().data()); long long bytesToRead = item.length() - m_currentItemReadSize; if (bytesToRead > m_totalRemainingSize) bytesToRead = m_totalRemainingSize; consumeData(reinterpret_cast(item.data().data()->data()) + item.offset() + m_currentItemReadSize, static_cast(bytesToRead)); m_currentItemReadSize = 0; } void NetworkDataTaskBlob::readFile(const BlobDataItem& item) { ASSERT(m_stream); if (m_fileOpened) { m_stream->read(m_buffer.data(), m_buffer.size()); return; } long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize; if (bytesToRead > m_totalRemainingSize) bytesToRead = static_cast(m_totalRemainingSize); m_stream->openForRead(item.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead); m_fileOpened = true; m_currentItemReadSize = 0; } void NetworkDataTaskBlob::didOpen(bool success) { if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) { clearStream(); return; } if (!success) { didFail(Error::NotReadableError); return; } Ref protectedThis(*this); read(); } void NetworkDataTaskBlob::didRead(int bytesRead) { if (m_state == State::Canceling || m_state == State::Completed || (!m_client && !isDownload())) { clearStream(); return; } if (bytesRead < 0) { didFail(Error::NotReadableError); return; } Ref protectedThis(*this); consumeData(m_buffer.data(), bytesRead); } void NetworkDataTaskBlob::consumeData(const char* data, int bytesRead) { m_totalRemainingSize -= bytesRead; if (bytesRead) { if (m_downloadFile != invalidPlatformFileHandle) { if (!writeDownload(data, bytesRead)) return; } else { ASSERT(m_client); m_client->didReceiveData(SharedBuffer::create(data, bytesRead)); } } if (m_fileOpened) { // When the current item is a file item, the reading is completed only if bytesRead is 0. if (!bytesRead) { // Close the file. m_fileOpened = false; m_stream->close(); // Move to the next item. m_readItemCount++; } } else { // Otherwise, we read the current text item as a whole and move to the next item. m_readItemCount++; } read(); } void NetworkDataTaskBlob::setPendingDownloadLocation(const String& filename, const SandboxExtension::Handle& sandboxExtensionHandle, bool allowOverwrite) { NetworkDataTask::setPendingDownloadLocation(filename, sandboxExtensionHandle, allowOverwrite); ASSERT(!m_sandboxExtension); m_sandboxExtension = SandboxExtension::create(sandboxExtensionHandle); if (m_sandboxExtension) m_sandboxExtension->consume(); if (allowOverwrite && fileExists(m_pendingDownloadLocation)) deleteFile(m_pendingDownloadLocation); } String NetworkDataTaskBlob::suggestedFilename() const { if (!m_suggestedFilename.isEmpty()) return m_suggestedFilename; return ASCIILiteral("unknown"); } void NetworkDataTaskBlob::download() { ASSERT(isDownload()); ASSERT(m_pendingDownloadLocation); LOG(NetworkSession, "%p - NetworkDataTaskBlob::download to %s", this, m_pendingDownloadLocation.utf8().data()); m_downloadFile = openFile(m_pendingDownloadLocation, OpenForWrite); if (m_downloadFile == invalidPlatformFileHandle) { didFailDownload(cancelledError(m_firstRequest)); return; } auto& downloadManager = NetworkProcess::singleton().downloadManager(); auto download = std::make_unique(downloadManager, m_pendingDownloadID, *this, m_session->sessionID(), suggestedFilename()); auto* downloadPtr = download.get(); downloadManager.dataTaskBecameDownloadTask(m_pendingDownloadID, WTFMove(download)); downloadPtr->didCreateDestination(m_pendingDownloadLocation); ASSERT(!m_client); m_buffer.resize(bufferSize); read(); } bool NetworkDataTaskBlob::writeDownload(const char* data, int bytesRead) { ASSERT(isDownload()); int bytesWritten = writeToFile(m_downloadFile, data, bytesRead); if (bytesWritten == -1) { didFailDownload(cancelledError(m_firstRequest)); return false; } ASSERT(bytesWritten == bytesRead); auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); ASSERT(download); download->didReceiveData(bytesWritten); return true; } void NetworkDataTaskBlob::cleanDownloadFiles() { if (m_downloadFile != invalidPlatformFileHandle) { closeFile(m_downloadFile); m_downloadFile = invalidPlatformFileHandle; } deleteFile(m_pendingDownloadLocation); } void NetworkDataTaskBlob::didFailDownload(const ResourceError& error) { LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFailDownload", this); clearStream(); cleanDownloadFiles(); if (m_sandboxExtension) { m_sandboxExtension->revoke(); m_sandboxExtension = nullptr; } if (m_client) m_client->didCompleteWithError(error); else { auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); ASSERT(download); download->didFail(error, IPC::DataReference()); } } void NetworkDataTaskBlob::didFinishDownload() { LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFinishDownload", this); ASSERT(isDownload()); closeFile(m_downloadFile); m_downloadFile = invalidPlatformFileHandle; if (m_sandboxExtension) { m_sandboxExtension->revoke(); m_sandboxExtension = nullptr; } clearStream(); auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); ASSERT(download); download->didFinish(); } void NetworkDataTaskBlob::didFail(Error errorCode) { ASSERT(!m_sandboxExtension); Ref protectedThis(*this); if (isDownload()) { didFailDownload(ResourceError(webKitBlobResourceDomain, static_cast(errorCode), m_firstRequest.url(), String())); return; } LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFail", this); clearStream(); ASSERT(m_client); m_client->didCompleteWithError(ResourceError(webKitBlobResourceDomain, static_cast(errorCode), m_firstRequest.url(), String())); } void NetworkDataTaskBlob::didFinish() { if (m_downloadFile != invalidPlatformFileHandle) { didFinishDownload(); return; } ASSERT(!m_sandboxExtension); LOG(NetworkSession, "%p - NetworkDataTaskBlob::didFinish", this); clearStream(); ASSERT(m_client); m_client->didCompleteWithError({ }); } } // namespace WebKit #endif // USE(NETWORK_SESSION)