aboutsummaryrefslogtreecommitdiff
path: root/src/js/node/url.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/node/url.js')
-rw-r--r--src/js/node/url.js1194
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,
};