Commit 1f4abb58 authored by Abdullatif Shikfa's avatar Abdullatif Shikfa Committed by Tristan Cavelier

Searchable Encrypton Storage prototype added to project

parent 207682c5
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
if(typeof module!="undefined"&&module.exports)module.exports=sjcl;
sjcl.cipher.aes=function(a){this.h[0][0][0]||this.z();var b,c,d,e,f=this.h[0][4],g=this.h[1];b=a.length;var h=1;if(b!==4&&b!==6&&b!==8)throw new sjcl.exception.invalid("invalid aes key size");this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(a%b===0||b===8&&a%b===4){c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255];if(a%b===0){c=c<<8^c>>>24^h<<24;h=h<<1^(h>>7)*283}}d[a]=d[a-b]^c}for(b=0;a;b++,a--){c=d[b&3?a:a-4];e[b]=a<=4||b<4?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^
g[3][f[c&255]]}};
sjcl.cipher.aes.prototype={encrypt:function(a){return this.I(a,0)},decrypt:function(a){return this.I(a,1)},h:[[[],[],[],[],[]],[[],[],[],[],[]]],z:function(){var a=this.h[0],b=this.h[1],c=a[4],d=b[4],e,f,g,h=[],i=[],k,j,l,m;for(e=0;e<0x100;e++)i[(h[e]=e<<1^(e>>7)*283)^e]=e;for(f=g=0;!c[f];f^=k||1,g=i[g]||1){l=g^g<<1^g<<2^g<<3^g<<4;l=l>>8^l&255^99;c[f]=l;d[l]=f;j=h[e=h[k=h[f]]];m=j*0x1010101^e*0x10001^k*0x101^f*0x1010100;j=h[l]*0x101^l*0x1010100;for(e=0;e<4;e++){a[e][f]=j=j<<24^j>>>8;b[e][l]=m=m<<24^m>>>8}}for(e=
0;e<5;e++){a[e]=a[e].slice(0);b[e]=b[e].slice(0)}},I:function(a,b){if(a.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var c=this.a[b],d=a[0]^c[0],e=a[b?3:1]^c[1],f=a[2]^c[2];a=a[b?1:3]^c[3];var g,h,i,k=c.length/4-2,j,l=4,m=[0,0,0,0];g=this.h[b];var n=g[0],o=g[1],p=g[2],q=g[3],r=g[4];for(j=0;j<k;j++){g=n[d>>>24]^o[e>>16&255]^p[f>>8&255]^q[a&255]^c[l];h=n[e>>>24]^o[f>>16&255]^p[a>>8&255]^q[d&255]^c[l+1];i=n[f>>>24]^o[a>>16&255]^p[d>>8&255]^q[e&255]^c[l+2];a=n[a>>>24]^o[d>>16&
255]^p[e>>8&255]^q[f&255]^c[l+3];l+=4;d=g;e=h;f=i}for(j=0;j<4;j++){m[b?3&-j:j]=r[d>>>24]<<24^r[e>>16&255]<<16^r[f>>8&255]<<8^r[a&255]^c[l++];g=d;d=e;e=f;f=a;a=g}return m}};
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===undefined?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(a.length===0||b.length===0)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return d===32?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;
if(b===0)return 0;return(b-1)*32+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(a.length*32<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;if(c>0&&b)a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1);return a},partial:function(a,b,c){if(a===32)return b;return(c?b|0:b<<32-a)+a*0x10000000000},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return false;var c=0,d;for(d=0;d<a.length;d++)c|=
a[d]^b[d];return c===0},P:function(a,b,c,d){var e;e=0;if(d===undefined)d=[];for(;b>=32;b-=32){d.push(c);c=0}if(b===0)return d.concat(a);for(e=0;e<a.length;e++){d.push(c|a[e]>>>b);c=a[e]<<32-b}e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,b+a>32?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++){if((d&3)===0)e=a[d/4];b+=String.fromCharCode(e>>>24);e<<=8}return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++){d=d<<8|a.charCodeAt(c);if((c&3)===3){b.push(d);d=0}}c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,d*4)}};
sjcl.codec.base64={F:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.F,g=0,h=sjcl.bitArray.bitLength(a);if(c)f=f.substr(0,62)+"-_";for(c=0;d.length*6<h;){d+=f.charAt((g^a[c]>>>e)>>>26);if(e<6){g=a[c]<<6-e;e+=26;c++}else{g<<=6;e-=6}}for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d=0,e=sjcl.codec.base64.F,f=0,g;if(b)e=e.substr(0,62)+"-_";for(b=0;b<a.length;b++){g=e.indexOf(a.charAt(b));
if(g<0)throw new sjcl.exception.invalid("this isn't base64!");if(d>26){d-=26;c.push(f^g>>>d);f=g<<32-d}else{d+=6;f^=g<<32-d}}d&56&&c.push(sjcl.bitArray.partial(d&56,f,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.z();if(a){this.n=a.n.slice(0);this.i=a.i.slice(0);this.e=a.e}else this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.n=this.N.slice(0);this.i=[];this.e=0;return this},update:function(a){if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);var b,c=this.i=sjcl.bitArray.concat(this.i,a);b=this.e;a=this.e=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.D(c.splice(0,16));return this},finalize:function(){var a,b=this.i,c=this.n;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.e/
4294967296));for(b.push(this.e|0);b.length;)this.D(b.splice(0,16));this.reset();return c},N:[],a:[],z:function(){function a(e){return(e-Math.floor(e))*0x100000000|0}var b=0,c=2,d;a:for(;b<64;c++){for(d=2;d*d<=c;d++)if(c%d===0)continue a;if(b<8)this.N[b]=a(Math.pow(c,0.5));this.a[b]=a(Math.pow(c,1/3));b++}},D:function(a){var b,c,d=a.slice(0),e=this.n,f=this.a,g=e[0],h=e[1],i=e[2],k=e[3],j=e[4],l=e[5],m=e[6],n=e[7];for(a=0;a<64;a++){if(a<16)b=d[a];else{b=d[a+1&15];c=d[a+14&15];b=d[a&15]=(b>>>7^b>>>18^
b>>>3^b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+d[a&15]+d[a+9&15]|0}b=b+n+(j>>>6^j>>>11^j>>>25^j<<26^j<<21^j<<7)+(m^j&(l^m))+f[a];n=m;m=l;l=j;j=k+b|0;k=i;i=h;h=g;g=b+(h&i^k&(h^i))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0}e[0]=e[0]+g|0;e[1]=e[1]+h|0;e[2]=e[2]+i|0;e[3]=e[3]+k|0;e[4]=e[4]+j|0;e[5]=e[5]+l|0;e[6]=e[6]+m|0;e[7]=e[7]+n|0}};
sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,i=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];if(i<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;f<4&&k>>>8*f;f++);if(f<15-i)f=15-i;c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.H(a,b,c,d,e,f);g=sjcl.mode.ccm.J(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),i=f.clamp(b,h-e),k=f.bitSlice(b,
h-e);h=(h-e)/8;if(g<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;b<4&&h>>>8*b;b++);if(b<15-g)b=15-g;c=f.clamp(c,8*(15-b));i=sjcl.mode.ccm.J(a,i,c,k,e,b);a=sjcl.mode.ccm.H(a,i.data,c,d,e,b);if(!f.equal(i.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return i.data},H:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,i=h.k;e/=8;if(e%2||e<4||e>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(d.length>0xffffffff||b.length>0xffffffff)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;if(c<=65279)g=[h.partial(16,c)];else if(c<=0xffffffff)g=h.concat([h.partial(16,65534)],[c]);g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(i(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(i(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,e*8)},J:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.k;var i=b.length,k=h.bitLength(b);c=h.concat([h.partial(8,
f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!i)return{tag:d,data:[]};for(g=0;g<i;g+=4){c[3]++;e=a.encrypt(c);b[g]^=e[0];b[g+1]^=e[1];b[g+2]^=e[2];b[g+3]^=e[3]}return{tag:d,data:h.clamp(b,k)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.B,i=sjcl.bitArray,k=i.k,j=[0,0,0,0];c=h(a.encrypt(c));var l,m=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4){l=b.slice(g,g+4);j=k(j,l);m=m.concat(k(c,a.encrypt(k(c,l))));c=h(c)}l=b.slice(g);b=i.bitLength(l);g=a.encrypt(k(c,[0,0,0,b]));l=i.clamp(k(l.concat([0,0,0]),g),b);j=k(j,k(l.concat([0,0,0]),g));j=a.encrypt(k(j,k(c,h(c))));
if(d.length)j=k(j,f?d:sjcl.mode.ocb2.pmac(a,d));return m.concat(i.concat(l,i.clamp(j,e)))},decrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.B,h=sjcl.bitArray,i=h.k,k=[0,0,0,0],j=g(a.encrypt(c)),l,m,n=sjcl.bitArray.bitLength(b)-e,o=[];d=d||[];for(c=0;c+4<n/32;c+=4){l=i(j,a.decrypt(i(j,b.slice(c,c+4))));k=i(k,l);o=o.concat(l);j=g(j)}m=n-c*32;l=a.encrypt(i(j,[0,0,0,m]));l=i(l,h.clamp(b.slice(c),
m).concat([0,0,0]));k=i(k,l);k=a.encrypt(i(k,i(j,g(j))));if(d.length)k=i(k,f?d:sjcl.mode.ocb2.pmac(a,d));if(!h.equal(h.clamp(k,e),h.bitSlice(b,n)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return o.concat(h.clamp(l,m))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.B,e=sjcl.bitArray,f=e.k,g=[0,0,0,0],h=a.encrypt([0,0,0,0]);h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4){h=d(h);g=f(g,a.encrypt(f(h,b.slice(c,c+4))))}b=b.slice(c);if(e.bitLength(b)<128){h=f(h,d(h));b=e.concat(b,[2147483648|0,0,
0,0])}g=f(g,b);return a.encrypt(f(d(f(h,d(h))),g))},B:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^(a[0]>>>31)*135]}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d=b.prototype.blockSize/32;this.l=[new b,new b];if(a.length>d)a=b.hash(a);for(b=0;b<d;b++){c[0][b]=a[b]^909522486;c[1][b]=a[b]^1549556828}this.l[0].update(c[0]);this.l[1].update(c[1])};
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a,b){a=(new this.M(this.l[0])).update(a,b).finalize();return(new this.M(this.l[1])).update(a).finalize()};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;if(d<0||c<0)throw sjcl.exception.invalid("invalid params to pbkdf2");if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,i,k=[],j=sjcl.bitArray;for(i=1;32*k.length<(d||1);i++){e=f=a.encrypt(j.concat(b,[i]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}if(d)k=j.clamp(k,d);return k};
sjcl.random={randomWords:function(a,b){var c=[];b=this.isReady(b);var d;if(b===0)throw new sjcl.exception.notReady("generator isn't seeded");else b&2&&this.U(!(b&1));for(b=0;b<a;b+=4){(b+1)%0x10000===0&&this.L();d=this.w();c.push(d[0],d[1],d[2],d[3])}this.L();return c.slice(0,a)},setDefaultParanoia:function(a){this.t=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.q[c],h=this.isReady(),i=0;d=this.G[c];if(d===undefined)d=this.G[c]=this.R++;if(g===undefined)g=this.q[c]=
0;this.q[c]=(this.q[c]+1)%this.b.length;switch(typeof a){case "number":if(b===undefined)b=1;this.b[g].update([d,this.u++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if(c==="[object Uint32Array]"){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else{if(c!=="[object Array]")i=1;for(c=0;c<a.length&&!i;c++)if(typeof a[c]!="number")i=1}if(!i){if(b===undefined)for(c=b=0;c<a.length;c++)for(e=a[c];e>0;){b++;e>>>=1}this.b[g].update([d,this.u++,2,b,f,a.length].concat(a))}break;case "string":if(b===
undefined)b=a.length;this.b[g].update([d,this.u++,3,b,f,a.length]);this.b[g].update(a);break;default:i=1}if(i)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.j[g]+=b;this.f+=b;if(h===0){this.isReady()!==0&&this.K("seeded",Math.max(this.g,this.f));this.K("progress",this.getProgress())}},isReady:function(a){a=this.C[a!==undefined?a:this.t];return this.g&&this.g>=a?this.j[0]>80&&(new Date).valueOf()>this.O?3:1:this.f>=a?2:0},getProgress:function(a){a=
this.C[a?a:this.t];return this.g>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.m){if(window.addEventListener){window.addEventListener("load",this.o,false);window.addEventListener("mousemove",this.p,false)}else if(document.attachEvent){document.attachEvent("onload",this.o);document.attachEvent("onmousemove",this.p)}else throw new sjcl.exception.bug("can't attach event");this.m=true}},stopCollectors:function(){if(this.m){if(window.removeEventListener){window.removeEventListener("load",
this.o,false);window.removeEventListener("mousemove",this.p,false)}else if(window.detachEvent){window.detachEvent("onload",this.o);window.detachEvent("onmousemove",this.p)}this.m=false}},addEventListener:function(a,b){this.r[a][this.Q++]=b},removeEventListener:function(a,b){var c;a=this.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&a[c]===b&&d.push(c);for(b=0;b<d.length;b++){c=d[b];delete a[c]}},b:[new sjcl.hash.sha256],j:[0],A:0,q:{},u:0,G:{},R:0,g:0,f:0,O:0,a:[0,0,0,0,0,0,0,0],d:[0,0,0,0],s:undefined,
t:6,m:false,r:{progress:{},seeded:{}},Q:0,C:[0,48,64,96,128,192,0x100,384,512,768,1024],w:function(){for(var a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}return this.s.encrypt(this.d)},L:function(){this.a=this.w().concat(this.w());this.s=new sjcl.cipher.aes(this.a)},T:function(a){this.a=sjcl.hash.sha256.hash(this.a.concat(a));this.s=new sjcl.cipher.aes(this.a);for(a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}},U:function(a){var b=[],c=0,d;this.O=b[0]=(new Date).valueOf()+3E4;for(d=
0;d<16;d++)b.push(Math.random()*0x100000000|0);for(d=0;d<this.b.length;d++){b=b.concat(this.b[d].finalize());c+=this.j[d];this.j[d]=0;if(!a&&this.A&1<<d)break}if(this.A>=1<<this.b.length){this.b.push(new sjcl.hash.sha256);this.j.push(0)}this.f-=c;if(c>this.g)this.g=c;this.A++;this.T(b)},p:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX||0,a.y||a.clientY||a.offsetY||0],2,"mouse")},o:function(){sjcl.random.addEntropy((new Date).valueOf(),2,"loadtime")},K:function(a,b){var c;a=sjcl.random.r[a];
var d=[];for(c in a)a.hasOwnProperty(c)&&d.push(a[c]);for(c=0;c<d.length;c++)d[c](b)}};try{var s=new Uint32Array(32);crypto.getRandomValues(s);sjcl.random.addEntropy(s,1024,"crypto['getRandomValues']")}catch(t){}
sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.c({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.c(f,c);c=f.adata;if(typeof f.salt==="string")f.salt=sjcl.codec.base64.toBits(f.salt);if(typeof f.iv==="string")f.iv=sjcl.codec.base64.toBits(f.iv);if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||typeof a==="string"&&f.iter<=100||f.ts!==64&&f.ts!==96&&f.ts!==128||f.ks!==128&&f.ks!==192&&f.ks!==0x100||f.iv.length<
2||f.iv.length>4)throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof a==="string"){g=sjcl.misc.cachedPbkdf2(a,f);a=g.key.slice(0,f.ks/32);f.salt=g.salt}if(typeof b==="string")b=sjcl.codec.utf8String.toBits(b);if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);g=new sjcl.cipher[f.cipher](a);e.c(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(f)},decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.c(e.c(e.c({},e.defaults),e.decode(b)),
c,true);var f;c=b.adata;if(typeof b.salt==="string")b.salt=sjcl.codec.base64.toBits(b.salt);if(typeof b.iv==="string")b.iv=sjcl.codec.base64.toBits(b.iv);if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||typeof a==="string"&&b.iter<=100||b.ts!==64&&b.ts!==96&&b.ts!==128||b.ks!==128&&b.ks!==192&&b.ks!==0x100||!b.iv||b.iv.length<2||b.iv.length>4)throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof a==="string"){f=sjcl.misc.cachedPbkdf2(a,b);a=f.key.slice(0,b.ks/32);b.salt=f.salt}if(typeof c===
"string")c=sjcl.codec.utf8String.toBits(c);f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.c(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+
sjcl.codec.base64.fromBits(a[b],1)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");b[d[2]]=
d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4])}return b},c:function(a,b,c){if(a===undefined)a={};if(b===undefined)return a;var d;for(d in b)if(b.hasOwnProperty(d)){if(c&&a[d]!==undefined&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},W:function(a,b){var c={},d;for(d in a)if(a.hasOwnProperty(d)&&a[d]!==b[d])c[d]=a[d];return c},V:function(a,b){var c={},d;for(d=0;d<b.length;d++)if(a[b[d]]!==undefined)c[b[d]]=
a[b[d]];return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.S={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.S,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===undefined?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
/***********************************************************************
** Written by Abdullatif Shikfa, Alcatel Lucent Bell-Labs France **
** With the invaluable help of Tristan Cavelier, Nexedi **
** 31/01/2014 **
***********************************************************************/
/*jslint indent:2,maxlen:80,nomen:true*/
/*global jIO, define, exports, require, sjcl*/
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(
require('jio'),
require('sjcl')
);
}
module(jIO, sjcl);
}([
'jio',
'sjcl'
], function (jIO, sjcl) {
"use strict";
/**
* SearchableEncryptionStorage is a function that creates
* the searchable encryption storage
*
*keywords: a list of keywords associated with the current document, and which
* can be searched with the searchable encryption algorithm.
*nbMaxKeywords: the maximum number of keywords that any document can contain.
*encryptedIndex: an index that represents the keywords in an encrypted form.
* It is a Bloom Filter, which allows queries, without false
* negatives but with a false positive probability.
*errorRate: the false positive rate is bound to 2 to the power -errorRate.
* Increasing the value of errorRate decreases the rate of false
* positives but increases the size of the encryptedIndex.
*password: a user chosen password used to encrypt metadata as well as to
* generate the encryptedIndex.
*/
function SearchableEncryptionStorage(storage_description) {
this._password = storage_description.password; //string
this._errorRate = storage_description.errorRate || 20; //int
this._nbMaxKeywords = storage_description.nbMaxKeywords || 100; //int
this._url = storage_description.url;
this._keywords = storage_description.keywords; //string array
if (typeof this._password !== 'string') {
throw new TypeError("'password' description property is not a string");
}
}
/**
* computeBFLength is a function that computes the length of a Bloom Filter
* depending on the false positive ratio and the maximum number of elements
* (keywords) that it will represent.
* A Bloom Filter is indeed a data structure which is a bit array that
* represents a set of elements.
* The false positive rate is bound to 2 to the power -errorRate, and the
* length is then computed according to the following formula.
*/
function computeBFLength(errorRate, nbMaxKeywords) {
return Math.ceil((errorRate * nbMaxKeywords) / Math.log(2));
}
/**
* intArrayToString is a helper function that converts an array of integers to
* one big string. It basically concatenates all the integers of the array.
*/
/*function intArrayToString(arr) {
var i, result = "";
for (i = 0; i < arr.length; i += 1) {
result = result + arr[i].toString();
}
return result;
}*/
/**
* bigModulo is a helper function that computes the remainder of a large
* integer divided by an operand. The large integer is represented as several
* regular integers (of 32 bits) in big endian in an array.
* The function leverages the modulo operation on integers implemented in
* javascript to perform the modulo on the large integer : it computes the
* modulo of each integer of the array multiplied by the modulo of the
* base (2 to the power 32) to the power of the position in the array.
* However, since javascript encodes integers on 32 bits we have to add another
* trick: we do the computations on half words and we use the function
* sjcl.bitArray.bitSlice which extracts some bits out of a bit array, and we
* and we thus mutliply by half of the base.
*/
function bigModulo(arr, mod) {
var i, result = 0, base = 1, maxIter = (2 * arr.length);
for (i = 0; i < maxIter; i += 1) {
result = result + (
(sjcl.bitArray.bitSlice(arr, i * 16, (i + 1) * 16)[0]) % mod
) * base;
base = (base * Math.pow(2, 16)) % mod;
}
result = result % mod;
return result;
}
/**
* constructBloomFilter is a function that constructs an encrypted Bloom Filter
* representing a set of elements (keywords) with a given password, a given
* false positive ratio and the maximum number of elements that any Bloom
* Filter can contain in our scenario (this is useful so that all documents
* have the same size of bloom filters).
* This function follows the algorithm proposed by Goh in 2004 in his article
* about "secure indexes" and that allows to perform searchable encryption.
* The function first computes the length of the Bloom Filter depending on the
* errorRate and nbMaxKeywords using an auxiliary function computeBFLength
* previously explained.
* It then creates an array of the said length initialized with 0 at all
* positions.
* The array is then filled with ones at certain positions using the following:
* algorithm:
* For each keyword in the array keywords compute errorRate hashes:
* Each hash is the SHA256 function applied to the keyword concatenated
* with the password and the iterator of the hash function (j). The
* resulting digest is an array that is converted to a base64 string and
* concatenated with the id of the documents (to obtain different results
* if a given keyword is found in several documents). The result is then
* taken modulo the length of the Bloom Filter and indicates a position
* in the array which is set to one.
* In the end there are at most bFLength * errorRate 1s in the array (and in
* fact less because several keywords can lead to the same position for
* different hash functions).
*/
function constructBloomFilter(
password,
errorRate,
nbMaxKeywords,
keywords,
id
) {
var bFLength = computeBFLength(errorRate, nbMaxKeywords), result = [], i, j;
for (i = 0; i < bFLength; i += 1) {
result[i] = 0;
}
for (i = 0; i < keywords.length; i += 1) {
for (j = 0; j < errorRate; j += 1) {
result[bigModulo(sjcl.hash.sha256.hash(sjcl.codec.base64.fromBits(
sjcl.hash.sha256.hash(keywords[i] + password + j)
) + id), bFLength)] = 1;
}
}
return result;
}
/**
* constructEncryptedQuery is a function that constructs an encrypted query
* from a keyword and a password. It basically performs the first step of
* adding a word to a Bloom Filter.
* It hashes the keyword errorRate times using different hash functions.
* Each hash is the SHA256 function applied to the keyword concatenated
* with the password and the iterator of the hash function (j). The
* resulting digest is an array that is converted to a base64 string using the
* sjcl.codec.base64.fromBits function.
* In the end, the encrypted query corresponding to a keyword is an array of
* errorRates base64 strings. Note that the query can only be computed by the
* client as it requires knowledge of the secret key.
*/
function constructEncryptedQuery(
password,
errorRate,
keyword
) {
var result = [], j;
for (j = 0; j < errorRate; j += 1) {
result[j] = sjcl.codec.base64.fromBits(sjcl.hash.sha256.hash(
keyword + password + j
));
}
return result;
}
/* // Encrypt a message
function encrypt(plaintext, password) {
var rp = {}, ct, p;
p = {
adata: "",
iter: 1,
mode: "ccm",
ts: 64,
ks: 128,
iv: "t6vxTD/94Lk7DM87LZkPQA==",
cipher: "aes",
salt: "SdieDA4jA08="
};
ct = sjcl.encrypt(password, plaintext, p, rp);//.replace(/,/g,",\n");
return JSON.parse(ct).ct;
}
// Decrypt a message
function decrypt(ciphertext, password) {
var p, plaintext, rp = {};
p = {
adata: "",
iter: 1,
mode: "ccm",
ts: 64,
ks: 128,
iv: "t6vxTD/94Lk7DM87LZkPQA==",
cipher: "aes",
salt: "SdieDA4jA08=",
ct: ciphertext
};
plaintext = sjcl.decrypt(password, JSON.stringify(p), {}, rp);
return plaintext;
}*/
//Copied from the davstorage connector
/**
* Creates a new document if not already exists
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to put
* @param {Object} options The command options
*/
/* SearchableEncryptionStorage.prototype.post = function (
command,
metadata
) {
// this.postOrPut('post', command, metadata);
metadata.encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
this._keywords,
metadata._id
);
}; */
/**
* Creates or updates a document
*
* @method put
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.put = function (
command,
metadata
) {
// First create the associated encryptedIndex to allow encrypted queries at a
// later stage
// Then we encrypt the data using sjcl library. This step is independant of
// the searchable encryption features, however it is also related to
// confidentiality hence we added it here as an example of how to use the sjcl
// library.
var encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
metadata.keywords,
metadata._id
), data = sjcl.encrypt(this._password, JSON.stringify(metadata));
// The remainder is a classical put using the ajax method
jIO.util.ajax({
"type": "PUT",
"url": this._url + "/" + metadata._id,
"dataType": "json",
"data": {"metadata": data, "encryptedIndex": encryptedIndex}
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Creates a document if it does not already exist.
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.post = function (
command,
metadata
) {
// First create the associated encryptedIndex to allow encrypted queries at a
// later stage
// Then we encrypt the data using sjcl library. This step is independant of
// the searchable encryption features, however it is also related to
// confidentiality hence we added it here as an example of how to use the sjcl
// library.
var encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
metadata.keywords,
metadata._id
), data = sjcl.encrypt(this._password, JSON.stringify(metadata));
// The remainder is a classical put using the ajax method
jIO.util.ajax({
"type": "POST",
"url": this._url + "/" + metadata._id,
"dataType": "json",
"data": {"metadata": data, "encryptedIndex": encryptedIndex}
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Adds attachments to a document
*
* @method putAttachment
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to putAttachment
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.putAttachment = function (
command,
param
) {
// This function adds an attachment to a document, it has nothing specific to
// searchable encryption. Optionally the attachment could be encrypted as well
// using the same primitive shown in previous methods.
jIO.util.ajax({
"type": "PUT",
"url": this._url + "/" + param._id + "/" + param._attachment,
"dataType": "blob",
"data": param._blob
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Retrieve metadata
*
* @method get
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.get = function (
command,
param
) {
// This function retrieves a document given its ID. It is not specific to
// searchable encryption. Here we also have to decrypt the metadata as we
// encrypted them in the put or post methods.
var that = this;
jIO.util.ajax({
"type": "GET",
"url": this._url + "/" + param._id
}).then(function (e) {
var data = JSON.parse(sjcl.decrypt(
that._password,
e.target.responseText
));
command.success(e.target.status, {"data": data});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document retrieval from server failed"
);
});
};
SearchableEncryptionStorage.prototype.getAttachment = function (
command,
param
) {
// This function retrieves an attachment of a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "GET",
"url": this._url + "/" + param._id + "/" + param._attachment
}).then(function (e) {
command.success(e.target.status, {"data": e.target.response});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document retrieval from server failed"
);
});
};
SearchableEncryptionStorage.prototype.remove = function (
command,
param
) {
// This function removes a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "DELETE",
"url": this._url + "/" + param._id
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document removal from server failed"
);
});
};
SearchableEncryptionStorage.prototype.removeAttachment = function (
command,
param
) {
// This function removes an attachment of a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "DELETE",
"url": this._url + "/" + param._id + "/" + param._attachment
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document removal from server failed"
);
});
};
/**
* AllDocs deals with encrypted queries. This is a core function of the
* searchable encryption connector.
* In this version, AllDOcs enables to retrieve all documents containing a
* keyword. However the server should not learn the keyword, and it should not
* learn anything with respect to the documents either if they are encrypted.
* The trick here is that the function encrypts the query (composed of a single
* keyword) by performing the first steps of the construction of the Bloom
* Filters: it hashes the keyword concatenated with the password and an
* iterator (which takes values between 0 and errorRate) and thus obtains an
* array of errorRate rows, each row is converted to a base64 string.
* Using this encrypted query the servers tests all documents it has stored with
* their respective encrypted indexes, and it returns the list of documents
* that match the query (without understanding the query though!). AllDocs
* simply has to decrypt all documents at this stage (since we encrypted them in
* the put and post steps): the user gets the documents he searched for with a
* high level of confidentiality against the server.
*/
SearchableEncryptionStorage.prototype.allDocs = function (
command,
param,
option
) {
/*jslint unparam: true */
var query, that = this;
query = constructEncryptedQuery(
this._password,
this._errorRate,
option.query
);
jIO.util.ajax({
"type": "POST",
"url": this._url,
"dataType": "json",
"data": {"query": query}
}).then(function (e) {
var document_list = e.target.response;
document_list = document_list.map(function (param) {
param = JSON.parse(sjcl.decrypt(that._password, param));
var row = {
"id": param._id,
"value": {}
};
if (option.include_docs === true) {
row.doc = param;
}
return row;
});
command.success(e.target.status, {"data": {
"total_rows": document_list.length,
"rows": document_list
}});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Documents retrieval from server failed"
);
});
};
jIO.addStorage("searchableencryption", SearchableEncryptionStorage);
}));
// Methods remaining to be defined: only check and repair
/***********************************************************************
** Written by Abdullatif Shikfa, Alcatel Lucent Bell-Labs France **
** With the invaluable help of Tristan Cavelier, Nexedi **
** 31/01/2014 **
***********************************************************************/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global module, test, stop, start, ok, deepEqual, RSVP, jIO, test_util, sjcl */
(function () {
"use strict";
var spec, use_fake_server = true;
spec = {
"type": "searchableencryption",
"url": "http://fakeserver",
"password": "coincoin"
};
function reverse(promise) {
return new RSVP.Promise(function (resolve, reject, notify) {
promise.then(reject, resolve, notify);
}, function () {
promise.cancel();
});
}
jIO.util.ajax = (function () {
// TEST SERVER
var baseURLRe = /^http:\/\/fakeserver(\/[^\/]+)?(\/[^\/]+)?$/;
var dataBase = {};
function bigModulo(arr, mod) {
var i, result = 0, base = 1, maxIter = (2 * arr.length);
for (i = 0; i < maxIter; i += 1) {
result = result + (
(sjcl.bitArray.bitSlice(arr, i * 16, (i + 1) * 16)[0]) % mod
) * base;
base = (base * Math.pow(2, 16)) % mod;
}
result = result % mod;
return result;
}
function test(id, encryptedQuery) {
var j, result = true;
for (j = 0; j < encryptedQuery.length; j += 1) {
result = result && (dataBase[id].encryptedIndex[bigModulo(
sjcl.hash.sha256.hash(encryptedQuery[j] + id),
dataBase[id].encryptedIndex.length
)] === 1);
}
return result;
}
function onPut(id, data) {
dataBase[id] = dataBase[id] || {};
dataBase[id].metadata = data.metadata;
dataBase[id].encryptedIndex = data.encryptedIndex;
}
function onPost(id, data) {
if (dataBase[id]) {
throw new Error("Document already exists");
}
dataBase[id] = {};
dataBase[id].metadata = data.metadata;
dataBase[id].encryptedIndex = data.encryptedIndex;
}
//Caution: this method can throw an error if the document id does not
//already exist
//The attachment ID must not be metadata or encryptedIndex !
function onPutAttachment(id, idAttachment, data) {
dataBase[id][idAttachment] = data;
}
function onGet(id) {
return dataBase[id].metadata;
}
function onGetAttachment(id, idAttachment) {
return dataBase[id][idAttachment];
}
function onRemove(id) {
delete dataBase[id];
}
function onRemoveAttachment(id, idAttachment) {
delete dataBase[id][idAttachment];
}
function onAllDocs(encryptedQuery) {
/*jslint forin: true */
var id, result = [];
for (id in dataBase) {
if (test(id, encryptedQuery)) {
result.push(dataBase[id].metadata);
}
}
return result;
}
function FakeEvent(target) {
this.target = target;
}
function ServerAjax(param) {
return new RSVP.Promise(function (resolve, reject) {
// param.type || "GET"
// param.url
// param.dataType (ignored)
// param.headers (ignored)
// param.beforeSend (ignored)
// param.data
var re_result = baseURLRe.exec(param.url), xhr = {};
if (!re_result) {
xhr.status = 1; // wrong url
xhr.statusText = "Unknown";
return reject(new FakeEvent(xhr));
}
if (re_result[1]) {
re_result[1] = re_result[1].slice(1);
}
if (re_result[2]) {
re_result[2] = re_result[2].slice(1);
}
xhr.status = 404;
xhr.statusText = "Not Found";
if (!param.type || param.type === "GET") {
try {
if (re_result[2]) {
// jio.getAttachment
xhr.response = new Blob([
onGetAttachment(re_result[1], re_result[2])
]);
} else {
// jio.get
xhr.response = onGet(re_result[1]);
xhr.responseText = xhr.response;
}
} catch (e) {
return reject(new FakeEvent(xhr));
}
xhr.status = 200;
xhr.statusText = "OK";
return resolve(new FakeEvent(xhr));
}
if (param.type === "DELETE") {
try {
if (re_result[2]) {
// jio.removeAttachment
onRemoveAttachment(re_result[1], re_result[2]);
} else {
// jio.remove
onRemove(re_result[1]);
}
} catch (e2) {
return reject(new FakeEvent(xhr));
}
xhr.status = 204;
xhr.statusText = "No Content";
return resolve(new FakeEvent(xhr));
}
xhr.status = 409;
xhr.statusText = "Conflict";
if (param.type === "POST") {
try {
if (re_result[1]) {
// jio.post
onPost(re_result[1], param.data);
} else {
// jio.allDocs
xhr.response = onAllDocs(param.data.query);
}
} catch (e1) {
return reject(new FakeEvent(xhr));
}
xhr.status = 200;
xhr.statusText = "OK";
return resolve(new FakeEvent(xhr));
}
if (param.type === "PUT") {
try {
if (re_result[2]) {
// jio.putAttachment
onPutAttachment(re_result[1], re_result[2], param.data);
} else {
// jio.put
onPut(re_result[1], param.data);
}
} catch (e3) {
return reject(new FakeEvent(xhr));
}
xhr.status = 204;
xhr.statusText = "No Content";
return resolve(new FakeEvent(xhr));
}
});
}
return ServerAjax;
}());
module("Searchable Encryption Storage");
/**
* Tested with local webDav server
*
* Used header are:
* - Header always set Cache-Control "no-cache" # "private", "public",
* "<seconds>" or "no-store" ("no-cache")
* - Header always set Access-Control-Max-Age "0"
* - Header always set Access-Control-Allow-Credentials "true"
* - Header always set Access-Control-Allow-Origin "*"
* - Header always set Access-Control-Allow-Methods "OPTIONS, GET, HEAD,
* POST, PUT, DELETE, PROPFIND"
* - Header always set Access-Control-Allow-Headers "Content-Type,
* X-Requested-With, X-HTTP-Method-Override, Accept, Authorization,
* Depth"
*/
test("Scenario", function () {
var server, responses = [], shared = {}, jio = jIO.createJIO(spec, {
"workspace": {},
"max_retry": 2
});
stop();
/* function postNewDocument() {
return jio.post({"title": "Unique ID"});
}
function postNewDocumentTest(answer) {
var uuid = answer.id;
answer.id = "<uuid>";
deepEqual(answer, {
"id": "<uuid>",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post a new document");
ok(test_util.isUuid(uuid), "New document id should look like " +
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + uuid);
shared.created_document_id = uuid;
}
*/
function getCreatedDocument() {
return jio.get({"_id": "a"});
}
function getCreatedDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_id": "a",
"title": "Hey",
"keywords": ["1", "2", "3"]
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get new document");
}
function postSpecificDocument() {
responses.push([404, {}, '']); // GET
responses.push([201, {}, '']); // PUT
return jio.post({"_id": "b", "title": "Bee"});
}
function postSpecificDocumentTest(answer) {
deepEqual(answer, {
"id": "b",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post specific document");
}
function listDocuments() {
return jio.allDocs({"query": "1"});
}
function listDocumentsTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 2,
"rows": [{
"id": "a",
"value": {}
}, {
"id": "b",
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 2 documents");
}
function listDocumentsTest2(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 1,
"rows": [{
"id": "b",
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 1 document");
}
function listOneDocument() {
return jio.allDocs({"query": "2"});
}
function listOneDocumentTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 1,
"rows": [{
"id": "a",
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 1 document");
}
function listOneDocumentTest2(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 0,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 0 document");
}
function removeCreatedDocument() {
return jio.remove({"_id": "a"});
}
function removeCreatedDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove first document.");
}
function removeSpecificDocument() {
return jio.remove({"_id": "b"});
}
function removeSpecificDocumentTest(answer) {
deepEqual(answer, {
"id": "b",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove second document.");
}
function listEmptyStorage() {
return jio.allDocs();
}
function listEmptyStorageTest(answer) {
deepEqual(answer, {
"data": {
"total_rows": 0,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List empty storage");
}
function putNewDocument() {
return jio.put({
"_id": "a",
"title": "Hey",
"keywords": ["1", "2", "3"]
});
}
function putNewDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Put new document");
}
function postNewDocument() {
return jio.post({
"_id": "b",
"title": "Hello",
"keywords": ["1", "4", "5"]
});
}
function postNewDocumentTest(answer) {
deepEqual(answer, {
"id": "b",
"method": "post",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Post new document");
}
function postCreatedDocument() {
return reverse(jio.post({
"_id": "b",
"title": "Hello",
"keywords": ["1", "4", "5"]
}));
}
function postCreatedDocumentTest(answer) {
deepEqual(answer, {
"error": "conflict",
"id": "b",
"message": "Document update from server failed",
"method": "post",
"reason": "Conflict",
"result": "error",
"status": 409,
"statusText": "Conflict"
}, "Post new document failed -> conflict 409");
}
function putNewDocument2() {
return jio.put({
"_id": "b",
"title": "Hello",
"keywords": ["1", "4", "5"]
});
}
function putNewDocument2Test(answer) {
deepEqual(answer, {
"id": "b",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Put new document");
}
function getCreatedDocument2() {
return jio.get({"_id": "a"});
}
function getCreatedDocument2Test(answer) {
deepEqual(answer, {
"data": {
"_id": "a",
"keywords": [
"1",
"2",
"3"
],
"title": "Hey"
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get new document");
}
function postSameDocument() {
return success(jio.post({"_id": "a", "title": "Hoo"}));
}
function postSameDocumentTest(answer) {
deepEqual(answer, {
"error": "conflict",
"id": "a",
"message": "DavStorage, cannot overwrite document metadata.",
"method": "post",
"reason": "Document exists",
"result": "error",
"status": 409,
"statusText": "Conflict"
}, "Unable to post the same document (conflict)");
}
function createAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "aa",
"_data": "aaa",
"_content_type": "text/plain"
});
}
function createAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Create new attachment");
}
function updateAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "aa",
"_data": "aab",
"_content_type": "text/plain"
});
}
function updateAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Update last attachment");
}
function createAnotherAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "ab",
"_data": "aba",
"_content_type": "text/plain"
});
}
function createAnotherAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Create another attachment");
}
function updateLastDocument() {
return jio.put({"_id": "a", "title": "Hoo", "keywords": ["1","2","3"]});
}
function updateLastDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Update document metadata");
}
function getFirstAttachment() {
return jio.getAttachment({"_id": "a", "_attachment": "aa"});
}
function getFirstAttachmentTest(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aab", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getSecondAttachment() {
return jio.getAttachment({"_id": "a", "_attachment": "ab"});
}
function getSecondAttachmentTest(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "ab",
"data": "<blob>",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aba", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getLastDocument() {
return jio.get({"_id": "a"});
}
function getLastDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_id": "a",
"title": "Hoo",
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
},
"ab": {
"content_type": "text/plain",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"length": 3
}
}
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get last document metadata");
}
function removeSecondAttachment() {
return jio.removeAttachment({"_id": "a", "_attachment": "ab"});
}
function removeSecondAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"id": "a",
"method": "removeAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove second document");
}
function getInexistentSecondAttachment() {
return reverse(jio.getAttachment({"_id": "a", "_attachment": "ab"}));
}
function getInexistentSecondAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "DavStorage, unable to get attachment.",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent second attachment");
}
function getOneAttachmentDocument() {
return jio.get({"_id": "a"});
}
function getOneAttachmentDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
}
},
"_id": "a",
"title": "Hoo"
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get document metadata");
}
function removeSecondAttachmentAgain() {
return success(jio.removeAttachment({"_id": "a", "_attachment": "ab"}));
}
function removeSecondAttachmentAgainTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "DavStorage, document attachment not found.",
"method": "removeAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove inexistent attachment");
}
function removeDocument() {
return jio.remove({"_id": "a"});
}
function removeDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove document and its attachments");
}
function getInexistentFirstAttachment() {
return success(jio.getAttachment({"_id": "a", "_attachment": "aa"}));
}
function getInexistentFirstAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"error": "not_found",
"id": "a",
"message": "DavStorage, unable to get attachment.",
"method": "getAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent first attachment");
}
function getInexistentDocument() {
return success(jio.get({"_id": "a"}));
}
function getInexistentDocumentTest(answer) {
deepEqual(answer, {
"error": "not_found",
"id": "a",
"message": "DavStorage, unable to get document.",
"method": "get",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent document");
}
function removeInexistentDocument() {
return success(jio.remove({"_id": "a"}));
}
function removeInexistentDocumentTest(answer) {
deepEqual(answer, {
"error": "not_found",
"id": "a",
"message": "DavStorage, unable to get metadata.",
"method": "remove",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove already removed document");
}
function unexpectedError(error) {
if (error instanceof Error) {
deepEqual([
error.name + ": " + error.message,
error
], "UNEXPECTED ERROR", "Unexpected error");
} else {
deepEqual(error, "UNEXPECTED ERROR", "Unexpected error");
}
}
// # Post new documents, list them and remove them
// put a 204
putNewDocument().then(putNewDocumentTest).
// post b 204
then(postNewDocument).then(postNewDocumentTest).
// post a 409
then(postCreatedDocument).then(postCreatedDocumentTest).
// get a 200
then(getCreatedDocument).then(getCreatedDocumentTest).
// put b 204
then(putNewDocument2).then(putNewDocument2Test).
// allD 200 2 documents
then(listDocuments).then(listDocumentsTest).
// allD 200 1 document
then(listOneDocument).then(listOneDocumentTest).
// remove a 204
then(removeCreatedDocument).then(removeCreatedDocumentTest).
// allD 200 1 document
then(listDocuments).then(listDocumentsTest2).
// allD 200 0 document
then(listOneDocument).then(listOneDocumentTest2).
// put a 204
then(putNewDocument).then(putNewDocumentTest).
// putA a 204
then(createAttachment).then(createAttachmentTest).
// putA a 204
then(updateAttachment).then(updateAttachmentTest).
// putA b 204
then(createAnotherAttachment).then(createAnotherAttachmentTest).
// get 200
then(getCreatedDocument2).then(getCreatedDocument2Test).
// put 204
then(updateLastDocument).then(updateLastDocumentTest).
/* // post b 201
then(postSpecificDocument).then(postSpecificDocumentTest).
// post 409
then(postSameDocument).then(postSameDocumentTest).
// getA a 200
// then(getFirstAttachment).then(getFirstAttachmentTest).
// getA b 200
// then(getSecondAttachment).then(getSecondAttachmentTest).
// get 200
then(getLastDocument).then(getLastDocumentTest).
// removeA b 204
// then(removeSecondAttachment).then(removeSecondAttachmentTest).
// getA b 404
// then(getInexistentSecondAttachment).
// then(getInexistentSecondAttachmentTest).
// get 200
// then(getOneAttachmentDocument).then(getOneAttachmentDocumentTest).
// removeA b 404
// then(removeSecondAttachmentAgain).then(removeSecondAttachmentAgainTest).
// remove 204
// then(removeDocument).then(removeDocumentTest).
// getA a 404
// then(getInexistentFirstAttachment).then(getInexistentFirstAttachmentTest).
// get 404
// then(getInexistentDocument).then(getInexistentDocumentTest).
// remove 404
// then(removeInexistentDocument).then(removeInexistentDocumentTest).
// check 204
//then(checkDocument).done(checkDocumentTest).
//then(checkStorage).done(checkStorageTest).*/
fail(unexpectedError).
always(start);
// always(server.restore.bind(server));
});
}());
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment