1/* 2 * Movable Type DataAPI SDK for JavaScript v1 3 * https://github.com/movabletype/mt-data-api-sdk-js 4 * Copyright (c) Six Apart Ltd. 5 * This program is distributed under the terms of the MIT license. 6 * 7 * Includes jQuery JavaScript Library in some parts. 8 * http://jquery.com/ 9 * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors 10 * Released under the MIT license 11 * http://jquery.org/license 12 */ 13 14;(function(window, factory) { 15 var DataAPI = factory(window); 16 17 if ( typeof module === "object" && typeof module.exports === "object" ) { 18 module.exports = DataAPI; 19 } else { 20 if ( typeof define === "function" && define.amd ) { 21 define("mt-data-api", [], function() { 22 return DataAPI; 23 }); 24 } 25 } 26}(typeof window === "undefined" ? undefined : window, function(window, undefined) { 27 28"use strict"; 29 30/** 31 * @namespace MT 32 */ 33 34/** 35 * The MT.DataAPI is a client class for accessing to the Movable Type DataAPI. 36 * @class DataAPI 37 * @constructor 38 * @param {Object} options Options. 39 * @param {String} options.clientId Client ID 40 * This value allows alphanumeric, (_)underscore, (-)dash. 41 * @param {String} options.baseUrl The absolute CGI URL of the DataAPI. 42 * (e.g. http://example.com/mt/mt-data-api.cgi) 43 * @param {String} [options.format] The format to serialize. 44 * @param {String} [options.sessionStore] The session store. 45 * In browser, the cookie is used by default. 46 * @param {String} [options.sessionDomain] The session domain. 47 * When using the cookie, this value is used as cookie domain. 48 * @param {String} [options.sessionPath] The session path 49 * When using the cookie, this value is used as cookie path. 50 * @param {Boolean} [options.async] If true, use asynchronous 51 * XMLHttpRequest. The default value is the true. 52 * @param {Number} [options.timeout] The number of milliseconds a 53 * request can take before automatically being terminated. 54 * The default value is not set up, browser's default is used. 55 * @param {Boolean} [options.cache] If false, add an additional 56 * parameter "_" to request to avoid cache. The default value 57 * is the true. 58 * @param {Boolean} [options.withoutAuthorization] If true, 59 * the "X-MT-Authorization" request header is not sent even if 60 * already got accessToken. The default value is the false. 61 * @param {Boolean} [options.loadPluginEndpoints] If true, load 62 * endpoint data extended by plugin and generate methods to 63 * access that endpoint automatically. The default value is 64 * the true. 65 * (However even if this option's value is false, you are able 66 * to use all the methods to access to core endpoint.) 67 * @param {Boolean} [options.suppressResponseCodes] If true, add 68 * suppressResponseCodes parameter to each request. As a result, 69 * the Data API always returns 200 as HTTP response code. 70 * The default value is not set up. 71 * The default value is the false when requested via XMLHttpRequest 72 * or IFRAME. The default value is the true when requested via 73 * XDomainRequest. 74 * @param {Boolean} [options.crossOrigin] If true, requests are sent as 75 * a cross-domain request. The default value is assigned 76 * automatically by document's URL and baseUrl. 77 * @param {Boolean(} [options.disableFormData] If false,use FormData 78 * class when available that. The default value is the false. 79 */ 80var DataAPI = function(options) { 81 var i, k, 82 requireds = ['clientId', 'baseUrl']; 83 84 this.o = { 85 clientId: undefined, 86 baseUrl: undefined, 87 format: undefined, 88 sessionStore: undefined, 89 sessionDomain: undefined, 90 sessionPath: undefined, 91 async: true, 92 timeout: undefined, 93 cache: true, 94 withoutAuthorization: false, 95 processOneTimeTokenOnInitialize: true, 96 loadPluginEndpoints: true, 97 suppressResponseCodes: undefined, 98 crossOrigin: undefined, 99 disableFormData: false 100 }; 101 for (k in options) { 102 if (k in this.o) { 103 this.o[k] = options[k]; 104 } 105 else { 106 throw 'Unkown option: ' + k; 107 } 108 } 109 110 for (i = 0; i < requireds.length; i++) { 111 if (! this.o[requireds[i]]) { 112 throw 'The "' + requireds[i] + '" is required.'; 113 } 114 } 115 116 this.callbacks = {}; 117 this.tokenData = null; 118 this.iframeId = 0; 119 120 this._initOptions(); 121 122 if (this.o.loadPluginEndpoints) { 123 this.loadEndpoints({ 124 excludeComponents: 'core' 125 }); 126 } 127 128 if (this.o.processOneTimeTokenOnInitialize) { 129 this._storeOneTimeToken(); 130 } 131 132 this.trigger('initialize'); 133}; 134 135 136/** 137 * The API version. 138 * @property version 139 * @static 140 * @private 141 * @type Number 142 */ 143DataAPI.version = 1; 144 145/** 146 * The key of access token of this api object. 147 * This value is used for the session store. 148 * @property accessTokenKey 149 * @static 150 * @private
151 * @type String 152 */ 153DataAPI.accessTokenKey = 'mt_data_api_access_token'; 154 155/** 156 * The name prefix for iframe that created to upload asset. 157 * @property iframePrefix 158 * @static 159 * @private 160 * @type String 161 */ 162DataAPI.iframePrefix = 'mt_data_api_iframe_'; 163 164/** 165 * The default format that serializes data. 166 * @property defaultFormat 167 * @static 168 * @private 169 * @type String 170 */ 171DataAPI.defaultFormat = 'json'; 172 173/** 174 * The default session store. 175 * @property defaultSessionStore 176 * @static 177 * @private 178 * @type String 179 */ 180DataAPI.defaultSessionStore = window.document ? 'cookie-encrypted' : 'fs'; 181 182/** 183 * Class level callbacks function data. 184 * @property callbacks 185 * @static 186 * @private 187 * @type Object 188 */ 189DataAPI.callbacks = {}; 190 191/** 192 * Available formats that serialize data. 193 * @property formats 194 * @static 195 * @private 196 * @type Object 197 */ 198DataAPI.formats = { 199 json: { 200 fileExtension: 'json', 201 mimeType: 'application/json', 202 serialize: function() { 203 return JSON.stringify.apply(JSON, arguments); 204 }, 205 unserialize: function() { 206 return JSON.parse.apply(JSON, arguments); 207 } 208 } 209}; 210 211/** 212 * Available session stores. 213 * @property sessionStores 214 * @static 215 * @private 216 * @type Object 217 */ 218DataAPI.sessionStores = {}; 219;(function() { 220 221function fetchCookieValues(name) { 222 var cookie = Cookie.fetch(name); 223 224 if (! cookie) { 225 return {}; 226 } 227 228 try { 229 return JSON.parse(cookie.value); 230 } 231 catch (e) { 232 return { 233 data: cookie.value 234 }; 235 } 236} 237 238function fillinDefaultCookieValues(values, o) { 239 var path = values.path, 240 currentPath = extractPath(documentUrl()); 241 if (! path || path.length > currentPath.length) { 242 path = currentPath; 243 } 244 245 return { 246 data: values.data, 247 domain: o.sessionDomain || values.domain || undefined, 248 path: o.sessionPath || path 249 }; 250} 251 252function documentUrl() { 253 if (! window.location) { 254 return ''; 255 } 256 257 var loc; 258 259 // IE may throw an exception when accessing 260 // a field from window.location if document.domain has been set 261 try { 262 loc = window.location.href; 263 } catch( e ) { 264 // Use the href attribute of an A element 265 // since IE will modify it given document.location 266 loc = window.document.createElement( "a" ); 267 loc.href = ""; 268 loc = loc.href; 269 } 270 271 return loc; 272} 273 274function extractPath(url) { 275 var urlRegexp = /^[\w.+-]+:(?:\/\/[^\/?#:]*(?::\d+|)|)(.*)\/[^\/]*$/, 276 match = urlRegexp.exec(url.toLowerCase()); 277 278 return match ? match[1] : null; 279} 280 281DataAPI.sessionStores['cookie'] = { 282 save: function(name, data, remember) { 283 var expires = remember ? new Date(new Date().getTime() + 315360000000) : undefined, // after 10 years 284 values = fillinDefaultCookieValues(fetchCookieValues(name), this.o); 285 Cookie.bake(name, JSON.stringify(values), values.domain, values.path, expires); 286 }, 287 fetch: function(name) { 288 fetchCookieValues(name).data; 289 }, 290 remove: function(name) { 291 var values = fillinDefaultCookieValues(fetchCookieValues(name)); 292 Cookie.bake(name, '', values.domain, values.path, new Date(0)); 293 } 294}; 295 296})(); 297 298;(function() { 299 300/** @fileOverview Javascript cryptography implementation. 301 * 302 * Crush to remove comments, shorten variable names and 303 * generally reduce transmission size. 304 * 305 * @author Emily Stark 306 * @author Mike Hamburg 307 * @author Dan Boneh 308 */ 309 310"use strict"; 311/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ 312/*global document, window, escape, unescape */ 313 314/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */ 315var sjcl = { 316 /** @namespace Symmetric ciphers. */ 317 cipher: {}, 318 319 /** @namespace Hash functions. Right now only SHA256 is implemented. */ 320 hash: {}, 321 322 /** @namespace Block cipher modes of operation. */ 323 mode: {}, 324 325 /** @namespace Miscellaneous. HMAC and PBKDF2. */ 326 misc: {}, 327 328 /** 329 * @namespace Bit array encoders and decoders. 330 * 331 * @description 332 * The members of this namespace are functions which translate between 333 * SJCL's bitArrays and other objects (usually strings). Because it 334 * isn't always clear which direction is encoding and which is decoding,
335 * the method names are "fromBits" and "toBits". 336 */ 337 codec: {}, 338 339 /** @namespace Exceptions. */ 340 exception: { 341 /** @class Ciphertext is corrupt. */ 342 corrupt: function(message) { 343 this.toString = function() { return "CORRUPT: "+this.message; }; 344 this.message = message; 345 }, 346 347 /** @class Invalid parameter. */ 348 invalid: function(message) { 349 this.toString = function() { return "INVALID: "+this.message; }; 350 this.message = message; 351 }, 352 353 /** @class Bug or missing feature in SJCL. */ 354 bug: function(message) { 355 this.toString = function() { return "BUG: "+this.message; }; 356 this.message = message; 357 }, 358 359 /** @class Something isn't ready. */ 360 notReady: function(message) { 361 this.toString = function() { return "NOT READY: "+this.message; }; 362 this.message = message; 363 } 364 } 365}; 366/** @fileOverview Low-level AES implementation. 367 * 368 * This file contains a low-level implementation of AES, optimized for 369 * size and for efficiency on several browsers. It is based on 370 * OpenSSL's aes_core.c, a public-domain implementation by Vincent 371 * Rijmen, Antoon Bosselaers and Paulo Barreto. 372 * 373 * An older version of this implementation is available in the public 374 * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, 375 * Stanford University 2008-2010 and BSD-licensed for liability 376 * reasons. 377 * 378 * @author Emily Stark 379 * @author Mike Hamburg 380 * @author Dan Boneh 381 */ 382 383/** 384 * Schedule out an AES key for both encryption and decryption. This 385 * is a low-level class. Use a cipher mode to do bulk encryption. 386 * 387 * @constructor 388 * @param {Array} key The key as an array of 4, 6 or 8 words. 389 * 390 * @class Advanced Encryption Standard (low-level interface) 391 */ 392sjcl.cipher.aes = function (key) { 393 if (!this._tables[0][0][0]) { 394 this._precompute(); 395 } 396 397 var i, j, tmp, 398 encKey, decKey, 399 sbox = this._tables[0][4], decTable = this._tables[1], 400 keyLen = key.length, rcon = 1; 401 402 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 403 throw new sjcl.exception.invalid("invalid aes key size"); 404 } 405 406 this._key = [encKey = key.slice(0), decKey = []]; 407 408 // schedule encryption keys 409 for (i = keyLen; i < 4 * keyLen + 28; i++) { 410 tmp = encKey[i-1]; 411 412 // apply sbox 413 if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) { 414 tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255]; 415 416 // shift rows and add rcon 417 if (i%keyLen === 0) { 418 tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; 419 rcon = rcon<<1 ^ (rcon>>7)*283; 420 } 421 } 422 423 encKey[i] = encKey[i-keyLen] ^ tmp; 424 } 425 426 // schedule decryption keys 427 for (j = 0; i; j++, i--) { 428 tmp = encKey[j&3 ? i : i - 4]; 429 if (i<=4 || j<4) { 430 decKey[j] = tmp; 431 } else { 432 decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^ 433 decTable[1][sbox[tmp>>16 & 255]] ^ 434 decTable[2][sbox[tmp>>8 & 255]] ^ 435 decTable[3][sbox[tmp & 255]]; 436 } 437 } 438}; 439 440sjcl.cipher.aes.prototype = { 441 // public 442 /* Something like this might appear here eventually 443 name: "AES", 444 blockSize: 4, 445 keySizes: [4,6,8], 446 */ 447 448 /** 449 * Encrypt an array of 4 big-endian words. 450 * @param {Array} data The plaintext. 451 * @return {Array} The ciphertext. 452 */ 453 encrypt:function (data) { return this._crypt(data,0); }, 454 455 /** 456 * Decrypt an array of 4 big-endian words. 457 * @param {Array} data The ciphertext. 458 * @return {Array} The plaintext. 459 */ 460 decrypt:function (data) { return this._crypt(data,1); }, 461 462 /** 463 * The expanded S-box and inverse S-box tables. These will be computed 464 * on the client so that we don't have to send them down the wire. 465 * 466 * There are two tables, _tables[0] is for encryption and 467 * _tables[1] is for decryption. 468 * 469 * The first 4 sub-tables are the expanded S-box with MixColumns. The 470 * last (_tables[01][4]) is the S-box itself. 471 * 472 * @private 473 */ 474 _tables: [[[],[],[],[],[]],[[],[],[],[],[]]], 475 476 /** 477 * Expand the S-box tables. 478 * 479 * @private 480 */ 481 _precompute: function () { 482 var encTable = this._tables[0], decTable = this._tables[1], 483 sbox = encTable[4], sboxInv = decTable[4], 484 i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec; 485 486 // Compute double and third tables 487 for (i = 0; i < 256; i++) { 488 th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i; 489 } 490 491 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 492 // Compute sbox 493 s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4; 494 s = s>>8 ^ s&255 ^ 99; 495 sbox[x] = s; 496 sboxInv[s] = x; 497 498 // Compute MixColumns 499 x8 = d[x4 = d[x2 = d[x]]]; 500 tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100; 501 tEnc = d[s]*0x101 ^ s*0x1010100; 502 503 for (i = 0; i < 4; i++) { 504 encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8; 505 decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8; 506 } 507 } 508 509 // Compactify. Considerable speedup on Firefox. 510 for (i = 0; i < 5; i++) { 511 encTable[i] = encTable[i].slice(0); 512 decTable[i] = decTable[i].slice(0); 513 } 514 }, 515 516 /** 517 * Encryption and decryption core. 518 * @param {Array} input Four words to be encrypted or decrypted. 519 * @param dir The direction, 0 for encrypt and 1 for decrypt. 520 * @return {Array} The four encrypted or decrypted words. 521 * @private 522 */ 523 _crypt:function (input, dir) { 524 if (input.length !== 4) { 525 throw new sjcl.exception.invalid("invalid aes block size"); 526 } 527 528 var key = this._key[dir], 529 // state variables a,b,c,d are loaded with pre-whitened data 530 a = input[0] ^ key[0], 531 b = input[dir ? 3 : 1] ^ key[1], 532 c = input[2] ^ key[2], 533 d = input[dir ? 1 : 3] ^ key[3], 534 a2, b2, c2, 535 536 nInnerRounds = key.length/4 - 2, 537 i, 538 kIndex = 4, 539 out = [0,0,0,0], 540 table = this._tables[dir], 541 542 // load up the tables 543 t0 = table[0], 544 t1 = table[1], 545 t2 = table[2], 546 t3 = table[3], 547 sbox = table[4]; 548 549 // Inner rounds. Cribbed from OpenSSL. 550 for (i = 0; i < nInnerRounds; i++) { 551 a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex]; 552 b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; 553 c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; 554 d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; 555 kIndex += 4; 556 a=a2; b=b2; c=c2; 557 } 558 559 // Last round. 560 for (i = 0; i < 4; i++) { 561 out[dir ? 3&-i : i] = 562 sbox[a>>>24 ]<<24 ^ 563 sbox[b>>16 & 255]<<16 ^ 564 sbox[c>>8 & 255]<<8 ^ 565 sbox[d & 255] ^ 566 key[kIndex++]; 567 a2=a; a=b; b=c; c=d; d=a2; 568 } 569 570 return out; 571 } 572}; 573 574/** @fileOverview Arrays of bits, encoded as arrays of Numbers. 575 * 576 * @author Emily Stark 577 * @author Mike Hamburg 578 * @author Dan Boneh 579 */ 580 581/** @namespace Arrays of bits, encoded as arrays of Numbers. 582 * 583 * @description 584 * <p> 585 * These objects are the currency accepted by SJCL's crypto functions. 586 * </p> 587 * 588 * <p> 589 * Most of our crypto primitives operate on arrays of 4-byte words internally, 590 * but many of them can take arguments that are not a multiple of 4 bytes. 591 * This library encodes arrays of bits (whose size need not be a multiple of 8 592 * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an 593 * array of words, 32 bits at a time. Since the words are double-precision 594 * floating point numbers, they fit some extra data. We use this (in a private, 595 * possibly-changing manner) to encode the number of bits actually present 596 * in the last word of the array. 597 * </p> 598 * 599 * <p> 600 * Because bitwise ops clear this out-of-band data, these arrays can be passed 601 * to ciphers like AES which want arrays of words. 602 * </p> 603 */ 604sjcl.bitArray = { 605 /** 606 * Array slices in units of bits. 607 * @param {bitArray a} The array to slice. 608 * @param {Number} bstart The offset to the start of the slice, in bits. 609 * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined, 610 * slice until the end of the array. 611 * @return {bitArray} The requested slice. 612 */ 613 bitSlice: function (a, bstart, bend) { 614 a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1); 615 return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart); 616 }, 617 618 /** 619 * Concatenate two bit arrays. 620 * @param {bitArray} a1 The first array. 621 * @param {bitArray} a2 The second array. 622 * @return {bitArray} The concatenation of a1 and a2. 623 */ 624 concat: function (a1, a2) { 625 if (a1.length === 0 || a2.length === 0) { 626 return a1.concat(a2); 627 } 628 629 var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last); 630 if (shift === 32) { 631 return a1.concat(a2); 632 } else { 633 return sjcl.bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1)); 634 } 635 }, 636 637 /** 638 * Find the length of an array of bits. 639 * @param {bitArray} a The array. 640 * @return {Number} The length of a, in bits. 641 */ 642 bitLength: function (a) { 643 var l = a.length, x; 644 if (l === 0) { return 0; } 645 x = a[l - 1]; 646 return (l-1) * 32 + sjcl.bitArray.getPartial(x); 647 }, 648 649 /** 650 * Truncate an array.
651 * @param {bitArray} a The array. 652 * @param {Number} len The length to truncate to, in bits. 653 * @return {bitArray} A new array, truncated to len bits. 654 */ 655 clamp: function (a, len) { 656 if (a.length * 32 < len) { return a; } 657 a = a.slice(0, Math.ceil(len / 32)); 658 var l = a.length; 659 len = len & 31; 660 if (l > 0 && len) { 661 a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1); 662 } 663 return a; 664 }, 665 666 /** 667 * Make a partial word for a bit array. 668 * @param {Number} len The number of bits in the word. 669 * @param {Number} x The bits. 670 * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side. 671 * @return {Number} The partial word. 672 */ 673 partial: function (len, x, _end) { 674 if (len === 32) { return x; } 675 return (_end ? x|0 : x << (32-len)) + len * 0x10000000000; 676 }, 677 678 /** 679 * Get the number of bits used by a partial word. 680 * @param {Number} x The partial word. 681 * @return {Number} The number of bits used by the partial word. 682 */ 683 getPartial: function (x) { 684 return Math.round(x/0x10000000000) || 32; 685 }, 686 687 /** 688 * Compare two arrays for equality in a predictable amount of time. 689 * @param {bitArray} a The first array. 690 * @param {bitArray} b The second array. 691 * @return {boolean} true if a == b; false otherwise. 692 */ 693 equal: function (a, b) { 694 if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) { 695 return false; 696 } 697 var x = 0, i; 698 for (i=0; i<a.length; i++) { 699 x |= a[i]^b[i]; 700 } 701 return (x === 0); 702 }, 703 704 /** Shift an array right. 705 * @param {bitArray} a The array to shift. 706 * @param {Number} shift The number of bits to shift. 707 * @param {Number} [carry=0] A byte to carry in 708 * @param {bitArray} [out=[]] An array to prepend to the output. 709 * @private 710 */ 711 _shiftRight: function (a, shift, carry, out) { 712 var i, last2=0, shift2; 713 if (out === undefined) { out = []; } 714 715 for (; shift >= 32; shift -= 32) { 716 out.push(carry); 717 carry = 0; 718 } 719 if (shift === 0) { 720 return out.concat(a); 721 } 722 723 for (i=0; i<a.length; i++) { 724 out.push(carry | a[i]>>>shift); 725 carry = a[i] << (32-shift); 726 } 727 last2 = a.length ? a[a.length-1] : 0; 728 shift2 = sjcl.bitArray.getPartial(last2); 729 out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1)); 730 return out; 731 }, 732 733 /** xor a block of 4 words together. 734 * @private 735 */ 736 _xor4: function(x,y) { 737 return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; 738 } 739}; 740/** @fileOverview Bit array codec implementations. 741 * 742 * @author Emily Stark 743 * @author Mike Hamburg 744 * @author Dan Boneh 745 */ 746 747/** @namespace UTF-8 strings */ 748sjcl.codec.utf8String = { 749 /** Convert from a bitArray to a UTF-8 string. */ 750 fromBits: function (arr) { 751 var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp; 752 for (i=0; i<bl/8; i++) { 753 if ((i&3) === 0) { 754 tmp = arr[i/4]; 755 } 756 out += String.fromCharCode(tmp >>> 24); 757 tmp <<= 8; 758 } 759 return decodeURIComponent(escape(out)); 760 }, 761 762 /** Convert from a UTF-8 string to a bitArray. */ 763 toBits: function (str) { 764 str = unescape(encodeURIComponent(str)); 765 var out = [], i, tmp=0; 766 for (i=0; i<str.length; i++) { 767 tmp = tmp << 8 | str.charCodeAt(i); 768 if ((i&3) === 3) { 769 out.push(tmp); 770 tmp = 0; 771 } 772 } 773 if (i&3) { 774 out.push(sjcl.bitArray.partial(8*(i&3), tmp)); 775 } 776 return out; 777 } 778}; 779/** @fileOverview Bit array codec implementations. 780 * 781 * @author Emily Stark 782 * @author Mike Hamburg 783 * @author Dan Boneh 784 */ 785 786/** @namespace Base64 encoding/decoding */ 787sjcl.codec.base64 = { 788 /** The base64 alphabet. 789 * @private 790 */ 791 _chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 792 793 /** Convert from a bitArray to a base64 string. */ 794 fromBits: function (arr, _noEquals) { 795 var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr); 796 for (i=0; out.length * 6 < bl; ) { 797 out += c.charAt((ta ^ arr[i]>>>bits) >>> 26); 798 if (bits < 6) { 799 ta = arr[i] << (6-bits); 800 bits += 26; 801 i++; 802 } else { 803 ta <<= 6; 804 bits -= 6; 805 } 806 } 807 while ((out.length & 3) && !_noEquals) { out += "="; } 808 return out; 809 }, 810
811 /** Convert from a base64 string to a bitArray */ 812 toBits: function(str) { 813 str = str.replace(/\s|=/g,''); 814 var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; 815 for (i=0; i<str.length; i++) { 816 x = c.indexOf(str.charAt(i)); 817 if (x < 0) { 818 throw new sjcl.exception.invalid("this isn't base64!"); 819 } 820 if (bits > 26) { 821 bits -= 26; 822 out.push(ta ^ x>>>bits); 823 ta = x << (32-bits); 824 } else { 825 bits += 6; 826 ta ^= x << (32-bits); 827 } 828 } 829 if (bits&56) { 830 out.push(sjcl.bitArray.partial(bits&56, ta, 1)); 831 } 832 return out; 833 } 834}; 835/** @fileOverview Javascript SHA-256 implementation. 836 * 837 * An older version of this implementation is available in the public 838 * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, 839 * Stanford University 2008-2010 and BSD-licensed for liability 840 * reasons. 841 * 842 * Special thanks to Aldo Cortesi for pointing out several bugs in 843 * this code. 844 * 845 * @author Emily Stark 846 * @author Mike Hamburg 847 * @author Dan Boneh 848 */ 849 850/** 851 * Context for a SHA-256 operation in progress. 852 * @constructor 853 * @class Secure Hash Algorithm, 256 bits. 854 */ 855sjcl.hash.sha256 = function (hash) { 856 if (!this._key[0]) { this._precompute(); } 857 if (hash) { 858 this._h = hash._h.slice(0); 859 this._buffer = hash._buffer.slice(0); 860 this._length = hash._length; 861 } else { 862 this.reset(); 863 } 864}; 865 866/** 867 * Hash a string or an array of words. 868 * @static 869 * @param {bitArray|String} data the data to hash. 870 * @return {bitArray} The hash value, an array of 16 big-endian words. 871 */ 872sjcl.hash.sha256.hash = function (data) { 873 return (new sjcl.hash.sha256()).update(data).finalize(); 874}; 875 876sjcl.hash.sha256.prototype = { 877 /** 878 * The hash's block size, in bits. 879 * @constant 880 */ 881 blockSize: 512, 882 883 /** 884 * Reset the hash state. 885 * @return this 886 */ 887 reset:function () { 888 this._h = this._init.slice(0); 889 this._buffer = []; 890 this._length = 0; 891 return this; 892 }, 893
894 /** 895 * Input several words to the hash. 896 * @param {bitArray|String} data the data to hash. 897 * @return this 898 */ 899 update: function (data) { 900 if (typeof data === "string") { 901 data = sjcl.codec.utf8String.toBits(data); 902 } 903 var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), 904 ol = this._length, 905 nl = this._length = ol + sjcl.bitArray.bitLength(data); 906 for (i = 512+ol & -512; i <= nl; i+= 512) { 907 this._block(b.splice(0,16)); 908 } 909 return this; 910 }, 911 912 /** 913 * Complete hashing and output the hash value. 914 * @return {bitArray} The hash value, an array of 16 big-endian words. 915 */ 916 finalize:function () { 917 var i, b = this._buffer, h = this._h; 918 919 // Round out and push the buffer 920 b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); 921 922 // Round out the buffer to a multiple of 16 words, less the 2 length words. 923 for (i = b.length + 2; i & 15; i++) { 924 b.push(0); 925 } 926 927 // append the length 928 b.push(Math.floor(this._length / 0x100000000)); 929 b.push(this._length | 0); 930 931 while (b.length) { 932 this._block(b.splice(0,16)); 933 } 934 935 this.reset(); 936 return h; 937 }, 938 939 /** 940 * The SHA-256 initialization vector, to be precomputed. 941 * @private 942 */ 943 _init:[], 944 /* 945 _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19], 946 */ 947 948 /** 949 * The SHA-256 hash key, to be precomputed. 950 * @private 951 */ 952 _key:[], 953 /* 954 _key: 955 [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 956 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 957 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 958 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 959 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 960 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 961 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 962 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2], 963 */ 964 965 966 /** 967 * Function to precompute _init and _key. 968 * @private 969 */ 970 _precompute: function () { 971 var i = 0, prime = 2, factor; 972 973 function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; } 974 975 outer: for (; i<64; prime++) { 976 for (factor=2; factor*factor <= prime; factor++) { 977 if (prime % factor === 0) { 978 // not a prime 979 continue outer; 980 } 981 } 982 983 if (i<8) { 984 this._init[i] = frac(Math.pow(prime, 1/2)); 985 } 986 this._key[i] = frac(Math.pow(prime, 1/3)); 987 i++; 988 } 989 }, 990 991 /** 992 * Perform one cycle of SHA-256. 993 * @param {bitArray} words one block of words. 994 * @private 995 */ 996 _block:function (words) { 997 var i, tmp, a, b, 998 w = words.slice(0), 999 h = this._h, 1000 k = this._key, 1001 h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3], 1002 h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7]; 1003 1004 /* Rationale for placement of |0 : 1005 * If a value can overflow is original 32 bits by a factor of more than a few 1006 * million (2^23 ish), there is a possibility that it might overflow the 1007 * 53-bit mantissa and lose precision. 1008 * 1009 * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that 1010 * propagates around the loop, and on the hash state h[]. I don't believe 1011 * that the clamps on h4 and on h0 are strictly necessary, but it's close 1012 * (for h4 anyway), and better safe than sorry. 1013 * 1014 * The clamps on h[] are necessary for the output to be correct even in the 1015 * common case and for short inputs. 1016 */ 1017 for (i=0; i<64; i++) { 1018 // load up the input word for this round 1019 if (i<16) { 1020 tmp = w[i]; 1021 } else { 1022 a = w[(i+1 ) & 15]; 1023 b = w[(i+14) & 15]; 1024 tmp = w[i&15] = ((a>>>7 ^ a>>>18 ^ a>>>3 ^ a<<25 ^ a<<14) + 1025 (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) + 1026 w[i&15] + w[(i+9) & 15]) | 0; 1027 } 1028 1029 tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]); // | 0; 1030 1031 // shift register 1032 h7 = h6; h6 = h5; h5 = h4; 1033 h4 = h3 + tmp | 0; 1034 h3 = h2; h2 = h1; h1 = h0; 1035 1036 h0 = (tmp + ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0; 1037 } 1038 1039 h[0] = h[0]+h0 | 0; 1040 h[1] = h[1]+h1 | 0; 1041 h[2] = h[2]+h2 | 0; 1042 h[3] = h[3]+h3 | 0; 1043 h[4] = h[4]+h4 | 0; 1044 h[5] = h[5]+h5 | 0; 1045 h[6] = h[6]+h6 | 0; 1046 h[7] = h[7]+h7 | 0; 1047 } 1048}; 1049 1050 1051/** @fileOverview CCM mode implementation. 1052 * 1053 * Special thanks to Roy Nicholson for pointing out a bug in our 1054 * implementation. 1055 * 1056 * @author Emily Stark 1057 * @author Mike Hamburg 1058 * @author Dan Boneh 1059 */ 1060 1061/** @namespace CTR mode with CBC MAC. */ 1062sjcl.mode.ccm = { 1063 /** The name of the mode. 1064 * @constant 1065 */ 1066 name: "ccm", 1067 1068 /** Encrypt in CCM mode. 1069 * @static 1070 * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. 1071 * @param {bitArray} plaintext The plaintext data. 1072 * @param {bitArray} iv The initialization value. 1073 * @param {bitArray} [adata=[]] The authenticated data. 1074 * @param {Number} [tlen=64] the desired tag length, in bits. 1075 * @return {bitArray} The encrypted data, an array of bytes. 1076 */ 1077 encrypt: function(prf, plaintext, iv, adata, tlen) { 1078 var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; 1079 tlen = tlen || 64; 1080 adata = adata || []; 1081 1082 if (ivl < 7) { 1083 throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); 1084 } 1085 1086 // compute the length of the length 1087 for (L=2; L<4 && ol >>> 8*L; L++) {} 1088 if (L < 15 - ivl) { L = 15-ivl; } 1089 iv = w.clamp(iv,8*(15-L)); 1090 1091 // compute the tag 1092 tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L); 1093 1094 // encrypt 1095 out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); 1096 1097 return w.concat(out.data, out.tag); 1098 }, 1099
1100 /** Decrypt in CCM mode. 1101 * @static 1102 * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. 1103 * @param {bitArray} ciphertext The ciphertext data. 1104 * @param {bitArray} iv The initialization value. 1105 * @param {bitArray} [[]] adata The authenticated data. 1106 * @param {Number} [64] tlen the desired tag length, in bits. 1107 * @return {bitArray} The decrypted data. 1108 */ 1109 decrypt: function(prf, ciphertext, iv, adata, tlen) { 1110 tlen = tlen || 64; 1111 adata = adata || []; 1112 var L, i, 1113 w=sjcl.bitArray, 1114 ivl = w.bitLength(iv) / 8, 1115 ol = w.bitLength(ciphertext), 1116 out = w.clamp(ciphertext, ol - tlen), 1117 tag = w.bitSlice(ciphertext, ol - tlen), tag2; 1118 1119 1120 ol = (ol - tlen) / 8; 1121 1122 if (ivl < 7) { 1123 throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); 1124 } 1125 1126 // compute the length of the length 1127 for (L=2; L<4 && ol >>> 8*L; L++) {} 1128 if (L < 15 - ivl) { L = 15-ivl; } 1129 iv = w.clamp(iv,8*(15-L)); 1130 1131 // decrypt 1132 out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); 1133 1134 // check the tag 1135 tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L); 1136 if (!w.equal(out.tag, tag2)) { 1137 throw new sjcl.exception.corrupt("ccm: tag doesn't match"); 1138 } 1139 1140 return out.data; 1141 }, 1142 1143 /* Compute the (unencrypted) authentication tag, according to the CCM specification 1144 * @param {Object} prf The pseudorandom function. 1145 * @param {bitArray} plaintext The plaintext data. 1146 * @param {bitArray} iv The initialization value. 1147 * @param {bitArray} adata The authenticated data. 1148 * @param {Number} tlen the desired tag length, in bits. 1149 * @return {bitArray} The tag, but not yet encrypted. 1150 * @private 1151 */ 1152 _computeTag: function(prf, plaintext, iv, adata, tlen, L) { 1153 // compute B[0] 1154 var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; 1155 1156 tlen /= 8; 1157 1158 // check tag length and message length 1159 if (tlen % 2 || tlen < 4 || tlen > 16) { 1160 throw new sjcl.exception.invalid("ccm: invalid tag length"); 1161 } 1162 1163 if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) { 1164 // I don't want to deal with extracting high words from doubles. 1165 throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); 1166 } 1167 1168 // mac the flags 1169 mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; 1170 1171 // mac the iv and length 1172 mac = w.concat(mac, iv); 1173 mac[3] |= w.bitLength(plaintext)/8; 1174 mac = prf.encrypt(mac); 1175 1176 1177 if (adata.length) { 1178 // mac the associated data. start with its length... 1179 tmp = w.bitLength(adata)/8; 1180 if (tmp <= 0xFEFF) { 1181 macData = [w.partial(16, tmp)]; 1182 } else if (tmp <= 0xFFFFFFFF) { 1183 macData = w.concat([w.partial(16,0xFFFE)], [tmp]); 1184 } // else ... 1185 1186 // mac the data itself 1187 macData = w.concat(macData, adata); 1188 for (i=0; i<macData.length; i += 4) { 1189 mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0]))); 1190 } 1191 } 1192 1193 // mac the plaintext 1194 for (i=0; i<plaintext.length; i+=4) { 1195 mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4).concat([0,0,0]))); 1196 } 1197 1198 return w.clamp(mac, tlen * 8); 1199 }, 1200 1201 /** CCM CTR mode. 1202 * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode. 1203 * May mutate its arguments. 1204 * @param {Object} prf The PRF. 1205 * @param {bitArray} data The data to be encrypted or decrypted. 1206 * @param {bitArray} iv The initialization vector. 1207 * @param {bitArray} tag The authentication tag. 1208 * @param {Number} tlen The length of th etag, in bits. 1209 * @param {Number} L The CCM L value. 1210 * @return {Object} An object with data and tag, the en/decryption of data and tag values. 1211 * @private 1212 */ 1213 _ctrMode: function(prf, data, iv, tag, tlen, L) { 1214 var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, b, l = data.length, bl=w.bitLength(data); 1215 1216 // start the ctr 1217 ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); 1218 1219 // en/decrypt the tag
1220 tag = w.bitSlice(xor(tag,prf.encrypt(ctr)), 0, tlen); 1221 1222 // en/decrypt the data 1223 if (!l) { return {tag:tag, data:[]}; } 1224 1225 for (i=0; i<l; i+=4) { 1226 ctr[3]++; 1227 enc = prf.encrypt(ctr); 1228 data[i] ^= enc[0]; 1229 data[i+1] ^= enc[1]; 1230 data[i+2] ^= enc[2]; 1231 data[i+3] ^= enc[3]; 1232 } 1233 return { tag:tag, data:w.clamp(data,bl) }; 1234 } 1235}; 1236/** @fileOverview HMAC implementation. 1237 * 1238 * @author Emily Stark 1239 * @author Mike Hamburg 1240 * @author Dan Boneh 1241 */ 1242 1243/** HMAC with the specified hash function. 1244 * @constructor 1245 * @param {bitArray} key the key for HMAC. 1246 * @param {Object} [hash=sjcl.hash.sha256] The hash function to use. 1247 */ 1248sjcl.misc.hmac = function (key, Hash) { 1249 this._hash = Hash = Hash || sjcl.hash.sha256; 1250 var exKey = [[],[]], i, 1251 bs = Hash.prototype.blockSize / 32; 1252 this._baseHash = [new Hash(), new Hash()]; 1253 1254 if (key.length > bs) { 1255 key = Hash.hash(key); 1256 } 1257 1258 for (i=0; i<bs; i++) { 1259 exKey[0][i] = key[i]^0x36363636; 1260 exKey[1][i] = key[i]^0x5C5C5C5C; 1261 } 1262 1263 this._baseHash[0].update(exKey[0]); 1264 this._baseHash[1].update(exKey[1]); 1265}; 1266 1267/** HMAC with the specified hash function. Also called encrypt since it's a prf. 1268 * @param {bitArray|String} data The data to mac. 1269 */ 1270sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data) { 1271 var w = new (this._hash)(this._baseHash[0]).update(data).finalize(); 1272 return new (this._hash)(this._baseHash[1]).update(w).finalize(); 1273}; 1274 1275/** @fileOverview Password-based key-derivation function, version 2.0. 1276 * 1277 * @author Emily Stark 1278 * @author Mike Hamburg 1279 * @author Dan Boneh 1280 */ 1281 1282/** Password-Based Key-Derivation Function, version 2.0. 1283 * 1284 * Generate keys from passwords using PBKDF2-HMAC-SHA256. 1285 * 1286 * This is the method specified by RSA's PKCS #5 standard. 1287 * 1288 * @param {bitArray|String} password The password. 1289 * @param {bitArray} salt The salt. Should have lots of entropy. 1290 * @param {Number} [count=1000] The number of iterations. Higher numbers make the function slower but more secure. 1291 * @param {Number} [length] The length of the derived key. Defaults to the 1292 output size of the hash function. 1293 * @param {Object} [Prff=sjcl.misc.hmac] The pseudorandom function family. 1294 * @return {bitArray} the derived key. 1295 */ 1296sjcl.misc.pbkdf2 = function (password, salt, count, length, Prff) { 1297 count = count || 1000; 1298 1299 if (length < 0 || count < 0) { 1300 throw sjcl.exception.invalid("invalid params to pbkdf2"); 1301 } 1302 1303 if (typeof password === "string") { 1304 password = sjcl.codec.utf8String.toBits(password); 1305 } 1306 1307 Prff = Prff || sjcl.misc.hmac; 1308 1309 var prf = new Prff(password), 1310 u, ui, i, j, k, out = [], b = sjcl.bitArray; 1311 1312 for (k = 1; 32 * out.length < (length || 1); k++) { 1313 u = ui = prf.encrypt(b.concat(salt,[k])); 1314 1315 for (i=1; i<count; i++) { 1316 ui = prf.encrypt(ui); 1317 for (j=0; j<ui.length; j++) { 1318 u[j] ^= ui[j]; 1319 } 1320 } 1321 1322 out = out.concat(u); 1323 } 1324 1325 if (length) { out = b.clamp(out, length); } 1326 1327 return out; 1328}; 1329/** @fileOverview Random number generator. 1330 * 1331 * @author Emily Stark 1332 * @author Mike Hamburg 1333 * @author Dan Boneh 1334 */ 1335 1336/** @namespace Random number generator 1337 * 1338 * @description 1339 * <p> 1340 * This random number generator is a derivative of Ferguson and Schneier's 1341 * generator Fortuna. It collects entropy from various events into several 1342 * pools, implemented by streaming SHA-256 instances. It differs from 1343 * ordinary Fortuna in a few ways, though. 1344 * </p> 1345 * 1346 * <p> 1347 * Most importantly, it has an entropy estimator. This is present because 1348 * there is a strong conflict here between making the generator available 1349 * as soon as possible, and making sure that it doesn't "run on empty". 1350 * In Fortuna, there is a saved state file, and the system is likely to have 1351 * time to warm up. 1352 * </p> 1353 * 1354 * <p> 1355 * Second, because users are unlikely to stay on the page for very long, 1356 * and to speed startup time, the number of pools increases logarithmically: 1357 * a new pool is created when the previous one is actually used for a reseed. 1358 * This gives the same asymptotic guarantees as Fortuna, but gives more 1359 * entropy to early reseeds. 1360 * </p> 1361 * 1362 * <p> 1363 * The entire mechanism here feels pretty klunky. Furthermore, there are 1364 * several improvements that should be made, including support for 1365 * dedicated cryptographic functions that may be present in some browsers;
1366 * state files in local storage; cookies containing randomness; etc. So 1367 * look for improvements in future versions. 1368 * </p> 1369 */ 1370sjcl.random = { 1371 /** Generate several random words, and return them in an array 1372 * @param {Number} nwords The number of words to generate. 1373 */ 1374 randomWords: function (nwords, paranoia) { 1375 var out = [], i, readiness = this.isReady(paranoia), g; 1376 1377 if (readiness === this._NOT_READY) { 1378 throw new sjcl.exception.notReady("generator isn't seeded"); 1379 } else if (readiness & this._REQUIRES_RESEED) { 1380 this._reseedFromPools(!(readiness & this._READY)); 1381 } 1382 1383 for (i=0; i<nwords; i+= 4) { 1384 if ((i+1) % this._MAX_WORDS_PER_BURST === 0) { 1385 this._gate(); 1386 } 1387 1388 g = this._gen4words(); 1389 out.push(g[0],g[1],g[2],g[3]); 1390 } 1391 this._gate(); 1392 1393 return out.slice(0,nwords); 1394 }, 1395 1396 setDefaultParanoia: function (paranoia) { 1397 this._defaultParanoia = paranoia; 1398 }, 1399 1400 /** 1401 * Add entropy to the pools. 1402 * @param data The entropic value. Should be a 32-bit integer, array of 32-bit integers, or string 1403 * @param {Number} estimatedEntropy The estimated entropy of data, in bits 1404 * @param {String} source The source of the entropy, eg "mouse" 1405 */ 1406 addEntropy: function (data, estimatedEntropy, source) { 1407 source = source || "user"; 1408 1409 var id, 1410 i, tmp, 1411 t = (new Date()).valueOf(), 1412 robin = this._robins[source], 1413 oldReady = this.isReady(), err = 0; 1414 1415 id = this._collectorIds[source]; 1416 if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; } 1417 1418 if (robin === undefined) { robin = this._robins[source] = 0; } 1419 this._robins[source] = ( this._robins[source] + 1 ) % this._pools.length; 1420 1421 switch(typeof(data)) { 1422 1423 case "number": 1424 if (estimatedEntropy === undefined) { 1425 estimatedEntropy = 1; 1426 } 1427 this._pools[robin].update([id,this._eventId++,1,estimatedEntropy,t,1,data|0]); 1428 break; 1429 1430 case "object": 1431 var objName = Object.prototype.toString.call(data); 1432 if (objName === "[object Uint32Array]") { 1433 tmp = []; 1434 for (i = 0; i < data.length; i++) { 1435 tmp.push(data[i]); 1436 } 1437 data = tmp; 1438 } else { 1439 if (objName !== "[object Array]") { 1440 err = 1; 1441 } 1442 for (i=0; i<data.length && !err; i++) { 1443 if (typeof(data[i]) != "number") { 1444 err = 1; 1445 } 1446 } 1447 } 1448 if (!err) { 1449 if (estimatedEntropy === undefined) { 1450 /* horrible entropy estimator */ 1451 estimatedEntropy = 0; 1452 for (i=0; i<data.length; i++) { 1453 tmp= data[i]; 1454 while (tmp>0) { 1455 estimatedEntropy++; 1456 tmp = tmp >>> 1; 1457 } 1458 } 1459 } 1460 this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data)); 1461 } 1462 break; 1463 1464 case "string": 1465 if (estimatedEntropy === undefined) { 1466 /* English text has just over 1 bit per character of entropy. 1467 * But this might be HTML or something, and have far less 1468 * entropy than English... Oh well, let's just say one bit. 1469 */ 1470 estimatedEntropy = data.length; 1471 } 1472 this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]); 1473 this._pools[robin].update(data); 1474 break; 1475 1476 default: 1477 err=1; 1478 } 1479 if (err) { 1480 throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"); 1481 } 1482 1483 /* record the new strength */ 1484 this._poolEntropy[robin] += estimatedEntropy; 1485 this._poolStrength += estimatedEntropy; 1486 1487 /* fire off events */ 1488 if (oldReady === this._NOT_READY) { 1489 if (this.isReady() !== this._NOT_READY) { 1490 this._fireEvent("seeded", Math.max(this._strength, this._poolStrength)); 1491 } 1492 this._fireEvent("progress", this.getProgress()); 1493 } 1494 }, 1495 1496 /** Is the generator ready? */ 1497 isReady: function (paranoia) { 1498 var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ]; 1499 1500 if (this._strength && this._strength >= entropyRequired) { 1501 return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ? 1502 this._REQUIRES_RESEED | this._READY : 1503 this._READY; 1504 } else { 1505 return (this._poolStrength >= entropyRequired) ? 1506 this._REQUIRES_RESEED | this._NOT_READY : 1507 this._NOT_READY; 1508 } 1509 }, 1510 1511 /** Get the generator's progress toward readiness, as a fraction */ 1512 getProgress: function (paranoia) { 1513 var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ]; 1514 1515 if (this._strength >= entropyRequired) { 1516 return 1.0; 1517 } else { 1518 return (this._poolStrength > entropyRequired) ? 1519 1.0 : 1520 this._poolStrength / entropyRequired; 1521 } 1522 }, 1523 1524 /** start the built-in entropy collectors */ 1525 startCollectors: function () { 1526 if (this._collectorsStarted) { return; } 1527 1528 if (window.addEventListener) { 1529 window.addEventListener("load", this._loadTimeCollector, false); 1530 window.addEventListener("mousemove", this._mouseCollector, false); 1531 } else if (document.attachEvent) { 1532 document.attachEvent("onload", this._loadTimeCollector); 1533 document.attachEvent("onmousemove", this._mouseCollector); 1534 } 1535 else { 1536 throw new sjcl.exception.bug("can't attach event"); 1537 } 1538 1539 this._collectorsStarted = true; 1540 }, 1541
1542 /** stop the built-in entropy collectors */ 1543 stopCollectors: function () { 1544 if (!this._collectorsStarted) { return; } 1545 1546 if (window.removeEventListener) { 1547 window.removeEventListener("load", this._loadTimeCollector, false); 1548 window.removeEventListener("mousemove", this._mouseCollector, false); 1549 } else if (window.detachEvent) { 1550 window.detachEvent("onload", this._loadTimeCollector); 1551 window.detachEvent("onmousemove", this._mouseCollector); 1552 } 1553 this._collectorsStarted = false; 1554 }, 1555 1556 /* use a cookie to store entropy. 1557 useCookie: function (all_cookies) { 1558 throw new sjcl.exception.bug("random: useCookie is unimplemented"); 1559 },*/ 1560 1561 /** add an event listener for progress or seeded-ness. */ 1562 addEventListener: function (name, callback) { 1563 this._callbacks[name][this._callbackI++] = callback; 1564 }, 1565 1566 /** remove an event listener for progress or seeded-ness */ 1567 removeEventListener: function (name, cb) { 1568 var i, j, cbs=this._callbacks[name], jsTemp=[]; 1569 1570 /* I'm not sure if this is necessary; in C++, iterating over a 1571 * collection and modifying it at the same time is a no-no. 1572 */ 1573 1574 for (j in cbs) { 1575 if (cbs.hasOwnProperty(j) && cbs[j] === cb) { 1576 jsTemp.push(j); 1577 } 1578 } 1579 1580 for (i=0; i<jsTemp.length; i++) { 1581 j = jsTemp[i]; 1582 delete cbs[j]; 1583 } 1584 }, 1585 1586 /* private */ 1587 _pools : [new sjcl.hash.sha256()], 1588 _poolEntropy : [0], 1589 _reseedCount : 0, 1590 _robins : {}, 1591 _eventId : 0, 1592 1593 _collectorIds : {}, 1594 _collectorIdNext : 0, 1595 1596 _strength : 0, 1597 _poolStrength : 0, 1598 _nextReseed : 0, 1599 _key : [0,0,0,0,0,0,0,0], 1600 _counter : [0,0,0,0], 1601 _cipher : undefined, 1602 _defaultParanoia : 6, 1603 1604 /* event listener stuff */ 1605 _collectorsStarted : false, 1606 _callbacks : {progress: {}, seeded: {}}, 1607 _callbackI : 0, 1608 1609 /* constants */ 1610 _NOT_READY : 0, 1611 _READY : 1, 1612 _REQUIRES_RESEED : 2, 1613 1614 _MAX_WORDS_PER_BURST : 65536, 1615 _PARANOIA_LEVELS : [0,48,64,96,128,192,256,384,512,768,1024], 1616 _MILLISECONDS_PER_RESEED : 30000, 1617 _BITS_PER_RESEED : 80, 1618 1619 /** Generate 4 random words, no reseed, no gate. 1620 * @private 1621 */ 1622 _gen4words: function () { 1623 for (var i=0; i<4; i++) { 1624 this._counter[i] = this._counter[i]+1 | 0; 1625 if (this._counter[i]) { break; } 1626 } 1627 return this._cipher.encrypt(this._counter); 1628 }, 1629
1630 /* Rekey the AES instance with itself after a request, or every _MAX_WORDS_PER_BURST words. 1631 * @private 1632 */ 1633 _gate: function () { 1634 this._key = this._gen4words().concat(this._gen4words()); 1635 this._cipher = new sjcl.cipher.aes(this._key); 1636 }, 1637 1638 /** Reseed the generator with the given words 1639 * @private 1640 */ 1641 _reseed: function (seedWords) { 1642 this._key = sjcl.hash.sha256.hash(this._key.concat(seedWords)); 1643 this._cipher = new sjcl.cipher.aes(this._key); 1644 for (var i=0; i<4; i++) { 1645 this._counter[i] = this._counter[i]+1 | 0; 1646 if (this._counter[i]) { break; } 1647 } 1648 }, 1649 1650 /** reseed the data from the entropy pools 1651 * @param full If set, use all the entropy pools in the reseed. 1652 */ 1653 _reseedFromPools: function (full) { 1654 var reseedData = [], strength = 0, i; 1655 1656 this._nextReseed = reseedData[0] = 1657 (new Date()).valueOf() + this._MILLISECONDS_PER_RESEED; 1658 1659 for (i=0; i<16; i++) { 1660 /* On some browsers, this is cryptographically random. So we might 1661 * as well toss it in the pot and stir... 1662 */ 1663 reseedData.push(Math.random()*0x100000000|0); 1664 } 1665 1666 for (i=0; i<this._pools.length; i++) { 1667 reseedData = reseedData.concat(this._pools[i].finalize()); 1668 strength += this._poolEntropy[i]; 1669 this._poolEntropy[i] = 0; 1670 1671 if (!full && (this._reseedCount & (1<<i))) { break; } 1672 } 1673 1674 /* if we used the last pool, push a new one onto the stack */ 1675 if (this._reseedCount >= 1 << this._pools.length) { 1676 this._pools.push(new sjcl.hash.sha256()); 1677 this._poolEntropy.push(0); 1678 } 1679 1680 /* how strong was this reseed? */ 1681 this._poolStrength -= strength; 1682 if (strength > this._strength) { 1683 this._strength = strength; 1684 } 1685 1686 this._reseedCount ++; 1687 this._reseed(reseedData); 1688 }, 1689 1690 _mouseCollector: function (ev) { 1691 var x = ev.x || ev.clientX || ev.offsetX || 0, y = ev.y || ev.clientY || ev.offsetY || 0; 1692 sjcl.random.addEntropy([x,y], 2, "mouse"); 1693 }, 1694
1695 _loadTimeCollector: function (ev) { 1696 sjcl.random.addEntropy((new Date()).valueOf(), 2, "loadtime"); 1697 }, 1698 1699 _fireEvent: function (name, arg) { 1700 var j, cbs=sjcl.random._callbacks[name], cbsTemp=[]; 1701 /* TODO: there is a race condition between removing collectors and firing them */ 1702 1703 /* I'm not sure if this is necessary; in C++, iterating over a 1704 * collection and modifying it at the same time is a no-no. 1705 */ 1706 1707 for (j in cbs) { 1708 if (cbs.hasOwnProperty(j)) { 1709 cbsTemp.push(cbs[j]); 1710 } 1711 } 1712 1713 for (j=0; j<cbsTemp.length; j++) { 1714 cbsTemp[j](arg); 1715 } 1716 } 1717}; 1718 1719(function(){ 1720 try { 1721 // get cryptographically strong entropy in Webkit 1722 var ab = new Uint32Array(32); 1723 crypto.getRandomValues(ab); 1724 sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues"); 1725 } catch (e) { 1726 // no getRandomValues :-( 1727 } 1728})(); 1729/** @fileOverview Convenince functions centered around JSON encapsulation. 1730 * 1731 * @author Emily Stark 1732 * @author Mike Hamburg 1733 * @author Dan Boneh 1734 */ 1735 1736 /** @namespace JSON encapsulation */ 1737 sjcl.json = { 1738 /** Default values for encryption */ 1739 defaults: { v:1, iter:1000, ks:128, ts:64, mode:"ccm", adata:"", cipher:"aes" }, 1740 1741 /** Simple encryption function. 1742 * @param {String|bitArray} password The password or key. 1743 * @param {String} plaintext The data to encrypt. 1744 * @param {Object} [params] The parameters including tag, iv and salt. 1745 * @param {Object} [rp] A returned version with filled-in parameters. 1746 * @return {String} The ciphertext. 1747 * @throws {sjcl.exception.invalid} if a parameter is invalid. 1748 */ 1749 encrypt: function (password, plaintext, params, rp) { 1750 params = params || {}; 1751 rp = rp || {}; 1752 1753 var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) }, 1754 j.defaults), tmp, prp, adata; 1755 j._add(p, params); 1756 adata = p.adata; 1757 if (typeof p.salt === "string") { 1758 p.salt = sjcl.codec.base64.toBits(p.salt); 1759 } 1760 if (typeof p.iv === "string") { 1761 p.iv = sjcl.codec.base64.toBits(p.iv); 1762 } 1763 1764 if (!sjcl.mode[p.mode] || 1765 !sjcl.cipher[p.cipher] || 1766 (typeof password === "string" && p.iter <= 100) || 1767 (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) || 1768 (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) || 1769 (p.iv.length < 2 || p.iv.length > 4)) { 1770 throw new sjcl.exception.invalid("json encrypt: invalid parameters"); 1771 } 1772 1773 if (typeof password === "string") { 1774 tmp = sjcl.misc.cachedPbkdf2(password, p); 1775 password = tmp.key.slice(0,p.ks/32); 1776 p.salt = tmp.salt; 1777 } 1778 if (typeof plaintext === "string") { 1779 plaintext = sjcl.codec.utf8String.toBits(plaintext); 1780 } 1781 if (typeof adata === "string") { 1782 adata = sjcl.codec.utf8String.toBits(adata); 1783 } 1784 prp = new sjcl.cipher[p.cipher](password); 1785 1786 /* return the json data */ 1787 j._add(rp, p); 1788 rp.key = password; 1789 1790 /* do the encryption */ 1791 p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); 1792 1793 //return j.encode(j._subtract(p, j.defaults)); 1794 return j.encode(p); 1795 }, 1796 1797 /** Simple decryption function. 1798 * @param {String|bitArray} password The password or key. 1799 * @param {String} ciphertext The ciphertext to decrypt. 1800 * @param {Object} [params] Additional non-default parameters. 1801 * @param {Object} [rp] A returned object with filled parameters. 1802 * @return {String} The plaintext. 1803 * @throws {sjcl.exception.invalid} if a parameter is invalid. 1804 * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. 1805 */ 1806 decrypt: function (password, ciphertext, params, rp) { 1807 params = params || {}; 1808 rp = rp || {}; 1809 1810 var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp, adata=p.adata; 1811 if (typeof p.salt === "string") { 1812 p.salt = sjcl.codec.base64.toBits(p.salt); 1813 } 1814 if (typeof p.iv === "string") { 1815 p.iv = sjcl.codec.base64.toBits(p.iv); 1816 } 1817 1818 if (!sjcl.mode[p.mode] || 1819 !sjcl.cipher[p.cipher] || 1820 (typeof password === "string" && p.iter <= 100) || 1821 (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) || 1822 (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) || 1823 (!p.iv) || 1824 (p.iv.length < 2 || p.iv.length > 4)) { 1825 throw new sjcl.exception.invalid("json decrypt: invalid parameters"); 1826 } 1827 1828 if (typeof password === "string") { 1829 tmp = sjcl.misc.cachedPbkdf2(password, p); 1830 password = tmp.key.slice(0,p.ks/32); 1831 p.salt = tmp.salt; 1832 } 1833 if (typeof adata === "string") { 1834 adata = sjcl.codec.utf8String.toBits(adata); 1835 }
1836 prp = new sjcl.cipher[p.cipher](password); 1837 1838 /* do the decryption */ 1839 ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); 1840 1841 /* return the json data */ 1842 j._add(rp, p); 1843 rp.key = password; 1844 1845 return sjcl.codec.utf8String.fromBits(ct); 1846 }, 1847 1848 /** Encode a flat structure into a JSON string. 1849 * @param {Object} obj The structure to encode. 1850 * @return {String} A JSON string. 1851 * @throws {sjcl.exception.invalid} if obj has a non-alphanumeric property. 1852 * @throws {sjcl.exception.bug} if a parameter has an unsupported type. 1853 */ 1854 encode: function (obj) { 1855 var i, out='{', comma=''; 1856 for (i in obj) { 1857 if (obj.hasOwnProperty(i)) { 1858 if (!i.match(/^[a-z0-9]+$/i)) { 1859 throw new sjcl.exception.invalid("json encode: invalid property name"); 1860 } 1861 out += comma + '"' + i + '":'; 1862 comma = ','; 1863 1864 switch (typeof obj[i]) { 1865 case 'number': 1866 case 'boolean': 1867 out += obj[i]; 1868 break; 1869 1870 case 'string': 1871 out += '"' + escape(obj[i]) + '"'; 1872 break; 1873 1874 case 'object': 1875 out += '"' + sjcl.codec.base64.fromBits(obj[i],1) + '"'; 1876 break; 1877 1878 default: 1879 throw new sjcl.exception.bug("json encode: unsupported type"); 1880 } 1881 } 1882 } 1883 return out+'}'; 1884 }, 1885 1886 /** Decode a simple (flat) JSON string into a structure. The ciphertext, 1887 * adata, salt and iv will be base64-decoded. 1888 * @param {String} str The string. 1889 * @return {Object} The decoded structure. 1890 * @throws {sjcl.exception.invalid} if str isn't (simple) JSON. 1891 */ 1892 decode: function (str) { 1893 str = str.replace(/\s/g,''); 1894 if (!str.match(/^\{.*\}$/)) { 1895 throw new sjcl.exception.invalid("json decode: this isn't json!"); 1896 } 1897 var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m; 1898 for (i=0; i<a.length; i++) { 1899 if (!(m=a[i].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) { 1900 throw new sjcl.exception.invalid("json decode: this isn't json!"); 1901 } 1902 if (m[3]) { 1903 out[m[2]] = parseInt(m[3],10); 1904 } else { 1905 out[m[2]] = m[2].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); 1906 } 1907 } 1908 return out; 1909 }, 1910 1911 /** Insert all elements of src into target, modifying and returning target. 1912 * @param {Object} target The object to be modified. 1913 * @param {Object} src The object to pull data from. 1914 * @param {boolean} [requireSame=false] If true, throw an exception if any field of target differs from corresponding field of src. 1915 * @return {Object} target. 1916 * @private 1917 */ 1918 _add: function (target, src, requireSame) { 1919 if (target === undefined) { target = {}; } 1920 if (src === undefined) { return target; } 1921 var i; 1922 for (i in src) { 1923 if (src.hasOwnProperty(i)) { 1924 if (requireSame && target[i] !== undefined && target[i] !== src[i]) { 1925 throw new sjcl.exception.invalid("required parameter overridden"); 1926 } 1927 target[i] = src[i]; 1928 } 1929 } 1930 return target; 1931 }, 1932 1933 /** Remove all elements of minus from plus. Does not modify plus. 1934 * @private 1935 _subtract: function (plus, minus) { 1936 var out = {}, i; 1937 1938 for (i in plus) { 1939 if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) { 1940 out[i] = plus[i]; 1941 } 1942 } 1943 1944 return out; 1945 }, 1946 */ 1947 1948 /** Return only the specified elements of src. 1949 * @private 1950 */ 1951 _filter: function (src, filter) { 1952 var out = {}, i; 1953 for (i=0; i<filter.length; i++) { 1954 if (src[filter[i]] !== undefined) { 1955 out[filter[i]] = src[filter[i]]; 1956 } 1957 } 1958 return out; 1959 } 1960}; 1961 1962/** Simple encryption function; convenient shorthand for sjcl.json.encrypt. 1963 * @param {String|bitArray} password The password or key. 1964 * @param {String} plaintext The data to encrypt. 1965 * @param {Object} [params] The parameters including tag, iv and salt. 1966 * @param {Object} [rp] A returned version with filled-in parameters. 1967 * @return {String} The ciphertext. 1968 */ 1969sjcl.encrypt = sjcl.json.encrypt; 1970 1971/** Simple decryption function; convenient shorthand for sjcl.json.decrypt. 1972 * @param {String|bitArray} password The password or key. 1973 * @param {String} ciphertext The ciphertext to decrypt. 1974 * @param {Object} [params] Additional non-default parameters. 1975 * @param {Object} [rp] A returned object with filled parameters. 1976 * @return {String} The plaintext. 1977 */ 1978sjcl.decrypt = sjcl.json.decrypt; 1979 1980/** The cache for cachedPbkdf2. 1981 * @private 1982 */ 1983sjcl.misc._pbkdf2Cache = {}; 1984 1985/** Cached PBKDF2 key derivation. 1986 * @param {String} The password. 1987 * @param {Object} The derivation params (iteration count and optional salt). 1988 * @return {Object} The derived data in key, the salt in salt. 1989 */ 1990sjcl.misc.cachedPbkdf2 = function (password, obj) { 1991 var cache = sjcl.misc._pbkdf2Cache, c, cp, str, salt, iter; 1992 1993 obj = obj || {}; 1994 iter = obj.iter || 1000; 1995 1996 /* open the cache for this password and iteration count */ 1997 cp = cache[password] = cache[password] || {}; 1998 c = cp[iter] = cp[iter] || { firstSalt: (obj.salt && obj.salt.length) ? 1999 obj.salt.slice(0) : sjcl.random.randomWords(2,0) }; 2000 2001 salt = (obj.salt === undefined) ? c.firstSalt : obj.salt; 2002 2003 c[salt] = c[salt] || sjcl.misc.pbkdf2(password, salt, obj.iter); 2004 return { key: c[salt].slice(0), salt:salt.slice(0) }; 2005}; 2006 2007 2008 2009 2010var localStorage = window.localStorage; 2011 2012function cookieName(name) { 2013 if (! window.location) { 2014 return name; 2015 } 2016 2017 var port = window.location.port || 2018 (window.location.protocol === 'https:' ? 443 : 80); 2019 2020 return name + '_' + port; 2021} 2022 2023function fetchCookieValues(name) { 2024 var cookie = Cookie.fetch(cookieName(name)); 2025 2026 if (! cookie) { 2027 return {}; 2028 } 2029 2030 try { 2031 return JSON.parse(cookie.value); 2032 } 2033 catch (e) { 2034 return {
2035 encryptKey: cookie.value 2036 }; 2037 } 2038} 2039 2040function fillinDefaultCookieValues(values, o) { 2041 function generateKey() { 2042 return sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0)); 2043 } 2044 2045 var path = values.path, 2046 currentPath = extractPath(documentUrl()); 2047 if (! path || path.length > currentPath.length) { 2048 path = currentPath; 2049 } 2050 2051 return { 2052 encryptKey: values.encryptKey || generateKey(), 2053 storageKey: values.storageKey || generateKey(), 2054 domain: o.sessionDomain || values.domain || undefined, 2055 path: o.sessionPath || path 2056 }; 2057} 2058 2059function documentUrl() { 2060 if (! window.location) { 2061 return ''; 2062 } 2063 2064 var loc; 2065 2066 // IE may throw an exception when accessing 2067 // a field from window.location if document.domain has been set 2068 try { 2069 loc = window.location.href; 2070 } catch( e ) { 2071 // Use the href attribute of an A element 2072 // since IE will modify it given document.location 2073 loc = window.document.createElement( "a" ); 2074 loc.href = ""; 2075 loc = loc.href; 2076 } 2077 2078 return loc; 2079} 2080 2081function extractPath(url) { 2082 var urlRegexp = /^[\w.+-]+:(?:\/\/[^\/?#:]*(?::\d+|)|)(.*)\/[^\/]*$/, 2083 match = urlRegexp.exec(url.toLowerCase()); 2084 2085 return match ? match[1] : null; 2086} 2087 2088// DEPRECATED 2089// This method will be removed in future version. 2090function buildLocalStorageNames(name, path) { 2091 function buildName(path) { 2092 return name + ':' + path; 2093 } 2094 2095 var names = []; 2096 2097 if (! path) { 2098 return [name]; 2099 } 2100 2101 while (true) { 2102 names.push(buildName(path)); 2103 if (path === '/') { 2104 break; 2105 } 2106 path = path.replace(/[^\/]+\/$/, ''); 2107 } 2108 return names; 2109} 2110 2111// DEPRECATED 2112// This method will be removed in future version. 2113function localStorageNames(name, o) { 2114 return buildLocalStorageNames(name, o.sessionPath || extractPath(documentUrl())+"/"); 2115} 2116 2117if (! localStorage) { 2118 DataAPI.sessionStores['cookie-encrypted'] = { 2119 save: function(){}, 2120 fetch: function(){}, 2121 remove: function(){} 2122 }; 2123} 2124else { 2125 DataAPI.sessionStores['cookie-encrypted'] = { 2126 save: function(name, data, remember) { 2127 var expires = remember ? new Date(new Date().getTime() + 315360000000) : undefined, // after 10 years 2128 values = fillinDefaultCookieValues(fetchCookieValues(name), this.o); 2129 2130 Cookie.bake(cookieName(name), JSON.stringify(values), values.domain, values.path, expires); 2131 localStorage.setItem(values.storageKey, sjcl.encrypt(values.encryptKey, data)); 2132 }, 2133 fetch: function(name) { 2134 var values = fetchCookieValues(name), 2135 i, names, data; 2136 2137 // Backward compatibility 2138 if (! values.storageKey) { 2139 names = localStorageNames(name, this.o); 2140 for (i = 0; i < names.length; i++) { 2141 if (localStorage.getItem(names[i])) { 2142 values.storageKey = names[i]; 2143 break; 2144 } 2145 } 2146 } 2147 2148 data = localStorage.getItem(values.storageKey); 2149 2150 try { 2151 return sjcl.decrypt(values.encryptKey, data); 2152 } 2153 catch (e) { 2154 } 2155 2156 return null; 2157 }, 2158 remove: function(name) { 2159 var values = fillinDefaultCookieValues(fetchCookieValues(name), this.o); 2160 2161 Cookie.bake(cookieName(name), '', values.domain, values.path, new Date(0)); 2162 2163 if (values.storageKey) { 2164 localStorage.removeItem(values.storageKey); 2165 } 2166 } 2167 }; 2168} 2169 2170})(); 2171 2172 2173/** 2174 * Register callback to class. 2175 * @method on 2176 * @static 2177 * @param {String} key Event name 2178 * @param {Function} callback Callback function 2179 * @category core 2180 * @example 2181 * var callback = function() { 2182 * // Do stuff 2183 * }; 2184 * DataAPI.on(eventName, callback); 2185 */ 2186DataAPI.on = function(key, callback) { 2187 if (! this.callbacks[key]) { 2188 this.callbacks[key] = []; 2189 } 2190 2191 this.callbacks[key].push(callback); 2192}; 2193 2194/** 2195 * Deregister callback from class. 2196 * @method off 2197 * @static 2198 * @param {String} key Event name 2199 * @param {Function} callback Callback function 2200 * @category core 2201 * @example 2202 * DataAPI.off(eventName, callback); 2203 */ 2204DataAPI.off = function(key, callback) { 2205 var i, callbacks; 2206 2207 if (callback) { 2208 callbacks = this.callbacks[key] || []; 2209 2210 for (i = 0; i < callbacks.length; i++) { 2211 if (callbacks[i] === callback) { 2212 callbacks.splice(i, 1); 2213 break; 2214 } 2215 } 2216 } 2217 else { 2218 delete this.callbacks[key]; 2219 } 2220}; 2221 2222/** 2223 * Register formats that serialize data. 2224 * @method registerFormat
2225 * @static 2226 * @param {String} key Format name 2227 * @param {Object} spec 2228 * @param {String} spec.fileExtension Extension 2229 * @param {String} spec.mimeType MIME type 2230 * @param {String} spec.serialize Serializing method 2231 * @param {String} spec.unserialize Unserializing method 2232 * @category core 2233 */ 2234DataAPI.registerFormat = function(key, spec) { 2235 this.formats[key] = spec; 2236}; 2237 2238/** 2239 * Register session store. 2240 * @method registerSessionStore 2241 * @static 2242 * @param {String} key Session store name 2243 * @param {Object} spec 2244 * @param {String} spec.save Saving method 2245 * @param {String} spec.restore Restoring method 2246 * @param {String} spec.dispose Disposing method 2247 * @category core 2248 */ 2249DataAPI.registerSessionStore = function(key, spec) { 2250 this.sessionStores[key] = spec; 2251}; 2252 2253/** 2254 * Get default format of this class. 2255 * @method getDefaultFormat 2256 * @static 2257 * @return {Object} Format 2258 * @category core 2259 */ 2260DataAPI.getDefaultFormat = function() { 2261 return this.formats[this.defaultFormat]; 2262}; 2263 2264/** 2265 * Get default session store of this class. 2266 * @method getDefaultSessionStore 2267 * @static 2268 * @return {Object} Format 2269 * @category core 2270 */ 2271DataAPI.getDefaultSessionStore = function() { 2272 return this.sessionStores[this.defaultSessionStore]; 2273}; 2274 2275DataAPI.prototype = { 2276 constructor: DataAPI.prototype.constructor, 2277 2278 _initOptions: function() { 2279 this._initCrossDomainOption(); 2280 }, 2281 2282 _initCrossDomainOption: function() { 2283 var loc, locParts, baseUrl, baseParts, 2284 urlRegexp = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/; 2285 2286 if ( window.document && typeof this.o.crossOrigin === 'undefined') { 2287 // IE may throw an exception when accessing 2288 // a field from window.location if document.domain has been set 2289 try { 2290 loc = window.location.href; 2291 } catch( e ) { 2292 // Use the href attribute of an A element 2293 // since IE will modify it given document.location 2294 loc = window.document.createElement( "a" ); 2295 loc.href = ""; 2296 loc = loc.href; 2297 } 2298 locParts = urlRegexp.exec( loc.toLowerCase() ) || []; 2299 2300 baseUrl = this.o.baseUrl.replace(/^\/\//, locParts[1]).toLowerCase(); 2301 baseParts = urlRegexp.exec( baseUrl ); 2302 2303 this.o.crossOrigin = !!( baseParts && 2304 ( baseParts[ 1 ] !== locParts[ 1 ] || baseParts[ 2 ] !== locParts[ 2 ] || 2305 ( baseParts[ 3 ] || ( baseParts[ 1 ] === "http:" ? "80" : "443" ) ) !== 2306 ( locParts[ 3 ] || ( locParts[ 1 ] === "http:" ? "80" : "443" ) ) ) 2307 ); 2308 } 2309 }, 2310 2311 /** 2312 * Get authorization URL. 2313 * @method getAuthorizationUrl 2314 * @param {String} redirectUrl The user is redirected to this URL with "#_login" if authorization succeeded. 2315 * @return {String} Authorization URL 2316 * @category core 2317 */ 2318 getAuthorizationUrl: function(redirectUrl) { 2319 return this.o.baseUrl.replace(/\/*$/, '/') + 2320 'v' + this.getVersion() + 2321 '/authorization' + 2322 '?clientId=' + this.o.clientId + 2323 '&redirectUrl=' + redirectUrl; 2324 }, 2325 2326 _getCurrentEpoch: function() { 2327 return Math.round(new Date().getTime() / 1000); 2328 }, 2329 2330 _getNextIframeName: function() { 2331 return this.constructor.iframePrefix + (++this.iframeId); 2332 }, 2333 2334 /** 2335 * Get API version. 2336 * @method getVersion 2337 * @return {String} API version 2338 * @category core 2339 */ 2340 getVersion: function() { 2341 return this.constructor.version; 2342 }, 2343 2344 /** 2345 * Get application key of this object. 2346 * @method getAppKey 2347 * @return {String} Application key 2348 * This value is used for the session store. 2349 * @category core 2350 */ 2351 getAppKey: function() { 2352 return this.constructor.accessTokenKey + '_' + this.o.clientId; 2353 }, 2354 2355 _findFormatInternal: function(mimeType) { 2356 if (! mimeType) { 2357 return null; 2358 } 2359 2360 for (var k in this.constructor.formats) { 2361 if (this.constructor.formats[k].mimeType === mimeType) { 2362 return this.constructor.formats[k]; 2363 } 2364 } 2365 2366 return null; 2367 }, 2368 2369 /** 2370 * Get format by MIME Type. 2371 * @method findFormat 2372 * @param {String} mimeType MIME Type 2373 * @return {Object|null} Format. Return null if any format is not found. 2374 * @category core 2375 */ 2376 findFormat: function(mimeType) { 2377 var format = this._findFormatInternal(mimeType); 2378 if (! format && mimeType.indexOf(';')) { 2379 format = this._findFormatInternal(mimeType.replace(/\s*;.*/, '')); 2380 } 2381 2382 return format; 2383 }, 2384 2385 /** 2386 * Get current format of this object. 2387 * @method getCurrentFormat 2388 * @return {Object} Format 2389 * @category core 2390 */ 2391 getCurrentFormat: function() { 2392 return this.constructor.formats[this.o.format] || 2393 this.constructor.getDefaultFormat(); 2394 }, 2395 2396 /** 2397 * Serialize data. 2398 * @method serializeData 2399 * @param {Object} data The data to serialize 2400 * @return {String} Serialized data 2401 * @category core 2402 */ 2403 serializeData: function() { 2404 return this.getCurrentFormat().serialize.apply(this, arguments); 2405 }, 2406 2407 /** 2408 * Unserialize data. 2409 * @method unserializeData 2410 * @param {String} data The data to unserialize 2411 * @return {Object} Unserialized data 2412 * @category core 2413 */ 2414 unserializeData: function() { 2415 return this.getCurrentFormat().unserialize.apply(this, arguments); 2416 }, 2417 2418 /** 2419 * Get current session store of this object. 2420 * @method getCurrentSessionStore 2421 * @return {Object} Session store 2422 * @category core 2423 */ 2424 getCurrentSessionStore: function() { 2425 return this.constructor.sessionStores[this.o.sessionStore] || 2426 this.constructor.getDefaultSessionStore(); 2427 }, 2428 2429 /** 2430 * Save session data. 2431 * @method saveSessionData 2432 * @param {String} name The name of session 2433 * @param {Object} data The data to save 2434 * @category core 2435 */ 2436 saveSessionData: function() { 2437 return this.getCurrentSessionStore().save.apply(this, arguments); 2438 }, 2439 2440 /** 2441 * Fetch session data. 2442 * @method fetchSessionData 2443 * @param {String} name The name of session 2444 * @return {String} The data fetched 2445 * @category core 2446 */ 2447 fetchSessionData: function() { 2448 return this.getCurrentSessionStore().fetch.apply(this, arguments); 2449 }, 2450 2451 /** 2452 * Remove session data. 2453 * @method removeSessionData 2454 * @param {String} name The name of session 2455 * @category core 2456 */ 2457 removeSessionData: function() { 2458 return this.getCurrentSessionStore().remove.apply(this, arguments); 2459 }, 2460 2461 /** 2462 * Store token data via current session store. 2463 * @method storeTokenData 2464 * @param {Object} tokenData The token data 2465 * @param {String} tokenData.accessToken access token 2466 * @param {String} tokenData.expiresIn The number of seconds 2467 * until access token becomes invalid 2468 * @param {String} [tokenData.sessionId] session ID 2469 * @category core 2470 */ 2471 storeTokenData: function(tokenData) { 2472 var oldData = this.getTokenData(); 2473 if (! tokenData.sessionId && oldData && oldData.sessionId) { 2474 tokenData.sessionId = oldData.sessionId; 2475 } 2476 2477 tokenData.startTime = this._getCurrentEpoch(); 2478 this.saveSessionData( 2479 this.getAppKey(), 2480 this.serializeData(tokenData), 2481 tokenData.sessionId && tokenData.remember 2482 ); 2483 this.tokenData = tokenData; 2484 }, 2485 2486 /** 2487 * Clear token data from object and session store. 2488 * @method clearTokenData 2489 * @category core 2490 */ 2491 clearTokenData: function() { 2492 this.removeSessionData(this.getAppKey()); 2493 this.tokenData = null; 2494 }, 2495 2496 _updateTokenFromDefaultCookie: function() { 2497 var defaultKey = this.constructor.accessTokenKey, 2498 defaultCookie = Cookie.fetch(defaultKey), 2499 defaultToken; 2500 2501 if (! defaultCookie) { 2502 return null; 2503 } 2504 2505 Cookie.bake(defaultKey, '', undefined, '/', new Date(0)); 2506 2507 try { 2508 defaultToken = this.unserializeData(defaultCookie.value); 2509 } 2510 catch (e) { 2511 return null; 2512 } 2513 2514 this.storeTokenData(defaultToken); 2515 return defaultToken; 2516 }, 2517 2518 _hasOneTimeToken: function() { 2519 return window.location && window.location.hash.indexOf('#_ott_') === 0; 2520 }, 2521 2522 _storeOneTimeToken: function() { 2523 var token, m; 2524 2525 if (! window.location) { 2526 return undefined; 2527 } 2528 2529 m = window.location.hash.match(/^#_ott_(.*)/); 2530 if (! m) { 2531 return undefined; 2532 } 2533 2534 token = { 2535 oneTimeToken: m[1] 2536 }; 2537 window.location.hash = '#_login'; 2538 2539 this.storeTokenData(token); 2540 return token; 2541 }, 2542 2543 /** 2544 * Get token data via current session store. 2545 * @method getTokenData 2546 * @return {Object} Token data 2547 * @category core 2548 */ 2549 getTokenData: function() { 2550 var token = this.tokenData; 2551 2552 if (! token) { 2553 if (window.location) { 2554 if (window.location.hash === '#_login') { 2555 try { 2556 token = this._updateTokenFromDefaultCookie(); 2557 } 2558 catch (e) { 2559 } 2560 } 2561 else if (this._hasOneTimeToken()) { 2562 token = this._storeOneTimeToken(); 2563 } 2564 } 2565 2566 if (! token) { 2567 try { 2568 token = this.unserializeData(this.fetchSessionData(this.getAppKey())); 2569 } 2570 catch (e) { 2571 } 2572 } 2573 } 2574 2575 if (token && 2576 'startTime' in token &&
2577 'expiresIn' in token && 2578 (token.startTime + token.expiresIn < this._getCurrentEpoch())) { 2579 delete token.accessToken; 2580 delete token.startTime; 2581 delete token.expiresIn; 2582 } 2583 2584 return this.tokenData = token || null; 2585 }, 2586 2587 /** 2588 * Get authorization request header. 2589 * @method getAuthorizationHeader 2590 * @return {String|null} Header string. Return null if api object has no token. 2591 * @category core 2592 */ 2593 getAuthorizationHeader: function(key) { 2594 var tokenData = this.getTokenData(); 2595 if (tokenData) { 2596 return 'MTAuth ' + key + '=' + (tokenData[key] || ''); 2597 } 2598 2599 return ''; 2600 }, 2601 2602 /** 2603 * Bind parameters to route spec. 2604 * @method bindEndpointParams 2605 * @param {String} route Specification of route 2606 * @param {Object} params parameters 2607 * @param {Number|Object|Function} params.{key} Value to bind 2608 * @return {String} Endpoint to witch parameters was bound 2609 * @example 2610 * api.bindEndpointParams('/sites/:site_id/entries/:entry_id/comments/:comment_id', { 2611 * blog_id: 1, 2612 * entry_id: {id: 1}, 2613 * comment_id: functioin(){ return 1; } 2614 * }); 2615 * @category core 2616 */ 2617 bindEndpointParams: function(route, params) { 2618 var k, v; 2619 2620 for (k in params) { 2621 v = params[k]; 2622 if (typeof v === 'object') { 2623 if (typeof v.id === 'function') { 2624 v = v.id(); 2625 } 2626 else { 2627 v = v.id; 2628 } 2629 } 2630 if (typeof v === 'function') { 2631 v = v(); 2632 } 2633 route = route.replace(new RegExp(':' + k), v); 2634 } 2635 return route; 2636 }, 2637 2638 _isElement: function(e, name) { 2639 if (! e || typeof e !== 'object') { 2640 return false; 2641 } 2642 var n = e.nodeName; 2643 return n && n.toLowerCase() === name; 2644 }, 2645 2646 _isFormElement: function(e) { 2647 return this._isElement(e, 'form'); 2648 }, 2649 2650 _isInputElement: function(e) { 2651 return this._isElement(e, 'input'); 2652 }, 2653 2654 _isFileInputElement: function(e) { 2655 return this._isInputElement(e) && e.type.toLowerCase() === 'file'; 2656 }, 2657 2658 _serializeObject: function(v) { 2659 function f(n) { 2660 return n < 10 ? '0' + n : n; 2661 } 2662 2663 function iso8601Date(v) { 2664 if (! isFinite(v.valueOf())) { 2665 return ''; 2666 } 2667 2668 var off, 2669 tz = v.getTimezoneOffset(); 2670 if(tz === 0) { 2671 off = 'Z'; 2672 } 2673 else { 2674 off = (tz > 0 ? '-': '+'); 2675 tz = Math.abs(tz); 2676 off += f(Math.floor(tz / 60)) + ':' + f(tz % 60); 2677 } 2678 2679 return v.getFullYear() + '-' + 2680 f(v.getMonth() + 1) + '-' + 2681 f(v.getDate()) + 'T' + 2682 f(v.getHours()) + ':' + 2683 f(v.getMinutes()) + ':' + 2684 f(v.getSeconds()) + off; 2685 } 2686 2687 if (this._isFormElement(v)) { 2688 v = this._serializeFormElementToObject(v); 2689 } 2690 2691 var type = typeof v; 2692 if (type === 'undefined' || v === null || (type === 'number' && ! isFinite(v))) { 2693 return ''; 2694 } 2695 else if (type === 'boolean') { 2696 return v ? '1' : ''; 2697 } 2698 else if (v instanceof Date) { 2699 return iso8601Date(v); 2700 } 2701 else if (window.File && v instanceof window.File) { 2702 return v; 2703 } 2704 else if (this._isFileInputElement(v)) { 2705 return v.files[0]; 2706 } 2707 else if (type === 'object') { 2708 return this.serializeData(v, function(key, value) { 2709 if (this[key] instanceof Date) { 2710 return iso8601Date(this[key]); 2711 } 2712 return value; 2713 }); 2714 } 2715 else { 2716 return v; 2717 } 2718 }, 2719 2720 _serializeParams: function(params) { 2721 if (! params) { 2722 return params; 2723 } 2724 if (typeof params === 'string') { 2725 return params; 2726 } 2727 if (this._isFormElement(params)) { 2728 params = this._serializeFormElementToObject(params); 2729 } 2730 2731 var k, 2732 str = ''; 2733 for (k in params) { 2734 if (! params.hasOwnProperty(k)) { 2735 continue; 2736 } 2737 if (str) { 2738 str += '&'; 2739 } 2740 2741 str += 2742 encodeURIComponent(k) + '=' + 2743 encodeURIComponent(this._serializeObject(params[k])); 2744 } 2745 return str; 2746 }, 2747 2748 _unserializeParams: function(params) { 2749 if (typeof params !== 'string') { 2750 return params; 2751 } 2752 2753 var i, pair, 2754 data = {}, 2755 values = params.split('&'); 2756 2757 for(i = 0; i < values.length; i++) { 2758 pair = values[i].split('='); 2759 data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); 2760 } 2761 2762 return data; 2763 }, 2764 2765 _newXMLHttpRequestStandard: function() { 2766 try { 2767 return new window.XMLHttpRequest(); 2768 } catch( e ) {} 2769 }, 2770 2771 _newXMLHttpRequestActiveX: function() { 2772 try { 2773 return new window.ActiveXObject("Microsoft.XMLHTTP"); 2774 } catch( e ) {} 2775 }, 2776 2777 /** 2778 * Create XMLHttpRequest by higher browser compatibility way. 2779 * @method newXMLHttpRequest 2780 * @return {XMLHttpRequest} Created XMLHttpRequest 2781 * @category core 2782 */ 2783 newXMLHttpRequest: function() { 2784 return this._newXMLHttpRequestStandard() || 2785 this._newXMLHttpRequestActiveX() || 2786 false; 2787 }, 2788 2789 _findFileInput: function(params) { 2790 if (typeof params !== 'object') { 2791 return null; 2792 } 2793 2794 for (var k in params) { 2795 if (this._isFileInputElement(params[k])) { 2796 return params[k]; 2797 } 2798 } 2799 2800 return null; 2801 }, 2802 2803 _isEmptyObject: function(o) { 2804 if (! o) { 2805 return true; 2806 } 2807 2808 for (var k in o) { 2809 if (o.hasOwnProperty(k)) { 2810 return false;
2811 } 2812 } 2813 return true; 2814 }, 2815 2816 /** 2817 * Send request to specified URL with params via XMLHttpRequest. 2818 * @method sendXMLHttpRequest 2819 * @param {XMLHttpRequest} xhr XMLHttpRequest object to send request 2820 * @param {String} method Request method 2821 * @param {String} url Request URL 2822 * @param {String|FormData} params Parameters to send with request 2823 * @return {XMLHttpRequest} 2824 * @category core 2825 */ 2826 sendXMLHttpRequest: function(xhr, method, url, params, defaultHeaders) { 2827 var k, headers, uk; 2828 2829 xhr.open(method, url, this.o.async); 2830 for (k in defaultHeaders) { 2831 xhr.setRequestHeader(k, defaultHeaders[k]); 2832 } 2833 if (typeof params === 'string') { 2834 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 2835 } 2836 if (! this.o.crossOrigin) { 2837 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 2838 } 2839 2840 function normalizeHeaderKey(all, prefix, letter) { 2841 return prefix + letter.toUpperCase(); 2842 } 2843 if (params && params.getHeaders) { 2844 headers = params.getHeaders(); 2845 for (k in headers) { 2846 uk = k.replace(/(^|-)([a-z])/g, normalizeHeaderKey); 2847 xhr.setRequestHeader(uk, headers[k]); 2848 } 2849 } 2850 2851 xhr.send(params); 2852 2853 return xhr; 2854 }, 2855 2856 _serializeFormElementToObject: function(form) { 2857 var i, e, type, 2858 data = {}, 2859 submitterTypes = /^(?:submit|button|image|reset)$/i, 2860 submittable = /^(?:input|select|textarea|keygen)/i, 2861 checkableTypes = /^(?:checkbox|radio)$/i; 2862 2863 for (i = 0; i < form.elements.length; i++) { 2864 e = form.elements[i]; 2865 type = e.type; 2866 2867 if ( 2868 ! e.name || 2869 e.disabled || 2870 ! submittable.test(e.nodeName) || 2871 submitterTypes.test(type) || 2872 (checkableTypes.test(type) && ! e.checked) 2873 ) { 2874 continue; 2875 } 2876 2877 if (this._isFileInputElement(e)) { 2878 data[e.name] = e; 2879 } 2880 else { 2881 data[e.name] = this._elementValue(e); 2882 } 2883 } 2884 2885 return data; 2886 }, 2887 2888 _elementValue: function(e) { 2889 if (e.nodeName.toLowerCase() === 'select') { 2890 var value, option, 2891 options = e.options, 2892 index = e.selectedIndex, 2893 one = e.type === "select-one" || index < 0, 2894 values = one ? null : [], 2895 max = one ? index + 1 : options.length, 2896 i = index < 0 ? 2897 max : 2898 one ? index : 0; 2899 2900 // Loop through all the selected options 2901 for ( ; i < max; i++ ) { 2902 option = options[ i ]; 2903 2904 // oldIE doesn't update selected after form reset (#2551) 2905 if ( ( option.selected || i === index ) && 2906 // Don't return options that are disabled or in a disabled optgroup 2907 ( !option.parentNode.disabled || option.parentNode.nodeName.toLowerCase() !== "optgroup" ) ) { 2908 2909 // Get the specific value for the option 2910 value = option.attributes.value; 2911 if (!value || value.specified) { 2912 value = option.value; 2913 } 2914 else { 2915 value = e.text; 2916 } 2917 2918 // We don't need an array for one selects 2919 if ( one ) { 2920 return value; 2921 } 2922 2923 // Multi-Selects return an array 2924 values.push( value ); 2925 } 2926 } 2927 2928 return values; 2929 } 2930 else { 2931 return e.value; 2932 } 2933 }, 2934 2935 /** 2936 * Execute function with specified options. 2937 * @method withOptions 2938 * @param {option} option Option to overwrite 2939 * @param {Function}
2939 func Function to execute 2940 * @return Return value of specified func 2941 * @example 2942 * // The DataAPI object is created with {async: true} 2943 * api.withOptions({async: false}, function() { 2944 * api.listEntries(1, function() { 2945 * // This is executed synchronously 2946 * }); 2947 * }); 2948 * api.listEntries(1, function() { 2949 * // This is executed asynchronously 2950 * }); 2951 * @category core 2952 */ 2953 withOptions: function(option, func) { 2954 var k, result, 2955 originalOption = this.o, 2956 o = {}; 2957 2958 for (k in originalOption) { 2959 o[k] = originalOption[k]; 2960 } 2961 for (k in option) { 2962 o[k] = option[k]; 2963 } 2964 2965 this.o = o; 2966 this._initOptions(); 2967 2968 result = func.apply(this); 2969 2970 this.o = originalOption; 2971 this._initOptions(); 2972 2973 return result; 2974 }, 2975 2976 _requestVia: function() { 2977 return (window.XDomainRequest && 2978 this.o.crossOrigin && 2979 /msie (8|9)\./i.test(window.navigator.appVersion)) ? 'xdr' : 'xhr'; 2980 }, 2981 2982 /** 2983 * Send a request to the endpoint with specified parameters. 2984 * @method request 2985 * @param {String} method Request method 2986 * @param {String} endpoint Endpoint to request 2987 * @param {String|Object} [queryParameter] 2988 * @param {String|Object|HTMLFormElement|FormData} [requestData] 2989 * @param {String|Object|HTMLFormElement} [requestData.{the-key-requires-json-text}] Can specify json-text value by string or object or HTMLFormElement. Serialize automatically if object or HTMLFormElement is passed. 2990 * @param {HTMLInputElement|File} [requestData.{the-key-requires-file}] Can specify file value by HTMLInputElement or File object. 2991 * @param {Function} [callback] 2992 * @return {XMLHttpRequest|null} Return XMLHttpRequest if request is sent 2993 * via XMLHttpRequest. Return null if request is not sent 2994 * via XMLHttpRequest (e.g. sent via iframe). 2995 * @category core 2996 */ 2997 request: function(method, endpoint) { 2998 var i, k, v, base, 2999 api = this, 3000 paramsList = [], 3001 params = null, 3002 callback = function(){}, 3003 xhr = null, 3004 xdr = null, 3005 via = this._requestVia(), 3006 tokenData = this.getTokenData(), 3007 authHeader = this.getAuthorizationHeader('accessToken'), 3008 currentFormat = this.getCurrentFormat(), 3009 originalMethod = method, 3010 originalArguments = Array.prototype.slice.call(arguments), 3011 defaultParams = {}, 3012 defaultHeaders = {}; 3013 3014 function serializeParams(params) { 3015 var k, data; 3016 3017 if (! api.o.disableFormData && window.FormData) { 3018 if (params instanceof window.FormData) { 3019 return params; 3020 } 3021 else if (api._isFormElement(params)) { 3022 return new window.FormData(params); 3023 } 3024 else if (window.FormData && typeof params === 'object') { 3025 data = new window.FormData(); 3026 for (k in params) { 3027 data.append(k, api._serializeObject(params[k])); 3028 } 3029 return data; 3030 } 3031 } 3032 3033 3034 if (api._isFormElement(params)) { 3035 params = api._serializeFormElementToObject(params); 3036 for (k in params) { 3037 if (params[k] instanceof Array) { 3038 params[k] = params[k].join(','); 3039 } 3040 } 3041 } 3042 3043 if (api._findFileInput(params)) { 3044 via = 'iframe'; 3045 3046 data = {}; 3047 for (k in params) { 3048 if (api._isFileInputElement(params[k])) { 3049 data[k] = params[k]; 3050 } 3051 else { 3052 data[k] = api._serializeObject(params[k]); 3053 } 3054 } 3055 params = data; 3056 } 3057 else if (typeof params !== 'string') { 3058 params = api._serializeParams(params); 3059 } 3060 3061 return params; 3062 } 3063 3064 function runCallback(response) { 3065 var status = callback(response); 3066 if (status !== false) { 3067 if (response.error) { 3068 api.trigger('error', response); 3069 } 3070 } 3071 return status; 3072 } 3073 3074 function needToRetry(response) { 3075 return response.error && 3076 response.error.code === 401 && 3077 endpoint !== '/token' && 3078 endpoint !== '/authentication'; 3079 } 3080 3081 function retryWithAuthentication() { 3082 api.request('POST', '/token', function(response) { 3083 if (response.error) { 3084 parseArguments(originalArguments); 3085 return runCallback(response); 3086 } 3087 else { 3088 api.storeTokenData(response); 3089 api.request.apply(api, originalArguments); 3090 return false;
3091 } 3092 }); 3093 } 3094 3095 function appendParamsToURL(base, params) { 3096 if (base.indexOf('?') === -1) { 3097 base += '?'; 3098 } 3099 else { 3100 base += '&'; 3101 } 3102 return base + api._serializeParams(params); 3103 } 3104 3105 function parseArguments(args) { 3106 for (i = 2; i < args.length; i++) { 3107 v = args[i]; 3108 switch (typeof v) { 3109 case 'function': 3110 callback = v; 3111 break; 3112 case 'object': 3113 if ( 3114 v && 3115 ! v.nodeName && 3116 ((window.ActiveXObject && v instanceof window.ActiveXObject) || 3117 (window.XMLHttpRequest && v instanceof window.XMLHttpRequest) || 3118 (window.XDomainRequest && v instanceof window.XDomainRequest)) 3119 ) { 3120 if (window.XDomainRequest && v instanceof window.XDomainRequest) { 3121 xdr = v; 3122 } 3123 else { 3124 xhr = v; 3125 } 3126 } 3127 else { 3128 paramsList.push(v); 3129 } 3130 break; 3131 case 'string': 3132 paramsList.push(api._unserializeParams(v)); 3133 break; 3134 } 3135 } 3136 } 3137 3138 if (! this.o.withoutAuthorization && 3139 tokenData && 3140 ! tokenData.accessToken && 3141 endpoint !== '/token' && 3142 endpoint !== '/authentication' 3143 ) { 3144 return retryWithAuthentication(); 3145 } 3146 3147 if (authHeader) { 3148 defaultHeaders['X-MT-Authorization'] = authHeader; 3149 } 3150 3151 if (endpoint === '/token' || endpoint === '/authentication') { 3152 if (tokenData && tokenData.oneTimeToken) { 3153 defaultHeaders['X-MT-Authorization'] = 3154 api.getAuthorizationHeader('oneTimeToken'); 3155 delete tokenData.oneTimeToken; 3156 } 3157 else if (tokenData && tokenData.sessionId) { 3158 defaultHeaders['X-MT-Authorization'] = 3159 api.getAuthorizationHeader('sessionId'); 3160 } 3161 else if (endpoint === '/token' && originalMethod.toLowerCase() === 'post') { 3162 delete defaultHeaders['X-MT-Authorization']; 3163 } 3164 defaultParams.clientId = api.o.clientId; 3165 } 3166 3167 if (this.o.withoutAuthorization) { 3168 delete defaultHeaders['X-MT-Authorization']; 3169 } 3170 3171 if (this.o.suppressResponseCodes || 3172 (typeof this.o.suppressResponseCodes === 'undefined' && via === 'xdr') 3173 ) { 3174 defaultParams.suppressResponseCodes = true; 3175 } 3176 3177 if (! this.o.cache) { 3178 defaultParams._ = new Date().getTime(); 3179 } 3180 3181 if (currentFormat !== this.constructor.getDefaultFormat()) { 3182 defaultParams.format = currentFormat.fileExtension; 3183 } 3184 3185 if (method.match(/^(put|delete)$/i)) { 3186 defaultParams.__method = method; 3187 method = 'POST'; 3188 } 3189 3190 parseArguments(arguments); 3191 3192 if (paramsList.length && (method.toLowerCase() === 'get' || paramsList.length >= 2)) { 3193 endpoint = appendParamsToURL(endpoint, paramsList.shift()); 3194 } 3195 3196 if (paramsList.length) { 3197 params = paramsList.shift(); 3198 } 3199 3200 if (! this._isEmptyObject(defaultParams)) { 3201 if (method.toLowerCase() === 'get') { 3202 endpoint = appendParamsToURL(endpoint, defaultParams); 3203 } 3204 else if (window.FormData && params && params instanceof window.FormData) { 3205 for (k in defaultParams) { 3206 params.append(k, defaultParams[k]); 3207 } 3208 } 3209 else { 3210 params = params || {}; 3211 for (k in defaultParams) { 3212 params[k] = defaultParams[k]; 3213 } 3214 } 3215 } 3216 3217 params = serializeParams(params); 3218 3219 3220 base = this.o.baseUrl.replace(/\/*$/, '/') + 'v' + this.getVersion(); 3221 endpoint = endpoint.replace(/^\/*/, '/'); 3222 3223 3224 function responseCallback(contentType, responseText, status, statusText, cleanup) { 3225 var response, mimeType, format, callbackResult; 3226 3227 try { 3228 mimeType = contentType; 3229 format = api.findFormat(mimeType) || api.getCurrentFormat(); 3230 response = format.unserialize(responseText); 3231 } 3232 catch (e) { 3233 response = { 3234 error: { 3235 code: +status, 3236 message: statusText || 'Communication Error' 3237 } 3238 }; 3239 } 3240 3241 if (needToRetry(response)) { 3242 retryWithAuthentication(); 3243 if (cleanup) { 3244 cleanup(); 3245 } 3246 return false;
3247 } 3248 3249 if ((! response.error && 3250 endpoint === '/authentication' && 3251 originalMethod.toLowerCase() === 'delete') || 3252 (response.error && response.error.code === 401 && ( 3253 (endpoint === '/authentication' && 3254 originalMethod.toLowerCase() === 'post') || 3255 (endpoint === '/token' && 3256 originalMethod.toLowerCase() === 'post')))) { 3257 api.clearTokenData(); 3258 } 3259 else if (! response.error && ( 3260 (endpoint === '/authentication' && 3261 originalMethod.toLowerCase() === 'post') || 3262 (endpoint === '/token' && 3263 originalMethod.toLowerCase() === 'post'))) { 3264 api.storeTokenData(response); 3265 } 3266 3267 callbackResult = runCallback(response); 3268 3269 if (callbackResult !== false && 3270 response.error && response.error.code === 401 && 3271 endpoint !== '/authentication') { 3272 api.trigger('authorizationRequired', response); 3273 } 3274 } 3275 3276 if (via === 'xdr') { 3277 if (! this._isEmptyObject(defaultHeaders)) { 3278 throw 'Cannot set request header when sending via XDomainRequest'; 3279 } 3280 3281 xdr = xdr || new window.XDomainRequest(); 3282 xdr.onload = function() { 3283 responseCallback(xdr.contentType, xdr.responseText, 200); 3284 }; 3285 xdr.onerror = function() { 3286 responseCallback(xdr.contentType, xdr.responseText, 404); 3287 }; 3288 xdr.onprogress = function(){}; 3289 xdr.ontimeout = function() { 3290 responseCallback(xdr.contentType, xdr.responseText, 0); 3291 }; 3292 if (typeof this.o.timeout !== 'undefined') { 3293 xdr.timeout = this.o.timeout || Number.MAX_VALUE; 3294 } 3295 xdr.open( method, base + endpoint); 3296 xdr.send( api._serializeParams(params) || null ); 3297 } 3298 else if (via === 'xhr') { 3299 xhr = xhr || this.newXMLHttpRequest(); 3300 if (typeof this.o.timeout !== 'undefined') { 3301 xhr.timeout = this.o.timeout; 3302 } 3303 xhr.onreadystatechange = function() { 3304 var responseResult, url; 3305 3306 if (xhr.readyState !== 4) { 3307 return; 3308 } 3309 3310 function cleanup() { 3311 xhr.onreadystatechange = function(){}; 3312 } 3313 3314 responseResult = responseCallback( 3315 xhr.getResponseHeader('Content-Type'), 3316 xhr.responseText, 3317 xhr.status, 3318 xhr.statusText, 3319 cleanup 3320 ); 3321 3322 if (responseResult === false) { 3323 return; 3324 } 3325 3326 url = xhr.getResponseHeader('X-MT-Next-Phase-URL'); 3327 if (url) { 3328 xhr.abort(); 3329 api.sendXMLHttpRequest(xhr, method, base + url, params, defaultHeaders); 3330 } 3331 else { 3332 cleanup(); 3333 } 3334 }; 3335 return this.sendXMLHttpRequest(xhr, method, base + endpoint, params, defaultHeaders); 3336 } 3337 else { 3338 (function() { 3339 var k, file, originalName, input, 3340 target = api._getNextIframeName(), 3341 doc = window.document, 3342 form = doc.createElement('form'), 3343 iframe = doc.createElement('iframe'); 3344 3345 3346 // Set up a form element 3347 form.action = base + endpoint; 3348 form.target = target; 3349 form.method = method; 3350 form.style.display = 'inline'; 3351 form.encoding = 'multipart/form-data'; 3352 form.enctype = 'multipart/form-data'; 3353 3354 // Set up a iframe element 3355 iframe.name = target; 3356 iframe.style.position = 'absolute'; 3357 iframe.style.top = '-9999px'; 3358 doc.body.appendChild(iframe); 3359 iframe.contentWindow.name = target; 3360 3361 3362 params = params || {}; 3363 for (k in defaultHeaders) { 3364 params[k] = defaultHeaders[k]; 3365 } 3366 params['X-MT-Requested-Via'] = 'IFRAME'; 3367 3368 for (k in params) { 3369 if (api._isFileInputElement(params[k])) { 3370 file = params[k]; 3371 originalName = file.name; 3372 file.name = k; 3373 if (file.parentNode) { 3374 file.parentNode.insertBefore(form, file); 3375 } 3376 else { 3377 doc.body.appendChild(form); 3378 } 3379 form.appendChild(file); 3380 continue; 3381 } 3382 3383 input = doc.createElement('input'); 3384 input.type = 'hidden'; 3385 input.name = k; 3386 input.value = params[k]; 3387 form.appendChild(input); 3388 } 3389 3390 form.submit(); 3391 3392 3393 function handler() { 3394 var body = iframe.contentWindow.document.body, 3395 contents = body.textContent || body.innerText, 3396 response; 3397 3398 function cleanup() { 3399 setTimeout(function() { 3400 file.name = originalName; 3401 if (form.parentNode) { 3402 form.parentNode.insertBefore(file, form); 3403 form.parentNode.removeChild(form); 3404 } 3405 if (iframe.parentNode) { 3406 iframe.parentNode.removeChild(iframe); 3407 } 3408 }); 3409 } 3410 3411 try { 3412 response = api.unserializeData(contents); 3413 } 3414 catch (e) { 3415 response = { 3416 error: { 3417 code: 500, 3418 message: 'Internal Server Error' 3419 } 3420 }; 3421 } 3422 3423 if (needToRetry(response)) { 3424 retryWithAuthentication(); 3425 cleanup(); 3426 return; 3427 } 3428 3429 cleanup(); 3430 runCallback(response); 3431 } 3432 if ( iframe.addEventListener ) { 3433 iframe.addEventListener('load', handler, false); 3434 } else if ( iframe.attachEvent ) { 3435 iframe.attachEvent('onload', handler); 3436 } 3437 })(); 3438 3439 return; 3440 } 3441 }, 3442 3443 /** 3444 * Register callback to instance. 3445 * @method on 3446 * @param {String} key Event name 3447 * @param {Function} callback Callback function 3448 * @category core 3449 * @example 3450 * var callback = function() { 3451 * // Do stuff 3452 * }; 3453 * api.on(eventName, callback); 3454 */ 3455 on: function() { 3456 this.constructor.on.apply(this, arguments); 3457 }, 3458 3459 /** 3460 * Deregister callback from instance. 3461 * @method off 3462 * @param {String} key Event name 3463 * @param {Function} callback Callback function 3464 * @category core 3465 * @example 3466 * api.off(eventName, callback); 3467 */ 3468 off: function() { 3469 this.constructor.off.apply(this, arguments); 3470 }, 3471 3472 /** 3473 * Trigger event.
3474 * First, run class level callbacks. Then, run instance level callbacks. 3475 * @method trigger 3476 * @param {String} key Event name 3477 * @category core 3478 */ 3479 trigger: function(key) { 3480 var i, 3481 args = Array.prototype.slice.call(arguments, 1), 3482 callbacks = (this.constructor.callbacks[key] || []) // Class level 3483 .concat(this.callbacks[key] || []); // Instance level 3484 3485 for (i = 0; i < callbacks.length; i++) { 3486 callbacks[i].apply(this, args); 3487 } 3488 }, 3489 3490 _generateEndpointMethod: function(e) { 3491 var api = this, 3492 varRegexp = new RegExp(':([a-zA-Z_-]+)', 'g'), 3493 vars = null, 3494 name = e.id.replace(/_(\w)/g, function(all, letter) { 3495 return letter.toUpperCase(); 3496 }); 3497 3498 function extractVars() { 3499 var m, vars = []; 3500 while ((m = varRegexp.exec(e.route)) !== null) { 3501 vars.push(m[1]); 3502 } 3503 return vars; 3504 } 3505 3506 api[name] = function() { 3507 if (! vars) { 3508 vars = extractVars(); 3509 } 3510 3511 var args = Array.prototype.slice.call(arguments), 3512 endpointParams = {}, 3513 resources = {}, 3514 route, i; 3515 3516 for (i = 0; i < vars.length; i++) { 3517 endpointParams[vars[i]] = args.shift(); 3518 } 3519 route = api.bindEndpointParams(e.route, endpointParams); 3520 3521 if (e.resources) { 3522 for (i = 0; i < e.resources.length; i++) { 3523 resources[e.resources[i]] = args.shift(); 3524 } 3525 args.push(resources); 3526 } 3527 3528 return api.request.apply(api, [e.verb, route].concat(args)); 3529 }; 3530 }, 3531 3532 /** 3533 * Generate methods to access endpoint. 3534 * @method generateEndpointMethods 3535 * @param {Array.Object} endpoints Endpoints to register 3536 * @param {Object} endpoints.{i} 3537 * @param {String} endpoints.{i}.id Normally, the ID is snake case, 3538 * but generated method is camel case. 3539 * @param {String} endpoints.{i}.route The template of route 3540 * @param {String} endpoints.{i}.verb The HTTP verb 3541 * @param {Array.String} [endpoints.{i}.resources] The required resource data 3542 * @example 3543 * api.generateEndpointMethods([ 3544 * { 3545 * "id": "list_entries", 3546 * "route": "/sites/:site_id/entries", 3547 * "verb": "GET", 3548 * }, 3549 * { 3550 * "id": "create_entry", 3551 * "route": "/sites/:site_id/entries", 3552 * "verb": "POST", 3553 * "resources": [ 3554 * "entry" 3555 * ] 3556 * } 3557 * ]); 3558 * @category core 3559 */ 3560 generateEndpointMethods: function(endpoints) { 3561 for (var i = 0; i < endpoints.length; i++) { 3562 this._generateEndpointMethod(endpoints[i]); 3563 } 3564 }, 3565 3566 /** 3567 * Load endpoint from DataAPI dynamically. 3568 * @method loadEndpoints 3569 * @param {Object} [params] 3570 * @param {String} [params.includeComponents] Comma separated component IDs to load 3571 * @param {String} [params.excludeComponents] Comma separated component IDs to exclude 3572 * @example 3573 * Load endpoints only from specified module. 3574 * 3575 * api.loadEndpoints({ 3576 * includeComponents: 'your-extension-module' 3577 * }); 3578 * api.getDataViaYourExtensionModule(function(response) { 3579 * // Do stuff 3580 * }); 3581 * 3582 * Load all endpoints except for core. 3583 * Since all the endpoints of core is already loaded. 3584 * 3585 * api.loadEndpoints({ 3586 * excludeComponents: 'core' 3587 * }); 3588 * api.getDataViaYourExtensionModule(function(response) { 3589 * // Do stuff 3590 * }); 3591 * @category core 3592 */ 3593 loadEndpoints: function(params) { 3594 var api = this; 3595 3596 api.withOptions({withoutAuthorization: true, async: false}, function() { 3597 api.request('GET', '/endpoints', params, function(response) { 3598 if (response.error) { 3599 return; 3600 } 3601 3602 api.generateEndpointMethods(response.items); 3603 }); 3604 }); 3605 } 3606}; 3607 3608/** 3609 * Triggered on initializing an instance 3610 * 3611 * @event initialize 3612 * @example 3613 * DataAPI.on("initialize", function() { 3614 * console.log("initializing..."); 3615 * }); 3616 **/ 3617 3618/** 3619 * Triggered on getting an error of a HTTP request 3620 * 3621 * @event error 3622 * @param {Object} response A response object 3623 * @param {Number} response.code The HTTP response code 3624 * @param {String} response.message The error message 3625 * @param {Object} response.data The data exists only if a current error has optional data 3626 * @example 3627 * api.on("error", function(response) { 3628 * console.log(response.error.message); 3629 * }); 3630 **/ 3631 3632/** 3633 * Triggered on receiving the HTTP response code 401 (Authorization required). 3634 * 3635 * @event authorizationRequired 3636 * @param {Object} response A response object 3637 * @param {Number} response.code The HTTP response code 3638 * @param {Number} response.message The error message 3639 * @example 3640 * api.on("authorizationRequired", function(response) { 3641 * // You will return to current URL after authorization succeeded. 3642 * location.href = api.getAuthorizationUrl(location.href); 3643 * }); 3644 **/ 3645 3646var Cookie = function( name, value, domain, path, expires, secure ) { 3647 this.name = name; 3648 this.value = value; 3649 this.domain = domain; 3650 this.path = path; 3651 this.expires = expires; 3652 this.secure = secure; 3653}; 3654 3655Cookie.prototype = { 3656 /** 3657 * Get this cookie from the web browser's store of cookies. Note that if the <code>document.cookie</code> 3658 * property has been written to repeatedly by the same client code in excess of 4K (regardless of the size 3659 * of the actual cookies), IE 6 will report an empty <code>document.cookie</code> collection of cookies. 3660 * @return <code>Cookie</code> The fetched cookie. 3661 */ 3662 fetch: function() { 3663 if (! window.document) { 3664 return undefined; 3665 } 3666 3667 var prefix = escape( this.name ) + "=", 3668 cookies = ("" + window.document.cookie).split( /;\s*/ ), 3669 i; 3670 3671 for( i = 0; i < cookies.length; i++ ) { 3672 if( cookies[ i ].indexOf( prefix ) === 0 ) { 3673 this.value = unescape( cookies[ i ].substring( prefix.length ) ); 3674 return this; 3675 } 3676 } 3677 3678 return undefined; 3679 }, 3680 3681 3682 /** 3683 * Set and store a cookie in the the web browser's native collection of cookies. 3684 * @return <code>Cookie</code> The set and stored ("baked") cookie. 3685 */ 3686 bake: function( value ) { 3687 if (! window.document) { 3688 return undefined; 3689 } 3690 3691 function exists(x) { 3692 return (x === undefined || x === null) ? false : true; 3693 } 3694 3695 if( !exists( this.name ) ) { 3696 return undefined; 3697 } 3698 3699 if( exists( value ) ) { 3700 this.value = value; 3701 } 3702 else { 3703 value = this.value; 3704 } 3705 3706 var name = escape( this.name ),
3707 attributes = ( this.domain ? "; domain=" + escape( this.domain ) : "") + 3708 (this.path ? "; path=" + escape( this.path ) : "") + 3709 (this.expires ? "; expires=" + this.expires.toGMTString() : "") + 3710 (this.secure ? "; secure=1" : ""), 3711 batter = name + "=" + escape( value ) + attributes; 3712 3713 window.document.cookie = batter; 3714 3715 return this; 3716 }, 3717 3718 3719 remove: function() { 3720 this.expires = new Date( 0 ); // "Thu, 01 Jan 1970 00:00:00 GMT" 3721 this.value = ""; 3722 this.bake(); 3723 } 3724}; 3725 3726Cookie.fetch = function( name ) { 3727 var cookie = new this( name ); 3728 return cookie.fetch(); 3729}; 3730 3731 3732Cookie.bake = function( name, value, domain, path, expires, secure ) { 3733 var cookie = new this( name, value, domain, path, expires, secure ); 3734 return cookie.bake(); 3735}; 3736 3737Cookie.remove = function( name ) { 3738 var cookie = this.fetch( name ); 3739 if ( cookie ) { 3740 return cookie.remove(); 3741 } 3742}; 3743 3744var JSON = window.JSON; 3745/* 3746 json2.js 3747 2012-10-08 3748 3749 Public Domain. 3750 3751 NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 3752 3753 See http://www.JSON.org/js.html 3754 3755 3756 This code should be minified before deployment. 3757 See http://javascript.crockford.com/jsmin.html 3758 3759 USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 3760 NOT CONTROL. 3761 3762 3763 This file creates a global JSON object containing two methods: stringify 3764 and parse. 3765 3766 JSON.stringify(value, replacer, space) 3767 value any JavaScript value, usually an object or array. 3768 3769 replacer an optional parameter that determines how object 3770 values are stringified for objects. It can be a 3771 function or an array of strings. 3772 3773 space an optional parameter that specifies the indentation 3774 of nested structures. If it is omitted, the text will 3775 be packed without extra whitespace. If it is a number, 3776 it will specify the number of spaces to indent at each 3777 level. If it is a string (such as '\t' or ' '), 3778 it contains the characters used to indent at each level. 3779 3780 This method produces a JSON text from a JavaScript value. 3781 3782 When an object value is found, if the object contains a toJSON 3783 method, its toJSON method will be called and the result will be 3784 stringified. A toJSON method does not serialize: it returns the 3785 value represented by the name/value pair that should be serialized, 3786 or undefined if nothing should be serialized. The toJSON method 3787 will be passed the key associated with the value, and this will be 3788 bound to the value 3789 3790 For example, this would serialize Dates as ISO strings. 3791 3792 Date.prototype.toJSON = function (key) { 3793 function f(n) { 3794 // Format integers to have at least two digits. 3795 return n < 10 ? '0' + n : n; 3796 } 3797 3798 return this.getUTCFullYear() + '-' + 3799 f(this.getUTCMonth() + 1) + '-' + 3800 f(this.getUTCDate()) + 'T' + 3801 f(this.getUTCHours()) + ':' + 3802 f(this.getUTCMinutes()) + ':' + 3803 f(this.getUTCSeconds()) + 'Z'; 3804 }; 3805 3806 You can provide an optional replacer method. It will be passed the 3807 key and value of each member, with this bound to the containing 3808 object. The value that is returned from your method will be 3809 serialized. If your method returns undefined, then the member will 3810 be excluded from the serialization. 3811 3812 If the replacer parameter is an array of strings, then it will be 3813 used to select the members to be serialized. It filters the results 3814 such that only members with keys listed in the replacer array are
3815 stringified. 3816 3817 Values that do not have JSON representations, such as undefined or 3818 functions, will not be serialized. Such values in objects will be 3819 dropped; in arrays they will be replaced with null. You can use 3820 a replacer function to replace those with JSON values. 3821 JSON.stringify(undefined) returns undefined. 3822 3823 The optional space parameter produces a stringification of the 3824 value that is filled with line breaks and indentation to make it 3825 easier to read. 3826 3827 If the space parameter is a non-empty string, then that string will 3828 be used for indentation. If the space parameter is a number, then 3829 the indentation will be that many spaces. 3830 3831 Example: 3832 3833 text = JSON.stringify(['e', {pluribus: 'unum'}]); 3834 // text is '["e",{"pluribus":"unum"}]' 3835 3836 3837 text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 3838 // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 3839 3840 text = JSON.stringify([new Date()], function (key, value) { 3841 return this[key] instanceof Date ? 3842 'Date(' + this[key] + ')' : value; 3843 }); 3844 // text is '["Date(---current time---)"]' 3845 3846 3847 JSON.parse(text, reviver) 3848 This method parses a JSON text to produce an object or array. 3849 It can throw a SyntaxError exception. 3850 3851 The optional reviver parameter is a function that can filter and 3852 transform the results. It receives each of the keys and values, 3853 and its return value is used instead of the original value. 3854 If it returns what it received, then the structure is not modified. 3855 If it returns undefined then the member is deleted. 3856 3857 Example: 3858 3859 // Parse the text. Values that look like ISO date strings will 3860 // be converted to Date objects. 3861 3862 myData = JSON.parse(text, function (key, value) { 3863 var a; 3864 if (typeof value === 'string') { 3865 a = 3866/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 3867 if (a) { 3868 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 3869 +a[5], +a[6])); 3870 } 3871 } 3872 return value; 3873 }); 3874 3875 myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 3876 var d; 3877 if (typeof value === 'string' && 3878 value.slice(0, 5) === 'Date(' && 3879 value.slice(-1) === ')') { 3880 d = new Date(value.slice(5, -1)); 3881 if (d) { 3882 return d; 3883 } 3884 } 3885 return value; 3886 }); 3887 3888 3889 This is a reference implementation. You are free to copy, modify, or 3890 redistribute. 3891*/ 3892 3893/*jslint evil: true, regexp: true */ 3894 3895/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 3896 call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 3897 getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 3898 lastIndex, length, parse, prototype, push, replace, slice, stringify, 3899 test, toJSON, toString, valueOf 3900*/ 3901 3902 3903// Create a JSON object only if one does not already exist. We create the 3904// methods in a closure to avoid creating global variables. 3905 3906if (typeof JSON !== 'object') { 3907 JSON = {}; 3908} 3909 3910(function () { 3911 'use strict'; 3912 3913 function f(n) { 3914 // Format integers to have at least two digits. 3915 return n < 10 ? '0' + n : n; 3916 }
vendor: 10,031 bytes, lines 3917-4209
3917 3918 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 3919 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 3920 gap, 3921 indent, 3922 meta = { // table of character substitutions 3923 '\b': '\\b', 3924 '\t': '\\t', 3925 '\n': '\\n', 3926 '\f': '\\f', 3927 '\r': '\\r', 3928 '"' : '\\"', 3929 '\\': '\\\\' 3930 }, 3931 rep; 3932 3933 3934 function quote(string) { 3935 3936// If the string contains no control characters, no quote characters, and no 3937// backslash characters, then we can safely slap some quotes around it. 3938// Otherwise we must also replace the offending characters with safe escape 3939// sequences. 3940 3941 escapable.lastIndex = 0; 3942 return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 3943 var c = meta[a]; 3944 return typeof c === 'string' 3945 ? c 3946 : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 3947 }) + '"' : '"' + string + '"'; 3948 } 3949 3950 3951 function str(key, holder) { 3952 3953// Produce a string from holder[key]. 3954 3955 var i, // The loop counter. 3956 k, // The member key. 3957 v, // The member value. 3958 length, 3959 mind = gap, 3960 partial, 3961 value = holder[key]; 3962 3963// If the value has a toJSON method, call it to obtain a replacement value. 3964 3965 if (value && typeof value === 'object' && 3966 typeof value.toJSON === 'function') { 3967 value = value.toJSON(key); 3968 } 3969 3970// If we were called with a replacer function, then call the replacer to 3971// obtain a replacement value. 3972 3973 if (typeof rep === 'function') { 3974 value = rep.call(holder, key, value); 3975 } 3976 3977// What happens next depends on the value's type. 3978 3979 switch (typeof value) { 3980 case 'string': 3981 return quote(value); 3982 3983 case 'number': 3984 3985// JSON numbers must be finite. Encode non-finite numbers as null. 3986 3987 return isFinite(value) ? String(value) : 'null'; 3988 3989 case 'boolean': 3990 case 'null': 3991 3992// If the value is a boolean or null, convert it to a string. Note: 3993// typeof null does not produce 'null'. The case is included here in 3994// the remote chance that this gets fixed someday. 3995 3996 return String(value); 3997 3998// If the type is 'object', we might be dealing with an object or an array or 3999// null. 4000 4001 case 'object': 4002 4003// Due to a specification blunder in ECMAScript, typeof null is 'object', 4004// so watch out for that case. 4005 4006 if (!value) { 4007 return 'null'; 4008 } 4009 4010// Make an array to hold the partial results of stringifying this object value. 4011 4012 gap += indent; 4013 partial = []; 4014 4015// Is the value an array? 4016 4017 if (Object.prototype.toString.apply(value) === '[object Array]') { 4018 4019// The value is an array. Stringify every element. Use null as a placeholder 4020// for non-JSON values. 4021 4022 length = value.length; 4023 for (i = 0; i < length; i += 1) { 4024 partial[i] = str(i, value) || 'null'; 4025 } 4026 4027// Join all of the elements together, separated with commas, and wrap them in 4028// brackets. 4029 4030 v = partial.length === 0 4031 ? '[]' 4032 : gap 4033 ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 4034 : '[' + partial.join(',') + ']'; 4035 gap = mind; 4036 return v; 4037 } 4038 4039// If the replacer is an array, use it to select the members to be stringified. 4040 4041 if (rep && typeof rep === 'object') { 4042 length = rep.length; 4043 for (i = 0; i < length; i += 1) { 4044 if (typeof rep[i] === 'string') { 4045 k = rep[i]; 4046 v = str(k, value); 4047 if (v) { 4048 partial.push(quote(k) + (gap ? ': ' : ':') + v); 4049 } 4050 } 4051 } 4052 } else { 4053 4054// Otherwise, iterate through all of the keys in the object. 4055 4056 for (k in value) { 4057 if (Object.prototype.hasOwnProperty.call(value, k)) { 4058 v = str(k, value); 4059 if (v) { 4060 partial.push(quote(k) + (gap ? ': ' : ':') + v); 4061 } 4062 } 4063 } 4064 } 4065 4066// Join all of the member texts together, separated with commas, 4067// and wrap them in braces. 4068 4069 v = partial.length === 0 4070 ? '{}' 4071 : gap 4072 ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 4073 : '{' + partial.join(',') + '}'; 4074 gap = mind; 4075 return v; 4076 } 4077 } 4078 4079// If the JSON object does not yet have a stringify method, give it one. 4080 4081 if (typeof JSON.stringify !== 'function') { 4082 JSON.stringify = function (value, replacer, space) { 4083 4084// The stringify method takes a value and an optional replacer, and an optional 4085// space parameter, and returns a JSON text. The replacer can be a function 4086// that can replace values, or an array of strings that will select the keys. 4087// A default replacer method can be provided. Use of the space parameter can 4088// produce text that is more easily readable. 4089 4090 var i; 4091 gap = ''; 4092 indent = ''; 4093 4094// If the space parameter is a number, make an indent string containing that 4095// many spaces. 4096 4097 if (typeof space === 'number') { 4098 for (i = 0; i < space; i += 1) { 4099 indent += ' '; 4100 } 4101 4102// If the space parameter is a string, it will be used as the indent string. 4103 4104 } else if (typeof space === 'string') { 4105 indent = space; 4106 } 4107 4108// If there is a replacer, it must be a function or an array. 4109// Otherwise, throw an error. 4110 4111 rep = replacer; 4112 if (replacer && typeof replacer !== 'function' && 4113 (typeof replacer !== 'object' || 4114 typeof replacer.length !== 'number')) { 4115 throw new Error('JSON.stringify'); 4116 } 4117 4118// Make a fake root object containing our value under the key of ''. 4119// Return the result of stringifying the value. 4120 4121 return str('', {'': value}); 4122 }; 4123 } 4124 4125 4126// If the JSON object does not yet have a parse method, give it one. 4127 4128 if (typeof JSON.parse !== 'function') { 4129 JSON.parse = function (text, reviver) { 4130 4131// The parse method takes a text and an optional reviver function, and returns 4132// a JavaScript value if the text is a valid JSON text. 4133 4134 var j; 4135 4136 function walk(holder, key) { 4137 4138// The walk method is used to recursively walk the resulting structure so 4139// that modifications can be made. 4140 4141 var k, v, value = holder[key]; 4142 if (value && typeof value === 'object') { 4143 for (k in value) { 4144 if (Object.prototype.hasOwnProperty.call(value, k)) { 4145 v = walk(value, k); 4146 if (v !== undefined) { 4147 value[k] = v; 4148 } else { 4149 delete value[k]; 4150 } 4151 } 4152 } 4153 } 4154 return reviver.call(holder, key, value); 4155 } 4156 4157 4158// Parsing happens in four stages. In the first stage, we replace certain 4159// Unicode characters with escape sequences. JavaScript handles many characters 4160// incorrectly, either silently deleting them, or treating them as line endings. 4161 4162 text = String(text); 4163 cx.lastIndex = 0; 4164 if (cx.test(text)) { 4165 text = text.replace(cx, function (a) { 4166 return '\\u' + 4167 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 4168 }); 4169 } 4170 4171// In the second stage, we run the text against regular expressions that look 4172// for non-JSON patterns. We are especially concerned with '()' and 'new' 4173// because they can cause invocation, and '=' because it can cause mutation. 4174// But just to be safe, we want to reject all unexpected forms. 4175 4176// We split the second stage into 4 regexp operations in order to work around 4177// crippling inefficiencies in IE's and Safari's regexp engines. First we 4178// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 4179// replace all simple value tokens with ']' characters. Third, we delete all 4180// open brackets that follow a colon or comma or that begin the text. Finally, 4181// we look to see that the remaining characters are only whitespace or ']' or 4182// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 4183 4184 if (/^[\],:{}\s]*$/ 4185 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 4186 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 4187 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 4188 4189// In the third stage we use the eval function to compile the text into a 4190// JavaScript structure. The '{' operator is subject to a syntactic ambiguity 4191// in JavaScript: it can begin a block or an object literal. We wrap the text 4192// in parens to eliminate the ambiguity. 4193 4194 j = eval('(' + text + ')'); 4195 4196// In the optional fourth stage, we recursively walk the new structure, passing 4197// each name/value pair to a reviver function for possible transformation. 4198 4199 return typeof reviver === 'function' 4200 ? walk({'': j}, '') 4201 : j; 4202 } 4203 4204// If the text is not JSON parseable, then a SyntaxError is thrown. 4205 4206 throw new SyntaxError('JSON.parse'); 4207 }; 4208 } 4209}
4209()); 4210 4211DataAPI.on('initialize', function() { 4212 this.generateEndpointMethods( 4213 [ 4214 { 4215 "id": "list_endpoints", 4216 "route": "/endpoints", 4217 "verb": "GET", 4218 "resources": null 4219 }, 4220 { 4221 "id": "authenticate", 4222 "route": "/authentication", 4223 "verb": "POST", 4224 "resources": null 4225 }, 4226 { 4227 "id": "get_token", 4228 "route": "/token", 4229 "verb": "POST", 4230 "resources": null 4231 }, 4232 { 4233 "id": "revoke_authentication", 4234 "route": "/authentication", 4235 "verb": "DELETE", 4236 "resources": null 4237 }, 4238 { 4239 "id": "revoke_token", 4240 "route": "/token", 4241 "verb": "DELETE", 4242 "resources": null 4243 }, 4244 { 4245 "id": "get_user", 4246 "route": "/users/:user_id", 4247 "verb": "GET", 4248 "resources": null 4249 }, 4250 { 4251 "id": "update_user", 4252 "route": "/users/:user_id", 4253 "verb": "PUT", 4254 "resources": [ 4255 "user" 4256 ] 4257 }, 4258 { 4259 "id": "list_blogs_for_user", 4260 "route": "/users/:user_id/sites", 4261 "verb": "GET", 4262 "resources": null 4263 }, 4264 { 4265 "id": "get_blog", 4266 "route": "/sites/:blog_id", 4267 "verb": "GET", 4268 "resources": null 4269 }, 4270 { 4271 "id": "list_entries", 4272 "route": "/sites/:site_id/entries", 4273 "verb": "GET", 4274 "resources": null 4275 }, 4276 { 4277 "id": "create_entry", 4278 "route": "/sites/:site_id/entries", 4279 "verb": "POST", 4280 "resources": [ 4281 "entry" 4282 ] 4283 }, 4284 { 4285 "id": "get_entry", 4286 "route": "/sites/:site_id/entries/:entry_id", 4287 "verb": "GET", 4288 "resources": null 4289 }, 4290 { 4291 "id": "update_entry", 4292 "route": "/sites/:site_id/entries/:entry_id", 4293 "verb": "PUT", 4294 "resources": [ 4295 "entry" 4296 ] 4297 }, 4298 { 4299 "id": "delete_entry", 4300 "route": "/sites/:site_id/entries/:entry_id", 4301 "verb": "DELETE", 4302 "resources": null 4303 }, 4304 { 4305 "id": "list_categories", 4306 "route": "/sites/:site_id/categories", 4307 "verb": "GET", 4308 "resources": null 4309 }, 4310 { 4311 "id": "list_comments", 4312 "route": "/sites/:site_id/comments", 4313 "verb": "GET", 4314 "resources": null 4315 }, 4316 { 4317 "id": "list_comments_for_entry", 4318 "route": "/sites/:site_id/entries/:entry_id/comments", 4319 "verb": "GET", 4320 "resources": null 4321 }, 4322 { 4323 "id": "create_comment", 4324 "route": "/sites/:site_id/entries/:entry_id/comments", 4325 "verb": "POST", 4326 "resources": [ 4327 "comment" 4328 ] 4329 }, 4330 { 4331 "id": "create_reply_comment", 4332 "route": "/sites/:site_id/entries/:entry_id/comments/:comment_id/replies", 4333 "verb": "POST", 4334 "resources": [ 4335 "comment" 4336 ] 4337 }, 4338 { 4339 "id": "get_comment", 4340 "route": "/sites/:site_id/comments/:comment_id", 4341 "verb": "GET", 4342 "resources": null 4343 }, 4344 { 4345 "id": "update_comment", 4346 "route": "/sites/:site_id/comments/:comment_id", 4347 "verb": "PUT", 4348 "resources": [ 4349 "comment" 4350 ] 4351 }, 4352 { 4353 "id": "delete_comment", 4354 "route": "/sites/:site_id/comments/:comment_id", 4355 "verb": "DELETE", 4356 "resources": null 4357 }, 4358 { 4359 "id": "list_trackbacks", 4360 "route": "/sites/:site_id/trackbacks", 4361 "verb": "GET", 4362 "resources": null 4363 }, 4364 { 4365 "id": "list_trackbacks_for_entry", 4366 "route": "/sites/:site_id/entries/:entry_id/trackbacks", 4367 "verb": "GET", 4368 "resources": null 4369 }, 4370 { 4371 "id": "get_trackback", 4372 "route": "/sites/:site_id/trackbacks/:ping_id", 4373 "verb": "GET", 4374 "resources": null 4375 }, 4376 { 4377 "id": "update_trackback", 4378 "route": "/sites/:site_id/trackbacks/:ping_id", 4379 "verb": "PUT", 4380 "resources": [ 4381 "trackback" 4382 ] 4383 }, 4384 { 4385 "id": "delete_trackback", 4386 "route": "/sites/:site_id/trackbacks/:ping_id", 4387 "verb": "DELETE", 4388 "resources": null 4389 }, 4390 { 4391 "id": "upload_asset", 4392 "route": "/sites/:site_id/assets/upload", 4393 "verb": "POST", 4394 "resources": null 4395 }, 4396 { 4397 "id": "list_permissions_for_user", 4398 "route": "/users/:user_id/permissions", 4399 "verb": "GET", 4400 "resources": null 4401 }, 4402 { 4403 "id": "publish_entries", 4404 "route": "/publish/entries", 4405 "verb": "GET", 4406 "resources": null 4407 }, 4408 { 4409 "id": "get_stats_provider", 4410 "route": "/sites/:site_id/stats/provider", 4411 "verb": "GET", 4412 "resources": null 4413 }, 4414 { 4415 "id": "list_stats_pageviews_for_path", 4416 "route": "/sites/:site_id/stats/path/pageviews", 4417 "verb": "GET", 4418 "resources": null 4419 }, 4420 { 4421 "id": "list_stats_visits_for_path", 4422 "route": "/sites/:site_id/stats/path/visits", 4423 "verb": "GET", 4424 "resources": null 4425 }, 4426 { 4427 "id": "list_stats_pageviews_for_date", 4428 "route": "/sites/:site_id/stats/date/pageviews", 4429 "verb": "GET", 4430 "resources": null 4431 }, 4432 { 4433 "id": "list_stats_visits_for_date", 4434 "route": "/sites/:site_id/stats/date/visits", 4435 "verb": "GET", 4436 "resources": null 4437 } 4438] 4439 4440 ); 4441}); 4442 4443window.MT = window.MT || {}; 4444window.MT.DataAPI = window.MT.DataAPI || DataAPI; 4445window.MT.DataAPI['v' + DataAPI.version] = DataAPI; 4446 4447 4448return DataAPI; 4449 4450}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.