From 6d712da7e3b15eae722f378b54cf9c32e856e0db Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 23 Sep 2014 19:14:30 +0800 Subject: [PATCH] Read the archive's header when there is a url request --- atom.gyp | 6 + .../browser/net/asar/asar_protocol_handler.cc | 53 ++++++-- atom/browser/net/asar/url_request_asar_job.cc | 54 ++++++++ atom/browser/net/asar/url_request_asar_job.h | 51 +++++++ atom/common/asar/archive.cc | 127 ++++++++++++++++++ atom/common/asar/archive.h | 44 ++++++ 6 files changed, 326 insertions(+), 9 deletions(-) create mode 100644 atom/common/asar/archive.cc create mode 100644 atom/common/asar/archive.h diff --git a/atom.gyp b/atom.gyp index 4bb3de978ce1..8a47a02a5885 100644 --- a/atom.gyp +++ b/atom.gyp @@ -121,6 +121,10 @@ 'atom/browser/native_window_observer.h', 'atom/browser/net/adapter_request_job.cc', 'atom/browser/net/adapter_request_job.h', + 'atom/browser/net/asar/asar_protocol_handler.cc', + 'atom/browser/net/asar/asar_protocol_handler.h', + 'atom/browser/net/asar/url_request_asar_job.cc', + 'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/atom_url_request_job_factory.cc', 'atom/browser/net/atom_url_request_job_factory.h', 'atom/browser/net/url_request_string_job.cc', @@ -186,6 +190,8 @@ 'atom/common/api/atom_bindings.h', 'atom/common/api/object_life_monitor.cc', 'atom/common/api/object_life_monitor.h', + 'atom/common/asar/archive.cc', + 'atom/common/asar/archive.h', 'atom/common/common_message_generator.cc', 'atom/common/common_message_generator.h', 'atom/common/crash_reporter/crash_reporter.cc', diff --git a/atom/browser/net/asar/asar_protocol_handler.cc b/atom/browser/net/asar/asar_protocol_handler.cc index 5bc93d8dd002..a1cbfd7f7e8a 100644 --- a/atom/browser/net/asar/asar_protocol_handler.cc +++ b/atom/browser/net/asar/asar_protocol_handler.cc @@ -4,26 +4,61 @@ #include "atom/browser/net/asar/asar_protocol_handler.h" +#include "atom/browser/net/asar/url_request_asar_job.h" #include "net/base/filename_util.h" #include "net/url_request/url_request_file_job.h" namespace asar { -AsarProtocolHandler::AsarProtocolHandler( - const scoped_refptr& file_task_runner) - : file_task_runner_(file_task_runner) { +namespace { + +const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar"); + +// Get the relative path in asar archive. +bool GetAsarPath(const base::FilePath& full_path, + base::FilePath* asar_path, + base::FilePath* relative_path) { + base::FilePath iter = full_path; + while (true) { + base::FilePath dirname = iter.DirName(); + if (iter.MatchesExtension(kAsarExtension)) + break; + else if (iter == dirname) + return false; + iter = dirname; + } + + base::FilePath tail; + if (!iter.AppendRelativePath(full_path, &tail)) + return false; + + *asar_path = iter; + *relative_path = tail; + return true; } -AsarProtocolHandler::~AsarProtocolHandler() { -} +} // namespace + +AsarProtocolHandler::AsarProtocolHandler( + const scoped_refptr& file_task_runner) + : file_task_runner_(file_task_runner) {} + +AsarProtocolHandler::~AsarProtocolHandler() {} net::URLRequestJob* AsarProtocolHandler::MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const { - base::FilePath file_path; - net::FileURLToFilePath(request->url(), &file_path); - return new net::URLRequestFileJob(request, network_delegate, file_path, - file_task_runner_); + base::FilePath full_path; + net::FileURLToFilePath(request->url(), &full_path); + + // Create asar:// job when the path contains "xxx.asar/". + base::FilePath asar_path, relative_path; + if (GetAsarPath(full_path, &asar_path, &relative_path)) + return new URLRequestAsarJob(request, network_delegate, asar_path, + relative_path, file_task_runner_); + else + return new net::URLRequestFileJob(request, network_delegate, full_path, + file_task_runner_); } bool AsarProtocolHandler::IsSafeRedirectTarget(const GURL& location) const { diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index e69de29bb2d1..a2ebd04c872c 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/net/asar/url_request_asar_job.h" + +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_status.h" + +namespace asar { + +URLRequestAsarJob::URLRequestAsarJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& asar_path, + const base::FilePath& file_path, + const scoped_refptr& file_task_runner) + : net::URLRequestJob(request, network_delegate), + archive_(asar_path), + file_path_(file_path), + file_task_runner_(file_task_runner), + weak_ptr_factory_(this) {} + +URLRequestAsarJob::~URLRequestAsarJob() {} + +void URLRequestAsarJob::Start() { + Archive::FileInfo info; + if (!archive_.Init() || !archive_.GetFileInfo(file_path_, &info)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FILE_NOT_FOUND)); + return; + } + + NotifyHeadersComplete(); +} + +void URLRequestAsarJob::Kill() { + weak_ptr_factory_.InvalidateWeakPtrs(); + URLRequestJob::Kill(); +} + +bool URLRequestAsarJob::ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) { + *bytes_read = 0; + return true; +} + +bool URLRequestAsarJob::GetMimeType(std::string* mime_type) const { + return net::GetMimeTypeFromFile(file_path_, mime_type); +} + +} // namespace asar diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index e69de29bb2d1..75653f282f11 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -0,0 +1,51 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_ +#define ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_ + +#include "atom/common/asar/archive.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/url_request/url_request_job.h" + +namespace base { +class TaskRunner; +} + +namespace asar { + +class URLRequestAsarJob : public net::URLRequestJob { + public: + URLRequestAsarJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& asar_path, + const base::FilePath& file_path, + const scoped_refptr& file_task_runner); + + // net::URLRequestJob: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + + protected: + virtual ~URLRequestAsarJob(); + + private: + Archive archive_; + base::FilePath file_path_; + const scoped_refptr file_task_runner_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestAsarJob); +}; + +} // namespace asar + +#endif // ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_ diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc new file mode 100644 index 000000000000..28116d69908c --- /dev/null +++ b/atom/common/asar/archive.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/asar/archive.h" + +#include +#include + +#include "base/files/file.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/json/json_string_value_serializer.h" +#include "base/strings/string_number_conversions.h" + +namespace asar { + +namespace { + +bool GetChildNode(const std::string& name, + const base::DictionaryValue* root, + const base::DictionaryValue** out) { + const base::DictionaryValue* files = NULL; + return root->GetDictionaryWithoutPathExpansion("files", &files) && + files->GetDictionaryWithoutPathExpansion(name, out); +} + +bool GetNodeFromPath(std::string path, + const base::DictionaryValue* root, + const base::DictionaryValue** out) { + for (size_t delimiter_position = path.find('/'); + delimiter_position != std::string::npos; + delimiter_position = path.find('/')) { + const base::DictionaryValue* child = NULL; + if (!GetChildNode(path.substr(0, delimiter_position), root, &child)) + return false; + + root = child; + path.erase(0, delimiter_position + 1); + } + + return GetChildNode(path, root, out); +} + +} // namespace + +Archive::Archive(const base::FilePath& path) : path_(path) { +} + +Archive::~Archive() { +} + +bool Archive::Init() { + base::File file(path_, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) { + PLOG(ERROR) << "Unable to open " << path_.value(); + return false; + } + + std::vector buf; + int len; + + buf.resize(8); + len = file.ReadAtCurrentPos(buf.data(), buf.size()); + if (len != static_cast(buf.size())) { + PLOG(ERROR) << "Failed to read header size from " << path_.value(); + return false; + } + + uint32 size; + if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadUInt32(&size)) { + LOG(ERROR) << "Failed to parse header size from " << path_.value(); + return false; + } + + buf.resize(size); + len = file.ReadAtCurrentPos(buf.data(), buf.size()); + if (len != static_cast(buf.size())) { + PLOG(ERROR) << "Failed to read header from " << path_.value(); + return false; + } + + std::string header; + if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadString(&header)) { + LOG(ERROR) << "Failed to parse header from " << path_.value(); + return false; + } + + std::string error; + JSONStringValueSerializer serializer(&header); + base::Value* value = serializer.Deserialize(NULL, &error); + if (!value || !value->IsType(base::Value::TYPE_DICTIONARY)) { + LOG(ERROR) << "Failed to parse header: " << error; + return false; + } + + header_.reset(static_cast(value)); + return true; +} + +bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) { + if (!header_) + return false; + + const base::DictionaryValue* node; + if (!GetNodeFromPath(path.AsUTF8Unsafe(), header_.get(), &node)) + return false; + + std::string link; + if (node->GetString("link", &link)) + return GetFileInfo(base::FilePath::FromUTF8Unsafe(link), info); + + std::string offset; + if (!node->GetString("offset", &offset)) + return false; + if (!base::StringToUint64(offset, &info->offset)) + return false; + + int size; + if (!node->GetInteger("size", &size)) + return false; + info->size = static_cast(size); + + return true; +} + +} // namespace asar diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h new file mode 100644 index 000000000000..ef11eadf40a4 --- /dev/null +++ b/atom/common/asar/archive.h @@ -0,0 +1,44 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_ASAR_ARCHIVE_H_ +#define ATOM_COMMON_ASAR_ARCHIVE_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +} + +namespace asar { + +class Archive { + public: + struct FileInfo { + uint32 size; + uint64 offset; + }; + + explicit Archive(const base::FilePath& path); + virtual ~Archive(); + + // Read and parse the header. + bool Init(); + + // Get the info of a file. + bool GetFileInfo(const base::FilePath& path, FileInfo* info); + + base::FilePath path() const { return path_; } + + private: + base::FilePath path_; + scoped_ptr header_; + + DISALLOW_COPY_AND_ASSIGN(Archive); +}; + +} // namespace asar + +#endif // ATOM_COMMON_ASAR_ARCHIVE_H_