blob: bbb5f1fd46697d160c953d07fdd7900d2c852cd0 [file] [log] [blame]
// 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();
}