vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
339 lines (332 loc) • 13.4 kB
JavaScript
;
// 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);
}