UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

339 lines (332 loc) 13.4 kB
"use strict"; // We don't use new URL() as it doesn't exactly do what we need, for example: // - It loses the original URL parts (which we need to manipulate and recreate URLs) // - It doesn't support the tauri:// protocol var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseUrl = parseUrl; exports.assertUsageUrlPathnameAbsolute = assertUsageUrlPathnameAbsolute; exports.assertUsageUrlRedirectTarget = assertUsageUrlRedirectTarget; exports.isUrl = isUrl; exports.isUri = isUri; exports.isUrlRedirectTarget = isUrlRedirectTarget; exports.isUrlRelative = isUrlRelative; exports.isUrlExternal = isUrlExternal; exports.isBaseServer = isBaseServer; exports.assertUrlComponents = assertUrlComponents; exports.createUrlFromComponents = createUrlFromComponents; const slice_js_1 = require("./slice.js"); const assert_js_1 = require("./assert.js"); const picocolors_1 = __importDefault(require("@brillout/picocolors")); function parseUrl(url, baseServer) { (0, assert_js_1.assert)(isUrl(url), url); (0, assert_js_1.assert)(baseServer.startsWith('/')); // Hash const { hashString: hashOriginal, withoutHash: urlWithoutHash } = extractHash(url); (0, assert_js_1.assert)(hashOriginal === null || hashOriginal.startsWith('#')); const hash = hashOriginal === null ? '' : decodeSafe(hashOriginal.slice(1)); // Search const { searchString: searchOriginal, withoutSearch: urlWithoutHashNorSearch } = extractSearch(urlWithoutHash); (0, assert_js_1.assert)(searchOriginal === null || searchOriginal.startsWith('?')); let searchString = ''; if (searchOriginal !== null) { searchString = searchOriginal; } else if (url.startsWith('#')) { const baseURI = getBaseURI(); searchString = (baseURI && extractSearch(baseURI).searchString) || ''; } const search = {}; const searchAll = {}; Array.from(new URLSearchParams(searchString)).forEach(([key, val]) => { search[key] = val; searchAll[key] = [...(searchAll.hasOwnProperty(key) ? searchAll[key] : []), val]; }); // Origin + pathname let { protocol, origin, pathnameAbsoluteWithBase } = getPathnameAbsoluteWithBase(urlWithoutHashNorSearch, baseServer); const pathnameOriginal = urlWithoutHashNorSearch.slice((origin || '').length); assertUrlComponents(url, origin, pathnameOriginal, searchOriginal, hashOriginal); // Base URL let { pathname, isBaseMissing } = removeBaseServer(pathnameAbsoluteWithBase, baseServer); // pageContext.urlParsed.href const href = createUrlFromComponents(origin, pathname, searchOriginal, hashOriginal); // pageContext.urlParsed.{hostname, port} const host = !origin ? null : origin.slice(protocol.length); const { hostname, port } = parseHost(host, url); // decode after setting href pathname = decodePathname(pathname); (0, assert_js_1.assert)(pathname.startsWith('/')); return { href, protocol, hostname, port, origin, pathname, pathnameOriginal: pathnameOriginal, isBaseMissing, search, searchAll, searchOriginal, hash, hashOriginal, }; } function extractHash(url) { const [withoutHash, ...parts] = url.split('#'); const hashString = ['', ...parts].join('#') || null; return { hashString, withoutHash: withoutHash }; } function extractSearch(url) { const [withoutSearch, ...parts] = url.split('?'); const searchString = ['', ...parts].join('?') || null; return { searchString, withoutSearch: withoutSearch }; } function decodeSafe(urlComponent) { try { return decodeURIComponent(urlComponent); } catch { } try { return decodeURI(urlComponent); } catch { } return urlComponent; } function decodePathname(urlPathname) { urlPathname = urlPathname.replace(/\s+$/, ''); urlPathname = urlPathname .split('/') .map((dir) => decodeSafe(dir).split('/').join('%2F')) .join('/'); return urlPathname; } function getPathnameAbsoluteWithBase(url, baseServer) { // Search and hash already extracted (0, assert_js_1.assert)(!url.includes('?') && !url.includes('#')); // url has origin { const { protocol, origin, pathname } = parseOrigin(url); if (origin) { return { protocol, origin, pathnameAbsoluteWithBase: pathname }; } (0, assert_js_1.assert)(pathname === url); } // url doesn't have origin if (url.startsWith('/')) { return { protocol: null, origin: null, pathnameAbsoluteWithBase: url }; } else { // url is a relative path const baseURI = getBaseURI(); let base; if (baseURI) { base = parseOrigin(baseURI.split('?')[0].split('#')[0]).pathname; } else { base = baseServer; } const pathnameAbsoluteWithBase = resolveUrlPathnameRelative(url, base); return { protocol: null, origin: null, pathnameAbsoluteWithBase }; } } function getBaseURI() { // In the browser, this is the Base URL of the current URL. // Safe access `window?.document?.baseURI` for users who shim `window` in Node.js const baseURI = typeof window !== 'undefined' ? window?.document?.baseURI : undefined; return baseURI; } function parseOrigin(url) { if (!isUrlWithProtocol(url)) { return { pathname: url, origin: null, protocol: null }; } else { const { protocol, uriWithoutProtocol } = parseProtocol(url); (0, assert_js_1.assert)(protocol); const [host, ...rest] = uriWithoutProtocol.split('/'); const origin = protocol + host; const pathname = '/' + rest.join('/'); return { pathname, origin, protocol }; } } function parseHost(host, url) { const ret = { hostname: null, port: null }; if (!host) return ret; // port const parts = host.split(':'); if (parts.length > 1) { const port = parseInt(parts.pop(), 10); (0, assert_js_1.assert)(port || port === 0, url); ret.port = port; } // hostname ret.hostname = parts.join(':'); return ret; } function parseProtocol(uri) { const SEP = ':'; const [before, ...after] = uri.split(SEP); if (after.length === 0 || // https://github.com/vikejs/vike/commit/886a99ff21e86a8ca699a25cee7edc184aa058e4#r143308934 // https://en.wikipedia.org/wiki/List_of_URI_schemes // https://www.rfc-editor.org/rfc/rfc7595 !/^[a-z][a-z0-9\+\-]*$/i.test(before)) { return { protocol: null, uriWithoutProtocol: uri }; } let protocol = before + SEP; let uriWithoutProtocol = after.join(SEP); const SEP2 = '//'; if (uriWithoutProtocol.startsWith(SEP2)) { protocol = protocol + SEP2; uriWithoutProtocol = uriWithoutProtocol.slice(SEP2.length); } return { protocol, uriWithoutProtocol }; } function isUrlProtocol(protocol) { // Is there an alternative to having a blocklist? // - If the blocklist becomes too big, maybe use a allowlist instead ['tauri://', 'file://', 'capacitor://', 'http://', 'https://'] const blocklist = [ // https://docs.ipfs.tech/how-to/address-ipfs-on-web 'ipfs://', 'ipns://', ]; if (blocklist.includes(protocol)) return false; return protocol.endsWith('://'); } // Adapted from https://stackoverflow.com/questions/14780350/convert-relative-path-to-absolute-using-javascript/14780463#14780463 function resolveUrlPathnameRelative(pathnameRelative, base) { const stack = base.split('/'); const parts = pathnameRelative.split('/'); let baseRestoreTrailingSlash = base.endsWith('/'); if (pathnameRelative.startsWith('.')) { // remove current file name stack.pop(); } for (const i in parts) { const p = parts[i]; if (p == '' && i === '0') continue; if (p == '.') continue; if (p == '..') stack.pop(); else { baseRestoreTrailingSlash = false; stack.push(p); } } let pathnameAbsolute = stack.join('/'); if (baseRestoreTrailingSlash && !pathnameAbsolute.endsWith('/')) pathnameAbsolute += '/'; if (!pathnameAbsolute.startsWith('/')) pathnameAbsolute = '/' + pathnameAbsolute; return pathnameAbsolute; } function removeBaseServer(pathnameAbsoluteWithBase, baseServer) { (0, assert_js_1.assert)(pathnameAbsoluteWithBase.startsWith('/')); (0, assert_js_1.assert)(isBaseServer(baseServer)); // Mutable let urlPathname = pathnameAbsoluteWithBase; (0, assert_js_1.assert)(urlPathname.startsWith('/')); (0, assert_js_1.assert)(baseServer.startsWith('/')); if (baseServer === '/') { const pathname = pathnameAbsoluteWithBase; return { pathname, isBaseMissing: false }; } // Support `url === '/some-base-url' && baseServer === '/some-base-url/'` let baseServerNormalized = baseServer; if (baseServer.endsWith('/') && urlPathname === (0, slice_js_1.slice)(baseServer, 0, -1)) { baseServerNormalized = (0, slice_js_1.slice)(baseServer, 0, -1); (0, assert_js_1.assert)(urlPathname === baseServerNormalized); } if (!urlPathname.startsWith(baseServerNormalized)) { const pathname = pathnameAbsoluteWithBase; return { pathname, isBaseMissing: true }; } (0, assert_js_1.assert)(urlPathname.startsWith('/') || urlPathname.startsWith('http')); (0, assert_js_1.assert)(urlPathname.startsWith(baseServerNormalized)); urlPathname = urlPathname.slice(baseServerNormalized.length); if (!urlPathname.startsWith('/')) urlPathname = '/' + urlPathname; (0, assert_js_1.assert)(urlPathname.startsWith('/')); return { pathname: urlPathname, isBaseMissing: false }; } function isBaseServer(baseServer) { return baseServer.startsWith('/'); } function assertUrlComponents(url, origin, pathnameOriginal, searchOriginal, hashOriginal) { const urlRecreated = createUrlFromComponents(origin, pathnameOriginal, searchOriginal, hashOriginal); (0, assert_js_1.assert)(url === urlRecreated); } function createUrlFromComponents(origin, pathname, search, hash) { const urlRecreated = `${origin || ''}${pathname}${search || ''}${hash || ''}`; return urlRecreated; } function isUrl(url) { // parseUrl() works with these URLs return isUrlWithProtocol(url) || url.startsWith('/') || isUrlRelative(url); } function isUrlRedirectTarget(url) { return url.startsWith('/') || isUri(url) || isUrlWithProtocol(url); } function isUrlRelative(url) { return ['.', '?', '#'].some((c) => url.startsWith(c)) || url === ''; } function isUrlExternal(url) { return !url.startsWith('/') && !isUrlRelative(url); } /* URL with protocol. http://example.com https://example.com tauri://localhost file://example.com capacitor://localhost/assets/chunks/chunk-DJBYDrsP.js [Tauri](https://github.com/vikejs/vike/issues/808) [Electron (`file://`)](https://github.com/vikejs/vike/issues/1557) [Capacitor](https://github.com/vikejs/vike/issues/1706) */ function isUrlWithProtocol(url) { const { protocol } = parseProtocol(url); return !!protocol && isUrlProtocol(protocol); } /* URIs that aren't URLs. mailto:john@example.com ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Vincent_van_Gogh.html magnet:?xt=urn:btih:3a15e1ac49683d91b20c2ffc252ea612a6c01bd7&dn=The.Empire.Strikes.Back.1980.Remastered.1080p.BluRay.DDP.7.1.x265-EDGE2020.mkv&xl=3225443573&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://tracker.torrent.eu.org:451&tr=udp://open.stealth.si:80/announce&tr=udp://tracker.openbittorrent.com:6969&tr=udp://tracker.tiny-vps.com:6969/announce&tr=udp://open.demonii.com:1337 We need to treat URIs differently than URLs. - For example, we cannot parse URIs (their structure is unknown e.g. a `magnet:` URI is completely different than a `http://` URL). - The protocols tauri:// file:// capacitor:// follow the same structure as http:// and https:// URLs. - Thus we can parse them like http:// and https:// URLs. */ function isUri(uri) { const { protocol } = parseProtocol(uri); return !!protocol && !isUrlProtocol(uri); } function assertUsageUrlPathnameAbsolute(url, errPrefix) { assertUsageUrl(url, errPrefix); } function assertUsageUrlRedirectTarget(url, errPrefix, isUnresolved) { assertUsageUrl(url, errPrefix, { isRedirectTarget: isUnresolved ? 'unresolved' : true }); } function assertUsageUrl(url, errPrefix, { isRedirectTarget } = {}) { if (url.startsWith('/')) return; let errMsg = `${errPrefix} is ${picocolors_1.default.string(url)} but it should start with ${picocolors_1.default.string('/')}`; if (isRedirectTarget) { if (isUrlRedirectTarget(url)) return; errMsg += ` or a protocol (${picocolors_1.default.string('http://')}, ${picocolors_1.default.string('mailto:')}, ...)`; if (isRedirectTarget === 'unresolved') { if (url === '*') return; errMsg += `, or be ${picocolors_1.default.string('*')}`; } } (0, assert_js_1.assertUsage)(false, errMsg); }