diff --git a/package-lock.json b/package-lock.json index a2610e38bdd..be1d648b7e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "null-loader": "4.0.1", "optional-require": "1.0.3", "promise.allsettled": "1.0.4", - "punycode": "2.1.1", + "punycode": "2.3.0", "react": "18.2.0", "react-dom": "18.2.0", "react-emoji-render": "1.2.4", @@ -15403,9 +15403,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "engines": { "node": ">=6" } @@ -31286,9 +31286,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, "qs": { "version": "6.9.7", diff --git a/package.json b/package.json index 0dbcc9dac25..597e436cfa8 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "null-loader": "4.0.1", "optional-require": "1.0.3", "promise.allsettled": "1.0.4", - "punycode": "2.1.1", + "punycode": "2.3.0", "react": "18.2.0", "react-dom": "18.2.0", "react-emoji-render": "1.2.4", diff --git a/react/features/base/react/components/native/Linkify.tsx b/react/features/base/react/components/native/Linkify.tsx index 1aaa919afb1..46c610d9865 100644 --- a/react/features/base/react/components/native/Linkify.tsx +++ b/react/features/base/react/components/native/Linkify.tsx @@ -1,9 +1,9 @@ -import punycode from 'punycode'; import React, { Component } from 'react'; import ReactLinkify from 'react-linkify'; import { Text } from 'react-native'; import { StyleType } from '../../../styles/functions.any'; +import { formatURLText } from '../../functions'; import Link from './Link'; @@ -72,7 +72,7 @@ export default class Linkify extends Component { key = { key } style = { this.props.linkStyle } url = { decoratedHref }> - { punycode.toASCII(decoratedText) } + { formatURLText(decoratedText) } ); } diff --git a/react/features/base/react/components/web/Linkify.tsx b/react/features/base/react/components/web/Linkify.tsx index 3090e0ed918..5dc1197ce84 100644 --- a/react/features/base/react/components/web/Linkify.tsx +++ b/react/features/base/react/components/web/Linkify.tsx @@ -1,7 +1,8 @@ -import punycode from 'punycode'; import React, { Component, ReactNode } from 'react'; import ReactLinkify from 'react-linkify'; +import { formatURLText } from '../../functions'; + interface IProps { /** @@ -43,7 +44,7 @@ export default class Linkify extends Component { key = { key } rel = 'noopener noreferrer' target = '_blank'> - { punycode.toASCII(decoratedText) } + { formatURLText(decoratedText) } ); } diff --git a/react/features/base/react/functions.ts b/react/features/base/react/functions.ts index df627fb70e5..3339f582229 100644 --- a/react/features/base/react/functions.ts +++ b/react/features/base/react/functions.ts @@ -1,3 +1,5 @@ +import punycode from 'punycode'; + /** * Returns the field value in a platform generic way. * @@ -7,3 +9,41 @@ export function getFieldValue(fieldParameter: { target: { value: string; }; } | string) { return typeof fieldParameter === 'string' ? fieldParameter : fieldParameter?.target?.value; } + +/** + * Formats the URL text for react-linkify. + * + * @param {string} text - The URL text. + * @returns {string} - The formatted text. + */ +export function formatURLText(text = '') { + let result; + + // In order to prevent homograph attacks we need to use punycode. Reference + // https://github.com/tasti/react-linkify/issues/84. In the same time it seems PunycodeJS will treat the URL + // as an email when there is '@' and will erase parts of it. This is problematic if there is a URL like + // https://example.com/@test@@@123/test@test, punycode will truncate this to https://example.com/@test which + // is security issue because parts of the URL are actually missing from the text that we display. That's why + // we use punycode on valid URLs(that don't have '@' as part of the host) only for the host part of the URL. + try { + const url = new URL(text); + const { host } = url; + + if (host) { + url.host = punycode.toASCII(host); + result = url.toString(); + } + } catch (e) { + // Not a valid URL + } + + if (!result) { + // This will be the case for invalid URLs or URLs without a host (emails for example). In this case beacuse + // of the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@' + // and use punycode for every separate part to prevent homograph attacks. + result = text.split('@').map(punycode.toASCII) + .join('@'); + } + + return result; +}