Remove many instances of deprecated url.parse
This commit is contained in:
parent
2fc3e4c698
commit
18abe93022
10 changed files with 220 additions and 89 deletions
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable strict */
|
||||
|
@ -7,6 +7,7 @@ const { Menu, clipboard, nativeImage } = require('electron');
|
|||
const osLocale = require('os-locale');
|
||||
const { uniq } = require('lodash');
|
||||
const url = require('url');
|
||||
const { maybeParseUrl } = require('../ts/util/url');
|
||||
|
||||
function getLanguages(userLocale, availableLocales) {
|
||||
const baseLocale = userLocale.split('-')[0];
|
||||
|
@ -97,7 +98,8 @@ exports.setup = (browserWindow, messages) => {
|
|||
label = messages.contextMenuCopyLink.message;
|
||||
} else if (isImage) {
|
||||
click = () => {
|
||||
if (url.parse(params.srcURL).protocol !== 'file:') {
|
||||
const parsedSrcUrl = maybeParseUrl(params.srcURL);
|
||||
if (!parsedSrcUrl || parsedSrcUrl.protocol !== 'file:') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// Copyright 2019-2020 Signal Messenger, LLC
|
||||
// Copyright 2019-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global URL */
|
||||
|
||||
const { isNumber, compact, isEmpty, range } = require('lodash');
|
||||
const nodeUrl = require('url');
|
||||
const LinkifyIt = require('linkify-it');
|
||||
const { maybeParseUrl } = require('../../ts/util/url');
|
||||
|
||||
const linkify = LinkifyIt();
|
||||
|
||||
|
@ -18,16 +17,8 @@ module.exports = {
|
|||
isStickerPack,
|
||||
};
|
||||
|
||||
function maybeParseHref(href) {
|
||||
try {
|
||||
return new URL(href);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isLinkSafeToPreview(href) {
|
||||
const url = maybeParseHref(href);
|
||||
const url = maybeParseUrl(href);
|
||||
return Boolean(url && url.protocol === 'https:' && !isLinkSneaky(href));
|
||||
}
|
||||
|
||||
|
@ -64,7 +55,7 @@ function findLinks(text, caretLocation) {
|
|||
}
|
||||
|
||||
function getDomain(href) {
|
||||
const url = maybeParseHref(href);
|
||||
const url = maybeParseUrl(href);
|
||||
return url ? url.hostname : null;
|
||||
}
|
||||
|
||||
|
@ -109,7 +100,7 @@ function isLinkSneaky(href) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const url = maybeParseHref(href);
|
||||
const url = maybeParseUrl(href);
|
||||
|
||||
// If we can't parse it, it's sneaky.
|
||||
if (!url) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2019-2020 Signal Messenger, LLC
|
||||
// Copyright 2019-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global
|
||||
|
@ -8,7 +8,6 @@
|
|||
navigator,
|
||||
reduxStore,
|
||||
reduxActions,
|
||||
URL,
|
||||
URLSearchParams
|
||||
*/
|
||||
|
||||
|
@ -38,6 +37,7 @@ const pMap = require('p-map');
|
|||
const Queue = require('p-queue').default;
|
||||
|
||||
const { makeLookup } = require('../../ts/util/makeLookup');
|
||||
const { maybeParseUrl } = require('../../ts/util/url');
|
||||
const {
|
||||
base64ToArrayBuffer,
|
||||
deriveStickerPackKey,
|
||||
|
@ -96,10 +96,8 @@ async function load() {
|
|||
}
|
||||
|
||||
function getDataFromLink(link) {
|
||||
let url;
|
||||
try {
|
||||
url = new URL(link);
|
||||
} catch (err) {
|
||||
const url = maybeParseUrl(link);
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
118
main.js
118
main.js
|
@ -4,7 +4,7 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const { pathToFileURL } = require('url');
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const crypto = require('crypto');
|
||||
|
@ -123,6 +123,7 @@ const {
|
|||
} = require('./ts/types/Settings');
|
||||
const { Environment } = require('./ts/environment');
|
||||
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
||||
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
|
||||
|
||||
const sql = new MainSQL();
|
||||
const challengeHandler = new ChallengeMainHandler();
|
||||
|
@ -228,49 +229,57 @@ const loadLocale = require('./app/locale').load;
|
|||
let logger;
|
||||
let locale;
|
||||
|
||||
function prepareURL(pathSegments, moreKeys) {
|
||||
const parsed = url.parse(path.join(...pathSegments));
|
||||
function prepareFileUrl(
|
||||
pathSegments /* : ReadonlyArray<string> */,
|
||||
moreKeys /* : undefined | Record<string, unknown> */
|
||||
) /* : string */ {
|
||||
const filePath = path.join(...pathSegments);
|
||||
const fileUrl = pathToFileURL(filePath);
|
||||
return prepareUrl(fileUrl, moreKeys);
|
||||
}
|
||||
|
||||
return url.format({
|
||||
...parsed,
|
||||
protocol: parsed.protocol || 'file:',
|
||||
slashes: true,
|
||||
query: {
|
||||
name: packageJson.productName,
|
||||
locale: locale.name,
|
||||
version: app.getVersion(),
|
||||
buildExpiration: config.get('buildExpiration'),
|
||||
serverUrl: config.get('serverUrl'),
|
||||
storageUrl: config.get('storageUrl'),
|
||||
directoryUrl: config.get('directoryUrl'),
|
||||
directoryEnclaveId: config.get('directoryEnclaveId'),
|
||||
directoryTrustAnchor: config.get('directoryTrustAnchor'),
|
||||
cdnUrl0: config.get('cdn').get('0'),
|
||||
cdnUrl2: config.get('cdn').get('2'),
|
||||
certificateAuthority: config.get('certificateAuthority'),
|
||||
environment: enableCI ? 'production' : config.environment,
|
||||
enableCI: enableCI ? true : undefined,
|
||||
node_version: process.versions.node,
|
||||
hostname: os.hostname(),
|
||||
appInstance: process.env.NODE_APP_INSTANCE,
|
||||
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
||||
contentProxyUrl: config.contentProxyUrl,
|
||||
sfuUrl: config.get('sfuUrl'),
|
||||
importMode: importMode ? true : undefined, // for stringify()
|
||||
reducedMotionSetting: animationSettings.prefersReducedMotion
|
||||
? true
|
||||
: undefined,
|
||||
serverPublicParams: config.get('serverPublicParams'),
|
||||
serverTrustRoot: config.get('serverTrustRoot'),
|
||||
appStartInitialSpellcheckSetting,
|
||||
...moreKeys,
|
||||
},
|
||||
});
|
||||
function prepareUrl(
|
||||
url /* : URL */,
|
||||
moreKeys = {} /* : undefined | Record<string, unknown> */
|
||||
) /* : string */ {
|
||||
return setUrlSearchParams(url, {
|
||||
name: packageJson.productName,
|
||||
locale: locale.name,
|
||||
version: app.getVersion(),
|
||||
buildExpiration: config.get('buildExpiration'),
|
||||
serverUrl: config.get('serverUrl'),
|
||||
storageUrl: config.get('storageUrl'),
|
||||
directoryUrl: config.get('directoryUrl'),
|
||||
directoryEnclaveId: config.get('directoryEnclaveId'),
|
||||
directoryTrustAnchor: config.get('directoryTrustAnchor'),
|
||||
cdnUrl0: config.get('cdn').get('0'),
|
||||
cdnUrl2: config.get('cdn').get('2'),
|
||||
certificateAuthority: config.get('certificateAuthority'),
|
||||
environment: enableCI ? 'production' : config.environment,
|
||||
enableCI: enableCI ? 'true' : '',
|
||||
node_version: process.versions.node,
|
||||
hostname: os.hostname(),
|
||||
appInstance: process.env.NODE_APP_INSTANCE,
|
||||
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
||||
contentProxyUrl: config.contentProxyUrl,
|
||||
sfuUrl: config.get('sfuUrl'),
|
||||
importMode: importMode ? 'true' : '',
|
||||
reducedMotionSetting: animationSettings.prefersReducedMotion ? 'true' : '',
|
||||
serverPublicParams: config.get('serverPublicParams'),
|
||||
serverTrustRoot: config.get('serverTrustRoot'),
|
||||
appStartInitialSpellcheckSetting,
|
||||
...moreKeys,
|
||||
}).href;
|
||||
}
|
||||
|
||||
async function handleUrl(event, target) {
|
||||
event.preventDefault();
|
||||
const { protocol, hostname } = url.parse(target);
|
||||
const parsedUrl = maybeParseUrl(target);
|
||||
if (!parsedUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { protocol, hostname } = parsedUrl;
|
||||
const isDevServer = config.enableHttp && hostname === 'localhost';
|
||||
// We only want to specially handle urls that aren't requesting the dev server
|
||||
if (isSgnlHref(target) || isSignalHttpsLink(target)) {
|
||||
|
@ -459,13 +468,20 @@ async function createWindow() {
|
|||
};
|
||||
|
||||
if (config.environment === 'test') {
|
||||
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html'], moreKeys));
|
||||
mainWindow.loadURL(
|
||||
prepareFileUrl([__dirname, 'test', 'index.html'], moreKeys)
|
||||
);
|
||||
} else if (config.environment === 'test-lib') {
|
||||
mainWindow.loadURL(
|
||||
prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'], moreKeys)
|
||||
prepareFileUrl(
|
||||
[__dirname, 'libtextsecure', 'test', 'index.html'],
|
||||
moreKeys
|
||||
)
|
||||
);
|
||||
} else {
|
||||
mainWindow.loadURL(prepareURL([__dirname, 'background.html'], moreKeys));
|
||||
mainWindow.loadURL(
|
||||
prepareFileUrl([__dirname, 'background.html'], moreKeys)
|
||||
);
|
||||
}
|
||||
|
||||
if (!enableCI && config.get('openDevTools')) {
|
||||
|
@ -766,7 +782,7 @@ function showAbout() {
|
|||
|
||||
handleCommonWindowEvents(aboutWindow);
|
||||
|
||||
aboutWindow.loadURL(prepareURL([__dirname, 'about.html']));
|
||||
aboutWindow.loadURL(prepareFileUrl([__dirname, 'about.html']));
|
||||
|
||||
aboutWindow.on('closed', () => {
|
||||
aboutWindow = null;
|
||||
|
@ -823,7 +839,7 @@ function showSettingsWindow() {
|
|||
|
||||
handleCommonWindowEvents(settingsWindow);
|
||||
|
||||
settingsWindow.loadURL(prepareURL([__dirname, 'settings.html']));
|
||||
settingsWindow.loadURL(prepareFileUrl([__dirname, 'settings.html']));
|
||||
|
||||
settingsWindow.on('closed', () => {
|
||||
removeDarkOverlay();
|
||||
|
@ -895,8 +911,10 @@ async function showStickerCreator() {
|
|||
handleCommonWindowEvents(stickerCreatorWindow);
|
||||
|
||||
const appUrl = config.enableHttp
|
||||
? prepareURL(['http://localhost:6380/sticker-creator/dist/index.html'])
|
||||
: prepareURL([__dirname, 'sticker-creator/dist/index.html']);
|
||||
? prepareUrl(
|
||||
new URL('http://localhost:6380/sticker-creator/dist/index.html')
|
||||
)
|
||||
: prepareFileUrl([__dirname, 'sticker-creator/dist/index.html']);
|
||||
|
||||
stickerCreatorWindow.loadURL(appUrl);
|
||||
|
||||
|
@ -947,7 +965,9 @@ async function showDebugLogWindow() {
|
|||
|
||||
handleCommonWindowEvents(debugLogWindow);
|
||||
|
||||
debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html'], { theme }));
|
||||
debugLogWindow.loadURL(
|
||||
prepareFileUrl([__dirname, 'debug_log.html'], { theme })
|
||||
);
|
||||
|
||||
debugLogWindow.on('closed', () => {
|
||||
removeDarkOverlay();
|
||||
|
@ -1000,7 +1020,7 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
|
|||
handleCommonWindowEvents(permissionsPopupWindow);
|
||||
|
||||
permissionsPopupWindow.loadURL(
|
||||
prepareURL([__dirname, 'permissions_popup.html'], {
|
||||
prepareFileUrl([__dirname, 'permissions_popup.html'], {
|
||||
theme,
|
||||
forCalling,
|
||||
forCamera,
|
||||
|
@ -1175,7 +1195,7 @@ app.on('ready', async () => {
|
|||
loadingWindow = null;
|
||||
});
|
||||
|
||||
loadingWindow.loadURL(prepareURL([__dirname, 'loading.html']));
|
||||
loadingWindow.loadURL(prepareFileUrl([__dirname, 'loading.html']));
|
||||
});
|
||||
|
||||
// Run window preloading in parallel with database initialization.
|
||||
|
|
|
@ -7,6 +7,7 @@ import { gzip } from 'zlib';
|
|||
import pify from 'pify';
|
||||
import got, { Response } from 'got';
|
||||
import { getUserAgent } from '../util/getUserAgent';
|
||||
import { maybeParseUrl } from '../util/url';
|
||||
|
||||
const BASE_URL = 'https://debuglogs.org';
|
||||
|
||||
|
@ -22,10 +23,8 @@ const parseTokenBody = (
|
|||
): { fields: Record<string, unknown>; url: string } => {
|
||||
const body = tokenBodySchema.parse(rawBody);
|
||||
|
||||
let parsedUrl: URL;
|
||||
try {
|
||||
parsedUrl = new URL(body.url);
|
||||
} catch (err) {
|
||||
const parsedUrl = maybeParseUrl(body.url);
|
||||
if (!parsedUrl) {
|
||||
throw new Error("Token body's URL was not a valid URL");
|
||||
}
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
|
|
87
ts/test-both/util/url_test.ts
Normal file
87
ts/test-both/util/url_test.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { size } from '../../util/iterables';
|
||||
|
||||
import { maybeParseUrl, setUrlSearchParams } from '../../util/url';
|
||||
|
||||
describe('URL utilities', () => {
|
||||
describe('maybeParseUrl', () => {
|
||||
it('parses valid URLs', () => {
|
||||
[
|
||||
'https://example.com',
|
||||
'https://example.com:123/pathname?query=string#hash',
|
||||
'file:///path/to/file.txt',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(maybeParseUrl(href), new URL(href));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined for invalid URLs', () => {
|
||||
['', 'example.com'].forEach(href => {
|
||||
assert.isUndefined(maybeParseUrl(href));
|
||||
});
|
||||
});
|
||||
|
||||
it('handles non-strings for compatibility, returning undefined', () => {
|
||||
[undefined, null, 123, ['https://example.com']].forEach(value => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
assert.isUndefined(maybeParseUrl(value as any));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setUrlSearchParams', () => {
|
||||
it('returns a new URL with updated search params', () => {
|
||||
const params = {
|
||||
normal_string: 'foo',
|
||||
empty_string: '',
|
||||
number: 123,
|
||||
true_bool: true,
|
||||
false_bool: false,
|
||||
null_value: null,
|
||||
undefined_value: undefined,
|
||||
array: ['ok', 'wow'],
|
||||
stringified: { toString: () => 'bar' },
|
||||
};
|
||||
|
||||
const newUrl = setUrlSearchParams(
|
||||
new URL('https://example.com/path?should_be=overwritten#hash'),
|
||||
params
|
||||
);
|
||||
|
||||
assert(newUrl.href.startsWith('https://example.com/path?'));
|
||||
assert.strictEqual(newUrl.hash, '#hash');
|
||||
|
||||
assert.strictEqual(
|
||||
size(newUrl.searchParams.entries()),
|
||||
Object.keys(params).length
|
||||
);
|
||||
assert.strictEqual(newUrl.searchParams.get('normal_string'), 'foo');
|
||||
assert.strictEqual(newUrl.searchParams.get('empty_string'), '');
|
||||
assert.strictEqual(newUrl.searchParams.get('number'), '123');
|
||||
assert.strictEqual(newUrl.searchParams.get('true_bool'), 'true');
|
||||
assert.strictEqual(newUrl.searchParams.get('false_bool'), 'false');
|
||||
assert.strictEqual(newUrl.searchParams.get('null_value'), '');
|
||||
assert.strictEqual(newUrl.searchParams.get('undefined_value'), '');
|
||||
assert.strictEqual(newUrl.searchParams.get('array'), 'ok,wow');
|
||||
assert.strictEqual(newUrl.searchParams.get('stringified'), 'bar');
|
||||
});
|
||||
|
||||
it("doesn't touch the original URL or its params", () => {
|
||||
const originalHref = 'https://example.com/path?query=string';
|
||||
const originalUrl = new URL(originalHref);
|
||||
|
||||
const params = { foo: 'bar' };
|
||||
|
||||
const newUrl = setUrlSearchParams(originalUrl, params);
|
||||
|
||||
assert.notStrictEqual(originalUrl, newUrl);
|
||||
assert.strictEqual(originalUrl.href, originalHref);
|
||||
|
||||
params.foo = 'should be ignored';
|
||||
assert.strictEqual(newUrl.search, '?foo=bar');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,26 +1,25 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { LoggerType } from '../types/Logging';
|
||||
import { maybeParseUrl } from './url';
|
||||
|
||||
function parseUrl(value: unknown, logger: LoggerType): null | URL {
|
||||
function parseUrl(value: string | URL, logger: LoggerType): undefined | URL {
|
||||
if (value instanceof URL) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return new URL(value);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
return maybeParseUrl(value);
|
||||
}
|
||||
|
||||
logger.warn('Tried to parse a sgnl:// URL but got an unexpected type');
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isSgnlHref(value: string | URL, logger: LoggerType): boolean {
|
||||
const url = parseUrl(value, logger);
|
||||
return url !== null && url.protocol === 'sgnl:';
|
||||
return Boolean(url?.protocol === 'sgnl:');
|
||||
}
|
||||
|
||||
export function isCaptchaHref(
|
||||
|
@ -28,7 +27,7 @@ export function isCaptchaHref(
|
|||
logger: LoggerType
|
||||
): boolean {
|
||||
const url = parseUrl(value, logger);
|
||||
return url !== null && url.protocol === 'signalcaptcha:';
|
||||
return Boolean(url?.protocol === 'signalcaptcha:');
|
||||
}
|
||||
|
||||
export function isSignalHttpsLink(
|
||||
|
|
35
ts/util/url.ts
Normal file
35
ts/util/url.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { mapValues } from 'lodash';
|
||||
|
||||
export function maybeParseUrl(value: string): undefined | URL {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return new URL(value);
|
||||
} catch (err) {
|
||||
/* Errors are ignored. */
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function setUrlSearchParams(
|
||||
url: Readonly<URL>,
|
||||
searchParams: Readonly<Record<string, unknown>>
|
||||
): URL {
|
||||
const result = cloneUrl(url);
|
||||
result.search = new URLSearchParams(
|
||||
mapValues(searchParams, stringifySearchParamValue)
|
||||
).toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
function cloneUrl(url: Readonly<URL>): URL {
|
||||
return new URL(url.href);
|
||||
}
|
||||
|
||||
function stringifySearchParamValue(value: unknown): string {
|
||||
return value == null ? '' : String(value);
|
||||
}
|
|
@ -11,6 +11,7 @@ import { MediaItemType } from '../components/LightboxGallery';
|
|||
import { MessageModel } from '../models/messages';
|
||||
import { MessageType } from '../state/ducks/conversations';
|
||||
import { assert } from '../util/assert';
|
||||
import { maybeParseUrl } from '../util/url';
|
||||
|
||||
type GetLinkPreviewImageResult = {
|
||||
data: ArrayBuffer;
|
||||
|
@ -3934,10 +3935,8 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
url: string,
|
||||
abortSignal: any
|
||||
): Promise<null | GetLinkPreviewResult> {
|
||||
let urlObject;
|
||||
try {
|
||||
urlObject = new URL(url);
|
||||
} catch (err) {
|
||||
const urlObject = maybeParseUrl(url);
|
||||
if (!urlObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Specify library files to be included in the compilation.
|
||||
"lib": [
|
||||
"dom", // Required to access `window`
|
||||
"dom.iterable",
|
||||
"es2020"
|
||||
],
|
||||
"incremental": true,
|
||||
|
|
Loading…
Add table
Reference in a new issue