'use strict'; /* Derived from normalize-url https://github.com/sindresorhus/normalize-url/main/index.js by Sindre Sorhus */ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain'; const DATA_URL_DEFAULT_CHARSET = 'us-ascii'; const supportedProtocols = new Set(['https:', 'http:', 'file:']); /** * @param {string} urlString * @return {boolean} */ function hasCustomProtocol(urlString) { try { const { protocol } = new URL(urlString); return protocol.endsWith(':') && !supportedProtocols.has(protocol); } catch { return false; } } /** * @param {string} urlString * @return {string} */ function normalizeDataURL(urlString) { const match = /^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec( urlString ); if (!match) { throw new Error(`Invalid URL: ${urlString}`); } let { type, data, hash } = /** @type {{type: string, data: string, hash: string}} */ (match.groups); const mediaType = type.split(';'); let isBase64 = false; if (mediaType[mediaType.length - 1] === 'base64') { mediaType.pop(); isBase64 = true; } // Lowercase MIME type const mimeType = mediaType.shift()?.toLowerCase() ?? ''; const attributes = mediaType .map( /** @type {(string: string) => string} */ (attribute) => { let [key, value = ''] = attribute .split('=') .map( /** @type {(string: string) => string} */ (string) => string.trim() ); // Lowercase `charset` if (key === 'charset') { value = value.toLowerCase(); if (value === DATA_URL_DEFAULT_CHARSET) { return ''; } } return `${key}${value ? `=${value}` : ''}`; } ) .filter(Boolean); const normalizedMediaType = [...attributes]; if (isBase64) { normalizedMediaType.push('base64'); } if ( normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE) ) { normalizedMediaType.unshift(mimeType); } return `data:${normalizedMediaType.join(';')},${ isBase64 ? data.trim() : data }${hash ? `#${hash}` : ''}`; } /** * @param {string} urlString * @return {string} */ function normalizeUrl(urlString) { urlString = urlString.trim(); // Data URL if (/^data:/i.test(urlString)) { return normalizeDataURL(urlString); } if (hasCustomProtocol(urlString)) { return urlString; } const hasRelativeProtocol = urlString.startsWith('//'); const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); // Prepend protocol if (!isRelativeUrl) { urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, 'http:'); } const urlObject = new URL(urlString); // Remove duplicate slashes if not preceded by a protocol if (urlObject.pathname) { urlObject.pathname = urlObject.pathname.replace( /(?