195 lines
5.5 KiB
JavaScript
Executable File
195 lines
5.5 KiB
JavaScript
Executable File
"use strict";
|
|
module.exports = URL;
|
|
|
|
function URL(url) {
|
|
if (!url) return Object.create(URL.prototype);
|
|
// Can't use String.trim() since it defines whitespace differently than HTML
|
|
this.url = url.replace(/^[ \t\n\r\f]+|[ \t\n\r\f]+$/g, "");
|
|
|
|
// See http://tools.ietf.org/html/rfc3986#appendix-B
|
|
// and https://url.spec.whatwg.org/#parsing
|
|
var match = URL.pattern.exec(this.url);
|
|
if (match) {
|
|
if (match[2]) this.scheme = match[2];
|
|
if (match[4]) {
|
|
// parse username/password
|
|
var userinfo = match[4].match(URL.userinfoPattern);
|
|
if (userinfo) {
|
|
this.username = userinfo[1];
|
|
this.password = userinfo[3];
|
|
match[4] = match[4].substring(userinfo[0].length);
|
|
}
|
|
if (match[4].match(URL.portPattern)) {
|
|
var pos = match[4].lastIndexOf(':');
|
|
this.host = match[4].substring(0, pos);
|
|
this.port = match[4].substring(pos+1);
|
|
}
|
|
else {
|
|
this.host = match[4];
|
|
}
|
|
}
|
|
if (match[5]) this.path = match[5];
|
|
if (match[6]) this.query = match[7];
|
|
if (match[8]) this.fragment = match[9];
|
|
}
|
|
}
|
|
|
|
URL.pattern = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/;
|
|
URL.userinfoPattern = /^([^@:]*)(:([^@]*))?@/;
|
|
URL.portPattern = /:\d+$/;
|
|
URL.authorityPattern = /^[^:\/?#]+:\/\//;
|
|
URL.hierarchyPattern = /^[^:\/?#]+:\//;
|
|
|
|
// Return a percentEncoded version of s.
|
|
// S should be a single-character string
|
|
// XXX: needs to do utf-8 encoding?
|
|
URL.percentEncode = function percentEncode(s) {
|
|
var c = s.charCodeAt(0);
|
|
if (c < 256) return "%" + c.toString(16);
|
|
else throw Error("can't percent-encode codepoints > 255 yet");
|
|
};
|
|
|
|
URL.prototype = {
|
|
constructor: URL,
|
|
|
|
// XXX: not sure if this is the precise definition of absolute
|
|
isAbsolute: function() { return !!this.scheme; },
|
|
isAuthorityBased: function() {
|
|
return URL.authorityPattern.test(this.url);
|
|
},
|
|
isHierarchical: function() {
|
|
return URL.hierarchyPattern.test(this.url);
|
|
},
|
|
|
|
toString: function() {
|
|
var s = "";
|
|
if (this.scheme !== undefined) s += this.scheme + ":";
|
|
if (this.isAbsolute()) {
|
|
s += '//';
|
|
if (this.username || this.password) {
|
|
s += this.username || '';
|
|
if (this.password) {
|
|
s += ':' + this.password;
|
|
}
|
|
s += '@';
|
|
}
|
|
if (this.host) {
|
|
s += this.host;
|
|
}
|
|
}
|
|
if (this.port !== undefined) s += ":" + this.port;
|
|
if (this.path !== undefined) s += this.path;
|
|
if (this.query !== undefined) s += "?" + this.query;
|
|
if (this.fragment !== undefined) s += "#" + this.fragment;
|
|
return s;
|
|
},
|
|
|
|
// See: http://tools.ietf.org/html/rfc3986#section-5.2
|
|
// and https://url.spec.whatwg.org/#constructors
|
|
resolve: function(relative) {
|
|
var base = this; // The base url we're resolving against
|
|
var r = new URL(relative); // The relative reference url to resolve
|
|
var t = new URL(); // The absolute target url we will return
|
|
|
|
if (r.scheme !== undefined) {
|
|
t.scheme = r.scheme;
|
|
t.username = r.username;
|
|
t.password = r.password;
|
|
t.host = r.host;
|
|
t.port = r.port;
|
|
t.path = remove_dot_segments(r.path);
|
|
t.query = r.query;
|
|
}
|
|
else {
|
|
t.scheme = base.scheme;
|
|
if (r.host !== undefined) {
|
|
t.username = r.username;
|
|
t.password = r.password;
|
|
t.host = r.host;
|
|
t.port = r.port;
|
|
t.path = remove_dot_segments(r.path);
|
|
t.query = r.query;
|
|
}
|
|
else {
|
|
t.username = base.username;
|
|
t.password = base.password;
|
|
t.host = base.host;
|
|
t.port = base.port;
|
|
if (!r.path) { // undefined or empty
|
|
t.path = base.path;
|
|
if (r.query !== undefined)
|
|
t.query = r.query;
|
|
else
|
|
t.query = base.query;
|
|
}
|
|
else {
|
|
if (r.path.charAt(0) === "/") {
|
|
t.path = remove_dot_segments(r.path);
|
|
}
|
|
else {
|
|
t.path = merge(base.path, r.path);
|
|
t.path = remove_dot_segments(t.path);
|
|
}
|
|
t.query = r.query;
|
|
}
|
|
}
|
|
}
|
|
t.fragment = r.fragment;
|
|
|
|
return t.toString();
|
|
|
|
|
|
function merge(basepath, refpath) {
|
|
if (base.host !== undefined && !base.path)
|
|
return "/" + refpath;
|
|
|
|
var lastslash = basepath.lastIndexOf("/");
|
|
if (lastslash === -1)
|
|
return refpath;
|
|
else
|
|
return basepath.substring(0, lastslash+1) + refpath;
|
|
}
|
|
|
|
function remove_dot_segments(path) {
|
|
if (!path) return path; // For "" or undefined
|
|
|
|
var output = "";
|
|
while(path.length > 0) {
|
|
if (path === "." || path === "..") {
|
|
path = "";
|
|
break;
|
|
}
|
|
|
|
var twochars = path.substring(0,2);
|
|
var threechars = path.substring(0,3);
|
|
var fourchars = path.substring(0,4);
|
|
if (threechars === "../") {
|
|
path = path.substring(3);
|
|
}
|
|
else if (twochars === "./") {
|
|
path = path.substring(2);
|
|
}
|
|
else if (threechars === "/./") {
|
|
path = "/" + path.substring(3);
|
|
}
|
|
else if (twochars === "/." && path.length === 2) {
|
|
path = "/";
|
|
}
|
|
else if (fourchars === "/../" ||
|
|
(threechars === "/.." && path.length === 3)) {
|
|
path = "/" + path.substring(4);
|
|
|
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
}
|
|
else {
|
|
var segment = path.match(/(\/?([^\/]*))/)[0];
|
|
output += segment;
|
|
path = path.substring(segment.length);
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
},
|
|
};
|