Files
AFFiNE-Mirror/blocksuite/affine/shared/src/utils/url.ts
T

158 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const ALLOWED_SCHEMES = [
'http',
'https',
'ftp',
'sftp',
'mailto',
'tel',
// may need support other schemes
];
// I guess you don't want to use the regex base the RFC 5322 Official Standard
// For more detail see https://stackoverflow.com/questions/201323/how-can-i-validate-an-email-address-using-a-regular-expression/1917982#1917982
const MAIL_REGEX =
/^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
// For more detail see https://stackoverflow.com/questions/8667070/javascript-regular-expression-to-validate-url
const URL_REGEX = new RegExp(
'^' +
// protocol identifier (optional)
// short syntax // still required
'(?:(?:(?:https?|ftp):)?\\/\\/)' +
// user:pass BasicAuth (optional)
'(?:\\S+(?::\\S*)?@)?' +
'(?:' +
// IP address exclusion
// private & local networks
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broadcast addresses
// (first & last IP address of each class)
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
'|' +
// host & domain names, may end with dot
// can be replaced by a shortest alternative
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
'(?:' +
'(?:' +
'[a-z0-9\\u00a1-\\uffff]' +
'[a-z0-9\\u00a1-\\uffff_-]{0,62}' +
')?' +
'[a-z0-9\\u00a1-\\uffff]\\.' +
')+' +
// TLD identifier name, may end with dot
// Addition: We limit the TLD to 2-6 characters, because it can cover most of the cases.
'(?:[a-z\\u00a1-\\uffff]{2,6}\\.?)' +
')' +
// port number (optional)
'(?::\\d{2,5})?' +
// resource path (optional)
'(?:[/?#]\\S*)?' +
'$',
'i'
);
export function normalizeUrl(url: string) {
const includeScheme = ALLOWED_SCHEMES.find(scheme =>
url.startsWith(scheme + ':')
);
if (includeScheme) {
// Any link include schema is a valid url
return url;
}
const isEmail = MAIL_REGEX.test(url);
if (isEmail) {
return 'mailto:' + url;
}
return 'http://' + url;
}
/**
* Assume user will input a url, we just need to check if it is valid.
*
* For more detail see https://www.ietf.org/rfc/rfc1738.txt
*/
export function isValidUrl(str: string) {
if (!str) {
return false;
}
const url = normalizeUrl(str);
if (url === str) {
// Skip check if user input scheme manually
try {
new URL(url);
} catch {
return false;
}
return true;
}
return URL_REGEX.test(url);
}
// https://en.wikipedia.org/wiki/Top-level_domain
const COMMON_TLDS = new Set([
'com',
'org',
'net',
'edu',
'gov',
'co',
'io',
'me',
'moe',
'mil',
'top',
'dev',
'xyz',
'info',
'cat',
'ru',
'de',
'jp',
'uk',
'pro',
]);
function isCommonTLD(url: URL) {
const tld = url.hostname.split('.').pop();
if (!tld) {
return false;
}
return COMMON_TLDS.has(tld);
}
/**
* Assuming the user will input anything, we need to check rigorously.
*/
export function isStrictUrl(str: string) {
try {
if (!isValidUrl(str)) {
return false;
}
const url = new URL(normalizeUrl(str));
return isCommonTLD(url);
} catch {
return false;
}
}
export function isUrlInClipboard(clipboardData: DataTransfer) {
const url = clipboardData.getData('text/plain');
return isValidUrl(url);
}
export function getHostName(url: string) {
try {
return new URL(url).hostname;
} catch {
return url;
}
}