fix: allow accessing file:// when web security is disabled (#28489)
* fix: allow accessing file:// when web security is disabled * test: fix webview tests on web security * chore: remove unused attributes * chore: cleanup RegisterURLLoaderFactories method
This commit is contained in:
parent
fe0da255b6
commit
e454bded3c
5 changed files with 100 additions and 54 deletions
|
@ -1286,9 +1286,10 @@ void ElectronBrowserClient::RegisterNonNetworkNavigationURLLoaderFactories(
|
||||||
context, ukm_source_id,
|
context, ukm_source_id,
|
||||||
false /* we don't support extensions::WebViewGuest */));
|
false /* we don't support extensions::WebViewGuest */));
|
||||||
#endif
|
#endif
|
||||||
|
// Always allow navigating to file:// URLs.
|
||||||
auto* protocol_registry = ProtocolRegistry::FromBrowserContext(context);
|
auto* protocol_registry = ProtocolRegistry::FromBrowserContext(context);
|
||||||
protocol_registry->RegisterURLLoaderFactories(
|
protocol_registry->RegisterURLLoaderFactories(factories,
|
||||||
URLLoaderFactoryType::kNavigation, factories);
|
true /* allow_file_access */);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElectronBrowserClient::
|
void ElectronBrowserClient::
|
||||||
|
@ -1297,8 +1298,10 @@ void ElectronBrowserClient::
|
||||||
NonNetworkURLLoaderFactoryMap* factories) {
|
NonNetworkURLLoaderFactoryMap* factories) {
|
||||||
auto* protocol_registry =
|
auto* protocol_registry =
|
||||||
ProtocolRegistry::FromBrowserContext(browser_context);
|
ProtocolRegistry::FromBrowserContext(browser_context);
|
||||||
protocol_registry->RegisterURLLoaderFactories(
|
// Workers are not allowed to request file:// URLs, there is no particular
|
||||||
URLLoaderFactoryType::kWorkerMainResource, factories);
|
// reason for it, and we could consider supporting it in future.
|
||||||
|
protocol_registry->RegisterURLLoaderFactories(factories,
|
||||||
|
false /* allow_file_access */);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
|
@ -1369,9 +1372,22 @@ void ElectronBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories(
|
||||||
if (!render_process_host || !render_process_host->GetBrowserContext())
|
if (!render_process_host || !render_process_host->GetBrowserContext())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
content::RenderFrameHost* frame_host =
|
||||||
|
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||||
|
content::WebContents* web_contents =
|
||||||
|
content::WebContents::FromRenderFrameHost(frame_host);
|
||||||
|
|
||||||
|
// Allow accessing file:// subresources from non-file protocols if web
|
||||||
|
// security is disabled.
|
||||||
|
bool allow_file_access = false;
|
||||||
|
if (web_contents) {
|
||||||
|
const auto& web_preferences = web_contents->GetOrCreateWebPreferences();
|
||||||
|
if (!web_preferences.web_security_enabled)
|
||||||
|
allow_file_access = true;
|
||||||
|
}
|
||||||
|
|
||||||
ProtocolRegistry::FromBrowserContext(render_process_host->GetBrowserContext())
|
ProtocolRegistry::FromBrowserContext(render_process_host->GetBrowserContext())
|
||||||
->RegisterURLLoaderFactories(URLLoaderFactoryType::kDocumentSubResource,
|
->RegisterURLLoaderFactories(factories, allow_file_access);
|
||||||
factories);
|
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id,
|
auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id,
|
||||||
|
@ -1379,10 +1395,6 @@ void ElectronBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories(
|
||||||
if (factory)
|
if (factory)
|
||||||
factories->emplace(extensions::kExtensionScheme, std::move(factory));
|
factories->emplace(extensions::kExtensionScheme, std::move(factory));
|
||||||
|
|
||||||
content::RenderFrameHost* frame_host =
|
|
||||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
|
||||||
content::WebContents* web_contents =
|
|
||||||
content::WebContents::FromRenderFrameHost(frame_host);
|
|
||||||
if (!web_contents)
|
if (!web_contents)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "content/public/browser/web_contents.h"
|
||||||
#include "services/network/public/cpp/self_deleting_url_loader_factory.h"
|
#include "services/network/public/cpp/self_deleting_url_loader_factory.h"
|
||||||
#include "shell/browser/electron_browser_context.h"
|
#include "shell/browser/electron_browser_context.h"
|
||||||
#include "shell/browser/net/asar/asar_url_loader.h"
|
#include "shell/browser/net/asar/asar_url_loader.h"
|
||||||
|
@ -61,22 +62,20 @@ ProtocolRegistry::ProtocolRegistry() {}
|
||||||
ProtocolRegistry::~ProtocolRegistry() = default;
|
ProtocolRegistry::~ProtocolRegistry() = default;
|
||||||
|
|
||||||
void ProtocolRegistry::RegisterURLLoaderFactories(
|
void ProtocolRegistry::RegisterURLLoaderFactories(
|
||||||
URLLoaderFactoryType type,
|
content::ContentBrowserClient::NonNetworkURLLoaderFactoryMap* factories,
|
||||||
content::ContentBrowserClient::NonNetworkURLLoaderFactoryMap* factories) {
|
bool allow_file_access) {
|
||||||
// Override the default FileURLLoaderFactory to support asar archives.
|
|
||||||
if (type == URLLoaderFactoryType::kNavigation) {
|
|
||||||
// Always allow navigating to file:// URLs.
|
|
||||||
//
|
|
||||||
// Note that Chromium calls |emplace| to create the default file factory
|
|
||||||
// after this call, so it won't override our asar factory.
|
|
||||||
DCHECK(!base::Contains(*factories, url::kFileScheme));
|
|
||||||
factories->emplace(url::kFileScheme, AsarURLLoaderFactory::Create());
|
|
||||||
} else if (type == URLLoaderFactoryType::kDocumentSubResource) {
|
|
||||||
// Only support requesting file:// subresource URLs when Chromium does so,
|
|
||||||
// it is usually supported under file:// or about:blank documents.
|
|
||||||
auto file_factory = factories->find(url::kFileScheme);
|
auto file_factory = factories->find(url::kFileScheme);
|
||||||
if (file_factory != factories->end())
|
if (file_factory != factories->end()) {
|
||||||
|
// If Chromium already allows file access then replace the url factory to
|
||||||
|
// also loading asar files.
|
||||||
file_factory->second = AsarURLLoaderFactory::Create();
|
file_factory->second = AsarURLLoaderFactory::Create();
|
||||||
|
} else if (allow_file_access) {
|
||||||
|
// Otherwise only allow file access when it is explicitly allowed.
|
||||||
|
//
|
||||||
|
// Note that Chromium may call |emplace| to create the default file factory
|
||||||
|
// after this call, it won't override our asar factory, but if asar support
|
||||||
|
// breaks in future, please check if Chromium has changed the call.
|
||||||
|
factories->emplace(url::kFileScheme, AsarURLLoaderFactory::Create());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& it : handlers_) {
|
for (const auto& it : handlers_) {
|
||||||
|
|
|
@ -26,8 +26,8 @@ class ProtocolRegistry {
|
||||||
content::ContentBrowserClient::URLLoaderFactoryType;
|
content::ContentBrowserClient::URLLoaderFactoryType;
|
||||||
|
|
||||||
void RegisterURLLoaderFactories(
|
void RegisterURLLoaderFactories(
|
||||||
URLLoaderFactoryType type,
|
content::ContentBrowserClient::NonNetworkURLLoaderFactoryMap* factories,
|
||||||
content::ContentBrowserClient::NonNetworkURLLoaderFactoryMap* factories);
|
bool allow_file_access);
|
||||||
|
|
||||||
const HandlersMap& intercept_handlers() const { return intercept_handlers_; }
|
const HandlersMap& intercept_handlers() const { return intercept_handlers_; }
|
||||||
|
|
||||||
|
|
|
@ -287,6 +287,39 @@ describe('web security', () => {
|
||||||
expect(response).to.equal('passed');
|
expect(response).to.equal('passed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('accessing file://', () => {
|
||||||
|
async function loadFile (w: BrowserWindow) {
|
||||||
|
const thisFile = url.format({
|
||||||
|
pathname: __filename.replace(/\\/g, '/'),
|
||||||
|
protocol: 'file',
|
||||||
|
slashes: true
|
||||||
|
});
|
||||||
|
await w.loadURL(`data:text/html,<script>
|
||||||
|
function loadFile() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch('${thisFile}').then(
|
||||||
|
() => resolve('loaded'),
|
||||||
|
() => resolve('failed')
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>`);
|
||||||
|
return await w.webContents.executeJavaScript('loadFile()');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('is forbidden when web security is enabled', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: true } });
|
||||||
|
const result = await loadFile(w);
|
||||||
|
expect(result).to.equal('failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is allowed when web security is disabled', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: false } });
|
||||||
|
const result = await loadFile(w);
|
||||||
|
expect(result).to.equal('loaded');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('does not crash when multiple WebContent are created with web security disabled', () => {
|
it('does not crash when multiple WebContent are created with web security disabled', () => {
|
||||||
const options = { show: false, webPreferences: { webSecurity: false } };
|
const options = { show: false, webPreferences: { webSecurity: false } };
|
||||||
const w1 = new BrowserWindow(options);
|
const w1 = new BrowserWindow(options);
|
||||||
|
|
|
@ -33,6 +33,28 @@ describe('<webview> tag', function () {
|
||||||
return event.message;
|
return event.message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function loadFileInWebView (webview, attributes = {}) {
|
||||||
|
const thisFile = url.format({
|
||||||
|
pathname: __filename.replace(/\\/g, '/'),
|
||||||
|
protocol: 'file',
|
||||||
|
slashes: true
|
||||||
|
});
|
||||||
|
const src = `<script>
|
||||||
|
function loadFile() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch('${thisFile}').then(
|
||||||
|
() => resolve('loaded'),
|
||||||
|
() => resolve('failed')
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('ok');
|
||||||
|
</script>`;
|
||||||
|
attributes.src = `data:text/html;base64,${btoa(unescape(encodeURIComponent(src)))}`;
|
||||||
|
await startLoadingWebViewAndWaitForMessage(webview, attributes);
|
||||||
|
return await webview.executeJavaScript('loadFile()');
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
webview = new WebView();
|
webview = new WebView();
|
||||||
});
|
});
|
||||||
|
@ -321,27 +343,13 @@ describe('<webview> tag', function () {
|
||||||
|
|
||||||
describe('disablewebsecurity attribute', () => {
|
describe('disablewebsecurity attribute', () => {
|
||||||
it('does not disable web security when not set', async () => {
|
it('does not disable web security when not set', async () => {
|
||||||
const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js');
|
const result = await loadFileInWebView(webview);
|
||||||
const src = `<script src='file://${jqueryPath}'></script> <script>console.log('ok');</script>`;
|
expect(result).to.equal('failed');
|
||||||
const encoded = btoa(unescape(encodeURIComponent(src)));
|
|
||||||
|
|
||||||
const message = await startLoadingWebViewAndWaitForMessage(webview, {
|
|
||||||
src: `data:text/html;base64,${encoded}`
|
|
||||||
});
|
|
||||||
expect(message).to.be.a('string');
|
|
||||||
expect(message).to.contain('Not allowed to load local resource');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables web security when set', async () => {
|
it('disables web security when set', async () => {
|
||||||
const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js');
|
const result = await loadFileInWebView(webview, { disablewebsecurity: '' });
|
||||||
const src = `<script src='file://${jqueryPath}'></script> <script>console.log('ok');</script>`;
|
expect(result).to.equal('loaded');
|
||||||
const encoded = btoa(unescape(encodeURIComponent(src)));
|
|
||||||
|
|
||||||
const message = await startLoadingWebViewAndWaitForMessage(webview, {
|
|
||||||
disablewebsecurity: '',
|
|
||||||
src: `data:text/html;base64,${encoded}`
|
|
||||||
});
|
|
||||||
expect(message).to.equal('ok');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not break node integration', async () => {
|
it('does not break node integration', async () => {
|
||||||
|
@ -483,16 +491,10 @@ describe('<webview> tag', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can disables web security and enable nodeintegration', async () => {
|
it('can disables web security and enable nodeintegration', async () => {
|
||||||
const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js');
|
const result = await loadFileInWebView(webview, { webpreferences: 'webSecurity=no, nodeIntegration=yes, contextIsolation=no' });
|
||||||
const src = `<script src='file://${jqueryPath}'></script> <script>console.log(typeof require);</script>`;
|
expect(result).to.equal('loaded');
|
||||||
const encoded = btoa(unescape(encodeURIComponent(src)));
|
const type = await webview.executeJavaScript('typeof require');
|
||||||
|
expect(type).to.equal('function');
|
||||||
const message = await startLoadingWebViewAndWaitForMessage(webview, {
|
|
||||||
src: `data:text/html;base64,${encoded}`,
|
|
||||||
webpreferences: 'webSecurity=no, nodeIntegration=yes, contextIsolation=no'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(message).to.equal('function');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue