(
url: string | null | undefined,
paramName = 'url',
options: { allowHttp?: boolean } = {}
)
| 697 | * ``` |
| 698 | */ |
| 699 | export function validateExternalUrl( |
| 700 | url: string | null | undefined, |
| 701 | paramName = 'url', |
| 702 | options: { allowHttp?: boolean } = {} |
| 703 | ): ValidationResult { |
| 704 | if (!url || typeof url !== 'string') { |
| 705 | return { |
| 706 | isValid: false, |
| 707 | error: `${paramName} is required and must be a string`, |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | let parsedUrl: URL |
| 712 | try { |
| 713 | parsedUrl = new URL(url) |
| 714 | } catch { |
| 715 | return { |
| 716 | isValid: false, |
| 717 | error: `${paramName} must be a valid URL`, |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | const protocol = parsedUrl.protocol |
| 722 | const hostname = parsedUrl.hostname.toLowerCase() |
| 723 | |
| 724 | const cleanHostname = |
| 725 | hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname |
| 726 | |
| 727 | let isLocalhost = cleanHostname === 'localhost' |
| 728 | if (ipaddr.isValid(cleanHostname)) { |
| 729 | const processedIP = ipaddr.process(cleanHostname).toString() |
| 730 | if (processedIP === '127.0.0.1' || processedIP === '::1') { |
| 731 | isLocalhost = true |
| 732 | } |
| 733 | } |
| 734 | |
| 735 | if (isLocalhost && isHosted) { |
| 736 | return { |
| 737 | isValid: false, |
| 738 | error: `${paramName} cannot point to localhost`, |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | if (options.allowHttp) { |
| 743 | if (protocol !== 'https:' && protocol !== 'http:') { |
| 744 | return { |
| 745 | isValid: false, |
| 746 | error: `${paramName} must use http:// or https:// protocol`, |
| 747 | } |
| 748 | } |
| 749 | } else if (protocol !== 'https:' && !(protocol === 'http:' && isLocalhost && !isHosted)) { |
| 750 | return { |
| 751 | isValid: false, |
| 752 | error: `${paramName} must use https:// protocol`, |
| 753 | } |
| 754 | } |
| 755 | |
| 756 | if (!isLocalhost && ipaddr.isValid(cleanHostname)) { |
no test coverage detected