Skip to content

Commit

Permalink
Sanitize data URL in offscreen document (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
asamuzaK authored Sep 9, 2023
1 parent d0999b2 commit 03992b4
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"scripts": {
"bundle": "npm-run-all -s bundle-*",
"bundle-compat": "node index compat -ci",
"bundle-copy": "copyfiles --up=1 --verbose src/LICENSE src/_locales/**/*.json src/css/*.css src/html/*.html src/img/*.* src/lib/**/* src/mjs/*.js src/js/*.js --exclude=src/mjs/background.js --exclude=src/mjs/exec-copy.js --exclude=src/mjs/offscreen.js --exclude=src/mjs/options.js --exclude=src/mjs/popup.js bundle",
"bundle-copy": "copyfiles --up=1 --verbose src/LICENSE src/_locales/**/*.json src/css/*.css src/html/*.html src/img/*.* src/lib/**/* src/mjs/*.js src/js/*.js --exclude=src/mjs/background.js --exclude=src/mjs/exec-copy.js --exclude=src/mjs/offscreen.js --exclude=src/mjs/options.js --exclude=src/mjs/popup.js --exclude=src/mjs/sanitize.js bundle",
"bundle-repl": "copyfiles --up=2 --verbose src/repl/*.js bundle/mjs",
"include": "npm-run-all -s include-*",
"include-browser": "copyfiles --up=3 --verbose node_modules/webext-schema/modules/browser.js src/mjs",
Expand Down
1 change: 1 addition & 0 deletions src/mjs/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const TEXT_TEXT_URL = 'TextURL';
export const TEXT_URL_ONLY = 'URLOnly';
export const THEME_DARK = '[email protected]';
export const THEME_LIGHT = '[email protected]';
export const URL_SANITIZE = 'sanitizeURL';
export const USER_INPUT = 'userInput';
export const USER_INPUT_DEFAULT = 'Edit content text of the link';
export const WEBEXT_ID = '[email protected]';
3 changes: 1 addition & 2 deletions src/mjs/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
*/

/* shared */
import '../lib/purify/purify.min.js';
import { sanitizeURL } from '../lib/url/url-sanitizer-wo-dompurify.min.js';
import {
executeScriptToTab, getActiveTab, getActiveTabId, getAllStorage,
getAllTabsInWindow, getHighlightedTab, getStorage, isTab, queryTabs,
Expand All @@ -23,6 +21,7 @@ import {
createContextMenu, removeContextMenu, updateContextMenu
} from './menu.js';
import { notifyOnCopy } from './notify.js';
import { sanitize as sanitizeURL } from './sanitize.js';
import {
BBCODE_URL, CMD_COPY, CONTEXT_INFO, CONTEXT_INFO_GET,
COPY_LINK, COPY_PAGE, COPY_TAB, COPY_TABS_ALL, COPY_TABS_OTHER,
Expand Down
19 changes: 15 additions & 4 deletions src/mjs/offscreen-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
*/

/* shared */
import '../lib/purify/purify.min.js';
import { sanitizeURL } from '../lib/url/url-sanitizer-wo-dompurify.min.js';
import { Clip } from './clipboard.js';
import { EXEC_COPY, MIME_HTML, MIME_PLAIN, NOTIFY_COPY } from './constant.js';
import { throwErr } from './common.js';
import {
EXEC_COPY, MIME_HTML, MIME_PLAIN, NOTIFY_COPY, URL_SANITIZE
} from './constant.js';

/* api */
const { runtime } = browser;
Expand Down Expand Up @@ -41,19 +46,25 @@ export const closeWindow = () => {
* @param {object} msg - message
* @returns {Promise.<Array>} - results of each handler
*/
export const handleMsg = async msg => {
export const handleMsg = msg => {
const func = [];
const items = msg && Object.entries(msg);
if (items) {
for (const item of items) {
const [key, value] = item;
switch (key) {
case EXEC_COPY:
case EXEC_COPY: {
func.push(execCopy(value).then(closeWindow));
break;
}
case URL_SANITIZE: {
const [url, opt] = value;
func.push(sanitizeURL(url, opt));
break;
}
default:
}
}
}
return Promise.all(func);
return Promise.all(func).catch(throwErr);
};
5 changes: 1 addition & 4 deletions src/mjs/offscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
*/

/* shared */
import { throwErr } from './common.js';
import { handleMsg } from './offscreen-main.js';

/* api */
const { runtime } = browser;

/* listener */
runtime.onMessage.addListener((msg, sender) =>
handleMsg(msg, sender).catch(throwErr)
);
runtime.onMessage.addListener(handleMsg);
18 changes: 18 additions & 0 deletions src/mjs/sanitize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* sanitize.js
*/

/* shared */
import '../lib/purify/purify.min.js';
import { sanitizeURL } from '../lib/url/url-sanitizer-wo-dompurify.min.js';

/**
* sanitize URL
* @param {string} url - URL
* @param {object} opt - options
* @returns {?string} - sanitized URL
*/
export const sanitize = async (url, opt) => {
const res = await sanitizeURL(url, opt);
return res || null;
};
41 changes: 41 additions & 0 deletions src/repl/sanitize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* sanitize.js
*/

/* shared */
import { sanitizeURL } from '../lib/url/url-sanitizer-wo-dompurify.min.js';
import { isString } from '../mjs/common.js';
import { URL_SANITIZE } from '../mjs/constant.js';

/* api */
const { offscreen, runtime } = chrome;

/**
* sanitize URL
* @param {string} url - URL
* @param {object} opt - options
* @returns {?string} - sanitized URL
*/
export const sanitize = async (url, opt) => {
let res;
if (url && isString(url)) {
const { protocol } = new URL(url);
if (protocol === 'data:') {
await offscreen.createDocument({
justification: 'Sanitize URL',
reasons: [offscreen.Reason.DOM_PARSER],
url: 'html/offscreen.html'
});
[res] = await runtime.sendMessage({
[URL_SANITIZE]: [
url,
opt
]
});
await offscreen.closeDocument();
} else {
res = await sanitizeURL(url, opt);
}
}
return res || null;
};
15 changes: 14 additions & 1 deletion test/offscreen-main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import sinon from 'sinon';
import { browser, createJsdom } from './mocha/setup.js';

/* test */
import { EXEC_COPY, MIME_PLAIN } from '../src/mjs/constant.js';
import { EXEC_COPY, MIME_PLAIN, URL_SANITIZE } from '../src/mjs/constant.js';
import * as mjs from '../src/mjs/offscreen-main.js';

describe('offscreen-main', () => {
Expand Down Expand Up @@ -145,5 +145,18 @@ describe('offscreen-main', () => {
const res = await func({ [EXEC_COPY]: {} });
assert.deepEqual(res, [undefined], 'result');
});

it('should get array', async () => {
const res = await func({
[URL_SANITIZE]: [
'data:,https://example.com/#<script>alert(1);</script>',
{
allow: ['data', 'file'],
remove: true
}
]
});
assert.deepEqual(res, ['data:,https://example.com/'], 'result');
});
});
});
88 changes: 88 additions & 0 deletions test/sanitize.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* sanitize.test.js
*/
/* eslint-disable import/order */

/* api */
import { assert } from 'chai';
import { afterEach, beforeEach, describe, it } from 'mocha';
import { browser, createJsdom } from './mocha/setup.js';

/* test */
import * as mjs from '../src/mjs/sanitize.js';

describe('sanitize', () => {
const globalKeys = [
'Blob',
'ClipboardItem',
'DOMParser',
'DOMPurify',
'HTMLUnknownElement',
'Node',
'XMLSerializer'
];
let window, document;
beforeEach(() => {
const dom = createJsdom();
window = dom && dom.window;
document = window.document;
browser._sandbox.reset();
browser.i18n.getMessage.callsFake((...args) => args.toString());
browser.permissions.contains.resolves(true);
browser.storage.local.get.resolves({});
global.browser = browser;
global.window = window;
global.document = document;
for (const key of globalKeys) {
global[key] = window[key];
}
});
afterEach(() => {
window = null;
document = null;
delete global.browser;
delete global.window;
delete global.document;
for (const key of globalKeys) {
delete global[key];
}
browser._sandbox.reset();
});

describe('sanitize URL', () => {
const func = mjs.sanitize;

it('should get null', async () => {
const res = await func();
assert.isNull(res, 'result');
});

it('should get null', async () => {
const res = await func('foo');
assert.isNull(res, 'result');
});

it('should get result', async () => {
const res = await func('https://example.com/"onclick="alert(1)"', {
remove: true
});
assert.strictEqual(res, 'https://example.com/', 'result');
});

it('should get result', async () => {
const res = await func('https://example.com/"onclick="alert(1)"', {
remove: true
});
assert.strictEqual(res, 'https://example.com/', 'result');
});

it('should get result', async () => {
const res =
await func('data:,https://example.com/#<script>alert(1);</script>', {
allow: ['data'],
remove: true
});
assert.strictEqual(res, 'data:,https://example.com/', 'result');
});
});
});

0 comments on commit 03992b4

Please sign in to comment.