Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions src/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,28 @@ brws.runtime.onStartup.addListener(() => {
brws.storage.local.set({ version: brws.runtime.getManifest().version });
});

brws.runtime.onMessage.addListener((request, _, sendResponse) => {
// Fields the crowd-contribute/query bridge accepts, matching the documented
// signature of crowdQuery/crowdContribute in helpers/dom.js. Anything else in
// request.detail is dropped rather than forwarded -- the content-script bridge
// has no way to verify the message actually came from the extension's own
// injected script rather than the hosting page itself (see security advisory
// GHSA-vw3r-qv4c-vw79 / GHSA-5vfw-qqg5-35wj-style report), so background.js is
// the last point that can still enforce a shape on what gets sent out.
const CROWD_FIELDS = {
crowdQuery: ['domain', 'path'],
crowdContribute: ['domain', 'path', 'target'],
followAndContribute: ['domain', 'path', 'target'],
};

brws.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'getTabId') {
// sender.tab.id is populated by the browser itself from the real
// connection the message arrived on -- a page script cannot forge it,
// unlike anything carried in a CustomEvent's `detail`.
sendResponse(sender.tab ? sender.tab.id : null);
return false;
}

(async () => {
let options = await getOptions();
if (options.optionCrowdBypass === false) {
Expand All @@ -127,14 +148,24 @@ brws.runtime.onMessage.addListener((request, _, sendResponse) => {
? (url = 'https://crowd.fastforward.team/crowd/query_v1')
: (url = 'https://crowd.fastforward.team/crowd/contribute_v1');

const allowedFields = CROWD_FIELDS[request.type];
if (!allowedFields) {
return;
}

let params = new URLSearchParams();

if (request.type !== 'followAndContribute') {
for (let key in request.detail) {
params.append(key, request.detail[key]);
for (let key of allowedFields) {
if (Object.prototype.hasOwnProperty.call(request.detail, key)) {
params.append(key, request.detail[key]);
}
}
} else {
for (let key in request.detail) {
for (let key of allowedFields) {
if (!Object.prototype.hasOwnProperty.call(request.detail, key)) {
continue;
}
if (key === 'target') {
let dest = new URL(request.detail[key]);
if (!fetchDomains.includes(dest.hostname)) {
Expand Down
27 changes: 21 additions & 6 deletions src/js/content_script.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
const brws = typeof browser !== 'undefined' ? browser : chrome;

// ffclipboard keys are namespaced by the real tab id, as reported by the
// browser itself via background.js's sender.tab.id -- a page script cannot
// forge this the way it can forge a CustomEvent's `detail`. This stops an
// unrelated tab/page from reading or poisoning another tab's in-flight
// ffclipboard state.
const ffClipboardTabId = brws.runtime.sendMessage({ type: 'getTabId' });
async function ffClipboardKey(key) {
const tabId = await ffClipboardTabId;
return `${tabId}:${key}`;
}

async function getOptions() {
return new Promise((resolve) => {
brws.storage.local.get('options').then((result) => {
Expand Down Expand Up @@ -71,31 +83,34 @@ document.addEventListener('ff53054c0e13_followAndContribute', (event) => {
});
});

function onFFClipboardSet(event) {
async function onFFClipboardSet(event) {
const { key, value } = event.detail;
const scopedKey = await ffClipboardKey(key);
chrome.storage.local.get('ffclipboard', (result) => {
const ffclipboard = result.ffclipboard || {};
ffclipboard[key] = value;
ffclipboard[scopedKey] = value;
chrome.storage.local.set({ ffclipboard });
});
}

function onFFClipboardGet(event) {
async function onFFClipboardGet(event) {
const { key } = event.detail;
const scopedKey = await ffClipboardKey(key);
chrome.storage.local.get('ffclipboard', (result) => {
const value = result.ffclipboard ? result.ffclipboard[key] : undefined;
const value = result.ffclipboard ? result.ffclipboard[scopedKey] : undefined;
const responseEvent = new CustomEvent('ff53054c0e13_ffclipboardResponse', {
detail: { key, value },
});
document.dispatchEvent(responseEvent);
});
}

function onFFClipboardClear(event) {
async function onFFClipboardClear(event) {
const { key } = event.detail;
const scopedKey = await ffClipboardKey(key);
chrome.storage.local.get('ffclipboard', (result) => {
if (result.ffclipboard) {
delete result.ffclipboard[key];
delete result.ffclipboard[scopedKey];
chrome.storage.local.set({ ffclipboard: result.ffclipboard });
}
});
Expand Down