/*! * jsUri * https://github.com/derek-watson/jsUri * * Copyright 2013, Derek Watson * Released under the MIT license. * * Includes parseUri regular expressions * http://blog.stevenlevithan.com/archives/parseuri * Copyright 2007, Steven Levithan * Released under the MIT license. */ /*globals define, module */ (function(global) { var re = { starts_with_slashes: /^\/+/, ends_with_slashes: /\/+$/, pluses: /\+/g, query_separator: /[&;]/, uri_parser: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*)(?::([^:@\/]*))?)?@)?(\[[0-9a-fA-F:.]+\]|[^:\/?#]*)(?::(\d+|(?=:)))?(:)?)((((?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ }; /** * Define forEach for older js environments * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility */ if (!Array.prototype.forEach) { Array.prototype.forEach = function(callback, thisArg) { var T, k; if (this == null) { throw new TypeError(' this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = thisArg; } k = 0; while (k < len) { var kValue; if (k in O) { kValue = O[k]; callback.call(T, kValue, k, O); } k++; } }; } /** * unescape a query param value * @param {string} s encoded value * @return {string} decoded value */ function decode(s) { if (s) { s = s.toString().replace(re.pluses, '%20'); s = decodeURIComponent(s); } return s; } /** * Breaks a uri string down into its individual parts * @param {string} str uri * @return {object} parts */ function parseUri(str) { var parser = re.uri_parser; var parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "isColonUri", "relative", "path", "directory", "file", "query", "anchor"]; var m = parser.exec(str || ''); var parts = {}; parserKeys.forEach(function(key, i) { parts[key] = m[i] || ''; }); return parts; } /** * Breaks a query string down into an array of key/value pairs * @param {string} str query * @return {array} array of arrays (key/value pairs) */ function parseQuery(str) { var i, ps, p, n, k, v, l; var pairs = []; if (typeof(str) === 'undefined' || str === null || str === '') { return pairs; } if (str.indexOf('?') === 0) { str = str.substring(1); } ps = str.toString().split(re.query_separator); for (i = 0, l = ps.length; i < l; i++) { p = ps[i]; n = p.indexOf('='); if (n !== 0) { k = decode(p.substring(0, n)); v = decode(p.substring(n + 1)); pairs.push(n === -1 ? [p, null] : [k, v]); } } return pairs; } /** * Creates a new Uri object * @constructor * @param {string} str */ function Uri(str) { this.uriParts = parseUri(str); this.queryPairs = parseQuery(this.uriParts.query); this.hasAuthorityPrefixUserPref = null; } /** * Define getter/setter methods */ ['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) { Uri.prototype[key] = function(val) { if (typeof val !== 'undefined') { this.uriParts[key] = val; } return this.uriParts[key]; }; }); /** * if there is no protocol, the leading // can be enabled or disabled * @param {Boolean} val * @return {Boolean} */ Uri.prototype.hasAuthorityPrefix = function(val) { if (typeof val !== 'undefined') { this.hasAuthorityPrefixUserPref = val; } if (this.hasAuthorityPrefixUserPref === null) { return (this.uriParts.source.indexOf('//') !== -1); } else { return this.hasAuthorityPrefixUserPref; } }; Uri.prototype.isColonUri = function (val) { if (typeof val !== 'undefined') { this.uriParts.isColonUri = !!val; } else { return !!this.uriParts.isColonUri; } }; /** * Serializes the internal state of the query pairs * @param {string} [val] set a new query string * @return {string} query string */ Uri.prototype.query = function(val) { var s = '', i, param, l; if (typeof val !== 'undefined') { this.queryPairs = parseQuery(val); } for (i = 0, l = this.queryPairs.length; i < l; i++) { param = this.queryPairs[i]; if (s.length > 0) { s += '&'; } if (param[1] === null) { s += param[0]; } else { s += param[0]; s += '='; if (typeof param[1] !== 'undefined') { s += encodeURIComponent(param[1]); } } } return s.length > 0 ? '?' + s : s; }; /** * returns the first query param value found for the key * @param {string} key query key * @return {string} first value found for key */ Uri.prototype.getQueryParamValue = function (key) { var param, i, l; for (i = 0, l = this.queryPairs.length; i < l; i++) { param = this.queryPairs[i]; if (key === param[0]) { return param[1]; } } }; /** * returns an array of query param values for the key * @param {string} key query key * @return {array} array of values */ Uri.prototype.getQueryParamValues = function (key) { var arr = [], i, param, l; for (i = 0, l = this.queryPairs.length; i < l; i++) { param = this.queryPairs[i]; if (key === param[0]) { arr.push(param[1]); } } return arr; }; /** * removes query parameters * @param {string} key remove values for key * @param {val} [val] remove a specific value, otherwise removes all * @return {Uri} returns self for fluent chaining */ Uri.prototype.deleteQueryParam = function (key, val) { var arr = [], i, param, keyMatchesFilter, valMatchesFilter, l; for (i = 0, l = this.queryPairs.length; i < l; i++) { param = this.queryPairs[i]; keyMatchesFilter = decode(param[0]) === decode(key); valMatchesFilter = param[1] === val; if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && (!keyMatchesFilter || !valMatchesFilter))) { arr.push(param); } } this.queryPairs = arr; return this; }; /** * adds a query parameter * @param {string} key add values for key * @param {string} val value to add * @param {integer} [index] specific index to add the value at * @return {Uri} returns self for fluent chaining */ Uri.prototype.addQueryParam = function (key, val, index) { if (arguments.length === 3 && index !== -1) { index = Math.min(index, this.queryPairs.length); this.queryPairs.splice(index, 0, [key, val]); } else if (arguments.length > 0) { this.queryPairs.push([key, val]); } return this; }; /** * test for the existence of a query parameter * @param {string} key check values for key * @return {Boolean} true if key exists, otherwise false */ Uri.prototype.hasQueryParam = function (key) { var i, len = this.queryPairs.length; for (i = 0; i < len; i++) { if (this.queryPairs[i][0] == key) return true; } return false; }; /** * replaces query param values * @param {string} key key to replace value for * @param {string} newVal new value * @param {string} [oldVal] replace only one specific value (otherwise replaces all) * @return {Uri} returns self for fluent chaining */ Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) { var index = -1, len = this.queryPairs.length, i, param; if (arguments.length === 3) { for (i = 0; i < len; i++) { param = this.queryPairs[i]; if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) { index = i; break; } } if (index >= 0) { this.deleteQueryParam(key, decode(oldVal)).addQueryParam(key, newVal, index); } } else { for (i = 0; i < len; i++) { param = this.queryPairs[i]; if (decode(param[0]) === decode(key)) { index = i; break; } } this.deleteQueryParam(key); this.addQueryParam(key, newVal, index); } return this; }; /** * Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc) */ ['protocol', 'hasAuthorityPrefix', 'isColonUri', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) { var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1); Uri.prototype[method] = function(val) { this[key](val); return this; }; }); /** * Scheme name, colon and doubleslash, as required * @return {string} http:// or possibly just // */ Uri.prototype.scheme = function() { var s = ''; if (this.protocol()) { s += this.protocol(); if (this.protocol().indexOf(':') !== this.protocol().length - 1) { s += ':'; } s += '//'; } else { if (this.hasAuthorityPrefix() && this.host()) { s += '//'; } } return s; }; /** * Same as Mozilla nsIURI.prePath * @return {string} scheme://user:password@host:port * @see https://developer.mozilla.org/en/nsIURI */ Uri.prototype.origin = function() { var s = this.scheme(); if (this.userInfo() && this.host()) { s += this.userInfo(); if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) { s += '@'; } } if (this.host()) { s += this.host(); if (this.port() || (this.path() && this.path().substr(0, 1).match(/[0-9]/))) { s += ':' + this.port(); } } return s; }; /** * Adds a trailing slash to the path */ Uri.prototype.addTrailingSlash = function() { var path = this.path() || ''; if (path.substr(-1) !== '/') { this.path(path + '/'); } return this; }; /** * Serializes the internal state of the Uri object * @return {string} */ Uri.prototype.toString = function() { var path, s = this.origin(); if (this.isColonUri()) { if (this.path()) { s += ':'+this.path(); } } else if (this.path()) { path = this.path(); if (!(re.ends_with_slashes.test(s) || re.starts_with_slashes.test(path))) { s += '/'; } else { if (s) { s.replace(re.ends_with_slashes, '/'); } path = path.replace(re.starts_with_slashes, '/'); } s += path; } else { if (this.host() && (this.query().toString() || this.anchor())) { s += '/'; } } if (this.query().toString()) { s += this.query().toString(); } if (this.anchor()) { if (this.anchor().indexOf('#') !== 0) { s += '#'; } s += this.anchor(); } return s; }; /** * Clone a Uri object * @return {Uri} duplicate copy of the Uri */ Uri.prototype.clone = function() { return new Uri(this.toString()); }; /** * export via AMD or CommonJS, otherwise leak a global */ if (typeof define === 'function' && define.amd) { define(function() { return Uri; }); } else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = Uri; } else { global.Uri = Uri; } }(this));