Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed the referrer for cross-domain iframes #2719

Merged
merged 4 commits into from Dec 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "testcafe-hammerhead",
"description": "A powerful web-proxy used as a core for the TestCafe testing framework (https://github.com/DevExpress/testcafe).",
"version": "24.5.9",
"version": "24.5.10",
"homepage": "https://github.com/DevExpress/testcafe-hammerhead",
"bugs": {
"url": "https://github.com/DevExpress/testcafe-hammerhead/issues"
Expand Down
11 changes: 7 additions & 4 deletions src/client/sandbox/node/document/index.ts
Expand Up @@ -2,7 +2,6 @@ import SandboxBase from '../../base';
import IframeSandbox from '../../iframe';
import nativeMethods from '../../native-methods';
import domProcessor from '../../../dom-processor';
import * as urlUtils from '../../../utils/url';
import settings from '../../../settings';
import { isIE } from '../../../utils/browser';
import { isIframeWithoutSrc, getFrameElement, isImgElement, isShadowUIElement } from '../../../utils/dom';
Expand All @@ -12,7 +11,8 @@ import INTERNAL_PROPS from '../../../../processing/dom/internal-properties';
import LocationAccessorsInstrumentation from '../../code-instrumentation/location';
import { overrideDescriptor, createOverriddenDescriptor, overrideFunction } from '../../../utils/overriding';
import NodeSandbox from '../index';
import { getDestinationUrl, isSpecialPage } from '../../../utils/url';
import { getDestinationUrl, isSpecialPage, convertToProxyUrl, getCrossDomainProxyOrigin } from '../../../utils/url';
import { getReferrer } from '../../../utils/destination-location';
import DocumentTitleStorageInitializer from './title-storage-initializer';
import CookieSandbox from '../../cookie';

Expand Down Expand Up @@ -204,7 +204,7 @@ export default class DocumentSandbox extends SandboxBase {
const el = nativeMethods.createElement.apply(this, args);

DocumentSandbox.forceProxySrcForImageIfNecessary(el);
domProcessor.processElement(el, urlUtils.convertToProxyUrl);
domProcessor.processElement(el, convertToProxyUrl);
documentSandbox._nodeSandbox.processNodes(el);

return el;
Expand All @@ -214,7 +214,7 @@ export default class DocumentSandbox extends SandboxBase {
const el = nativeMethods.createElementNS.apply(this, args);

DocumentSandbox.forceProxySrcForImageIfNecessary(el);
domProcessor.processElement(el, urlUtils.convertToProxyUrl);
domProcessor.processElement(el, convertToProxyUrl);
documentSandbox._nodeSandbox.processNodes(el);

return el;
Expand Down Expand Up @@ -243,6 +243,9 @@ export default class DocumentSandbox extends SandboxBase {
getter: function () {
const referrer = getDestinationUrl(nativeMethods.documentReferrerGetter.call(this));

if (referrer === getCrossDomainProxyOrigin() + '/')
return getReferrer();

return isSpecialPage(referrer) ? '' : referrer;
}
});
Expand Down
7 changes: 7 additions & 0 deletions src/client/utils/destination-location.ts
Expand Up @@ -65,6 +65,13 @@ export let get = function (): string {
return parsedProxyUrl ? parsedProxyUrl.destUrl : location;
}

export function getReferrer () {
const location = getLocation();
const parsedProxyUrl = sharedUrlUtils.parseProxyUrl(location);

return parsedProxyUrl?.reqOrigin ? parsedProxyUrl.reqOrigin + '/' : '';
}

export function overrideGet (func: typeof get) {
get = func;
}
Expand Down
11 changes: 11 additions & 0 deletions src/client/utils/url.ts
Expand Up @@ -134,6 +134,9 @@ export let getProxyUrl = function (url: string, opts?): string {
reqOrigin = reqOrigin || destLocation.getOriginHeader();
}

if (parsedResourceType.isIframe && proxyPort === settings.get().crossDomainProxyPort)
reqOrigin = reqOrigin || destLocation.getOriginHeader();

return sharedUrlUtils.getProxyUrl(resolvedUrl, {
proxyProtocol,
proxyHostname,
Expand Down Expand Up @@ -248,6 +251,14 @@ export let convertToProxyUrl = function (url: string, resourceType, charset, isC
});
}

export function getCrossDomainProxyOrigin () {
return sharedUrlUtils.getDomain({
protocol: location.protocol, // eslint-disable-line no-restricted-properties
hostname: location.hostname, // eslint-disable-line no-restricted-properties
port: settings.get().crossDomainProxyPort,
});
}

export function overrideConvertToProxyUrl (func: typeof convertToProxyUrl): void {
convertToProxyUrl = func;
}
Expand Down
3 changes: 3 additions & 0 deletions src/request-pipeline/context.ts
Expand Up @@ -436,6 +436,9 @@ export default class RequestPipelineContext {
const sessionId = this.session.id;
const windowId = this.windowId;

if (isCrossDomain)
reqOrigin = this.dest.domain;

return urlUtils.getProxyUrl(url, {
proxyHostname,
proxyProtocol,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/url.ts
Expand Up @@ -206,6 +206,8 @@ function parseRequestDescriptor (desc: string): RequestDescriptor | null {
parsedDesc.charset = resourceData[0];
else if (parsedResourceType.isWebSocket)
parsedDesc.reqOrigin = decodeURIComponent(restoreShortOrigin(resourceData[0]));
else if (parsedResourceType.isIframe && resourceData[0])
parsedDesc.reqOrigin = decodeURIComponent(restoreShortOrigin(resourceData[0]));
else if (parsedResourceType.isAjax) {
parsedDesc.credentials = parseInt(resourceData[0]);

Expand Down
2 changes: 1 addition & 1 deletion test/client/before-test.js
Expand Up @@ -209,7 +209,7 @@
};

window.waitForMessage = function (receiver) {
return new Promise(function (resolve) {
return new hammerhead.Promise(function (resolve) {
receiver.onmessage = function (e) {
receiver.onmessage = void 0;

Expand Down
20 changes: 20 additions & 0 deletions test/client/data/cross-domain/get-referrer.html
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<script src="/hammerhead.js" class="script-hammerhead-shadow-ui"></script>
<script type="text/javascript">
var hammerhead = window['%hammerhead%'];

hammerhead.start({ crossDomainProxyPort: 2000 });

var INSTRUCTION = hammerhead.PROCESSING_INSTRUCTIONS.dom.script;
var callMethod = window[INSTRUCTION.callMethod];

callMethod(top, 'postMessage', [document.referrer, '*']);
</script>
</head>
<body>
</body>
</html>
2 changes: 1 addition & 1 deletion test/client/fixtures/sandbox/iframe-flag-test.js
Expand Up @@ -430,7 +430,7 @@ module('regression');
test('setAttribute: frame src (GH-1070)', function () {
var frame = document.createElement('frame');
var proxyUrl = 'http://' + location.hostname + ':' + settings.get().crossDomainProxyPort +
'/sessionId!i/https://example.com/';
'/sessionId!i!s*example.com/https://example.com/';

frame.setAttribute('src', 'https://example.com/');

Expand Down
16 changes: 15 additions & 1 deletion test/client/fixtures/sandbox/iframe-test.js
Expand Up @@ -187,9 +187,10 @@ if (nativeMethods.iframeSrcdocGetter) {
iframe.contentDocument.close();

return new Promise(function (resolve) {
window.addEventListener('message', function (e) {
window.addEventListener('message', function onMessage (e) {
strictEqual(e.data, 'message');

window.removeEventListener('message', onMessage);
document.body.removeChild(iframe);
resolve();
});
Expand Down Expand Up @@ -502,6 +503,8 @@ test('self-removing script shouldn\'t throw an error (GH-TC-2469)', function ()
};
iframeDocument.write('<html><head></head><body></body></html>');
iframeDocument.close();

document.body.removeChild(iframe);
});

test('write "doctype" markup without head and body tags (GH-TC-2639)', function () {
Expand Down Expand Up @@ -677,3 +680,14 @@ test('Uninitialized iframes should correctly behave when they are rewritten (GH-
strictEqual(iframe2.contentDocument.body.lastChild.textContent, 'Second iframe!');
});
});

if (browserUtils.isChrome && browserUtils.version > 88 || browserUtils.isFirefox) {
test('the correct referrer for cross-domain iframes', function () {
createTestIframe({ src: getCrossDomainPageUrl('../../data/cross-domain/get-referrer.html') });

return waitForMessage(window)
.then(function (referrer) {
strictEqual(referrer, 'https://example.com/');
});
});
}
4 changes: 2 additions & 2 deletions test/client/fixtures/utils/html-test.js
Expand Up @@ -95,9 +95,9 @@ test('form', function () {
module('process html');

test('iframe', function () {
var processedHtml = htmlUtils.processHtml('<iframe src="http://example.com/">');
var processedHtml = htmlUtils.processHtml('<iframe src="https://example.com/">');

ok(processedHtml.indexOf('sessionId!i/http://example.com/"') !== -1);
ok(processedHtml.indexOf('sessionId!i/https://example.com/"') !== -1);
});

test('element with error in attribute', function () {
Expand Down
2 changes: 1 addition & 1 deletion test/client/fixtures/utils/url-test.js
Expand Up @@ -30,7 +30,7 @@ test('getCrossDomainIframeProxyUrl (GH-749)', function () {
settings.get().crossDomainProxyPort = '5555';

strictEqual(urlUtils.getCrossDomainIframeProxyUrl(destUrl),
'http://' + location.hostname + ':5555' + '/sessionId!i/https://example.com/' + destUrl);
'http://' + location.hostname + ':5555' + '/sessionId!i!s*example.com/https://example.com/' + destUrl);

settings.get().crossDomainProxyPort = storedCrossDomainport;
});
Expand Down
1 change: 1 addition & 0 deletions test/playground/server.js
Expand Up @@ -59,6 +59,7 @@ exports.start = (options = {}) => {

res
.status(301)
.set('referrer-policy', 'no-referrer')
.set('location', proxy.openSession(url, createSession()))
.end();
}
Expand Down
1 change: 1 addition & 0 deletions test/server/data/page/expected-https.html
Expand Up @@ -121,4 +121,5 @@
<input formaction="https://127.0.0.1:1836/sessionId*12345!f/http://input.formaction.com/" formaction-hammerhead-stored-value="http://input.formaction.com/" autocomplete-hammerhead-stored-value="hammerhead|autocomplete-attribute-absence-marker" autocomplete="off">
<button formaction="https://127.0.0.1:1836/sessionId*12345!f/http://button.formaction.com/" formaction-hammerhead-stored-value="http://button.formaction.com/"></button>
<iframe srcdoc="<html><head><meta class=&quot;charset-hammerhead-shadow-ui&quot; charset=&quot;utf-8&quot;><script class=&quot;self-removing-script-hammerhead-shadow-ui&quot;>(function () {var currentScript = document.currentScript;if (!currentScript) {var scripts = document.scripts;var scriptsLength = scripts.length;currentScript = scripts[scriptsLength - 1];}currentScript.parentNode.removeChild(currentScript);var parentHammerhead = null;if (!window[&quot;%hammerhead%&quot;])Object.defineProperty(window, &quot;hammerhead|document-was-cleaned&quot;, { value: true, configurable: true });try {parentHammerhead = window.parent[&quot;%hammerhead%&quot;];} catch(e) {}if (parentHammerhead)parentHammerhead.sandbox.onIframeDocumentRecreated(window.frameElement);})();</script></head><body><a href=&quot;https://127.0.0.1:1836/sessionId*12345!i/http://link.url&quot; href-hammerhead-stored-value=&quot;http://link.url&quot;>link</a></body></html>" srcdoc-hammerhead-stored-value="<a href='http://link.url'>link</a>"></iframe>
<iframe src="https://127.0.0.1:1837/sessionId*12345!i!127.0.0.1%3A2000/http://cross.domain.com" src-hammerhead-stored-value="http://cross.domain.com"></iframe>
</body></html>
1 change: 1 addition & 0 deletions test/server/data/page/expected.html
Expand Up @@ -121,4 +121,5 @@
<input formaction="http://127.0.0.1:1836/sessionId*12345!f/http://input.formaction.com/" formaction-hammerhead-stored-value="http://input.formaction.com/" autocomplete-hammerhead-stored-value="hammerhead|autocomplete-attribute-absence-marker" autocomplete="off">
<button formaction="http://127.0.0.1:1836/sessionId*12345!f/http://button.formaction.com/" formaction-hammerhead-stored-value="http://button.formaction.com/"></button>
<iframe srcdoc="<html><head><meta class=&quot;charset-hammerhead-shadow-ui&quot; charset=&quot;utf-8&quot;><script class=&quot;self-removing-script-hammerhead-shadow-ui&quot;>(function () {var currentScript = document.currentScript;if (!currentScript) {var scripts = document.scripts;var scriptsLength = scripts.length;currentScript = scripts[scriptsLength - 1];}currentScript.parentNode.removeChild(currentScript);var parentHammerhead = null;if (!window[&quot;%hammerhead%&quot;])Object.defineProperty(window, &quot;hammerhead|document-was-cleaned&quot;, { value: true, configurable: true });try {parentHammerhead = window.parent[&quot;%hammerhead%&quot;];} catch(e) {}if (parentHammerhead)parentHammerhead.sandbox.onIframeDocumentRecreated(window.frameElement);})();</script></head><body><a href=&quot;http://127.0.0.1:1836/sessionId*12345!i/http://link.url&quot; href-hammerhead-stored-value=&quot;http://link.url&quot;>link</a></body></html>" srcdoc-hammerhead-stored-value="<a href='http://link.url'>link</a>"></iframe>
<iframe src="http://127.0.0.1:1837/sessionId*12345!i!127.0.0.1%3A2000/http://cross.domain.com" src-hammerhead-stored-value="http://cross.domain.com"></iframe>
</body></html>
1 change: 1 addition & 0 deletions test/server/data/page/src.html
Expand Up @@ -126,5 +126,6 @@
<input formaction="http://input.formaction.com/" />
<button formaction="http://button.formaction.com/"></button>
<iframe srcdoc="<a href='http://link.url'>link</a>"></iframe>
<iframe src="http://cross.domain.com"></iframe>
</body>
</html>
3 changes: 2 additions & 1 deletion test/server/proxy/file-protocol-test.js
Expand Up @@ -54,7 +54,8 @@ describe('file:// protocol', () => {
// NOTE: The host property is empty in url with file: protocol.
// The expected.html template is used for both tests with http: and file: protocol.
const expected = fs.readFileSync('test/server/data/page/expected.html').toString()
.replace(/(hammerhead\|storage-wrapper\|sessionId\|)127\.0\.0\.1:2000/g, '$1');
.replace(/(hammerhead\|storage-wrapper\|sessionId\|)127\.0\.0\.1:2000/g, '$1')
.replace('!127.0.0.1%3A2000', '!');

compareCode(body, expected);
});
Expand Down
2 changes: 1 addition & 1 deletion test/server/proxy/regression-test.js
Expand Up @@ -802,7 +802,7 @@ describe('Regression', () => {

it('Should process "x-frame-options" header (GH-1017)', () => {
const getIframeProxyUrl = url => getProxyUrl(url, { isIframe: true });
const getCrossDomainIframeProxyUrl = url => getProxyUrl(url, { isIframe: true }, void 0, void 0, true);
const getCrossDomainIframeProxyUrl = url => getProxyUrl(url, { isIframe: true }, 'http://127.0.0.1:2000', void 0, true);

proxy.openSession('http://127.0.0.1:2000/', session);

Expand Down