/* * Copyright (C) 2010 Google Inc. All rights reserved. * * 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 "FileReaderLoader.h" #include "Blob.h" #include "BlobURL.h" #include "ContentSecurityPolicy.h" #include "ExceptionCode.h" #include "FileReaderLoaderClient.h" #include "HTTPHeaderNames.h" #include "HTTPStatusCodes.h" #include "ResourceError.h" #include "ResourceRequest.h" #include "ResourceResponse.h" #include "ScriptExecutionContext.h" #include "SecurityOrigin.h" #include "SharedBuffer.h" #include "TextResourceDecoder.h" #include "ThreadableBlobRegistry.h" #include "ThreadableLoader.h" #include #include #include #include #include #include #include #include namespace WebCore { DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(FileReaderLoader); const int defaultBufferLength = 32768; Ref FileReaderLoader::create(ReadType readType, FileReaderLoaderClient* client) { return adoptRef(*new FileReaderLoader(readType, client)); } FileReaderLoader::FileReaderLoader(ReadType readType, FileReaderLoaderClient* client) : m_readType(readType) , m_client(client) , m_isRawDataConverted(false) , m_stringResult(emptyString()) , m_variableLength(false) , m_bytesLoaded(0) , m_totalBytes(0) { } FileReaderLoader::~FileReaderLoader() { cancel(); } void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, Blob& blob) { start(scriptExecutionContext, blob.url()); } void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, const URL& blobURL) { ASSERT(scriptExecutionContext); // The blob is read by routing through the request handling layer given a temporary public url. RefPtr securityOrigin = scriptExecutionContext->securityOrigin(); m_urlForReading = { BlobURL::createPublicURL(securityOrigin.get()), scriptExecutionContext->topOrigin().data() }; if (m_urlForReading.isEmpty()) { failed(ExceptionCode::SecurityError); return; } CheckedPtr contentSecurityPolicy = scriptExecutionContext->contentSecurityPolicy(); if (!contentSecurityPolicy) return; ThreadableBlobRegistry::registerBlobURL(securityOrigin.get(), scriptExecutionContext->policyContainer(), m_urlForReading, blobURL); // Construct and load the request. ResourceRequest request(URL { m_urlForReading }); request.setHTTPMethod("GET"_s); request.setHiddenFromInspector(true); ThreadableLoaderOptions options; options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks; options.dataBufferingPolicy = DataBufferingPolicy::DoNotBufferData; options.credentials = FetchOptions::Credentials::Include; options.mode = FetchOptions::Mode::SameOrigin; options.contentSecurityPolicyEnforcement = ContentSecurityPolicyEnforcement::DoNotEnforce; if (m_client) { auto loader = ThreadableLoader::create(*scriptExecutionContext, *this, WTF::move(request), options); if (!loader) return; std::exchange(m_loader, loader); } else ThreadableLoader::loadResourceSynchronously(*scriptExecutionContext, WTF::move(request), *this, options); } void FileReaderLoader::cancel() { m_errorCode = ExceptionCode::AbortError; terminate(); } void FileReaderLoader::terminate() { if (RefPtr loader = m_loader) { loader->cancel(); cleanup(); } } void FileReaderLoader::cleanup() { m_loader = nullptr; // If we get any error, we do not need to keep a buffer around. if (m_errorCode) { m_rawData = nullptr; m_stringResult = emptyString(); } } bool FileReaderLoader::processResponse(const ResourceResponse& response) { if (response.httpStatusCode() != httpStatus200OK) { failed(httpStatusCodeToErrorCode(response.httpStatusCode())); return false; } if (m_readType == ReadType::ReadAsBinaryChunks) return true; long long length = response.expectedContentLength(); // A negative value means that the content length wasn't specified, so the buffer will need to be dynamically grown. if (length < 0) { m_variableLength = true; length = defaultBufferLength; } // Check that we can cast to unsigned since we have to do // so to call ArrayBuffer's create function. // FIXME: Support reading more than the current size limit of ArrayBuffer. if (length > std::numeric_limits::max()) { failed(ExceptionCode::NotReadableError); return false; } ASSERT(!m_rawData); m_rawData = ArrayBuffer::tryCreate(static_cast(length), 1); if (!m_rawData) { failed(ExceptionCode::NotReadableError); return false; } m_totalBytes = static_cast(length); return true; } void FileReaderLoader::didReceiveResponse(ScriptExecutionContextIdentifier, std::optional, const ResourceResponse& response) { if (!processResponse(response)) return; if (RefPtr client = m_client.get()) client->didStartLoading(); } void FileReaderLoader::didReceiveData(const SharedBuffer& buffer) { ASSERT(buffer.size()); // Bail out if we already encountered an error. if (m_errorCode) return; if (m_readType == ReadType::ReadAsBinaryChunks) { if (RefPtr client = m_client.get()) client->didReceiveBinaryChunk(buffer); return; } int length = buffer.size(); unsigned remainingBufferSpace = m_totalBytes - m_bytesLoaded; if (length > static_cast(remainingBufferSpace)) { // If the buffer has hit maximum size, it can't be grown any more. if (m_totalBytes >= std::numeric_limits::max()) { failed(ExceptionCode::NotReadableError); return; } if (m_variableLength) { unsigned newLength = m_totalBytes + buffer.size(); if (newLength < m_totalBytes) { failed(ExceptionCode::NotReadableError); return; } newLength = std::max(newLength, m_totalBytes + m_totalBytes / 4 + 1); auto newData = ArrayBuffer::tryCreate(newLength, 1); if (!newData) { // Not enough memory. failed(ExceptionCode::NotReadableError); return; } memcpySpan(newData->mutableSpan(), protect(m_rawData)->span().first(m_bytesLoaded)); m_rawData = newData; m_totalBytes = static_cast(newLength); } else { // This can only happen if we get more data than indicated in expected content length (i.e. never, unless the networking layer is buggy). length = remainingBufferSpace; } } if (length <= 0) return; memcpySpan(protect(m_rawData)->mutableSpan().subspan(m_bytesLoaded), buffer.span().first(length)); m_bytesLoaded += length; m_isRawDataConverted = false; if (RefPtr client = m_client.get()) client->didReceiveData(); } void FileReaderLoader::didFinishLoading(ScriptExecutionContextIdentifier, std::optional, const NetworkLoadMetrics&) { if (m_variableLength && m_totalBytes > m_bytesLoaded) { m_rawData = protect(m_rawData)->slice(0, m_bytesLoaded); m_totalBytes = m_bytesLoaded; } cleanup(); if (RefPtr client = m_client.get()) client->didFinishLoading(); } void FileReaderLoader::didFail(std::optional, const ResourceError& error) { // If we're aborting, do not proceed with normal error handling since it is covered in aborting code. if (m_errorCode && m_errorCode.value() == ExceptionCode::AbortError) return; failed(toErrorCode(static_cast(error.errorCode()))); } void FileReaderLoader::failed(ExceptionCode errorCode) { m_errorCode = errorCode; cleanup(); if (RefPtr client = m_client.get()) client->didFail(errorCode); } ExceptionCode FileReaderLoader::toErrorCode(BlobResourceHandle::Error error) { switch (error) { case BlobResourceHandle::Error::NotFoundError: return ExceptionCode::NotFoundError; default: return ExceptionCode::NotReadableError; } } ExceptionCode FileReaderLoader::httpStatusCodeToErrorCode(int httpStatusCode) { switch (httpStatusCode) { case httpStatus403Forbidden: return ExceptionCode::SecurityError; default: return ExceptionCode::NotReadableError; } } RefPtr FileReaderLoader::arrayBufferResult() const { ASSERT(m_readType == ReadAsArrayBuffer); // If the loading is not started or an error occurs, return an empty result. if (!m_rawData || m_errorCode) return nullptr; // If completed, we can simply return our buffer. if (isCompleted()) return m_rawData; // Otherwise, return a copy. return ArrayBuffer::create(*protect(m_rawData)); } String FileReaderLoader::stringResult() { ASSERT(m_readType != ReadAsArrayBuffer && m_readType != ReadAsBlob); // If the loading is not started or an error occurs, return an empty result. if (!m_rawData || m_errorCode) return m_stringResult; // If already converted from the raw data, return the result now. if (m_isRawDataConverted) return m_stringResult; switch (m_readType) { case ReadAsArrayBuffer: // No conversion is needed. break; case ReadAsBinaryString: m_stringResult = byteCast(protect(m_rawData)->span().first(m_bytesLoaded)); break; case ReadAsText: convertToText(); break; case ReadAsDataURL: // Partial data is not supported when reading as data URL. if (isCompleted()) convertToDataURL(); break; case ReadAsBlob: case ReadAsBinaryChunks: ASSERT_NOT_REACHED(); } return m_stringResult; } void FileReaderLoader::convertToText() { if (!m_bytesLoaded) return; // Decode the data. // The File API spec says that we should use the supplied encoding if it is valid. However, we choose to ignore this // requirement in order to be consistent with how WebKit decodes the web content: always has the BOM override the // provided encoding. // FIXME: consider supporting incremental decoding to improve the perf. if (!m_decoder) m_decoder = TextResourceDecoder::create("text/plain"_s, m_encoding.isValid() ? m_encoding : PAL::UTF8Encoding()); Ref decoder = *m_decoder; Ref rawData = *m_rawData; if (isCompleted()) m_stringResult = decoder->decodeAndFlush(rawData->span().first(m_bytesLoaded)); else m_stringResult = decoder->decode(rawData->span().first(m_bytesLoaded)); } void FileReaderLoader::convertToDataURL() { m_stringResult = makeString("data:"_s, m_dataType.isEmpty() ? "application/octet-stream"_s : m_dataType, ";base64,"_s, base64Encoded(m_rawData ? protect(m_rawData)->span().first(m_bytesLoaded) : std::span())); } bool FileReaderLoader::isCompleted() const { return m_bytesLoaded == m_totalBytes; } void FileReaderLoader::setEncoding(StringView encoding) { if (!encoding.isEmpty()) m_encoding = PAL::TextEncoding(encoding); } } // namespace WebCore