229 lines
6.4 KiB
JavaScript
Vendored
229 lines
6.4 KiB
JavaScript
Vendored
'use strict';
|
|
|
|
var required = require('requires-port')
|
|
, lolcation = require('./lolcation')
|
|
, qs = require('querystringify')
|
|
, relativere = /^\/(?!\/)/;
|
|
|
|
/**
|
|
* These are the parse instructions for the URL parsers, it informs the parser
|
|
* about:
|
|
*
|
|
* 0. The char it Needs to parse, if it's a string it should be done using
|
|
* indexOf, RegExp using exec and NaN means set as current value.
|
|
* 1. The property we should set when parsing this value.
|
|
* 2. Indication if it's backwards or forward parsing, when set as number it's
|
|
* the value of extra chars that should be split off.
|
|
* 3. Inherit from location if non existing in the parser.
|
|
* 4. `toLowerCase` the resulting value.
|
|
*/
|
|
var instructions = [
|
|
['#', 'hash'], // Extract from the back.
|
|
['?', 'query'], // Extract from the back.
|
|
['//', 'protocol', 2, 1, 1], // Extract from the front.
|
|
['/', 'pathname'], // Extract from the back.
|
|
['@', 'auth', 1], // Extract from the front.
|
|
[NaN, 'host', undefined, 1, 1], // Set left over value.
|
|
[/\:(\d+)$/, 'port'], // RegExp the back.
|
|
[NaN, 'hostname', undefined, 1, 1] // Set left over.
|
|
];
|
|
|
|
/**
|
|
* The actual URL instance. Instead of returning an object we've opted-in to
|
|
* create an actual constructor as it's much more memory efficient and
|
|
* faster and it pleases my CDO.
|
|
*
|
|
* @constructor
|
|
* @param {String} address URL we want to parse.
|
|
* @param {Boolean|function} parser Parser for the query string.
|
|
* @param {Object} location Location defaults for relative paths.
|
|
* @api public
|
|
*/
|
|
function URL(address, location, parser) {
|
|
if (!(this instanceof URL)) {
|
|
return new URL(address, location, parser);
|
|
}
|
|
|
|
var relative = relativere.test(address)
|
|
, parse, instruction, index, key
|
|
, type = typeof location
|
|
, url = this
|
|
, i = 0;
|
|
|
|
//
|
|
// The following if statements allows this module two have compatibility with
|
|
// 2 different API:
|
|
//
|
|
// 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments
|
|
// where the boolean indicates that the query string should also be parsed.
|
|
//
|
|
// 2. The `URL` interface of the browser which accepts a URL, object as
|
|
// arguments. The supplied object will be used as default values / fall-back
|
|
// for relative paths.
|
|
//
|
|
if ('object' !== type && 'string' !== type) {
|
|
parser = location;
|
|
location = null;
|
|
}
|
|
|
|
if (parser && 'function' !== typeof parser) {
|
|
parser = qs.parse;
|
|
}
|
|
|
|
location = lolcation(location);
|
|
|
|
for (; i < instructions.length; i++) {
|
|
instruction = instructions[i];
|
|
parse = instruction[0];
|
|
key = instruction[1];
|
|
|
|
if (parse !== parse) {
|
|
url[key] = address;
|
|
} else if ('string' === typeof parse) {
|
|
if (~(index = address.indexOf(parse))) {
|
|
if ('number' === typeof instruction[2]) {
|
|
url[key] = address.slice(0, index);
|
|
address = address.slice(index + instruction[2]);
|
|
} else {
|
|
url[key] = address.slice(index);
|
|
address = address.slice(0, index);
|
|
}
|
|
}
|
|
} else if (index = parse.exec(address)) {
|
|
url[key] = index[1];
|
|
address = address.slice(0, address.length - index[0].length);
|
|
}
|
|
|
|
url[key] = url[key] || (instruction[3] || ('port' === key && relative) ? location[key] || '' : '');
|
|
|
|
//
|
|
// Hostname, host and protocol should be lowercased so they can be used to
|
|
// create a proper `origin`.
|
|
//
|
|
if (instruction[4]) {
|
|
url[key] = url[key].toLowerCase();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Also parse the supplied query string in to an object. If we're supplied
|
|
// with a custom parser as function use that instead of the default build-in
|
|
// parser.
|
|
//
|
|
if (parser) url.query = parser(url.query);
|
|
|
|
//
|
|
// We should not add port numbers if they are already the default port number
|
|
// for a given protocol. As the host also contains the port number we're going
|
|
// override it with the hostname which contains no port number.
|
|
//
|
|
if (!required(url.port, url.protocol)) {
|
|
url.host = url.hostname;
|
|
url.port = '';
|
|
}
|
|
|
|
//
|
|
// Parse down the `auth` for the username and password.
|
|
//
|
|
url.username = url.password = '';
|
|
if (url.auth) {
|
|
instruction = url.auth.split(':');
|
|
url.username = instruction[0] || '';
|
|
url.password = instruction[1] || '';
|
|
}
|
|
|
|
//
|
|
// The href is just the compiled result.
|
|
//
|
|
url.href = url.toString();
|
|
}
|
|
|
|
/**
|
|
* This is convenience method for changing properties in the URL instance to
|
|
* insure that they all propagate correctly.
|
|
*
|
|
* @param {String} prop Property we need to adjust.
|
|
* @param {Mixed} value The newly assigned value.
|
|
* @returns {URL}
|
|
* @api public
|
|
*/
|
|
URL.prototype.set = function set(part, value, fn) {
|
|
var url = this;
|
|
|
|
if ('query' === part) {
|
|
if ('string' === typeof value && value.length) {
|
|
value = (fn || qs.parse)(value);
|
|
}
|
|
|
|
url[part] = value;
|
|
} else if ('port' === part) {
|
|
url[part] = value;
|
|
|
|
if (!required(value, url.protocol)) {
|
|
url.host = url.hostname;
|
|
url[part] = '';
|
|
} else if (value) {
|
|
url.host = url.hostname +':'+ value;
|
|
}
|
|
} else if ('hostname' === part) {
|
|
url[part] = value;
|
|
|
|
if (url.port) value += ':'+ url.port;
|
|
url.host = value;
|
|
} else if ('host' === part) {
|
|
url[part] = value;
|
|
|
|
if (/\:\d+/.test(value)) {
|
|
value = value.split(':');
|
|
url.hostname = value[0];
|
|
url.port = value[1];
|
|
}
|
|
} else {
|
|
url[part] = value;
|
|
}
|
|
|
|
url.href = url.toString();
|
|
return url;
|
|
};
|
|
|
|
/**
|
|
* Transform the properties back in to a valid and full URL string.
|
|
*
|
|
* @param {Function} stringify Optional query stringify function.
|
|
* @returns {String}
|
|
* @api public
|
|
*/
|
|
URL.prototype.toString = function toString(stringify) {
|
|
if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;
|
|
|
|
var query
|
|
, url = this
|
|
, result = url.protocol +'//';
|
|
|
|
if (url.username) {
|
|
result += url.username;
|
|
if (url.password) result += ':'+ url.password;
|
|
result += '@';
|
|
}
|
|
|
|
result += url.hostname;
|
|
if (url.port) result += ':'+ url.port;
|
|
|
|
result += url.pathname;
|
|
|
|
query = 'object' === typeof url.query ? stringify(url.query) : url.query;
|
|
if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;
|
|
|
|
if (url.hash) result += url.hash;
|
|
|
|
return result;
|
|
};
|
|
|
|
//
|
|
// Expose the URL parser and some additional properties that might be useful for
|
|
// others.
|
|
//
|
|
URL.qs = qs;
|
|
URL.location = lolcation;
|
|
module.exports = URL;
|