Skip to content

Commit

Permalink
chrore: proxyless switches (#2793)
Browse files Browse the repository at this point in the history
* more proxyless switches

* fix tests
  • Loading branch information
miherlosev committed Sep 6, 2022
1 parent d43fd5d commit 40502fc
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 60 deletions.
4 changes: 2 additions & 2 deletions 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.7.3",
"version": "24.7.4",
"homepage": "https://github.com/DevExpress/testcafe-hammerhead",
"bugs": {
"url": "https://github.com/DevExpress/testcafe-hammerhead/issues"
Expand Down Expand Up @@ -89,8 +89,8 @@
"rollup": "^2.38.5",
"rollup-plugin-typescript2": "^0.32.1",
"tmp": "0.0.26",
"typescript": "^4.7.4",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"ws": "^7.4.6"
},
"main": "lib/index",
Expand Down
39 changes: 22 additions & 17 deletions src/client/sandbox/code-instrumentation/methods.ts
Expand Up @@ -14,29 +14,34 @@ export default class MethodCallInstrumentation extends SandboxBase {
constructor (private readonly _messageSandbox: MessageSandbox) {
super();

this._buildMethodWrappers();
}

_buildMethodWrappers (): void {
this.methodWrappers = {
postMessage: {
condition: isWindow,
method: (contentWindow: Window, args: any[]) => _messageSandbox.postMessage(contentWindow, args),
method: (contentWindow: Window, args: any[]) => this._messageSandbox.postMessage(contentWindow, args),
},
};

// NOTE: We cannot get the location wrapper for a cross-domain window. Therefore, we need to
// intercept calls to the native 'replace' method.
replace: {
condition: isLocation,
method: (location: Location, args: any[]) => location.replace(getProxyUrl(args[0], {
resourceType: MethodCallInstrumentation._getLocationResourceType(location),
})),
},
if (this.proxyless)
return;

// NOTE: We cannot get the location wrapper for a cross-domain window. Therefore, we need to
// intercept calls to the native 'assign' method.
assign: {
condition: isLocation,
method: (location: Location, args: any[]) => location.assign(getProxyUrl(args[0], {
resourceType: MethodCallInstrumentation._getLocationResourceType(location),
})),
},
// NOTE: We cannot get the location wrapper for a cross-domain window. Therefore, we need to
// intercept calls to the native 'replace' and 'assign' methods.
this.methodWrappers.replace = {
condition: isLocation,
method: (location: Location, args: any[]) => location.replace(getProxyUrl(args[0], {
resourceType: MethodCallInstrumentation._getLocationResourceType(location),
})),
};

this.methodWrappers.assign = {
condition: isLocation,
method: (location: Location, args: any[]) => location.assign(getProxyUrl(args[0], {
resourceType: MethodCallInstrumentation._getLocationResourceType(location),
})),
};
}

Expand Down
10 changes: 7 additions & 3 deletions src/client/sandbox/node/element.ts
Expand Up @@ -196,9 +196,13 @@ export default class ElementSandbox extends SandboxBase {
domUtils.isElementInDocument(el, currentDocument))
urlResolver.updateBase(value, currentDocument);

args[valueIndex] = isIframe && isCrossDomainUrl
? urlUtils.getCrossDomainIframeProxyUrl(value)
: urlUtils.getProxyUrl(value, { resourceType, charset: elCharset, doc: currentDocument });
if (this.proxyless)
args[valueIndex] = value;
else {
args[valueIndex] = isIframe && isCrossDomainUrl
? urlUtils.getCrossDomainIframeProxyUrl(value)
: urlUtils.getProxyUrl(value, { resourceType, charset: elCharset, doc: currentDocument });
}
}
}
else if (value && !isSpecialPage && !urlUtils.parseProxyUrl(value)) {
Expand Down
75 changes: 41 additions & 34 deletions src/client/sandbox/node/window.ts
Expand Up @@ -422,6 +422,38 @@ export default class WindowSandbox extends SandboxBase {
}
}

private _overrideAllUrlAttrDescriptors (): void {
this._overrideUrlAttrDescriptors('data', [window.HTMLObjectElement]);

this._overrideUrlAttrDescriptors('src', [
window.HTMLImageElement,
window.HTMLScriptElement,
window.HTMLEmbedElement,
window.HTMLSourceElement,
window.HTMLMediaElement,
window.HTMLInputElement,
window.HTMLFrameElement,
window.HTMLIFrameElement,
]);

this._overrideUrlAttrDescriptors('action', [window.HTMLFormElement]);

this._overrideUrlAttrDescriptors('formAction', [
window.HTMLInputElement,
window.HTMLButtonElement,
]);

this._overrideUrlAttrDescriptors('href', [
window.HTMLAnchorElement,
window.HTMLLinkElement,
window.HTMLAreaElement,
window.HTMLBaseElement,
]);

if (nativeMethods.htmlManifestGetter)
this._overrideUrlAttrDescriptors('manifest', [window.HTMLHtmlElement]);
}

static isProxyObject (obj: any): boolean {
try {
return obj[IS_PROXY_OBJECT_INTERNAL_PROP_NAME] === IS_PROXY_OBJECT_INTERNAL_PROP_VALUE;
Expand Down Expand Up @@ -802,7 +834,9 @@ export default class WindowSandbox extends SandboxBase {
return nativeMethods.functionToString.call(this);
});

if (typeof window.history.pushState === 'function' && typeof window.history.replaceState === 'function') {
if (typeof window.history.pushState === 'function'
&& typeof window.history.replaceState === 'function'
&& !this.proxyless) {
const createWrapperForHistoryStateManipulationFn = function (nativeFn) {
return function (this: History, ...args) {
const url = args[2];
Expand All @@ -818,7 +852,7 @@ export default class WindowSandbox extends SandboxBase {
overrideFunction(window.history, 'replaceState', createWrapperForHistoryStateManipulationFn(nativeMethods.historyReplaceState));
}

if (nativeMethods.sendBeacon) {
if (nativeMethods.sendBeacon && !this.proxyless) {
overrideFunction(window.Navigator.prototype, 'sendBeacon', function (this: Navigator) {
if (typeof arguments[0] === 'string')
arguments[0] = getProxyUrl(arguments[0]);
Expand All @@ -827,7 +861,7 @@ export default class WindowSandbox extends SandboxBase {
});
}

if (window.navigator.registerProtocolHandler) {
if (window.navigator.registerProtocolHandler && !this.proxyless) {
overrideFunction(window.navigator, 'registerProtocolHandler', function (...args) {
const urlIndex = 1;

Expand Down Expand Up @@ -872,7 +906,7 @@ export default class WindowSandbox extends SandboxBase {
});
}

if (window.WebSocket) {
if (window.WebSocket && !this.proxyless) {
overrideConstructor(window, 'WebSocket', function (url, protocols) {
if (arguments.length === 0)
return new nativeMethods.WebSocket();
Expand Down Expand Up @@ -965,7 +999,7 @@ export default class WindowSandbox extends SandboxBase {
},
});

if (nativeMethods.performanceEntryNameGetter) {
if (nativeMethods.performanceEntryNameGetter && !this.proxyless) {
overrideDescriptor(window.PerformanceEntry.prototype, 'name', {
getter: function () {
const name = nativeMethods.performanceEntryNameGetter.call(this);
Expand Down Expand Up @@ -1055,35 +1089,8 @@ export default class WindowSandbox extends SandboxBase {
},
});

this._overrideUrlAttrDescriptors('data', [window.HTMLObjectElement]);

this._overrideUrlAttrDescriptors('src', [
window.HTMLImageElement,
window.HTMLScriptElement,
window.HTMLEmbedElement,
window.HTMLSourceElement,
window.HTMLMediaElement,
window.HTMLInputElement,
window.HTMLFrameElement,
window.HTMLIFrameElement,
]);

this._overrideUrlAttrDescriptors('action', [window.HTMLFormElement]);

this._overrideUrlAttrDescriptors('formAction', [
window.HTMLInputElement,
window.HTMLButtonElement,
]);

this._overrideUrlAttrDescriptors('href', [
window.HTMLAnchorElement,
window.HTMLLinkElement,
window.HTMLAreaElement,
window.HTMLBaseElement,
]);

if (nativeMethods.htmlManifestGetter)
this._overrideUrlAttrDescriptors('manifest', [window.HTMLHtmlElement]);
if (!this.proxyless)
this._overrideAllUrlAttrDescriptors();

this._overrideAttrDescriptors('target', [
window.HTMLAnchorElement,
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Expand Up @@ -23,6 +23,7 @@ import promisifyStream from './utils/promisify-stream';
import PageProcessor from './processing/resources/page';
import { SCRIPTS } from './session/injectables';
import { acceptCrossOrigin } from './utils/http';
import getAssetPath from './utils/get-asset-path';

export default {
Proxy,
Expand All @@ -49,4 +50,5 @@ export default {
injectResources: PageProcessor.injectResources,
INJECTABLE_SCRIPTS: SCRIPTS,
acceptCrossOrigin,
getAssetPath,
};
2 changes: 1 addition & 1 deletion src/session/index.ts
Expand Up @@ -464,7 +464,7 @@ export default abstract class Session extends EventEmitter {
abstract getIframePayloadScript (iframeWithoutSrc: boolean): Promise<string>;
abstract getPayloadScript (): Promise<string>;
abstract handleFileDownload (): void;
abstract handleAttachment ({ isOpenedInNewWindow: boolean }): void;
abstract handleAttachment ({ isOpenedInNewWindow }: { isOpenedInNewWindow: boolean }): void;
abstract handlePageError (ctx: RequestPipelineContext, err: string): void;
abstract getAuthCredentials (): Credentials;

Expand Down
14 changes: 14 additions & 0 deletions src/utils/get-asset-path.ts
@@ -0,0 +1,14 @@
import { basename } from 'path';
import getClientScriptSuffix from './get-client-script-suffix';

export default function (originPath: string, developmentMode: boolean): string {
if (!developmentMode)
return originPath;

const filename = basename(originPath);
const otherPathPart = originPath.replace(filename, '');
const filenameParts = filename.split('.');
const resultFilename = `${filenameParts[0]}${getClientScriptSuffix(developmentMode)}.${filenameParts[1]}`;

return otherPathPart + resultFilename;
}
5 changes: 2 additions & 3 deletions src/utils/load-client-script.ts
@@ -1,6 +1,6 @@
import SERVICE_ROUTES from '../proxy/service-routes';
import { readSync as read } from 'read-file-relative';
import getClientScriptSuffix from './get-client-script-suffix';
import getAssetPath from './get-asset-path';

const serviceRouteValues = Object.values(SERVICE_ROUTES);

Expand All @@ -21,8 +21,7 @@ export default function (name: string, devMode: boolean): string {
if (!serviceRouteValues.includes(name))
throw new Error(`Unknown service route value: ${name}`);

const parts = name.split('.');
const resultPath = `../client${parts[0]}${getClientScriptSuffix(devMode)}.${parts[1]}`;
const resultPath = `../client${getAssetPath(name, devMode)}`;

return get(resultPath);
}
3 changes: 3 additions & 0 deletions ts-defs/index.d.ts
Expand Up @@ -529,4 +529,7 @@ declare module 'testcafe-hammerhead' {

/** Allows to accept cross-origin request for proxy routes **/
function acceptCrossOrigin (res: ServerResponse): void;

/** Calculates the asset path depending on the run mode (production or development) **/
function getAssetPath(originPath: string, developmentMode: boolean): string;
}

0 comments on commit 40502fc

Please sign in to comment.