var punycode = require('punycode'); exports.parse = urlParse; exports.resolve = urlResolve; exports.resolveObject = urlResolveObject; exports.format = urlFormat; var protocolPattern = /^([a-z0-9.+-]+:)/i, portPattern = /:[0-9]*$/, delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'] , unwise = ['{', '}', '|', '\\', '^', '~', '`'] .concat(delims), autoEscape = ['\''] .concat(delims), nonHostChars = ['%', '/', '?', ';', '#'] .concat(unwise).concat(autoEscape), nonAuthChars = ['/', '@', '?', '#'] .concat(delims), hostnameMaxLen = 255, hostnamePartPattern = /^[a-zA-Z0-9][a-z0-9A-Z_-]{0,62}$/, hostnamePartStart = /^([a-zA-Z0-9][a-z0-9A-Z_-]{0,62})(.*)$/, unsafeProtocol = { 'javascript': true , 'javascript:': true } , hostlessProtocol = { 'javascript': true , 'javascript:': true } , pathedProtocol = { 'http': true , 'https': true , 'ftp': true , 'gopher': true , 'file': true , 'http:': true , 'ftp:': true , 'gopher:': true , 'file:': true } , slashedProtocol = { 'http': true , 'https': true , 'ftp': true , 'gopher': true , 'file': true , 'http:': true , 'https:': true , 'ftp:': true , 'gopher:': true , 'file:': true } , querystring = require('querystring'); function urlParse(url, parseQueryString, slashesDenoteHost){ if (url && typeof (url) === 'object' && _AN_Read_href('href', url)) return url; if (typeof url !== 'string') { throw new TypeError("Parameter 'url' must be a string, not " + typeof url) } var out = { } , rest = url; rest = rest.trim(); var proto = protocolPattern.exec(rest); if (proto) { proto = proto[0]; var lowerProto = proto.toLowerCase(); _AN_Write_protocol("protocol", out, false , lowerProto); rest = rest.substr(_AN_Read_length("length", proto)); } if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { var slashes = rest.substr(0, 2) === '//'; if (slashes && !(proto && hostlessProtocol[proto])) { rest = rest.substr(2); out.slashes = true ; } } if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) { var atSign = rest.indexOf('@'); if (atSign !== -1) { var auth = rest.slice(0, atSign); var hasAuth = true ; for (var i = 0, l = _AN_Read_length('length', nonAuthChars); i < l; i++ ){ if (auth.indexOf(nonAuthChars[i]) !== -1) { hasAuth = false ; break ; } } if (hasAuth) { out.auth = decodeURIComponent(auth); rest = rest.substr(atSign + 1); } } var firstNonHost = -1; for (var i = 0, l = _AN_Read_length('length', nonHostChars); i < l; i++ ){ var index = rest.indexOf(nonHostChars[i]); if (index !== -1 && (firstNonHost < 0 || index < firstNonHost)) firstNonHost = index; } if (firstNonHost !== -1) { _AN_Write_host('host', out, false , rest.substr(0, firstNonHost)); rest = rest.substr(firstNonHost); } else { _AN_Write_host('host', out, false , rest); rest = ''; } var p = parseHost(_AN_Read_host('host', out)); var keys = Object.keys(p); for (var i = 0, l = _AN_Read_length('length', keys); i < l; i++ ){ var key = keys[i]; out[key] = p[key]; } _AN_Write_hostname('hostname', out, false , _AN_Read_hostname('hostname', out) || ''); var ipv6Hostname = _AN_Read_hostname('hostname', out)[0] === '[' && _AN_Read_hostname('hostname', out)[_AN_Read_length('length', _AN_Read_hostname('hostname', out)) - 1] === ']'; if (_AN_Read_length('length', _AN_Read_hostname('hostname', out)) > hostnameMaxLen) { _AN_Write_hostname('hostname', out, false , ''); } else if (!ipv6Hostname) { var hostparts = _AN_Read_hostname('hostname', out).split(/\./); for (var i = 0, l = _AN_Read_length('length', hostparts); i < l; i++ ){ var part = hostparts[i]; if (!part) continue ; if (!part.match(hostnamePartPattern)) { var newpart = ''; for (var j = 0, k = _AN_Read_length('length', part); j < k; j++ ){ if (part.charCodeAt(j) > 127) { newpart += 'x'; } else { newpart += part[j]; } } 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; } _AN_Write_hostname('hostname', out, false , validParts.join('.')); break ; } } } } _AN_Write_hostname('hostname', out, false , _AN_Read_hostname('hostname', out).toLowerCase()); if (!ipv6Hostname) { var domainArray = _AN_Read_hostname('hostname', out).split('.'); var newOut = [] ; for (var i = 0; i < _AN_Read_length('length', domainArray); ++i){ var s = domainArray[i]; newOut.push(s.match(/[^A-Za-z0-9_-]/)? 'xn--' + punycode.encode(s): s); } _AN_Write_hostname('hostname', out, false , newOut.join('.')); } _AN_Write_host('host', out, false , (_AN_Read_hostname('hostname', out) || '') + ((_AN_Read_port('port', out))? ':' + _AN_Read_port('port', out): '')); _AN_Write_href('href', out, true , _AN_Read_host('host', out)); if (ipv6Hostname) { _AN_Write_hostname('hostname', out, false , _AN_Read_hostname('hostname', out).substr(1, _AN_Read_length('length', _AN_Read_hostname('hostname', out)) - 2)); if (rest[0] !== '/') { rest = '/' + rest; } } } if (!unsafeProtocol[lowerProto]) { for (var i = 0, l = _AN_Read_length('length', autoEscape); i < l; i++ ){ var ae = autoEscape[i]; var esc = encodeURIComponent(ae); if (esc === ae) { esc = escape(ae); } rest = rest.split(ae).join(esc); } } var hash = rest.indexOf('#'); if (hash !== -1) { _AN_Write_hash('hash', out, false , rest.substr(hash)); rest = rest.slice(0, hash); } var qm = rest.indexOf('?'); if (qm !== -1) { _AN_Write_search('search', out, false , rest.substr(qm)); out.query = rest.substr(qm + 1); if (parseQueryString) { out.query = querystring.parse(out.query); } rest = rest.slice(0, qm); } else if (parseQueryString) { _AN_Write_search('search', out, false , ''); out.query = { } ; } if (rest) _AN_Write_pathname('pathname', out, false , rest); if (slashedProtocol[proto] && _AN_Read_hostname('hostname', out) && !_AN_Read_pathname('pathname', out)) { _AN_Write_pathname('pathname', out, false , '/'); } if (_AN_Read_pathname('pathname', out) || _AN_Read_search('search', out)) { out.path = (_AN_Read_pathname('pathname', out)? _AN_Read_pathname('pathname', out): '') + (_AN_Read_search('search', out)? _AN_Read_search('search', out): ''); } _AN_Write_href('href', out, false , urlFormat(out)); return out; } function urlFormat(obj){ if (typeof (obj) === 'string') obj = urlParse(obj); var auth = obj.auth || ''; if (auth) { auth = encodeURIComponent(auth); auth = _AN_Call_replace('replace', auth, /%3A/i, ':'); auth += '@'; } var protocol = _AN_Read_protocol('protocol', obj) || '', pathname = _AN_Read_pathname('pathname', obj) || '', hash = _AN_Read_hash('hash', obj) || '', host = false , query = ''; if (_AN_Read_host('host', obj) !== undefined) { host = auth + _AN_Read_host('host', obj); } else if (_AN_Read_hostname('hostname', obj) !== undefined) { host = auth + (_AN_Read_hostname('hostname', obj).indexOf(':') === -1? _AN_Read_hostname('hostname', obj): '[' + _AN_Read_hostname('hostname', obj) + ']'); if (obj.port) { host += ':' + _AN_Read_port('port', obj); } } if (obj.query && typeof obj.query === 'object' && _AN_Read_length('length', Object.keys(obj.query))) { query = querystring.stringify(obj.query); } var search = _AN_Read_search('search', obj) || (query && ('?' + query)) || ''; if (protocol && protocol.substr(-1) !== ':') protocol += ':'; if (obj.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; return protocol + host + pathname + search + hash; } function urlResolve(source, relative){ return urlFormat(urlResolveObject(source, relative)); } function urlResolveObject(source, relative){ if (!source) return relative; source = urlParse(urlFormat(source), false , true ); relative = urlParse(urlFormat(relative), false , true ); _AN_Write_hash('hash', source, false , _AN_Read_hash('hash', relative)); if (_AN_Read_href('href', relative) === '') { _AN_Write_href('href', source, false , urlFormat(source)); return source; } if (relative.slashes && !_AN_Read_protocol('protocol', relative)) { _AN_Write_protocol('protocol', relative, false , _AN_Read_protocol('protocol', source)); if (slashedProtocol[_AN_Read_protocol('protocol', relative)] && _AN_Read_hostname('hostname', relative) && !_AN_Read_pathname('pathname', relative)) { relative.path = _AN_Write_pathname('pathname', relative, false , '/'); } _AN_Write_href('href', relative, false , urlFormat(relative)); return relative; } if (_AN_Read_protocol('protocol', relative) && _AN_Read_protocol('protocol', relative) !== _AN_Read_protocol('protocol', source)) { if (!slashedProtocol[_AN_Read_protocol('protocol', relative)]) { _AN_Write_href('href', relative, false , urlFormat(relative)); return relative; } _AN_Write_protocol('protocol', source, false , _AN_Read_protocol('protocol', relative)); if (!_AN_Read_host('host', relative) && !hostlessProtocol[_AN_Read_protocol('protocol', relative)]) { var relPath = (_AN_Read_pathname('pathname', relative) || '').split('/'); while (_AN_Read_length('length', relPath) && !(_AN_Write_host('host', relative, false , relPath.shift()))); if (!_AN_Read_host('host', relative)) _AN_Write_host('host', relative, false , ''); if (!_AN_Read_hostname('hostname', relative)) _AN_Write_hostname('hostname', relative, false , ''); if (relPath[0] !== '') relPath.unshift(''); if (_AN_Read_length('length', relPath) < 2) relPath.unshift(''); _AN_Write_pathname('pathname', relative, false , relPath.join('/')); } _AN_Write_pathname('pathname', source, false , _AN_Read_pathname('pathname', relative)); _AN_Write_search('search', source, false , _AN_Read_search('search', relative)); source.query = relative.query; _AN_Write_host('host', source, false , _AN_Read_host('host', relative) || ''); source.auth = relative.auth; _AN_Write_hostname('hostname', source, false , _AN_Read_hostname('hostname', relative) || _AN_Read_host('host', relative)); _AN_Write_port('port', source, false , _AN_Read_port('port', relative)); if (_AN_Read_pathname('pathname', source) !== undefined || _AN_Read_search('search', source) !== undefined) { source.path = (_AN_Read_pathname('pathname', source)? _AN_Read_pathname('pathname', source): '') + (_AN_Read_search('search', source)? _AN_Read_search('search', source): ''); } source.slashes = source.slashes || relative.slashes; _AN_Write_href('href', source, false , urlFormat(source)); return source; } var isSourceAbs = (_AN_Read_pathname('pathname', source) && _AN_Read_pathname('pathname', source).charAt(0) === '/'), isRelAbs = (_AN_Read_host('host', relative) !== undefined || _AN_Read_pathname('pathname', relative) && _AN_Read_pathname('pathname', relative).charAt(0) === '/'), mustEndAbs = (isRelAbs || isSourceAbs || (_AN_Read_host('host', source) && _AN_Read_pathname('pathname', relative))), removeAllDots = mustEndAbs, srcPath = _AN_Read_pathname('pathname', source) && _AN_Read_pathname('pathname', source).split('/') || [] , relPath = _AN_Read_pathname('pathname', relative) && _AN_Read_pathname('pathname', relative).split('/') || [] , psychotic = _AN_Read_protocol('protocol', source) && !slashedProtocol[_AN_Read_protocol('protocol', source)]; if (psychotic) { delete source.hostname; delete source.port; if (source.host) { if (srcPath[0] === '') srcPath[0] = _AN_Read_host('host', source); else srcPath.unshift(_AN_Read_host('host', source)); } delete source.host; if (relative.protocol) { delete relative.hostname; delete relative.port; if (relative.host) { if (relPath[0] === '') relPath[0] = _AN_Read_host('host', relative); else relPath.unshift(_AN_Read_host('host', relative)); } delete relative.host; } mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); } if (isRelAbs) { _AN_Write_host('host', source, false , (_AN_Read_host('host', relative) || _AN_Read_host('host', relative) === '')? _AN_Read_host('host', relative): _AN_Read_host('host', source)); _AN_Write_hostname('hostname', source, false , (_AN_Read_hostname('hostname', relative) || _AN_Read_hostname('hostname', relative) === '')? _AN_Read_hostname('hostname', relative): _AN_Read_hostname('hostname', source)); _AN_Write_search('search', source, false , _AN_Read_search('search', relative)); source.query = relative.query; srcPath = relPath; } else if (relPath.length) { if (!srcPath) srcPath = [] ; srcPath.pop(); srcPath = srcPath.concat(relPath); _AN_Write_search('search', source, false , _AN_Read_search('search', relative)); source.query = relative.query; } else if ('search' in relative) { if (psychotic) { _AN_Write_hostname('hostname', source, false , _AN_Write_host('host', source, false , srcPath.shift())); var authInHost = _AN_Read_host('host', source) && _AN_Read_host('host', source).indexOf('@') > 0? _AN_Read_host('host', source).split('@'): false ; if (authInHost) { source.auth = authInHost.shift(); _AN_Write_host('host', source, false , _AN_Write_hostname('hostname', source, false , authInHost.shift())); } } _AN_Write_search('search', source, false , _AN_Read_search('search', relative)); source.query = relative.query; if (_AN_Read_pathname('pathname', source) !== undefined || _AN_Read_search('search', source) !== undefined) { source.path = (_AN_Read_pathname('pathname', source)? _AN_Read_pathname('pathname', source): '') + (_AN_Read_search('search', source)? _AN_Read_search('search', source): ''); } _AN_Write_href('href', source, false , urlFormat(source)); return source; } if (!_AN_Read_length('length', srcPath)) { delete source.pathname; if (!_AN_Read_search('search', source)) { source.path = '/' + _AN_Read_search('search', source); } else { delete source.path; } _AN_Write_href('href', source, false , urlFormat(source)); return source; } var last = srcPath.slice(-1)[0]; var hasTrailingSlash = ((_AN_Read_host('host', source) || _AN_Read_host('host', relative)) && (last === '.' || last === '..') || last === ''); var up = 0; for (var i = _AN_Read_length('length', srcPath); 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 (!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) === '/'); if (psychotic) { _AN_Write_hostname('hostname', source, false , _AN_Write_host('host', source, false , isAbsolute? '': _AN_Read_length('length', srcPath)? srcPath.shift(): '')); var authInHost = _AN_Read_host('host', source) && _AN_Read_host('host', source).indexOf('@') > 0? _AN_Read_host('host', source).split('@'): false ; if (authInHost) { source.auth = authInHost.shift(); _AN_Write_host('host', source, false , _AN_Write_hostname('hostname', source, false , authInHost.shift())); } } mustEndAbs = mustEndAbs || (_AN_Read_host('host', source) && _AN_Read_length('length', srcPath)); if (mustEndAbs && !isAbsolute) { srcPath.unshift(''); } _AN_Write_pathname('pathname', source, false , srcPath.join('/')); if (_AN_Read_pathname('pathname', source) !== undefined || _AN_Read_search('search', source) !== undefined) { source.path = (_AN_Read_pathname('pathname', source)? _AN_Read_pathname('pathname', source): '') + (_AN_Read_search('search', source)? _AN_Read_search('search', source): ''); } source.auth = relative.auth || source.auth; source.slashes = source.slashes || relative.slashes; _AN_Write_href('href', source, false , urlFormat(source)); return source; } function parseHost(host){ var out = { } ; var port = portPattern.exec(host); if (port) { port = port[0]; if (port !== ':') { _AN_Write_port('port', out, false , port.substr(1)); } host = host.substr(0, _AN_Read_length('length', host) - _AN_Read_length('length', port)); } if (host) _AN_Write_hostname('hostname', out, false , host); return out; }