182 lines
5.3 KiB
TypeScript
182 lines
5.3 KiB
TypeScript
// Copyright 2018-2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type {
|
|
protocol as ElectronProtocol,
|
|
ProtocolRequest,
|
|
ProtocolResponse,
|
|
} from 'electron';
|
|
|
|
import { isAbsolute, normalize } from 'path';
|
|
import { existsSync, realpathSync } from 'fs';
|
|
import {
|
|
getAvatarsPath,
|
|
getBadgesPath,
|
|
getDraftPath,
|
|
getPath,
|
|
getStickersPath,
|
|
getTempPath,
|
|
getUpdateCachePath,
|
|
} from '../ts/util/attachments';
|
|
|
|
type CallbackType = (response: string | ProtocolResponse) => void;
|
|
|
|
function _eliminateAllAfterCharacter(
|
|
string: string,
|
|
character: string
|
|
): string {
|
|
const index = string.indexOf(character);
|
|
if (index < 0) {
|
|
return string;
|
|
}
|
|
|
|
return string.slice(0, index);
|
|
}
|
|
|
|
export function _urlToPath(
|
|
targetUrl: string,
|
|
options?: { isWindows: boolean }
|
|
): string {
|
|
const decoded = decodeURIComponent(targetUrl);
|
|
|
|
// We generally expect URLs to start with file:// or file:/// here, but for users with
|
|
// their home directory redirected to a UNC share, it will start with //.
|
|
const withoutScheme = decoded.startsWith('//')
|
|
? decoded
|
|
: decoded.slice(options?.isWindows ? 8 : 7);
|
|
|
|
const withoutQuerystring = _eliminateAllAfterCharacter(withoutScheme, '?');
|
|
const withoutHash = _eliminateAllAfterCharacter(withoutQuerystring, '#');
|
|
|
|
return withoutHash;
|
|
}
|
|
|
|
function _createFileHandler({
|
|
userDataPath,
|
|
installPath,
|
|
isWindows,
|
|
}: {
|
|
userDataPath: string;
|
|
installPath: string;
|
|
isWindows: boolean;
|
|
}) {
|
|
const allowedRoots = [
|
|
userDataPath,
|
|
installPath,
|
|
getAvatarsPath(userDataPath),
|
|
getBadgesPath(userDataPath),
|
|
getDraftPath(userDataPath),
|
|
getPath(userDataPath),
|
|
getStickersPath(userDataPath),
|
|
getTempPath(userDataPath),
|
|
getUpdateCachePath(userDataPath),
|
|
];
|
|
return (request: ProtocolRequest, callback: CallbackType): void => {
|
|
let targetPath;
|
|
|
|
if (!request.url) {
|
|
// This is an "invalid URL" error. See [Chromium's net error list][0].
|
|
//
|
|
// [0]: https://source.chromium.org/chromium/chromium/src/+/master:net/base/net_error_list.h;l=563;drc=a836ee9868cf1b9673fce362a82c98aba3e195de
|
|
callback({ error: -300 });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
targetPath = _urlToPath(request.url, { isWindows });
|
|
|
|
// normalize() is primarily useful here for switching / to \ on windows
|
|
const target = normalize(targetPath);
|
|
// here we attempt to follow symlinks to the ultimate final path, reflective of what
|
|
// we do in main.js on userDataPath and installPath
|
|
const realPath = existsSync(target) ? realpathSync(target) : target;
|
|
// finally we do case-insensitive checks on windows
|
|
const properCasing = isWindows ? realPath.toLowerCase() : realPath;
|
|
|
|
if (!isAbsolute(realPath)) {
|
|
console.log(
|
|
`Warning: denying request to non-absolute path '${realPath}'`
|
|
);
|
|
// This is an "Access Denied" error. See [Chromium's net error list][0].
|
|
//
|
|
// [0]: https://source.chromium.org/chromium/chromium/src/+/master:net/base/net_error_list.h;l=57;drc=a836ee9868cf1b9673fce362a82c98aba3e195de
|
|
callback({ error: -10 });
|
|
return;
|
|
}
|
|
|
|
for (const root of allowedRoots) {
|
|
if (properCasing.startsWith(isWindows ? root.toLowerCase() : root)) {
|
|
callback({ path: realPath });
|
|
return;
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`Warning: denying request to path '${realPath}' (allowedRoots: '${allowedRoots}')`
|
|
);
|
|
callback({ error: -10 });
|
|
} catch (err) {
|
|
const errorMessage =
|
|
err && typeof err.message === 'string'
|
|
? err.message
|
|
: 'no error message';
|
|
console.log(
|
|
`Warning: denying request because of an error: ${errorMessage}`
|
|
);
|
|
|
|
callback({ error: -300 });
|
|
}
|
|
};
|
|
}
|
|
|
|
export function installFileHandler({
|
|
protocol,
|
|
userDataPath,
|
|
installPath,
|
|
isWindows,
|
|
}: {
|
|
protocol: typeof ElectronProtocol;
|
|
userDataPath: string;
|
|
installPath: string;
|
|
isWindows: boolean;
|
|
}): void {
|
|
protocol.interceptFileProtocol(
|
|
'file',
|
|
_createFileHandler({ userDataPath, installPath, isWindows })
|
|
);
|
|
}
|
|
|
|
// Turn off browser URI scheme since we do all network requests via Node.js
|
|
function _disabledHandler(
|
|
_request: ProtocolRequest,
|
|
callback: CallbackType
|
|
): void {
|
|
callback({ error: -10 });
|
|
}
|
|
|
|
export function installWebHandler({
|
|
protocol,
|
|
enableHttp,
|
|
}: {
|
|
protocol: typeof ElectronProtocol;
|
|
enableHttp: boolean;
|
|
}): void {
|
|
protocol.interceptFileProtocol('about', _disabledHandler);
|
|
protocol.interceptFileProtocol('content', _disabledHandler);
|
|
protocol.interceptFileProtocol('chrome', _disabledHandler);
|
|
protocol.interceptFileProtocol('cid', _disabledHandler);
|
|
protocol.interceptFileProtocol('data', _disabledHandler);
|
|
protocol.interceptFileProtocol('filesystem', _disabledHandler);
|
|
protocol.interceptFileProtocol('ftp', _disabledHandler);
|
|
protocol.interceptFileProtocol('gopher', _disabledHandler);
|
|
protocol.interceptFileProtocol('javascript', _disabledHandler);
|
|
protocol.interceptFileProtocol('mailto', _disabledHandler);
|
|
|
|
if (!enableHttp) {
|
|
protocol.interceptFileProtocol('http', _disabledHandler);
|
|
protocol.interceptFileProtocol('https', _disabledHandler);
|
|
protocol.interceptFileProtocol('ws', _disabledHandler);
|
|
protocol.interceptFileProtocol('wss', _disabledHandler);
|
|
}
|
|
}
|