text: // "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;
      }
    },
  };
  