Fixes 1731, compare URLs case-insensitive (#1980)

zio/stable
Eric Bailey 2023-11-22 17:20:35 -06:00 committed by GitHub
parent ec819f06ce
commit edf3114e47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 5 deletions

View File

@ -1,3 +1,5 @@
import {it, describe, expect} from '@jest/globals'
import { import {
linkRequiresWarning, linkRequiresWarning,
isPossiblyAUrl, isPossiblyAUrl,
@ -6,6 +8,7 @@ import {
describe('linkRequiresWarning', () => { describe('linkRequiresWarning', () => {
type Case = [string, string, boolean] type Case = [string, string, boolean]
const cases: Case[] = [ const cases: Case[] = [
['http://example.com', 'http://example.com', false], ['http://example.com', 'http://example.com', false],
['http://example.com', 'example.com', false], ['http://example.com', 'example.com', false],
@ -64,6 +67,10 @@ describe('linkRequiresWarning', () => {
['http://bsky.app/', 'https://google.com', true], ['http://bsky.app/', 'https://google.com', true],
['https://bsky.app/', 'https://google.com', true], ['https://bsky.app/', 'https://google.com', true],
// case insensitive
['https://Example.com', 'example.com', false],
['https://example.com', 'Example.com', false],
// bad uri inputs, default to true // bad uri inputs, default to true
['', '', true], ['', '', true],
['example.com', 'example.com', true], ['example.com', 'example.com', true],

View File

@ -2,6 +2,9 @@
import {configure} from '@testing-library/react-native' import {configure} from '@testing-library/react-native'
import 'react-native-gesture-handler/jestSetup' import 'react-native-gesture-handler/jestSetup'
// IMPORTANT: this is what's used in the native runtime
import 'react-native-url-polyfill/auto'
configure({asyncUtilTimeout: 20000}) configure({asyncUtilTimeout: 20000})
jest.mock('@react-native-async-storage/async-storage', () => jest.mock('@react-native-async-storage/async-storage', () =>

View File

@ -168,8 +168,15 @@ export function getYoutubeVideoId(link: string): string | undefined {
return videoId return videoId
} }
/**
* Checks if the label in the post text matches the host of the link facet.
*
* Hosts are case-insensitive, so should be lowercase for comparison.
* @see https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2
*/
export function linkRequiresWarning(uri: string, label: string) { export function linkRequiresWarning(uri: string, label: string) {
const labelDomain = labelToDomain(label) const labelDomain = labelToDomain(label)
let urip let urip
try { try {
urip = new URL(uri) urip = new URL(uri)
@ -177,7 +184,9 @@ export function linkRequiresWarning(uri: string, label: string) {
return true return true
} }
if (urip.hostname === 'bsky.app') { const host = urip.hostname.toLowerCase()
if (host === 'bsky.app') {
// if this is a link to internal content, // if this is a link to internal content,
// warn if it represents itself as a URL to another app // warn if it represents itself as a URL to another app
if ( if (
@ -194,20 +203,26 @@ export function linkRequiresWarning(uri: string, label: string) {
if (!labelDomain) { if (!labelDomain) {
return true return true
} }
return labelDomain !== urip.hostname return labelDomain !== host
} }
} }
function labelToDomain(label: string): string | undefined { /**
* Returns a lowercase domain hostname if the label is a valid URL.
*
* Hosts are case-insensitive, so should be lowercase for comparison.
* @see https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2
*/
export function labelToDomain(label: string): string | undefined {
// any spaces just immediately consider the label a non-url // any spaces just immediately consider the label a non-url
if (/\s/.test(label)) { if (/\s/.test(label)) {
return undefined return undefined
} }
try { try {
return new URL(label).hostname return new URL(label).hostname.toLowerCase()
} catch {} } catch {}
try { try {
return new URL('https://' + label).hostname return new URL('https://' + label).hostname.toLowerCase()
} catch {} } catch {}
return undefined return undefined
} }