| /* |
| * 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 "core/fileapi/FileReaderLoader.h" |
| |
| #include <memory> |
| #include "core/dom/DOMArrayBuffer.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/fileapi/Blob.h" |
| #include "core/fileapi/FileReaderLoaderClient.h" |
| #include "core/html/parser/TextResourceDecoder.h" |
| #include "core/loader/ThreadableLoader.h" |
| #include "platform/blob/BlobRegistry.h" |
| #include "platform/blob/BlobURL.h" |
| #include "platform/loader/fetch/FetchInitiatorTypeNames.h" |
| #include "platform/loader/fetch/ResourceError.h" |
| #include "platform/loader/fetch/ResourceLoaderOptions.h" |
| #include "platform/loader/fetch/ResourceRequest.h" |
| #include "platform/loader/fetch/ResourceResponse.h" |
| #include "platform/loader/fetch/TextResourceDecoderOptions.h" |
| #include "platform/wtf/PassRefPtr.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "platform/wtf/RefPtr.h" |
| #include "platform/wtf/Vector.h" |
| #include "platform/wtf/text/Base64.h" |
| #include "platform/wtf/text/StringBuilder.h" |
| #include "public/platform/WebURLRequest.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| FileReaderLoader::FileReaderLoader(ReadType read_type, |
| FileReaderLoaderClient* client) |
| : read_type_(read_type), client_(client) {} |
| |
| FileReaderLoader::~FileReaderLoader() { |
| Cleanup(); |
| UnadjustReportedMemoryUsageToV8(); |
| if (!url_for_reading_.IsEmpty()) { |
| BlobRegistry::RevokePublicBlobURL(url_for_reading_); |
| } |
| } |
| |
| void FileReaderLoader::Start(ExecutionContext* execution_context, |
| PassRefPtr<BlobDataHandle> blob_data) { |
| DCHECK(execution_context); |
| // The blob is read by routing through the request handling layer given a |
| // temporary public url. |
| url_for_reading_ = |
| BlobURL::CreatePublicURL(execution_context->GetSecurityOrigin()); |
| if (url_for_reading_.IsEmpty()) { |
| Failed(FileError::kSecurityErr); |
| return; |
| } |
| |
| BlobRegistry::RegisterPublicBlobURL(execution_context->GetSecurityOrigin(), |
| url_for_reading_, std::move(blob_data)); |
| // Construct and load the request. |
| ResourceRequest request(url_for_reading_); |
| request.SetExternalRequestStateFromRequestorAddressSpace( |
| execution_context->GetSecurityContext().AddressSpace()); |
| |
| // FIXME: Should this really be 'internal'? Do we know anything about the |
| // actual request that generated this fetch? |
| request.SetRequestContext(WebURLRequest::kRequestContextInternal); |
| |
| request.SetHTTPMethod(HTTPNames::GET); |
| if (has_range_) |
| request.SetHTTPHeaderField( |
| HTTPNames::Range, |
| AtomicString(String::Format("bytes=%d-%d", range_start_, range_end_))); |
| |
| ThreadableLoaderOptions options; |
| options.fetch_request_mode = WebURLRequest::kFetchRequestModeSameOrigin; |
| // FIXME: Is there a directive to which this load should be subject? |
| options.content_security_policy_enforcement = |
| kDoNotEnforceContentSecurityPolicy; |
| |
| ResourceLoaderOptions resource_loader_options; |
| // Use special initiator to hide the request from the inspector. |
| resource_loader_options.initiator_info.name = |
| FetchInitiatorTypeNames::internal; |
| |
| if (client_) { |
| DCHECK(!loader_); |
| loader_ = ThreadableLoader::Create(*execution_context, this, options, |
| resource_loader_options); |
| loader_->Start(request); |
| } else { |
| ThreadableLoader::LoadResourceSynchronously( |
| *execution_context, request, *this, options, resource_loader_options); |
| } |
| } |
| |
| void FileReaderLoader::Cancel() { |
| error_code_ = FileError::kAbortErr; |
| Cleanup(); |
| } |
| |
| void FileReaderLoader::Cleanup() { |
| if (loader_) { |
| loader_->Cancel(); |
| loader_ = nullptr; |
| } |
| |
| // If we get any error, we do not need to keep a buffer around. |
| if (error_code_) { |
| raw_data_.reset(); |
| string_result_ = ""; |
| is_raw_data_converted_ = true; |
| decoder_.reset(); |
| array_buffer_result_ = nullptr; |
| UnadjustReportedMemoryUsageToV8(); |
| } |
| } |
| |
| void FileReaderLoader::AdjustReportedMemoryUsageToV8(int64_t usage) { |
| if (!usage) |
| return; |
| memory_usage_reported_to_v8_ += usage; |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(usage); |
| DCHECK_GE(memory_usage_reported_to_v8_, 0); |
| } |
| |
| void FileReaderLoader::UnadjustReportedMemoryUsageToV8() { |
| if (!memory_usage_reported_to_v8_) |
| return; |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory( |
| -memory_usage_reported_to_v8_); |
| memory_usage_reported_to_v8_ = 0; |
| } |
| |
| void FileReaderLoader::DidReceiveResponse( |
| unsigned long, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| DCHECK(!handle); |
| if (response.HttpStatusCode() != 200) { |
| Failed(HttpStatusCodeToErrorCode(response.HttpStatusCode())); |
| return; |
| } |
| |
| // A negative value means that the content length wasn't specified. |
| total_bytes_ = response.ExpectedContentLength(); |
| |
| long long initial_buffer_length = -1; |
| |
| if (total_bytes_ >= 0) { |
| initial_buffer_length = total_bytes_; |
| } else if (has_range_) { |
| // Set m_totalBytes and allocate a buffer based on the specified range. |
| total_bytes_ = 1LL + range_end_ - range_start_; |
| initial_buffer_length = total_bytes_; |
| } else { |
| // Nothing is known about the size of the resource. Normalize |
| // m_totalBytes to -1 and initialize the buffer for receiving with the |
| // default size. |
| total_bytes_ = -1; |
| } |
| |
| DCHECK(!raw_data_); |
| |
| if (read_type_ != kReadByClient) { |
| // 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 (initial_buffer_length > std::numeric_limits<unsigned>::max()) { |
| Failed(FileError::kNotReadableErr); |
| return; |
| } |
| |
| if (initial_buffer_length < 0) |
| raw_data_ = WTF::MakeUnique<ArrayBufferBuilder>(); |
| else |
| raw_data_ = WTF::WrapUnique( |
| new ArrayBufferBuilder(static_cast<unsigned>(initial_buffer_length))); |
| |
| if (!raw_data_ || !raw_data_->IsValid()) { |
| Failed(FileError::kNotReadableErr); |
| return; |
| } |
| |
| if (initial_buffer_length >= 0) { |
| // Total size is known. Set m_rawData to ignore overflowed data. |
| raw_data_->SetVariableCapacity(false); |
| } |
| } |
| |
| if (client_) |
| client_->DidStartLoading(); |
| } |
| |
| void FileReaderLoader::DidReceiveData(const char* data, unsigned data_length) { |
| DCHECK(data); |
| |
| // Bail out if we already encountered an error. |
| if (error_code_) |
| return; |
| |
| if (read_type_ == kReadByClient) { |
| bytes_loaded_ += data_length; |
| |
| if (client_) |
| client_->DidReceiveDataForClient(data, data_length); |
| return; |
| } |
| |
| unsigned bytes_appended = raw_data_->Append(data, data_length); |
| if (!bytes_appended) { |
| raw_data_.reset(); |
| bytes_loaded_ = 0; |
| Failed(FileError::kNotReadableErr); |
| return; |
| } |
| bytes_loaded_ += bytes_appended; |
| is_raw_data_converted_ = false; |
| AdjustReportedMemoryUsageToV8(bytes_appended); |
| |
| if (client_) |
| client_->DidReceiveData(); |
| } |
| |
| void FileReaderLoader::DidFinishLoading(unsigned long, double) { |
| if (read_type_ != kReadByClient && raw_data_) { |
| raw_data_->ShrinkToFit(); |
| is_raw_data_converted_ = false; |
| } |
| |
| if (total_bytes_ == -1) { |
| // Update m_totalBytes only when in dynamic buffer grow mode. |
| total_bytes_ = bytes_loaded_; |
| } |
| |
| finished_loading_ = true; |
| |
| Cleanup(); |
| if (client_) |
| client_->DidFinishLoading(); |
| } |
| |
| void FileReaderLoader::DidFail(const ResourceError& error) { |
| if (error.IsCancellation()) |
| return; |
| // If we're aborting, do not proceed with normal error handling since it is |
| // covered in aborting code. |
| if (error_code_ == FileError::kAbortErr) |
| return; |
| |
| Failed(FileError::kNotReadableErr); |
| } |
| |
| void FileReaderLoader::Failed(FileError::ErrorCode error_code) { |
| error_code_ = error_code; |
| Cleanup(); |
| if (client_) |
| client_->DidFail(error_code_); |
| } |
| |
| FileError::ErrorCode FileReaderLoader::HttpStatusCodeToErrorCode( |
| int http_status_code) { |
| switch (http_status_code) { |
| case 403: |
| return FileError::kSecurityErr; |
| case 404: |
| return FileError::kNotFoundErr; |
| default: |
| return FileError::kNotReadableErr; |
| } |
| } |
| |
| DOMArrayBuffer* FileReaderLoader::ArrayBufferResult() { |
| DCHECK_EQ(read_type_, kReadAsArrayBuffer); |
| if (array_buffer_result_) |
| return array_buffer_result_; |
| |
| // If the loading is not started or an error occurs, return an empty result. |
| if (!raw_data_ || error_code_) |
| return nullptr; |
| |
| DOMArrayBuffer* result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer()); |
| if (finished_loading_) { |
| array_buffer_result_ = result; |
| AdjustReportedMemoryUsageToV8(-1 * raw_data_->ByteLength()); |
| raw_data_.reset(); |
| } |
| return result; |
| } |
| |
| String FileReaderLoader::StringResult() { |
| DCHECK_NE(read_type_, kReadAsArrayBuffer); |
| DCHECK_NE(read_type_, kReadByClient); |
| |
| if (!raw_data_ || error_code_ || is_raw_data_converted_) |
| return string_result_; |
| |
| switch (read_type_) { |
| case kReadAsArrayBuffer: |
| // No conversion is needed. |
| return string_result_; |
| case kReadAsBinaryString: |
| SetStringResult(raw_data_->ToString()); |
| break; |
| case kReadAsText: |
| SetStringResult(ConvertToText()); |
| break; |
| case kReadAsDataURL: |
| // Partial data is not supported when reading as data URL. |
| if (finished_loading_) |
| SetStringResult(ConvertToDataURL()); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| if (finished_loading_) { |
| DCHECK(is_raw_data_converted_); |
| AdjustReportedMemoryUsageToV8(-1 * raw_data_->ByteLength()); |
| raw_data_.reset(); |
| } |
| return string_result_; |
| } |
| |
| void FileReaderLoader::SetStringResult(const String& result) { |
| AdjustReportedMemoryUsageToV8(-1 * string_result_.CharactersSizeInBytes()); |
| is_raw_data_converted_ = true; |
| string_result_ = result; |
| AdjustReportedMemoryUsageToV8(string_result_.CharactersSizeInBytes()); |
| } |
| |
| String FileReaderLoader::ConvertToText() { |
| if (!bytes_loaded_) |
| 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. |
| StringBuilder builder; |
| if (!decoder_) { |
| decoder_ = TextResourceDecoder::Create(TextResourceDecoderOptions( |
| TextResourceDecoderOptions::kPlainTextContent, |
| encoding_.IsValid() ? encoding_ : UTF8Encoding())); |
| } |
| builder.Append(decoder_->Decode(static_cast<const char*>(raw_data_->Data()), |
| raw_data_->ByteLength())); |
| |
| if (finished_loading_) |
| builder.Append(decoder_->Flush()); |
| |
| return builder.ToString(); |
| } |
| |
| String FileReaderLoader::ConvertToDataURL() { |
| StringBuilder builder; |
| builder.Append("data:"); |
| |
| if (!bytes_loaded_) |
| return builder.ToString(); |
| |
| builder.Append(data_type_); |
| builder.Append(";base64,"); |
| |
| Vector<char> out; |
| Base64Encode(static_cast<const char*>(raw_data_->Data()), |
| raw_data_->ByteLength(), out); |
| out.push_back('\0'); |
| builder.Append(out.data()); |
| |
| return builder.ToString(); |
| } |
| |
| void FileReaderLoader::SetEncoding(const String& encoding) { |
| if (!encoding.IsEmpty()) |
| encoding_ = WTF::TextEncoding(encoding); |
| } |
| |
| } // namespace blink |