diff options
author | 2023-07-11 19:14:34 -0700 | |
---|---|---|
committer | 2023-07-11 19:14:34 -0700 | |
commit | cbb88672f217a90db1aa1eb29cd92d5d9035b22b (patch) | |
tree | 43a00501f3cde495967e116f0b660777051551f8 /src/js/node/url.js | |
parent | 1f900cff453700b19bca2acadfe26da4468c1282 (diff) | |
parent | 34b0e7a2bbd8bf8097341cdb0075d0908283e834 (diff) | |
download | bun-jarred/esm-conditions.tar.gz bun-jarred/esm-conditions.tar.zst bun-jarred/esm-conditions.zip |
Merge branch 'main' into jarred/esm-conditionsjarred/esm-conditions
Diffstat (limited to 'src/js/node/url.js')
-rw-r--r-- | src/js/node/url.js | 1194 |
1 files changed, 824 insertions, 370 deletions
diff --git a/src/js/node/url.js b/src/js/node/url.js index f9a4427ce..bb7093bcc 100644 --- a/src/js/node/url.js +++ b/src/js/node/url.js @@ -1,398 +1,852 @@ -// Hardcoded module "node:url" +/* + * Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + "use strict"; -const { URL: F, URLSearchParams: M, [Symbol.for("Bun.lazy")]: S } = globalThis; -function it(s) { - return typeof s == "string"; + +const { URL, URLSearchParams } = globalThis; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; } -function D(s) { - return typeof s == "object" && s !== null; -} -function I(s) { - return s === null; -} -function E(s) { - return s == null; -} -function ft(s) { - return s === void 0; -} -function m() { - (this.protocol = null), - (this.slashes = null), - (this.auth = null), - (this.host = null), - (this.port = null), - (this.hostname = null), - (this.hash = null), - (this.search = null), - (this.query = null), - (this.pathname = null), - (this.path = null), - (this.href = null); -} -var tt = /^([a-z0-9.+-]+:)/i, - st = /:[0-9]*$/, - ht = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, - et = [ - "<", - ">", - '"', - "`", - " ", - "\r", - ` -`, - " ", - ], - rt = ["{", "}", "|", "\\", "^", "`"].concat(et), - B = ["'"].concat(rt), - G = ["%", "/", "?", ";", "#"].concat(B), - J = ["/", "?", "#"], - ot = 255, - K = /^[+a-z0-9A-Z_-]{0,63}$/, - at = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, - nt = { javascript: !0, "javascript:": !0 }, - N = { javascript: !0, "javascript:": !0 }, - R = { - http: !0, - https: !0, - ftp: !0, - gopher: !0, - file: !0, - "http:": !0, - "https:": !0, - "ftp:": !0, - "gopher:": !0, - "file:": !0, + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +/* + * define these here so at least they only have to be + * compiled once on the first module load. + */ +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/, + /* + * RFC 2396: characters reserved for delimiting URLs. + * We actually just auto-escape these. + */ + delims = ["<", ">", '"', "`", " ", "\r", "\n", "\t"], + // RFC 2396: characters not allowed for various reasons. + unwise = ["{", "}", "|", "\\", "^", "`"].concat(delims), + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ["'"].concat(unwise), + /* + * Characters that are never ever allowed in a hostname. + * Note that any invalid chars are also handled, but these + * are the ones that are *expected* to be seen, so we fast-path + * them. + */ + nonHostChars = ["%", "/", "?", ";", "#"].concat(autoEscape), + hostEndingChars = ["/", "?", "#"], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + unsafeProtocol = { + javascript: true, + "javascript:": true, + }, + // protocols that never have a hostname. + hostlessProtocol = { + javascript: true, + "javascript:": true, }, - Z = { - parse(s) { - var r = decodeURIComponent; - return (s + "") - .replace(/\+/g, " ") - .split("&") - .filter(Boolean) - .reduce(function (t, o, a) { - var l = o.split("="), - f = r(l[0] || ""), - h = r(l[1] || ""), - g = t[f]; - return (t[f] = g === void 0 ? h : [].concat(g, h)), t; - }, {}); - }, - stringify(s) { - var r = encodeURIComponent; - return Object.keys(s || {}) - .reduce(function (t, o) { - return ( - [].concat(s[o]).forEach(function (a) { - t.push(r(o) + "=" + r(a)); - }), - t - ); - }, []) - .join("&") - .replace(/\s/g, "+"); - }, + // protocols that always contain a // bit. + slashedProtocol = { + http: true, + https: true, + ftp: true, + gopher: true, + file: true, + "http:": true, + "https:": true, + "ftp:": true, + "gopher:": true, + "file:": true, }; -function A(s, r, t) { - if (s && D(s) && s instanceof m) return s; - var o = new m(); - return o.parse(s, r, t), o; + +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url && typeof url === "object" && url instanceof Url) { + return url; + } + + var u = new Url(); + u.parse(url, parseQueryString, slashesDenoteHost); + return u; } -m.prototype.parse = function (s, r, t) { - if (!it(s)) throw new TypeError("Parameter 'url' must be a string, not " + typeof s); - var o = s.indexOf("?"), - a = o !== -1 && o < s.indexOf("#") ? "?" : "#", - l = s.split(a), - f = /\\/g; - (l[0] = l[0].replace(f, "/")), (s = l.join(a)); - var h = s; - if (((h = h.trim()), !t && s.split("#").length === 1)) { - var g = ht.exec(h); - if (g) - return ( - (this.path = h), - (this.href = h), - (this.pathname = g[1]), - g[2] - ? ((this.search = g[2]), - r ? (this.query = Z.parse(this.search.substr(1))) : (this.query = this.search.substr(1))) - : r && ((this.search = ""), (this.query = {})), - this - ); - } - var c = tt.exec(h); - if (c) { - c = c[0]; - var v = c.toLowerCase(); - (this.protocol = v), (h = h.substr(c.length)); - } - if (t || c || h.match(/^\/\/[^@\/]+@[^@\/]+/)) { - var j = h.substr(0, 2) === "//"; - j && !(c && N[c]) && ((h = h.substr(2)), (this.slashes = !0)); - } - if (!N[c] && (j || (c && !R[c]))) { - for (var u = -1, n = 0; n < J.length; n++) { - var b = h.indexOf(J[n]); - b !== -1 && (u === -1 || b < u) && (u = b); + +Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { + if (typeof url !== "string") { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } + + /* + * Copy chrome, IE, opera backslash-handling behavior. + * Back slashes before the query string get converted to forward slashes + * See: https://code.google.com/p/chromium/issues/detail?id=25916 + */ + var queryIndex = url.indexOf("?"), + splitter = queryIndex !== -1 && queryIndex < url.indexOf("#") ? "?" : "#", + uSplit = url.split(splitter), + slashRegex = /\\/g; + uSplit[0] = uSplit[0].replace(slashRegex, "/"); + url = uSplit.join(splitter); + + var rest = url; + + /* + * trim before proceeding. + * This is to support parse stuff like " http://foo.com \n" + */ + rest = rest.trim(); + + if (!slashesDenoteHost && url.split("#").length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = new URLSearchParams(this.search.substr(1)).toJSON(); + } else { + this.query = this.search.substr(1); + } + } else if (parseQueryString) { + this.search = ""; + this.query = {}; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); + } + + /* + * figure out if it's got a host + * user@server is *always* interpreted as a hostname, and url + * resolution will treat //foo/bar as host=foo,path=bar because that's + * how the browser resolves relative URLs. + */ + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@/]+@[^@/]+/)) { + var slashes = rest.substr(0, 2) === "//"; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) { + /* + * there's a hostname. + * the first instance of /, ?, ;, or # ends the host. + * + * If there is an @ in the hostname, then non-host chars *are* allowed + * to the left of the last @ sign, unless some host-ending character + * comes *before* the @-sign. + * URLs are obnoxious. + * + * ex: + * http://a@b@c/ => user:a@b host:c + * http://a@b?@c => user:a host:c path:/?@c + */ + + /* + * v0.12 TODO(isaacs): This is not quite how Chrome does things. + * Review our test case against browsers more comprehensively. + */ + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + + /* + * at this point, either we have an explicit point where the + * auth portion cannot go past, or the last @ char is the decider. + */ + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf("@"); + } else { + /* + * atSign must be in auth portion. + * http://a@b/c@d => host:b auth:a path:/c@d + */ + atSign = rest.lastIndexOf("@", hostEnd); + } + + /* + * Now we have a portion which is definitely the auth. + * Pull that off. + */ + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); } - var P, p; - u === -1 ? (p = h.lastIndexOf("@")) : (p = h.lastIndexOf("@", u)), - p !== -1 && ((P = h.slice(0, p)), (h = h.slice(p + 1)), (this.auth = decodeURIComponent(P))), - (u = -1); - for (var n = 0; n < G.length; n++) { - var b = h.indexOf(G[n]); - b !== -1 && (u === -1 || b < u) && (u = b); + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; } - u === -1 && (u = h.length), - (this.host = h.slice(0, u)), - (h = h.slice(u)), - this.parseHost(), - (this.hostname = this.hostname || ""); - var C = this.hostname[0] === "[" && this.hostname[this.hostname.length - 1] === "]"; - if (!C) - for (var e = this.hostname.split(/\./), n = 0, i = e.length; n < i; n++) { - var d = e[n]; - if (!!d && !d.match(K)) { - for (var y = "", x = 0, _ = d.length; x < _; x++) d.charCodeAt(x) > 127 ? (y += "x") : (y += d[x]); - if (!y.match(K)) { - var q = e.slice(0, n), - O = e.slice(n + 1), - U = d.match(at); - U && (q.push(U[1]), O.unshift(U[2])), - O.length && (h = "/" + O.join(".") + h), - (this.hostname = q.join(".")); + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(); + + /* + * we've indicated that there is a hostname, + * so even if it's empty, it has to be present. + */ + this.hostname = this.hostname || ""; + + /* + * if hostname begins with [ and ends with ] + * assume that it's an IPv6 address. + */ + var ipv6Hostname = this.hostname[0] === "[" && this.hostname[this.hostname.length - 1] === "]"; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { + continue; + } + if (!part.match(hostnamePartPattern)) { + var newpart = ""; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + /* + * we replace non-ASCII char with a temporary placeholder + * we need this to make sure size of hostname is not + * broken by replacing non-ASCII by nothing + */ + newpart += "x"; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = "/" + notHost.join(".") + rest; + } + this.hostname = validParts.join("."); break; } } } - this.hostname.length > ot ? (this.hostname = "") : (this.hostname = this.hostname.toLowerCase()), - C || (this.hostname = new F(`https://${this.hostname}`).hostname); - var w = this.port ? ":" + this.port : "", - H = this.hostname || ""; - (this.host = H + w), - (this.href += this.host), - C && ((this.hostname = this.hostname.substr(1, this.hostname.length - 2)), h[0] !== "/" && (h = "/" + h)); - } - if (!nt[v]) - for (var n = 0, i = B.length; n < i; n++) { - var L = B[n]; - if (h.indexOf(L) !== -1) { - var z = encodeURIComponent(L); - z === L && (z = escape(L)), (h = h.split(L).join(z)); + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ""; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + /* + * IDNA Support: Returns a punycoded representation of "domain". + * It only converts parts of the domain name that + * have non-ASCII characters, i.e. it doesn't matter if + * you call it with a domain that already is ASCII-only. + */ + this.hostname = new URL("http://" + this.hostname).hostname; + } + + var p = this.port ? ":" + this.port : ""; + var h = this.hostname || ""; + this.host = h + p; + this.href += this.host; + + /* + * strip [ and ] from the hostname + * the host field still retains them, though + */ + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== "/") { + rest = "/" + rest; + } + } + } + + /* + * now rest is set to the post-host stuff. + * chop off any delim chars. + */ + if (!unsafeProtocol[lowerProto]) { + /* + * First, make 100% sure that any "autoEscape" chars get + * escaped, even if encodeURIComponent doesn't think they + * need to be. + */ + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + if (rest.indexOf(ae) === -1) { + continue; + } + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); } + rest = rest.split(ae).join(esc); + } + } + + // chop off from the tail first. + var hash = rest.indexOf("#"); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf("?"); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = new URLSearchParams(this.query); } - var $ = h.indexOf("#"); - $ !== -1 && ((this.hash = h.substr($)), (h = h.slice(0, $))); - var T = h.indexOf("?"); - if ( - (T !== -1 - ? ((this.search = h.substr(T)), - (this.query = h.substr(T + 1)), - r && (this.query = Z.parse(this.query)), - (h = h.slice(0, T))) - : r && ((this.search = ""), (this.query = {})), - h && (this.pathname = h), - R[v] && this.hostname && !this.pathname && (this.pathname = "/"), - this.pathname || this.search) - ) { - var w = this.pathname || "", - Q = this.search || ""; - this.path = w + Q; - } - return (this.href = this.format()), this; + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ""; + this.query = {}; + } + if (rest) { + this.pathname = rest; + } + if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { + this.pathname = "/"; + } + + // to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ""; + var s = this.search || ""; + this.path = p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; }; -function V(s) { - return it(s) && (s = A(s)), s instanceof m ? s.format() : m.prototype.format.call(s); + +// format a parsed object into a url string +function urlFormat(obj) { + /* + * ensure it's an object, and not a string url. + * If it's an obj, this is a no-op. + * this way, you can call url_format() on strings + * to clean up potentially wonky urls. + */ + if (typeof obj === "string") { + obj = urlParse(obj); + } + if (!(obj instanceof Url)) { + return Url.prototype.format.call(obj); + } + return obj.format(); } -m.prototype.format = function () { - var s = this.auth || ""; - s && ((s = encodeURIComponent(s)), (s = s.replace(/%3A/i, ":")), (s += "@")); - var r = this.protocol || "", - t = this.pathname || "", - o = this.hash || "", - a = !1, - l = ""; - this.host - ? (a = s + this.host) - : this.hostname && - ((a = s + (this.hostname.indexOf(":") === -1 ? this.hostname : "[" + this.hostname + "]")), - this.port && (a += ":" + this.port)), - this.query && D(this.query) && Object.keys(this.query).length && (l = Z.stringify(this.query)); - var f = this.search || (l && "?" + l) || ""; - return ( - r && r.substr(-1) !== ":" && (r += ":"), - this.slashes || ((!r || R[r]) && a !== !1) - ? ((a = "//" + (a || "")), t && t.charAt(0) !== "/" && (t = "/" + t)) - : a || (a = ""), - o && o.charAt(0) !== "#" && (o = "#" + o), - f && f.charAt(0) !== "?" && (f = "?" + f), - (t = t.replace(/[?#]/g, function (h) { - return encodeURIComponent(h); - })), - (f = f.replace("#", "%23")), - r + a + t + f + o - ); + +Url.prototype.format = function () { + var auth = this.auth || ""; + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ":"); + auth += "@"; + } + + var protocol = this.protocol || "", + pathname = this.pathname || "", + hash = this.hash || "", + host = false, + query = ""; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(":") === -1 ? this.hostname : "[" + this.hostname + "]"); + if (this.port) { + host += ":" + this.port; + } + } + + if (this.query && typeof this.query === "object" && Object.keys(this.query).length) { + query = new URLSearchParams(this.query).toString(); + } + + var search = this.search || (query && "?" + query) || ""; + + if (protocol && protocol.substr(-1) !== ":") { + protocol += ":"; + } + + /* + * only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + * unless they had them to begin with. + */ + if (this.slashes || ((!protocol || slashedProtocol[protocol]) && host !== false)) { + host = "//" + (host || ""); + if (pathname && pathname.charAt(0) !== "/") { + pathname = "/" + pathname; + } + } else if (!host) { + host = ""; + } + + if (hash && hash.charAt(0) !== "#") { + hash = "#" + hash; + } + if (search && search.charAt(0) !== "?") { + search = "?" + search; + } + + pathname = pathname.replace(/[?#]/g, function (match) { + return encodeURIComponent(match); + }); + search = search.replace("#", "%23"); + + return protocol + host + pathname + search + hash; }; -function W(s, r) { - return A(s, !1, !0).resolve(r); + +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); } -m.prototype.resolve = function (s) { - return this.resolveObject(A(s, !1, !0)).format(); + +Url.prototype.resolve = function (relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); }; -function X(s, r) { - return s ? A(s, !1, !0).resolveObject(r) : r; + +function urlResolveObject(source, relative) { + if (!source) { + return relative; + } + return urlParse(source, false, true).resolveObject(relative); } -(m.prototype.resolveObject = function (s) { - if (it(s)) { - var r = new m(); - r.parse(s, !1, !0), (s = r); - } - for (var t = new m(), o = Object.keys(this), a = 0; a < o.length; a++) { - var l = o[a]; - t[l] = this[l]; - } - if (((t.hash = s.hash), s.href === "")) return (t.href = t.format()), t; - if (s.slashes && !s.protocol) { - for (var f = Object.keys(s), h = 0; h < f.length; h++) { - var g = f[h]; - g !== "protocol" && (t[g] = s[g]); + +Url.prototype.resolveObject = function (relative) { + if (typeof relative === "string") { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } + + var result = new Url(); + var tkeys = Object.keys(this); + for (var tk = 0; tk < tkeys.length; tk++) { + var tkey = tkeys[tk]; + result[tkey] = this[tkey]; + } + + /* + * hash is always overridden, no matter what. + * even href="" will remove it. + */ + result.hash = relative.hash; + + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === "") { + result.href = result.format(); + return result; + } + + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + var rkeys = Object.keys(relative); + for (var rk = 0; rk < rkeys.length; rk++) { + var rkey = rkeys[rk]; + if (rkey !== "protocol") { + result[rkey] = relative[rkey]; + } } - return R[t.protocol] && t.hostname && !t.pathname && (t.path = t.pathname = "/"), (t.href = t.format()), t; + + // urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && result.hostname && !result.pathname) { + result.pathname = "/"; + result.path = result.pathname; + } + + result.href = result.format(); + return result; } - if (s.protocol && s.protocol !== t.protocol) { - if (!R[s.protocol]) { - for (var c = Object.keys(s), v = 0; v < c.length; v++) { - var j = c[v]; - t[j] = s[j]; + + if (relative.protocol && relative.protocol !== result.protocol) { + /* + * if it's a known url protocol, then changing + * the protocol does weird things + * first, if it's not file:, then we MUST have a host, + * and if there was a path + * to begin with, then we MUST have a path. + * if it is file:, then the host is dropped, + * because that's known to be hostless. + * anything else is assumed to be absolute. + */ + if (!slashedProtocol[relative.protocol]) { + var keys = Object.keys(relative); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + result[k] = relative[k]; } - return (t.href = t.format()), t; + result.href = result.format(); + return result; } - if (((t.protocol = s.protocol), !s.host && !N[s.protocol])) { - for (var i = (s.pathname || "").split("/"); i.length && !(s.host = i.shift()); ); - s.host || (s.host = ""), - s.hostname || (s.hostname = ""), - i[0] !== "" && i.unshift(""), - i.length < 2 && i.unshift(""), - (t.pathname = i.join("/")); - } else t.pathname = s.pathname; - if ( - ((t.search = s.search), - (t.query = s.query), - (t.host = s.host || ""), - (t.auth = s.auth), - (t.hostname = s.hostname || s.host), - (t.port = s.port), - t.pathname || t.search) - ) { - var u = t.pathname || "", - n = t.search || ""; - t.path = u + n; + + result.protocol = relative.protocol; + if (!relative.host && !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || "").split("/"); + while (relPath.length && !(relative.host = relPath.shift())) {} + if (!relative.host) { + relative.host = ""; + } + if (!relative.hostname) { + relative.hostname = ""; + } + if (relPath[0] !== "") { + relPath.unshift(""); + } + if (relPath.length < 2) { + relPath.unshift(""); + } + result.pathname = relPath.join("/"); + } else { + result.pathname = relative.pathname; } - return (t.slashes = t.slashes || s.slashes), (t.href = t.format()), t; - } - var b = t.pathname && t.pathname.charAt(0) === "/", - P = s.host || (s.pathname && s.pathname.charAt(0) === "/"), - p = P || b || (t.host && s.pathname), - C = p, - e = (t.pathname && t.pathname.split("/")) || [], - i = (s.pathname && s.pathname.split("/")) || [], - d = t.protocol && !R[t.protocol]; - if ( - (d && - ((t.hostname = ""), - (t.port = null), - t.host && (e[0] === "" ? (e[0] = t.host) : e.unshift(t.host)), - (t.host = ""), - s.protocol && - ((s.hostname = null), - (s.port = null), - s.host && (i[0] === "" ? (i[0] = s.host) : i.unshift(s.host)), - (s.host = null)), - (p = p && (i[0] === "" || e[0] === ""))), - P) - ) - (t.host = s.host || s.host === "" ? s.host : t.host), - (t.hostname = s.hostname || s.hostname === "" ? s.hostname : t.hostname), - (t.search = s.search), - (t.query = s.query), - (e = i); - else if (i.length) e || (e = []), e.pop(), (e = e.concat(i)), (t.search = s.search), (t.query = s.query); - else if (!E(s.search)) { - if (d) { - t.hostname = t.host = e.shift(); - var y = t.host && t.host.indexOf("@") > 0 ? t.host.split("@") : !1; - y && ((t.auth = y.shift()), (t.host = t.hostname = y.shift())); + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ""; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ""; + var s = result.search || ""; + result.path = p + s; } - return ( - (t.search = s.search), - (t.query = s.query), - (!I(t.pathname) || !I(t.search)) && (t.path = (t.pathname ? t.pathname : "") + (t.search ? t.search : "")), - (t.href = t.format()), - t - ); - } - if (!e.length) - return (t.pathname = null), t.search ? (t.path = "/" + t.search) : (t.path = null), (t.href = t.format()), t; - for ( - var x = e.slice(-1)[0], - _ = ((t.host || s.host || e.length > 1) && (x === "." || x === "..")) || x === "", - q = 0, - O = e.length; - O >= 0; - O-- - ) - (x = e[O]), x === "." ? e.splice(O, 1) : x === ".." ? (e.splice(O, 1), q++) : q && (e.splice(O, 1), q--); - if (!p && !C) for (; q--; q) e.unshift(".."); - p && e[0] !== "" && (!e[0] || e[0].charAt(0) !== "/") && e.unshift(""), - _ && e.join("/").substr(-1) !== "/" && e.push(""); - var U = e[0] === "" || (e[0] && e[0].charAt(0) === "/"); - if (d) { - t.hostname = t.host = U ? "" : e.length ? e.shift() : ""; - var y = t.host && t.host.indexOf("@") > 0 ? t.host.split("@") : !1; - y && ((t.auth = y.shift()), (t.host = t.hostname = y.shift())); - } - return ( - (p = p || (t.host && e.length)), - p && !U && e.unshift(""), - e.length ? (t.pathname = e.join("/")) : ((t.pathname = null), (t.path = null)), - (!I(t.pathname) || !I(t.search)) && (t.path = (t.pathname ? t.pathname : "") + (t.search ? t.search : "")), - (t.auth = s.auth || t.auth), - (t.slashes = t.slashes || s.slashes), - (t.href = t.format()), - t - ); -}), - (m.prototype.parseHost = function () { - var s = this.host, - r = st.exec(s); - r && ((r = r[0]), r !== ":" && (this.port = r.substr(1)), (s = s.substr(0, s.length - r.length))), - s && (this.hostname = s); - }); -var Y, k; -S && ((Y = S("pathToFileURL")), (k = S("fileURLToPath"))); -var ut = { - parse: A, - resolve: W, - resolveObject: X, - format: V, - Url: m, - pathToFileURL: Y, - fileURLToPath: k, - URL: F, - URLSearchParams: M, + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + var isSourceAbs = result.pathname && result.pathname.charAt(0) === "/", + isRelAbs = relative.host || (relative.pathname && relative.pathname.charAt(0) === "/"), + mustEndAbs = isRelAbs || isSourceAbs || (result.host && relative.pathname), + removeAllDots = mustEndAbs, + srcPath = (result.pathname && result.pathname.split("/")) || [], + relPath = (relative.pathname && relative.pathname.split("/")) || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; + + /* + * if the url is a non-slashed url, then relative + * links like ../.. should be able + * to crawl up to the hostname, as well. This is strange. + * result.protocol has already been set by now. + * Later on, put the first path part into the host field. + */ + if (psychotic) { + result.hostname = ""; + result.port = null; + if (result.host) { + if (srcPath[0] === "") { + srcPath[0] = result.host; + } else { + srcPath.unshift(result.host); + } + } + result.host = ""; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === "") { + relPath[0] = relative.host; + } else { + relPath.unshift(relative.host); + } + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === "" || srcPath[0] === ""); + } + + if (isRelAbs) { + // it's absolute. + result.host = relative.host || relative.host === "" ? relative.host : result.host; + result.hostname = relative.hostname || relative.hostname === "" ? relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + /* + * it's relative + * throw away the existing file, and take the new path instead. + */ + if (!srcPath) { + srcPath = []; + } + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (relative.search != null) { + /* + * just pull out the search. + * like href='?foo'. + * Put this after the other two cases because it simplifies the booleans + */ + if (psychotic) { + result.host = srcPath.shift(); + result.hostname = result.host; + /* + * occationaly the auth can get stuck only in host + * this especially happens in cases like + * url.resolveObject('mailto:local1@domain1', 'local2@domain2') + */ + var authInHost = result.host && result.host.indexOf("@") > 0 ? result.host.split("@") : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.hostname = authInHost.shift(); + result.host = result.hostname; + } + } + result.search = relative.search; + result.query = relative.query; + // to support http.request + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : "") + (result.search ? result.search : ""); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + /* + * no path at all. easy. + * we've already handled the other stuff above. + */ + result.pathname = null; + // to support http.request + if (result.search) { + result.path = "/" + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + /* + * if a url ENDs in . or .., then it must get a trailing slash. + * however, if it ends in anything else non-slashy, + * then it must NOT get a trailing slash. + */ + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = + ((result.host || relative.host || srcPath.length > 1) && (last === "." || last === "..")) || last === ""; + + /* + * strip single dots, resolve double dots to parent dir + * if the path tries to go above the root, `up` ends up > 0 + */ + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last === ".") { + srcPath.splice(i, 1); + } else if (last === "..") { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift(".."); + } + } + + if (mustEndAbs && srcPath[0] !== "" && (!srcPath[0] || srcPath[0].charAt(0) !== "/")) { + srcPath.unshift(""); + } + + if (hasTrailingSlash && srcPath.join("/").substr(-1) !== "/") { + srcPath.push(""); + } + + var isAbsolute = srcPath[0] === "" || (srcPath[0] && srcPath[0].charAt(0) === "/"); + + // put the host back + if (psychotic) { + result.hostname = isAbsolute ? "" : srcPath.length ? srcPath.shift() : ""; + result.host = result.hostname; + /* + * occationaly the auth can get stuck only in host + * this especially happens in cases like + * url.resolveObject('mailto:local1@domain1', 'local2@domain2') + */ + var authInHost = result.host && result.host.indexOf("@") > 0 ? result.host.split("@") : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.hostname = authInHost.shift(); + result.host = result.hostname; + } + } + + mustEndAbs = mustEndAbs || (result.host && srcPath.length); + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(""); + } + + if (srcPath.length > 0) { + result.pathname = srcPath.join("/"); + } else { + result.pathname = null; + result.path = null; + } + + // to support request.http + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : "") + (result.search ? result.search : ""); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function () { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ":") { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { + this.hostname = host; + } +}; +function urlToHttpOptions(url) { + const options = { + protocol: url.protocol, + hostname: + typeof url.hostname === "string" && url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname, + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname || ""}${url.search || ""}`, + href: url.href, + }; + if (url.port !== "") { + options.port = Number(url.port); + } + if (url.username || url.password) { + options.auth = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`; + } + return options; +} + +const lazy = globalThis[Symbol.for("Bun.lazy")]; +const pathToFileURL = lazy("pathToFileURL"); +const fileURLToPath = lazy("fileURLToPath"); +const defaultObject = { + parse: urlParse, + resolve: urlResolve, + resolveObject: urlResolveObject, + format: urlFormat, + Url, + URLSearchParams, + URL, + pathToFileURL, + fileURLToPath, + urlToHttpOptions, + [Symbol.for("CommonJS")]: 0, }; -("use strict"); + export { - F as URL, - M as URLSearchParams, - m as Url, - ut as default, - k as fileURLToPath, - V as format, - A as parse, - Y as pathToFileURL, - W as resolve, - X as resolveObject, + defaultObject as default, + urlParse as parse, + urlResolve as resolve, + urlResolveObject as resolveObject, + urlFormat as format, + Url, + URLSearchParams, + URL, + pathToFileURL, + fileURLToPath, + urlToHttpOptions, }; |