From 60d348e7cb4a405d467504cfa7d89c8e19352f96 Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:29:01 -0600 Subject: [PATCH] Open all Signal links in app --- app/main.ts | 7 +++- ts/test-node/util/sgnlHref_test.ts | 61 +++++++++++++++++++++++++++++- ts/util/sgnlHref.ts | 29 ++++++++++++-- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/app/main.ts b/app/main.ts index ca31ef5df..7af5c9ffa 100644 --- a/app/main.ts +++ b/app/main.ts @@ -82,6 +82,7 @@ import { parseSgnlHref, parseCaptchaHref, parseSignalHttpsLink, + rewriteSignalHrefsIfNecessary, } from '../ts/util/sgnlHref'; import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow'; import { @@ -334,13 +335,15 @@ function prepareUrl( }).href; } -async function handleUrl(event: Electron.Event, target: string) { +async function handleUrl(event: Electron.Event, rawTarget: string) { event.preventDefault(); - const parsedUrl = maybeParseUrl(target); + const parsedUrl = maybeParseUrl(rawTarget); if (!parsedUrl) { return; } + const target = rewriteSignalHrefsIfNecessary(rawTarget); + const { protocol, hostname } = parsedUrl; const isDevServer = process.env.SIGNAL_ENABLE_HTTP && hostname === 'localhost'; diff --git a/ts/test-node/util/sgnlHref_test.ts b/ts/test-node/util/sgnlHref_test.ts index 66e8e298c..5ba29c4fe 100644 --- a/ts/test-node/util/sgnlHref_test.ts +++ b/ts/test-node/util/sgnlHref_test.ts @@ -1,4 +1,4 @@ -// Copyright 2020-2021 Signal Messenger, LLC +// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; @@ -13,6 +13,7 @@ import { parseCaptchaHref, parseE164FromSignalDotMeHash, parseSignalHttpsLink, + rewriteSignalHrefsIfNecessary, } from '../../util/sgnlHref'; function shouldNeverBeCalled() { @@ -380,4 +381,62 @@ describe('sgnlHref', () => { ); }); }); + + describe('rewriteSignalHrefsIfNecessary', () => { + it('rewrites http://signal.group hrefs, making them use HTTPS', () => { + assert.strictEqual( + rewriteSignalHrefsIfNecessary('http://signal.group/#abc123'), + 'https://signal.group/#abc123' + ); + }); + + it('rewrites http://signal.art hrefs, making them use HTTPS', () => { + assert.strictEqual( + rewriteSignalHrefsIfNecessary( + 'http://signal.art/addstickers/#pack_id=abc123' + ), + 'https://signal.art/addstickers/#pack_id=abc123' + ); + }); + + it('rewrites http://signal.me hrefs, making them use HTTPS', () => { + assert.strictEqual( + rewriteSignalHrefsIfNecessary('http://signal.me/#p/+18885551234'), + 'https://signal.me/#p/+18885551234' + ); + }); + + it('removes auth if present', () => { + assert.strictEqual( + rewriteSignalHrefsIfNecessary( + 'http://user:pass@signal.group/ab?c=d#ef' + ), + 'https://signal.group/ab?c=d#ef' + ); + assert.strictEqual( + rewriteSignalHrefsIfNecessary( + 'https://user:pass@signal.group/ab?c=d#ef' + ), + 'https://signal.group/ab?c=d#ef' + ); + }); + + it('does nothing to other hrefs', () => { + [ + // Normal URLs + 'http://example.com', + // Already HTTPS + 'https://signal.art/addstickers/#pack_id=abc123', + // Different port + 'http://signal.group:1234/abc?d=e#fg', + // Different subdomain + 'http://subdomain.signal.group/#abcdef', + // Different protocol + 'ftp://signal.group/#abc123', + 'ftp://user:pass@signal.group/#abc123', + ].forEach(href => { + assert.strictEqual(rewriteSignalHrefsIfNecessary(href), href); + }); + }); + }); }); diff --git a/ts/util/sgnlHref.ts b/ts/util/sgnlHref.ts index 043baaa13..3bcf95ce9 100644 --- a/ts/util/sgnlHref.ts +++ b/ts/util/sgnlHref.ts @@ -1,10 +1,11 @@ -// Copyright 2020-2021 Signal Messenger, LLC +// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { LoggerType } from '../types/Logging'; import { maybeParseUrl } from './url'; import { isValidE164 } from './isValidE164'; +const SIGNAL_HOSTS = new Set(['signal.group', 'signal.art', 'signal.me']); const SIGNAL_DOT_ME_HASH_PREFIX = 'p/'; function parseUrl(value: string | URL, logger: LoggerType): undefined | URL { @@ -44,9 +45,7 @@ export function isSignalHttpsLink( !url.password && !url.port && url.protocol === 'https:' && - (url.host === 'signal.group' || - url.host === 'signal.art' || - url.host === 'signal.me') + SIGNAL_HOSTS.has(url.host) ); } @@ -143,3 +142,25 @@ export function parseE164FromSignalDotMeHash(hash: string): undefined | string { const maybeE164 = hash.slice(SIGNAL_DOT_ME_HASH_PREFIX.length); return isValidE164(maybeE164, true) ? maybeE164 : undefined; } + +/** + * Converts `http://signal.group/#abc` to `https://signal.group/#abc`. Does the same for + * other Signal hosts, like signal.me. Does nothing to other URLs. Expects a valid href. + */ +export function rewriteSignalHrefsIfNecessary(href: string): string { + const resultUrl = new URL(href); + + const isHttp = resultUrl.protocol === 'http:'; + const isHttpOrHttps = isHttp || resultUrl.protocol === 'https:'; + + if (SIGNAL_HOSTS.has(resultUrl.host) && isHttpOrHttps) { + if (isHttp) { + resultUrl.protocol = 'https:'; + } + resultUrl.username = ''; + resultUrl.password = ''; + return resultUrl.href; + } + + return href; +}