diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..8bf8a41 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,51 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + jshint: { + all: ['Gruntfile.js', 'lib/*.js'] + }, + + concat: { + dist: { + src: [ + 'lib/jose-jwe-core.js', + 'lib/jose-jwe-utils.js', + 'lib/jose-jwe-encrypt.js' + ], + dest: 'dist/jose-jwe.js' + } + }, + + uglify: { + dist: { + src: 'dist/jose-jwe.js', + dest: 'dist/jose-jwe.min.js' + } + }, + + karma: { + continuous: { + options: { + frameworks: ['qunit'], + files: [ + {pattern: 'dist/jose-jwe.js', watching: false, included: false}, + {pattern: 'test/qunit-promises.js', watching: false, included: false}, + 'test/jose-jwe-test.html' + ], + autoWatch: true, + browsers: ['Chrome'], + singleRun: true + } + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-karma'); + + grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'karma']); +}; \ No newline at end of file diff --git a/README.md b/README.md index 5fe68ab..d1c5d6d 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,7 @@ following versions of the drafts: Example usage ------------- - - - + var joseJWE = new JoseJWE(); var rsa_key = JoseJWE.Utils.importRsaKeyFromHex({ "n": "c2:4b:af:0f:2d:2b:ad:36:72:a7:91:0f:ee:30:a0:95:d5:3a:46:82:86:96:7e:42:c6:fe:8f:20:97:af:49:f6:48:a3:91:53:ac:2e:e6:ec:9a:9a:e0:0a:fb:1c:db:44:40:5b:8c:fc:d5:1c:cb:b6:9b:60:c0:a8:ac:06:f1:6b:29:5e:2f:7b:09:d9:93:32:da:3f:db:53:9c:2e:ea:3b:41:7f:6b:c9:7b:88:9f:2e:c5:dd:42:1e:7f:8f:04:f6:60:3c:fe:43:6d:32:10:ce:8d:99:cb:76:f7:10:97:05:af:28:1e:39:0f:78:35:50:7b:8e:28:22:a4:7d:11:51:22:d1:0e:ab:6b:6f:96:cb:cf:7d:eb:c6:aa:a2:6a:2e:97:2a:93:af:a5:89:e6:c8:bc:9f:fd:85:2b:0f:b4:c0:e4:ca:b5:a7:9a:01:05:81:93:6b:f5:8d:1c:f7:f3:77:0e:6e:53:34:92:0f:48:21:34:33:44:14:5e:4a:00:41:3a:7d:cb:38:82:c1:65:e0:79:ea:a1:05:84:b2:6e:40:19:77:1a:0e:38:4b:28:1f:34:b5:cb:ac:c5:2f:58:51:d7:ec:a8:08:0e:7c:c0:20:c1:5e:a1:4d:b1:30:17:63:0e:e7:58:8e:7f:6e:9f:a4:77:8b:1e:a2:d2:2e:1b:e9", @@ -58,6 +56,14 @@ Content Encryption: * A256GCM (default) +Building +-------- + +* `npm install` +* `grunt` + + + Some background info -------------------- @@ -73,10 +79,10 @@ probably needs a thin abstraction layer. Some other interesting resources: -* Web Crypto API polyfill (_no longer under active development_): http://polycrypt.net/ -* Netflix' Web Crypto API polyfill: https://github.com/Netflix/NfWebCrypto +* Web Crypto API polyfill (_no longer under active development_): http://polycrypt.net/ +* Netflix' Web Crypto API polyfill: https://github.com/Netflix/NfWebCrypto * JWT: https://github.com/michaelrhanson/jwt-js * BigInt, RSA, ECC: http://www-cs-students.stanford.edu/~tjw/jsbn/ * asn1, pem, x509 and more: http://kjur.github.io/jsrsasign/ and - https://github.com/michaelrhanson/jwt-js/tree/master/lib/kurushima-jsrsa + https://github.com/michaelrhanson/jwt-js/tree/master/lib/kurushima-jsrsa * Stanford Javascript Crypto library: https://github.com/bitwiseshiftleft/sjcl diff --git a/dist/jose-jwe.js b/dist/jose-jwe.js new file mode 100644 index 0000000..837ce52 --- /dev/null +++ b/dist/jose-jwe.js @@ -0,0 +1,518 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * JSON Web Encryption (JWE) library for JavaScript. + * + * @author Alok Menghrajani + */ + +/** + * TODO: + * 1. consider mixing entropy from the server side (e.g.
...
) + * 2. jslint + * 3. more unittests + * 4. improve error handling. Having everything be a Promise might simplify + * error handling? + */ + +/** + * Initializes a JoseJWE object. + */ +function JoseJWE() { + this.setKeyEncryptionAlgorithm("RSA-OAEP"); + this.setContentEncryptionAlgorithm("A256GCM"); +} + +/** + * Feel free to override this function. + */ +JoseJWE.assert = function(expr, msg) { + if (!expr) { + throw new Error(msg); + } +}; + +/** + * Overrides the default key encryption algorithm + * @param alg string + */ +JoseJWE.prototype.setKeyEncryptionAlgorithm = function(alg) { + this.key_encryption = JoseJWE.getCryptoConfig(alg); +}; + +/** + * Overrides the default content encryption algorithm + * @param alg string + */ +JoseJWE.prototype.setContentEncryptionAlgorithm = function(alg) { + this.content_encryption = JoseJWE.getCryptoConfig(alg); +}; + +// Private functions + +/** + * Converts the Jose web algorithms into data which is + * useful for the Web Crypto API. + * + * length = in bits + * bytes = in bytes + */ +JoseJWE.getCryptoConfig = function(alg) { + switch (alg) { + // Key encryption + case "RSA-OAEP": + return { + jwe_name: "RSA-OAEP", + id: {name: "RSA-OAEP", hash: {name: "SHA-1"}} + }; + case "RSA-OAEP-256": + return { + jwe_name: "RSA-OAEP-256", + id: {name: "RSA-OAEP", hash: {name: "SHA-256"}} + }; + case "A128KW": + return { + jwe_name: "A128KW", + id: {name: "AES-KW", length: 128} + }; + case "A256KW": + return { + jwe_name: "A256KW", + id: {name: "AES-KW", length: 256} + }; + + // Content encryption + case "A128CBC-HS256": + return { + jwe_name: "A128CBC-HS256", + id: {name: "AES-CBC", length: 128}, + iv_bytes: 16, + specific_cek_bytes: 32, + auth: { + key_bytes: 16, + id: {name: "HMAC", hash: {name: "SHA-256"}}, + truncated_bytes: 16 + } + }; + case "A256CBC-HS512": + return { + id: {name: "AES-CBC", length: 256}, + iv_bytes: 16, + specific_cek_bytes: 64, + auth: { + key_bytes: 32, + id: {name: "HMAC", hash: {name: "SHA-512"}}, + truncated_bytes: 32 + } + }; + case "A128GCM": + return { + id: {name: "AES-GCM", length: 128}, + iv_bytes: 12, + auth: { + aead: true, + tag_bytes: 16 + } + }; + case "A256GCM": + return { + jwe_name: "A256GCM", + id: {name: "AES-GCM", length: 256}, + iv_bytes: 12, + auth: { + aead: true, + tag_bytes: 16 + } + }; + default: + JoseJWE.assert(false, "unsupported algorithm: " + alg); + } +}; + + +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +JoseJWE.Utils = {}; + +JoseJWE.Utils.isString = function(str) { + return ((typeof(str) == "string") || (str instanceof String)); +}; + +/** + * Converts the modulus/exponent from `openssl x509 -text` or + * `openssl rsa -text` into a CryptoKey which can then be used with RSA-OAEP. + * + * The key is either a public key or a public/private pair. + * + * @param rsa_key json, e.g. {"n": "00:c2:4b:af:0f:2d....", "e": 65537} + * @param purpose array, e.g. ["encrypt", "decrypt"] + * @return Promise + * + * TODO: handle private pair version... + */ +JoseJWE.Utils.importRsaKeyFromHex = function(rsa_key, purpose) { + if (!JoseJWE.Utils.isString(rsa_key.n)) { + return Promise.reject("importRsaKeyFromHex: expecting rsa_key['n'] to be a string"); + } + if (typeof(rsa_key.e) != "number") { + return Promise.reject("importRsaKeyFromHex: expecting rsa_key['e'] to be a number"); + } + if (!(purpose instanceof Array)) { + return Promise.reject("importRsaKeyFromHex: expecting purpose to be an array"); + } + purpose.push("wrapKey"); + var n = rsa_key.n + .split(':') + .map(function(e){return String.fromCharCode(parseInt(e, 16));}) + .join(''); + var jwk = { + "kty": "RSA", + "n": JoseJWE.Utils.Base64Url.encode(n), + "e": JoseJWE.Utils.Base64Url.encodeArrayBuffer(JoseJWE.Utils.arrayFromInt32(rsa_key.e)) + }; + var config = JoseJWE.getCryptoConfig("RSA-OAEP"); + return crypto.subtle.importKey("jwk", jwk, config.id, false, purpose); +}; + +/** + * Very simple wrapper to import a JWK into the Web Crypto API. + * + * @param jwk json + * @param purpose array + * @return Promise + */ +JoseJWE.Utils.importRSAfromJWK = function(jwk, purpose) { + if (!JoseJWE.Utils.isString(jwk.kty)) { + return Promise.reject("importRSAfromJWK: expecting jwk['kty'] to be a string"); + } + if (jwk.kty != "RSA") { + return Promise.reject("importRSAfromJWK: expecting jwk['kty'] to be RSA"); + } + if (!JoseJWE.Utils.isString(jwk.n)) { + return Promise.reject("importRSAfromJWK: expecting jwk['n'] to be a string"); + } + if (!JoseJWE.Utils.isString(jwk.e)) { + return Promise.reject("importRSAfromJWK: expecting jwk['e'] to be a string"); + } + if (!(purpose instanceof Array)) { + return Promise.reject("importRSAfromJWK: expecting purpose to be an array"); + } + purpose.push("wrapKey"); + var config = JoseJWE.getCryptoConfig("RSA-OAEP"); + + return crypto.subtle.importKey( + "jwk", + jwk, + config.id, + false, + purpose + ); +}; + +/** + * Converts a string into an array of ascii codes. + * + * @param str string + * @return Uint8Array + */ +JoseJWE.Utils.arrayFromString = function(str) { + var arr = str.split('').map(function(c){return c.charCodeAt(0);}); + return new Uint8Array(arr); +}; + +/** + * Converts a number into an array of 4 bytes. + * @param i number + * @return ArrayBuffer + */ +JoseJWE.Utils.arrayFromInt32 = function(i) { + JoseJWE.assert(typeof(i) == "number"); + JoseJWE.assert(i == i|0, "arrayFromInt32: out of range"); + + return new Uint32Array([i]).buffer; +}; + +/** + * Concatenates ArrayBuffers. + * + * @param two or more ArrayBuffer + * @return Uint8Array + */ +JoseJWE.Utils.arrayBufferConcat = function(/* ... */) { + // Compute total size + var total = 0; + for (var i=0; i + */ +JoseJWE.prototype.createCEK = function() { + var len = this.content_encryption.specific_cek_bytes; + if (len) { + // In some cases, e.g. A128CBC-HS256, the CEK gets split into two + // keys. The Web Crypto API does not allow us to generate an arbitrary + // number of bytes and then create a CryptoKey without any associated + // algorithm. We therefore piggy back on AES-CBS and HMAC which allows + // us to create CEKs of size 16, 32, 64 and 128 bytes. + if (len == 16) { + return crypto.subtle.generateKey({name: "AES-CBC", length: 128}, true, ["encrypt"]); + } else if (len == 32) { + return crypto.subtle.generateKey({name: "AES-CBC", length: 256}, true, ["encrypt"]); + } else if (len == 64) { + return crypto.subtle.generateKey({name: "HMAC", hash: {name: "SHA-256"}}, true, ["sign"]); + } else if (len == 128) { + return crypto.subtle.generateKey({name: "HMAC", hash: {name: "SHA-384"}}, true, ["sign"]); + } else { + JoseJWE.assert(false, "createCEK: invalid specific_cek_bytes"); + } + } + return crypto.subtle.generateKey(this.content_encryption.id, true, ["encrypt"]); +}; + +/** + * Performs encryption + * + * @param key_promise Promise, either RSA or shared key + * @param plain_text string to encrypt + * @return Promise + */ +JoseJWE.prototype.encrypt = function(key_promise, plain_text) { + // Create a CEK key + var cek_promise = this.createCEK(); + + // Key & Cek allows us to create the encrypted_cek + var encrypted_cek = this.encryptCek(key_promise, cek_promise); + + // Cek allows us to encrypy the plaintext + var enc_promise = this.encryptPlaintext(cek_promise, plain_text); + + // Once we have all the promises, we can base64 encode all the pieces. + return Promise.all([encrypted_cek, enc_promise]).then(function(all) { + var encrypted_cek = all[0]; + var data = all[1]; + return data.header + "." + + JoseJWE.Utils.Base64Url.encodeArrayBuffer(encrypted_cek) + "." + + JoseJWE.Utils.Base64Url.encodeArrayBuffer(data.iv.buffer) + "." + + JoseJWE.Utils.Base64Url.encodeArrayBuffer(data.cipher) + "." + + JoseJWE.Utils.Base64Url.encodeArrayBuffer(data.tag); + }); +}; + +/** + * Encrypts the CEK + * + * @param key_promise Promise + * @param cek_promise Promise + * @return Promise + */ +JoseJWE.prototype.encryptCek = function(key_promise, cek_promise) { + var config = this.key_encryption; + return Promise.all([key_promise, cek_promise]).then(function(all) { + var key = all[0]; + var cek = all[1]; + return crypto.subtle.wrapKey("raw", cek, key, config.id); + }); +}; + +/** + * Encrypts plain_text with CEK. + * + * @param cek_promise Promise + * @param plain_text string + * @return Promise + */ +JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { + // Create header + var jwe_protected_header = JoseJWE.Utils.Base64Url.encode(JSON.stringify({ + "alg": this.key_encryption.jwe_name, + "enc": this.content_encryption.jwe_name + })); + + // Create the IV + var iv = this.createIV(); + if (iv.length != this.content_encryption.iv_bytes) { + return Promise.reject("encryptPlaintext: invalid IV length"); + } + + // Create the AAD + var aad = JoseJWE.Utils.arrayFromString(jwe_protected_header); + plain_text = JoseJWE.Utils.arrayFromString(plain_text); + + var config = this.content_encryption; + if (config.auth.aead) { + var tag_bytes = config.auth.tag_bytes; + + var enc = { + name: config.id.name, + iv: iv, + additionalData: aad, + tagLength: tag_bytes * 8 + }; + + return cek_promise.then(function(cek) { + return crypto.subtle.encrypt(enc, cek, plain_text).then(function(cipher_text) { + var offset = cipher_text.byteLength - tag_bytes; + return { + header: jwe_protected_header, + iv: iv, + cipher: cipher_text.slice(0, offset), + tag: cipher_text.slice(offset) + }; + }); + }); + } else { + // We need to split the CEK key into a MAC and ENC keys + var cek_bytes_promise = cek_promise.then(function(cek) { + return crypto.subtle.exportKey("raw", cek); + }); + var mac_key_promise = cek_bytes_promise.then(function(cek_bytes) { + if (cek_bytes.byteLength * 8 != config.id.length + config.auth.key_bytes * 8) { + return Promise.reject("encryptPlaintext: incorrect cek length"); + } + var bytes = cek_bytes.slice(0, config.auth.key_bytes); + return crypto.subtle.importKey("raw", bytes, config.auth.id, false, ["sign"]); + }); + var enc_key_promise = cek_bytes_promise.then(function(cek_bytes) { + if (cek_bytes.byteLength * 8 != config.id.length + config.auth.key_bytes * 8) { + return Promise.reject("encryptPlaintext: incorrect cek length"); + } + var bytes = cek_bytes.slice(config.auth.key_bytes); + return crypto.subtle.importKey("raw", bytes, config.id, false, ["encrypt"]); + }); + + // Encrypt the plaintext + var cipher_text_promise = enc_key_promise.then(function(enc_key) { + var enc = { + name: config.id.name, + iv: iv, + }; + return crypto.subtle.encrypt(enc, enc_key, plain_text); + }); + + // MAC the aad, iv and ciphertext + var mac_promise = Promise.all([mac_key_promise, cipher_text_promise]).then(function(all) { + var mac_key = all[0]; + var cipher_text = all[1]; + var al = new Uint8Array(JoseJWE.Utils.arrayFromInt32(aad.length * 8)); + var al_bigendian = new Uint8Array(8); + for (var i=0; i<4; i++) { + al_bigendian[7-i] = al[i]; + } + var buf = JoseJWE.Utils.arrayBufferConcat(aad.buffer, iv.buffer, cipher_text, al_bigendian.buffer); + return crypto.subtle.sign(config.auth.id, mac_key, buf); + }); + + return Promise.all([cipher_text_promise, mac_promise]).then(function(all) { + var cipher_text = all[0]; + var mac = all[1]; + return { + header: jwe_protected_header, + iv: iv, + cipher: cipher_text, + tag: mac.slice(0, config.auth.truncated_bytes) + }; + }); + } +}; diff --git a/dist/jose-jwe.min.js b/dist/jose-jwe.min.js new file mode 100644 index 0000000..e20626a --- /dev/null +++ b/dist/jose-jwe.min.js @@ -0,0 +1 @@ +function JoseJWE(){this.setKeyEncryptionAlgorithm("RSA-OAEP"),this.setContentEncryptionAlgorithm("A256GCM")}JoseJWE.assert=function(a,b){if(!a)throw new Error(b)},JoseJWE.prototype.setKeyEncryptionAlgorithm=function(a){this.key_encryption=JoseJWE.getCryptoConfig(a)},JoseJWE.prototype.setContentEncryptionAlgorithm=function(a){this.content_encryption=JoseJWE.getCryptoConfig(a)},JoseJWE.getCryptoConfig=function(a){switch(a){case"RSA-OAEP":return{jwe_name:"RSA-OAEP",id:{name:"RSA-OAEP",hash:{name:"SHA-1"}}};case"RSA-OAEP-256":return{jwe_name:"RSA-OAEP-256",id:{name:"RSA-OAEP",hash:{name:"SHA-256"}}};case"A128KW":return{jwe_name:"A128KW",id:{name:"AES-KW",length:128}};case"A256KW":return{jwe_name:"A256KW",id:{name:"AES-KW",length:256}};case"A128CBC-HS256":return{jwe_name:"A128CBC-HS256",id:{name:"AES-CBC",length:128},iv_bytes:16,specific_cek_bytes:32,auth:{key_bytes:16,id:{name:"HMAC",hash:{name:"SHA-256"}},truncated_bytes:16}};case"A256CBC-HS512":return{id:{name:"AES-CBC",length:256},iv_bytes:16,specific_cek_bytes:64,auth:{key_bytes:32,id:{name:"HMAC",hash:{name:"SHA-512"}},truncated_bytes:32}};case"A128GCM":return{id:{name:"AES-GCM",length:128},iv_bytes:12,auth:{aead:!0,tag_bytes:16}};case"A256GCM":return{jwe_name:"A256GCM",id:{name:"AES-GCM",length:256},iv_bytes:12,auth:{aead:!0,tag_bytes:16}};default:JoseJWE.assert(!1,"unsupported algorithm: "+a)}},JoseJWE.Utils={},JoseJWE.Utils.isString=function(a){return"string"==typeof a||a instanceof String},JoseJWE.Utils.importRsaKeyFromHex=function(a,b){if(!JoseJWE.Utils.isString(a.n))return Promise.reject("importRsaKeyFromHex: expecting rsa_key['n'] to be a string");if("number"!=typeof a.e)return Promise.reject("importRsaKeyFromHex: expecting rsa_key['e'] to be a number");if(!(b instanceof Array))return Promise.reject("importRsaKeyFromHex: expecting purpose to be an array");b.push("wrapKey");var c=a.n.split(":").map(function(a){return String.fromCharCode(parseInt(a,16))}).join(""),d={kty:"RSA",n:JoseJWE.Utils.Base64Url.encode(c),e:JoseJWE.Utils.Base64Url.encodeArrayBuffer(JoseJWE.Utils.arrayFromInt32(a.e))},e=JoseJWE.getCryptoConfig("RSA-OAEP");return crypto.subtle.importKey("jwk",d,e.id,!1,b)},JoseJWE.Utils.importRSAfromJWK=function(a,b){if(!JoseJWE.Utils.isString(a.kty))return Promise.reject("importRSAfromJWK: expecting jwk['kty'] to be a string");if("RSA"!=a.kty)return Promise.reject("importRSAfromJWK: expecting jwk['kty'] to be RSA");if(!JoseJWE.Utils.isString(a.n))return Promise.reject("importRSAfromJWK: expecting jwk['n'] to be a string");if(!JoseJWE.Utils.isString(a.e))return Promise.reject("importRSAfromJWK: expecting jwk['e'] to be a string");if(!(b instanceof Array))return Promise.reject("importRSAfromJWK: expecting purpose to be an array");b.push("wrapKey");var c=JoseJWE.getCryptoConfig("RSA-OAEP");return crypto.subtle.importKey("jwk",a,c.id,!1,b)},JoseJWE.Utils.arrayFromString=function(a){var b=a.split("").map(function(a){return a.charCodeAt(0)});return new Uint8Array(b)},JoseJWE.Utils.arrayFromInt32=function(a){return JoseJWE.assert("number"==typeof a),JoseJWE.assert(a==a|0,"arrayFromInt32: out of range"),new Uint32Array([a]).buffer},JoseJWE.Utils.arrayBufferConcat=function(){for(var a=0,b=0;bi;i++)h[7-i]=g[i];var j=JoseJWE.Utils.arrayBufferConcat(e.buffer,d.buffer,c,h.buffer);return crypto.subtle.sign(f.auth.id,b,j)});return Promise.all([l,m]).then(function(a){var b=a[0],e=a[1];return{header:c,iv:d,cipher:b,tag:e.slice(0,f.auth.truncated_bytes)}})}; \ No newline at end of file diff --git a/jose-jwe-example1.html b/examples/jose-jwe-example1.html similarity index 100% rename from jose-jwe-example1.html rename to examples/jose-jwe-example1.html diff --git a/jose-jwe-example2.html b/examples/jose-jwe-example2.html similarity index 100% rename from jose-jwe-example2.html rename to examples/jose-jwe-example2.html diff --git a/jose-jwe.js b/lib/jose-jwe-core.js similarity index 100% rename from jose-jwe.js rename to lib/jose-jwe-core.js diff --git a/jose-jwe-encrypt.js b/lib/jose-jwe-encrypt.js similarity index 97% rename from jose-jwe-encrypt.js rename to lib/jose-jwe-encrypt.js index 40b2622..83fc22d 100644 --- a/jose-jwe-encrypt.js +++ b/lib/jose-jwe-encrypt.js @@ -111,7 +111,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { var jwe_protected_header = JoseJWE.Utils.Base64Url.encode(JSON.stringify({ "alg": this.key_encryption.jwe_name, "enc": this.content_encryption.jwe_name - })) + })); // Create the IV var iv = this.createIV(); @@ -121,7 +121,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { // Create the AAD var aad = JoseJWE.Utils.arrayFromString(jwe_protected_header); - var plain_text = JoseJWE.Utils.arrayFromString(plain_text); + plain_text = JoseJWE.Utils.arrayFromString(plain_text); var config = this.content_encryption; if (config.auth.aead) { @@ -142,7 +142,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { iv: iv, cipher: cipher_text.slice(0, offset), tag: cipher_text.slice(offset) - } + }; }); }); } else { @@ -171,7 +171,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { name: config.id.name, iv: iv, }; - return crypto.subtle.encrypt(enc, enc_key, plain_text) + return crypto.subtle.encrypt(enc, enc_key, plain_text); }); // MAC the aad, iv and ciphertext @@ -184,7 +184,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { al_bigendian[7-i] = al[i]; } var buf = JoseJWE.Utils.arrayBufferConcat(aad.buffer, iv.buffer, cipher_text, al_bigendian.buffer); - return crypto.subtle.sign(config.auth.id, mac_key, buf) + return crypto.subtle.sign(config.auth.id, mac_key, buf); }); return Promise.all([cipher_text_promise, mac_promise]).then(function(all) { @@ -195,7 +195,7 @@ JoseJWE.prototype.encryptPlaintext = function(cek_promise, plain_text) { iv: iv, cipher: cipher_text, tag: mac.slice(0, config.auth.truncated_bytes) - } + }; }); } }; diff --git a/jose-jwe-utils.js b/lib/jose-jwe-utils.js similarity index 99% rename from jose-jwe-utils.js rename to lib/jose-jwe-utils.js index 8d08615..016da91 100644 --- a/jose-jwe-utils.js +++ b/lib/jose-jwe-utils.js @@ -45,13 +45,13 @@ JoseJWE.Utils.importRsaKeyFromHex = function(rsa_key, purpose) { purpose.push("wrapKey"); var n = rsa_key.n .split(':') - .map(function(e){return String.fromCharCode(parseInt(e, 16))}) + .map(function(e){return String.fromCharCode(parseInt(e, 16));}) .join(''); var jwk = { "kty": "RSA", "n": JoseJWE.Utils.Base64Url.encode(n), "e": JoseJWE.Utils.Base64Url.encodeArrayBuffer(JoseJWE.Utils.arrayFromInt32(rsa_key.e)) - } + }; var config = JoseJWE.getCryptoConfig("RSA-OAEP"); return crypto.subtle.importKey("jwk", jwk, config.id, false, purpose); }; @@ -98,7 +98,7 @@ JoseJWE.Utils.importRSAfromJWK = function(jwk, purpose) { * @return Uint8Array */ JoseJWE.Utils.arrayFromString = function(str) { - var arr = str.split('').map(function(c){return c.charCodeAt(0)}); + var arr = str.split('').map(function(c){return c.charCodeAt(0);}); return new Uint8Array(arr); }; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a146933 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "jose-jwe", + "version": "0.0.1", + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-uglify": "^0.6.0", + "grunt-karma": "^0.9.0", + "karma": "^0.12.28", + "karma-chrome-launcher": "^0.1.5", + "karma-qunit": "^0.1.4", + "qunitjs": "^1.15.0" + } +} diff --git a/qunit-1.15.0.css b/qunit-1.15.0.css deleted file mode 100644 index 9437b4b..0000000 --- a/qunit-1.15.0.css +++ /dev/null @@ -1,237 +0,0 @@ -/*! - * QUnit 1.15.0 - * http://qunitjs.com/ - * - * Copyright 2014 jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-08-08T16:00Z - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699A4; - background-color: #0D3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: 400; - - border-radius: 5px 5px 0 0; -} - -#qunit-header a { - text-decoration: none; - color: #C2CCD1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #FFF; -} - -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 0.5em 0 0.1em; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 1em 0.5em 1em; - color: #5E740B; - background-color: #EEE; - overflow: hidden; -} - -#qunit-userAgent { - padding: 0.5em 1em 0.5em 1em; - background-color: #2B81AF; - color: #FFF; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; -} - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 1em 0.4em 1em; - border-bottom: 1px solid #FFF; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #C2CCD1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests li .runtime { - float: right; - font-size: smaller; -} - -.qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #FFF; - - border-radius: 5px; -} - -.qunit-collapsed { - display: none; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: 0.2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 0.5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #E0F2BE; - color: #374E0C; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #FFCACA; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: #000; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #FFF; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3C510C; - background-color: #FFF; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #FFF; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; -} - -#qunit-tests .fail { color: #000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: #008000; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 1em 0.5em 1em; - - color: #2B81AF; - background-color: #D2E0E6; - - border-bottom: 1px solid #FFF; -} -#qunit-testresult .module-name { - font-weight: 700; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} diff --git a/qunit-1.15.0.js b/qunit-1.15.0.js deleted file mode 100644 index 474cfe5..0000000 --- a/qunit-1.15.0.js +++ /dev/null @@ -1,2495 +0,0 @@ -/*! - * QUnit 1.15.0 - * http://qunitjs.com/ - * - * Copyright 2014 jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-08-08T16:00Z - */ - -(function( window ) { - -var QUnit, - config, - onErrorFnPrev, - fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - now = Date.now || function() { - return new Date().getTime(); - }, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: typeof window.document !== "undefined", - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[ key ]; - vals[ key ] = val === Object( val ) ? objectValues( val ) : val; - } - } - return vals; - }; - -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[ name ] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - test = new Test({ - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - start: function( count ) { - var message; - - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - - // Set the starting time when the first test is run - QUnit.config.started = QUnit.config.started || now(); - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - - message = "Called start() while already started (QUnit.config.semaphore was 0 already)"; - - if ( config.current ) { - QUnit.pushFailure( message, sourceFromStacktrace( 2 ) ); - } else { - throw new Error( message ); - } - - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13 ); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// We use the prototype to distinguish between properties that should -// be exposed as globals (and in exports) and those that shouldn't -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // by default, scroll to top of the page when suite is done - scrolltop: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - callbacks: {} -}; - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = []; - if ( urlParams.testNumber ) { - - // Ensure that urlParams.testNumber is an array - urlParams.testNumber = [].concat( urlParams.testNumber ); - for ( i = 0; i < urlParams.testNumber.length; i++ ) { - current = urlParams.testNumber[ i ]; - config.testNumber.push( parseInt( current, 10 ) ); - } - } - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -extend( QUnit, { - - config: config, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), - type = match && match[ 1 ] || ""; - - switch ( type ) { - case "Number": - if ( isNaN( obj ) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total, runtime } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); - -QUnit.load = function() { - runLoggingCallbacks( "begin", { - totalTests: Test.count - }); - - // Initialize the configuration options - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: 0, - updateRate: 1000, - autostart: true, - filter: "", - semaphore: 1 - }, true ); - - config.blocking = false; - - if ( config.autostart ) { - QUnit.start(); - } -}; - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend(function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - delete config.previousModule; - - var runtime = now() - config.started, - passed = config.stats.all - config.stats.bad; - - runLoggingCallbacks( "done", { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = ( test.module + ": " + test.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber.length > 0 ) { - if ( inArray( test.testNumber, config.testNumber ) < 0 ) { - return false; - } - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// Doesn't support IE6 to IE9 -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 4 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - - // Opera 12.x - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - - // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node - stack = e.stack.split( "\n" ); - if ( /^error$/i.test( stack[ 0 ] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - - // Safari < 6 - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = now(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[ i ] === b[ j ] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b, undefOnly ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -function registerLoggingCallback( key ) { - - // Initialize key collection of logging callback - if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - return function( callback ) { - config.callbacks[ key ].push( callback ); - }; -} - -function runLoggingCallbacks( key, args ) { - var i, l, callbacks; - - callbacks = config.callbacks[ key ]; - for ( i = 0, l = callbacks.length; i < l; i++ ) { - callbacks[ i ]( args ); - } -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -function Test( settings ) { - extend( this, settings ); - this.assert = new Assert( this ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - setup: function() { - if ( - - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = now(); - runLoggingCallbacks( "testStart", { - name: this.testName, - module: this.module, - testNumber: this.testNumber - }); - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, this.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, this.assert ); - } catch ( e ) { - this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - } - }, - run: function() { - config.current = this; - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = now(); - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, this.assert ); - this.callbackRuntime = now() - this.callbackStarted; - return; - } - - try { - this.callback.call( this.testEnvironment, this.assert ); - this.callbackRuntime = now() - this.callbackStarted; - } catch ( e ) { - this.callbackRuntime = now() - this.callbackStarted; - - this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = now() - this.callbackStarted; - } - this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); - } catch ( e ) { - this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var i, - bad = 0; - - this.runtime = now() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[ i ].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - runLoggingCallbacks( "testDone", { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: this.runtime, - - // HTML Reporter use - assertions: this.assertions, - testNumber: this.testNumber, - - // DEPRECATED: this property will be removed in 2.0.0, use runtime instead - duration: this.runtime - }); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - }, - - push: function( result, actual, expected, message ) { - var source, - details = { - module: this.module, - name: this.testName, - result: result, - message: message, - actual: actual, - expected: expected, - testNumber: this.testNumber - }; - - if ( !result ) { - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - } - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push({ - result: !!result, - message: message - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !this instanceof Test ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) ); - } - - var details = { - module: this.module, - name: this.testName, - result: false, - message: message || "error", - actual: actual || null, - testNumber: this.testNumber - }; - - if ( source ) { - details.source = source; - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push({ - result: false, - message: message - }); - } -}; - -QUnit.pushFailure = function() { - if ( !QUnit.config.current ) { - throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) ); - } - - // Gets current test obj - var currentTest = QUnit.config.current.assert.test; - - return currentTest.pushFailure.apply( currentTest, arguments ); -}; - -function Assert( testContext ) { - this.test = testContext; -} - -// Assert helpers -QUnit.assert = Assert.prototype = { - - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if ( arguments.length === 1 ) { - this.test.expected = asserts; - } else { - return this.test.expected; - } - }, - - // Exports test.push() to the user API - push: function() { - var assert = this; - - // Backwards compatibility fix. - // Allows the direct use of global exported assertions and QUnit.assert.* - // Although, it's use is not recommended as it can leak assertions - // to other tests from async tests, because we only get a reference to the current test, - // not exactly the test where assertion were intended to be called. - if ( !QUnit.config.current ) { - throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); - } - if ( !( assert instanceof Assert ) ) { - assert = QUnit.config.current.assert; - } - return assert.test.push.apply( assert.test, arguments ); - }, - - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, message ) { - message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + - QUnit.dump.parse( result ) ); - if ( !!result ) { - this.push( true, result, true, message ); - } else { - this.test.pushFailure( message, null, result ); - } - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - this.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - this.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, expectedType, - expectedOutput = expected, - ok = false; - - // 'expected' is optional unless doing string comparison - if ( message == null && typeof expected === "string" ) { - message = expected; - expected = null; - } - - this.test.ignoreGlobalErrors = true; - try { - block.call( this.test.testEnvironment ); - } catch (e) { - actual = e; - } - this.test.ignoreGlobalErrors = false; - - if ( actual ) { - expectedType = QUnit.objectType( expected ); - - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - - // expected is a regexp - } else if ( expectedType === "regexp" ) { - ok = expected.test( errorString( actual ) ); - - // expected is a string - } else if ( expectedType === "string" ) { - ok = expected === errorString( actual ); - - // expected is a constructor, maybe an Error constructor - } else if ( expectedType === "function" && actual instanceof expected ) { - ok = true; - - // expected is an Error object - } else if ( expectedType === "object" ) { - ok = actual instanceof expected.constructor && - actual.name === expected.name && - actual.message === expected.message; - - // expected is a validation function which returns true if validation passed - } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - this.push( ok, actual, expectedOutput, message ); - } else { - this.test.pushFailure( message, null, "No exception was thrown." ); - } - } -}; - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - - // stack to decide between skip/abort functions - callers = [], - - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function( obj ) { - /* jshint camelcase: false, proto: true */ - return obj.__proto__; - }, - callbacks = (function() { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - - // the regex itself - a.source === b.source && - - // and its modifiers - a.global === b.global && - - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[ callers.length - 1 ]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "object": function( b, a ) { - - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) || - ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push( i ); - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return ( (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType( a ) !== QUnit.objectType( b ) ) { - - // don't lose time with error prone cases - return false; - } else { - return bindCallbacks( a, callbacks, [ b, a ] ); - } - - // apply transition with (1..n) arguments - }( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); - }; - - return innerEquiv; -}()); - -// Based on jsDump by Ariel Flesler -// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html -QUnit.dump = (function() { - function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = dump.separator(), - base = dump.indent(), - inner = dump.indent( 1 ); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join( s ); - } - function array( arr, stack ) { - var i = arr.length, - ret = new Array( i ); - this.up(); - while ( i-- ) { - ret[ i ] = this.parse( arr[ i ], undefined, stack ); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - dump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || []; - var inStack, res, - parser = this.parsers[ type || this.typeOf( obj ) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + ( inStack - stack.length ) + ")"; - } - if ( type === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( type === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj ) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj ) ) { - type = "date"; - } else if ( QUnit.is( "function", obj ) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - - // native arrays - toString.call( obj ) === "[object Array]" || - - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join( chr ); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[ name ] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - depth: 1, - // This is the list of parsers, to modify them, use dump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function( error ) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - // functions never have name in IE - name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, dump.parse( fn, "functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - /*jshint forin:false */ - var ret = [], keys, key, val, i, nonEnumerableProperties; - dump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - - // Some properties are not always enumerable on Error objects. - nonEnumerableProperties = [ "message", "name" ]; - for ( i in nonEnumerableProperties ) { - key = nonEnumerableProperties[ i ]; - if ( key in map && !( key in keys ) ) { - keys.push( key ); - } - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) ); - } - dump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = dump.HTML ? "<" : "<", - close = dump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[ i ].nodeValue; - - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array( l ); - while ( l-- ) { - - // 97 is 'a' - args[ l ] = String.fromCharCode( 97 + l ); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return dump; -}()); - -// back compat -QUnit.jsDump = QUnit.dump; - -// For browser, export only select globals -if ( typeof window !== "undefined" ) { - - // Deprecated - // Extend assert methods to QUnit and Global scope through Backwards compatibility - (function() { - var i, - assertions = Assert.prototype; - - function applyCurrent( current ) { - return function() { - var assert = new Assert( QUnit.config.current ); - current.apply( assert, arguments ); - }; - } - - for ( i in assertions ) { - QUnit[ i ] = applyCurrent( assertions[ i ] ); - } - })(); - - (function() { - var i, l, - keys = [ - "test", - "module", - "expect", - "asyncTest", - "start", - "stop", - "ok", - "equal", - "notEqual", - "propEqual", - "notPropEqual", - "deepEqual", - "notDeepEqual", - "strictEqual", - "notStrictEqual", - "throws" - ]; - - for ( i = 0, l = keys.length; i < l; i++ ) { - window[ keys[ i ] ] = QUnit[ keys[ i ] ]; - } - })(); - - window.QUnit = QUnit; -} - -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { - module.exports = QUnit; -} - -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); - -/*istanbul ignore next */ -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - var hasOwn = Object.prototype.hasOwnProperty; - - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[ i ] ) ) { - ns[ n[ i ] ] = { - rows: [], - o: null - }; - } - ns[ n[ i ] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[ i ] ) ) { - os[ o[ i ] ] = { - rows: [], - n: null - }; - } - os[ o[ i ] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) { - n[ ns[ i ].rows[ 0 ] ] = { - text: n[ ns[ i ].rows[ 0 ] ], - row: os[ i ].rows[ 0 ] - }; - o[ os[ i ].rows[ 0 ] ] = { - text: o[ os[ i ].rows[ 0 ] ], - row: ns[ i ].rows[ 0 ] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null && - n[ i + 1 ] == o[ n[ i ].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[ i ].row + 1 - }; - o[ n[ i ].row + 1 ] = { - text: o[ n[ i ].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null && - n[ i - 1 ] == o[ n[ i ].row - 1 ] ) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[ i ].row - 1 - }; - o[ n[ i ].row - 1 ] = { - text: o[ n[ i ].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ), - oSpace = o.match( /\s+/g ), - nSpace = n.match( /\s+/g ); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[ i ] + oSpace[ i ] + ""; - } - } else { - if ( out.n[ 0 ].text == null ) { - for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) { - str += "" + out.o[ n ] + oSpace[ n ] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if ( out.n[ i ].text == null ) { - str += "" + out.n[ i ] + nSpace[ i ] + ""; - } else { - - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) { - pre += "" + out.o[ n ] + oSpace[ n ] + ""; - } - str += " " + out.n[ i ].text + nSpace[ i ] + pre; - } - } - } - - return str; - }; -}()); - -(function() { - -// Deprecated QUnit.init - Ref #530 -// Re-initialize the configuration options -QUnit.init = function() { - var tests, banner, result, qunit, - config = QUnit.config; - - config.stats = { all: 0, bad: 0 }; - config.moduleStats = { all: 0, bad: 0 }; - config.started = 0; - config.updateRate = 1000; - config.blocking = false; - config.autostart = true; - config.autorun = false; - config.filter = ""; - config.queue = []; - config.semaphore = 1; - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { - return; - } - - qunit = id( "qunit" ); - if ( qunit ) { - qunit.innerHTML = - "

" + escapeText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } -}; - -// Resets the test setup. Useful for tests that modify the DOM. -/* -DEPRECATED: Use multiple tests instead of resetting inside a test. -Use testStart or testDone for custom cleanup. -This method will throw an error in 2.0, and will be removed in 2.1 -*/ -QUnit.reset = function() { - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { - return; - } - - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } -}; - -// Don't load the HTML Reporter on non-Browser environments -if ( typeof window === "undefined" ) { - return; -} - -var config = QUnit.config, - hasOwn = Object.prototype.hasOwnProperty, - defined = { - document: typeof window.document !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }()) - }; - -/** -* Escape text for attribute or text content. -*/ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch ( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, fn ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[ i ], type, fn ); - } -} - -function hasClass( elem, name ) { - return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += ( elem.className ? " " : "" ) + name; - } -} - -function toggleClass( elem, name ) { - if ( hasClass( elem, name ) ) { - removeClass( elem, name ); - } else { - addClass( elem, name ); - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - - // Class name may appear multiple times - while ( set.indexOf( " " + name + " " ) >= 0 ) { - set = set.replace( " " + name + " ", " " ); - } - - // trim for prettiness - elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} - -function getUrlConfigHtml() { - var i, j, val, - escaped, escapedTooltip, - selection = false, - len = config.urlConfig.length, - urlConfigHtml = ""; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[ i ]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - - escaped = escapeText( val.id ); - escapedTooltip = escapeText( val.tooltip ); - - config[ val.id ] = QUnit.urlParams[ val.id ]; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; -} - -function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); - - return urlConfigContainer; -} - -function getModuleNames() { - var i, - moduleNames = []; - - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push( i ); - } - } - - moduleNames.sort(function( a, b ) { - return a.localeCompare( b ); - }); - - return moduleNames; -} - -function toolbarModuleFilterHtml() { - var i, - moduleFilterHtml = "", - moduleNames = getModuleNames(); - - if ( moduleNames.length <= 1 ) { - return false; - } - - moduleFilterHtml += "" + - ""; - - return moduleFilterHtml; -} - -function toolbarModuleFilter() { - var moduleFilter = document.createElement( "span" ), - moduleFilterHtml = toolbarModuleFilterHtml(); - - if ( !moduleFilterHtml ) { - return false; - } - - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ], - selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - - return moduleFilter; -} - -function toolbarFilter() { - var testList = id( "qunit-tests" ), - filter = document.createElement( "input" ); - - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - if ( filter.checked ) { - addClass( testList, "hidepass" ); - if ( defined.sessionStorage ) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } - } else { - removeClass( testList, "hidepass" ); - if ( defined.sessionStorage ) { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && - sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - - addClass( testList, "hidepass" ); - } - - return filter; -} - -function toolbarLabel() { - var label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - - return label; -} - -function appendToolbar() { - var moduleFilter, - toolbar = id( "qunit-testrunner-toolbar" ); - - if ( toolbar ) { - toolbar.appendChild( toolbarFilter() ); - toolbar.appendChild( toolbarLabel() ); - toolbar.appendChild( toolbarUrlConfigContainer() ); - - moduleFilter = toolbarModuleFilter(); - if ( moduleFilter ) { - toolbar.appendChild( moduleFilter ); - } - } -} - -function appendBanner() { - var banner = id( "qunit-banner" ); - - if ( banner ) { - banner.className = ""; - banner.innerHTML = "" + banner.innerHTML + " "; - } -} - -function appendTestResults() { - var tests = id( "qunit-tests" ), - result = id( "qunit-testresult" ); - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - tests.innerHTML = ""; - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } -} - -function storeFixture() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - config.fixture = fixture.innerHTML; - } -} - -function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } -} - -// HTML Reporter initialization and load -QUnit.begin(function() { - var qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "
    " + - "

    " + - "
      "; - } - - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - storeFixture(); -}); - -QUnit.done(function( details ) { - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - html = [ - "Tests completed in ", - details.runtime, - " milliseconds.
      ", - "", - details.passed, - " assertions of ", - details.total, - " passed, ", - details.failed, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = details.failed ? "qunit-fail" : "qunit-pass"; - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( details.failed ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo( 0, 0 ); - } -}); - -function getNameHtml( name, module ) { - var nameHtml = ""; - - if ( module ) { - nameHtml = "" + escapeText( module ) + ": "; - } - - nameHtml += "" + escapeText( name ) + ""; - - return nameHtml; -} - -QUnit.testStart(function( details ) { - var a, b, li, running, assertList, - name = getNameHtml( details.name, details.module ), - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = name; - - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: details.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = "qunit-test-output" + details.testNumber; - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; - - li.appendChild( assertList ); - - tests.appendChild( li ); - } - - running = id( "qunit-testresult" ); - if ( running ) { - running.innerHTML = "Running:
      " + name; - } - -}); - -QUnit.log(function( details ) { - var assertList, assertLi, - message, expected, actual, - testItem = id( "qunit-test-output" + details.testNumber ); - - if ( !testItem ) { - return; - } - - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - - // pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if ( !details.result && hasOwn.call( details, "expected" ) ) { - expected = escapeText( QUnit.dump.parse( details.expected ) ); - actual = escapeText( QUnit.dump.parse( details.actual ) ); - message += ""; - - if ( actual !== expected ) { - message += "" + - ""; - } - - if ( details.source ) { - message += ""; - } - - message += "
      Expected:
      " +
      -			expected +
      -			"
      Result:
      " +
      -				actual + "
      Diff:
      " +
      -				QUnit.diff( expected, actual ) + "
      Source:
      " +
      -				escapeText( details.source ) + "
      "; - - // this occours when pushFailure is set and we have an extracted stack trace - } else if ( !details.result && details.source ) { - message += "" + - "" + - "
      Source:
      " +
      -			escapeText( details.source ) + "
      "; - } - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - assertLi = document.createElement( "li" ); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); -}); - -QUnit.testDone(function( details ) { - var testTitle, time, testItem, assertList, - good, bad, testCounts, - tests = id( "qunit-tests" ); - - // QUnit.reset() is deprecated and will be replaced for a new - // fixture reset function on QUnit 2.0/2.1. - // It's still called here for backwards compatibility handling - QUnit.reset(); - - if ( !tests ) { - return; - } - - testItem = id( "qunit-test-output" + details.testNumber ); - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - good = details.passed; - bad = details.failed; - - // store result when possible - if ( config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); - } - } - - if ( bad === 0 ) { - addClass( assertList, "qunit-collapsed" ); - } - - // testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; - - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; - - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - }); - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - - testItem.className = bad ? "fail" : "pass"; - - testItem.insertBefore( time, assertList ); -}); - -if ( !defined.document || document.readyState === "complete" ) { - config.autorun = true; -} - -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); -} - -})(); diff --git a/jose-jwe-test.html b/test/jose-jwe-test.html similarity index 94% rename from jose-jwe-test.html rename to test/jose-jwe-test.html index c86bc07..acf7b5e 100644 --- a/jose-jwe-test.html +++ b/test/jose-jwe-test.html @@ -3,13 +3,9 @@ Jose JWE Test - - - + - - - + - +
      \ No newline at end of file diff --git a/qunit-promises.js b/test/qunit-promises.js similarity index 100% rename from qunit-promises.js rename to test/qunit-promises.js