diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e0d6c6..0fd25f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,3 +20,21 @@ Compatibility reports are always helpful. Use the "Compatibility Report" issue t Either fork and edit the messages files manually or to easily add/edit localizations, use the web-ext-translator tool: https://lusito.github.io/web-ext-translator/?gh=https://github.com/hensm/fx_cast/ + + +Missing/outdated strings: + +* `de` + * `optionsBridgeBackupEnabled` + * `optionsUserAgentWhitelistRestrictedEnabled` + * `optionsUserAgentWhitelistRestrictedEnabledDescription` + +* `es` + * `optionsBridgeBackupEnabled` + * `optionsUserAgentWhitelistRestrictedEnabled` + * `optionsUserAgentWhitelistRestrictedEnabledDescription` + +* `nl` + * `optionsBridgeBackupEnabled` + * `optionsUserAgentWhitelistRestrictedEnabled` + * `optionsUserAgentWhitelistRestrictedEnabledDescription` diff --git a/ext/src/_locales/en/messages.json b/ext/src/_locales/en/messages.json index e782616..c083921 100755 --- a/ext/src/_locales/en/messages.json +++ b/ext/src/_locales/en/messages.json @@ -314,6 +314,14 @@ "message": "Enable site whitelist" , "description": "Whitelist enabled checkbox label." } + , "optionsUserAgentWhitelistRestrictedEnabled": { + "message": "Enable restricted mode" + , "description": "Whitelist restricted mode enabled checkbox label." + } + , "optionsUserAgentWhitelistRestrictedEnabledDescription": { + "message": "Also apply whitelist restrictions to sites attempting to load cast functionality regardless of the current user agent." + , "description": "Whitelist restricted mode enabled description." + } , "optionsUserAgentWhitelistContent": { "message": "Match patterns (newline-separated):" , "description": "Match patterns editor widget label." diff --git a/ext/src/background/background.ts b/ext/src/background/background.ts index 96002aa..347be68 100755 --- a/ext/src/background/background.ts +++ b/ext/src/background/background.ts @@ -423,13 +423,119 @@ async function initMenus () { } -async function initRequestListener () { - logger.info("init (request listener)"); +async function initWhitelist () { + logger.info("init (whitelist)"); + // Missing on @types/firefox-webext-browser + type OnBeforeSendHeadersDetails = Parameters[0]>[0] & { + frameAncestors?: Array<{ url: string, frameId: number }> + }; type OnBeforeRequestDetails = Parameters[0]>[0]; + typeof browser.webRequest.onBeforeRequest.addListener>[0]>[0] & { + frameAncestors?: Array<{ url: string, frameId: number }> + }; + + + const originUrlCache: string[] = []; + const chromeUserAgent = getChromeUserAgent( + (await browser.runtime.getPlatformInfo()).os, true); + + /** + * Web apps usually only load the sender library and + * provide cast functionality if the browser is detected + * as Chrome, so we should rewrite the User-Agent header + * to reflect this on whitelisted sites. + */ + async function onWhitelistedBeforeSendHeaders ( + details: OnBeforeSendHeadersDetails) { + + if (!details.requestHeaders) { + throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders."); + } + + if (details.originUrl && !originUrlCache.includes(details.originUrl)) { + originUrlCache.push(details.originUrl); + } + + const host = details.requestHeaders.find( + header => header.name === "Host"); + + for (const header of details.requestHeaders) { + if (header.name === "User-Agent") { + header.value = chromeUserAgent; + break; + } + } + + return { + requestHeaders: details.requestHeaders + }; + } + + /** + * Requests from within child frames should also adopt + * the modified User-Agent header to support embedded + * players on other origins (like CDN domains) when the + * main site is whitelisted. + */ + function onWhitelistedChildBeforeSendHeaders ( + details: OnBeforeSendHeadersDetails) { + + if (!details.requestHeaders || !details.frameAncestors) { + return; + } + + for (const ancestor of details.frameAncestors) { + if (originUrlCache.includes(ancestor.url)) { + for (const header of details.requestHeaders) { + if (header.name === "User-Agent") { + header.value = chromeUserAgent; + break; + } + } + + return { + requestHeaders: details.requestHeaders + }; + } + } + } + + + /** + * Sender applications load a cast_sender.js script that + * functions as a loader for the internal chrome-extension: + * hosted script. + * + * We can redirect this and inject our own script to setup + * the API shim. + */ + async function onBeforeCastSDKRequest (details: OnBeforeRequestDetails) { + if (!details.originUrl) { + return; + } + + // Check against whitelist if restricted mode is enabled + if (await options.get("userAgentWhitelistRestrictedEnabled")) { + if (!details?.frameAncestors?.length) { + if (!originUrlCache.includes(details.originUrl)) { + return; + } + } else { + let hasMatchingAncestor = false; + for (const ancestor of details.frameAncestors) { + if (originUrlCache.includes(ancestor.url)) { + hasMatchingAncestor = true; + } + } + + if (!hasMatchingAncestor) { + return; + } + } + } - async function onBeforeRequest (details: OnBeforeRequestDetails) { await browser.tabs.executeScript(details.tabId, { code: ` window.isFramework = ${ @@ -450,114 +556,42 @@ async function initRequestListener () { }; } - /** - * Sender applications load a cast_sender.js script that - * functions as a loader for the internal chrome-extension: - * hosted script. - * - * We can redirect this and inject our own script to setup - * the API shim. - */ - browser.webRequest.onBeforeRequest.addListener( - onBeforeRequest - , { urls: [ - CAST_LOADER_SCRIPT_URL - , CAST_FRAMEWORK_LOADER_SCRIPT_URL ]} - , [ "blocking" ]); -} - - -async function initWhitelist () { - logger.info("init (whitelist)"); - - type OnBeforeSendHeadersDetails = Parameters[0]>[0] & { - // Missing on @types/firefox-webext-browser - frameAncestors?: Array<{ url: string, frameId: number }> - }; - - const originUrlCache: string[] = []; - const chromeUserAgent = getChromeUserAgent( - (await browser.runtime.getPlatformInfo()).os, true); - - /** - * Web apps usually only load the sender library and - * provide cast functionality if the browser is detected - * as Chrome, so we should rewrite the User-Agent header - * to reflect this on whitelisted sites. - */ - async function onBeforeSendHeaders (details: OnBeforeSendHeadersDetails) { - if (!details.requestHeaders) { - throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders."); - } - - if (details.originUrl && !originUrlCache.includes(details.originUrl)) { - originUrlCache.push(details.originUrl); - } - - const host = details.requestHeaders.find( - header => header.name === "Host"); - - for (const header of details.requestHeaders) { - const { os } = await browser.runtime.getPlatformInfo(); - - if (header.name === "User-Agent") { - header.value = chromeUserAgent; - break; - } - } - - return { - requestHeaders: details.requestHeaders - }; - } - - function handleResourceRequests (details: OnBeforeSendHeadersDetails) { - if (!details.requestHeaders || !details.frameAncestors) { - return; - } - - for (const ancestor of details.frameAncestors) { - if (originUrlCache.includes(ancestor.url)) { - for (const header of details.requestHeaders) { - if (header.name === "User-Agent") { - header.value = chromeUserAgent; - } - } - - return { - requestHeaders: details.requestHeaders - }; - } - } - } async function registerUserAgentWhitelist () { const { userAgentWhitelist , userAgentWhitelistEnabled } = await options.getAll(); - if (!userAgentWhitelistEnabled) { + browser.webRequest.onBeforeRequest.addListener( + onBeforeCastSDKRequest + , { urls: [ + CAST_LOADER_SCRIPT_URL + , CAST_FRAMEWORK_LOADER_SCRIPT_URL ]} + , [ "blocking" ]); + + if (!userAgentWhitelistEnabled || !userAgentWhitelist.length) { return; } browser.webRequest.onBeforeSendHeaders.addListener( - handleResourceRequests - , { urls: [ "" ] } - , [ "blocking", "requestHeaders" ]); - - browser.webRequest.onBeforeSendHeaders.addListener( - onBeforeSendHeaders + onWhitelistedBeforeSendHeaders , { urls: userAgentWhitelist } , [ "blocking", "requestHeaders" ]); + + browser.webRequest.onBeforeSendHeaders.addListener( + onWhitelistedChildBeforeSendHeaders + , { urls: [ "" ] } + , [ "blocking", "requestHeaders" ]); } function unregisterUserAgentWhitelist () { originUrlCache.length = 0; browser.webRequest.onBeforeSendHeaders - .removeListener(handleResourceRequests); + .removeListener(onWhitelistedBeforeSendHeaders); browser.webRequest.onBeforeSendHeaders - .removeListener(onBeforeSendHeaders); + .removeListener(onWhitelistedChildBeforeSendHeaders); + browser.webRequest.onBeforeRequest + .removeListener(onBeforeCastSDKRequest); } // Register on first run @@ -674,7 +708,6 @@ async function init () { await ShimManager.init(); await initMenus(); - await initRequestListener(); await initWhitelist(); await initMediaOverlay(); await initBrowserAction(); diff --git a/ext/src/defaultOptions.ts b/ext/src/defaultOptions.ts index be96ba2..0e285ec 100644 --- a/ext/src/defaultOptions.ts +++ b/ext/src/defaultOptions.ts @@ -21,6 +21,7 @@ export default { , receiverSelectorCloseIfFocusLost: true , receiverSelectorWaitForConnection: true , userAgentWhitelistEnabled: true + , userAgentWhitelistRestrictedEnabled: true , userAgentWhitelist: [ "https://www.netflix.com/*" ] diff --git a/ext/src/lib/options.ts b/ext/src/lib/options.ts index 31f20ca..7de072c 100644 --- a/ext/src/lib/options.ts +++ b/ext/src/lib/options.ts @@ -30,6 +30,7 @@ export interface Options { receiverSelectorCloseIfFocusLost: boolean; receiverSelectorWaitForConnection: boolean; userAgentWhitelistEnabled: boolean; + userAgentWhitelistRestrictedEnabled: boolean; userAgentWhitelist: string[]; [key: string]: Options[keyof Options]; diff --git a/ext/src/ui/options/index.tsx b/ext/src/ui/options/index.tsx index e8cabfe..143a60f 100644 --- a/ext/src/ui/options/index.tsx +++ b/ext/src/ui/options/index.tsx @@ -386,6 +386,24 @@ class OptionsApp extends Component<{}, OptionsAppState> { + +
{ _("optionsUserAgentWhitelistContent") }