UNPKG

authorify

Version:

Authorization and authentication system for REST server

1,866 lines (1,711 loc) 1.12 MB
(function(root, factory) { if(typeof define === 'function' && define.amd) { define([], factory); } else { root.forge = factory(); } })(this, function() { /** * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/almond for details */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. /*jslint sloppy: true */ /*global setTimeout: false */ var requirejs, require, define; (function (undef) { var main, req, makeMap, handlers, defined = {}, waiting = {}, config = {}, defining = {}, hasOwn = Object.prototype.hasOwnProperty, aps = [].slice, jsSuffixRegExp = /\.js$/; function hasProp(obj, prop) { return hasOwn.call(obj, prop); } /** * Given a relative module name, like ./something, normalize it to * a real name that can be mapped to a path. * @param {String} name the relative name * @param {String} baseName a real name that the name arg is relative * to. * @returns {String} normalized name */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, foundI, foundStarMap, starI, i, j, part, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. if (name && name.charAt(0) === ".") { //If have a base name, try to normalize against it, //otherwise, assume it is a top-level require that will //be relative to baseUrl in the end. if (baseName) { //Convert baseName to array, and lop off the last part, //so that . matches that "directory" and not name of the baseName's //module. For instance, baseName of "one/two/three", maps to //"one/two/three.js", but we want the directory, "one/two" for //this normalization. baseParts = baseParts.slice(0, baseParts.length - 1); name = name.split('/'); lastIndex = name.length - 1; // Node .js allowance: if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); } name = baseParts.concat(name); //start trimDots for (i = 0; i < name.length; i += 1) { part = name[i]; if (part === ".") { name.splice(i, 1); i -= 1; } else if (part === "..") { if (i === 1 && (name[2] === '..' || name[0] === '..')) { //End of the line. Keep at least one non-dot //path segment at the front so it can be mapped //correctly to disk. Otherwise, there is likely //no path mapping for a path starting with '..'. //This can still fail, but catches the most reasonable //uses of .. break; } else if (i > 0) { name.splice(i - 1, 2); i -= 2; } } } //end trimDots name = name.join("/"); } else if (name.indexOf('./') === 0) { // No baseName, so this is ID is resolved relative // to baseUrl, pull off the leading dot. name = name.substring(2); } } //Apply map config if available. if ((baseParts || starMap) && map) { nameParts = name.split('/'); for (i = nameParts.length; i > 0; i -= 1) { nameSegment = nameParts.slice(0, i).join("/"); if (baseParts) { //Find the longest baseName segment match in the config. //So, do joins on the biggest to smallest lengths of baseParts. for (j = baseParts.length; j > 0; j -= 1) { mapValue = map[baseParts.slice(0, j).join('/')]; //baseName segment has config, find if it has one for //this name. if (mapValue) { mapValue = mapValue[nameSegment]; if (mapValue) { //Match, update name to the new value. foundMap = mapValue; foundI = i; break; } } } } if (foundMap) { break; } //Check for a star map match, but just hold on to it, //if there is a shorter segment match later in a matching //config, then favor over this star map. if (!foundStarMap && starMap && starMap[nameSegment]) { foundStarMap = starMap[nameSegment]; starI = i; } } if (!foundMap && foundStarMap) { foundMap = foundStarMap; foundI = starI; } if (foundMap) { nameParts.splice(0, foundI, foundMap); name = nameParts.join('/'); } } return name; } function makeRequire(relName, forceSync) { return function () { //A version of a require function that passes a moduleName //value for items that may need to //look up paths relative to the moduleName return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); }; } function makeNormalize(relName) { return function (name) { return normalize(name, relName); }; } function makeLoad(depName) { return function (value) { defined[depName] = value; }; } function callDep(name) { if (hasProp(waiting, name)) { var args = waiting[name]; delete waiting[name]; defining[name] = true; main.apply(undef, args); } if (!hasProp(defined, name) && !hasProp(defining, name)) { throw new Error('No ' + name); } return defined[name]; } //Turns a plugin!resource to [plugin, resource] //with the plugin being undefined if the name //did not have a plugin prefix. function splitPrefix(name) { var prefix, index = name ? name.indexOf('!') : -1; if (index > -1) { prefix = name.substring(0, index); name = name.substring(index + 1, name.length); } return [prefix, name]; } /** * Makes a name map, normalizing the name, and using a plugin * for normalization if necessary. Grabs a ref to plugin * too, as an optimization. */ makeMap = function (name, relName) { var plugin, parts = splitPrefix(name), prefix = parts[0]; name = parts[1]; if (prefix) { prefix = normalize(prefix, relName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { name = plugin.normalize(name, makeNormalize(relName)); } else { name = normalize(name, relName); } } else { name = normalize(name, relName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; if (prefix) { plugin = callDep(prefix); } } //Using ridiculous property names for space reasons return { f: prefix ? prefix + '!' + name : name, //fullName n: name, pr: prefix, p: plugin }; }; function makeConfig(name) { return function () { return (config && config.config && config.config[name]) || {}; }; } handlers = { require: function (name) { return makeRequire(name); }, exports: function (name) { var e = defined[name]; if (typeof e !== 'undefined') { return e; } else { return (defined[name] = {}); } }, module: function (name) { return { id: name, uri: '', exports: defined[name], config: makeConfig(name) }; } }; main = function (name, deps, callback, relName) { var cjsModule, depName, ret, map, i, args = [], callbackType = typeof callback, usingExports; //Use name if no relName relName = relName || name; //Call the callback to define the module, if necessary. if (callbackType === 'undefined' || callbackType === 'function') { //Pull out the defined dependencies and pass the ordered //values to the callback. //Default to [require, exports, module] if no deps deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; for (i = 0; i < deps.length; i += 1) { map = makeMap(deps[i], relName); depName = map.f; //Fast path CommonJS standard dependencies. if (depName === "require") { args[i] = handlers.require(name); } else if (depName === "exports") { //CommonJS module spec 1.1 args[i] = handlers.exports(name); usingExports = true; } else if (depName === "module") { //CommonJS module spec 1.1 cjsModule = args[i] = handlers.module(name); } else if (hasProp(defined, depName) || hasProp(waiting, depName) || hasProp(defining, depName)) { args[i] = callDep(depName); } else if (map.p) { map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); args[i] = defined[depName]; } else { throw new Error(name + ' missing ' + depName); } } ret = callback ? callback.apply(defined[name], args) : undefined; if (name) { //If setting exports via "module" is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. if (cjsModule && cjsModule.exports !== undef && cjsModule.exports !== defined[name]) { defined[name] = cjsModule.exports; } else if (ret !== undef || !usingExports) { //Use the return value from the function. defined[name] = ret; } } } else if (name) { //May just be an object definition for the module. Only //worry about defining if have a module name. defined[name] = callback; } }; requirejs = require = req = function (deps, callback, relName, forceSync, alt) { if (typeof deps === "string") { if (handlers[deps]) { //callback in this case is really relName return handlers[deps](callback); } //Just return the module wanted. In this scenario, the //deps arg is the module name, and second arg (if passed) //is just the relName. //Normalize module name, if it contains . or .. return callDep(makeMap(deps, callback).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; if (config.deps) { req(config.deps, config.callback); } if (!callback) { return; } if (callback.splice) { //callback is an array, which means it is a dependency list. //Adjust args if there are dependencies deps = callback; callback = relName; relName = null; } else { deps = undef; } } //Support require(['a']) callback = callback || function () {}; //If relName is a function, it is an errback handler, //so remove it. if (typeof relName === 'function') { relName = forceSync; forceSync = alt; } //Simulate async callback; if (forceSync) { main(undef, deps, callback, relName); } else { //Using a non-zero value because of concern for what old browsers //do, and latest browsers "upgrade" to 4 if lower value is used: //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: //If want a value immediately, use require('id') instead -- something //that works in almond on the global level, but not guaranteed and //unlikely to work in other AMD implementations. setTimeout(function () { main(undef, deps, callback, relName); }, 4); } return req; }; /** * Just drops the config on the floor, but returns req in case * the config return value is used. */ req.config = function (cfg) { return req(cfg); }; /** * Expose module registry for debugging and tooling */ requirejs._defined = defined; define = function (name, deps, callback) { //This module may not have dependencies if (!deps.splice) { //deps is not an array, so probably means //an object literal or factory function for //the value. Adjust args. callback = deps; deps = []; } if (!hasProp(defined, name) && !hasProp(waiting, name)) { waiting[name] = [name, deps, callback]; } }; define.amd = { jQuery: true }; }()); define("node_modules/almond/almond", function(){}); /** * Utility functions for web applications. * * @author Dave Longley * * Copyright (c) 2010-2014 Digital Bazaar, Inc. */ (function() { /* ########## Begin module implementation ########## */ function initModule(forge) { /* Utilities API */ var util = forge.util = forge.util || {}; // define setImmediate and nextTick if(typeof process === 'undefined' || !process.nextTick) { if(typeof setImmediate === 'function') { util.setImmediate = setImmediate; util.nextTick = function(callback) { return setImmediate(callback); }; } else { util.setImmediate = function(callback) { setTimeout(callback, 0); }; util.nextTick = util.setImmediate; } } else { util.nextTick = process.nextTick; if(typeof setImmediate === 'function') { util.setImmediate = setImmediate; } else { util.setImmediate = util.nextTick; } } // define isArray util.isArray = Array.isArray || function(x) { return Object.prototype.toString.call(x) === '[object Array]'; }; // define isArrayBuffer util.isArrayBuffer = function(x) { return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer; }; // define isArrayBufferView var _arrayBufferViews = []; if(typeof Int8Array !== 'undefined') { _arrayBufferViews.push(Int8Array); } if(typeof Uint8Array !== 'undefined') { _arrayBufferViews.push(Uint8Array); } if(typeof Uint8ClampedArray !== 'undefined') { _arrayBufferViews.push(Uint8ClampedArray); } if(typeof Int16Array !== 'undefined') { _arrayBufferViews.push(Int16Array); } if(typeof Uint16Array !== 'undefined') { _arrayBufferViews.push(Uint16Array); } if(typeof Int32Array !== 'undefined') { _arrayBufferViews.push(Int32Array); } if(typeof Uint32Array !== 'undefined') { _arrayBufferViews.push(Uint32Array); } if(typeof Float32Array !== 'undefined') { _arrayBufferViews.push(Float32Array); } if(typeof Float64Array !== 'undefined') { _arrayBufferViews.push(Float64Array); } util.isArrayBufferView = function(x) { for(var i = 0; i < _arrayBufferViews.length; ++i) { if(x instanceof _arrayBufferViews[i]) { return true; } } return false; }; /** * Constructor for a byte buffer. * * @param [b] the bytes to wrap (either encoded as string, one byte per * character, or as an ArrayBuffer or Typed Array). */ util.ByteBuffer = function(b) { // the data in this buffer this.data = ''; // the pointer for reading from this buffer this.read = 0; if(typeof b === 'string') { this.data = b; } else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) { // convert native buffer to forge buffer // FIXME: support native buffers internally instead var arr = new Uint8Array(b); try { this.data = String.fromCharCode.apply(null, arr); } catch(e) { for(var i = 0; i < arr.length; ++i) { this.putByte(arr[i]); } } } else if(b instanceof util.ByteBuffer) { // copy existing buffer this.data = b.data; this.read = b.read; } else if(typeof b === 'object' && 'data' in b && 'read' in b && typeof b.data === 'string' && typeof b.read === 'number') { // copy existing buffer w/matching interface this.data = b.data; this.read = b.read; } }; /** * Gets the number of bytes in this buffer. * * @return the number of bytes in this buffer. */ util.ByteBuffer.prototype.length = function() { return this.data.length - this.read; }; /** * Gets whether or not this buffer is empty. * * @return true if this buffer is empty, false if not. */ util.ByteBuffer.prototype.isEmpty = function() { return this.length() <= 0; }; /** * Puts a byte in this buffer. * * @param b the byte to put. * * @return this buffer. */ util.ByteBuffer.prototype.putByte = function(b) { this.data += String.fromCharCode(b); return this; }; /** * Puts a byte in this buffer N times. * * @param b the byte to put. * @param n the number of bytes of value b to put. * * @return this buffer. */ util.ByteBuffer.prototype.fillWithByte = function(b, n) { b = String.fromCharCode(b); var d = this.data; while(n > 0) { if(n & 1) { d += b; } n >>>= 1; if(n > 0) { b += b; } } this.data = d; return this; }; /** * Puts bytes in this buffer. * * @param bytes the bytes (as a UTF-8 encoded string) to put. * * @return this buffer. */ util.ByteBuffer.prototype.putBytes = function(bytes) { this.data += bytes; return this; }; /** * Puts a UTF-16 encoded string into this buffer. * * @param str the string to put. * * @return this buffer. */ util.ByteBuffer.prototype.putString = function(str) { this.data += util.encodeUtf8(str); return this; }; /** * Puts a 16-bit integer in this buffer in big-endian order. * * @param i the 16-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt16 = function(i) { this.data += String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i & 0xFF); return this; }; /** * Puts a 24-bit integer in this buffer in big-endian order. * * @param i the 24-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt24 = function(i) { this.data += String.fromCharCode(i >> 16 & 0xFF) + String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i & 0xFF); return this; }; /** * Puts a 32-bit integer in this buffer in big-endian order. * * @param i the 32-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt32 = function(i) { this.data += String.fromCharCode(i >> 24 & 0xFF) + String.fromCharCode(i >> 16 & 0xFF) + String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i & 0xFF); return this; }; /** * Puts a 16-bit integer in this buffer in little-endian order. * * @param i the 16-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt16Le = function(i) { this.data += String.fromCharCode(i & 0xFF) + String.fromCharCode(i >> 8 & 0xFF); return this; }; /** * Puts a 24-bit integer in this buffer in little-endian order. * * @param i the 24-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt24Le = function(i) { this.data += String.fromCharCode(i & 0xFF) + String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i >> 16 & 0xFF); return this; }; /** * Puts a 32-bit integer in this buffer in little-endian order. * * @param i the 32-bit integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt32Le = function(i) { this.data += String.fromCharCode(i & 0xFF) + String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i >> 16 & 0xFF) + String.fromCharCode(i >> 24 & 0xFF); return this; }; /** * Puts an n-bit integer in this buffer in big-endian order. * * @param i the n-bit integer. * @param n the number of bits in the integer. * * @return this buffer. */ util.ByteBuffer.prototype.putInt = function(i, n) { do { n -= 8; this.data += String.fromCharCode((i >> n) & 0xFF); } while(n > 0); return this; }; /** * Puts a signed n-bit integer in this buffer in big-endian order. Two's * complement representation is used. * * @param i the n-bit integer. * @param n the number of bits in the integer. * * @return this buffer. */ util.ByteBuffer.prototype.putSignedInt = function(i, n) { if(i < 0) { i += 2 << (n - 1); } return this.putInt(i, n); }; /** * Puts the given buffer into this buffer. * * @param buffer the buffer to put into this one. * * @return this buffer. */ util.ByteBuffer.prototype.putBuffer = function(buffer) { this.data += buffer.getBytes(); return this; }; /** * Gets a byte from this buffer and advances the read pointer by 1. * * @return the byte. */ util.ByteBuffer.prototype.getByte = function() { return this.data.charCodeAt(this.read++); }; /** * Gets a uint16 from this buffer in big-endian order and advances the read * pointer by 2. * * @return the uint16. */ util.ByteBuffer.prototype.getInt16 = function() { var rval = ( this.data.charCodeAt(this.read) << 8 ^ this.data.charCodeAt(this.read + 1)); this.read += 2; return rval; }; /** * Gets a uint24 from this buffer in big-endian order and advances the read * pointer by 3. * * @return the uint24. */ util.ByteBuffer.prototype.getInt24 = function() { var rval = ( this.data.charCodeAt(this.read) << 16 ^ this.data.charCodeAt(this.read + 1) << 8 ^ this.data.charCodeAt(this.read + 2)); this.read += 3; return rval; }; /** * Gets a uint32 from this buffer in big-endian order and advances the read * pointer by 4. * * @return the word. */ util.ByteBuffer.prototype.getInt32 = function() { var rval = ( this.data.charCodeAt(this.read) << 24 ^ this.data.charCodeAt(this.read + 1) << 16 ^ this.data.charCodeAt(this.read + 2) << 8 ^ this.data.charCodeAt(this.read + 3)); this.read += 4; return rval; }; /** * Gets a uint16 from this buffer in little-endian order and advances the read * pointer by 2. * * @return the uint16. */ util.ByteBuffer.prototype.getInt16Le = function() { var rval = ( this.data.charCodeAt(this.read) ^ this.data.charCodeAt(this.read + 1) << 8); this.read += 2; return rval; }; /** * Gets a uint24 from this buffer in little-endian order and advances the read * pointer by 3. * * @return the uint24. */ util.ByteBuffer.prototype.getInt24Le = function() { var rval = ( this.data.charCodeAt(this.read) ^ this.data.charCodeAt(this.read + 1) << 8 ^ this.data.charCodeAt(this.read + 2) << 16); this.read += 3; return rval; }; /** * Gets a uint32 from this buffer in little-endian order and advances the read * pointer by 4. * * @return the word. */ util.ByteBuffer.prototype.getInt32Le = function() { var rval = ( this.data.charCodeAt(this.read) ^ this.data.charCodeAt(this.read + 1) << 8 ^ this.data.charCodeAt(this.read + 2) << 16 ^ this.data.charCodeAt(this.read + 3) << 24); this.read += 4; return rval; }; /** * Gets an n-bit integer from this buffer in big-endian order and advances the * read pointer by n/8. * * @param n the number of bits in the integer. * * @return the integer. */ util.ByteBuffer.prototype.getInt = function(n) { var rval = 0; do { rval = (rval << 8) + this.data.charCodeAt(this.read++); n -= 8; } while(n > 0); return rval; }; /** * Gets a signed n-bit integer from this buffer in big-endian order, using * two's complement, and advances the read pointer by n/8. * * @param n the number of bits in the integer. * * @return the integer. */ util.ByteBuffer.prototype.getSignedInt = function(n) { var x = this.getInt(n); var max = 2 << (n - 2); if(x >= max) { x -= max << 1; } return x; }; /** * Reads bytes out into a UTF-8 string and clears them from the buffer. * * @param count the number of bytes to read, undefined or null for all. * * @return a UTF-8 string of bytes. */ util.ByteBuffer.prototype.getBytes = function(count) { var rval; if(count) { // read count bytes count = Math.min(this.length(), count); rval = this.data.slice(this.read, this.read + count); this.read += count; } else if(count === 0) { rval = ''; } else { // read all bytes, optimize to only copy when needed rval = (this.read === 0) ? this.data : this.data.slice(this.read); this.clear(); } return rval; }; /** * Gets a UTF-8 encoded string of the bytes from this buffer without modifying * the read pointer. * * @param count the number of bytes to get, omit to get all. * * @return a string full of UTF-8 encoded characters. */ util.ByteBuffer.prototype.bytes = function(count) { return (typeof(count) === 'undefined' ? this.data.slice(this.read) : this.data.slice(this.read, this.read + count)); }; /** * Gets a byte at the given index without modifying the read pointer. * * @param i the byte index. * * @return the byte. */ util.ByteBuffer.prototype.at = function(i) { return this.data.charCodeAt(this.read + i); }; /** * Puts a byte at the given index without modifying the read pointer. * * @param i the byte index. * @param b the byte to put. * * @return this buffer. */ util.ByteBuffer.prototype.setAt = function(i, b) { this.data = this.data.substr(0, this.read + i) + String.fromCharCode(b) + this.data.substr(this.read + i + 1); return this; }; /** * Gets the last byte without modifying the read pointer. * * @return the last byte. */ util.ByteBuffer.prototype.last = function() { return this.data.charCodeAt(this.data.length - 1); }; /** * Creates a copy of this buffer. * * @return the copy. */ util.ByteBuffer.prototype.copy = function() { var c = util.createBuffer(this.data); c.read = this.read; return c; }; /** * Compacts this buffer. * * @return this buffer. */ util.ByteBuffer.prototype.compact = function() { if(this.read > 0) { this.data = this.data.slice(this.read); this.read = 0; } return this; }; /** * Clears this buffer. * * @return this buffer. */ util.ByteBuffer.prototype.clear = function() { this.data = ''; this.read = 0; return this; }; /** * Shortens this buffer by triming bytes off of the end of this buffer. * * @param count the number of bytes to trim off. * * @return this buffer. */ util.ByteBuffer.prototype.truncate = function(count) { var len = Math.max(0, this.length() - count); this.data = this.data.substr(this.read, len); this.read = 0; return this; }; /** * Converts this buffer to a hexadecimal string. * * @return a hexadecimal string. */ util.ByteBuffer.prototype.toHex = function() { var rval = ''; for(var i = this.read; i < this.data.length; ++i) { var b = this.data.charCodeAt(i); if(b < 16) { rval += '0'; } rval += b.toString(16); } return rval; }; /** * Converts this buffer to a UTF-16 string (standard JavaScript string). * * @return a UTF-16 string. */ util.ByteBuffer.prototype.toString = function() { return util.decodeUtf8(this.bytes()); }; /** * Creates a buffer that stores bytes. A value may be given to put into the * buffer that is either a string of bytes or a UTF-16 string that will * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding). * * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode * as UTF-8. * @param [encoding] (default: 'raw', other: 'utf8'). */ util.createBuffer = function(input, encoding) { encoding = encoding || 'raw'; if(input !== undefined && encoding === 'utf8') { input = util.encodeUtf8(input); } return new util.ByteBuffer(input); }; /** * Fills a string with a particular value. If you want the string to be a byte * string, pass in String.fromCharCode(theByte). * * @param c the character to fill the string with, use String.fromCharCode * to fill the string with a byte value. * @param n the number of characters of value c to fill with. * * @return the filled string. */ util.fillString = function(c, n) { var s = ''; while(n > 0) { if(n & 1) { s += c; } n >>>= 1; if(n > 0) { c += c; } } return s; }; /** * Performs a per byte XOR between two byte strings and returns the result as a * string of bytes. * * @param s1 first string of bytes. * @param s2 second string of bytes. * @param n the number of bytes to XOR. * * @return the XOR'd result. */ util.xorBytes = function(s1, s2, n) { var s3 = ''; var b = ''; var t = ''; var i = 0; var c = 0; for(; n > 0; --n, ++i) { b = s1.charCodeAt(i) ^ s2.charCodeAt(i); if(c >= 10) { s3 += t; t = ''; c = 0; } t += String.fromCharCode(b); ++c; } s3 += t; return s3; }; /** * Converts a hex string into a UTF-8 string of bytes. * * @param hex the hexadecimal string to convert. * * @return the string of bytes. */ util.hexToBytes = function(hex) { var rval = ''; var i = 0; if(hex.length & 1 == 1) { // odd number of characters, convert first character alone i = 1; rval += String.fromCharCode(parseInt(hex[0], 16)); } // convert 2 characters (1 byte) at a time for(; i < hex.length; i += 2) { rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); } return rval; }; /** * Converts a UTF-8 byte string into a string of hexadecimal characters. * * @param bytes the byte string to convert. * * @return the string of hexadecimal characters. */ util.bytesToHex = function(bytes) { return util.createBuffer(bytes).toHex(); }; /** * Converts an 32-bit integer to 4-big-endian byte string. * * @param i the integer. * * @return the byte string. */ util.int32ToBytes = function(i) { return ( String.fromCharCode(i >> 24 & 0xFF) + String.fromCharCode(i >> 16 & 0xFF) + String.fromCharCode(i >> 8 & 0xFF) + String.fromCharCode(i & 0xFF)); }; // base64 characters, reverse mapping var _base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var _base64Idx = [ /*43 -43 = 0*/ /*'+', 1, 2, 3,'/' */ 62, -1, -1, -1, 63, /*'0','1','2','3','4','5','6','7','8','9' */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /*15, 16, 17,'=', 19, 20, 21 */ -1, -1, -1, 64, -1, -1, -1, /*65 - 43 = 22*/ /*'A','B','C','D','E','F','G','H','I','J','K','L','M', */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, /*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /*91 - 43 = 48 */ /*48, 49, 50, 51, 52, 53 */ -1, -1, -1, -1, -1, -1, /*97 - 43 = 54*/ /*'a','b','c','d','e','f','g','h','i','j','k','l','m' */ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, /*'n','o','p','q','r','s','t','u','v','w','x','y','z' */ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ]; /** * Base64 encodes a UTF-8 string of bytes. * * @param input the UTF-8 string of bytes to encode. * @param maxline the maximum number of encoded bytes per line to use, * defaults to none. * * @return the base64-encoded output. */ util.encode64 = function(input, maxline) { var line = ''; var output = ''; var chr1, chr2, chr3; var i = 0; while(i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); // encode 4 character group line += _base64.charAt(chr1 >> 2); line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4)); if(isNaN(chr2)) { line += '=='; } else { line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6)); line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63); } if(maxline && line.length > maxline) { output += line.substr(0, maxline) + '\r\n'; line = line.substr(maxline); } } output += line; return output; }; /** * Base64 decodes a string into a UTF-8 string of bytes. * * @param input the base64-encoded input. * * @return the raw bytes. */ util.decode64 = function(input) { // remove all non-base64 characters input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); var output = ''; var enc1, enc2, enc3, enc4; var i = 0; while(i < input.length) { enc1 = _base64Idx[input.charCodeAt(i++) - 43]; enc2 = _base64Idx[input.charCodeAt(i++) - 43]; enc3 = _base64Idx[input.charCodeAt(i++) - 43]; enc4 = _base64Idx[input.charCodeAt(i++) - 43]; output += String.fromCharCode((enc1 << 2) | (enc2 >> 4)); if(enc3 !== 64) { // decoded at least 2 bytes output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2)); if(enc4 !== 64) { // decoded 3 bytes output += String.fromCharCode(((enc3 & 3) << 6) | enc4); } } } return output; }; /** * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript * string). Non-ASCII characters will be encoded as multiple bytes according * to UTF-8. * * @param str the string to encode. * * @return the UTF-8 encoded string. */ util.encodeUtf8 = function(str) { return unescape(encodeURIComponent(str)); }; /** * Decodes a UTF-8 encoded string into a UTF-16 string. * * @param str the string to encode. * * @return the UTF-16 encoded string (standard JavaScript string). */ util.decodeUtf8 = function(str) { return decodeURIComponent(escape(str)); }; /** * Deflates the given data using a flash interface. * * @param api the flash interface. * @param bytes the data. * @param raw true to return only raw deflate data, false to include zlib * header and trailer. * * @return the deflated data as a string. */ util.deflate = function(api, bytes, raw) { bytes = util.decode64(api.deflate(util.encode64(bytes)).rval); // strip zlib header and trailer if necessary if(raw) { // zlib header is 2 bytes (CMF,FLG) where FLG indicates that // there is a 4-byte DICT (alder-32) block before the data if // its 5th bit is set var start = 2; var flg = bytes.charCodeAt(1); if(flg & 0x20) { start = 6; } // zlib trailer is 4 bytes of adler-32 bytes = bytes.substring(start, bytes.length - 4); } return bytes; }; /** * Inflates the given data using a flash interface. * * @param api the flash interface. * @param bytes the data. * @param raw true if the incoming data has no zlib header or trailer and is * raw DEFLATE data. * * @return the inflated data as a string, null on error. */ util.inflate = function(api, bytes, raw) { // TODO: add zlib header and trailer if necessary/possible var rval = api.inflate(util.encode64(bytes)).rval; return (rval === null) ? null : util.decode64(rval); }; /** * Sets a storage object. * * @param api the storage interface. * @param id the storage ID to use. * @param obj the storage object, null to remove. */ var _setStorageObject = function(api, id, obj) { if(!api) { throw new Error('WebStorage not available.'); } var rval; if(obj === null) { rval = api.removeItem(id); } else { // json-encode and base64-encode object obj = util.encode64(JSON.stringify(obj)); rval = api.setItem(id, obj); } // handle potential flash error if(typeof(rval) !== 'undefined' && rval.rval !== true) { throw rval.error; } }; /** * Gets a storage object. * * @param api the storage interface. * @param id the storage ID to use. * * @return the storage object entry or null if none exists. */ var _getStorageObject = function(api, id) { if(!api) { throw new Error('WebStorage not available.'); } // get the existing entry var rval = api.getItem(id); /* Note: We check api.init because we can't do (api == localStorage) on IE because of "Class doesn't support Automation" exception. Only the flash api has an init method so this works too, but we need a better solution in the future. */ // flash returns item wrapped in an object, handle special case if(api.init) { if(rval.rval === null) { if(rval.error) { throw rval.error; } // no error, but also no item rval = null; } else { rval = rval.rval; } } // handle decoding if(rval !== null) { // base64-decode and json-decode data rval = JSON.parse(util.decode64(rval)); } return rval; }; /** * Stores an item in local storage. * * @param api the storage interface. * @param id the storage ID to use. * @param key the key for the item. * @param data the data for the item (any javascript object/primitive). */ var _setItem = function(api, id, key, data) { // get storage object var obj = _getStorageObject(api, id); if(obj === null) { // create a new storage object obj = {}; } // update key obj[key] = data; // set storage object _setStorageObject(api, id, obj); }; /** * Gets an item from local storage. * * @param api the storage interface. * @param id the storage ID to use. * @param key the key for the item. * * @return the item. */ var _getItem = function(api, id, key) { // get storage object var rval = _getStorageObject(api, id); if(rval !== null) { // return data at key rval = (key in rval) ? rval[key] : null; } return rval; }; /** * Removes an item from local storage. * * @param api the storage interface. * @param id the storage ID to use. * @param key the key for the item. */ var _removeItem = function(api, id, key) { // get storage object var obj = _getStorageObject(api, id); if(obj !== null && key in obj) { // remove key delete obj[key]; // see if entry has no keys remaining var empty = true; for(var prop in obj) { empty = false; break; } if(empty) { // remove entry entirely if no keys are left obj = null; } // set storage object _setStorageObject(api, id, obj); } }; /** * Clears the local disk storage identified by the given ID. * * @param api the storage interface. * @param id the storage ID to use. */ var _clearItems = function(api, id) { _setStorageObject(api, id, null); }; /** * Calls a storage function. * * @param func the function to call. * @param args the arguments for the function. * @param location the location argument. * * @return the return value from the function. */ var _callStorageFunction = function(func, args, location) { var rval = null; // default storage types if(typeof(location) === 'undefined') { location = ['web', 'flash']; } // apply storage types in order of preference var type; var done = false; var exception = null; for(var idx in location) { type = location[idx]; try { if(type === 'flash' || type === 'both') { if(args[0] === null) { throw new Error('Flash local storage not available.'); } else { rval = func.apply(this, args); done = (type === 'flash'); } } if(type === 'web' || type === 'both') { args[0] = localStorage; rval = func.apply(this, args); done = true; } } catch(ex) { exception = ex; } if(done) { break; } } if(!done) { throw exception; } return rval; }; /** * Stores an item on local disk. * * The available types of local storage include 'flash', 'web', and 'both'. * * The type 'flash' refers to flash local storage (SharedObject). In order * to use flash local storage, the 'api' parameter must be valid. The type * 'web' refers to WebStorage, if supported by the browser. The type 'both' * refers to storing using both 'flash' and 'web', not just one or the * other. * * The location array should list the storage types to use in order of * preference: * * ['flash']: flash only storage * ['web']: web only storage * ['both']: try to store in both * ['flash','web']: store in flash first, but if not available, 'web' * ['web','flash']: store in web first, but if not available, 'flash' * * The location array defaults to: ['web', 'flash'] * * @param api the flash interface, null to use only WebStorage. * @param id the storage ID to use. * @param key the key for the item. * @param data the data for the item (any javascript object/primitive). * @param location an array with the preferred types of storage to use. */ util.setItem = function(api, id, key, data, location) { _callStorageFunction(_setItem, arguments, location); }; /** * Gets an item on local disk. * * Set setItem() for details on storage types. * * @param api the flash interface, null to use only WebStorage. * @param id the storage ID to use. * @param key the key for the item. * @param location an array with the preferred types of storage to use. * * @return the item. */ util.getItem = function(api, id, key, location) { return _callStorageFunction(_getItem, arguments, location); }; /** * Removes an item on local disk. * * Set setItem() for details on storage types. * * @param api the flash interface. * @param id the storage ID to use. * @param key the key for the item. * @param location an array with the preferred types of storage to use. */ util.removeItem = function(api, id, key, location) { _callStorageFunction(_removeItem, arguments, location); }; /** * Clears the local disk storage identified by the given ID. * * Set setItem() for details on storage types. * * @param api the flash interface if flash is available. * @param id the storage ID to use. * @param location an array with the preferred types of storage to use. */ util.clearItems = function(api, id, location) { _callStorageFunction(_clearItems, arguments, location); }; /** * Parses the scheme, host, and port from an http(s) url. * * @param str the url string. * * @return the parsed url object or null if the url is invalid. */ util.parseUrl = function(str) { // FIXME: this regex looks a bit broken var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g; regex.lastIndex = 0; var m = regex.exec(str); var url = (m === null) ? null : { full: str, scheme: m[1], host: m[2], port: m[3], path: m[4] }; if(url) { url.fullHost = url.host; if(url.port) { if(url.port !== 80 && url.scheme === 'http') { url.fullHost += ':' + url.port; } else if(url.port !== 443 && url.scheme === 'https') { url.fullHost += ':' + url.port; } } else if(url.scheme === 'http') { url.port = 80; } else if(url.scheme === 'https') { url.port = 443; } url.full = url.scheme + '://' + url.fullHost; } return url; }; /* Storage for query variables */ var _queryVariables = null; /** * Returns the window location query variables. Query is parsed on the first * call and the same object is returned on subsequent calls. The mapping * is from keys to an array of values. Parameters without values will have * an object key set but no value added to the value array. Values are * unescaped. * * ...?k1=v1&k2=v2: * { * "k1": ["v1"], * "k2": ["v2"] * } * * ...?k1=v1&k1=v2: * { * "k1": ["v1", "v2"] * } * * ...?k1=v1&k2: * { * "k1": ["v1"], * "k2": [] * } * * ...?k1=v1&k1: * { * "k1": ["v1"] * } * * ...?k1&k1: * { * "k1": [] * } * * @param query the query string to parse (optional, default to cached * results from parsing window location search query). * * @return object mapping keys to variables. */ util.getQueryVariables = function(query) { var parse = function(q) { var rval = {}; var kvpairs = q.split('&'); for(var i = 0; i < kvpairs.length; i++) { var pos = kvpairs[i].indexOf('='); var key; var val; if(pos > 0) { key = kvpairs[i].substring(0,pos); val = kvpairs[i].substring(pos+1); } else { key = kvpairs[i]; val = null; } if(!(key in rval)) { rval[key] = []; } // disallow overriding object prototype keys if(!(key in Object.prototype) && val !== null) { rval[key].push(unescape(val)); } } return rval; }; var rval; if(typeof(query) === 'undefined') { // set cached variables if needed if(_queryVariables === null) { if(typeof(window) === 'undefined') { // no query variables available _queryVariables = {}; } else { // parse window search query _queryVariables = parse(window.location.search.substring(1)); } } rval = _queryVariables; } else { // parse given query rval = parse(query); } return rval; }; /** * Parses a fragment into a path and query. This method will take a URI * fragment and break it up as if it were the main URI. For example: * /bar/baz?a=1&b=2 * results in: * { * path: ["bar", "baz"], * query: {"k1": ["v1"], "k2": ["v2"]} * } * * @return object with a path array and query object. */ util.parseFragment = function(fragment) { // default to whole fragment var fp = fragment; var fq = ''; // split into path and query if possible at the first '?' var pos = fragment.indexOf('?'); if(pos > 0) { fp = fragment.substring(0,pos); fq = fragment.substring(pos+1); } // split path based on '/' and ignore first element if empty var path = fp.split('/'); if(path.length > 0 && path[0] === '') { path.shift(); } // convert query into object var query = (fq === '') ? {} : util.getQueryVariables(fq); return { pathString: fp, queryString: fq, path: path, query: query }; }; /** * Makes a request out of a URI-like request string. This is intended to * be used where a fragment id (after a URI '#') is parsed as a URI with * path and query parts. The string should have a path beginning and * delimited by '/' and optional query parameters following a '?'. The * query should be a standard URL set of key value pairs delimited by * '&'. For backwards compatibility the initial '/' on the path is not * required. The request object has the following API, (fully described * in the method code): * { * path: <the path string part>. * query: <the query string part>, * getPath(i): get part or all of the split path array, * getQuery(k, i): get part or all of a query key array, * getQueryLast(k, _default): get last element of a query key array. * } * * @return object with request parameters. */ util.makeRequest = function(reqString) { var frag = util.parseFragment(reqString); var req = { // full path string path: frag.pathString, // full query string query: frag.queryString, /** * Get path or element in path. * * @param i optional path index. * * @return path or part of path if i provided. */ getPath: function(i) { return (typeof(i) === 'undefined') ? frag.path : frag.path[i]; }, /** * Get query, values for a key, or value for a key index. * * @param k optional query key. * @param i optional query key index. * * @return query, values for a key, or value for a key index. */ getQuery: function(k, i) { var rval; if(typeof(k) === 'undefined') { rval = frag.query; } else { rval = frag.query[k]; if(rval && typeof(i) !== 'undefined') { rval = rval[i]; } } return rval; }, getQueryLast: function(k, _default) { var rval; var vals = req.getQuery(k); if(vals) { rval = vals[vals.length - 1]; } else { rval = _default; } return rval; } }; return req; }; /** * Makes a URI out of a path, an object with query parameters, and a * fragment. Uses jQuery.param() internally for query string creation. * If the path is an array, it will be joined with '/'. * * @param path string path or array of strings. * @param query object with query parameters. (optional) * @param fragment fragment string. (optional) * * @return string object with request parameters. */ ut