From d2681d2ba1b18c719fa606d19d77a16f1cd80577 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 12 Aug 2015 15:18:31 +0800 Subject: [PATCH] Implement protocol.registerFileProtocol --- atom/browser/api/atom_api_protocol.cc | 5 +- atom/browser/lib/chrome-extension.coffee | 16 +- .../browser/net/asar/asar_protocol_handler.cc | 39 +-- atom/browser/net/asar/url_request_asar_job.cc | 271 +++++++++++++++--- atom/browser/net/asar/url_request_asar_job.h | 68 ++++- .../browser/net/url_request_async_asar_job.cc | 40 +++ atom/browser/net/url_request_async_asar_job.h | 30 ++ filenames.gypi | 2 + 8 files changed, 383 insertions(+), 88 deletions(-) create mode 100644 atom/browser/net/url_request_async_asar_job.cc create mode 100644 atom/browser/net/url_request_async_asar_job.h diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 18a3ccca57e7..0bd6728b4cf7 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -8,6 +8,7 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/net/url_request_string_job.h" +#include "atom/browser/net/url_request_async_asar_job.h" #include "atom/common/native_mate_converters/callback.h" #include "native_mate/dictionary.h" @@ -46,7 +47,9 @@ mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( return mate::ObjectTemplateBuilder(isolate) .SetMethod("registerStandardSchemes", &Protocol::RegisterStandardSchemes) .SetMethod("registerStringProtocol", - &Protocol::RegisterProtocol); + &Protocol::RegisterProtocol) + .SetMethod("registerFileProtocol", + &Protocol::RegisterProtocol); } void Protocol::RegisterStandardSchemes( diff --git a/atom/browser/lib/chrome-extension.coffee b/atom/browser/lib/chrome-extension.coffee index c29df5d96f66..ccb52d37a4b4 100644 --- a/atom/browser/lib/chrome-extension.coffee +++ b/atom/browser/lib/chrome-extension.coffee @@ -64,14 +64,16 @@ app.once 'ready', -> catch e # The chrome-extension: can map a extension URL request to real file path. - # protocol.registerProtocol 'chrome-extension', (request) -> - # parsed = url.parse request.url - # return unless parsed.hostname and parsed.path? - # return unless /extension-\d+/.test parsed.hostname + chromeExtensionHandler = (request, callback) -> + parsed = url.parse request.url + return callback() unless parsed.hostname and parsed.path? + return callback() unless /extension-\d+/.test parsed.hostname - # directory = getPathForHost parsed.hostname - # return unless directory? - # return new protocol.RequestFileJob(path.join(directory, parsed.path)) + directory = getPathForHost parsed.hostname + return callback() unless directory? + callback path.join(directory, parsed.path) + protocol.registerFileProtocol 'chrome-extension', chromeExtensionHandler, -> + console.error 'Unable to register chrome-extension protocol' BrowserWindow::_loadDevToolsExtensions = (extensionInfoArray) -> @devToolsWebContents?.executeJavaScript "DevToolsAPI.addExtensions(#{JSON.stringify(extensionInfoArray)});" diff --git a/atom/browser/net/asar/asar_protocol_handler.cc b/atom/browser/net/asar/asar_protocol_handler.cc index 6d2a2cd5bf0a..324f8339c8c9 100644 --- a/atom/browser/net/asar/asar_protocol_handler.cc +++ b/atom/browser/net/asar/asar_protocol_handler.cc @@ -5,45 +5,11 @@ #include "atom/browser/net/asar/asar_protocol_handler.h" #include "atom/browser/net/asar/url_request_asar_job.h" -#include "atom/common/asar/archive.h" -#include "atom/common/asar/asar_util.h" #include "net/base/filename_util.h" #include "net/base/net_errors.h" -#include "net/url_request/url_request_error_job.h" -#include "net/url_request/url_request_file_job.h" namespace asar { -// static -net::URLRequestJob* CreateJobFromPath( - const base::FilePath& full_path, - net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const scoped_refptr file_task_runner) { - // Create asar:// job when the path contains "xxx.asar/", otherwise treat the - // URL request as file://. - base::FilePath asar_path, relative_path; - if (!GetAsarArchivePath(full_path, &asar_path, &relative_path)) - return new net::URLRequestFileJob(request, network_delegate, full_path, - file_task_runner); - - std::shared_ptr archive = GetOrCreateAsarArchive(asar_path); - Archive::FileInfo file_info; - if (!archive || !archive->GetFileInfo(relative_path, &file_info)) - return new net::URLRequestErrorJob(request, network_delegate, - net::ERR_FILE_NOT_FOUND); - - if (file_info.unpacked) { - base::FilePath real_path; - archive->CopyFileOut(relative_path, &real_path); - return new net::URLRequestFileJob(request, network_delegate, real_path, - file_task_runner); - } - - return new URLRequestAsarJob(request, network_delegate, archive, - relative_path, file_info, file_task_runner); -} - AsarProtocolHandler::AsarProtocolHandler( const scoped_refptr& file_task_runner) : file_task_runner_(file_task_runner) {} @@ -56,8 +22,9 @@ net::URLRequestJob* AsarProtocolHandler::MaybeCreateJob( net::NetworkDelegate* network_delegate) const { base::FilePath full_path; net::FileURLToFilePath(request->url(), &full_path); - return CreateJobFromPath(full_path, request, network_delegate, - file_task_runner_); + URLRequestAsarJob* job = new URLRequestAsarJob(request, network_delegate); + job->Initialize(file_task_runner_, full_path); + return job; } 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 477c6610b1b1..33cb04c9ede8 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -6,47 +6,126 @@ #include +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "atom/common/asar/archive.h" +#include "atom/common/asar/asar_util.h" #include "net/base/file_stream.h" +#include "net/base/filename_util.h" #include "net/base/io_buffer.h" +#include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" +#include "net/filter/filter.h" +#include "net/http/http_util.h" #include "net/url_request/url_request_status.h" +#if defined(OS_WIN) +#include "base/win/shortcut.h" +#endif + namespace asar { +URLRequestAsarJob::FileMetaInfo::FileMetaInfo() + : file_size(0), + mime_type_result(false), + file_exists(false), + is_directory(false) { +} + URLRequestAsarJob::URLRequestAsarJob( net::URLRequest* request, - net::NetworkDelegate* network_delegate, - std::shared_ptr archive, - const base::FilePath& file_path, - const Archive::FileInfo& file_info, - const scoped_refptr& file_task_runner) + net::NetworkDelegate* network_delegate) : net::URLRequestJob(request, network_delegate), - archive_(archive), - file_path_(file_path), - file_info_(file_info), - stream_(new net::FileStream(file_task_runner)), + type_(TYPE_ERROR), remaining_bytes_(0), - file_task_runner_(file_task_runner), weak_ptr_factory_(this) {} URLRequestAsarJob::~URLRequestAsarJob() {} -void URLRequestAsarJob::Start() { - remaining_bytes_ = static_cast(file_info_.size); +void URLRequestAsarJob::Initialize( + const scoped_refptr file_task_runner, + const base::FilePath& file_path) { + // Determine whether it is an asar file. + base::FilePath asar_path, relative_path; + if (!GetAsarArchivePath(file_path, &asar_path, &relative_path)) { + InitializeFileJob(file_task_runner, file_path); + return; + } - int flags = base::File::FLAG_OPEN | - base::File::FLAG_READ | - base::File::FLAG_ASYNC; - int rv = stream_->Open(archive_->path(), flags, - base::Bind(&URLRequestAsarJob::DidOpen, - weak_ptr_factory_.GetWeakPtr())); - if (rv != net::ERR_IO_PENDING) - DidOpen(rv); + std::shared_ptr archive = GetOrCreateAsarArchive(asar_path); + Archive::FileInfo file_info; + if (!archive || !archive->GetFileInfo(relative_path, &file_info)) { + type_ = TYPE_ERROR; + return; + } + + if (file_info.unpacked) { + base::FilePath real_path; + archive->CopyFileOut(relative_path, &real_path); + InitializeFileJob(file_task_runner, real_path); + return; + } + + InitializeAsarJob(file_task_runner, archive, relative_path, file_info); +} + +void URLRequestAsarJob::InitializeAsarJob( + const scoped_refptr file_task_runner, + std::shared_ptr archive, + const base::FilePath& file_path, + const Archive::FileInfo& file_info) { + type_ = TYPE_ASAR; + file_task_runner_ = file_task_runner; + stream_.reset(new net::FileStream(file_task_runner_)); + archive_ = archive; + file_path_ = file_path; + file_info_ = file_info; +} + +void URLRequestAsarJob::InitializeFileJob( + const scoped_refptr file_task_runner, + const base::FilePath& file_path) { + type_ = TYPE_FILE; + file_task_runner_ = file_task_runner; + stream_.reset(new net::FileStream(file_task_runner_)); + file_path_ = file_path; +} + +void URLRequestAsarJob::Start() { + if (type_ == TYPE_ASAR) { + remaining_bytes_ = static_cast(file_info_.size); + + int flags = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_ASYNC; + int rv = stream_->Open(archive_->path(), flags, + base::Bind(&URLRequestAsarJob::DidOpen, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + DidOpen(rv); + } else if (type_ == TYPE_FILE) { + FileMetaInfo* meta_info = new FileMetaInfo(); + file_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&URLRequestAsarJob::FetchMetaInfo, file_path_, + base::Unretained(meta_info)), + base::Bind(&URLRequestAsarJob::DidFetchMetaInfo, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(meta_info))); + } else { + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FILE_NOT_FOUND)); + } } void URLRequestAsarJob::Kill() { + stream_.reset(); weak_ptr_factory_.InvalidateWeakPtrs(); + URLRequestJob::Kill(); } @@ -85,8 +164,97 @@ bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, return false; } +bool URLRequestAsarJob::IsRedirectResponse(GURL* location, + int* http_status_code) { + if (type_ != TYPE_FILE) + return false; +#if defined(OS_WIN) + // Follow a Windows shortcut. + // We just resolve .lnk file, ignore others. + if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk")) + return false; + + base::FilePath new_path = file_path_; + bool resolved; + resolved = base::win::ResolveShortcut(new_path, &new_path, NULL); + + // If shortcut is not resolved succesfully, do not redirect. + if (!resolved) + return false; + + *location = net::FilePathToFileURL(new_path); + *http_status_code = 301; + return true; +#else + return false; +#endif +} + +net::Filter* URLRequestAsarJob::SetupFilter() const { + // Bug 9936 - .svgz files needs to be decompressed. + return LowerCaseEqualsASCII(file_path_.Extension(), ".svgz") + ? net::Filter::GZipFactory() : NULL; +} + bool URLRequestAsarJob::GetMimeType(std::string* mime_type) const { - return net::GetMimeTypeFromFile(file_path_, mime_type); + if (type_ == TYPE_ASAR) { + return net::GetMimeTypeFromFile(file_path_, mime_type); + } else { + if (meta_info_.mime_type_result) { + *mime_type = meta_info_.mime_type; + return true; + } + return false; + } +} + +void URLRequestAsarJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { + // We only care about "Range" header here. + std::vector ranges; + if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + if (ranges.size() == 1) { + byte_range_ = ranges[0]; + } else { + NotifyDone(net::URLRequestStatus( + net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + } + } + } +} + +void URLRequestAsarJob::FetchMetaInfo(const base::FilePath& file_path, + FileMetaInfo* meta_info) { + base::File::Info file_info; + meta_info->file_exists = base::GetFileInfo(file_path, &file_info); + if (meta_info->file_exists) { + meta_info->file_size = file_info.size; + meta_info->is_directory = file_info.is_directory; + } + // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be + // done in WorkerPool. + meta_info->mime_type_result = + net::GetMimeTypeFromFile(file_path, &meta_info->mime_type); +} + +void URLRequestAsarJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { + meta_info_ = *meta_info; + if (!meta_info_.file_exists || meta_info_.is_directory) { + DidOpen(net::ERR_FILE_NOT_FOUND); + return; + } + + int flags = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_ASYNC; + int rv = stream_->Open(file_path_, flags, + base::Bind(&URLRequestAsarJob::DidOpen, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + DidOpen(rv); } void URLRequestAsarJob::DidOpen(int result) { @@ -95,24 +263,59 @@ void URLRequestAsarJob::DidOpen(int result) { return; } - int rv = stream_->Seek(base::File::FROM_BEGIN, - file_info_.offset, - base::Bind(&URLRequestAsarJob::DidSeek, - weak_ptr_factory_.GetWeakPtr())); - if (rv != net::ERR_IO_PENDING) { - // stream_->Seek() failed, so pass an intentionally erroneous value - // into DidSeek(). - DidSeek(-1); + if (type_ == TYPE_ASAR) { + int rv = stream_->Seek(base::File::FROM_BEGIN, + file_info_.offset, + base::Bind(&URLRequestAsarJob::DidSeek, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) { + // stream_->Seek() failed, so pass an intentionally erroneous value + // into DidSeek(). + DidSeek(-1); + } + } else { + if (!byte_range_.ComputeBounds(meta_info_.file_size)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + + if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) { + int rv = stream_->Seek(base::File::FROM_BEGIN, + byte_range_.first_byte_position(), + base::Bind(&URLRequestAsarJob::DidSeek, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) { + // stream_->Seek() failed, so pass an intentionally erroneous value + // into DidSeek(). + DidSeek(-1); + } + } else { + // We didn't need to call stream_->Seek() at all, so we pass to DidSeek() + // the value that would mean seek success. This way we skip the code + // handling seek failure. + DidSeek(byte_range_.first_byte_position()); + } } } void URLRequestAsarJob::DidSeek(int64 result) { - if (result != static_cast(file_info_.offset)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); - return; + if (type_ == TYPE_ASAR) { + if (result != static_cast(file_info_.offset)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } + } else { + if (result != byte_range_.first_byte_position()) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } } - set_expected_content_size(remaining_bytes_); NotifyHeadersComplete(); } diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index adcac85b37d7..15a723d79e89 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -8,10 +8,12 @@ #include #include +#include "atom/browser/net/js_asker.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/http/http_byte_range.h" #include "net/url_request/url_request_job.h" namespace base { @@ -34,11 +36,20 @@ net::URLRequestJob* CreateJobFromPath( class URLRequestAsarJob : public net::URLRequestJob { public: URLRequestAsarJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - std::shared_ptr archive, - const base::FilePath& file_path, - const Archive::FileInfo& file_info, - const scoped_refptr& file_task_runner); + net::NetworkDelegate* network_delegate); + + void Initialize(const scoped_refptr file_task_runner, + const base::FilePath& file_path); + + protected: + virtual ~URLRequestAsarJob(); + + void InitializeAsarJob(const scoped_refptr file_task_runner, + std::shared_ptr archive, + const base::FilePath& file_path, + const Archive::FileInfo& file_info); + void InitializeFileJob(const scoped_refptr file_task_runner, + const base::FilePath& file_path); // net::URLRequestJob: void Start() override; @@ -46,12 +57,39 @@ class URLRequestAsarJob : public net::URLRequestJob { bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override; + bool IsRedirectResponse(GURL* location, int* http_status_code) override; + net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; - - protected: - virtual ~URLRequestAsarJob(); + void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override; private: + // Meta information about the file. It's used as a member in the + // URLRequestFileJob and also passed between threads because disk access is + // necessary to obtain it. + struct FileMetaInfo { + FileMetaInfo(); + + // Size of the file. + int64 file_size; + // Mime type associated with the file. + std::string mime_type; + // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether + // obtaining of the mime type was successful. + bool mime_type_result; + // Flag showing whether the file exists. + bool file_exists; + // Flag showing whether the file name actually refers to a directory. + bool is_directory; + }; + + // Fetches file info on a background thread. + static void FetchMetaInfo(const base::FilePath& file_path, + FileMetaInfo* meta_info); + + // Callback after fetching file info on a background thread. + void DidFetchMetaInfo(const FileMetaInfo* meta_info); + + // Callback after opening file on a background thread. void DidOpen(int result); @@ -62,14 +100,24 @@ class URLRequestAsarJob : public net::URLRequestJob { // Callback after data is asynchronously read from the file into |buf|. void DidRead(scoped_refptr buf, int result); + // The type of this job. + enum JobType { + TYPE_ERROR, + TYPE_ASAR, + TYPE_FILE, + }; + JobType type_; + std::shared_ptr archive_; base::FilePath file_path_; Archive::FileInfo file_info_; scoped_ptr stream_; - int64 remaining_bytes_; + FileMetaInfo meta_info_; + scoped_refptr file_task_runner_; - const scoped_refptr file_task_runner_; + net::HttpByteRange byte_range_; + int64 remaining_bytes_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc new file mode 100644 index 000000000000..eb6697b76f50 --- /dev/null +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -0,0 +1,40 @@ +// 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 "atom/browser/net/url_request_async_asar_job.h" + +namespace atom { + +UrlRequestAsyncAsarJob::UrlRequestAsyncAsarJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + v8::Isolate* isolate, + const JavaScriptHandler& handler) + : JsAsker(request, network_delegate, isolate, + handler) { +} + +void UrlRequestAsyncAsarJob::StartAsync(scoped_ptr options) { + base::FilePath::StringType file_path; + if (options->IsType(base::Value::TYPE_DICTIONARY)) { + static_cast(options.get())->GetString( + "path", &file_path); + } else if (options->IsType(base::Value::TYPE_STRING)) { + options->GetAsString(&file_path); + } + + if (file_path.empty()) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); + } else { + asar::URLRequestAsarJob::Initialize( + content::BrowserThread::GetBlockingPool()-> + GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN), + base::FilePath(file_path)); + asar::URLRequestAsarJob::Start(); + } +} + +} // namespace atom diff --git a/atom/browser/net/url_request_async_asar_job.h b/atom/browser/net/url_request_async_asar_job.h new file mode 100644 index 000000000000..0b9676e7f368 --- /dev/null +++ b/atom/browser/net/url_request_async_asar_job.h @@ -0,0 +1,30 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ +#define ATOM_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ + +#include "atom/browser/net/asar/url_request_asar_job.h" +#include "atom/browser/net/js_asker.h" + +namespace atom { + +// Like URLRequestAsarJob, but asks the JavaScript handler for file path. +class UrlRequestAsyncAsarJob : public JsAsker { + public: + UrlRequestAsyncAsarJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + v8::Isolate* isolate, + const JavaScriptHandler& handler); + + // JsAsker: + void StartAsync(scoped_ptr options) override; + + private: + DISALLOW_COPY_AND_ASSIGN(UrlRequestAsyncAsarJob); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ diff --git a/filenames.gypi b/filenames.gypi index 2dc110ef8594..260c9a4c8a17 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -156,6 +156,8 @@ 'atom/browser/net/http_protocol_handler.h', 'atom/browser/net/js_asker.cc', 'atom/browser/net/js_asker.h', + 'atom/browser/net/url_request_async_asar_job.cc', + 'atom/browser/net/url_request_async_asar_job.h', 'atom/browser/net/url_request_string_job.cc', 'atom/browser/net/url_request_string_job.h', 'atom/browser/net/url_request_buffer_job.cc',