From a3a1c50b4eb8ccbde01242c59e7ec40e26514a6c Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Tue, 3 Dec 2013 15:14:20 -0500 Subject: [PATCH 1/5] Add Zlib adapter, generalize adapter tests --- lib/zlib.js | 40 ++++++++++++ src/adapters/adapters.js | 8 ++- src/adapters/crypto.js | 41 ++++++++++-- src/adapters/zlib.js | 63 +++++++++++++++++++ ...rypto.spec.js => adapters.general.spec.js} | 52 ++++++++++----- tests/spec/adapters/adapters.spec.js | 8 +++ tests/test-manifest.js | 2 +- 7 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 lib/zlib.js create mode 100644 src/adapters/zlib.js rename tests/spec/adapters/{adapters.crypto.spec.js => adapters.general.spec.js} (76%) diff --git a/lib/zlib.js b/lib/zlib.js new file mode 100644 index 0000000..34f0e8f --- /dev/null +++ b/lib/zlib.js @@ -0,0 +1,40 @@ +/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function l(d){throw d;}var u=void 0,x=!0,aa=this;function z(d,a){var c=d.split("."),f=aa;!(c[0]in f)&&f.execScript&&f.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==u?f[b]=a:f=f[b]?f[b]:f[b]={}};var E="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function G(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(E?Uint8Array:Array)?d:new (E?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&l(Error("invalid index"));this.buffer.length<=this.index&&this.f()}G.prototype.f=function(){var d=this.buffer,a,c=d.length,f=new (E?Uint8Array:Array)(c<<1);if(E)f.set(d);else for(a=0;a>>8&255]<<16|N[d>>>16&255]<<8|N[d>>>24&255])>>32-a:N[d]>>8-a);if(8>a+e)g=g<>a-h-1&1,8===++e&&(e=0,f[b++]=N[g],g=0,b===f.length&&(f=this.f()));f[b]=g;this.buffer=f;this.i=e;this.index=b};G.prototype.finish=function(){var d=this.buffer,a=this.index,c;0O;++O){for(var P=O,Q=P,ga=7,P=P>>>1;P;P>>>=1)Q<<=1,Q|=P&1,--ga;fa[O]=(Q<>>0}var N=fa;function ha(d){this.buffer=new (E?Uint16Array:Array)(2*d);this.length=0}ha.prototype.getParent=function(d){return 2*((d-2)/4|0)};ha.prototype.push=function(d,a){var c,f,b=this.buffer,e;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0b[f])e=b[c],b[c]=b[f],b[f]=e,e=b[c+1],b[c+1]=b[f+1],b[f+1]=e,c=f;else break;return this.length}; +ha.prototype.pop=function(){var d,a,c=this.buffer,f,b,e;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(e=0;;){b=2*e+2;if(b>=this.length)break;b+2c[b]&&(b+=2);if(c[b]>c[e])f=c[e],c[e]=c[b],c[b]=f,f=c[e+1],c[e+1]=c[b+1],c[b+1]=f;else break;e=b}return{index:d,value:a,length:this.length}};function R(d){var a=d.length,c=0,f=Number.POSITIVE_INFINITY,b,e,g,h,k,n,q,r,p;for(r=0;rc&&(c=d[r]),d[r]>=1;for(p=n;pS;S++)switch(x){case 143>=S:oa.push([S+48,8]);break;case 255>=S:oa.push([S-144+400,9]);break;case 279>=S:oa.push([S-256+0,7]);break;case 287>=S:oa.push([S-280+192,8]);break;default:l("invalid literal: "+S)} +ia.prototype.j=function(){var d,a,c,f,b=this.input;switch(this.h){case 0:c=0;for(f=b.length;c>>8&255;p[m++]=n&255;p[m++]=n>>>8&255;if(E)p.set(e,m),m+=e.length,p=p.subarray(0,m);else{q=0;for(r=e.length;qv)for(;0< +v--;)H[F++]=0,K[0]++;else for(;0v?v:138,C>v-3&&C=C?(H[F++]=17,H[F++]=C-3,K[17]++):(H[F++]=18,H[F++]=C-11,K[18]++),v-=C;else if(H[F++]=I[t],K[I[t]]++,v--,3>v)for(;0v?v:6,C>v-3&&CA;A++)ra[A]=ka[gb[A]];for(W=19;4=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272, +b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:l("invalid length: "+b)}}var a=[],c,f;for(c=3;258>=c;c++)f=d(c),a[c]=f[2]<<24|f[1]<< +16|f[0];return a}(),wa=E?new Uint32Array(va):va; +function pa(d,a){function c(b,c){var a=b.G,d=[],e=0,f;f=wa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(x){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a- +65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>= +a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:l("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h=e;)w[e++]=0;for(e=0;29>=e;)y[e++]=0}w[256]=1;f=0;for(b=a.length;f=b){r&&c(r,-1);e=0;for(g=b-f;eg&&a+ge&&(b=f,e=g);if(258===g)break}return new ta(e,a-b)} +function qa(d,a){var c=d.length,f=new ha(572),b=new (E?Uint8Array:Array)(c),e,g,h,k,n;if(!E)for(k=0;k2*b[m-1]+e[m]&&(b[m]=2*b[m-1]+e[m]),h[m]=Array(b[m]),k[m]=Array(b[m]);for(p=0;pd[p]?(h[m][s]=w,k[m][s]=a,y+=2):(h[m][s]=d[p],k[m][s]=p,++p);n[m]=0;1===e[m]&&f(m)}return g} +function sa(d){var a=new (E?Uint16Array:Array)(d.length),c=[],f=[],b=0,e,g,h,k;e=0;for(g=d.length;e>>=1}return a};function T(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=E?new Uint8Array(d):d;this.s=!1;this.n=za;this.B=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.B=a.resize);switch(this.n){case Aa:this.b=32768;this.a=new (E?Uint8Array:Array)(32768+this.m+258);break;case za:this.b=0;this.a=new (E?Uint8Array:Array)(this.m);this.f=this.J;this.t=this.H;this.o=this.I;break;default:l(Error("invalid inflate mode"))}} +var Aa=0,za=1,Ba={D:Aa,C:za}; +T.prototype.p=function(){for(;!this.s;){var d=Y(this,3);d&1&&(this.s=x);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,f=this.a,b=this.b,e=u,g=u,h=u,k=f.length,n=u;this.e=this.g=0;e=a[c++];e===u&&l(Error("invalid uncompressed block header: LEN (first byte)"));g=e;e=a[c++];e===u&&l(Error("invalid uncompressed block header: LEN (second byte)"));g|=e<<8;e=a[c++];e===u&&l(Error("invalid uncompressed block header: NLEN (first byte)"));h=e;e=a[c++];e===u&&l(Error("invalid uncompressed block header: NLEN (second byte)"));h|= +e<<8;g===~h&&l(Error("invalid uncompressed block header: length verify"));c+g>a.length&&l(Error("input buffer is broken"));switch(this.n){case Aa:for(;b+g>f.length;){n=k-b;g-=n;if(E)f.set(a.subarray(c,c+n),b),b+=n,c+=n;else for(;n--;)f[b++]=a[c++];this.b=b;f=this.f();b=this.b}break;case za:for(;b+g>f.length;)f=this.f({v:2});break;default:l(Error("invalid inflate mode"))}if(E)f.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)f[b++]=a[c++];this.c=c;this.b=b;this.a=f;break;case 1:this.o(Ca,Ra);break; +case 2:Sa(this);break;default:l(Error("unknown BTYPE: "+d))}}return this.t()}; +var Ta=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],Ua=E?new Uint16Array(Ta):Ta,Va=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],Wa=E?new Uint16Array(Va):Va,Xa=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Ya=E?new Uint8Array(Xa):Xa,Za=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],$a=E?new Uint16Array(Za):Za,ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10, +10,11,11,12,12,13,13],bb=E?new Uint8Array(ab):ab,cb=new (E?Uint8Array:Array)(288),Z,db;Z=0;for(db=cb.length;Z=Z?8:255>=Z?9:279>=Z?7:8;var Ca=R(cb),eb=new (E?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb>>a;d.e=f-a;d.c=e;return g} +function ib(d,a){for(var c=d.g,f=d.e,b=d.input,e=d.c,g=a[0],h=a[1],k,n,q;f>>16;d.g=c>>q;d.e=f-q;d.c=e;return n&65535} +function Sa(d){function a(a,b,c){var d,f,e,g;for(g=0;ge)f>=b&&(this.b=f,c=this.f(),f=this.b),c[f++]=e;else{g=e-257;k=Wa[g];0=b&&(this.b=f,c=this.f(),f=this.b);for(;k--;)c[f]=c[f++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=f}; +T.prototype.I=function(d,a){var c=this.a,f=this.b;this.u=d;for(var b=c.length,e,g,h,k;256!==(e=ib(this,d));)if(256>e)f>=b&&(c=this.f(),b=c.length),c[f++]=e;else{g=e-257;k=Wa[g];0b&&(c=this.f(),b=c.length);for(;k--;)c[f]=c[f++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=f}; +T.prototype.f=function(){var d=new (E?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,f,b=this.a;if(E)d.set(b.subarray(32768,d.length));else{c=0;for(f=d.length;cc;++c)b[c]=b[a+c];this.b=32768;return b}; +T.prototype.J=function(d){var a,c=this.input.length/this.c+1|0,f,b,e,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.F&&(c+=d.F));2>c?(f=(g.length-this.c)/this.u[2],e=258*(f/2)|0,b=ea&&(this.a.length=a),d=this.a);return this.buffer=d};function jb(d){if("string"===typeof d){var a=d.split(""),c,f;c=0;for(f=a.length;c>>0;d=a}for(var b=1,e=0,g=d.length,h,k=0;0>>0};function kb(d,a){var c,f;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.M=a.verify);c=d[this.c++];f=d[this.c++];switch(c&15){case lb:this.method=lb;break;default:l(Error("unsupported compression method"))}0!==((c<<8)+f)%31&&l(Error("invalid fcheck flag:"+((c<<8)+f)%31));f&32&&l(Error("fdict flag is not supported"));this.A=new T(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})} +kb.prototype.p=function(){var d=this.input,a,c;a=this.A.p();this.c=this.A.c;this.M&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==jb(a)&&l(Error("invalid adler-32 checksum")));return a};var lb=8;function mb(d,a){this.input=d;this.a=new (E?Uint8Array:Array)(32768);this.h=$.k;var c={},f;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(f in a)c[f]=a[f];c.outputBuffer=this.a;this.z=new ia(this.input,c)}var $=na; +mb.prototype.j=function(){var d,a,c,f,b,e,g,h=0;g=this.a;d=lb;switch(d){case lb:a=Math.LOG2E*Math.log(32768)-8;break;default:l(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case lb:switch(this.h){case $.NONE:b=0;break;case $.r:b=1;break;case $.k:b=2;break;default:l(Error("unsupported compression type"))}break;default:l(Error("invalid compression method"))}f=b<<6|0;g[h++]=f|31-(256*c+f)%31;e=jb(this.input);this.z.b=h;g=this.z.j();h=g.length;E&&(g=new Uint8Array(g.buffer),g.length<= +h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=e>>24&255;g[h++]=e>>16&255;g[h++]=e>>8&255;g[h++]=e&255;return g};function nb(d,a){var c,f,b,e;if(Object.keys)c=Object.keys(a);else for(f in c=[],b=0,a)c[b++]=f;b=0;for(e=c.length;b>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + u8[i]=byte; + } + + return u8; + }, + + parse: function (u8arr) { + // Shortcut + var len = u8arr.length; + + // Convert + var words = []; + for (var i = 0; i < len; i++) { + words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); + } + + return CryptoJS.lib.WordArray.create(words, len); + } + }; function buildCryptoAdapter(encryptionType) { // It is up to the app using this wrapper how the passphrase is acquired, probably by @@ -44,12 +75,14 @@ define(function(require) { function CryptoAdapter(passphrase, provider) { this.provider = provider; this.encrypt = function(plain) { - return CryptoJS[encryptionType].encrypt(plain, passphrase) - .toString(); + return CryptoJS[encryptionType] + .encrypt(plain, passphrase, {format: Uint8ArrayFormatter}) + .toString(); }; this.decrypt = function(encrypted) { - return CryptoJS[encryptionType].decrypt(encrypted, passphrase) - .toString(CryptoJS.enc.Utf8); + return CryptoJS[encryptionType] + .decrypt(encrypted, passphrase, {format: Uint8ArrayFormatter}) + .toString(); //CryptoJS.enc.Utf8); }; } CryptoAdapter.isSupported = function() { diff --git a/src/adapters/zlib.js b/src/adapters/zlib.js new file mode 100644 index 0000000..29d3c87 --- /dev/null +++ b/src/adapters/zlib.js @@ -0,0 +1,63 @@ +define(function(require) { + + // ZLib compression, see + // https://github.com/imaya/zlib.js/blob/master/bin/zlib.min.js + require("zlib"); + + var Inflate = Zlib.Inflate; + function inflate(compressed) { + return (new Inflate(compressed)).decompress(); + } + + var Deflate = Zlib.Deflate; + function deflate(buffer) { + return (new Deflate(buffer)).compress(); + } + + function ZlibContext(context) { + this.context = context; + } + ZlibContext.prototype.clear = function(callback) { + this.context.clear(callback); + }; + ZlibContext.prototype.get = function(key, callback) { + this.context.get(key, function(err, result) { + if(err) { + callback(err); + return; + } + // Deal with result being null + if(result) { + result = inflate(result); + } + callback(null, result); + }); + }; + ZlibContext.prototype.put = function(key, value, callback) { + value = deflate(value); + this.context.put(key, value, callback); + }; + ZlibContext.prototype.delete = function(key, callback) { + this.context.delete(key, callback); + }; + + + function ZlibAdapter(provider, inflate, deflate) { + this.provider = provider; + } + ZlibAdapter.isSupported = function() { + return true; + }; + + ZlibAdapter.prototype.open = function(callback) { + this.provider.open(callback); + }; + ZlibAdapter.prototype.getReadOnlyContext = function() { + return new ZlibContext(this.provider.getReadOnlyContext()); + }; + ZlibAdapter.prototype.getReadWriteContext = function() { + return new ZlibContext(this.provider.getReadWriteContext()); + }; + + return ZlibAdapter; +}); diff --git a/tests/spec/adapters/adapters.crypto.spec.js b/tests/spec/adapters/adapters.general.spec.js similarity index 76% rename from tests/spec/adapters/adapters.crypto.spec.js rename to tests/spec/adapters/adapters.general.spec.js index adbcb85..7e7cf7d 100644 --- a/tests/spec/adapters/adapters.crypto.spec.js +++ b/tests/spec/adapters/adapters.general.spec.js @@ -1,15 +1,23 @@ define(["IDBFS"], function(IDBFS) { - // We reuse the same set of tests for all crypto adapters. - // buildTestsFor() creates a set of tests bound to a crypto + // We reuse the same set of tests for all adapters. + // buildTestsFor() creates a set of tests bound to an // adapter, and uses a Memory() provider internally. - function buildTestsFor(adapterName) { - var passphrase = '' + Date.now(); + function buildTestsFor(adapterName, buildAdapter) { + function encode(str) { + // TextEncoder is either native, or shimmed by IDBFS + return (new TextEncoder("utf-8")).encode(str); + } + + // Make some string + binary buffer versions of things we'll need + var valueStr = "value", valueBuffer = encode(valueStr); + var value1Str = "value1", value1Buffer = encode(value1Str); + var value2Str = "value2", value2Buffer = encode(value2Str); function createProvider() { var memoryProvider = new IDBFS.FileSystem.providers.Memory(); - return new IDBFS.FileSystem.adapters[adapterName](passphrase, memoryProvider); + return buildAdapter(memoryProvider); } describe("IDBFS.FileSystem.adapters." + adapterName, function() { @@ -57,7 +65,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key", "value", function(err, result) { + context.put("key", valueBuffer, function(err, result) { _error = _error || err; context.get("key", function(err, result) { _error = _error || err; @@ -74,7 +82,7 @@ define(["IDBFS"], function(IDBFS) { runs(function() { expect(_error).toEqual(null); - expect(_result).toEqual("value"); + expect(_result).toEqual(valueBuffer); }); }); @@ -87,7 +95,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key", "value", function(err, result) { + context.put("key", valueBuffer, function(err, result) { _error = _error || err; context.delete("key", function(err, result) { _error = _error || err; @@ -120,9 +128,9 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key1", "value1", function(err, result) { + context.put("key1", value1Buffer, function(err, result) { _error = _error || err; - context.put("key2", "value2", function(err, result) { + context.put("key2", value2Buffer, function(err, result) { _error = _error || err; context.clear(function(err) { @@ -164,7 +172,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadOnlyContext(); - context.put("key1", "value1", function(err, result) { + context.put("key1", value1Buffer, function(err, result) { _error = _error || err; _result = result; @@ -185,8 +193,24 @@ define(["IDBFS"], function(IDBFS) { }); } - buildTestsFor('AES'); - buildTestsFor('TripleDES'); - buildTestsFor('Rabbit'); + + // Encryption + buildTestsFor('AES', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.AES(passphrase, provider); + }); + buildTestsFor('TripleDES', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.TripleDES(passphrase, provider); + }); + buildTestsFor('Rabbit', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.Rabbit(passphrase, provider); + }); + + // Compression + buildTestsFor('Zlib', function buildAdapter(provider) { + return new IDBFS.FileSystem.adapters.Zlib(provider); + }); }); diff --git a/tests/spec/adapters/adapters.spec.js b/tests/spec/adapters/adapters.spec.js index 9d55a59..c3b223a 100644 --- a/tests/spec/adapters/adapters.spec.js +++ b/tests/spec/adapters/adapters.spec.js @@ -19,5 +19,13 @@ define(["IDBFS"], function(IDBFS) { it("has a default Encryption constructor", function() { expect(typeof IDBFS.FileSystem.adapters.Encryption).toEqual('function'); }); + + it("has a Zlib constructor", function() { + expect(typeof IDBFS.FileSystem.adapters.Zlib).toEqual('function'); + }); + + it("has a default Compression constructor", function() { + expect(typeof IDBFS.FileSystem.adapters.Compression).toEqual('function'); + }); }); }); diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 698fe61..0f30a63 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -36,7 +36,7 @@ define([ // IDBFS.FileSystem.adapters.* "spec/adapters/adapters.spec", - "spec/adapters/adapters.crypto.spec", + "spec/adapters/adapters.general.spec", // Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test) "spec/node-js/simple/test-fs-mkdir", From aca2d8010461b3ddea393430188b3eda0cc13e7b Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Tue, 3 Dec 2013 15:23:47 -0500 Subject: [PATCH 2/5] Trying to fix encoding issue in crypto.js, not working yet --- src/adapters/crypto.js | 71 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/adapters/crypto.js b/src/adapters/crypto.js index 71c5ded..291f849 100644 --- a/src/adapters/crypto.js +++ b/src/adapters/crypto.js @@ -7,6 +7,38 @@ define(function(require) { // Rabbit, see http://code.google.com/p/crypto-js/#Rabbit require("crypto-js/rollups/rabbit"); + // Move back and forth from Uint8Arrays and CryptoJS' WordArray + // source: https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0 + Uint8ArrayFormatter = { + fromWordArray: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var u8 = new Uint8Array(sigBytes); + for (var i = 0; i < sigBytes; i++) { + var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + u8[i]=byte; + } +console.log("stringify", wordArray, u8); + return u8; + }, + + toWordArray: function (u8arr) { + // Shortcut + var len = u8arr.length; + + // Convert + var words = []; + for (var i = 0; i < len; i++) { + words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); + } +console.log("parse", u8arr, CryptoJS.lib.WordArray.create(words, len)); + return CryptoJS.lib.WordArray.create(words, len); + } + }; + function CryptoContext(context, encrypt, decrypt) { this.context = context; @@ -24,7 +56,7 @@ define(function(require) { return; } if(value) { - value = decrypt(value); + value = Uint8ArrayFormatter.fromWordArray(decrypt(value)); } callback(null, value); }); @@ -37,37 +69,6 @@ define(function(require) { this.context.delete(key, callback); }; - // Move back and forth from Uint8Arrays and CryptoJS' WordArray - // source: https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0 - Uint8ArrayFormatter = { - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var u8 = new Uint8Array(sigBytes); - for (var i = 0; i < sigBytes; i++) { - var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - u8[i]=byte; - } - - return u8; - }, - - parse: function (u8arr) { - // Shortcut - var len = u8arr.length; - - // Convert - var words = []; - for (var i = 0; i < len; i++) { - words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); - } - - return CryptoJS.lib.WordArray.create(words, len); - } - }; function buildCryptoAdapter(encryptionType) { // It is up to the app using this wrapper how the passphrase is acquired, probably by @@ -76,13 +77,13 @@ define(function(require) { this.provider = provider; this.encrypt = function(plain) { return CryptoJS[encryptionType] - .encrypt(plain, passphrase, {format: Uint8ArrayFormatter}) + .encrypt(Uint8ArrayFormatter.toWordArray(plain), passphrase) .toString(); }; this.decrypt = function(encrypted) { return CryptoJS[encryptionType] - .decrypt(encrypted, passphrase, {format: Uint8ArrayFormatter}) - .toString(); //CryptoJS.enc.Utf8); + .decrypt(encrypted, passphrase) + .toString(); }; } CryptoAdapter.isSupported = function() { From 82f4648ef584c2a6e424426d3f7c7956baf05bf7 Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Tue, 3 Dec 2013 16:14:51 -0500 Subject: [PATCH 3/5] Trying to sort out encodings to/from Uint8Array, not working --- src/adapters/crypto.js | 81 +++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/src/adapters/crypto.js b/src/adapters/crypto.js index 291f849..8271590 100644 --- a/src/adapters/crypto.js +++ b/src/adapters/crypto.js @@ -9,36 +9,30 @@ define(function(require) { // Move back and forth from Uint8Arrays and CryptoJS' WordArray // source: https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0 - Uint8ArrayFormatter = { - fromWordArray: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var u8 = new Uint8Array(sigBytes); - for (var i = 0; i < sigBytes; i++) { - var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - u8[i]=byte; - } -console.log("stringify", wordArray, u8); - return u8; - }, - - toWordArray: function (u8arr) { - // Shortcut - var len = u8arr.length; - - // Convert - var words = []; - for (var i = 0; i < len; i++) { - words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); - } -console.log("parse", u8arr, CryptoJS.lib.WordArray.create(words, len)); - return CryptoJS.lib.WordArray.create(words, len); + var WordArray = CryptoJS.lib.WordArray; + function fromWordArray(wordArray) { + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var u8 = new Uint8Array(sigBytes); + for (var i = 0; i < sigBytes; i++) { + var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + u8[i]=byte; } - }; + return u8; + } + function toWordArray(u8arr) { + var len = u8arr.length; + var words = []; + for (var i = 0; i < len; i++) { + words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); + } + return WordArray.create(words, len); + } + CryptoJS.enc.Uint8Array = { + stringify: fromWordArray, + parse: toWordArray + }; function CryptoContext(context, encrypt, decrypt) { this.context = context; @@ -56,7 +50,7 @@ console.log("parse", u8arr, CryptoJS.lib.WordArray.create(words, len)); return; } if(value) { - value = Uint8ArrayFormatter.fromWordArray(decrypt(value)); + value = decrypt(value); } callback(null, value); }); @@ -75,15 +69,30 @@ console.log("parse", u8arr, CryptoJS.lib.WordArray.create(words, len)); // prompting the user to enter it when the file system is being opened. function CryptoAdapter(passphrase, provider) { this.provider = provider; - this.encrypt = function(plain) { - return CryptoJS[encryptionType] - .encrypt(Uint8ArrayFormatter.toWordArray(plain), passphrase) - .toString(); + this.encrypt = function(buffer) { + var wordArray = toWordArray(buffer); +// return CryptoJS[encryptionType] +// .encrypt(wordArray, passphrase) +// .toString(CryptoJS.enc.Uint8Array); + + var e = CryptoJS[encryptionType].encrypt(wordArray, passphrase); + var e2 = e.ciphertext.toString(CryptoJS.enc.Uint8Array); + console.log("encrypt", e, e2); + return e2; }; this.decrypt = function(encrypted) { - return CryptoJS[encryptionType] - .decrypt(encrypted, passphrase) - .toString(); +debugger; + var wordArray = toWordArray(encrypted); +// return CryptoJS[encryptionType] +// .decrypt(wordArray, passphrase) +// .toString(CryptoJS.enc.Uint8Array); + +// var cipherParams = CryptoJS.lib.CipherParams.create({ +// ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct) +// }); + + var result = CryptoJS[encryptionType].decrypt({ciphertext:wordArray}, passphrase); + return result.toString(CryptoJS.enc.Uint8Array); }; } CryptoAdapter.isSupported = function() { From 3d11b34893ed265638a79a0c897ffd4c71096fd7 Mon Sep 17 00:00:00 2001 From: "David Humphrey (:humph) david.humphrey@senecacollege.ca" Date: Wed, 4 Dec 2013 12:14:16 -0500 Subject: [PATCH 4/5] Update adapter tests to use Uint8Array and fix crypto encodings, complete zlib work. Fixes #52. --- src/adapters/crypto.js | 69 ++++++++++++-------- src/adapters/zlib.js | 4 +- tests/spec/adapters/adapters.general.spec.js | 15 ++++- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/adapters/crypto.js b/src/adapters/crypto.js index 8271590..a172ddc 100644 --- a/src/adapters/crypto.js +++ b/src/adapters/crypto.js @@ -7,16 +7,19 @@ define(function(require) { // Rabbit, see http://code.google.com/p/crypto-js/#Rabbit require("crypto-js/rollups/rabbit"); - // Move back and forth from Uint8Arrays and CryptoJS' WordArray - // source: https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0 + + // Move back and forth from Uint8Arrays and CryptoJS WordArray + // See http://code.google.com/p/crypto-js/#The_Cipher_Input and + // https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0 var WordArray = CryptoJS.lib.WordArray; function fromWordArray(wordArray) { var words = wordArray.words; var sigBytes = wordArray.sigBytes; var u8 = new Uint8Array(sigBytes); + var b; for (var i = 0; i < sigBytes; i++) { - var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - u8[i]=byte; + b = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + u8[i] = b; } return u8; } @@ -29,10 +32,16 @@ define(function(require) { return WordArray.create(words, len); } - CryptoJS.enc.Uint8Array = { - stringify: fromWordArray, - parse: toWordArray - }; + + // UTF8 Text De/Encoders + require('encoding'); + function encode(str) { + return (new TextEncoder('utf-8')).encode(str); + } + function decode(u8arr) { + return (new TextDecoder('utf-8')).decode(u8arr); + } + function CryptoContext(context, encrypt, decrypt) { this.context = context; @@ -69,30 +78,34 @@ define(function(require) { // prompting the user to enter it when the file system is being opened. function CryptoAdapter(passphrase, provider) { this.provider = provider; + + // Cache cipher algorithm we'll use in encrypt/decrypt + var cipher = CryptoJS[encryptionType]; + + // To encrypt: + // 1) accept a buffer (Uint8Array) containing binary data + // 2) convert the buffer to a CipherJS WordArray + // 3) encrypt the WordArray using the chosen cipher algorithm + passphrase + // 4) convert the resulting ciphertext to a UTF8 encoded Uint8Array and return this.encrypt = function(buffer) { var wordArray = toWordArray(buffer); -// return CryptoJS[encryptionType] -// .encrypt(wordArray, passphrase) -// .toString(CryptoJS.enc.Uint8Array); - - var e = CryptoJS[encryptionType].encrypt(wordArray, passphrase); - var e2 = e.ciphertext.toString(CryptoJS.enc.Uint8Array); - console.log("encrypt", e, e2); - return e2; + var encrypted = cipher.encrypt(wordArray, passphrase); + var utf8EncodedBuf = encode(encrypted); + return utf8EncodedBuf; }; - this.decrypt = function(encrypted) { -debugger; - var wordArray = toWordArray(encrypted); -// return CryptoJS[encryptionType] -// .decrypt(wordArray, passphrase) -// .toString(CryptoJS.enc.Uint8Array); -// var cipherParams = CryptoJS.lib.CipherParams.create({ -// ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct) -// }); - - var result = CryptoJS[encryptionType].decrypt({ciphertext:wordArray}, passphrase); - return result.toString(CryptoJS.enc.Uint8Array); + // To decrypt: + // 1) accept a buffer (Uint8Array) containing a UTF8 encoded Uint8Array + // 2) convert the buffer to string (i.e., the ciphertext we got from encrypting) + // 3) decrypt the ciphertext string + // 4) convert the decrypted cipherParam object to a UTF8 string + // 5) encode the UTF8 string to a Uint8Array buffer and return + this.decrypt = function(buffer) { + var encryptedStr = decode(buffer); + var decrypted = cipher.decrypt(encryptedStr, passphrase); + var decryptedUtf8 = decrypted.toString(CryptoJS.enc.Utf8); + var utf8EncodedBuf = encode(decryptedUtf8); + return utf8EncodedBuf; }; } CryptoAdapter.isSupported = function() { diff --git a/src/adapters/zlib.js b/src/adapters/zlib.js index 29d3c87..e7cfe16 100644 --- a/src/adapters/zlib.js +++ b/src/adapters/zlib.js @@ -1,6 +1,6 @@ define(function(require) { - // ZLib compression, see + // Zlib compression, see // https://github.com/imaya/zlib.js/blob/master/bin/zlib.min.js require("zlib"); @@ -43,7 +43,7 @@ define(function(require) { function ZlibAdapter(provider, inflate, deflate) { - this.provider = provider; + this.provider = provider; } ZlibAdapter.isSupported = function() { return true; diff --git a/tests/spec/adapters/adapters.general.spec.js b/tests/spec/adapters/adapters.general.spec.js index 7e7cf7d..a22272f 100644 --- a/tests/spec/adapters/adapters.general.spec.js +++ b/tests/spec/adapters/adapters.general.spec.js @@ -22,7 +22,10 @@ define(["IDBFS"], function(IDBFS) { describe("IDBFS.FileSystem.adapters." + adapterName, function() { it("is supported -- if it isn't, none of these tests can run.", function() { - expect(IDBFS.FileSystem.adapters[adapterName].isSupported()).toEqual(true); + // Allow for combined adapters (e.g., 'AES+Zlib') joined by '+' + adapterName.split('+').forEach(function(name) { + expect(IDBFS.FileSystem.adapters[name].isSupported()).toEqual(true); + }); }); it("has open, getReadOnlyContext, and getReadWriteContext instance methods", function() { @@ -55,7 +58,7 @@ define(["IDBFS"], function(IDBFS) { }); }); - describe("Read/Write operations on a Memory provider with an " + adapterName + "adapter", function() { + describe("Read/Write operations on a Memory provider with an " + adapterName + " adapter", function() { it("should allow put() and get()", function() { var complete = false; var _error, _result; @@ -213,4 +216,12 @@ define(["IDBFS"], function(IDBFS) { return new IDBFS.FileSystem.adapters.Zlib(provider); }); + // AES + Zlib together + buildTestsFor('AES+Zlib', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + var zlib = new IDBFS.FileSystem.adapters.Zlib(provider); + var AESwithZlib = new IDBFS.FileSystem.adapters.AES(passphrase, zlib); + return AESwithZlib; + }); + }); From 21fe8d3b980723922786a28b61d813f0ec3d4d17 Mon Sep 17 00:00:00 2001 From: "David Humphrey (:humph) david.humphrey@senecacollege.ca" Date: Wed, 4 Dec 2013 16:30:10 -0500 Subject: [PATCH 5/5] Update README with info about Compression adapter --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f78dce1..c11006b 100644 --- a/README.md +++ b/README.md @@ -143,21 +143,26 @@ IDBFS based file systems can acquire new functionality by using adapters. These of storage providers without altering them in anway. An adapter can be used with any provider, and multiple adapters can be used together in order to compose complex functionality on top of a provider. -There are currently 4 adapters available: +There are currently 5 adapters available: +* `FileSystem.adapters.Compression(provider)` - a default compression adapter that uses [Zlib](https://github.com/imaya/zlib.js) +* `FileSystem.adapters.Encryption(passphrase, provider)` - a default encryption adapter that uses [AES encryption](http://code.google.com/p/crypto-js/#AES) + +You can also pick from other encryption cipher algorithms: * `FileSystem.adapters.AES(passphrase, provider)` - extends a provider with [AES encryption](http://code.google.com/p/crypto-js/#AES) * `FileSystem.adapters.TripleDES(passphrase, provider)` - extends a provider with [TripleDES encryption](http://code.google.com/p/crypto-js/#DES,_Triple_DES) * `FileSystem.adapters.Rabbit(passphrase, provider)` - extends a provider with [Rabbit encryption](http://code.google.com/p/crypto-js/#Rabbit) -* `FileSystem.adapters.Encryption(passphrase, provider)` - a default encryption adapter that uses [AES encryption](http://code.google.com/p/crypto-js/#AES) ```javascript var FileSystem = IDBFS.FileSystem; var providers = FileSystem.providers; var adapters = FileSystem.adapters; -// Create a WebSQL-based, Encrypted File System. +// Create a WebSQL-based, Encrypted, Compressed File System by +// composing a provider and adatpers. var webSQLProvider = new providers.WebSQL(); var encryptionAdatper = new adapters.Encryption('super-secret-passphrase', webSQLProvider); -var fs1 = new FileSystem({ provider: encryptionAdapter }); +var compressionAdatper = new adatpers.Compression(encryptionAdapter); +var fs = new FileSystem({ provider: compressionAdapter }); ``` You can also write your own adapter if you need to add new capabilities to the providers. Adapters share the same