mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Add restricted whitelist mode
This commit is contained in:
@@ -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:
|
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/
|
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`
|
||||||
|
|||||||
@@ -314,6 +314,14 @@
|
|||||||
"message": "Enable site whitelist"
|
"message": "Enable site whitelist"
|
||||||
, "description": "Whitelist enabled checkbox label."
|
, "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": {
|
, "optionsUserAgentWhitelistContent": {
|
||||||
"message": "Match patterns (newline-separated):"
|
"message": "Match patterns (newline-separated):"
|
||||||
, "description": "Match patterns editor widget label."
|
, "description": "Match patterns editor widget label."
|
||||||
|
|||||||
@@ -423,13 +423,119 @@ async function initMenus () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function initRequestListener () {
|
async function initWhitelist () {
|
||||||
logger.info("init (request listener)");
|
logger.info("init (whitelist)");
|
||||||
|
|
||||||
|
// Missing on @types/firefox-webext-browser
|
||||||
|
type OnBeforeSendHeadersDetails = Parameters<Parameters<
|
||||||
|
typeof browser.webRequest.onBeforeSendHeaders.addListener>[0]>[0] & {
|
||||||
|
frameAncestors?: Array<{ url: string, frameId: number }>
|
||||||
|
};
|
||||||
type OnBeforeRequestDetails = Parameters<Parameters<
|
type OnBeforeRequestDetails = Parameters<Parameters<
|
||||||
typeof browser.webRequest.onBeforeRequest.addListener>[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, {
|
await browser.tabs.executeScript(details.tabId, {
|
||||||
code: `
|
code: `
|
||||||
window.isFramework = ${
|
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<Parameters<
|
|
||||||
typeof browser.webRequest.onBeforeSendHeaders.addListener>[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 () {
|
async function registerUserAgentWhitelist () {
|
||||||
const { userAgentWhitelist
|
const { userAgentWhitelist
|
||||||
, userAgentWhitelistEnabled } = await options.getAll();
|
, 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders.addListener(
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
handleResourceRequests
|
onWhitelistedBeforeSendHeaders
|
||||||
, { urls: [ "<all_urls>" ] }
|
|
||||||
, [ "blocking", "requestHeaders" ]);
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
||||||
onBeforeSendHeaders
|
|
||||||
, { urls: userAgentWhitelist }
|
, { urls: userAgentWhitelist }
|
||||||
, [ "blocking", "requestHeaders" ]);
|
, [ "blocking", "requestHeaders" ]);
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
onWhitelistedChildBeforeSendHeaders
|
||||||
|
, { urls: [ "<all_urls>" ] }
|
||||||
|
, [ "blocking", "requestHeaders" ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterUserAgentWhitelist () {
|
function unregisterUserAgentWhitelist () {
|
||||||
originUrlCache.length = 0;
|
originUrlCache.length = 0;
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders
|
browser.webRequest.onBeforeSendHeaders
|
||||||
.removeListener(handleResourceRequests);
|
.removeListener(onWhitelistedBeforeSendHeaders);
|
||||||
browser.webRequest.onBeforeSendHeaders
|
browser.webRequest.onBeforeSendHeaders
|
||||||
.removeListener(onBeforeSendHeaders);
|
.removeListener(onWhitelistedChildBeforeSendHeaders);
|
||||||
|
browser.webRequest.onBeforeRequest
|
||||||
|
.removeListener(onBeforeCastSDKRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register on first run
|
// Register on first run
|
||||||
@@ -674,7 +708,6 @@ async function init () {
|
|||||||
await ShimManager.init();
|
await ShimManager.init();
|
||||||
|
|
||||||
await initMenus();
|
await initMenus();
|
||||||
await initRequestListener();
|
|
||||||
await initWhitelist();
|
await initWhitelist();
|
||||||
await initMediaOverlay();
|
await initMediaOverlay();
|
||||||
await initBrowserAction();
|
await initBrowserAction();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
, receiverSelectorCloseIfFocusLost: true
|
, receiverSelectorCloseIfFocusLost: true
|
||||||
, receiverSelectorWaitForConnection: true
|
, receiverSelectorWaitForConnection: true
|
||||||
, userAgentWhitelistEnabled: true
|
, userAgentWhitelistEnabled: true
|
||||||
|
, userAgentWhitelistRestrictedEnabled: true
|
||||||
, userAgentWhitelist: [
|
, userAgentWhitelist: [
|
||||||
"https://www.netflix.com/*"
|
"https://www.netflix.com/*"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface Options {
|
|||||||
receiverSelectorCloseIfFocusLost: boolean;
|
receiverSelectorCloseIfFocusLost: boolean;
|
||||||
receiverSelectorWaitForConnection: boolean;
|
receiverSelectorWaitForConnection: boolean;
|
||||||
userAgentWhitelistEnabled: boolean;
|
userAgentWhitelistEnabled: boolean;
|
||||||
|
userAgentWhitelistRestrictedEnabled: boolean;
|
||||||
userAgentWhitelist: string[];
|
userAgentWhitelist: string[];
|
||||||
|
|
||||||
[key: string]: Options[keyof Options];
|
[key: string]: Options[keyof Options];
|
||||||
|
|||||||
@@ -386,6 +386,24 @@ class OptionsApp extends Component<{}, OptionsAppState> {
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label className="option option--inline">
|
||||||
|
<div className="option__control">
|
||||||
|
<input name="userAgentWhitelistRestrictedEnabled"
|
||||||
|
type="checkbox"
|
||||||
|
checked={ this.state.options?.userAgentWhitelistRestrictedEnabled }
|
||||||
|
onChange={ this.handleInputChange } />
|
||||||
|
</div>
|
||||||
|
<div className="option__label">
|
||||||
|
{ _("optionsUserAgentWhitelistRestrictedEnabled") }
|
||||||
|
<span className="option__recommended">
|
||||||
|
{ _("optionsOptionRecommended") }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="option__description">
|
||||||
|
{ _("optionsUserAgentWhitelistRestrictedEnabledDescription") }
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div className="option">
|
<div className="option">
|
||||||
<div className="option__label">
|
<div className="option__label">
|
||||||
{ _("optionsUserAgentWhitelistContent") }
|
{ _("optionsUserAgentWhitelistContent") }
|
||||||
|
|||||||
Reference in New Issue
Block a user