// Copyright (c) 2014 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include #include "base/numerics/safe_math.h" #include "gin/handle.h" #include "gin/object_template_builder.h" #include "gin/wrappable.h" #include "shell/common/asar/archive.h" #include "shell/common/asar/asar_util.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/error_thrower.h" #include "shell/common/gin_helper/function_template_extensions.h" #include "shell/common/gin_helper/promise.h" #include "shell/common/node_includes.h" #include "shell/common/node_util.h" namespace { class Archive : public gin::Wrappable { public: static gin::Handle Create(v8::Isolate* isolate, const base::FilePath& path) { auto archive = std::make_unique(path); if (!archive->Init()) return gin::Handle(); return gin::CreateHandle(isolate, new Archive(isolate, std::move(archive))); } // gin::Wrappable static gin::WrapperInfo kWrapperInfo; gin::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override { return gin::ObjectTemplateBuilder(isolate) .SetProperty("path", &Archive::GetPath) .SetMethod("getFileInfo", &Archive::GetFileInfo) .SetMethod("stat", &Archive::Stat) .SetMethod("readdir", &Archive::Readdir) .SetMethod("realpath", &Archive::Realpath) .SetMethod("copyFileOut", &Archive::CopyFileOut) .SetMethod("read", &Archive::Read) .SetMethod("readSync", &Archive::ReadSync); } const char* GetTypeName() override { return "Archive"; } protected: Archive(v8::Isolate* isolate, std::unique_ptr archive) : archive_(std::move(archive)) {} // Returns the path of the file. base::FilePath GetPath() { return archive_->path(); } // Reads the offset and size of file. v8::Local GetFileInfo(v8::Isolate* isolate, const base::FilePath& path) { asar::Archive::FileInfo info; if (!archive_ || !archive_->GetFileInfo(path, &info)) return v8::False(isolate); gin_helper::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("size", info.size); dict.Set("unpacked", info.unpacked); dict.Set("offset", info.offset); return dict.GetHandle(); } // Returns a fake result of fs.stat(path). v8::Local Stat(v8::Isolate* isolate, const base::FilePath& path) { asar::Archive::Stats stats; if (!archive_ || !archive_->Stat(path, &stats)) return v8::False(isolate); gin_helper::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("size", stats.size); dict.Set("offset", stats.offset); dict.Set("isFile", stats.is_file); dict.Set("isDirectory", stats.is_directory); dict.Set("isLink", stats.is_link); return dict.GetHandle(); } // Returns all files under a directory. v8::Local Readdir(v8::Isolate* isolate, const base::FilePath& path) { std::vector files; if (!archive_ || !archive_->Readdir(path, &files)) return v8::False(isolate); return gin::ConvertToV8(isolate, files); } // Returns the path of file with symbol link resolved. v8::Local Realpath(v8::Isolate* isolate, const base::FilePath& path) { base::FilePath realpath; if (!archive_ || !archive_->Realpath(path, &realpath)) return v8::False(isolate); return gin::ConvertToV8(isolate, realpath); } // Copy the file out into a temporary file and returns the new path. v8::Local CopyFileOut(v8::Isolate* isolate, const base::FilePath& path) { base::FilePath new_path; if (!archive_ || !archive_->CopyFileOut(path, &new_path)) return v8::False(isolate); return gin::ConvertToV8(isolate, new_path); } v8::Local ReadSync(gin_helper::ErrorThrower thrower, uint64_t offset, uint64_t length) { base::CheckedNumeric safe_offset(offset); base::CheckedNumeric safe_end = safe_offset + length; if (!safe_end.IsValid() || safe_end.ValueOrDie() > archive_->file()->length()) { thrower.ThrowError("Out of bounds read"); return v8::Local(); } auto array_buffer = v8::ArrayBuffer::New(thrower.isolate(), length); auto backing_store = array_buffer->GetBackingStore(); memcpy(backing_store->Data(), archive_->file()->data() + offset, length); return array_buffer; } v8::Local Read(v8::Isolate* isolate, uint64_t offset, uint64_t length) { gin_helper::Promise> promise(isolate); v8::Local handle = promise.GetHandle(); base::CheckedNumeric safe_offset(offset); base::CheckedNumeric safe_end = safe_offset + length; if (!safe_end.IsValid() || safe_end.ValueOrDie() > archive_->file()->length()) { promise.RejectWithErrorMessage("Out of bounds read"); return handle; } auto backing_store = v8::ArrayBuffer::NewBackingStore(isolate, length); base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, base::BindOnce(&Archive::ReadOnIO, isolate, archive_, std::move(backing_store), offset, length), base::BindOnce(&Archive::ResolveReadOnUI, std::move(promise))); return handle; } private: static std::unique_ptr ReadOnIO( v8::Isolate* isolate, std::shared_ptr archive, std::unique_ptr backing_store, uint64_t offset, uint64_t length) { memcpy(backing_store->Data(), archive->file()->data() + offset, length); return backing_store; } static void ResolveReadOnUI( gin_helper::Promise> promise, std::unique_ptr backing_store) { v8::HandleScope scope(promise.isolate()); v8::Context::Scope context_scope(promise.GetContext()); auto array_buffer = v8::ArrayBuffer::New(promise.isolate(), std::move(backing_store)); promise.Resolve(array_buffer); } std::shared_ptr archive_; DISALLOW_COPY_AND_ASSIGN(Archive); }; // static gin::WrapperInfo Archive::kWrapperInfo = {gin::kEmbedderNativeGin}; void InitAsarSupport(v8::Isolate* isolate, v8::Local require) { // Evaluate asar_bundle.js. std::vector> asar_bundle_params = { node::FIXED_ONE_BYTE_STRING(isolate, "require")}; std::vector> asar_bundle_args = {require}; electron::util::CompileAndCall( isolate->GetCurrentContext(), "electron/js2c/asar_bundle", &asar_bundle_params, &asar_bundle_args, nullptr); } v8::Local SplitPath(v8::Isolate* isolate, const base::FilePath& path) { gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate); base::FilePath asar_path, file_path; if (asar::GetAsarArchivePath(path, &asar_path, &file_path, true)) { dict.Set("isAsar", true); dict.Set("asarPath", asar_path); dict.Set("filePath", file_path); } else { dict.Set("isAsar", false); } return dict.GetHandle(); } void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { gin_helper::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createArchive", &Archive::Create); dict.SetMethod("splitPath", &SplitPath); dict.SetMethod("initAsarSupport", &InitAsarSupport); } } // namespace NODE_LINKED_MODULE_CONTEXT_AWARE(electron_common_asar, Initialize)