103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// found in the LICENSE file. 403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/shell/browser/shell_nacl_browser_delegate.h" 603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include <string> 803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/base_paths.h" 1003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/command_line.h" 1103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/path_service.h" 1203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/strings/string_split.h" 1303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/strings/string_util.h" 1403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "content/public/browser/browser_context.h" 1503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "content/public/browser/browser_thread.h" 1603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "content/public/browser/render_frame_host.h" 1703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "content/public/browser/site_instance.h" 1803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/browser/extension_system.h" 1903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/browser/info_map.h" 2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/browser/process_manager.h" 2103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/common/constants.h" 2203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/common/extension.h" 2303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/common/url_pattern.h" 2403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "extensions/shell/common/version.h" // Generated file. 2503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "ppapi/c/private/ppb_nacl_private.h" 2603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "url/gurl.h" 2703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 2803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)using content::BrowserContext; 2903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)using content::BrowserThread; 3003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)using content::BrowserPpapiHost; 3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 3203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)namespace extensions { 3303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)namespace { 3403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 3503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// Handles an extension's NaCl process transitioning in or out of idle state by 3603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// relaying the state to the extension's process manager. See Chrome's 3703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// NaClBrowserDelegateImpl for another example. 3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)void OnKeepaliveOnUIThread( 3903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const BrowserPpapiHost::OnKeepaliveInstanceData& instance_data, 4003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const base::FilePath& profile_data_directory) { 4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) DCHECK_CURRENTLY_ON(BrowserThread::UI); 4203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 4303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // Only one instance will exist for NaCl embeds, even when more than one 4403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // embed of the same plugin exists on the same page. 4503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) DCHECK(instance_data.size() == 1); 4603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (instance_data.size() < 1) 4703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return; 4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) ProcessManager::OnKeepaliveFromPlugin(instance_data[0].render_process_id, 5003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) instance_data[0].render_frame_id, 5103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) instance_data[0].document_url.host()); 5203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 5303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 5403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// Calls OnKeepaliveOnUIThread on UI thread. 5503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)void OnKeepalive(const BrowserPpapiHost::OnKeepaliveInstanceData& instance_data, 5603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const base::FilePath& profile_data_directory) { 5703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 5803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) BrowserThread::PostTask( 5903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) BrowserThread::UI, 6003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) FROM_HERE, 6103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) base::Bind( 6203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) &OnKeepaliveOnUIThread, instance_data, profile_data_directory)); 6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 6403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 6503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} // namespace 6603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 6703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)ShellNaClBrowserDelegate::ShellNaClBrowserDelegate(BrowserContext* context) 6803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) : browser_context_(context) { 6903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) DCHECK(browser_context_); 7003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 7103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 7203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)ShellNaClBrowserDelegate::~ShellNaClBrowserDelegate() { 7303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 7403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 7503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)void ShellNaClBrowserDelegate::ShowMissingArchInfobar(int render_process_id, 7603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) int render_view_id) { 7703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // app_shell does not have infobars. 7803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) LOG(ERROR) << "Missing architecture for pid " << render_process_id; 7903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 8003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 8103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::DialogsAreSuppressed() { 8203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 8303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 8403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 8503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::GetCacheDirectory(base::FilePath* cache_dir) { 8603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // Just use the general cache directory, not a subdirectory like Chrome does. 871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#if defined(OS_POSIX) 8803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return PathService::Get(base::DIR_CACHE, cache_dir); 891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#elif defined(OS_WIN) 901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // TODO(yoz): Find an appropriate persistent directory to use here. 911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return PathService::Get(base::DIR_TEMP, cache_dir); 921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#endif 9303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 9403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 9503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::GetPluginDirectory(base::FilePath* plugin_dir) { 9603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // On Posix, plugins are in the module directory. 9703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return PathService::Get(base::DIR_MODULE, plugin_dir); 9803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 9903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 10003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::GetPnaclDirectory(base::FilePath* pnacl_dir) { 10103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // On Posix, the pnacl directory is inside the plugin directory. 10203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) base::FilePath plugin_dir; 10303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (!GetPluginDirectory(&plugin_dir)) 10403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 10503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *pnacl_dir = plugin_dir.Append(FILE_PATH_LITERAL("pnacl")); 10603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return true; 10703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 10803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 10903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::GetUserDirectory(base::FilePath* user_dir) { 11003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) base::FilePath path = browser_context_->GetPath(); 11103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (!path.empty()) { 11203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *user_dir = path; 11303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return true; 11403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) } 11503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 11603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 11703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 11803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)std::string ShellNaClBrowserDelegate::GetVersionString() const { 11903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // A version change triggers an update of the NaCl validation caches. 12003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // Example version: "39.0.2129.0 (290550)". 12103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return PRODUCT_VERSION " (" LAST_CHANGE ")"; 12203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 12303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 12403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)ppapi::host::HostFactory* ShellNaClBrowserDelegate::CreatePpapiHostFactory( 12503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) content::BrowserPpapiHost* ppapi_host) { 12603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return NULL; 12703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 12803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 12903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)void ShellNaClBrowserDelegate::SetDebugPatterns(std::string debug_patterns) { 13003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // No debugger support. Developers should use Chrome for debugging. 13103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 13203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 13303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::URLMatchesDebugPatterns( 13403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const GURL& manifest_url) { 13503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // No debugger support. Developers should use Chrome for debugging. 13603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 13703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 13803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 13903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// This function is security sensitive. Be sure to check with a security 14003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// person before you modify it. 14103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// TODO(jamescook): Refactor this code into the extensions module so it can 14203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// be shared with Chrome's NaClBrowserDelegateImpl. http://crbug.com/403017 14303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::MapUrlToLocalFilePath( 14403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const GURL& file_url, 14503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) bool use_blocking_api, 14603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const base::FilePath& profile_directory, 14703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) base::FilePath* file_path) { 14803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) scoped_refptr<InfoMap> info_map = 14903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) ExtensionSystem::Get(browser_context_)->info_map(); 15003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // Check that the URL is recognized by the extension system. 15103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const Extension* extension = 15203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) info_map->extensions().GetExtensionOrAppByURL(file_url); 15303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (!extension) 15403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 15503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 15603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // This is a short-cut which avoids calling a blocking file operation 15703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // (GetFilePath()), so that this can be called on the IO thread. It only 15803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // handles a subset of the urls. 15903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (!use_blocking_api) { 16003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (file_url.SchemeIs(kExtensionScheme)) { 16103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) std::string path = file_url.path(); 16203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) base::TrimString(path, "/", &path); // Remove first slash 16303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *file_path = extension->path().AppendASCII(path); 16403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return true; 16503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) } 16603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 16703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) } 16803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 16903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // Check that the URL references a resource in the extension. 17003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // NOTE: app_shell does not support shared modules. 17103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) ExtensionResource resource = extension->GetResource(file_url.path()); 17203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (resource.empty()) 17303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 17403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 17503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) // GetFilePath is a blocking function call. 17603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const base::FilePath resource_file_path = resource.GetFilePath(); 17703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) if (resource_file_path.empty()) 17803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 17903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 18003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *file_path = resource_file_path; 18103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return true; 18203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 18303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 18403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)content::BrowserPpapiHost::OnKeepaliveCallback 18503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)ShellNaClBrowserDelegate::GetOnKeepaliveCallback() { 18603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return base::Bind(&OnKeepalive); 18703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 18803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 18903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool ShellNaClBrowserDelegate::IsNonSfiModeAllowed( 19003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const base::FilePath& profile_directory, 19103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) const GURL& manifest_url) { 19203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return false; 19303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} 19403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 19503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} // namespace extensions 196