github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/view/assets/js/url.js (about) 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 <mikhus@gmail.com> 10 */ 11 (function (ns) { 12 'use strict'; 13 14 var RX_PROTOCOL = /^[a-z]+:/; 15 var RX_PORT = /[-a-z0-9]+(\.[-a-z0-9])*:\d+/i; 16 var RX_CREDS = /\/\/(.*?)(?::(.*?))?@/; 17 var RX_WIN = /^win/i; 18 var RX_PROTOCOL_REPL = /:$/; 19 var RX_QUERY_REPL = /^\?/; 20 var RX_HASH_REPL = /^#/; 21 var RX_PATH = /(.*\/)/; 22 var RX_PATH_FIX = /^\/{2,}/; 23 var RX_SINGLE_QUOTE = /'/g; 24 var RX_DECODE_1 = /%([ef][0-9a-f])%([89ab][0-9a-f])%([89ab][0-9a-f])/gi; 25 var RX_DECODE_2 = /%([cd][0-9a-f])%([89ab][0-9a-f])/gi; 26 var RX_DECODE_3 = /%([0-7][0-9a-f])/gi; 27 var RX_PLUS = /\+/g; 28 var RX_PATH_SEMI = /^\w:$/; 29 var RX_URL_TEST = /[^/#?]/; 30 31 // configure given url options 32 function urlConfig(url) { 33 var config = { 34 path: true, 35 query: true, 36 hash: true 37 }; 38 39 if (!url) { 40 return config; 41 } 42 43 if (RX_PROTOCOL.test(url)) { 44 config.protocol = true; 45 config.host = true; 46 47 if (RX_PORT.test(url)) { 48 config.port = true; 49 } 50 51 if (RX_CREDS.test(url)) { 52 config.user = true; 53 config.pass = true; 54 } 55 } 56 57 return config; 58 } 59 60 var isNode = typeof window === 'undefined' && 61 typeof global !== 'undefined' && 62 typeof require === 'function'; 63 64 // Trick to bypass Webpack's require at compile time 65 var nodeRequire = isNode ? ns['require'] : null; 66 67 // mapping between what we want and <a> element properties 68 var map = { 69 protocol: 'protocol', 70 host: 'hostname', 71 port: 'port', 72 path: 'pathname', 73 query: 'search', 74 hash: 'hash' 75 }; 76 77 // jscs: disable 78 /** 79 * default ports as defined by http://url.spec.whatwg.org/#default-port 80 * We need them to fix IE behavior, @see https://github.com/Mikhus/jsurl/issues/2 81 */ 82 // jscs: enable 83 var defaultPorts = { 84 ftp: 21, 85 gopher: 70, 86 http: 80, 87 https: 443, 88 ws: 80, 89 wss: 443 90 }; 91 92 var _currNodeUrl; 93 function getCurrUrl() { 94 if (isNode) { 95 if (!_currNodeUrl) { 96 _currNodeUrl = ('file://' + 97 (process.platform.match(RX_WIN) ? '/' : '') + 98 nodeRequire('fs').realpathSync('.') 99 ); 100 } 101 return _currNodeUrl; 102 } else { 103 return document.location.href; 104 } 105 } 106 107 function parse(self, url, absolutize) { 108 var link, i, auth; 109 110 if (!url) { 111 url = getCurrUrl(); 112 } 113 114 if (isNode) { 115 link = nodeRequire('url').parse(url); 116 } 117 118 else { 119 link = document.createElement('a'); 120 link.href = url; 121 } 122 123 var config = urlConfig(url); 124 125 auth = url.match(RX_CREDS) || []; 126 127 for (i in map) { 128 if (config[i]) { 129 self[i] = link[map[i]] || ''; 130 } 131 132 else { 133 self[i] = ''; 134 } 135 } 136 137 // fix-up some parts 138 self.protocol = self.protocol.replace(RX_PROTOCOL_REPL, ''); 139 self.query = self.query.replace(RX_QUERY_REPL, ''); 140 self.hash = decode(self.hash.replace(RX_HASH_REPL, '')); 141 self.user = decode(auth[1] || ''); 142 self.pass = decode(auth[2] || ''); 143 /* jshint ignore:start */ 144 self.port = ( 145 // loosely compare because port can be a string 146 defaultPorts[self.protocol] == self.port || self.port == 0 147 ) ? '' : self.port; // IE fix, Android browser fix 148 /* jshint ignore:end */ 149 150 if (!config.protocol && RX_URL_TEST.test(url.charAt(0))) { 151 self.path = url.split('?')[0].split('#')[0]; 152 } 153 154 if (!config.protocol && absolutize) { 155 // is IE and path is relative 156 var base = new Url(getCurrUrl().match(RX_PATH)[0]); 157 var basePath = base.path.split('/'); 158 var selfPath = self.path.split('/'); 159 var props = ['protocol', 'user', 'pass', 'host', 'port']; 160 var s = props.length; 161 162 basePath.pop(); 163 164 for (i = 0; i < s; i++) { 165 self[props[i]] = base[props[i]]; 166 } 167 168 while (selfPath[0] === '..') { // skip all "../ 169 basePath.pop(); 170 selfPath.shift(); 171 } 172 173 self.path = 174 (url.charAt(0) !== '/' ? basePath.join('/') : '') + 175 '/' + selfPath.join('/') 176 ; 177 } 178 179 self.path = self.path.replace(RX_PATH_FIX, '/'); 180 181 self.paths(self.paths()); 182 183 self.query = new QueryString(self.query); 184 } 185 186 function encode(s) { 187 return encodeURIComponent(s).replace(RX_SINGLE_QUOTE, '%27'); 188 } 189 190 function decode(s) { 191 s = s.replace(RX_PLUS, ' '); 192 s = s.replace(RX_DECODE_1, function (code, hex1, hex2, hex3) { 193 var n1 = parseInt(hex1, 16) - 0xE0; 194 var n2 = parseInt(hex2, 16) - 0x80; 195 196 if (n1 === 0 && n2 < 32) { 197 return code; 198 } 199 200 var n3 = parseInt(hex3, 16) - 0x80; 201 var n = (n1 << 12) + (n2 << 6) + n3; 202 203 if (n > 0xFFFF) { 204 return code; 205 } 206 207 return String.fromCharCode(n); 208 }); 209 s = s.replace(RX_DECODE_2, function (code, hex1, hex2) { 210 var n1 = parseInt(hex1, 16) - 0xC0; 211 212 if (n1 < 2) { 213 return code; 214 } 215 216 var n2 = parseInt(hex2, 16) - 0x80; 217 218 return String.fromCharCode((n1 << 6) + n2); 219 }); 220 221 return s.replace(RX_DECODE_3, function (code, hex) { 222 return String.fromCharCode(parseInt(hex, 16)); 223 }); 224 } 225 226 /** 227 * Class QueryString 228 * 229 * @param {string} qs - string representation of QueryString 230 * @constructor 231 */ 232 function QueryString(qs) { 233 var parts = qs.split('&'); 234 235 for (var i = 0, s = parts.length; i < s; i++) { 236 var keyVal = parts[i].split('='); 237 var key = decodeURIComponent(keyVal[0].replace(RX_PLUS, ' ')); 238 239 if (!key) { 240 continue; 241 } 242 243 var value = keyVal[1] !== undefined ? decode(keyVal[1]) : null; 244 245 if (typeof this[key] === 'undefined') { 246 this[key] = value; 247 } else { 248 if (!(this[key] instanceof Array)) { 249 this[key] = [this[key]]; 250 } 251 252 this[key].push(value); 253 } 254 } 255 } 256 257 /** 258 * Converts QueryString object back to string representation 259 * 260 * @returns {string} 261 */ 262 QueryString.prototype.toString = function () { 263 var s = ''; 264 var e = encode; 265 var i, ii; 266 267 for (i in this) { 268 var w = this[i]; 269 270 if (w instanceof Function || w === null) { 271 continue; 272 } 273 274 if (w instanceof Array) { 275 var len = w.length; 276 277 if (len) { 278 for (ii = 0; ii < len; ii++) { 279 var v = w[ii]; 280 s += s ? '&' : ''; 281 s += e(i) + (v === undefined || v === null 282 ? '' 283 : '=' + e(v)); 284 } 285 } 286 287 else { 288 // parameter is an empty array, so treat as 289 // an empty argument 290 s += (s ? '&' : '') + e(i) + '='; 291 } 292 } 293 294 else { 295 s += s ? '&' : ''; 296 s += e(i) + (w === undefined ? '' : '=' + e(w)); 297 } 298 } 299 300 return s; 301 }; 302 303 /** 304 * Class Url 305 * 306 * @param {string} [url] - string URL representation 307 * @param {boolean} [noTransform] - do not transform to absolute URL 308 * @constructor 309 */ 310 function Url(url, noTransform) { 311 parse(this, url, !noTransform); 312 } 313 314 /** 315 * Clears QueryString, making it contain no params at all 316 * 317 * @returns {Url} 318 */ 319 Url.prototype.clearQuery = function () { 320 for (var key in this.query) { 321 if (!(this.query[key] instanceof Function)) { 322 delete this.query[key]; 323 } 324 } 325 326 return this; 327 }; 328 329 /** 330 * Returns total number of parameters in QueryString 331 * 332 * @returns {number} 333 */ 334 Url.prototype.queryLength = function () { 335 var count = 0; 336 337 for (var key in this.query) { 338 if (!(this.query[key] instanceof Function)) { 339 count++; 340 } 341 } 342 343 return count; 344 }; 345 346 /** 347 * Returns true if QueryString contains no parameters, false otherwise 348 * 349 * @returns {boolean} 350 */ 351 Url.prototype.isEmptyQuery = function () { 352 return this.queryLength() === 0; 353 }; 354 355 /** 356 * 357 * @param {Array} [paths] - an array pf path parts (if given will modify 358 * Url.path property 359 * @returns {Array} - an array representation of the Url.path property 360 */ 361 Url.prototype.paths = function (paths) { 362 var prefix = ''; 363 var i = 0; 364 var s; 365 366 if (paths && paths.length && paths + '' !== paths) { 367 if (this.isAbsolute()) { 368 prefix = '/'; 369 } 370 371 for (s = paths.length; i < s; i++) { 372 paths[i] = !i && RX_PATH_SEMI.test(paths[i]) 373 ? paths[i] 374 : encode(paths[i]); 375 } 376 377 this.path = prefix + paths.join('/'); 378 } 379 380 paths = (this.path.charAt(0) === '/' ? 381 this.path.slice(1) : this.path).split('/'); 382 383 for (i = 0, s = paths.length; i < s; i++) { 384 paths[i] = decode(paths[i]); 385 } 386 387 return paths; 388 }; 389 390 /** 391 * Performs URL-specific encoding of the given string 392 * 393 * @method Url#encode 394 * @param {string} s - string to encode 395 * @returns {string} 396 */ 397 Url.prototype.encode = encode; 398 399 /** 400 * Performs URL-specific decoding of the given encoded string 401 * 402 * @method Url#decode 403 * @param {string} s - string to decode 404 * @returns {string} 405 */ 406 Url.prototype.decode = decode; 407 408 /** 409 * Checks if current URL is an absolute resource locator (globally absolute 410 * or absolute path to current server) 411 * 412 * @returns {boolean} 413 */ 414 Url.prototype.isAbsolute = function () { 415 return this.protocol || this.path.charAt(0) === '/'; 416 }; 417 418 /** 419 * Returns string representation of current Url object 420 * 421 * @returns {string} 422 */ 423 Url.prototype.toString = function () { 424 return ( 425 (this.protocol && (this.protocol + '://')) + 426 (this.user && ( 427 encode(this.user) + (this.pass && (':' + encode(this.pass)) 428 ) + '@')) + 429 (this.host && this.host) + 430 (this.port && (':' + this.port)) + 431 (this.path && this.path) + 432 (this.query.toString() && ('?' + this.query)) + 433 (this.hash && ('#' + encode(this.hash))) 434 ); 435 }; 436 437 ns[ns.exports ? 'exports' : 'Url'] = Url; 438 }(typeof module !== 'undefined' && module.exports ? module : window));