Skip to content
Draft
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
1 change: 1 addition & 0 deletions .addonslinterignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
# Setting innerHTML in rester-dom-purify-frame.html should be fine because HTML
# is sanitized with DOMPurify.
site/scripts/bundle.js UNSAFE_VAR_ASSIGNMENT
<no_file> JSON_INVALID "/manifest_version" must be <= 2
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"author": "Jan Kühle <jkuehle90@gmail.com> (https://kuehle.me)",
"repository": "frigus02/RESTer",
"scripts": {
"start": "yarn run clean:build && concurrently \"yarn run build:files -w\" \"yarn run webpack --progress --mode development --devtool source-map --watch\"",
"start": "yarn run clean:build && concurrently \"yarn run build:files development\" \"yarn run webpack --progress --mode development --devtool source-map --watch\"",
"clean:build": "rimraf build",
"clean:package": "rimraf package",
"build": "yarn run clean:build && yarn run build:files && yarn run build:site",
"build:files": "cpx \"src/{images/**,manifest.json}\" build/",
"build": "yarn run clean:build && yarn run build:files production && yarn run build:site",
"build:files": "node tools/scripts/copy-manifest-and-icons.mjs",
"build:site": "webpack --mode production",
"lint": "yarn run lint:javascript && yarn run lint:firefox",
"lint:javascript": "eslint .",
"lint:firefox": "node tools/scripts/lint-firefox-addon.mjs build/",
"lint:firefox": "node tools/scripts/lint-firefox-addon.mjs build/firefox/",
"format": "prettier --write \"**/*.{js,mjs,md}\" !build/**",
"test": "jest --env jsdom \"src/.+\\.test\\.js$\"",
"test:e2e": "jest \"test-e2e/.+\\.test\\.js$\"",
Expand Down Expand Up @@ -77,7 +77,6 @@
"chalk": "5.2.0",
"concurrently": "7.6.0",
"copy-webpack-plugin": "11.0.0",
"cpx2": "4.2.0",
"eslint": "8.36.0",
"eslint-config-crockford": "2.0.0",
"eslint-config-prettier": "8.7.0",
Expand Down
41 changes: 17 additions & 24 deletions src/background/data/utils/data-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class DataStore extends CustomEventTarget {
const actions = {};

['add', 'put', 'delete'].forEach((action) => {
actions[action] = function (tableName, entity) {
actions[action] = function(tableName, entity) {
if (!queue[tableName]) {
queue[tableName] = [];
}
Expand All @@ -72,7 +72,7 @@ class DataStore extends CustomEventTarget {
};
});

actions.execute = function () {
actions.execute = function() {
return dataStore._withWriteLock((changes) => {
const result = [];

Expand Down Expand Up @@ -335,33 +335,26 @@ class DataStore extends CustomEventTarget {
return this._performStorageLocalOperation('remove', keys);
}

_performStorageLocalOperation(
async _performStorageLocalOperation(
op,
keys,
transformSuccessResult = (arg) => arg
) {
return new Promise((resolve, reject) => {
const start = performance.now();
chrome.storage.local[op](keys, (result) => {
const millis = performance.now() - start;
if (millis > 500) {
this.dispatchEvent(
new CustomEvent('slowPerformance', {
detail: {
operation: `storage.local.${op}(...)`,
duration: Math.round(millis),
},
})
);
}
const start = performance.now();
const result = await chrome.storage.local[op](keys);
const millis = performance.now() - start;
if (millis > 500) {
this.dispatchEvent(
new CustomEvent('slowPerformance', {
detail: {
operation: `storage.local.${op}(...)`,
duration: Math.round(millis),
},
})
);
}

if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(transformSuccessResult(result));
}
});
});
return transformSuccessResult(result);
}
}

Expand Down
41 changes: 19 additions & 22 deletions src/background/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import * as exportImport from './exportImport/index.js';
import * as settings from './settings/index.js';
import { select } from './utils/fields.js';

// WARNING: All properties must be in quotes. Otherwise UglifyJS would ufligy
// them and they wouldn't be reachable by name.
const resterApi = {
data: {
authorizationProviderConfigurations: {
Expand Down Expand Up @@ -52,29 +50,28 @@ const resterApi = {
},
};

chrome.browserAction.onClicked.addListener(() => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const resterUrl = chrome.runtime.getURL('site/index.html');
const blankUrls = ['about:blank', 'about:newtab'];
if (blankUrls.includes(tabs[0].url)) {
try {
chrome.tabs.update({
loadReplace: true,
url: resterUrl,
});
} catch (e) {
// Chrome does not support loadReplace and throws an exception,
// it is specified. Try again without loadReplace.
chrome.tabs.update({
url: resterUrl,
});
}
} else {
chrome.tabs.create({
chrome.action.onClicked.addListener(async () => {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true })
const resterUrl = chrome.runtime.getURL('site/index.html');
const blankUrls = ['about:blank', 'about:newtab'];
if (blankUrls.includes(tabs[0].url)) {
try {
chrome.tabs.update({
loadReplace: true,
url: resterUrl,
});
} catch (e) {
// Chrome does not support loadReplace and throws an exception,
// it is specified. Try again without loadReplace.
chrome.tabs.update({
url: resterUrl,
});
}
});
} else {
chrome.tabs.create({
url: resterUrl,
});
}
});

chrome.runtime.onConnect.addListener((port) => {
Expand Down
25 changes: 5 additions & 20 deletions src/background/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,13 @@ const DEFAULTS = {
theme: 'dark',
};

function getSettings() {
return new Promise((resolve, reject) => {
chrome.storage.local.get('settings', (result) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(result.settings || {});
}
});
});
async function getSettings() {
const result = await chrome.storage.local.get('settings');
return result.settings || {};
}

function setSettings(settings) {
return new Promise((resolve, reject) => {
chrome.storage.local.set({ settings }, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve();
}
});
});
async function setSettings(settings) {
await chrome.storage.local.set({ settings });
}

export const e = new CustomEventTarget();
Expand Down
37 changes: 37 additions & 0 deletions src/manifest-chrome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"manifest_version": 3,
"name": "RESTer",
"version": "4.11.0",
"description": "A REST client for almost any web service.",
"author": "Jan Kühle",
"homepage_url": "https://github.com/frigus02/RESTer",
"icons": {
"48": "images/icon-dev48.png",
"128": "images/icon-dev128.png"
},
"permissions": [
"activeTab",
"downloads",
"storage",
"unlimitedStorage",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess"
],
"optional_permissions": [
"cookies"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background/bundle.js"
},
"action": {
"default_icon": {
"16": "images/icon-dev16.png",
"24": "images/icon-dev24.png",
"32": "images/icon-dev32.png"
}
},
"minimum_chrome_version": "111"
}
20 changes: 13 additions & 7 deletions src/manifest.json → src/manifest-firefox.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "RESTer",
"version": "4.11.0",
"description": "A REST client for almost any web service.",
Expand All @@ -13,20 +13,20 @@
"activeTab",
"downloads",
"storage",
"unlimitedStorage",
"<all_urls>"
"unlimitedStorage"
],
"optional_permissions": [
"cookies",
"webRequest",
"webRequestBlocking"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"scripts": [
"background/bundle.js"
]
"scripts": ["background/bundle.js"]
},
"browser_action": {
"action": {
"default_icon": {
"16": "images/icon-dev16.png",
"24": "images/icon-dev24.png",
Expand All @@ -49,5 +49,11 @@
"size": 32
}
]
},
"browser_specific_settings": {
"gecko": {
"id": "rester@kuehle.me",
"strict_min_version": "110.0"
}
}
}
37 changes: 37 additions & 0 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,40 @@ export function parseStatusLine(statusLine) {
const reasonPhrase = statusLine.substring(secondSpace + 1);
return { httpVersion, statusCode, reasonPhrase };
}

export function getFilenameFromContentDispositionHeader(disposition) {
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-.]+)(?:; ?|$)/i;
const asciiFilenameRegex = /filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;

let fileName = null;
if (utf8FilenameRegex.test(disposition)) {
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
} else {
// Prevent ReDos attacks by anchoring the ascii regex to string start
// and slicing off everything before 'filename='
const filenameStart = disposition.toLowerCase().indexOf('filename=');
if (filenameStart >= 0) {
const partialDisposition = disposition.slice(filenameStart);
const matches = asciiFilenameRegex.exec(partialDisposition);
if (matches !== null && matches[2]) {
fileName = matches[2];
}
}
}

if (fileName !== null) {
// Sanitize filename for illegal characters
const illegalRe = /[/?<>\\:*|":]/g;
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
const reservedRe = /^\.+/g;
const windowsReservedRe =
/^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
fileName = fileName
.replace(illegalRe, '')
.replace(controlRe, '')
.replace(reservedRe, '')
.replace(windowsReservedRe, '');
}

return fileName;
}
27 changes: 27 additions & 0 deletions src/shared/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
mergeCookies,
parseMediaType,
parseStatusLine,
getFilenameFromContentDispositionHeader,
} from './util.js';

describe('clone', function () {
Expand Down Expand Up @@ -215,3 +216,29 @@ describe('parseStatusLine', function () {
});
});
});

describe('getFilenameFromContentDispositionHeader', function () {
test('utf8', function () {
expect(
getFilenameFromContentDispositionHeader(
"attachment;filename*=UTF-8''%6A%C5%AF%C5%AF%C5%AF%C5%BE%C4%9B%2E%74%78%74"
)
).toEqual('jůůůžě.txt');
});

test('ascii', function () {
expect(
getFilenameFromContentDispositionHeader(
'attachment; filename="cool.html"'
)
).toEqual('cool.html');
});

test('reverved characters', function () {
expect(
getFilenameFromContentDispositionHeader(
'attachment; filename="../c|o?ol.html"'
)
).toEqual('cool.html');
});
});
17 changes: 6 additions & 11 deletions src/site/elements/controls/rester-authorization-provider-cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ async function ensureCookiesPermission() {
permissions: ['cookies'],
};

return new Promise((resolve, reject) => {
chrome.permissions.request(requiredPermissions, (result) => {
if (result) {
resolve();
} else {
reject(
'RESTer needs the permissions to read cookies for this.'
);
}
});
});
const result = await chrome.permissions.request(requiredPermissions);
if (!result) {
throw new Error(
'RESTer needs the permissions to read cookies for this.'
);
}
}

function filterCookies(cookies, cookieNames) {
Expand Down
Loading