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
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable strict */
|
/* eslint-disable strict */
|
||||||
|
@ -7,6 +7,7 @@ const { Menu, clipboard, nativeImage } = require('electron');
|
||||||
const osLocale = require('os-locale');
|
const osLocale = require('os-locale');
|
||||||
const { uniq } = require('lodash');
|
const { uniq } = require('lodash');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
const { maybeParseUrl } = require('../ts/util/url');
|
||||||
|
|
||||||
function getLanguages(userLocale, availableLocales) {
|
function getLanguages(userLocale, availableLocales) {
|
||||||
const baseLocale = userLocale.split('-')[0];
|
const baseLocale = userLocale.split('-')[0];
|
||||||
|
@ -97,7 +98,8 @@ exports.setup = (browserWindow, messages) => {
|
||||||
label = messages.contextMenuCopyLink.message;
|
label = messages.contextMenuCopyLink.message;
|
||||||
} else if (isImage) {
|
} else if (isImage) {
|
||||||
click = () => {
|
click = () => {
|
||||||
if (url.parse(params.srcURL).protocol !== 'file:') {
|
const parsedSrcUrl = maybeParseUrl(params.srcURL);
|
||||||
|
if (!parsedSrcUrl || parsedSrcUrl.protocol !== 'file:') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
// Copyright 2019-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* global URL */
|
|
||||||
|
|
||||||
const { isNumber, compact, isEmpty, range } = require('lodash');
|
const { isNumber, compact, isEmpty, range } = require('lodash');
|
||||||
const nodeUrl = require('url');
|
const nodeUrl = require('url');
|
||||||
const LinkifyIt = require('linkify-it');
|
const LinkifyIt = require('linkify-it');
|
||||||
|
const { maybeParseUrl } = require('../../ts/util/url');
|
||||||
|
|
||||||
const linkify = LinkifyIt();
|
const linkify = LinkifyIt();
|
||||||
|
|
||||||
|
@ -18,16 +17,8 @@ module.exports = {
|
||||||
isStickerPack,
|
isStickerPack,
|
||||||
};
|
};
|
||||||
|
|
||||||
function maybeParseHref(href) {
|
|
||||||
try {
|
|
||||||
return new URL(href);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLinkSafeToPreview(href) {
|
function isLinkSafeToPreview(href) {
|
||||||
const url = maybeParseHref(href);
|
const url = maybeParseUrl(href);
|
||||||
return Boolean(url && url.protocol === 'https:' && !isLinkSneaky(href));
|
return Boolean(url && url.protocol === 'https:' && !isLinkSneaky(href));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +55,7 @@ function findLinks(text, caretLocation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDomain(href) {
|
function getDomain(href) {
|
||||||
const url = maybeParseHref(href);
|
const url = maybeParseUrl(href);
|
||||||
return url ? url.hostname : null;
|
return url ? url.hostname : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +100,7 @@ function isLinkSneaky(href) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = maybeParseHref(href);
|
const url = maybeParseUrl(href);
|
||||||
|
|
||||||
// If we can't parse it, it's sneaky.
|
// If we can't parse it, it's sneaky.
|
||||||
if (!url) {
|
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
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* global
|
/* global
|
||||||
|
@ -8,7 +8,6 @@
|
||||||
navigator,
|
navigator,
|
||||||
reduxStore,
|
reduxStore,
|
||||||
reduxActions,
|
reduxActions,
|
||||||
URL,
|
|
||||||
URLSearchParams
|
URLSearchParams
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -38,6 +37,7 @@ const pMap = require('p-map');
|
||||||
const Queue = require('p-queue').default;
|
const Queue = require('p-queue').default;
|
||||||
|
|
||||||
const { makeLookup } = require('../../ts/util/makeLookup');
|
const { makeLookup } = require('../../ts/util/makeLookup');
|
||||||
|
const { maybeParseUrl } = require('../../ts/util/url');
|
||||||
const {
|
const {
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
deriveStickerPackKey,
|
deriveStickerPackKey,
|
||||||
|
@ -96,10 +96,8 @@ async function load() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataFromLink(link) {
|
function getDataFromLink(link) {
|
||||||
let url;
|
const url = maybeParseUrl(link);
|
||||||
try {
|
if (!url) {
|
||||||
url = new URL(link);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
118
main.js
118
main.js
|
@ -4,7 +4,7 @@
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
const { pathToFileURL } = require('url');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
@ -123,6 +123,7 @@ const {
|
||||||
} = require('./ts/types/Settings');
|
} = require('./ts/types/Settings');
|
||||||
const { Environment } = require('./ts/environment');
|
const { Environment } = require('./ts/environment');
|
||||||
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
const { ChallengeMainHandler } = require('./ts/main/challengeMain');
|
||||||
|
const { maybeParseUrl, setUrlSearchParams } = require('./ts/util/url');
|
||||||
|
|
||||||
const sql = new MainSQL();
|
const sql = new MainSQL();
|
||||||
const challengeHandler = new ChallengeMainHandler();
|
const challengeHandler = new ChallengeMainHandler();
|
||||||
|
@ -228,49 +229,57 @@ const loadLocale = require('./app/locale').load;
|
||||||
let logger;
|
let logger;
|
||||||
let locale;
|
let locale;
|
||||||
|
|
||||||
function prepareURL(pathSegments, moreKeys) {
|
function prepareFileUrl(
|
||||||
const parsed = url.parse(path.join(...pathSegments));
|
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({
|
function prepareUrl(
|
||||||
...parsed,
|
url /* : URL */,
|
||||||
protocol: parsed.protocol || 'file:',
|
moreKeys = {} /* : undefined | Record<string, unknown> */
|
||||||
slashes: true,
|
) /* : string */ {
|
||||||
query: {
|
return setUrlSearchParams(url, {
|
||||||
name: packageJson.productName,
|
name: packageJson.productName,
|
||||||
locale: locale.name,
|
locale: locale.name,
|
||||||
version: app.getVersion(),
|
version: app.getVersion(),
|
||||||
buildExpiration: config.get('buildExpiration'),
|
buildExpiration: config.get('buildExpiration'),
|
||||||
serverUrl: config.get('serverUrl'),
|
serverUrl: config.get('serverUrl'),
|
||||||
storageUrl: config.get('storageUrl'),
|
storageUrl: config.get('storageUrl'),
|
||||||
directoryUrl: config.get('directoryUrl'),
|
directoryUrl: config.get('directoryUrl'),
|
||||||
directoryEnclaveId: config.get('directoryEnclaveId'),
|
directoryEnclaveId: config.get('directoryEnclaveId'),
|
||||||
directoryTrustAnchor: config.get('directoryTrustAnchor'),
|
directoryTrustAnchor: config.get('directoryTrustAnchor'),
|
||||||
cdnUrl0: config.get('cdn').get('0'),
|
cdnUrl0: config.get('cdn').get('0'),
|
||||||
cdnUrl2: config.get('cdn').get('2'),
|
cdnUrl2: config.get('cdn').get('2'),
|
||||||
certificateAuthority: config.get('certificateAuthority'),
|
certificateAuthority: config.get('certificateAuthority'),
|
||||||
environment: enableCI ? 'production' : config.environment,
|
environment: enableCI ? 'production' : config.environment,
|
||||||
enableCI: enableCI ? true : undefined,
|
enableCI: enableCI ? 'true' : '',
|
||||||
node_version: process.versions.node,
|
node_version: process.versions.node,
|
||||||
hostname: os.hostname(),
|
hostname: os.hostname(),
|
||||||
appInstance: process.env.NODE_APP_INSTANCE,
|
appInstance: process.env.NODE_APP_INSTANCE,
|
||||||
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
||||||
contentProxyUrl: config.contentProxyUrl,
|
contentProxyUrl: config.contentProxyUrl,
|
||||||
sfuUrl: config.get('sfuUrl'),
|
sfuUrl: config.get('sfuUrl'),
|
||||||
importMode: importMode ? true : undefined, // for stringify()
|
importMode: importMode ? 'true' : '',
|
||||||
reducedMotionSetting: animationSettings.prefersReducedMotion
|
reducedMotionSetting: animationSettings.prefersReducedMotion ? 'true' : '',
|
||||||
? true
|
serverPublicParams: config.get('serverPublicParams'),
|
||||||
: undefined,
|
serverTrustRoot: config.get('serverTrustRoot'),
|
||||||
serverPublicParams: config.get('serverPublicParams'),
|
appStartInitialSpellcheckSetting,
|
||||||
serverTrustRoot: config.get('serverTrustRoot'),
|
...moreKeys,
|
||||||
appStartInitialSpellcheckSetting,
|
}).href;
|
||||||
...moreKeys,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUrl(event, target) {
|
async function handleUrl(event, target) {
|
||||||
event.preventDefault();
|
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';
|
const isDevServer = config.enableHttp && hostname === 'localhost';
|
||||||
// We only want to specially handle urls that aren't requesting the dev server
|
// We only want to specially handle urls that aren't requesting the dev server
|
||||||
if (isSgnlHref(target) || isSignalHttpsLink(target)) {
|
if (isSgnlHref(target) || isSignalHttpsLink(target)) {
|
||||||
|
@ -459,13 +468,20 @@ async function createWindow() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.environment === 'test') {
|
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') {
|
} else if (config.environment === 'test-lib') {
|
||||||
mainWindow.loadURL(
|
mainWindow.loadURL(
|
||||||
prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'], moreKeys)
|
prepareFileUrl(
|
||||||
|
[__dirname, 'libtextsecure', 'test', 'index.html'],
|
||||||
|
moreKeys
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadURL(prepareURL([__dirname, 'background.html'], moreKeys));
|
mainWindow.loadURL(
|
||||||
|
prepareFileUrl([__dirname, 'background.html'], moreKeys)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enableCI && config.get('openDevTools')) {
|
if (!enableCI && config.get('openDevTools')) {
|
||||||
|
@ -766,7 +782,7 @@ function showAbout() {
|
||||||
|
|
||||||
handleCommonWindowEvents(aboutWindow);
|
handleCommonWindowEvents(aboutWindow);
|
||||||
|
|
||||||
aboutWindow.loadURL(prepareURL([__dirname, 'about.html']));
|
aboutWindow.loadURL(prepareFileUrl([__dirname, 'about.html']));
|
||||||
|
|
||||||
aboutWindow.on('closed', () => {
|
aboutWindow.on('closed', () => {
|
||||||
aboutWindow = null;
|
aboutWindow = null;
|
||||||
|
@ -823,7 +839,7 @@ function showSettingsWindow() {
|
||||||
|
|
||||||
handleCommonWindowEvents(settingsWindow);
|
handleCommonWindowEvents(settingsWindow);
|
||||||
|
|
||||||
settingsWindow.loadURL(prepareURL([__dirname, 'settings.html']));
|
settingsWindow.loadURL(prepareFileUrl([__dirname, 'settings.html']));
|
||||||
|
|
||||||
settingsWindow.on('closed', () => {
|
settingsWindow.on('closed', () => {
|
||||||
removeDarkOverlay();
|
removeDarkOverlay();
|
||||||
|
@ -895,8 +911,10 @@ async function showStickerCreator() {
|
||||||
handleCommonWindowEvents(stickerCreatorWindow);
|
handleCommonWindowEvents(stickerCreatorWindow);
|
||||||
|
|
||||||
const appUrl = config.enableHttp
|
const appUrl = config.enableHttp
|
||||||
? prepareURL(['http://localhost:6380/sticker-creator/dist/index.html'])
|
? prepareUrl(
|
||||||
: prepareURL([__dirname, 'sticker-creator/dist/index.html']);
|
new URL('http://localhost:6380/sticker-creator/dist/index.html')
|
||||||
|
)
|
||||||
|
: prepareFileUrl([__dirname, 'sticker-creator/dist/index.html']);
|
||||||
|
|
||||||
stickerCreatorWindow.loadURL(appUrl);
|
stickerCreatorWindow.loadURL(appUrl);
|
||||||
|
|
||||||
|
@ -947,7 +965,9 @@ async function showDebugLogWindow() {
|
||||||
|
|
||||||
handleCommonWindowEvents(debugLogWindow);
|
handleCommonWindowEvents(debugLogWindow);
|
||||||
|
|
||||||
debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html'], { theme }));
|
debugLogWindow.loadURL(
|
||||||
|
prepareFileUrl([__dirname, 'debug_log.html'], { theme })
|
||||||
|
);
|
||||||
|
|
||||||
debugLogWindow.on('closed', () => {
|
debugLogWindow.on('closed', () => {
|
||||||
removeDarkOverlay();
|
removeDarkOverlay();
|
||||||
|
@ -1000,7 +1020,7 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
|
||||||
handleCommonWindowEvents(permissionsPopupWindow);
|
handleCommonWindowEvents(permissionsPopupWindow);
|
||||||
|
|
||||||
permissionsPopupWindow.loadURL(
|
permissionsPopupWindow.loadURL(
|
||||||
prepareURL([__dirname, 'permissions_popup.html'], {
|
prepareFileUrl([__dirname, 'permissions_popup.html'], {
|
||||||
theme,
|
theme,
|
||||||
forCalling,
|
forCalling,
|
||||||
forCamera,
|
forCamera,
|
||||||
|
@ -1175,7 +1195,7 @@ app.on('ready', async () => {
|
||||||
loadingWindow = null;
|
loadingWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
loadingWindow.loadURL(prepareURL([__dirname, 'loading.html']));
|
loadingWindow.loadURL(prepareFileUrl([__dirname, 'loading.html']));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run window preloading in parallel with database initialization.
|
// Run window preloading in parallel with database initialization.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { gzip } from 'zlib';
|
||||||
import pify from 'pify';
|
import pify from 'pify';
|
||||||
import got, { Response } from 'got';
|
import got, { Response } from 'got';
|
||||||
import { getUserAgent } from '../util/getUserAgent';
|
import { getUserAgent } from '../util/getUserAgent';
|
||||||
|
import { maybeParseUrl } from '../util/url';
|
||||||
|
|
||||||
const BASE_URL = 'https://debuglogs.org';
|
const BASE_URL = 'https://debuglogs.org';
|
||||||
|
|
||||||
|
@ -22,10 +23,8 @@ const parseTokenBody = (
|
||||||
): { fields: Record<string, unknown>; url: string } => {
|
): { fields: Record<string, unknown>; url: string } => {
|
||||||
const body = tokenBodySchema.parse(rawBody);
|
const body = tokenBodySchema.parse(rawBody);
|
||||||
|
|
||||||
let parsedUrl: URL;
|
const parsedUrl = maybeParseUrl(body.url);
|
||||||
try {
|
if (!parsedUrl) {
|
||||||
parsedUrl = new URL(body.url);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error("Token body's URL was not a valid URL");
|
throw new Error("Token body's URL was not a valid URL");
|
||||||
}
|
}
|
||||||
if (parsedUrl.protocol !== 'https:') {
|
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
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { LoggerType } from '../types/Logging';
|
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) {
|
if (value instanceof URL) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
try {
|
return maybeParseUrl(value);
|
||||||
return new URL(value);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('Tried to parse a sgnl:// URL but got an unexpected type');
|
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 {
|
export function isSgnlHref(value: string | URL, logger: LoggerType): boolean {
|
||||||
const url = parseUrl(value, logger);
|
const url = parseUrl(value, logger);
|
||||||
return url !== null && url.protocol === 'sgnl:';
|
return Boolean(url?.protocol === 'sgnl:');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCaptchaHref(
|
export function isCaptchaHref(
|
||||||
|
@ -28,7 +27,7 @@ export function isCaptchaHref(
|
||||||
logger: LoggerType
|
logger: LoggerType
|
||||||
): boolean {
|
): boolean {
|
||||||
const url = parseUrl(value, logger);
|
const url = parseUrl(value, logger);
|
||||||
return url !== null && url.protocol === 'signalcaptcha:';
|
return Boolean(url?.protocol === 'signalcaptcha:');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSignalHttpsLink(
|
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 { MessageModel } from '../models/messages';
|
||||||
import { MessageType } from '../state/ducks/conversations';
|
import { MessageType } from '../state/ducks/conversations';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
|
import { maybeParseUrl } from '../util/url';
|
||||||
|
|
||||||
type GetLinkPreviewImageResult = {
|
type GetLinkPreviewImageResult = {
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer;
|
||||||
|
@ -3934,10 +3935,8 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
url: string,
|
url: string,
|
||||||
abortSignal: any
|
abortSignal: any
|
||||||
): Promise<null | GetLinkPreviewResult> {
|
): Promise<null | GetLinkPreviewResult> {
|
||||||
let urlObject;
|
const urlObject = maybeParseUrl(url);
|
||||||
try {
|
if (!urlObject) {
|
||||||
urlObject = new URL(url);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// Specify library files to be included in the compilation.
|
// Specify library files to be included in the compilation.
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom", // Required to access `window`
|
"dom", // Required to access `window`
|
||||||
|
"dom.iterable",
|
||||||
"es2020"
|
"es2020"
|
||||||
],
|
],
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue