Improve file handler check to add path separator

Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-04-13 11:53:49 -05:00
committed by GitHub
parent 2174414e9e
commit 1c50a2c047
2 changed files with 173 additions and 5 deletions
+13 -4
View File
@@ -16,11 +16,17 @@ import {
getUpdateCachePath,
} from './attachments.node.ts';
import { createLogger } from '../ts/logging/log.std.ts';
import { isPathInside } from '../ts/util/isPathInside.node.ts';
const log = createLogger('protocol_filter');
type CallbackType = (response: string | ProtocolResponse) => void;
export type FileHandlerType = (
request: ProtocolRequest,
callback: CallbackType
) => void;
function _eliminateAllAfterCharacter(
string: string,
character: string
@@ -50,7 +56,7 @@ export function _urlToPath(
return withoutQuerystring;
}
function _createFileHandler({
export function _createFileHandler({
userDataPath,
installPath,
isWindows,
@@ -58,7 +64,7 @@ function _createFileHandler({
userDataPath: string;
installPath: string;
isWindows: boolean;
}) {
}): FileHandlerType {
const allowedRoots = [
userDataPath,
installPath,
@@ -71,6 +77,9 @@ function _createFileHandler({
getTempPath(userDataPath),
getUpdateCachePath(userDataPath),
];
const allowedRootsCased = allowedRoots.map(root =>
isWindows ? root.toLowerCase() : root
);
return (request: ProtocolRequest, callback: CallbackType): void => {
let targetPath;
@@ -102,8 +111,8 @@ function _createFileHandler({
return;
}
for (const root of allowedRoots) {
if (properCasing.startsWith(isWindows ? root.toLowerCase() : root)) {
for (const root of allowedRootsCased) {
if (isPathInside(properCasing, root)) {
callback({ path: realPath });
return;
}
+160 -1
View File
@@ -2,10 +2,24 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import type { ProtocolResponse } from 'electron';
import * as sinon from 'sinon';
import { _urlToPath } from '../../../app/protocol_filter.node.ts';
import {
_createFileHandler,
_urlToPath,
type FileHandlerType,
} from '../../../app/protocol_filter.node.ts';
describe('Protocol Filter', () => {
beforeEach(function (this: Mocha.Context) {
this.sandbox = sinon.createSandbox();
});
afterEach(function (this: Mocha.Context) {
this.sandbox.restore();
});
describe('_urlToPath', () => {
it('returns proper file path for unix style file URI with querystring', () => {
const path =
@@ -79,4 +93,149 @@ describe('Protocol Filter', () => {
assert.strictEqual(actual, expected);
});
});
describe('_createFileHandler', () => {
function testFn({
fileHandler,
url,
expectedResult,
}: {
fileHandler: FileHandlerType;
url: string;
expectedResult: string | Partial<ProtocolResponse>;
}) {
const testCallback = sinon.stub();
const request = {
headers: {},
method: 'GET',
referrer: '',
url,
};
fileHandler(request, testCallback);
sinon.assert.calledWithMatch(testCallback, expectedResult);
}
describe('windows', () => {
before(function (this: Mocha.Context) {
if (process.platform !== 'win32') {
this.skip();
}
});
function getFileHandler(): FileHandlerType {
return _createFileHandler({
userDataPath: 'C:\\Users\\signaluser\\AppData\\Roaming\\Signal',
installPath:
'C:\\Users\\signaluser\\AppData\\Local\\Programs\\signal-desktop',
isWindows: true,
});
}
it('allows files in userData', () => {
testFn({
fileHandler: getFileHandler(),
url: 'file:///C:/Users/signaluser/AppData/Roaming/Signal/foo',
expectedResult: {
path: 'C:\\Users\\signaluser\\AppData\\Roaming\\Signal\\foo',
},
});
});
it('disallows files in other places', () => {
const fileHandler = getFileHandler();
testFn({
fileHandler,
url: 'file:///C:/Windows/foo',
expectedResult: { error: -10 },
});
testFn({
fileHandler,
url: 'file:///C:/Users/signaluser/AppData/Roaming/Signal Beta/foo',
expectedResult: { error: -10 },
});
});
});
describe('macos', () => {
before(function (this: Mocha.Context) {
if (process.platform === 'win32') {
this.skip();
}
});
function getFileHandler(): FileHandlerType {
return _createFileHandler({
userDataPath: '/Users/signaluser/Library/Application Support/Signal',
installPath: '/Applications/Signal',
isWindows: false,
});
}
it('allows files in userData', () => {
testFn({
fileHandler: getFileHandler(),
url: 'file:///Users/signaluser/Library/Application Support/Signal/foo',
expectedResult: {
path: '/Users/signaluser/Library/Application Support/Signal/foo',
},
});
});
it('disallows files in other places', () => {
const fileHandler = getFileHandler();
testFn({
fileHandler,
url: 'file:///Applications/foo',
expectedResult: { error: -10 },
});
testFn({
fileHandler,
url: 'file:///Users/signaluser/Library/Application Support/Signal Beta/foo',
expectedResult: { error: -10 },
});
});
});
describe('linux', () => {
before(function (this: Mocha.Context) {
if (process.platform === 'win32') {
this.skip();
}
});
function getFileHandler(): FileHandlerType {
return _createFileHandler({
userDataPath: '/home/signaluser/.config/Signal',
installPath: '/usr/bin/signal-desktop',
isWindows: false,
});
}
it('allows files in userData', () => {
testFn({
fileHandler: getFileHandler(),
url: 'file:///home/signaluser/.config/Signal/foo',
expectedResult: { path: '/home/signaluser/.config/Signal/foo' },
});
});
it('disallows files in other places', () => {
const fileHandler = getFileHandler();
testFn({
fileHandler,
url: 'file:///usr/bin/foo',
expectedResult: { error: -10 },
});
testFn({
fileHandler,
url: 'file:///home/signaluser/.config/Signal Beta/foo',
expectedResult: { error: -10 },
});
});
});
});
});