react / react-0.13.3 / examples / basic-commonjs / node_modules / browserify / node_modules / url / url.js
80708 views// Copyright Joyent, Inc. and other Node contributors.1//2// Permission is hereby granted, free of charge, to any person obtaining a3// copy of this software and associated documentation files (the4// "Software"), to deal in the Software without restriction, including5// without limitation the rights to use, copy, modify, merge, publish,6// distribute, sublicense, and/or sell copies of the Software, and to permit7// persons to whom the Software is furnished to do so, subject to the8// following conditions:9//10// The above copyright notice and this permission notice shall be included11// in all copies or substantial portions of the Software.12//13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF15// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN16// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,17// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR18// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE19// USE OR OTHER DEALINGS IN THE SOFTWARE.2021var punycode = require('punycode');2223exports.parse = urlParse;24exports.resolve = urlResolve;25exports.resolveObject = urlResolveObject;26exports.format = urlFormat;2728exports.Url = Url;2930function Url() {31this.protocol = null;32this.slashes = null;33this.auth = null;34this.host = null;35this.port = null;36this.hostname = null;37this.hash = null;38this.search = null;39this.query = null;40this.pathname = null;41this.path = null;42this.href = null;43}4445// Reference: RFC 3986, RFC 1808, RFC 23964647// define these here so at least they only have to be48// compiled once on the first module load.49var protocolPattern = /^([a-z0-9.+-]+:)/i,50portPattern = /:[0-9]*$/,5152// RFC 2396: characters reserved for delimiting URLs.53// We actually just auto-escape these.54delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],5556// RFC 2396: characters not allowed for various reasons.57unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),5859// Allowed by RFCs, but cause of XSS attacks. Always escape these.60autoEscape = ['\''].concat(unwise),61// Characters that are never ever allowed in a hostname.62// Note that any invalid chars are also handled, but these63// are the ones that are *expected* to be seen, so we fast-path64// them.65nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),66hostEndingChars = ['/', '?', '#'],67hostnameMaxLen = 255,68hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,69hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,70// protocols that can allow "unsafe" and "unwise" chars.71unsafeProtocol = {72'javascript': true,73'javascript:': true74},75// protocols that never have a hostname.76hostlessProtocol = {77'javascript': true,78'javascript:': true79},80// protocols that always contain a // bit.81slashedProtocol = {82'http': true,83'https': true,84'ftp': true,85'gopher': true,86'file': true,87'http:': true,88'https:': true,89'ftp:': true,90'gopher:': true,91'file:': true92},93querystring = require('querystring');9495function urlParse(url, parseQueryString, slashesDenoteHost) {96if (url && isObject(url) && url instanceof Url) return url;9798var u = new Url;99u.parse(url, parseQueryString, slashesDenoteHost);100return u;101}102103Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {104if (!isString(url)) {105throw new TypeError("Parameter 'url' must be a string, not " + typeof url);106}107108var rest = url;109110// trim before proceeding.111// This is to support parse stuff like " http://foo.com \n"112rest = rest.trim();113114var proto = protocolPattern.exec(rest);115if (proto) {116proto = proto[0];117var lowerProto = proto.toLowerCase();118this.protocol = lowerProto;119rest = rest.substr(proto.length);120}121122// figure out if it's got a host123// user@server is *always* interpreted as a hostname, and url124// resolution will treat //foo/bar as host=foo,path=bar because that's125// how the browser resolves relative URLs.126if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {127var slashes = rest.substr(0, 2) === '//';128if (slashes && !(proto && hostlessProtocol[proto])) {129rest = rest.substr(2);130this.slashes = true;131}132}133134if (!hostlessProtocol[proto] &&135(slashes || (proto && !slashedProtocol[proto]))) {136137// there's a hostname.138// the first instance of /, ?, ;, or # ends the host.139//140// If there is an @ in the hostname, then non-host chars *are* allowed141// to the left of the last @ sign, unless some host-ending character142// comes *before* the @-sign.143// URLs are obnoxious.144//145// ex:146// http://a@b@c/ => user:a@b host:c147// http://a@b?@c => user:a host:c path:/?@c148149// v0.12 TODO(isaacs): This is not quite how Chrome does things.150// Review our test case against browsers more comprehensively.151152// find the first instance of any hostEndingChars153var hostEnd = -1;154for (var i = 0; i < hostEndingChars.length; i++) {155var hec = rest.indexOf(hostEndingChars[i]);156if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))157hostEnd = hec;158}159160// at this point, either we have an explicit point where the161// auth portion cannot go past, or the last @ char is the decider.162var auth, atSign;163if (hostEnd === -1) {164// atSign can be anywhere.165atSign = rest.lastIndexOf('@');166} else {167// atSign must be in auth portion.168// http://a@b/c@d => host:b auth:a path:/c@d169atSign = rest.lastIndexOf('@', hostEnd);170}171172// Now we have a portion which is definitely the auth.173// Pull that off.174if (atSign !== -1) {175auth = rest.slice(0, atSign);176rest = rest.slice(atSign + 1);177this.auth = decodeURIComponent(auth);178}179180// the host is the remaining to the left of the first non-host char181hostEnd = -1;182for (var i = 0; i < nonHostChars.length; i++) {183var hec = rest.indexOf(nonHostChars[i]);184if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))185hostEnd = hec;186}187// if we still have not hit it, then the entire thing is a host.188if (hostEnd === -1)189hostEnd = rest.length;190191this.host = rest.slice(0, hostEnd);192rest = rest.slice(hostEnd);193194// pull out port.195this.parseHost();196197// we've indicated that there is a hostname,198// so even if it's empty, it has to be present.199this.hostname = this.hostname || '';200201// if hostname begins with [ and ends with ]202// assume that it's an IPv6 address.203var ipv6Hostname = this.hostname[0] === '[' &&204this.hostname[this.hostname.length - 1] === ']';205206// validate a little.207if (!ipv6Hostname) {208var hostparts = this.hostname.split(/\./);209for (var i = 0, l = hostparts.length; i < l; i++) {210var part = hostparts[i];211if (!part) continue;212if (!part.match(hostnamePartPattern)) {213var newpart = '';214for (var j = 0, k = part.length; j < k; j++) {215if (part.charCodeAt(j) > 127) {216// we replace non-ASCII char with a temporary placeholder217// we need this to make sure size of hostname is not218// broken by replacing non-ASCII by nothing219newpart += 'x';220} else {221newpart += part[j];222}223}224// we test again with ASCII char only225if (!newpart.match(hostnamePartPattern)) {226var validParts = hostparts.slice(0, i);227var notHost = hostparts.slice(i + 1);228var bit = part.match(hostnamePartStart);229if (bit) {230validParts.push(bit[1]);231notHost.unshift(bit[2]);232}233if (notHost.length) {234rest = '/' + notHost.join('.') + rest;235}236this.hostname = validParts.join('.');237break;238}239}240}241}242243if (this.hostname.length > hostnameMaxLen) {244this.hostname = '';245} else {246// hostnames are always lower case.247this.hostname = this.hostname.toLowerCase();248}249250if (!ipv6Hostname) {251// IDNA Support: Returns a puny coded representation of "domain".252// It only converts the part of the domain name that253// has non ASCII characters. I.e. it dosent matter if254// you call it with a domain that already is in ASCII.255var domainArray = this.hostname.split('.');256var newOut = [];257for (var i = 0; i < domainArray.length; ++i) {258var s = domainArray[i];259newOut.push(s.match(/[^A-Za-z0-9_-]/) ?260'xn--' + punycode.encode(s) : s);261}262this.hostname = newOut.join('.');263}264265var p = this.port ? ':' + this.port : '';266var h = this.hostname || '';267this.host = h + p;268this.href += this.host;269270// strip [ and ] from the hostname271// the host field still retains them, though272if (ipv6Hostname) {273this.hostname = this.hostname.substr(1, this.hostname.length - 2);274if (rest[0] !== '/') {275rest = '/' + rest;276}277}278}279280// now rest is set to the post-host stuff.281// chop off any delim chars.282if (!unsafeProtocol[lowerProto]) {283284// First, make 100% sure that any "autoEscape" chars get285// escaped, even if encodeURIComponent doesn't think they286// need to be.287for (var i = 0, l = autoEscape.length; i < l; i++) {288var ae = autoEscape[i];289var esc = encodeURIComponent(ae);290if (esc === ae) {291esc = escape(ae);292}293rest = rest.split(ae).join(esc);294}295}296297298// chop off from the tail first.299var hash = rest.indexOf('#');300if (hash !== -1) {301// got a fragment string.302this.hash = rest.substr(hash);303rest = rest.slice(0, hash);304}305var qm = rest.indexOf('?');306if (qm !== -1) {307this.search = rest.substr(qm);308this.query = rest.substr(qm + 1);309if (parseQueryString) {310this.query = querystring.parse(this.query);311}312rest = rest.slice(0, qm);313} else if (parseQueryString) {314// no query string, but parseQueryString still requested315this.search = '';316this.query = {};317}318if (rest) this.pathname = rest;319if (slashedProtocol[lowerProto] &&320this.hostname && !this.pathname) {321this.pathname = '/';322}323324//to support http.request325if (this.pathname || this.search) {326var p = this.pathname || '';327var s = this.search || '';328this.path = p + s;329}330331// finally, reconstruct the href based on what has been validated.332this.href = this.format();333return this;334};335336// format a parsed object into a url string337function urlFormat(obj) {338// ensure it's an object, and not a string url.339// If it's an obj, this is a no-op.340// this way, you can call url_format() on strings341// to clean up potentially wonky urls.342if (isString(obj)) obj = urlParse(obj);343if (!(obj instanceof Url)) return Url.prototype.format.call(obj);344return obj.format();345}346347Url.prototype.format = function() {348var auth = this.auth || '';349if (auth) {350auth = encodeURIComponent(auth);351auth = auth.replace(/%3A/i, ':');352auth += '@';353}354355var protocol = this.protocol || '',356pathname = this.pathname || '',357hash = this.hash || '',358host = false,359query = '';360361if (this.host) {362host = auth + this.host;363} else if (this.hostname) {364host = auth + (this.hostname.indexOf(':') === -1 ?365this.hostname :366'[' + this.hostname + ']');367if (this.port) {368host += ':' + this.port;369}370}371372if (this.query &&373isObject(this.query) &&374Object.keys(this.query).length) {375query = querystring.stringify(this.query);376}377378var search = this.search || (query && ('?' + query)) || '';379380if (protocol && protocol.substr(-1) !== ':') protocol += ':';381382// only the slashedProtocols get the //. Not mailto:, xmpp:, etc.383// unless they had them to begin with.384if (this.slashes ||385(!protocol || slashedProtocol[protocol]) && host !== false) {386host = '//' + (host || '');387if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;388} else if (!host) {389host = '';390}391392if (hash && hash.charAt(0) !== '#') hash = '#' + hash;393if (search && search.charAt(0) !== '?') search = '?' + search;394395pathname = pathname.replace(/[?#]/g, function(match) {396return encodeURIComponent(match);397});398search = search.replace('#', '%23');399400return protocol + host + pathname + search + hash;401};402403function urlResolve(source, relative) {404return urlParse(source, false, true).resolve(relative);405}406407Url.prototype.resolve = function(relative) {408return this.resolveObject(urlParse(relative, false, true)).format();409};410411function urlResolveObject(source, relative) {412if (!source) return relative;413return urlParse(source, false, true).resolveObject(relative);414}415416Url.prototype.resolveObject = function(relative) {417if (isString(relative)) {418var rel = new Url();419rel.parse(relative, false, true);420relative = rel;421}422423var result = new Url();424Object.keys(this).forEach(function(k) {425result[k] = this[k];426}, this);427428// hash is always overridden, no matter what.429// even href="" will remove it.430result.hash = relative.hash;431432// if the relative url is empty, then there's nothing left to do here.433if (relative.href === '') {434result.href = result.format();435return result;436}437438// hrefs like //foo/bar always cut to the protocol.439if (relative.slashes && !relative.protocol) {440// take everything except the protocol from relative441Object.keys(relative).forEach(function(k) {442if (k !== 'protocol')443result[k] = relative[k];444});445446//urlParse appends trailing / to urls like http://www.example.com447if (slashedProtocol[result.protocol] &&448result.hostname && !result.pathname) {449result.path = result.pathname = '/';450}451452result.href = result.format();453return result;454}455456if (relative.protocol && relative.protocol !== result.protocol) {457// if it's a known url protocol, then changing458// the protocol does weird things459// first, if it's not file:, then we MUST have a host,460// and if there was a path461// to begin with, then we MUST have a path.462// if it is file:, then the host is dropped,463// because that's known to be hostless.464// anything else is assumed to be absolute.465if (!slashedProtocol[relative.protocol]) {466Object.keys(relative).forEach(function(k) {467result[k] = relative[k];468});469result.href = result.format();470return result;471}472473result.protocol = relative.protocol;474if (!relative.host && !hostlessProtocol[relative.protocol]) {475var relPath = (relative.pathname || '').split('/');476while (relPath.length && !(relative.host = relPath.shift()));477if (!relative.host) relative.host = '';478if (!relative.hostname) relative.hostname = '';479if (relPath[0] !== '') relPath.unshift('');480if (relPath.length < 2) relPath.unshift('');481result.pathname = relPath.join('/');482} else {483result.pathname = relative.pathname;484}485result.search = relative.search;486result.query = relative.query;487result.host = relative.host || '';488result.auth = relative.auth;489result.hostname = relative.hostname || relative.host;490result.port = relative.port;491// to support http.request492if (result.pathname || result.search) {493var p = result.pathname || '';494var s = result.search || '';495result.path = p + s;496}497result.slashes = result.slashes || relative.slashes;498result.href = result.format();499return result;500}501502var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),503isRelAbs = (504relative.host ||505relative.pathname && relative.pathname.charAt(0) === '/'506),507mustEndAbs = (isRelAbs || isSourceAbs ||508(result.host && relative.pathname)),509removeAllDots = mustEndAbs,510srcPath = result.pathname && result.pathname.split('/') || [],511relPath = relative.pathname && relative.pathname.split('/') || [],512psychotic = result.protocol && !slashedProtocol[result.protocol];513514// if the url is a non-slashed url, then relative515// links like ../.. should be able516// to crawl up to the hostname, as well. This is strange.517// result.protocol has already been set by now.518// Later on, put the first path part into the host field.519if (psychotic) {520result.hostname = '';521result.port = null;522if (result.host) {523if (srcPath[0] === '') srcPath[0] = result.host;524else srcPath.unshift(result.host);525}526result.host = '';527if (relative.protocol) {528relative.hostname = null;529relative.port = null;530if (relative.host) {531if (relPath[0] === '') relPath[0] = relative.host;532else relPath.unshift(relative.host);533}534relative.host = null;535}536mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');537}538539if (isRelAbs) {540// it's absolute.541result.host = (relative.host || relative.host === '') ?542relative.host : result.host;543result.hostname = (relative.hostname || relative.hostname === '') ?544relative.hostname : result.hostname;545result.search = relative.search;546result.query = relative.query;547srcPath = relPath;548// fall through to the dot-handling below.549} else if (relPath.length) {550// it's relative551// throw away the existing file, and take the new path instead.552if (!srcPath) srcPath = [];553srcPath.pop();554srcPath = srcPath.concat(relPath);555result.search = relative.search;556result.query = relative.query;557} else if (!isNullOrUndefined(relative.search)) {558// just pull out the search.559// like href='?foo'.560// Put this after the other two cases because it simplifies the booleans561if (psychotic) {562result.hostname = result.host = srcPath.shift();563//occationaly the auth can get stuck only in host564//this especialy happens in cases like565//url.resolveObject('mailto:local1@domain1', 'local2@domain2')566var authInHost = result.host && result.host.indexOf('@') > 0 ?567result.host.split('@') : false;568if (authInHost) {569result.auth = authInHost.shift();570result.host = result.hostname = authInHost.shift();571}572}573result.search = relative.search;574result.query = relative.query;575//to support http.request576if (!isNull(result.pathname) || !isNull(result.search)) {577result.path = (result.pathname ? result.pathname : '') +578(result.search ? result.search : '');579}580result.href = result.format();581return result;582}583584if (!srcPath.length) {585// no path at all. easy.586// we've already handled the other stuff above.587result.pathname = null;588//to support http.request589if (result.search) {590result.path = '/' + result.search;591} else {592result.path = null;593}594result.href = result.format();595return result;596}597598// if a url ENDs in . or .., then it must get a trailing slash.599// however, if it ends in anything else non-slashy,600// then it must NOT get a trailing slash.601var last = srcPath.slice(-1)[0];602var hasTrailingSlash = (603(result.host || relative.host) && (last === '.' || last === '..') ||604last === '');605606// strip single dots, resolve double dots to parent dir607// if the path tries to go above the root, `up` ends up > 0608var up = 0;609for (var i = srcPath.length; i >= 0; i--) {610last = srcPath[i];611if (last == '.') {612srcPath.splice(i, 1);613} else if (last === '..') {614srcPath.splice(i, 1);615up++;616} else if (up) {617srcPath.splice(i, 1);618up--;619}620}621622// if the path is allowed to go above the root, restore leading ..s623if (!mustEndAbs && !removeAllDots) {624for (; up--; up) {625srcPath.unshift('..');626}627}628629if (mustEndAbs && srcPath[0] !== '' &&630(!srcPath[0] || srcPath[0].charAt(0) !== '/')) {631srcPath.unshift('');632}633634if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {635srcPath.push('');636}637638var isAbsolute = srcPath[0] === '' ||639(srcPath[0] && srcPath[0].charAt(0) === '/');640641// put the host back642if (psychotic) {643result.hostname = result.host = isAbsolute ? '' :644srcPath.length ? srcPath.shift() : '';645//occationaly the auth can get stuck only in host646//this especialy happens in cases like647//url.resolveObject('mailto:local1@domain1', 'local2@domain2')648var authInHost = result.host && result.host.indexOf('@') > 0 ?649result.host.split('@') : false;650if (authInHost) {651result.auth = authInHost.shift();652result.host = result.hostname = authInHost.shift();653}654}655656mustEndAbs = mustEndAbs || (result.host && srcPath.length);657658if (mustEndAbs && !isAbsolute) {659srcPath.unshift('');660}661662if (!srcPath.length) {663result.pathname = null;664result.path = null;665} else {666result.pathname = srcPath.join('/');667}668669//to support request.http670if (!isNull(result.pathname) || !isNull(result.search)) {671result.path = (result.pathname ? result.pathname : '') +672(result.search ? result.search : '');673}674result.auth = relative.auth || result.auth;675result.slashes = result.slashes || relative.slashes;676result.href = result.format();677return result;678};679680Url.prototype.parseHost = function() {681var host = this.host;682var port = portPattern.exec(host);683if (port) {684port = port[0];685if (port !== ':') {686this.port = port.substr(1);687}688host = host.substr(0, host.length - port.length);689}690if (host) this.hostname = host;691};692693function isString(arg) {694return typeof arg === "string";695}696697function isObject(arg) {698return typeof arg === 'object' && arg !== null;699}700701function isNull(arg) {702return arg === null;703}704function isNullOrUndefined(arg) {705return arg == null;706}707708709