Better emoji support in linkify/previews
This commit is contained in:
parent
65ad608aa7
commit
773aa9af19
15 changed files with 337 additions and 260 deletions
14
js/modules/link_previews.d.ts
vendored
14
js/modules/link_previews.d.ts
vendored
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2019-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export function isLinkSafeToPreview(link: string): boolean;
|
||||
|
||||
export function findLinks(text: string, caretLocation?: number): Array<string>;
|
||||
|
||||
export function getDomain(href: string): string;
|
||||
|
||||
export function isGroupLink(href: string): boolean;
|
||||
|
||||
export function isLinkSneaky(link: string): boolean;
|
||||
|
||||
export function isStickerPack(href: string): boolean;
|
|
@ -1,163 +0,0 @@
|
|||
// Copyright 2019-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
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();
|
||||
|
||||
module.exports = {
|
||||
findLinks,
|
||||
getDomain,
|
||||
isGroupLink,
|
||||
isLinkSafeToPreview,
|
||||
isLinkSneaky,
|
||||
isStickerPack,
|
||||
};
|
||||
|
||||
function isLinkSafeToPreview(href) {
|
||||
const url = maybeParseUrl(href);
|
||||
return Boolean(url && url.protocol === 'https:' && !isLinkSneaky(href));
|
||||
}
|
||||
|
||||
function isStickerPack(link) {
|
||||
return (link || '').startsWith('https://signal.art/addstickers/');
|
||||
}
|
||||
|
||||
function isGroupLink(link) {
|
||||
return (link || '').startsWith('https://signal.group/');
|
||||
}
|
||||
|
||||
function findLinks(text, caretLocation) {
|
||||
const haveCaretLocation = isNumber(caretLocation);
|
||||
const textLength = text ? text.length : 0;
|
||||
|
||||
const matches = linkify.match(text || '') || [];
|
||||
return compact(
|
||||
matches.map(match => {
|
||||
if (!haveCaretLocation) {
|
||||
return match.text;
|
||||
}
|
||||
|
||||
if (match.lastIndex === textLength && caretLocation === textLength) {
|
||||
return match.text;
|
||||
}
|
||||
|
||||
if (match.index > caretLocation || match.lastIndex < caretLocation) {
|
||||
return match.text;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getDomain(href) {
|
||||
const url = maybeParseUrl(href);
|
||||
return url ? url.hostname : null;
|
||||
}
|
||||
|
||||
// See <https://tools.ietf.org/html/rfc3986>.
|
||||
const VALID_URI_CHARACTERS = new Set([
|
||||
'%',
|
||||
// "gen-delims"
|
||||
':',
|
||||
'/',
|
||||
'?',
|
||||
'#',
|
||||
'[',
|
||||
']',
|
||||
'@',
|
||||
// "sub-delims"
|
||||
'!',
|
||||
'$',
|
||||
'&',
|
||||
"'",
|
||||
'(',
|
||||
')',
|
||||
'*',
|
||||
'+',
|
||||
',',
|
||||
';',
|
||||
'=',
|
||||
// unreserved
|
||||
...String.fromCharCode(...range(65, 91), ...range(97, 123)),
|
||||
...range(10).map(String),
|
||||
'-',
|
||||
'.',
|
||||
'_',
|
||||
'~',
|
||||
]);
|
||||
const ASCII_PATTERN = new RegExp('[\\u0020-\\u007F]', 'g');
|
||||
const MAX_HREF_LENGTH = 2 ** 12;
|
||||
|
||||
function isLinkSneaky(href) {
|
||||
// This helps users avoid extremely long links (which could be hiding something
|
||||
// sketchy) and also sidesteps the performance implications of extremely long hrefs.
|
||||
if (href.length > MAX_HREF_LENGTH) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const url = maybeParseUrl(href);
|
||||
|
||||
// If we can't parse it, it's sneaky.
|
||||
if (!url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Any links which contain auth are considered sneaky
|
||||
if (url.username || url.password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the domain is falsy, something fishy is going on
|
||||
if (!url.hostname) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// To quote [RFC 1034][0]: "the total number of octets that represent a
|
||||
// domain name [...] is limited to 255." To be extra careful, we set a
|
||||
// maximum of 2048. (This also uses the string's `.length` property,
|
||||
// which isn't exactly the same thing as the number of octets.)
|
||||
// [0]: https://tools.ietf.org/html/rfc1034
|
||||
if (url.hostname.length > 2048) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Domains cannot contain encoded characters
|
||||
if (url.hostname.includes('%')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// There must be at least 2 domain labels, and none of them can be empty.
|
||||
const labels = url.hostname.split('.');
|
||||
if (labels.length < 2 || labels.some(isEmpty)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is necesary because getDomain returns domains in punycode form.
|
||||
const unicodeDomain = nodeUrl.domainToUnicode
|
||||
? nodeUrl.domainToUnicode(url.hostname)
|
||||
: url.hostname;
|
||||
|
||||
const withoutPeriods = unicodeDomain.replace(/\./g, '');
|
||||
|
||||
const hasASCII = ASCII_PATTERN.test(withoutPeriods);
|
||||
const withoutASCII = withoutPeriods.replace(ASCII_PATTERN, '');
|
||||
|
||||
const isMixed = hasASCII && withoutASCII.length > 0;
|
||||
if (isMixed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't use `url.pathname` (and so on) because it automatically encodes strings.
|
||||
// For example, it turns `/aquí` into `/aqu%C3%AD`.
|
||||
const startOfPathAndHash = href.indexOf('/', url.protocol.length + 4);
|
||||
const pathAndHash =
|
||||
startOfPathAndHash === -1 ? '' : href.substr(startOfPathAndHash);
|
||||
return [...pathAndHash].some(
|
||||
character => !VALID_URI_CHARACTERS.has(character)
|
||||
);
|
||||
}
|
|
@ -21,7 +21,6 @@ const Stickers = require('./stickers');
|
|||
const Settings = require('./settings');
|
||||
const RemoteConfig = require('../../ts/RemoteConfig');
|
||||
const Util = require('../../ts/util');
|
||||
const LinkPreviews = require('./link_previews');
|
||||
|
||||
// Components
|
||||
const {
|
||||
|
@ -445,7 +444,6 @@ exports.setup = (options = {}) => {
|
|||
Groups,
|
||||
GroupChange,
|
||||
IndexedDB,
|
||||
LinkPreviews,
|
||||
Migrations,
|
||||
Notifications,
|
||||
OS,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue