// Copyright (c) 2015 The Chromium Embedded Framework 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 "include/wrapper/cef_resource_manager.h" #include #include #include "include/base/cef_macros.h" #include "include/base/cef_weak_ptr.h" #include "include/cef_parser.h" #include "include/wrapper/cef_stream_resource_handler.h" #include "include/wrapper/cef_zip_archive.h" namespace { #if defined(OS_WIN) #define PATH_SEP '\\' #else #define PATH_SEP '/' #endif // Returns |url| without the query or fragment components, if any. std::string GetUrlWithoutQueryOrFragment(const std::string& url) { // Find the first instance of '?' or '#'. const size_t pos = std::min(url.find('?'), url.find('#')); if (pos != std::string::npos) return url.substr(0, pos); return url; } // Determine the mime type based on the |url| file extension. std::string GetMimeType(const std::string& url) { std::string mime_type; const std::string& url_without_query = GetUrlWithoutQueryOrFragment(url); size_t sep = url_without_query.find_last_of("."); if (sep != std::string::npos) { mime_type = CefGetMimeType(url_without_query.substr(sep + 1)); if (!mime_type.empty()) return mime_type; } return "text/html"; } // Default no-op filter. std::string GetFilteredUrl(const std::string& url) { return url; } // Provider of fixed contents. class ContentProvider : public CefResourceManager::Provider { public: ContentProvider(const std::string& url, const std::string& content, const std::string& mime_type) : url_(url), content_(content), mime_type_(mime_type) { DCHECK(!url.empty()); DCHECK(!content.empty()); } bool OnRequest(scoped_refptr request) OVERRIDE { CEF_REQUIRE_IO_THREAD(); const std::string& url = request->url(); if (url != url_) { // Not handled by this provider. return false; } CefRefPtr stream = CefStreamReader::CreateForData( static_cast(const_cast(content_.data())), content_.length()); // Determine the mime type a single time if it isn't already set. if (mime_type_.empty()) mime_type_ = request->mime_type_resolver().Run(url); request->Continue(new CefStreamResourceHandler(mime_type_, stream)); return true; } private: std::string url_; std::string content_; std::string mime_type_; DISALLOW_COPY_AND_ASSIGN(ContentProvider); }; // Provider of contents loaded from a directory on the file system. class DirectoryProvider : public CefResourceManager::Provider { public: DirectoryProvider(const std::string& url_path, const std::string& directory_path) : url_path_(url_path), directory_path_(directory_path) { DCHECK(!url_path_.empty()); DCHECK(!directory_path_.empty()); // Normalize the path values. if (url_path_[url_path_.size() - 1] != '/') url_path_ += '/'; if (directory_path_[directory_path_.size() - 1] != PATH_SEP) directory_path_ += PATH_SEP; } bool OnRequest(scoped_refptr request) OVERRIDE { CEF_REQUIRE_IO_THREAD(); const std::string& url = request->url(); if (url.find(url_path_) != 0U) { return false; } const std::string& file_path = GetFilePath(url); // Open |file_path| on the FILE thread. CefPostTask(TID_FILE, base::Bind(&DirectoryProvider::OpenOnFileThread, file_path, request)); return true; } private: std::string GetFilePath(const std::string& url) { std::string path_part = url.substr(url_path_.length()); #if defined(OS_WIN) std::replace(path_part.begin(), path_part.end(), '/', '\\'); #endif return directory_path_ + path_part; } static void OpenOnFileThread( const std::string& file_path, scoped_refptr request) { CEF_REQUIRE_FILE_THREAD(); CefRefPtr stream = CefStreamReader::CreateForFile(file_path); // Continue loading on the IO thread. CefPostTask(TID_IO, base::Bind(&DirectoryProvider::ContinueOpenOnIOThread, request, stream)); } static void ContinueOpenOnIOThread( scoped_refptr request, CefRefPtr stream) { CEF_REQUIRE_IO_THREAD(); CefRefPtr handler; if (stream.get()) { handler = new CefStreamResourceHandler( request->mime_type_resolver().Run(request->url()), stream); } request->Continue(handler); } std::string url_path_; std::string directory_path_; DISALLOW_COPY_AND_ASSIGN(DirectoryProvider); }; // Provider of contents loaded from an archive file. class ArchiveProvider : public CefResourceManager::Provider { public: ArchiveProvider(const std::string& url_path, const std::string& archive_path, const std::string& password) : url_path_(url_path), archive_path_(archive_path), password_(password), archive_load_started_(false), archive_load_ended_(false), ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { DCHECK(!url_path_.empty()); DCHECK(!archive_path_.empty()); // Normalize the path values. if (url_path_[url_path_.size() - 1] != '/') url_path_ += '/'; } bool OnRequest(scoped_refptr request) OVERRIDE { CEF_REQUIRE_IO_THREAD(); const std::string& url = request->url(); if (url.find(url_path_) != 0U) { // Not handled by this provider. return false; } if (!archive_load_started_) { // Initiate archive loading and queue the pending request. archive_load_started_ = true; pending_requests_.push_back(request); // Load the archive file on the FILE thread. CefPostTask(TID_FILE, base::Bind(&ArchiveProvider::LoadOnFileThread, weak_ptr_factory_.GetWeakPtr(), archive_path_, password_)); return true; } if (archive_load_started_ && !archive_load_ended_) { // The archive load has already started. Queue the pending request. pending_requests_.push_back(request); return true; } // Archive loading is done. return ContinueRequest(request); } private: static void LoadOnFileThread(base::WeakPtr ptr, const std::string& archive_path, const std::string& password) { CEF_REQUIRE_FILE_THREAD(); CefRefPtr archive; CefRefPtr stream = CefStreamReader::CreateForFile(archive_path); if (stream.get()) { archive = new CefZipArchive; if (archive->Load(stream, password, true) == 0) { DLOG(WARNING) << "Empty archive file: " << archive_path; archive = NULL; } } else { DLOG(WARNING) << "Failed to load archive file: " << archive_path; } CefPostTask(TID_IO, base::Bind(&ArchiveProvider::ContinueOnIOThread, ptr, archive)); } void ContinueOnIOThread(CefRefPtr archive) { CEF_REQUIRE_IO_THREAD(); archive_load_ended_ = true; archive_ = archive; if (!pending_requests_.empty()) { // Continue all pending requests. PendingRequests::const_iterator it = pending_requests_.begin(); for (; it != pending_requests_.end(); ++it) ContinueRequest(*it); pending_requests_.clear(); } } bool ContinueRequest(scoped_refptr request) { CefRefPtr handler; // |archive_| will be NULL if the archive file failed to load or was empty. if (archive_.get()) { const std::string& url = request->url(); const std::string& relative_path = url.substr(url_path_.length()); CefRefPtr file = archive_->GetFile(relative_path); if (file.get()) { handler = new CefStreamResourceHandler( request->mime_type_resolver().Run(url), file->GetStreamReader()); } } if (!handler.get()) return false; request->Continue(handler); return true; } std::string url_path_; std::string archive_path_; std::string password_; bool archive_load_started_; bool archive_load_ended_; CefRefPtr archive_; // List of requests that are pending while the archive is being loaded. typedef std::vector> PendingRequests; PendingRequests pending_requests_; // Must be the last member. base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(ArchiveProvider); }; } // namespace // CefResourceManager::ProviderEntry implementation. struct CefResourceManager::ProviderEntry { ProviderEntry(Provider* provider, int order, const std::string& identifier) : provider_(provider), order_(order), identifier_(identifier), deletion_pending_(false) {} scoped_ptr provider_; int order_; std::string identifier_; // List of pending requests currently associated with this provider. RequestList pending_requests_; // True if deletion of this provider is pending. bool deletion_pending_; }; // CefResourceManager::RequestState implementation. CefResourceManager::RequestState::~RequestState() { // Always execute the callback. if (callback_.get()) callback_->Continue(true); } // CefResourceManager::Request implementation. void CefResourceManager::Request::Continue( CefRefPtr handler) { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::Request::Continue, this, handler)); return; } if (!state_.get()) return; // Disassociate |state_| immediately so that Provider::OnRequestCanceled is // not called unexpectedly if Provider::OnRequest calls this method and then // calls CefResourceManager::Remove*. CefPostTask(TID_IO, base::Bind(&CefResourceManager::Request::ContinueOnIOThread, base::Passed(&state_), handler)); } void CefResourceManager::Request::Stop() { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::Request::Stop, this)); return; } if (!state_.get()) return; // Disassociate |state_| immediately so that Provider::OnRequestCanceled is // not called unexpectedly if Provider::OnRequest calls this method and then // calls CefResourceManager::Remove*. CefPostTask(TID_IO, base::Bind(&CefResourceManager::Request::StopOnIOThread, base::Passed(&state_))); } CefResourceManager::Request::Request(scoped_ptr state) : state_(state.Pass()), params_(state_->params_) { CEF_REQUIRE_IO_THREAD(); ProviderEntry* entry = *(state_->current_entry_pos_); // Should not be on a deleted entry. DCHECK(!entry->deletion_pending_); // Add this request to the entry's pending request list. entry->pending_requests_.push_back(this); state_->current_request_pos_ = --entry->pending_requests_.end(); } // Detaches and returns |state_| if the provider indicates that it will not // handle the request. Note that |state_| may already be NULL if OnRequest // executes a callback before returning, in which case execution will continue // asynchronously in any case. scoped_ptr CefResourceManager::Request::SendRequest() { CEF_REQUIRE_IO_THREAD(); Provider* provider = (*state_->current_entry_pos_)->provider_.get(); if (!provider->OnRequest(this)) return state_.Pass(); return scoped_ptr(); } bool CefResourceManager::Request::HasState() { CEF_REQUIRE_IO_THREAD(); return (state_.get() != NULL); } // static void CefResourceManager::Request::ContinueOnIOThread( scoped_ptr state, CefRefPtr handler) { CEF_REQUIRE_IO_THREAD(); // The manager may already have been deleted. base::WeakPtr manager = state->manager_; if (manager) manager->ContinueRequest(state.Pass(), handler); } // static void CefResourceManager::Request::StopOnIOThread( scoped_ptr state) { CEF_REQUIRE_IO_THREAD(); // The manager may already have been deleted. base::WeakPtr manager = state->manager_; if (manager) manager->StopRequest(state.Pass()); } // CefResourceManager implementation. CefResourceManager::CefResourceManager() : url_filter_(base::Bind(GetFilteredUrl)), mime_type_resolver_(base::Bind(GetMimeType)) {} CefResourceManager::~CefResourceManager() { CEF_REQUIRE_IO_THREAD(); RemoveAllProviders(); // Delete all entryies now. Requests may still be pending but they will not // call back into this manager due to the use of WeakPtr. if (!providers_.empty()) { ProviderEntryList::iterator it = providers_.begin(); for (; it != providers_.end(); ++it) delete *it; providers_.clear(); } } void CefResourceManager::AddContentProvider(const std::string& url, const std::string& content, const std::string& mime_type, int order, const std::string& identifier) { AddProvider(new ContentProvider(url, content, mime_type), order, identifier); } void CefResourceManager::AddDirectoryProvider(const std::string& url_path, const std::string& directory_path, int order, const std::string& identifier) { AddProvider(new DirectoryProvider(url_path, directory_path), order, identifier); } void CefResourceManager::AddArchiveProvider(const std::string& url_path, const std::string& archive_path, const std::string& password, int order, const std::string& identifier) { AddProvider(new ArchiveProvider(url_path, archive_path, password), order, identifier); } void CefResourceManager::AddProvider(Provider* provider, int order, const std::string& identifier) { DCHECK(provider); if (!provider) return; if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::AddProvider, this, provider, order, identifier)); return; } scoped_ptr new_entry( new ProviderEntry(provider, order, identifier)); if (providers_.empty()) { providers_.push_back(new_entry.release()); return; } // Insert before the first entry with a higher |order| value. ProviderEntryList::iterator it = providers_.begin(); for (; it != providers_.end(); ++it) { if ((*it)->order_ > order) break; } providers_.insert(it, new_entry.release()); } void CefResourceManager::RemoveProviders(const std::string& identifier) { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::RemoveProviders, this, identifier)); return; } if (providers_.empty()) return; ProviderEntryList::iterator it = providers_.begin(); while (it != providers_.end()) { if ((*it)->identifier_ == identifier) DeleteProvider(it, false); else ++it; } } void CefResourceManager::RemoveAllProviders() { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::RemoveAllProviders, this)); return; } if (providers_.empty()) return; ProviderEntryList::iterator it = providers_.begin(); while (it != providers_.end()) DeleteProvider(it, true); } void CefResourceManager::SetMimeTypeResolver(const MimeTypeResolver& resolver) { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::SetMimeTypeResolver, this, resolver)); return; } if (!resolver.is_null()) mime_type_resolver_ = resolver; else mime_type_resolver_ = base::Bind(GetMimeType); } void CefResourceManager::SetUrlFilter(const UrlFilter& filter) { if (!CefCurrentlyOn(TID_IO)) { CefPostTask(TID_IO, base::Bind(&CefResourceManager::SetUrlFilter, this, filter)); return; } if (!filter.is_null()) url_filter_ = filter; else url_filter_ = base::Bind(GetFilteredUrl); } cef_return_value_t CefResourceManager::OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) { CEF_REQUIRE_IO_THREAD(); // Find the first provider that is not pending deletion. ProviderEntryList::iterator current_entry_pos = providers_.begin(); GetNextValidProvider(current_entry_pos); if (current_entry_pos == providers_.end()) { // No providers so continue the request immediately. return RV_CONTINUE; } scoped_ptr state(new RequestState); if (!weak_ptr_factory_.get()) { // WeakPtrFactory instances need to be created and destroyed on the same // thread. This object performs most of its work on the IO thread and will // be destroyed on the IO thread so, now that we're on the IO thread, // properly initialize the WeakPtrFactory. weak_ptr_factory_.reset(new base::WeakPtrFactory(this)); } state->manager_ = weak_ptr_factory_->GetWeakPtr(); state->callback_ = callback; state->params_.url_ = GetUrlWithoutQueryOrFragment(url_filter_.Run(request->GetURL())); state->params_.browser_ = browser; state->params_.frame_ = frame; state->params_.request_ = request; state->params_.url_filter_ = url_filter_; state->params_.mime_type_resolver_ = mime_type_resolver_; state->current_entry_pos_ = current_entry_pos; // If the request is potentially handled we need to continue asynchronously. return SendRequest(state.Pass()) ? RV_CONTINUE_ASYNC : RV_CONTINUE; } CefRefPtr CefResourceManager::GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) { CEF_REQUIRE_IO_THREAD(); if (pending_handlers_.empty()) return NULL; CefRefPtr handler; PendingHandlersMap::iterator it = pending_handlers_.find(request->GetIdentifier()); if (it != pending_handlers_.end()) { handler = it->second; pending_handlers_.erase(it); } return handler; } // Send the request to providers in order until one potentially handles it or we // run out of providers. Returns true if the request is potentially handled. bool CefResourceManager::SendRequest(scoped_ptr state) { bool potentially_handled = false; do { // Should not be on the last provider entry. DCHECK(state->current_entry_pos_ != providers_.end()); scoped_refptr request = new Request(state.Pass()); // Give the provider an opportunity to handle the request. state = request->SendRequest(); if (state.get()) { // The provider will not handle the request. Move to the next provider if // any. if (!IncrementProvider(state.get())) StopRequest(state.Pass()); } else { potentially_handled = true; } } while (state.get()); return potentially_handled; } void CefResourceManager::ContinueRequest( scoped_ptr state, CefRefPtr handler) { CEF_REQUIRE_IO_THREAD(); if (handler.get()) { // The request has been handled. Associate the request ID with the handler. pending_handlers_.insert( std::make_pair(state->params_.request_->GetIdentifier(), handler)); StopRequest(state.Pass()); } else { // Move to the next provider if any. if (IncrementProvider(state.get())) SendRequest(state.Pass()); else StopRequest(state.Pass()); } } void CefResourceManager::StopRequest(scoped_ptr state) { CEF_REQUIRE_IO_THREAD(); // Detach from the current provider. DetachRequestFromProvider(state.get()); // Delete the state object and execute the callback. state.reset(); } // Move state to the next provider if any and return true if there are more // providers. bool CefResourceManager::IncrementProvider(RequestState* state) { // Identify the next provider. ProviderEntryList::iterator next_entry_pos = state->current_entry_pos_; GetNextValidProvider(++next_entry_pos); // Detach from the current provider. DetachRequestFromProvider(state); if (next_entry_pos != providers_.end()) { // Update the state to reference the new provider entry. state->current_entry_pos_ = next_entry_pos; return true; } return false; } // The new provider, if any, should be determined before calling this method. void CefResourceManager::DetachRequestFromProvider(RequestState* state) { if (state->current_entry_pos_ != providers_.end()) { // Remove the association from the current provider entry. ProviderEntryList::iterator current_entry_pos = state->current_entry_pos_; ProviderEntry* current_entry = *(current_entry_pos); current_entry->pending_requests_.erase(state->current_request_pos_); if (current_entry->deletion_pending_ && current_entry->pending_requests_.empty()) { // Delete the current provider entry now. providers_.erase(current_entry_pos); delete current_entry; } // Set to the end for error checking purposes. state->current_entry_pos_ = providers_.end(); } } // Move to the next provider that is not pending deletion. void CefResourceManager::GetNextValidProvider( ProviderEntryList::iterator& iterator) { while (iterator != providers_.end() && (*iterator)->deletion_pending_) { ++iterator; } } void CefResourceManager::DeleteProvider(ProviderEntryList::iterator& iterator, bool stop) { CEF_REQUIRE_IO_THREAD(); ProviderEntry* current_entry = *(iterator); if (current_entry->deletion_pending_) return; if (!current_entry->pending_requests_.empty()) { // Don't delete the provider entry until all pending requests have cleared. current_entry->deletion_pending_ = true; // Continue pending requests immediately. RequestList::iterator it = current_entry->pending_requests_.begin(); for (; it != current_entry->pending_requests_.end(); ++it) { const scoped_refptr& request = *it; if (request->HasState()) { if (stop) request->Stop(); else request->Continue(NULL); current_entry->provider_->OnRequestCanceled(request); } } ++iterator; } else { // Delete the provider entry now. iterator = providers_.erase(iterator); delete current_entry; } }