| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume.h" |
| |
| #include <cstring> |
| #include <sstream> |
| #include <utility> |
| |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/char_coding.h" |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/javascript_message_sender_interface.h" |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/javascript_requestor_interface.h" |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/request.h" |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h" |
| #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_reader_javascript_stream.h" |
| |
| namespace { |
| |
| typedef std::map<std::string, VolumeArchive*>::const_iterator |
| volume_archive_iterator; |
| |
| const char kPathDelimiter[] = "/"; |
| |
| // size is int64_t and modification_time is time_t because this is how |
| // minizip is going to pass them to us. |
| pp::VarDictionary CreateEntry(int64_t index, |
| const std::string& name, |
| bool is_directory, |
| int64_t size, |
| time_t modification_time) { |
| pp::VarDictionary entry_metadata; |
| // index is int64_t, unsupported by pp::Var |
| std::stringstream ss_index; |
| ss_index << index; |
| entry_metadata.Set("index", ss_index.str()); |
| entry_metadata.Set("isDirectory", is_directory); |
| entry_metadata.Set("name", name); |
| // size is int64_t, unsupported by pp::Var |
| std::stringstream ss_size; |
| ss_size << size; |
| entry_metadata.Set("size", ss_size.str()); |
| // mtime is time_t, unsupported by pp::Var |
| std::stringstream ss_modification_time; |
| ss_modification_time << modification_time; |
| entry_metadata.Set("modificationTime", ss_modification_time.str()); |
| |
| if (is_directory) |
| entry_metadata.Set("entries", pp::VarDictionary()); |
| |
| return entry_metadata; |
| } |
| |
| void ConstructMetadata(int64_t index, |
| const std::string& entry_path, |
| int64_t size, |
| bool is_directory, |
| time_t modification_time, |
| pp::VarDictionary* parent_metadata) { |
| if (entry_path == "") |
| return; |
| |
| pp::VarDictionary parent_entries = |
| pp::VarDictionary(parent_metadata->Get("entries")); |
| |
| std::string::size_type position = entry_path.find(kPathDelimiter); |
| pp::VarDictionary entry_metadata; |
| std::string entry_name; |
| |
| if (position == std::string::npos) { // The entry itself. |
| entry_name = entry_path; |
| entry_metadata = |
| CreateEntry(index, entry_name, is_directory, size, modification_time); |
| |
| // Update directory information. Required as sometimes the directory itself |
| // is returned after the files inside it. |
| pp::Var old_entry_metadata_var = parent_entries.Get(entry_name); |
| if (!old_entry_metadata_var.is_undefined()) { |
| pp::VarDictionary old_entry_metadata = |
| pp::VarDictionary(old_entry_metadata_var); |
| PP_DCHECK(old_entry_metadata.Get("isDirectory").AsBool()); |
| entry_metadata.Set("entries", old_entry_metadata.Get("entries")); |
| } |
| } else { // Get next parent on the way to the entry. |
| entry_name = entry_path.substr(0, position); |
| |
| // Get next parent metadata. If none, create a new directory entry for it. |
| // Some archives don't have directory information inside and for some the |
| // information is returned later than the files inside it. |
| pp::Var entry_metadata_var = parent_entries.Get(entry_name); |
| if (entry_metadata_var.is_undefined()) |
| entry_metadata = CreateEntry(-1, entry_name, true, 0, modification_time); |
| else |
| entry_metadata = pp::VarDictionary(parent_entries.Get(entry_name)); |
| |
| // Continue to construct metadata for all directories on the path to the |
| // to the entry and for the entry itself. |
| std::string entry_path_without_next_parent = entry_path.substr( |
| position + sizeof(kPathDelimiter) - 1 /* Last char is '\0'. */); |
| |
| ConstructMetadata(index, entry_path_without_next_parent, size, is_directory, |
| modification_time, &entry_metadata); |
| } |
| |
| // Recreate parent_metadata. This is necessary because pp::VarDictionary::Get |
| // returns a Var, not a Var& or Var* to directly modify the result. |
| parent_entries.Set(entry_name, entry_metadata); |
| parent_metadata->Set("entries", parent_entries); |
| } |
| |
| // An internal implementation of JavaScriptRequestorInterface. |
| class JavaScriptRequestor : public JavaScriptRequestorInterface { |
| public: |
| // JavaScriptRequestor does not own the volume pointer. |
| explicit JavaScriptRequestor(Volume* volume) : volume_(volume) {} |
| |
| void RequestFileChunk(const std::string& request_id, |
| int64_t offset, |
| int64_t bytes_to_read) override { |
| PP_DCHECK(offset >= 0); |
| PP_DCHECK(bytes_to_read > 0); |
| volume_->message_sender()->SendFileChunkRequest( |
| volume_->file_system_id(), request_id, offset, bytes_to_read); |
| } |
| |
| void RequestPassphrase(const std::string& request_id) override { |
| volume_->message_sender()->SendPassphraseRequest(volume_->file_system_id(), |
| request_id); |
| } |
| |
| private: |
| Volume* volume_; |
| }; |
| |
| // An internal implementation of VolumeArchiveFactoryInterface for default |
| // Volume constructor. |
| class VolumeArchiveFactory : public VolumeArchiveFactoryInterface { |
| public: |
| std::unique_ptr<VolumeArchive> Create( |
| std::unique_ptr<VolumeReader> reader) override { |
| return std::make_unique<VolumeArchiveMinizip>(std::move(reader)); |
| } |
| }; |
| |
| // An internal implementation of VolumeReaderFactoryInterface for default Volume |
| // constructor. |
| class VolumeReaderFactory : public VolumeReaderFactoryInterface { |
| public: |
| // VolumeReaderFactory does not own the volume pointer. |
| explicit VolumeReaderFactory(Volume* volume) : volume_(volume) {} |
| |
| std::unique_ptr<VolumeReader> Create(int64_t archive_size) override { |
| return std::make_unique<VolumeReaderJavaScriptStream>(archive_size, |
| volume_->requestor()); |
| } |
| |
| private: |
| Volume* volume_; |
| }; |
| |
| } // namespace |
| |
| struct Volume::OpenFileArgs { |
| OpenFileArgs(const std::string& request_id, |
| int64_t index, |
| const std::string& encoding, |
| int64_t archive_size) |
| : request_id(request_id), |
| index(index), |
| encoding(encoding), |
| archive_size(archive_size) {} |
| const std::string request_id; |
| const int64_t index; |
| const std::string encoding; |
| const int64_t archive_size; |
| }; |
| |
| Volume::Volume(const pp::InstanceHandle& instance_handle, |
| const std::string& file_system_id, |
| JavaScriptMessageSenderInterface* message_sender) |
| : file_system_id_(file_system_id), |
| message_sender_(message_sender), |
| worker_(instance_handle), |
| callback_factory_(this), |
| requestor_(std::make_unique<JavaScriptRequestor>(this)), |
| volume_archive_factory_(std::make_unique<VolumeArchiveFactory>()), |
| volume_reader_factory_(std::make_unique<VolumeReaderFactory>(this)) { |
| // Delegating constructors only from c++11. |
| } |
| |
| Volume::Volume( |
| const pp::InstanceHandle& instance_handle, |
| const std::string& file_system_id, |
| JavaScriptMessageSenderInterface* message_sender, |
| std::unique_ptr<VolumeArchiveFactoryInterface> volume_archive_factory, |
| std::unique_ptr<VolumeReaderFactoryInterface> volume_reader_factory) |
| : file_system_id_(file_system_id), |
| message_sender_(message_sender), |
| worker_(instance_handle), |
| callback_factory_(this), |
| requestor_(std::make_unique<JavaScriptRequestor>(this)), |
| volume_archive_factory_(std::move(volume_archive_factory)), |
| volume_reader_factory_(std::move(volume_reader_factory)) {} |
| |
| Volume::~Volume() { |
| worker_.Join(); |
| |
| if (volume_archive_) { |
| volume_archive_->Cleanup(); |
| } |
| } |
| |
| bool Volume::Init() { |
| return worker_.Start(); |
| } |
| |
| void Volume::ReadMetadata(const std::string& request_id, |
| const std::string& encoding, |
| int64_t archive_size) { |
| worker_.message_loop().PostWork(callback_factory_.NewCallback( |
| &Volume::ReadMetadataCallback, request_id, encoding, archive_size)); |
| } |
| |
| void Volume::OpenFile(const std::string& request_id, |
| int64_t index, |
| const std::string& encoding, |
| int64_t archive_size) { |
| worker_.message_loop().PostWork(callback_factory_.NewCallback( |
| &Volume::OpenFileCallback, |
| OpenFileArgs(request_id, index, encoding, archive_size))); |
| } |
| |
| void Volume::CloseFile(const std::string& request_id, |
| const std::string& open_request_id) { |
| // Though close file could be executed on main thread, we send it to worker_ |
| // in order to ensure thread safety. |
| worker_.message_loop().PostWork(callback_factory_.NewCallback( |
| &Volume::CloseFileCallback, request_id, open_request_id)); |
| } |
| |
| void Volume::ReadFile(const std::string& request_id, |
| const pp::VarDictionary& dictionary) { |
| worker_.message_loop().PostWork(callback_factory_.NewCallback( |
| &Volume::ReadFileCallback, request_id, dictionary)); |
| } |
| |
| void Volume::ReadChunkDone(const std::string& request_id, |
| const pp::VarArrayBuffer& array_buffer, |
| int64_t read_offset) { |
| PP_DCHECK(volume_archive_); |
| |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->SetBufferAndSignal(array_buffer, read_offset); |
| } |
| |
| void Volume::ReadChunkError(const std::string& request_id) { |
| PP_DCHECK(volume_archive_); |
| |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->ReadErrorSignal(); |
| } |
| |
| void Volume::ReadPassphraseDone(const std::string& request_id, |
| const std::string& passphrase) { |
| PP_DCHECK(volume_archive_); |
| |
| job_lock_.Acquire(); |
| if (request_id == reader_request_id_) { |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->SetPassphraseAndSignal(passphrase); |
| } |
| job_lock_.Release(); |
| } |
| |
| void Volume::ReadPassphraseError(const std::string& request_id) { |
| PP_DCHECK(volume_archive_); |
| |
| job_lock_.Acquire(); |
| if (request_id == reader_request_id_) { |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->PassphraseErrorSignal(); |
| } |
| job_lock_.Release(); |
| } |
| |
| void Volume::ReadMetadataCallback(int32_t /*result*/, |
| const std::string& request_id, |
| const std::string& encoding, |
| int64_t archive_size) { |
| if (volume_archive_) { |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| "ALREADY_OPENED"); |
| } |
| |
| job_lock_.Acquire(); |
| volume_archive_ = volume_archive_factory_->Create( |
| volume_reader_factory_->Create(archive_size)); |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->SetRequestId(request_id); |
| reader_request_id_ = request_id; |
| job_lock_.Release(); |
| |
| if (!volume_archive_->Init(encoding)) { |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| volume_archive_->error_message()); |
| ClearJob(); |
| volume_archive_.reset(); |
| return; |
| } |
| |
| // Read and construct metadata. |
| pp::VarDictionary root_metadata = CreateEntry(-1, "" /* name */, true, 0, 0); |
| |
| std::string path_name; |
| int64_t size = 0; |
| bool is_directory = false; |
| time_t modification_time = 0; |
| int64_t index = 0; |
| |
| for (;;) { |
| path_name.clear(); |
| bool is_encoded_in_utf8; |
| if (volume_archive_->GetCurrentFileInfo( |
| &path_name, &is_encoded_in_utf8, &size, &is_directory, |
| &modification_time) == VolumeArchive::RESULT_FAIL) { |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| volume_archive_->error_message()); |
| ClearJob(); |
| volume_archive_.reset(); |
| return; |
| } |
| |
| if (path_name.empty()) // End of archive. |
| break; |
| |
| std::string display_name; |
| if (is_encoded_in_utf8) { |
| display_name = std::string(path_name); |
| } else { |
| display_name = Cp437ToUtf8(path_name); |
| } |
| |
| ConstructMetadata(index, display_name.c_str(), size, is_directory, |
| modification_time, &root_metadata); |
| |
| index_to_pathname_[index] = path_name; |
| |
| ++index; |
| |
| int return_value = volume_archive_->GoToNextFile(); |
| if (return_value == VolumeArchive::RESULT_FAIL) { |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| volume_archive_->error_message()); |
| ClearJob(); |
| volume_archive_.reset(); |
| return; |
| } |
| if (return_value == VolumeArchive::RESULT_EOF) |
| break; |
| } |
| |
| ClearJob(); |
| |
| // Send metadata back to JavaScript. |
| message_sender_->SendReadMetadataDone(file_system_id_, request_id, |
| root_metadata); |
| } |
| |
| void Volume::OpenFileCallback(int32_t /*result*/, const OpenFileArgs& args) { |
| if (!volume_archive_) { |
| message_sender_->SendFileSystemError(file_system_id_, args.request_id, |
| "NOT_OPENED"); |
| return; |
| } |
| |
| job_lock_.Acquire(); |
| if (!reader_request_id_.empty()) { |
| // It is illegal to open a file while another operation is in progress or |
| // another file is opened. |
| message_sender_->SendFileSystemError(file_system_id_, args.request_id, |
| "ILLEGAL"); |
| job_lock_.Release(); |
| return; |
| } |
| |
| static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader()) |
| ->SetRequestId(args.request_id); |
| reader_request_id_ = args.request_id; |
| job_lock_.Release(); |
| |
| std::string path_name = index_to_pathname_[args.index]; |
| int64_t size = 0; |
| bool is_directory = false; |
| time_t modification_time = 0; |
| |
| if (!volume_archive_->SeekHeader(path_name)) { |
| message_sender_->SendFileSystemError(file_system_id_, args.request_id, |
| volume_archive_->error_message()); |
| ClearJob(); |
| return; |
| } |
| |
| bool is_encoded_in_utf8; |
| if (volume_archive_->GetCurrentFileInfo( |
| &path_name, &is_encoded_in_utf8, &size, &is_directory, |
| &modification_time) != VolumeArchive::RESULT_SUCCESS) { |
| message_sender_->SendFileSystemError(file_system_id_, args.request_id, |
| volume_archive_->error_message()); |
| ClearJob(); |
| return; |
| } |
| |
| // Send successful opened file response to NaCl. |
| message_sender_->SendOpenFileDone(file_system_id_, args.request_id); |
| } |
| |
| void Volume::CloseFileCallback(int32_t /*result*/, |
| const std::string& request_id, |
| const std::string& open_request_id) { |
| job_lock_.Acquire(); |
| reader_request_id_ = ""; |
| job_lock_.Release(); |
| |
| message_sender_->SendCloseFileDone(file_system_id_, request_id, |
| open_request_id); |
| } |
| |
| void Volume::ReadFileCallback(int32_t /*result*/, |
| const std::string& request_id, |
| const pp::VarDictionary& dictionary) { |
| if (!volume_archive_) { |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| "NOT_OPENED"); |
| return; |
| } |
| |
| std::string open_request_id( |
| dictionary.Get(request::key::kOpenRequestId).AsString()); |
| int64_t offset = |
| request::GetInt64FromString(dictionary, request::key::kOffset); |
| int64_t length = |
| request::GetInt64FromString(dictionary, request::key::kLength); |
| PP_DCHECK(length > 0); // JavaScript must not make requests with length <= 0. |
| |
| job_lock_.Acquire(); |
| if (open_request_id != reader_request_id_) { |
| // The file is not opened. |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| "FILE_NOT_OPENED"); |
| job_lock_.Release(); |
| return; |
| } |
| job_lock_.Release(); |
| |
| // Decompress data and send it to JavaScript. Sending data is done in chunks |
| // depending on how many bytes VolumeArchive::ReadData returns. |
| int64_t left_length = length; |
| while (left_length > 0) { |
| const char* destination_buffer = nullptr; |
| int64_t read_bytes = |
| volume_archive_->ReadData(offset, left_length, &destination_buffer); |
| |
| if (read_bytes < 0) { |
| // Error messages should be sent to the read request (request_id), not |
| // open request (open_request_id), as the last one has finished and this |
| // is a read file. |
| message_sender_->SendFileSystemError(file_system_id_, request_id, |
| volume_archive_->error_message()); |
| |
| // Should not cleanup VolumeArchive as Volume::CloseFile will be called in |
| // case of failure. |
| return; |
| } |
| |
| // Send response back to ReadFile request. |
| pp::VarArrayBuffer array_buffer(read_bytes); |
| if (read_bytes > 0) { |
| char* array_buffer_data = static_cast<char*>(array_buffer.Map()); |
| memcpy(array_buffer_data, destination_buffer, read_bytes); |
| array_buffer.Unmap(); |
| } |
| |
| bool has_more_data = left_length - read_bytes > 0 && read_bytes > 0; |
| message_sender_->SendReadFileDone(file_system_id_, request_id, array_buffer, |
| has_more_data); |
| |
| if (read_bytes == 0) |
| break; // No more available data. |
| |
| left_length -= read_bytes; |
| offset += read_bytes; |
| } |
| volume_archive_->MaybeDecompressAhead(); |
| } |
| |
| void Volume::ClearJob() { |
| job_lock_.Acquire(); |
| reader_request_id_ = ""; |
| job_lock_.Release(); |
| } |