Update trusted hosts, allow `#`, and add more tests (#3232)
* Update trusted hosts, allow `#`, and add more tests * update commentszio/stable
parent
5b4b8e47d9
commit
5e0a6a12ff
|
@ -4,6 +4,7 @@ import {
|
||||||
linkRequiresWarning,
|
linkRequiresWarning,
|
||||||
isPossiblyAUrl,
|
isPossiblyAUrl,
|
||||||
splitApexDomain,
|
splitApexDomain,
|
||||||
|
isTrustedUrl,
|
||||||
} from '../../../src/lib/strings/url-helpers'
|
} from '../../../src/lib/strings/url-helpers'
|
||||||
|
|
||||||
describe('linkRequiresWarning', () => {
|
describe('linkRequiresWarning', () => {
|
||||||
|
@ -74,6 +75,10 @@ describe('linkRequiresWarning', () => {
|
||||||
// bad uri inputs, default to true
|
// bad uri inputs, default to true
|
||||||
['', '', true],
|
['', '', true],
|
||||||
['example.com', 'example.com', true],
|
['example.com', 'example.com', true],
|
||||||
|
['/profile', 'Username', false],
|
||||||
|
['#', 'Show More', false],
|
||||||
|
['https://docs.bsky.app', 'https://docs.bsky.app', false],
|
||||||
|
['https://bsky.app/compose/intent?text=test', 'Compose a post', false],
|
||||||
]
|
]
|
||||||
|
|
||||||
it.each(cases)(
|
it.each(cases)(
|
||||||
|
@ -139,3 +144,36 @@ describe('splitApexDomain', () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isTrustedUrl', () => {
|
||||||
|
const cases = [
|
||||||
|
['#', true],
|
||||||
|
['#profile', true],
|
||||||
|
['/', true],
|
||||||
|
['/profile', true],
|
||||||
|
['/profile/', true],
|
||||||
|
['/profile/bob.test', true],
|
||||||
|
['https://bsky.app', true],
|
||||||
|
['https://bsky.app/', true],
|
||||||
|
['https://bsky.app/profile/bob.test', true],
|
||||||
|
['https://www.bsky.app', true],
|
||||||
|
['https://www.bsky.app/', true],
|
||||||
|
['https://docs.bsky.app', true],
|
||||||
|
['https://bsky.social', true],
|
||||||
|
['https://bsky.social/blog', true],
|
||||||
|
['https://blueskyweb.xyz', true],
|
||||||
|
['https://blueskyweb.zendesk.com', true],
|
||||||
|
['http://bsky.app', true],
|
||||||
|
['http://bsky.social', true],
|
||||||
|
['http://blueskyweb.xyz', true],
|
||||||
|
['http://blueskyweb.zendesk.com', true],
|
||||||
|
['https://google.com', false],
|
||||||
|
['https://docs.google.com', false],
|
||||||
|
['https://google.com/#', false],
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(cases)('given input uri %p, returns %p', (str, expected) => {
|
||||||
|
const output = isTrustedUrl(str)
|
||||||
|
expect(output).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -4,6 +4,23 @@ import TLDs from 'tlds'
|
||||||
import psl from 'psl'
|
import psl from 'psl'
|
||||||
|
|
||||||
export const BSKY_APP_HOST = 'https://bsky.app'
|
export const BSKY_APP_HOST = 'https://bsky.app'
|
||||||
|
const BSKY_TRUSTED_HOSTS = [
|
||||||
|
'bsky.app',
|
||||||
|
'bsky.social',
|
||||||
|
'blueskyweb.xyz',
|
||||||
|
'blueskyweb.zendesk.com',
|
||||||
|
...(__DEV__ ? ['localhost:19006', 'localhost:8100'] : []),
|
||||||
|
]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This will allow any BSKY_TRUSTED_HOSTS value by itself or with a subdomain.
|
||||||
|
* It will also allow relative paths like /profile as well as #.
|
||||||
|
*/
|
||||||
|
const TRUSTED_REGEX = new RegExp(
|
||||||
|
`^(http(s)?://(([\\w-]+\\.)?${BSKY_TRUSTED_HOSTS.join(
|
||||||
|
'|([\\w-]+\\.)?',
|
||||||
|
)})|/|#)`,
|
||||||
|
)
|
||||||
|
|
||||||
export function isValidDomain(str: string): boolean {
|
export function isValidDomain(str: string): boolean {
|
||||||
return !!TLDs.find(tld => {
|
return !!TLDs.find(tld => {
|
||||||
|
@ -86,6 +103,10 @@ export function isExternalUrl(url: string): boolean {
|
||||||
return external || rss
|
return external || rss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTrustedUrl(url: string): boolean {
|
||||||
|
return TRUSTED_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
export function isBskyPostUrl(url: string): boolean {
|
export function isBskyPostUrl(url: string): boolean {
|
||||||
if (isBskyAppUrl(url)) {
|
if (isBskyAppUrl(url)) {
|
||||||
try {
|
try {
|
||||||
|
@ -163,8 +184,8 @@ export function feedUriToHref(url: string): string {
|
||||||
export function linkRequiresWarning(uri: string, label: string) {
|
export function linkRequiresWarning(uri: string, label: string) {
|
||||||
const labelDomain = labelToDomain(label)
|
const labelDomain = labelToDomain(label)
|
||||||
|
|
||||||
// If the uri started with a / we know it is internal.
|
// We should trust any relative URL or a # since we know it links to internal content
|
||||||
if (isRelativeUrl(uri)) {
|
if (isRelativeUrl(uri) || uri === '#') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,18 +197,11 @@ export function linkRequiresWarning(uri: string, label: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = urip.hostname.toLowerCase()
|
const host = urip.hostname.toLowerCase()
|
||||||
// Hosts that end with bsky.app or bsky.social should be trusted by default.
|
if (isTrustedUrl(uri)) {
|
||||||
if (
|
// if this is a link to internal content, warn if it represents itself as a URL to another app
|
||||||
host.endsWith('bsky.app') ||
|
|
||||||
host.endsWith('bsky.social') ||
|
|
||||||
host.endsWith('blueskyweb.xyz')
|
|
||||||
) {
|
|
||||||
// if this is a link to internal content,
|
|
||||||
// warn if it represents itself as a URL to another app
|
|
||||||
return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain)
|
return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain)
|
||||||
} else {
|
} else {
|
||||||
// if this is a link to external content,
|
// if this is a link to external content, warn if the label doesnt match the target
|
||||||
// warn if the label doesnt match the target
|
|
||||||
if (!labelDomain) {
|
if (!labelDomain) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue