From f0d08f4da128e0c02f848e7ba7a1a3bf9018836f Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Thu, 5 Apr 2018 16:13:24 -0700 Subject: [PATCH] Propagate referrer to new windows (#12397) * Propagate referrer to new windows Fixes #9205 * Rearrange -new-window event arguments for backwards-compatibility * Plumb referrer policy through guest-window-manager * Document the Referrer structure and its uses * Add tests for referrer in new windows * Docs nits --- atom/browser/api/atom_api_app.cc | 4 +- atom/browser/api/atom_api_web_contents.cc | 14 +++-- atom/browser/api/atom_api_web_contents.h | 1 + .../native_mate_converters/blink_converter.cc | 52 +++++++++++++++++++ .../native_mate_converters/blink_converter.h | 8 +++ .../content_converter.cc | 27 ++++++++++ .../content_converter.h | 9 ++++ docs/api/browser-window.md | 2 +- docs/api/structures/referrer.md | 10 ++++ docs/api/web-contents.md | 5 +- docs/api/webview-tag.md | 2 +- lib/browser/api/browser-window.js | 11 ++-- lib/browser/guest-window-manager.js | 17 +++--- spec/api-web-contents-spec.js | 46 ++++++++++++++++ 14 files changed, 186 insertions(+), 22 deletions(-) create mode 100644 docs/api/structures/referrer.md diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index c38f9e2372..e5a6307a64 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -689,8 +689,8 @@ bool App::CanCreateWindow( content::WebContents::FromRenderFrameHost(opener); if (web_contents) { auto api_web_contents = WebContents::CreateFrom(isolate(), web_contents); - api_web_contents->OnCreateWindow(target_url, frame_name, disposition, - additional_features, body); + api_web_contents->OnCreateWindow(target_url, referrer, frame_name, + disposition, additional_features, body); } return false; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index e542763c03..a4eeca7761 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -525,12 +525,14 @@ bool WebContents::DidAddMessageToConsole(content::WebContents* source, void WebContents::OnCreateWindow( const GURL& target_url, + const content::Referrer& referrer, const std::string& frame_name, WindowOpenDisposition disposition, const std::vector& features, const scoped_refptr& body) { if (type_ == BROWSER_WINDOW || type_ == OFF_SCREEN) - Emit("-new-window", target_url, frame_name, disposition, features, body); + Emit("-new-window", target_url, frame_name, disposition, features, body, + referrer); else Emit("new-window", target_url, frame_name, disposition, features); } @@ -1082,10 +1084,12 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { content::NavigationController::LoadURLParams params(url); - GURL http_referrer; - if (options.Get("httpReferrer", &http_referrer)) - params.referrer = content::Referrer(http_referrer.GetAsReferrer(), - blink::kWebReferrerPolicyDefault); + if (!options.Get("httpReferrer", ¶ms.referrer)) { + GURL http_referrer; + if (options.Get("httpReferrer", &http_referrer)) + params.referrer = content::Referrer(http_referrer.GetAsReferrer(), + blink::kWebReferrerPolicyDefault); + } std::string user_agent; if (options.Get("userAgent", &user_agent)) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 7d92d3f666..9c0155c74e 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -223,6 +223,7 @@ class WebContents : public mate::TrackableObject, // Create window with the given disposition. void OnCreateWindow( const GURL& target_url, + const content::Referrer& referrer, const std::string& frame_name, WindowOpenDisposition disposition, const std::vector& features, diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index b2fb1113bb..a3b6eb079a 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -481,4 +481,56 @@ v8::Local Converter::ToV8( return dict.GetHandle(); } +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const blink::WebReferrerPolicy& in) { + switch (in) { + case blink::kWebReferrerPolicyDefault: + return mate::StringToV8(isolate, "default"); + case blink::kWebReferrerPolicyAlways: + return mate::StringToV8(isolate, "unsafe-url"); + case blink::kWebReferrerPolicyNoReferrerWhenDowngrade: + return mate::StringToV8(isolate, "no-referrer-when-downgrade"); + case blink::kWebReferrerPolicyNever: + return mate::StringToV8(isolate, "no-referrer"); + case blink::kWebReferrerPolicyOrigin: + return mate::StringToV8(isolate, "origin"); + case blink::kWebReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin: + return mate::StringToV8(isolate, "strict-origin-when-cross-origin"); + case blink::kWebReferrerPolicySameOrigin: + return mate::StringToV8(isolate, "same-origin"); + case blink::kWebReferrerPolicyStrictOrigin: + return mate::StringToV8(isolate, "strict-origin"); + default: + return mate::StringToV8(isolate, "no-referrer"); + } +} + +// static +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, blink::WebReferrerPolicy* out) { + std::string policy = base::ToLowerASCII(V8ToString(val)); + if (policy == "default") + *out = blink::kWebReferrerPolicyDefault; + else if (policy == "unsafe-url") + *out = blink::kWebReferrerPolicyAlways; + else if (policy == "no-referrer-when-downgrade") + *out = blink::kWebReferrerPolicyNoReferrerWhenDowngrade; + else if (policy == "no-referrer") + *out = blink::kWebReferrerPolicyNever; + else if (policy == "origin") + *out = blink::kWebReferrerPolicyOrigin; + else if (policy == "strict-origin-when-cross-origin") + *out = + blink::kWebReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin; + else if (policy == "same-origin") + *out = blink::kWebReferrerPolicySameOrigin; + else if (policy == "strict-origin") + *out = blink::kWebReferrerPolicyStrictOrigin; + else + return false; + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 715aa272f1..a3f3d063b8 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -119,6 +119,14 @@ struct Converter { const blink::WebCache::ResourceTypeStats& stats); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebReferrerPolicy& in); + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebReferrerPolicy* out); +}; + v8::Local EditFlagsToV8(v8::Isolate* isolate, int editFlags); v8::Local MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags); diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 01682bb770..2f475731ed 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -318,4 +318,31 @@ bool Converter::FromV8( return true; } +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const content::Referrer& val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("url", ConvertToV8(isolate, val.url)); + dict.Set("policy", ConvertToV8(isolate, val.policy)); + return mate::ConvertToV8(isolate, dict); +} + +// static +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + content::Referrer* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + + if (!dict.Get("url", &out->url)) + return false; + + if (!dict.Get("policy", &out->policy)) + return false; + + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h index 9ce7973ac3..c1e3442551 100644 --- a/atom/common/native_mate_converters/content_converter.h +++ b/atom/common/native_mate_converters/content_converter.h @@ -9,6 +9,7 @@ #include "content/public/browser/permission_type.h" #include "content/public/common/menu_item.h" +#include "content/public/common/referrer.h" #include "content/public/common/stop_find_action.h" #include "native_mate/converter.h" #include "third_party/WebKit/public/platform/modules/permissions/permission_status.mojom.h" @@ -71,6 +72,14 @@ struct Converter { content::WebContents** out); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const content::Referrer& val); + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::Referrer* out); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index ec1255109e..ae379f5348 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1148,7 +1148,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `url` String * `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. + * `httpReferrer` String (optional) - An HTTP Referrer url. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData[]](structures/upload-raw-data.md) | [UploadFile[]](structures/upload-file.md) | [UploadFileSystem[]](structures/upload-file-system.md) | [UploadBlob[]](structures/upload-blob.md)) (optional) diff --git a/docs/api/structures/referrer.md b/docs/api/structures/referrer.md new file mode 100644 index 0000000000..eb788dca30 --- /dev/null +++ b/docs/api/structures/referrer.md @@ -0,0 +1,10 @@ +# Referrer Object + +* `url` String - HTTP Referrer URL. +* `policy` String - Can be `default`, `unsafe-url`, + `no-referrer-when-downgrade`, `no-referrer`, `origin`, + `strict-origin-when-cross-origin`, `same-origin`, `strict-origin`, or + `no-referrer`. See the [Referrer-Policy spec][1] for more details on the + meaning of these values. + +[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 6aaa2bdd9b..97c957b816 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -151,6 +151,9 @@ Returns: [`BrowserWindow`](browser-window.md). * `additionalFeatures` String[] - The non-standard features (features not handled by Chromium or Electron) given to `window.open()`. +* `referrer` [Referrer](structures/referrer.md) - The referrer that will be + passed to the new window. May or may not result in the `Referer` header being + sent, depending on the referrer policy. Emitted when the page requests to open a new window for a `url`. It could be requested by `window.open` or an external link like ``. @@ -609,7 +612,7 @@ for windows with *offscreen rendering* enabled. * `url` String * `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. + * `httpReferrer` (String | [Referrer](structures/referrer.md)) (optional) - An HTTP Referrer url. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n". * `postData` ([UploadRawData[]](structures/upload-raw-data.md) | [UploadFile[]](structures/upload-file.md) | [UploadFileSystem[]](structures/upload-file-system.md) | [UploadBlob[]](structures/upload-blob.md)) (optional) diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 2d8e142db0..dbf97b573e 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -306,7 +306,7 @@ webview.addEventListener('dom-ready', () => { * `url` URL * `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. + * `httpReferrer` String (optional) - An HTTP Referrer url. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData[]](structures/upload-raw-data.md) | [UploadFile[]](structures/upload-file.md) | [UploadFileSystem[]](structures/upload-file-system.md) | [UploadBlob[]](structures/upload-blob.md)) (optional) - diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 22565dcf98..527aeec39e 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -19,16 +19,16 @@ BrowserWindow.prototype._init = function () { } // Make new windows requested by links behave like "window.open" - this.webContents.on('-new-window', (event, url, frameName, - disposition, additionalFeatures, - postData) => { + this.webContents.on('-new-window', (event, url, frameName, disposition, + additionalFeatures, postData, + referrer) => { const options = { show: true, width: 800, height: 600 } ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', - event, url, frameName, disposition, + event, url, referrer, frameName, disposition, options, additionalFeatures, postData) }) @@ -59,8 +59,9 @@ BrowserWindow.prototype._init = function () { height: height || 600, webContents: webContents } + const referrer = { url: '', policy: 'default' } ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', - event, url, frameName, disposition, options) + event, url, referrer, frameName, disposition, options) }) // window.resizeTo(...) diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index ec77a7abdf..8e2aa3bb98 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -102,7 +102,7 @@ const setupGuest = function (embedder, frameName, guest, options) { } // Create a new guest created by |embedder| with |options|. -const createGuest = function (embedder, url, frameName, options, postData) { +const createGuest = function (embedder, url, referrer, frameName, options, postData) { let guest = frameToGuest.get(frameName) if (frameName && (guest != null)) { guest.loadURL(url) @@ -137,7 +137,9 @@ const createGuest = function (embedder, url, frameName, options, postData) { // // The above code would not work if a navigation to "about:blank" is done // here, since the window would be cleared of all changes in the next tick. - const loadOptions = {} + const loadOptions = { + httpReferrer: referrer + } if (postData != null) { loadOptions.postData = postData loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded' @@ -242,16 +244,17 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, } } + const referrer = { url: '', policy: 'default' } ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, - url, frameName, disposition, options, additionalFeatures) + url, referrer, frameName, disposition, options, additionalFeatures) }) // Routed window.open messages with fully parsed options -ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName, - disposition, options, +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer, + frameName, disposition, options, additionalFeatures, postData) { options = mergeBrowserWindowOptions(event.sender, options) - event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures) + event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer) const {newGuest} = event if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { if (newGuest != null) { @@ -265,7 +268,7 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event event.returnValue = null } } else { - event.returnValue = createGuest(event.sender, url, frameName, options, postData) + event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData) } }) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index cf90252289..51692778d8 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -708,4 +708,50 @@ describe('webContents module', () => { w.loadURL(`file://${fixtures}/pages/a.html`) }) }) + + describe('referrer', () => { + it('propagates referrer information to new target=_blank windows', (done) => { + const server = http.createServer((req, res) => { + if (req.url === '/should_have_referrer') { + assert.equal(req.headers.referer, 'http://127.0.0.1:' + server.address().port + '/') + return done() + } + res.end('link') + }) + server.listen(0, '127.0.0.1', () => { + const url = 'http://127.0.0.1:' + server.address().port + '/' + w.webContents.once('did-finish-load', () => { + w.webContents.once('new-window', (event, newUrl, frameName, disposition, options, features, referrer) => { + assert.equal(referrer.url, url) + assert.equal(referrer.policy, 'no-referrer-when-downgrade') + }) + w.webContents.executeJavaScript('a.click()') + }) + w.loadURL(url) + }) + }) + + // TODO(jeremy): window.open() in a real browser passes the referrer, but + // our hacked-up window.open() shim doesn't. It should. + xit('propagates referrer information to windows opened with window.open', (done) => { + const server = http.createServer((req, res) => { + if (req.url === '/should_have_referrer') { + assert.equal(req.headers.referer, 'http://127.0.0.1:' + server.address().port + '/') + return done() + } + res.end('') + }) + server.listen(0, '127.0.0.1', () => { + const url = 'http://127.0.0.1:' + server.address().port + '/' + w.webContents.once('did-finish-load', () => { + w.webContents.once('new-window', (event, newUrl, frameName, disposition, options, features, referrer) => { + assert.equal(referrer.url, url) + assert.equal(referrer.policy, 'no-referrer-when-downgrade') + }) + w.webContents.executeJavaScript('window.open(location.href + "should_have_referrer")') + }) + w.loadURL(url) + }) + }) + }) })