UNPKG

react-cmi5

Version:

React wrapper component for an xapi/cmi5 assignable unit @see https://github.com/AICC/CMI-5_Spec_Current/blob/quartz/cmi5_spec.md#au_requirements

1,660 lines (1,457 loc) 586 kB
/*! cmi5.js 2.0.1 2018-01-17T13:25:59-0600 */ /*! http://mths.be/punycode v1.2.3 by @mathias */ ;(function(root) { /** Detect free variables */ var freeExports = typeof exports == 'object' && exports; var freeModule = typeof module == 'object' && module && module.exports == freeExports && module; var freeGlobal = typeof global == 'object' && global; if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { root = freeGlobal; } /** * The `punycode` object. * @name punycode * @type Object */ var punycode, /** Highest positive signed 32-bit float value */ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 /** Bootstring parameters */ base = 36, tMin = 1, tMax = 26, skew = 38, damp = 700, initialBias = 72, initialN = 128, // 0x80 delimiter = '-', // '\x2D' /** Regular expressions */ regexPunycode = /^xn--/, regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators /** Error messages */ errors = { 'overflow': 'Overflow: input needs wider integers to process', 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 'invalid-input': 'Invalid input' }, /** Convenience shortcuts */ baseMinusTMin = base - tMin, floor = Math.floor, stringFromCharCode = String.fromCharCode, /** Temporary variable */ key; /*--------------------------------------------------------------------------*/ /** * A generic error utility function. * @private * @param {String} type The error type. * @returns {Error} Throws a `RangeError` with the applicable error message. */ function error(type) { throw RangeError(errors[type]); } /** * A generic `Array#map` utility function. * @private * @param {Array} array The array to iterate over. * @param {Function} callback The function that gets called for every array * item. * @returns {Array} A new array of values returned by the callback function. */ function map(array, fn) { var length = array.length; while (length--) { array[length] = fn(array[length]); } return array; } /** * A simple `Array#map`-like wrapper to work with domain name strings. * @private * @param {String} domain The domain name. * @param {Function} callback The function that gets called for every * character. * @returns {Array} A new string of characters returned by the callback * function. */ function mapDomain(string, fn) { return map(string.split(regexSeparators), fn).join('.'); } /** * Creates an array containing the numeric code points of each Unicode * character in the string. While JavaScript uses UCS-2 internally, * this function will convert a pair of surrogate halves (each of which * UCS-2 exposes as separate characters) into a single code point, * matching UTF-16. * @see `punycode.ucs2.encode` * @see <http://mathiasbynens.be/notes/javascript-encoding> * @memberOf punycode.ucs2 * @name decode * @param {String} string The Unicode input string (UCS-2). * @returns {Array} The new array of code points. */ function ucs2decode(string) { var output = [], counter = 0, length = string.length, value, extra; while (counter < length) { value = string.charCodeAt(counter++); if (value >= 0xD800 && value <= 0xDBFF && counter < length) { // high surrogate, and there is a next character extra = string.charCodeAt(counter++); if ((extra & 0xFC00) == 0xDC00) { // low surrogate output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); } else { // unmatched surrogate; only append this code unit, in case the next // code unit is the high surrogate of a surrogate pair output.push(value); counter--; } } else { output.push(value); } } return output; } /** * Creates a string based on an array of numeric code points. * @see `punycode.ucs2.decode` * @memberOf punycode.ucs2 * @name encode * @param {Array} codePoints The array of numeric code points. * @returns {String} The new Unicode string (UCS-2). */ function ucs2encode(array) { return map(array, function(value) { var output = ''; if (value > 0xFFFF) { value -= 0x10000; output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); value = 0xDC00 | value & 0x3FF; } output += stringFromCharCode(value); return output; }).join(''); } /** * Converts a basic code point into a digit/integer. * @see `digitToBasic()` * @private * @param {Number} codePoint The basic numeric code point value. * @returns {Number} The numeric value of a basic code point (for use in * representing integers) in the range `0` to `base - 1`, or `base` if * the code point does not represent a value. */ function basicToDigit(codePoint) { if (codePoint - 48 < 10) { return codePoint - 22; } if (codePoint - 65 < 26) { return codePoint - 65; } if (codePoint - 97 < 26) { return codePoint - 97; } return base; } /** * Converts a digit/integer into a basic code point. * @see `basicToDigit()` * @private * @param {Number} digit The numeric value of a basic code point. * @returns {Number} The basic code point whose value (when used for * representing integers) is `digit`, which needs to be in the range * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is * used; else, the lowercase form is used. The behavior is undefined * if `flag` is non-zero and `digit` has no uppercase form. */ function digitToBasic(digit, flag) { // 0..25 map to ASCII a..z or A..Z // 26..35 map to ASCII 0..9 return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); } /** * Bias adaptation function as per section 3.4 of RFC 3492. * http://tools.ietf.org/html/rfc3492#section-3.4 * @private */ function adapt(delta, numPoints, firstTime) { var k = 0; delta = firstTime ? floor(delta / damp) : delta >> 1; delta += floor(delta / numPoints); for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { delta = floor(delta / baseMinusTMin); } return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); } /** * Converts a Punycode string of ASCII-only symbols to a string of Unicode * symbols. * @memberOf punycode * @param {String} input The Punycode string of ASCII-only symbols. * @returns {String} The resulting string of Unicode symbols. */ function decode(input) { // Don't use UCS-2 var output = [], inputLength = input.length, out, i = 0, n = initialN, bias = initialBias, basic, j, index, oldi, w, k, digit, t, length, /** Cached calculation results */ baseMinusT; // Handle the basic code points: let `basic` be the number of input code // points before the last delimiter, or `0` if there is none, then copy // the first basic code points to the output. basic = input.lastIndexOf(delimiter); if (basic < 0) { basic = 0; } for (j = 0; j < basic; ++j) { // if it's not a basic code point if (input.charCodeAt(j) >= 0x80) { error('not-basic'); } output.push(input.charCodeAt(j)); } // Main decoding loop: start just after the last delimiter if any basic code // points were copied; start at the beginning otherwise. for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { // `index` is the index of the next character to be consumed. // Decode a generalized variable-length integer into `delta`, // which gets added to `i`. The overflow checking is easier // if we increase `i` as we go, then subtract off its starting // value at the end to obtain `delta`. for (oldi = i, w = 1, k = base; /* no condition */; k += base) { if (index >= inputLength) { error('invalid-input'); } digit = basicToDigit(input.charCodeAt(index++)); if (digit >= base || digit > floor((maxInt - i) / w)) { error('overflow'); } i += digit * w; t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (digit < t) { break; } baseMinusT = base - t; if (w > floor(maxInt / baseMinusT)) { error('overflow'); } w *= baseMinusT; } out = output.length + 1; bias = adapt(i - oldi, out, oldi == 0); // `i` was supposed to wrap around from `out` to `0`, // incrementing `n` each time, so we'll fix that now: if (floor(i / out) > maxInt - n) { error('overflow'); } n += floor(i / out); i %= out; // Insert `n` at position `i` of the output output.splice(i++, 0, n); } return ucs2encode(output); } /** * Converts a string of Unicode symbols to a Punycode string of ASCII-only * symbols. * @memberOf punycode * @param {String} input The string of Unicode symbols. * @returns {String} The resulting Punycode string of ASCII-only symbols. */ function encode(input) { var n, delta, handledCPCount, basicLength, bias, j, m, q, k, t, currentValue, output = [], /** `inputLength` will hold the number of code points in `input`. */ inputLength, /** Cached calculation results */ handledCPCountPlusOne, baseMinusT, qMinusT; // Convert the input in UCS-2 to Unicode input = ucs2decode(input); // Cache the length inputLength = input.length; // Initialize the state n = initialN; delta = 0; bias = initialBias; // Handle the basic code points for (j = 0; j < inputLength; ++j) { currentValue = input[j]; if (currentValue < 0x80) { output.push(stringFromCharCode(currentValue)); } } handledCPCount = basicLength = output.length; // `handledCPCount` is the number of code points that have been handled; // `basicLength` is the number of basic code points. // Finish the basic string - if it is not empty - with a delimiter if (basicLength) { output.push(delimiter); } // Main encoding loop: while (handledCPCount < inputLength) { // All non-basic code points < n have been handled already. Find the next // larger one: for (m = maxInt, j = 0; j < inputLength; ++j) { currentValue = input[j]; if (currentValue >= n && currentValue < m) { m = currentValue; } } // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, // but guard against overflow handledCPCountPlusOne = handledCPCount + 1; if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { error('overflow'); } delta += (m - n) * handledCPCountPlusOne; n = m; for (j = 0; j < inputLength; ++j) { currentValue = input[j]; if (currentValue < n && ++delta > maxInt) { error('overflow'); } if (currentValue == n) { // Represent delta as a generalized variable-length integer for (q = delta, k = base; /* no condition */; k += base) { t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (q < t) { break; } qMinusT = q - t; baseMinusT = base - t; output.push( stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) ); q = floor(qMinusT / baseMinusT); } output.push(stringFromCharCode(digitToBasic(q, 0))); bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); delta = 0; ++handledCPCount; } } ++delta; ++n; } return output.join(''); } /** * Converts a Punycode string representing a domain name to Unicode. Only the * Punycoded parts of the domain name will be converted, i.e. it doesn't * matter if you call it on a string that has already been converted to * Unicode. * @memberOf punycode * @param {String} domain The Punycode domain name to convert to Unicode. * @returns {String} The Unicode representation of the given Punycode * string. */ function toUnicode(domain) { return mapDomain(domain, function(string) { return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string; }); } /** * Converts a Unicode string representing a domain name to Punycode. Only the * non-ASCII parts of the domain name will be converted, i.e. it doesn't * matter if you call it with a domain that's already in ASCII. * @memberOf punycode * @param {String} domain The domain name to convert, as a Unicode string. * @returns {String} The Punycode representation of the given domain name. */ function toASCII(domain) { return mapDomain(domain, function(string) { return regexNonASCII.test(string) ? 'xn--' + encode(string) : string; }); } /*--------------------------------------------------------------------------*/ /** Define the public API */ punycode = { /** * A string representing the current Punycode.js version number. * @memberOf punycode * @type String */ 'version': '1.2.3', /** * An object of methods to convert from JavaScript's internal character * representation (UCS-2) to Unicode code points, and back. * @see <http://mathiasbynens.be/notes/javascript-encoding> * @memberOf punycode * @type Object */ 'ucs2': { 'decode': ucs2decode, 'encode': ucs2encode }, 'decode': decode, 'encode': encode, 'toASCII': toASCII, 'toUnicode': toUnicode }; /** Expose `punycode` */ // Some AMD build optimizers, like r.js, check for specific condition patterns // like the following: if ( typeof define == 'function' && typeof define.amd == 'object' && define.amd ) { define(function() { return punycode; }); } else if (freeExports && !freeExports.nodeType) { if (freeModule) { // in Node.js or RingoJS v0.8.0+ freeModule.exports = punycode; } else { // in Narwhal or RingoJS v0.7.0- for (key in punycode) { punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); } } } else { // in Rhino or a web browser root.punycode = punycode; } }(this)); /*! * URI.js - Mutating URLs * * Version: 1.14.2 * * Author: Rodney Rehm * Web: http://medialize.github.io/URI.js/ * * Licensed under * MIT License http://www.opensource.org/licenses/mit-license * GPL v3 http://opensource.org/licenses/GPL-3.0 * */ (function (root, factory) { 'use strict'; // https://github.com/umdjs/umd/blob/master/returnExports.js if (typeof exports === 'object') { // Node module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['./punycode', './IPv6', './SecondLevelDomains'], factory); } else { // Browser globals (root is window) root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); } }(this, function (punycode, IPv6, SLD, root) { 'use strict'; /*global location, escape, unescape */ // FIXME: v2.0.0 renamce non-camelCase properties to uppercase /*jshint camelcase: false */ // save current URI variable, if any var _URI = root && root.URI; function URI(url, base) { // Allow instantiation without the 'new' keyword if (!(this instanceof URI)) { return new URI(url, base); } if (url === undefined) { if (arguments.length) { throw new TypeError('undefined is not a valid argument for URI'); } if (typeof location !== 'undefined') { url = location.href + ''; } else { url = ''; } } this.href(url); // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor if (base !== undefined) { return this.absoluteTo(base); } return this; } URI.version = '1.14.2'; var p = URI.prototype; var hasOwn = Object.prototype.hasOwnProperty; function escapeRegEx(string) { // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); } function getType(value) { // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value if (value === undefined) { return 'Undefined'; } return String(Object.prototype.toString.call(value)).slice(8, -1); } function isArray(obj) { return getType(obj) === 'Array'; } function filterArrayValues(data, value) { var lookup = {}; var i, length; if (isArray(value)) { for (i = 0, length = value.length; i < length; i++) { lookup[value[i]] = true; } } else { lookup[value] = true; } for (i = 0, length = data.length; i < length; i++) { if (lookup[data[i]] !== undefined) { data.splice(i, 1); length--; i--; } } return data; } function arrayContains(list, value) { var i, length; // value may be string, number, array, regexp if (isArray(value)) { // Note: this can be optimized to O(n) (instead of current O(m * n)) for (i = 0, length = value.length; i < length; i++) { if (!arrayContains(list, value[i])) { return false; } } return true; } var _type = getType(value); for (i = 0, length = list.length; i < length; i++) { if (_type === 'RegExp') { if (typeof list[i] === 'string' && list[i].match(value)) { return true; } } else if (list[i] === value) { return true; } } return false; } function arraysEqual(one, two) { if (!isArray(one) || !isArray(two)) { return false; } // arrays can't be equal if they have different amount of content if (one.length !== two.length) { return false; } one.sort(); two.sort(); for (var i = 0, l = one.length; i < l; i++) { if (one[i] !== two[i]) { return false; } } return true; } URI._parts = function() { return { protocol: null, username: null, password: null, hostname: null, urn: null, port: null, path: null, query: null, fragment: null, // state duplicateQueryParameters: URI.duplicateQueryParameters, escapeQuerySpace: URI.escapeQuerySpace }; }; // state: allow duplicate query parameters (a=1&a=1) URI.duplicateQueryParameters = false; // state: replaces + with %20 (space in query strings) URI.escapeQuerySpace = true; // static properties URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; URI.idn_expression = /[^a-z0-9\.-]/i; URI.punycode_expression = /(xn--)/i; // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; // credits to Rich Brown // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 // specification: http://www.ietf.org/rfc/rfc4291.txt URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; // expression used is "gruber revised" (@gruber v2) determined to be the // best solution in a regex-golf we did a couple of ages ago at // * http://mathiasbynens.be/demo/url-regex // * http://rodneyrehm.de/t/url-regex.html URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»]))/ig; URI.findUri = { // valid "scheme://" or "www." start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, // everything up to the next whitespace end: /[\s\r\n]|$/, // trim trailing punctuation captured by end RegExp trim: /[`!()\[\]{};:'".,<>?«»]+$/ }; // http://www.iana.org/assignments/uri-schemes.html // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports URI.defaultPorts = { http: '80', https: '443', ftp: '21', gopher: '70', ws: '80', wss: '443' }; // allowed hostname characters according to RFC 3986 // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/; // map DOM Elements to their URI attribute URI.domAttributes = { 'a': 'href', 'blockquote': 'cite', 'link': 'href', 'base': 'href', 'script': 'src', 'form': 'action', 'img': 'src', 'area': 'href', 'iframe': 'src', 'embed': 'src', 'source': 'src', 'track': 'src', 'input': 'src', // but only if type="image" 'audio': 'src', 'video': 'src' }; URI.getDomAttribute = function(node) { if (!node || !node.nodeName) { return undefined; } var nodeName = node.nodeName.toLowerCase(); // <input> should only expose src for type="image" if (nodeName === 'input' && node.type !== 'image') { return undefined; } return URI.domAttributes[nodeName]; }; function escapeForDumbFirefox36(value) { // https://github.com/medialize/URI.js/issues/91 return escape(value); } // encoding / decoding according to RFC3986 function strictEncodeURIComponent(string) { // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent return encodeURIComponent(string) .replace(/[!'()*]/g, escapeForDumbFirefox36) .replace(/\*/g, '%2A'); } // Drawn from https://www.ietf.org/rfc/rfc3987.txt function _isIriCodePoint(point) { // Unless otherwise noted, all these hex ranges are defined in RFC 3987 under the // ucschar portion of the grammar. return ( point === 0x00002d || point === 0x0002E || // -, . point === 0x00005F || point === 0x0007E || // _, ~ (point >= 0x000030 && point < 0x000040) || // ASCII Digits (point >= 0x000041 && point < 0x00005B) || // ASCII Uppercase (point >= 0x000061 && point < 0x00007B) || // ASCII Lowercase (point >= 0x0000A0 && point < 0x00D800) || (point >= 0x00E000 && point < 0x00F8FF) || // iprivate (point >= 0x00F900 && point < 0x00FDD0) || (point >= 0x00FDF0 && point < 0x00FFF0) || (point >= 0x010000 && point < 0x01FFFE) || (point >= 0x020000 && point < 0x02FFFE) || (point >= 0x030000 && point < 0x03FFFE) || (point >= 0x040000 && point < 0x04FFFE) || (point >= 0x050000 && point < 0x05FFFE) || (point >= 0x060000 && point < 0x06FFFE) || (point >= 0x070000 && point < 0x07FFFE) || (point >= 0x080000 && point < 0x08FFFE) || (point >= 0x090000 && point < 0x09FFFE) || (point >= 0x0A0000 && point < 0x0AFFFE) || (point >= 0x0B0000 && point < 0x0BFFFE) || (point >= 0x0C0000 && point < 0x0CFFFE) || (point >= 0x0D0000 && point < 0x0DFFFE) || (point >= 0x0E0000 && point < 0x0EFFFE) || (point >= 0x0F0000 && point < 0x0FFFFE) || // iprivate (point >= 0x100000 && point < 0x10FFFE) // iprivate ); } // IRIs are "unicode" (i.e., they use UTF-8 for %-encoding) URIs, but with a far greater range of // non-ASCII characters that are allowed. Note that ISO-8859 IRIs are an impossibility; ISO-8859 // cannot represent the full range of unicode characters. function encodeIRIComponent(string) { var inputCodePoints = punycode.ucs2.decode(string); var output = ''; for (var i = 0; i < inputCodePoints.length; i++) { var codePoint = inputCodePoints[i]; if (_isIriCodePoint(codePoint)) { output += punycode.ucs2.encode([codePoint]); } else { var asString = punycode.ucs2.encode([codePoint]); output += strictEncodeURIComponent(asString); } } return output; } function recodeIRIHostname(string) { if (URI.punycode_expression.test(string)) { string = punycode.toUnicode(string); } return encodeIRIComponent(string); } URI._defaultRecodeHostname = punycode ? punycode.toASCII : function(string) { return string; }; URI.iso8859 = function() { URI.recodeHostname = URI._defaultRecodeHostname; URI.encode = escape; URI.decode = unescape; }; URI.unicode = function() { URI.recodeHostname = URI._defaultRecodeHostname; URI.encode = strictEncodeURIComponent; URI.decode = decodeURIComponent; }; URI.iri = function() { URI.recodeHostname = recodeIRIHostname; URI.encode = encodeIRIComponent; URI.decode = decodeURIComponent; }; // By default, start in unicode mode. URI.unicode(); URI.characters = { pathname: { encode: { // RFC3986 2.1: For consistency, URI producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, map: { // -._~!'()* '%24': '$', '%26': '&', '%2B': '+', '%2C': ',', '%3B': ';', '%3D': '=', '%3A': ':', '%40': '@' } }, decode: { expression: /[\/\?#]/g, map: { '/': '%2F', '?': '%3F', '#': '%23' } } }, reserved: { encode: { // RFC3986 2.1: For consistency, URI producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, map: { // gen-delims '%3A': ':', '%2F': '/', '%3F': '?', '%23': '#', '%5B': '[', '%5D': ']', '%40': '@', // sub-delims '%21': '!', '%24': '$', '%26': '&', '%27': '\'', '%28': '(', '%29': ')', '%2A': '*', '%2B': '+', '%2C': ',', '%3B': ';', '%3D': '=' } } }, urnpath: { // The characters under `encode` are the characters called out by RFC 2141 as being acceptable // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also // note that the colon character is not featured in the encoding map; this is because URI.js // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it // should not appear unencoded in a segment itself. // See also the note above about RFC3986 and capitalalized hex digits. encode: { expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig, map: { '%21': '!', '%24': '$', '%27': '\'', '%28': '(', '%29': ')', '%2A': '*', '%2B': '+', '%2C': ',', '%3B': ';', '%3D': '=', '%40': '@' } }, // These characters are the characters called out by RFC2141 as "reserved" characters that // should never appear in a URN, plus the colon character (see note above). decode: { expression: /[\/\?#:]/g, map: { '/': '%2F', '?': '%3F', '#': '%23', ':': '%3A' } } } }; URI.encodeQuery = function(string, escapeQuerySpace) { var escaped = URI.encode(string + ''); if (escapeQuerySpace === undefined) { escapeQuerySpace = URI.escapeQuerySpace; } return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped; }; URI.decodeQuery = function(string, escapeQuerySpace) { string += ''; if (escapeQuerySpace === undefined) { escapeQuerySpace = URI.escapeQuerySpace; } try { return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); } catch(e) { // we're not going to mess with weird encodings, // give up and return the undecoded original string // see https://github.com/medialize/URI.js/issues/87 // see https://github.com/medialize/URI.js/issues/92 return string; } }; // generate encode/decode path functions var _parts = {'encode':'encode', 'decode':'decode'}; var _part; var generateAccessor = function(_group, _part) { return function(string) { try { return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) { return URI.characters[_group][_part].map[c]; }); } catch (e) { // we're not going to mess with weird encodings, // give up and return the undecoded original string // see https://github.com/medialize/URI.js/issues/87 // see https://github.com/medialize/URI.js/issues/92 return string; } }; }; for (_part in _parts) { URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]); URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]); } var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) { return function(string) { // Why pass in names of functions, rather than the function objects themselves? The // definitions of some functions (but in particular, URI.decode) will occasionally change due // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure // that the functions we use here are "fresh". var actualCodingFunc; if (!_innerCodingFuncName) { actualCodingFunc = URI[_codingFuncName]; } else { actualCodingFunc = function(string) { return URI[_codingFuncName](URI[_innerCodingFuncName](string)); }; } var segments = (string + '').split(_sep); for (var i = 0, length = segments.length; i < length; i++) { segments[i] = actualCodingFunc(segments[i]); } return segments.join(_sep); }; }; // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions. URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment'); URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment'); URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode'); URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode'); URI.encodeReserved = generateAccessor('reserved', 'encode'); URI.parse = function(string, parts) { var pos; if (!parts) { parts = {}; } // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] // extract fragment pos = string.indexOf('#'); if (pos > -1) { // escaping? parts.fragment = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract query pos = string.indexOf('?'); if (pos > -1) { // escaping? parts.query = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract protocol if (string.substring(0, 2) === '//') { // relative-scheme parts.protocol = null; string = string.substring(2); // extract "user:pass@host:port" string = URI.parseAuthority(string, parts); } else { pos = string.indexOf(':'); if (pos > -1) { parts.protocol = string.substring(0, pos) || null; if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { // : may be within the path parts.protocol = undefined; } else if (string.substring(pos + 1, pos + 3) === '//') { string = string.substring(pos + 3); // extract "user:pass@host:port" string = URI.parseAuthority(string, parts); } else { string = string.substring(pos + 1); parts.urn = true; } } } // what's left must be the path parts.path = string; // and we're done return parts; }; URI.parseHost = function(string, parts) { // extract host:port var pos = string.indexOf('/'); var bracketPos; var t; if (pos === -1) { pos = string.length; } if (string.charAt(0) === '[') { // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts // IPv6+port in the format [2001:db8::1]:80 (for the time being) bracketPos = string.indexOf(']'); parts.hostname = string.substring(1, bracketPos) || null; parts.port = string.substring(bracketPos + 2, pos) || null; if (parts.port === '/') { parts.port = null; } } else { var firstColon = string.indexOf(':'); var firstSlash = string.indexOf('/'); var nextColon = string.indexOf(':', firstColon + 1); if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) { // IPv6 host contains multiple colons - but no port // this notation is actually not allowed by RFC 3986, but we're a liberal parser parts.hostname = string.substring(0, pos) || null; parts.port = null; } else { t = string.substring(0, pos).split(':'); parts.hostname = t[0] || null; parts.port = t[1] || null; } } if (parts.hostname && string.substring(pos).charAt(0) !== '/') { pos++; string = '/' + string; } return string.substring(pos) || '/'; }; URI.parseAuthority = function(string, parts) { string = URI.parseUserinfo(string, parts); return URI.parseHost(string, parts); }; URI.parseUserinfo = function(string, parts) { // extract username:password var firstSlash = string.indexOf('/'); var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); var t; // authority@ must come before /path if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { t = string.substring(0, pos).split(':'); parts.username = t[0] ? URI.decode(t[0]) : null; t.shift(); parts.password = t[0] ? URI.decode(t.join(':')) : null; string = string.substring(pos + 1); } else { parts.username = null; parts.password = null; } return string; }; URI.parseQuery = function(string, escapeQuerySpace) { if (!string) { return {}; } // throw out the funky business - "?"[name"="value"&"]+ string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); if (!string) { return {}; } var items = {}; var splits = string.split('&'); var length = splits.length; var v, name, value; for (var i = 0; i < length; i++) { v = splits[i].split('='); name = URI.decodeQuery(v.shift(), escapeQuerySpace); // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; if (hasOwn.call(items, name)) { if (typeof items[name] === 'string') { items[name] = [items[name]]; } items[name].push(value); } else { items[name] = value; } } return items; }; URI.build = function(parts) { var t = ''; if (parts.protocol) { t += parts.protocol + ':'; } if (!parts.urn && (t || parts.hostname)) { t += '//'; } t += (URI.buildAuthority(parts) || ''); if (typeof parts.path === 'string') { if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') { t += '/'; } t += parts.path; } if (typeof parts.query === 'string' && parts.query) { t += '?' + parts.query; } if (typeof parts.fragment === 'string' && parts.fragment) { t += '#' + parts.fragment; } return t; }; URI.buildHost = function(parts) { var t = ''; if (!parts.hostname) { return ''; } else if (URI.ip6_expression.test(parts.hostname)) { t += '[' + parts.hostname + ']'; } else { t += parts.hostname; } if (parts.port) { t += ':' + parts.port; } return t; }; URI.buildAuthority = function(parts) { return URI.buildUserinfo(parts) + URI.buildHost(parts); }; URI.buildUserinfo = function(parts) { var t = ''; if (parts.username) { t += URI.encode(parts.username); if (parts.password) { t += ':' + URI.encode(parts.password); } t += '@'; } return t; }; URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! // URI.js treats the query string as being application/x-www-form-urlencoded // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type var t = ''; var unique, key, i, length; for (key in data) { if (hasOwn.call(data, key) && key) { if (isArray(data[key])) { unique = {}; for (i = 0, length = data[key].length; i < length; i++) { if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); if (duplicateQueryParameters !== true) { unique[data[key][i] + ''] = true; } } } } else if (data[key] !== undefined) { t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); } } } return t.substring(1); }; URI.buildQueryParameter = function(name, value, escapeQuerySpace) { // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : ''); }; URI.addQuery = function(data, name, value) { if (typeof name === 'object') { for (var key in name) { if (hasOwn.call(name, key)) { URI.addQuery(data, key, name[key]); } } } else if (typeof name === 'string') { if (data[name] === undefined) { data[name] = value; return; } else if (typeof data[name] === 'string') { data[name] = [data[name]]; } if (!isArray(value)) { value = [value]; } data[name] = (data[name] || []).concat(value); } else { throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); } }; URI.removeQuery = function(data, name, value) { var i, length, key; if (isArray(name)) { for (i = 0, length = name.length; i < length; i++) { data[name[i]] = undefined; } } else if (typeof name === 'object') { for (key in name) { if (hasOwn.call(name, key)) { URI.removeQuery(data, key, name[key]); } } } else if (typeof name === 'string') { if (value !== undefined) { if (data[name] === value) { data[name] = undefined; } else if (isArray(data[name])) { data[name] = filterArrayValues(data[name], value); } } else { data[name] = undefined; } } else { throw new TypeError('URI.removeQuery() accepts an object, string as the first parameter'); } }; URI.hasQuery = function(data, name, value, withinArray) { if (typeof name === 'object') { for (var key in name) { if (hasOwn.call(name, key)) { if (!URI.hasQuery(data, key, name[key])) { return false; } } } return true; } else if (typeof name !== 'string') { throw new TypeError('URI.hasQuery() accepts an object, string as the name parameter'); } switch (getType(value)) { case 'Undefined': // true if exists (but may be empty) return name in data; // data[name] !== undefined; case 'Boolean': // true if exists and non-empty var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); return value === _booly; case 'Function': // allow complex comparison return !!value(data[name], name, data); case 'Array': if (!isArray(data[name])) { return false; } var op = withinArray ? arrayContains : arraysEqual; return op(data[name], value); case 'RegExp': if (!isArray(data[name])) { return Boolean(data[name] && data[name].match(value)); } if (!withinArray) { return false; } return arrayContains(data[name], value); case 'Number': value = String(value); /* falls through */ case 'String': if (!isArray(data[name])) { return data[name] === value; } if (!withinArray) { return false; } return arrayContains(data[name], value); default: throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter'); } }; URI.commonPath = function(one, two) { var length = Math.min(one.length, two.length); var pos; // find first non-matching character for (pos = 0; pos < length; pos++) { if (one.charAt(pos) !== two.charAt(pos)) { pos--; break; } } if (pos < 1) { return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; } // revert to last / if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { pos = one.substring(0, pos).lastIndexOf('/'); } return one.substring(0, pos + 1); }; URI.withinString = function(string, callback, options) { options || (options = {}); var _start = options.start || URI.findUri.start; var _end = options.end || URI.findUri.end; var _trim = options.trim || URI.findUri.trim; var _attributeOpen = /[a-z0-9-]=["']?$/i; _start.lastIndex = 0; while (true) { var match = _start.exec(string); if (!match) { break; } var start = match.index; if (options.ignoreHtml) { // attribut(e=["']?$) var attributeOpen = string.slice(Math.max(start - 3, 0), start); if (attributeOpen && _attributeOpen.test(attributeOpen)) { continue; } } var end = start + string.slice(start).search(_end); var slice = string.slice(start, end).replace(_trim, ''); if (options.ignore && options.ignore.test(slice)) { continue; } end = start + slice.length; var result = callback(slice, start, end, string); string = string.slice(0, start) + result + string.slice(end); _start.lastIndex = start + result.length; } _start.lastIndex = 0; return string; }; URI.ensureValidHostname = function(v) { // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) // they are not part of DNS and therefore ignored by URI.js if (v.match(URI.invalid_hostname_characters)) { // test punycode if (!punycode) { throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available'); } if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); } } }; // noConflict URI.noConflict = function(removeAll) { if (removeAll) { var unconflicted = { URI: this.noConflict() }; if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') { unconflicted.URITemplate = root.URITemplate.noConflict(); } if (root.IPv6 && typeof root.IPv6.noConflict === 'function') { unconflicted.IPv6 = root.IPv6.noConflict(); } if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') { unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict(); } return unconflicted; } else if (root.URI === this) { root.URI = _URI; } return this; }; p.build = function(deferBuild) { if (deferBuild === true) { this._deferred_build = true; } else if (deferBuild === undefined || this._deferred_build) { this._string = URI.build(this._parts); this._deferred_build = false; } return this; }; p.clone = function() { return new URI(this); }; p.valueOf = p.toString = function() { return this.build(false)._string; }; function generateSimpleAccessor(_part){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ''; } else { this._parts[_part] = v || null; this.build(!build); return this; } }; } function generatePrefixAccessor(_part, _key){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ''; } else { if (v !== null) { v = v + ''; if (v.charAt(0) === _key) { v = v.substring(1); } } this._parts[_part] = v; this.build(!build); return this; } }; } p.protocol = generateSimpleAccessor('protocol'); p.username = generateSimpleAccessor('username'); p.password = generateSimpleAccessor('password'); p.hostname = generateSimpleAccessor('hostname'); p.port = generateSimpleAccessor('port'); p.query = generatePrefixAccessor('query', '?'); p.fragment = generatePrefixAccessor('fragment', '#'); p.search = function(v, build) { var t = this.query(v, build); return typeof t === 'string' && t.length ? ('?' + t) : t; }; p.hash = function(v, build) { var t = this.fragment(v, build); return typeof t === 'string' && t.length ? ('#' + t) : t; }; p.pathname = function(v, build) { if (v === undefined || v === true) { var res = this._parts.path || (this._parts.hostname ? '/' : ''); return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res; } else { if (this._parts.urn) { this._parts.path = v ? URI.recodeUrnPath(v) : ''; } else { this._parts.path = v ? URI.recodePath(v) : '/'; } this.build(!build); return this; } }; p.path = p.pathname; p.href = function(href, build) { var key; if (href === undefined) { return this.toString(); } this._string = ''; this._parts = URI._parts(); var _URI = href instanceof URI; var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); if (href.nodeName) { var attribute = URI.getDomAttribute(href); href = href[attribute] || ''; _object = false; } // window.location is reported to be an object, but it's not the sort // of object we're looking for: // * location.protocol ends with a colon // * location.query != object.search // * location.hash != object.fragment // simply serializing the unknown object should do the trick // (for location, not for everything...) if (!_URI && _object && href.pathname !== undefined) { href = href.toString(); } if (typeof href === 'string' || href instanceof String) { this._parts = URI.parse(String(href), this._parts); } else if (_URI || _object) { var src = _URI ? href._parts : href; for (key in src) { if (hasOwn.call(this._parts, key)) { this._parts[key] = src[key]; } } } else { throw new TypeError('invalid input'); } this.build(!build); return this; }; // identification accessors p.is = function(what) { var ip = false; var ip4 = false; var ip6 = false; var name = false; var sld = false; var idn = false; var punycode = false; var relative = !this._parts.urn; if (this._parts.hostname) { relative = false; ip4 = URI.ip4_expression.test(this._parts.hostname); ip6 = URI.ip6_expression.test(this._parts.hostname); ip = ip4 || ip6; name = !ip; sld = name && SLD && SLD.has(this._parts.hostname); idn = name && URI.idn_expression.test(this._parts.hostname); punycode = name && URI.punycode_expression.test(this._parts.hostname); } switch (what.toLowerCase()) { case 'relativ