diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index e7d7a6e36cab..c3ad11e1438e 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -15,6 +15,7 @@ #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" #include "atom/renderer/atom_render_view_observer.h" +#include "atom/renderer/content_settings_observer.h" #include "atom/renderer/guest_view_container.h" #include "atom/renderer/node_array_buffer_bridge.h" #include "atom/renderer/preferences_manager.h" @@ -176,6 +177,7 @@ void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); new AtomRenderFrameObserver(render_frame, this); + new ContentSettingsObserver(render_frame); // Allow file scheme to handle service worker by default. // FIXME(zcbenz): Can this be moved elsewhere? diff --git a/atom/renderer/content_settings_observer.cc b/atom/renderer/content_settings_observer.cc new file mode 100644 index 000000000000..9f0f202b5c56 --- /dev/null +++ b/atom/renderer/content_settings_observer.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/renderer/content_settings_observer.h" + +#include "content/public/renderer/render_frame.h" +#include "third_party/WebKit/public/platform/URLConversion.h" +#include "third_party/WebKit/public/platform/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" + +namespace atom { + +ContentSettingsObserver::ContentSettingsObserver( + content::RenderFrame* render_frame) + : content::RenderFrameObserver(render_frame) { + render_frame->GetWebFrame()->setContentSettingsClient(this); +} + +ContentSettingsObserver::~ContentSettingsObserver() { +} + +bool ContentSettingsObserver::allowDatabase( + const blink::WebString& name, + const blink::WebString& display_name, + unsigned estimated_size) { + blink::WebFrame* frame = render_frame()->GetWebFrame(); + if (frame->getSecurityOrigin().isUnique() || + frame->top()->getSecurityOrigin().isUnique()) + return false; + auto origin = blink::WebStringToGURL(frame->getSecurityOrigin().toString()); + if (!origin.IsStandard()) + return false; + return true; +} + +bool ContentSettingsObserver::allowStorage(bool local) { + blink::WebFrame* frame = render_frame()->GetWebFrame(); + if (frame->getSecurityOrigin().isUnique() || + frame->top()->getSecurityOrigin().isUnique()) + return false; + auto origin = blink::WebStringToGURL(frame->getSecurityOrigin().toString()); + if (!origin.IsStandard()) + return false; + return true; +} + +bool ContentSettingsObserver::allowIndexedDB( + const blink::WebString& name, + const blink::WebSecurityOrigin& security_origin) { + blink::WebFrame* frame = render_frame()->GetWebFrame(); + if (frame->getSecurityOrigin().isUnique() || + frame->top()->getSecurityOrigin().isUnique()) + return false; + auto origin = blink::WebStringToGURL(frame->getSecurityOrigin().toString()); + if (!origin.IsStandard()) + return false; + return true; +} + +void ContentSettingsObserver::OnDestruct() { + delete this; +} + +} // namespace atom diff --git a/atom/renderer/content_settings_observer.h b/atom/renderer/content_settings_observer.h new file mode 100644 index 000000000000..66e679d6d06a --- /dev/null +++ b/atom/renderer/content_settings_observer.h @@ -0,0 +1,37 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_RENDERER_CONTENT_SETTINGS_OBSERVER_H_ +#define ATOM_RENDERER_CONTENT_SETTINGS_OBSERVER_H_ + +#include "base/compiler_specific.h" +#include "content/public/renderer/render_frame_observer.h" +#include "third_party/WebKit/public/web/WebContentSettingsClient.h" + +namespace atom { + +class ContentSettingsObserver : public content::RenderFrameObserver, + public blink::WebContentSettingsClient { + public: + explicit ContentSettingsObserver(content::RenderFrame* render_frame); + ~ContentSettingsObserver() override; + + // blink::WebContentSettingsClient implementation. + bool allowDatabase(const blink::WebString& name, + const blink::WebString& display_name, + unsigned estimated_size) override; + bool allowStorage(bool local) override; + bool allowIndexedDB(const blink::WebString& name, + const blink::WebSecurityOrigin& security_origin) override; + + private: + // content::RenderFrameObserver implementation. + void OnDestruct() override; + + DISALLOW_COPY_AND_ASSIGN(ContentSettingsObserver); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_CONTENT_SETTINGS_OBSERVER_H_ diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 5864cf173fbe..4e02facaba68 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -50,8 +50,11 @@ non-standard schemes can not recognize relative URLs: Registering a scheme as standard will allow access to files through the [FileSystem API][file-system-api]. Otherwise the renderer will throw a security -error for the scheme. So in general if you want to register a custom protocol to -replace the `http` protocol, you have to register it as a standard scheme: +error for the scheme. + +By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies) +are disabled for non standard schemes. So in general if you want to register a +custom protocol to replace the `http` protocol, you have to register it as a standard scheme: ```javascript const {app, protocol} = require('electron') diff --git a/filenames.gypi b/filenames.gypi index 88a8dc0510e1..f8d80c0fda4e 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -433,6 +433,8 @@ 'atom/renderer/atom_render_view_observer.h', 'atom/renderer/atom_renderer_client.cc', 'atom/renderer/atom_renderer_client.h', + 'atom/renderer/content_settings_observer.cc', + 'atom/renderer/content_settings_observer.h', 'atom/renderer/guest_view_container.cc', 'atom/renderer/guest_view_container.h', 'atom/renderer/node_array_buffer_bridge.cc', diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index e26cba87c38b..d37fb3c0cd25 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -5,7 +5,7 @@ const ws = require('ws') const url = require('url') const remote = require('electron').remote -const {BrowserWindow, session, webContents} = remote +const {BrowserWindow, ipcMain, protocol, session, webContents} = remote const isCI = remote.getGlobal('isCi') @@ -442,6 +442,88 @@ describe('chromium feature', function () { done() }) }) + + describe('custom non standard schemes', function () { + const protocolName = 'storage' + let contents = null + before(function (done) { + const handler = function (request, callback) { + let parsedUrl = url.parse(request.url) + let filename + switch (parsedUrl.pathname) { + case '/localStorage' : filename = 'local_storage.html'; break + case '/sessionStorage' : filename = 'session_storage.html'; break + case '/WebSQL' : filename = 'web_sql.html'; break + case '/indexedDB' : filename = 'indexed_db.html'; break + case '/cookie' : filename = 'cookie.html'; break + default : filename = '' + } + callback({path: fixtures + '/pages/storage/' + filename}) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { + done(error) + }) + }) + + after(function (done) { + protocol.unregisterProtocol(protocolName, () => done()) + }) + + beforeEach(function () { + contents = webContents.create({}) + }) + + afterEach(function () { + contents.destroy() + contents = null + }) + + it('cannot access localStorage', function (done) { + ipcMain.once('local-storage-response', function (event, error) { + assert.equal( + error, + 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.') + done() + }) + contents.loadURL(protocolName + '://host/localStorage') + }) + + it('cannot access sessionStorage', function (done) { + ipcMain.once('session-storage-response', function (event, error) { + assert.equal( + error, + 'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.') + done() + }) + contents.loadURL(protocolName + '://host/sessionStorage') + }) + + it('cannot access WebSQL database', function (done) { + ipcMain.once('web-sql-response', function (event, error) { + assert.equal( + error, + 'An attempt was made to break through the security policy of the user agent.') + done() + }) + contents.loadURL(protocolName + '://host/WebSQL') + }) + + it('cannot access indexedDB', function (done) { + ipcMain.once('indexed-db-response', function (event, error) { + assert.equal(error, 'The user denied permission to access the database.') + done() + }) + contents.loadURL(protocolName + '://host/indexedDB') + }) + + it('cannot access cookie', function (done) { + ipcMain.once('cookie-response', function (event, cookie) { + assert(!cookie) + done() + }) + contents.loadURL(protocolName + '://host/cookie') + }) + }) }) describe('websockets', function () { diff --git a/spec/fixtures/pages/storage/cookie.html b/spec/fixtures/pages/storage/cookie.html new file mode 100644 index 000000000000..dc6a425f42e5 --- /dev/null +++ b/spec/fixtures/pages/storage/cookie.html @@ -0,0 +1,5 @@ + diff --git a/spec/fixtures/pages/storage/indexed_db.html b/spec/fixtures/pages/storage/indexed_db.html new file mode 100644 index 000000000000..1515489ba001 --- /dev/null +++ b/spec/fixtures/pages/storage/indexed_db.html @@ -0,0 +1,7 @@ + diff --git a/spec/fixtures/pages/storage/local_storage.html b/spec/fixtures/pages/storage/local_storage.html new file mode 100644 index 000000000000..fc9bab009084 --- /dev/null +++ b/spec/fixtures/pages/storage/local_storage.html @@ -0,0 +1,8 @@ + diff --git a/spec/fixtures/pages/storage/session_storage.html b/spec/fixtures/pages/storage/session_storage.html new file mode 100644 index 000000000000..2e41dd8d1c95 --- /dev/null +++ b/spec/fixtures/pages/storage/session_storage.html @@ -0,0 +1,8 @@ + diff --git a/spec/fixtures/pages/storage/web_sql.html b/spec/fixtures/pages/storage/web_sql.html new file mode 100644 index 000000000000..8ba4eec9252b --- /dev/null +++ b/spec/fixtures/pages/storage/web_sql.html @@ -0,0 +1,8 @@ +