mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-13 02:21:14 +01:00
089a59168e
This is a better approach as initially thought in #191. What is missing is outlined in the (several) TODOs.
459 lines
12 KiB
JavaScript
459 lines
12 KiB
JavaScript
/*!
|
|
* 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));
|