Skip to content

Commit

Permalink
Add handling for dynamic strings
Browse files Browse the repository at this point in the history
  • Loading branch information
aselbie committed Jun 13, 2024
1 parent 089967f commit d35a446
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 8 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"devDependencies": {
"@rollup/plugin-eslint": "^9.0.5",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.5",
"@types/chrome": "^0.0.248",
"@types/jest": "^29.4.0",
Expand All @@ -33,5 +34,8 @@
"ts-jest": "^29.0.0",
"tslib": "^2.5.0",
"typescript": "^5.2.2"
},
"dependencies": {
"acorn": "^8.11.3"
}
}
3 changes: 2 additions & 1 deletion rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {Plugin, RollupOptions} from 'rollup';
import cleanOnce from './build/rollup-plugin-clean-once';
import eslintPlugin from '@rollup/plugin-eslint';
import typescript from '@rollup/plugin-typescript';
import nodeResolve from '@rollup/plugin-node-resolve';
import prettierBuildStart from './build/rollup-plugin-prettier-build-start';
import staticFiles from './build/rollup-plugin-static-files';
import watch from './build/rollup-plugin-watch-additional';
Expand Down Expand Up @@ -45,7 +46,7 @@ const config: Array<RollupOptions> = contentScriptSteps.concat([
file: `dist/${target}/background.js`,
format: 'iife',
})),
plugins: [typescript(), prettierSrc(), eslint()],
plugins: [typescript(), prettierSrc(), eslint(), nodeResolve()],
},
{
input: 'src/js/popup.ts',
Expand Down
55 changes: 55 additions & 0 deletions src/js/__tests__/removeDynamicStrings-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

import {removeDynamicStrings} from '../background/removeDynamicStrings';

describe('removeDynamicStrings', () => {
it('Handles different quote types', () => {
expect(
removeDynamicStrings(`const foo = /*BTDS*/'dynamic string';`),
).toEqual(`const foo = /*BTDS*/'';`);
expect(
removeDynamicStrings(`const foo = /*BTDS*/"dynamic string";`),
).toEqual(`const foo = /*BTDS*/"";`);
});
it('Handles strings in different scenarios', () => {
expect(removeDynamicStrings(`/*BTDS*/'dynamic string';`)).toEqual(
`/*BTDS*/'';`,
);
expect(
removeDynamicStrings(
`/*BTDS*/'dynamic string' + /*BTDS*/'dynamic string';`,
),
).toEqual(`/*BTDS*/'' + /*BTDS*/'';`);
expect(
removeDynamicStrings(`const foo = JSON.parse(/*BTDS*/'dynamic string');`),
).toEqual(`const foo = JSON.parse(/*BTDS*/'');`);
expect(
removeDynamicStrings("`before ${/*BTDS*/'dynamic string'} after`;"),
).toEqual("`before ${/*BTDS*/''} after`;");
});
it('Handles multiple strings', () => {
expect(
removeDynamicStrings(
`/*BTDS*/'dynamic string';/*BTDS*/'dynamic string';/*BTDS*/'dynamic string';`,
),
).toEqual(`/*BTDS*/'';/*BTDS*/'';/*BTDS*/'';`);
});
it('Handles strings across line breaks', () => {
expect(
removeDynamicStrings(`/*BTDS*/'dynamic \
string';`),
).toEqual(`/*BTDS*/'';`);
});
it('Throws if parsing fails', () => {
expect(() =>
removeDynamicStrings(`const foo = JSON.parse(/*BTDS*/'dynamic string';`),
).toThrow();
});
});
51 changes: 51 additions & 0 deletions src/js/__tests__/serviceWorkerE2E-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

import {removeDynamicStrings} from '../background/removeDynamicStrings';
import {createHash} from 'crypto';

const packages = getSampleResponse().split('/*FB_PKG_DELIM*/\n');
const trimmedPackages = packages.map(pkg => pkg.trimStart());
const packagesWithStringRemoved = trimmedPackages.map(removeDynamicStrings);
const hashes = packagesWithStringRemoved.map(pkg =>
createHash('sha256').update(pkg).digest('hex'),
);

describe('serviceWorker end to end', () => {
it('Should split on the delimiter', () => {
expect(packages).toHaveLength(4);
});
it('Should identify packages with dynamic strings', () => {
expect(packages.filter(pkg => pkg.includes('/*BTDS*/'))).toHaveLength(3);
});
it('Should hash the dynamic packages', () => {
expect(hashes).toEqual([
'4c8dc7ae702b26a0bae9c60f93f8bb6e233c406b119c6d9f0289d9a304c4257f',
'61a916dff3b6a2f84e1e67fa4b17dc94c340f37ffb6b203a5a1b418d143a08ae',
'12af311c30cdf809dfd3f6fb3597c93f4366dd837aa48318e806e7621ebe5058',
'94dd9337b54dd0f617468a3c15010252353c1441cbc85dfd540022c566ac8075',
]);
});
});

function getSampleResponse() {
return `__DEV__=1;typeof window === 'object' && Object.defineProperty(window,'__DEV__',{set(){},get(){return 1}});/*FB_PKG_DELIM*/
self.__swData=JSON.parse(/*BTDS*/"{}");/*FB_PKG_DELIM*/
self.SW_LOGGING_URL = /*BTDS*/"https:\/\/www.my-od.instagram.com\/sw\/log\/init\/";/*FB_PKG_DELIM*/
if (self.trustedTypes && self.trustedTypes.createPolicy) {
const escapeScriptURLPolicy = self.trustedTypes.createPolicy("workerPolicy", {
createScriptURL: url => url
});
importScripts(escapeScriptURLPolicy.createScriptURL(/*BTDS*/"https:\/\/www.my-od.facebook.com\/intern\/rsrc.php\/v3\/yo\/js\/service_workers\/InstagramWebServiceWorker.bundle.js?_=er_xlCBVeZr&p_content_refs=readable&p_js_user_agent=modern_es6&p_function_visitor=on&p_min=no_min&p_inline_require_opt=no_regen&p_forget=off&p_patch=off&p_locale=en_US&origin=www.my-od.instagram.com&sourcemaps=1&_nc_x=Ij3Wp8lg5Kz"));
} else {
importScripts(/*BTDS*/"https:\/\/www.my-od.facebook.com\/intern\/rsrc.php\/v3\/yo\/js\/service_workers\/InstagramWebServiceWorker.bundle.js?_=er_xlCBVeZr&p_content_refs=readable&p_js_user_agent=modern_es6&p_function_visitor=on&p_min=no_min&p_inline_require_opt=no_regen&p_forget=off&p_patch=off&p_locale=en_US&origin=www.my-od.instagram.com&sourcemaps=1&_nc_x=Ij3Wp8lg5Kz");
}
`;
}
12 changes: 11 additions & 1 deletion src/js/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {Origin, STATES} from './config';
import {DYNAMIC_STRING_MARKER, Origin, STATES} from './config';
import {MESSAGE_TYPE, ORIGIN_HOST, ORIGIN_TIMEOUT} from './config';

import {
Expand All @@ -16,6 +16,7 @@ import setupCSPListener from './background/setupCSPListener';
import setUpWebRequestsListener from './background/setUpWebRequestsListener';
import {validateMetaCompanyManifest} from './background/validateMetaCompanyManifest';
import {validateSender} from './background/validateSender';
import {removeDynamicStrings} from './background/removeDynamicStrings';
import {MessagePayload, MessageResponse} from './shared/MessageTypes';
import {setOrUpdateSetInMap} from './shared/nestedDataHelpers';

Expand Down Expand Up @@ -149,6 +150,15 @@ function handleMessages(
return;
}

if (message.rawjs.includes(DYNAMIC_STRING_MARKER)) {
try {
message.rawjs = removeDynamicStrings(message.rawjs);
} catch (e) {
sendResponse({valid: false, reason: 'failed parsing AST'});
return;
}
}

// fetch the src
const encoder = new TextEncoder();
const encodedJS = encoder.encode(message.rawjs);
Expand Down
47 changes: 47 additions & 0 deletions src/js/background/removeDynamicStrings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {Parser} from 'acorn';
import {DYNAMIC_STRING_MARKER} from '../config';

const markerLength = DYNAMIC_STRING_MARKER.length;

// This variable, and the parser, are reused because extending the
// parser is surprisingly expensive. This is currently safe because
// `removeDynamicStrings` is synchronous.
let ranges = [0];
function plugin(BaseParser: typeof Parser): typeof Parser {
// @ts-ignore third party typing doesn't support extension well.
return class extends BaseParser {
parseLiteral(value: string) {
// @ts-ignore third party typing doesn't support extension well.
const node = super.parseLiteral(value);
const before = this.input.substring(
node.start - markerLength,
node.start,
);
if (before === DYNAMIC_STRING_MARKER) {
ranges.push(node.start + 1, node.end - 1);
}
return node;
}
};
}

const extendedParser = Parser.extend(plugin);

export function removeDynamicStrings(rawjs: string): string {
ranges = [0];
extendedParser.parse(rawjs, {ecmaVersion: 'latest'});

let result = '';
for (let i = 0; i < ranges.length; i += 2) {
result += rawjs.substring(ranges[i], ranges[i + 1]);
}

return result;
}
2 changes: 2 additions & 0 deletions src/js/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,5 @@ export const DOWNLOAD_JS_ENABLED =
'CompressionStream' in window && 'showSaveFilePicker' in window;

export const MANIFEST_TIMEOUT = 45000;

export const DYNAMIC_STRING_MARKER = '/*BTDS*/';
25 changes: 19 additions & 6 deletions src/js/contentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const FOUND_MANIFEST_VERSIONS = new Set<string>();
type ScriptDetailsWithSrc = {
otherType: string;
src: string;
isServiceWorker?: boolean;
};
type ScriptDetailsRaw = {
type: typeof MESSAGE_TYPE.RAW_JS;
Expand Down Expand Up @@ -355,6 +356,11 @@ async function processJSWithSrc(
await alertBackgroundOfImminentFetch(script.src);
const sourceResponse = await fetch(script.src, {
method: 'GET',
// When the browser fetches a service worker it adds this header.
// If this is missing it will cause a cache miss, resulting in invalidation.
headers: script.isServiceWorker
? {'Service-Worker': 'script'}
: undefined,
});
if (DOWNLOAD_JS_ENABLED) {
const fileNameArr = script.src.split('/');
Expand Down Expand Up @@ -558,12 +564,6 @@ chrome.runtime.onMessage.addListener(request => {
);
} else if (request.greeting === 'checkIfScriptWasProcessed') {
if (isUserLoggedIn && !ALL_FOUND_SCRIPT_TAGS.has(request.response.url)) {
if (
'serviceWorker' in navigator &&
navigator.serviceWorker.controller?.scriptURL === request.response.url
) {
return;
}
const hostname = window.location.hostname;
const resourceURL = new URL(request.response.url);
if (resourceURL.hostname === hostname) {
Expand Down Expand Up @@ -593,6 +593,7 @@ chrome.runtime.onMessage.addListener(request => {
uninitializedScripts.push({
src: request.response.url,
otherType: currentFilterType,
isServiceWorker: hasServiceWorkerHeader(request.response),
});
}
updateCurrentState(STATES.PROCESSING);
Expand All @@ -614,3 +615,15 @@ chrome.runtime.onMessage.addListener(request => {
}
}
});

function hasServiceWorkerHeader(
response: chrome.webRequest.WebResponseCacheDetails,
): boolean {
return (
response.responseHeaders?.find(
header =>
header.name.includes('vary') &&
header.value?.includes('Service-Worker'),
) !== undefined
);
}
39 changes: 39 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,18 @@
"@rollup/pluginutils" "^5.0.1"
eslint "^8.24.0"

"@rollup/plugin-node-resolve@^15.2.3":
version "15.2.3"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9"
integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==
dependencies:
"@rollup/pluginutils" "^5.0.1"
"@types/resolve" "1.20.2"
deepmerge "^4.2.2"
is-builtin-module "^3.2.1"
is-module "^1.0.0"
resolve "^1.22.1"

"@rollup/plugin-typescript@^11.1.5":
version "11.1.5"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz#039c763bf943a5921f3f42be255895e75764cb91"
Expand Down Expand Up @@ -817,6 +829,11 @@
dependencies:
undici-types "~5.25.1"

"@types/[email protected]":
version "1.20.2"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==

"@types/semver@^7.5.0":
version "7.5.4"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff"
Expand Down Expand Up @@ -967,6 +984,11 @@ acorn@^8.1.0, acorn@^8.8.1, acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==

acorn@^8.11.3:
version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==

agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
Expand Down Expand Up @@ -1174,6 +1196,11 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==

builtin-modules@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==

callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
Expand Down Expand Up @@ -1972,6 +1999,13 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==

is-builtin-module@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
dependencies:
builtin-modules "^3.3.0"

is-core-module@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
Expand Down Expand Up @@ -2001,6 +2035,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
dependencies:
is-extglob "^2.1.1"

is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==

is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
Expand Down

0 comments on commit d35a446

Please sign in to comment.