Skip to content

Commit

Permalink
redo replacement logic, move matching to matchRequestProps helper
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Oct 20, 2022
1 parent ae6d501 commit 15f743d
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 49 deletions.
3 changes: 2 additions & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export * from './array-utils';
export * from './cookie-utils';
export * from './number-utils';
export * from './adjust-set-utils';
export * from './fetch-utils';
export * from './request-utils';
export * from './object-utils';
export * from './prevent-window-open-utils';
export * from './add-event-listener-utils';
Expand All @@ -26,3 +26,4 @@ export * from './regexp-utils';
export * from './random-response';
export * from './get-descriptor-addon';
export * from './parse-flags';
export * from './match-request-props';
35 changes: 35 additions & 0 deletions src/helpers/match-request-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
getMatchPropsData,
validateParsedData,
parseMatchProps,
} from './request-utils';

/**
* Checks if given propsToMatch string matches with given request data
* This is used by prevent-xhr, prevent-fetch, trusted-replace-xhr-response
* and trusted-replace-fetch-response scriptlets
* @param {string} propsToMatch
* @param {Object} requestData object with standard properties of fetch/xhr like url, method etc
* @returns {boolean}
*/
export const matchRequestProps = (propsToMatch, requestData) => {
let isMatched;

const parsedData = parseMatchProps(propsToMatch);
if (!validateParsedData(parsedData)) {
// eslint-disable-next-line no-console
console.log(`Invalid parameter: ${propsToMatch}`);
isMatched = false;
} else {
const matchData = getMatchPropsData(parsedData);
// prevent only if all props match
isMatched = Object.keys(matchData)
.every((matchKey) => {
const matchValue = matchData[matchKey];
return Object.prototype.hasOwnProperty.call(requestData, matchKey)
&& matchValue.test(requestData[matchKey]);
});
}

return isMatched;
};
19 changes: 19 additions & 0 deletions src/helpers/fetch-utils.js → src/helpers/request-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ export const getFetchData = (args) => {
return fetchPropsObj;
};

/**
* Collect xhr.open arguments to object
* @param {string} method
* @param {string} url
* @param {string} async
* @param {string} user
* @param {string} password
* @returns {Object}
*/
export const getXhrData = (method, url, async, user, password) => {
return {
method,
url,
async,
user,
password,
};
};

/**
* Parse propsToMatch input string into object;
* used for prevent-fetch and prevent-xhr
Expand Down
134 changes: 86 additions & 48 deletions src/scriptlets/trusted-replace-xhr-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
parseMatchProps,
validateParsedData,
getMatchPropsData,
matchRequestProps,
getXhrData,
// following helpers should be imported and injected
// because they are used by helpers above
toRegExp,
Expand Down Expand Up @@ -69,7 +71,7 @@ import {
* ```
*/
/* eslint-enable max-len */
export function trustedReplaceXhrResponse(source, pattern, replacement, propsToMatch) {
export function trustedReplaceXhrResponse(source, pattern = '', replacement = '', propsToMatch) {
// do nothing if browser does not support Proxy (e.g. Internet Explorer)
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
if (typeof Proxy === 'undefined') {
Expand All @@ -80,37 +82,38 @@ export function trustedReplaceXhrResponse(source, pattern, replacement, propsToM
return;
}

const origOpen = window.XMLHttpRequest.prototype.open;
const origSend = window.XMLHttpRequest.prototype.send;

const MATCH_ALL_CHARACTERS_REGEX = /[\s\S]/;

let shouldReplace = false;
let responseUrl;
let xhrData;
const requestHeaders = [];

const openWrapper = (target, thisArg, args) => {
// Get method and url from .open()
const xhrData = {
method: args[0],
url: args[1],
};
responseUrl = xhrData.url;
xhrData = getXhrData(...args);

if (pattern === '' && replacement === '') {
// Log if no propsToMatch given
const logMessage = `log: xhr( ${objectToString(xhrData)} )`;
hit(source, logMessage);
} else {
const parsedData = parseMatchProps(propsToMatch);
if (!validateParsedData(parsedData)) {
// eslint-disable-next-line no-console
console.log(`Invalid parameter: ${propsToMatch}`);
shouldReplace = false;
} else {
const matchData = getMatchPropsData(parsedData);
// prevent only if all props match
shouldReplace = Object.keys(matchData)
.every((matchKey) => {
const matchValue = matchData[matchKey];
return Object.prototype.hasOwnProperty.call(xhrData, matchKey)
&& matchValue.test(xhrData[matchKey]);
});
}
shouldReplace = matchRequestProps(propsToMatch, xhrData);
}

// Trap setRequestHeader of target xhr object to mimic request headers later
if (shouldReplace) {
const setRequestHeaderWrapper = (target, thisArg, args) => {
requestHeaders.push(args);
return Reflect.apply(target, thisArg, args);
};

const setRequestHeaderHandler = {
apply: setRequestHeaderWrapper,
};

thisArg.setRequestHeader = new Proxy(thisArg.setRequestHeader, setRequestHeaderHandler);
}

return Reflect.apply(target, thisArg, args);
Expand All @@ -121,35 +124,68 @@ export function trustedReplaceXhrResponse(source, pattern, replacement, propsToM
return Reflect.apply(target, thisArg, args);
}

const parsedPattern = pattern === getWildcardSymbol()
? MATCH_ALL_CHARACTERS_REGEX
: pattern;

const modifiedContent = thisArg.responseText.replace(parsedPattern, replacement);

// Mock response object
Object.defineProperties(thisArg, {
readyState: { value: 4, writable: false },
response: { value: modifiedContent, writable: false },
responseText: { value: modifiedContent, writable: false },
responseURL: { value: responseUrl, writable: false },
responseXML: { value: '', writable: false },
status: { value: 200, writable: false },
statusText: { value: 'OK', writable: false },
const secretXhr = new XMLHttpRequest();
secretXhr.addEventListener('readystatechange', () => {
if (secretXhr.readyState !== 4) {
return;
}

const {
readyState,
response,
responseText,
responseURL,
responseXML,
status,
statusText,
} = secretXhr;

const parsedPattern = pattern === getWildcardSymbol()
? MATCH_ALL_CHARACTERS_REGEX
: pattern;
const content = response || responseText;

const modifiedContent = content.replace(parsedPattern, replacement);

// Manually put required values into target XHR object
// as thisArg can't be redefined and XHR objects can't be (re)assigned or copied
Object.defineProperties(thisArg, {
readyState: { value: readyState },
response: { value: modifiedContent },
responseText: { value: modifiedContent },
responseURL: { value: responseURL },
responseXML: { value: responseXML },
status: { value: status },
statusText: { value: statusText },
});

// Mock events
setTimeout(() => {
const stateEvent = new Event('readystatechange');
thisArg.dispatchEvent(stateEvent);

const loadEvent = new Event('load');
thisArg.dispatchEvent(loadEvent);

const loadEndEvent = new Event('loadend');
thisArg.dispatchEvent(loadEndEvent);
}, 1);

hit(source);
});
// Mock events
setTimeout(() => {
const stateEvent = new Event('readystatechange');
thisArg.dispatchEvent(stateEvent);

const loadEvent = new Event('load');
thisArg.dispatchEvent(loadEvent);
origOpen.apply(secretXhr, [xhrData.method, xhrData.url]);

// Mimic request headers before sending
// setRequestHeader can only be called on open xhrs
requestHeaders.forEach((header) => {
const name = header[0];
const value = header[1];

const loadEndEvent = new Event('loadend');
thisArg.dispatchEvent(loadEndEvent);
}, 1);
secretXhr.setRequestHeader(name, value);
});

hit(source);
origSend.call(secretXhr, args);
return undefined;
};

Expand All @@ -175,6 +211,8 @@ trustedReplaceXhrResponse.injections = [
objectToString,
getWildcardSymbol,
parseMatchProps,
matchRequestProps,
getXhrData,
validateParsedData,
getMatchPropsData,
toRegExp,
Expand Down

0 comments on commit 15f743d

Please sign in to comment.