Signal-Desktop/ts/util/privacy.ts

128 lines
3.4 KiB
TypeScript
Raw Normal View History

2021-06-01 18:15:23 +00:00
// Copyright 2018-2021 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-env node */
2021-06-01 18:15:23 +00:00
import is from '@sindresorhus/is';
import { join as pathJoin } from 'path';
2021-06-01 18:15:23 +00:00
import { compose } from 'lodash/fp';
import { escapeRegExp } from 'lodash';
export const APP_ROOT_PATH = pathJoin(__dirname, '..', '..');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
2021-11-11 22:43:05 +00:00
const UUID_PATTERN =
/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{9}([0-9A-F]{3})/gi;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
2020-09-09 02:25:05 +00:00
const GROUP_V2_ID_PATTERN = /(groupv2\()([^=)]+)(=?=?\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
2021-06-01 18:15:23 +00:00
export type RedactFunction = (value: string) => string;
export const _redactPath = (filePath: string): RedactFunction => {
if (!is.string(filePath)) {
2018-04-11 19:44:52 +00:00
throw new TypeError("'filePath' must be a string");
}
2021-06-01 20:40:55 +00:00
const filePathPattern = _pathToRegExp(filePath);
2021-06-01 18:15:23 +00:00
return (text: string): string => {
if (!is.string(text)) {
2018-04-11 19:44:52 +00:00
throw new TypeError("'text' must be a string");
}
if (!is.regExp(filePathPattern)) {
return text;
}
return text.replace(filePathPattern, REDACTION_PLACEHOLDER);
};
};
2021-06-01 18:15:23 +00:00
export const _pathToRegExp = (filePath: string): RegExp | undefined => {
2018-03-07 15:57:39 +00:00
try {
const pathWithNormalizedSlashes = filePath.replace(/\//g, '\\');
2018-04-06 18:25:55 +00:00
const pathWithEscapedSlashes = filePath.replace(/\\/g, '\\\\');
const urlEncodedPath = encodeURI(filePath);
2018-03-07 15:57:39 +00:00
// Safe `String::replaceAll`:
// https://github.com/lodash/lodash/issues/1084#issuecomment-86698786
const patternString = [
filePath,
pathWithNormalizedSlashes,
2018-04-06 18:25:55 +00:00
pathWithEscapedSlashes,
urlEncodedPath,
2018-04-27 21:25:04 +00:00
]
.map(escapeRegExp)
.join('|');
return new RegExp(patternString, 'g');
2018-03-07 15:57:39 +00:00
} catch (error) {
2021-06-01 18:15:23 +00:00
return undefined;
2018-03-07 15:57:39 +00:00
}
};
// Public API
2021-06-01 18:15:23 +00:00
export const redactPhoneNumbers = (text: string): string => {
if (!is.string(text)) {
2018-04-11 19:44:52 +00:00
throw new TypeError("'text' must be a string");
}
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
};
2021-06-01 18:15:23 +00:00
export const redactUuids = (text: string): string => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(UUID_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
};
2021-06-01 18:15:23 +00:00
export const redactGroupIds = (text: string): string => {
if (!is.string(text)) {
2018-04-11 19:44:52 +00:00
throw new TypeError("'text' must be a string");
}
2020-09-09 02:25:05 +00:00
return text
.replace(
GROUP_ID_PATTERN,
2021-06-01 18:15:23 +00:00
(_, before, id, after) =>
2020-09-09 02:25:05 +00:00
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
-3
)}${after}`
)
.replace(
GROUP_V2_ID_PATTERN,
2021-06-01 18:15:23 +00:00
(_, before, id, after) =>
2020-09-09 02:25:05 +00:00
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
-3
)}${after}`
);
};
2021-06-01 18:15:23 +00:00
const createRedactSensitivePaths = (
paths: ReadonlyArray<string>
): RedactFunction => {
2021-06-01 20:40:55 +00:00
return compose(paths.map(filePath => _redactPath(filePath)));
2021-06-01 18:15:23 +00:00
};
const sensitivePaths: Array<string> = [];
let redactSensitivePaths: RedactFunction = (text: string) => text;
export const addSensitivePath = (filePath: string): void => {
sensitivePaths.push(filePath);
redactSensitivePaths = createRedactSensitivePaths(sensitivePaths);
};
addSensitivePath(APP_ROOT_PATH);
2021-06-01 18:15:23 +00:00
export const redactAll: RedactFunction = compose(
(text: string) => redactSensitivePaths(text),
redactGroupIds,
redactPhoneNumbers,
redactUuids
);
2018-05-03 15:46:21 +00:00
2021-06-01 18:15:23 +00:00
const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');