1/*! 2 * Lightweight URL manipulation with JavaScript 3 * This library is independent of any other libraries and has pretty simple 4 * interface and lightweight code-base. 5 * Some ideas of query string parsing had been taken from Jan Wolter 6 * @see http://unixpapa.com/js/querystring.html 7 * 8 * @license MIT 9 * @author Mykhailo Stadnyk <[email protected]> 10 */ 11(function (ns) { 12 'use strict'; 13 14 // mapping between what we want and <a> element properties 15 var map = { 16 protocol: 'protocol', 17 host: 'hostname', 18 port: 'port', 19 path: 'pathname', 20 query: 'search', 21 hash: 'hash' 22 }; 23 24 // jscs: disable 25 /** 26 * default ports as defined by http://url.spec.whatwg.org/#default-port 27 * We need them to fix IE behavior, @see https://github.com/Mikhus/jsurl/issues/2 28 */ 29 // jscs: enable 30 var defaultPorts = { 31 ftp: 21, 32 gopher: 70, 33 http: 80, 34 https: 443, 35 ws: 80, 36 wss: 443 37 }; 38 39 function parse (self, url) { 40 var currUrl, link, i, auth; 41 42 if (typeof document === 'undefined' && typeof require === 'function') { 43 currUrl = 'file://' + 44 (process.platform.match(/^win/i) ? '/' : '') + 45 require('fs').realpathSync('.'); 46 47 if (url && url.charAt(0) !== '/' && !url.match(/^\w+:\/\//)) { 48 url = currUrl + require('path').sep + url; 49 } 50 51 link = require('url').parse(url || currUrl); 52 } 53 54 else { 55 currUrl = document.location.href; 56 link = document.createElement('a'); 57 link.href = url || currUrl; 58 } 59 60 auth = (url || currUrl).match(/\/\/(.*?)(?::(.*?))?@/) || []; 61 62 for (i in map) { 63 self[i] = link[map[i]] || ''; 64 } 65 66 // fix-up some parts 67 self.protocol = self.protocol.replace(/:$/, ''); 68 self.query = self.query.replace(/^\?/, ''); 69 self.hash = decode(self.hash.replace(/^#/, '')); 70 self.user = decode(auth[1] || ''); 71 self.pass = decode(auth[2] || ''); 72 self.port = ( 73 defaultPorts[self.protocol] === self.port || self.port === 0 74 ) ? '' : self.port; // IE fix, Android browser fix 75 76 if (!self.protocol && !/^([a-z]+:)?\/\/\/?/.test(url)) { 77 // is IE and path is relative 78 var base = new Url(currUrl.match(/(.*\/)/)[0]); 79 var basePath = base.path.split('/'); 80 var selfPath = self.path.split('/'); 81 var props = ['protocol', 'user', 'pass', 'host', 'port']; 82 var s = props.length; 83 84 basePath.pop(); 85 86 for (i = 0; i < s; i++) { 87 self[props[i]] = base[props[i]]; 88 } 89 90 while (selfPath[0] == '..') { // skip all "../ 91 basePath.pop(); 92 selfPath.shift(); 93 } 94 95 self.path = 96 (url.charAt(0) != '/' ? basePath.join('/') : '') + 97 '/' + selfPath.join('/') 98 ; 99 } 100 101 else { 102 // fix absolute URL's path in IE 103 self.path = self.path.replace(/^\/?/, '/'); 104 } 105 106 self.paths((self.path.charAt(0) == '/' ? 107 self.path.slice(1) : self.path).split('/') 108 ); 109 110 self.query = new QueryString(self.query); 111 } 112 113 function encode (s) { 114 return encodeURIComponent(s).replace(/'/g, '%27'); 115 } 116 117 function decode (s) { 118 s = s.replace(/\+/g, ' '); 119 120 s = s.replace(/%([ef][0-9a-f])%([89ab][0-9a-f])%([89ab][0-9a-f])/gi, 121 function (code, hex1, hex2, hex3) { 122 var n1 = parseInt(hex1, 16) - 0xE0; 123 var n2 = parseInt(hex2, 16) - 0x80; 124 125 if (n1 === 0 && n2 < 32) { 126 return code; 127 } 128 129 var n3 = parseInt(hex3, 16) - 0x80; 130 var n = (n1 << 12) + (n2 << 6) + n3; 131 132 if (n > 0xFFFF) { 133 return code; 134 } 135 136 return String.fromCharCode(n); 137 } 138 ); 139 140 s = s.replace(/%([cd][0-9a-f])%([89ab][0-9a-f])/gi, 141 function (code, hex1, hex2) { 142 var n1 = parseInt(hex1, 16) - 0xC0; 143 144 if (n1 < 2) { 145 return code; 146 } 147 148 var n2 = parseInt(hex2, 16) - 0x80; 149 150 return String.fromCharCode((n1 << 6) + n2); 151 } 152 ); 153 154 return s.replace(/%([0-7][0-9a-f])/gi, 155 function (code, hex) { 156 return String.fromCharCode(parseInt(hex, 16)); 157 } 158 ); 159 } 160 161 /** 162 * Class QueryString 163 * 164 * @param {string} qs - string representation of QueryString 165 * @constructor 166 */ 167 function QueryString (qs) { 168 var re = /([^=&]+)(=([^&]*))?/g; 169 var match; 170 171 while ((match = re.exec(qs))) { 172 var key = decodeURIComponent(match[1].replace(/\+/g, ' ')); 173 var value = match[3] ? decode(match[3]) : ''; 174 175 if (!(this[key] === undefined || this[key] === null)) { 176 if (!(this[key] instanceof Array)) { 177 this[key] = [this[key]]; 178 } 179 180 this[key].push(value); 181 } 182 183 else { 184 this[key] = value; 185 } 186 } 187 } 188 189 /** 190 * Converts QueryString object back to string representation 191 * 192 * @returns {string} 193 */ 194 QueryString.prototype.toString = function () { 195 var s = ''; 196 var e = encode; 197 var i, ii; 198 199 for (i in this) { 200 if (this[i] instanceof Function || this[i] === null) { 201 continue; 202 } 203 204 if (this[i] instanceof Array) { 205 var len = this[i].length; 206 207 if (len) { 208 for (ii = 0; ii < len; ii++) { 209 s += s ? '&' : ''; 210 s += e(i) + '=' + e(this[i][ii]); 211 } 212 } 213 214 else { 215 // parameter is an empty array, so treat as 216 // an empty argument 217 s += (s ? '&' : '') + e(i) + '='; 218 } 219 } 220 221 else { 222 s += s ? '&' : ''; 223 s += e(i) + '=' + e(this[i]); 224 } 225 } 226 227 return s; 228 }; 229 230 /** 231 * Class Url 232 * 233 * @param {string} [url] - string URL representation 234 * @constructor 235 */ 236 function Url (url) { 237 parse(this, url); 238 } 239 240 /** 241 * Clears QueryString, making it contain no params at all 242 * 243 * @returns {Url} 244 */ 245 Url.prototype.clearQuery = function () { 246 for (var key in this.query) { 247 if (!(this.query[key] instanceof Function)) { 248 delete this.query[key]; 249 } 250 } 251 252 return this; 253 }; 254 255 /** 256 * Returns total number of parameters in QueryString 257 * 258 * @returns {number} 259 */ 260 Url.prototype.queryLength = function () { 261 var count = 0; 262 var key; 263 264 for (key in this) { 265 if (!(this[key] instanceof Function)) { 266 count++; 267 } 268 } 269 270 return count; 271 }; 272 273 /**
274 * Returns true if QueryString contains no parameters, false otherwise 275 * 276 * @returns {boolean} 277 */ 278 Url.prototype.isEmptyQuery = function () { 279 return this.queryLength() === 0; 280 }; 281 282 /** 283 * 284 * @param {Array} [paths] - an array pf path parts (if given will modify 285 * Url.path property 286 * @returns {Array} - an array representation of the Url.path property 287 */ 288 Url.prototype.paths = function (paths) { 289 var prefix = ''; 290 var i = 0; 291 var s; 292 293 if (paths && paths.length && paths + '' !== paths) { 294 if (this.isAbsolute()) { 295 prefix = '/'; 296 } 297 298 for (s = paths.length; i < s; i++) { 299 paths[i] = !i && paths[i].match(/^\w:$/) ? paths[i] : 300 encode(paths[i]); 301 } 302 303 this.path = prefix + paths.join('/'); 304 } 305 306 paths = (this.path.charAt(0) === '/' ? 307 this.path.slice(1) : this.path).split('/'); 308 309 for (i = 0, s = paths.length; i < s; i++) { 310 paths[i] = decode(paths[i]); 311 } 312 313 return paths; 314 }; 315 316 /** 317 * Performs URL-specific encoding of the given string 318 * 319 * @method Url#encode 320 * @param {string} s - string to encode 321 * @returns {string} 322 */ 323 Url.prototype.encode = encode; 324 325 /** 326 * Performs URL-specific decoding of the given encoded string 327 * 328 * @method Url#decode 329 * @param {string} s - string to decode 330 * @returns {string} 331 */ 332 Url.prototype.decode = decode; 333 334 /** 335 * Checks if current URL is an absolute resource locator (globally absolute 336 * or absolute path to current server) 337 * 338 * @returns {boolean} 339 */ 340 Url.prototype.isAbsolute = function () { 341 return this.protocol || this.path.charAt(0) === '/'; 342 }; 343 344 /** 345 * Returns string representation of current Url object 346 * 347 * @returns {string} 348 */ 349 Url.prototype.toString = function () { 350 return ( 351 (this.protocol && (this.protocol + '://')) + 352 (this.user && ( 353 encode(this.user) + (this.pass && (':' + encode(this.pass)) 354 ) + '@')) + 355 (this.host && this.host) + 356 (this.port && (':' + this.port)) + 357 (this.path && this.path) + 358 (this.query.toString() && ('?' + this.query)) + 359 (this.hash && ('#' + encode(this.hash))) 360 ); 361 }; 362 363 ns[ns.exports ? 'exports' : 'Url'] = Url; 364}(typeof module !== 'undefined' && module.exports ? module : window));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.