diff --git a/lib/jsSha1/sha1.js b/lib/jsSha1/sha1.js index 032d78af78603900b05843ca5d87dbf7af75fc85..34a7b9f6b0a86287fa8bacbf223f3b5770581258 100644 --- a/lib/jsSha1/sha1.js +++ b/lib/jsSha1/sha1.js @@ -1,203 +1,203 @@ -/* - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Version 2.1a Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - */ - -/* - * Configurable variables. You may need to tweak these to be compatible with - * the server-side, but the defaults work in most cases. - */ - -var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ -var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ -var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ - -/* - * These are the functions you'll usually want to call - * They take string arguments and return either hex or base-64 encoded strings - */ -function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} -function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} -function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} -function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} -function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} -function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} - -/* - * Perform a simple self-test to see if the VM is working - */ -function sha1_vm_test() -{ - return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; -} - -/* - * Calculate the SHA-1 of an array of big-endian words, and a bit length - */ -function core_sha1(x, len) -{ - /* append padding */ - x[len >> 5] |= 0x80 << (24 - len % 32); - x[((len + 64 >> 9) << 4) + 15] = len; - - var w = Array(80); - var a = 1732584193; - var b = -271733879; - var c = -1732584194; - var d = 271733878; - var e = -1009589776; - - for(var i = 0; i < x.length; i += 16) - { - var olda = a; - var oldb = b; - var oldc = c; - var oldd = d; - var olde = e; - - for(var j = 0; j < 80; j++) - { - if(j < 16) w[j] = x[i + j]; - else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); - var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), - safe_add(safe_add(e, w[j]), sha1_kt(j))); - e = d; - d = c; - c = rol(b, 30); - b = a; - a = t; - } - - a = safe_add(a, olda); - b = safe_add(b, oldb); - c = safe_add(c, oldc); - d = safe_add(d, oldd); - e = safe_add(e, olde); - } - return Array(a, b, c, d, e); - -} - -/* - * Perform the appropriate triplet combination function for the current - * iteration - */ -function sha1_ft(t, b, c, d) -{ - if(t < 20) return (b & c) | ((~b) & d); - if(t < 40) return b ^ c ^ d; - if(t < 60) return (b & c) | (b & d) | (c & d); - return b ^ c ^ d; -} - -/* - * Determine the appropriate additive constant for the current iteration - */ -function sha1_kt(t) -{ - return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : - (t < 60) ? -1894007588 : -899497514; -} - -/* - * Calculate the HMAC-SHA1 of a key and some data - */ -function core_hmac_sha1(key, data) -{ - var bkey = str2binb(key); - if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); - - var ipad = Array(16), opad = Array(16); - for(var i = 0; i < 16; i++) - { - ipad[i] = bkey[i] ^ 0x36363636; - opad[i] = bkey[i] ^ 0x5C5C5C5C; - } - - var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); - return core_sha1(opad.concat(hash), 512 + 160); -} - -/* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ -function safe_add(x, y) -{ - var lsw = (x & 0xFFFF) + (y & 0xFFFF); - var msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); -} - -/* - * Bitwise rotate a 32-bit number to the left. - */ -function rol(num, cnt) -{ - return (num << cnt) | (num >>> (32 - cnt)); -} - -/* - * Convert an 8-bit or 16-bit string to an array of big-endian words - * In 8-bit function, characters >255 have their hi-byte silently ignored. - */ -function str2binb(str) -{ - var bin = Array(); - var mask = (1 << chrsz) - 1; - for(var i = 0; i < str.length * chrsz; i += chrsz) - bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); - return bin; -} - -/* - * Convert an array of big-endian words to a string - */ -function binb2str(bin) -{ - var str = ""; - var mask = (1 << chrsz) - 1; - for(var i = 0; i < bin.length * 32; i += chrsz) - str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); - return str; -} - -/* - * Convert an array of big-endian words to a hex string. - */ -function binb2hex(binarray) -{ - var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; - var str = ""; - for(var i = 0; i < binarray.length * 4; i++) - { - str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + - hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); - } - return str; -} - -/* - * Convert an array of big-endian words to a base-64 string - */ -function binb2b64(binarray) -{ - var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - var str = ""; - for(var i = 0; i < binarray.length * 4; i += 3) - { - var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) - | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) - | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); - for(var j = 0; j < 4; j++) - { - if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; - else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); - } - } - return str; +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ + +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} +function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} +function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for(var j = 0; j < 80; j++) + { + if(j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); + +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if(t < 20) return (b & c) | ((~b) & d); + if(t < 40) return b ^ c ^ d; + if(t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); + return str; +} + +/* + * Convert an array of big-endian words to a hex string. + */ +function binb2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; } \ No newline at end of file diff --git a/src/jio.storage/s3storage.js b/src/jio.storage/s3storage.js index b1c2e5288a69570e12491b8b087a010033843e07..3fbe0374570d8178d1e7b358dc13db674e66d749 100644 --- a/src/jio.storage/s3storage.js +++ b/src/jio.storage/s3storage.js @@ -1,1036 +1,1036 @@ -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global jIO: true, btoa: true, b64_hmac_sha1: true */ -/*global XMLHttpRequest: true, XHRwrapper: true, FormData: true, $: true*/ -/** - * JIO S3 Storage. Type = "s3". - * Amazon S3 "database" storage. - */ -jIO.addStorageType("s3", function (spec, my) { - var evt, that, priv = {}; - spec = spec || {}; - that = my.basicStorage(spec, my); - - // attributes - priv.username = spec.username || ''; - priv.AWSIdentifier = spec.AWSIdentifier || ''; - priv.password = spec.password || ''; - priv.server = spec.server || ''; /*|| jiobucket ||*/ - priv.url = spec.url || ''; /*||> https://s3-eu-west-1.amazonaws.com <||*/ - - priv.acl = spec.acl || ''; - - /*||> "private, - public-read, - public-read-write, - authenticated-read, - bucket-owner-read, - bucket-owner-full-control" <||*/ - - priv.actionStatus = spec.actionStatus || ''; - - priv.contenTType = spec.contenTType || ''; - - /** - * Update [doc] the document object and remove [doc] keys - * which are not in [new_doc]. It only changes [doc] keys not starting - * with an underscore. - * ex: doc: {key:value1,_key:value2} with - * new_doc: {key:value3,_key:value4} updates - * doc: {key:value3,_key:value2}. - * @param {object} doc The original document object. - * @param {object} new_doc The new document object - **/ - - priv.secureDocId = function (string) { - var split = string.split('/'), i; - if (split[0] === '') { - split = split.slice(1); - } - for (i = 0; i < split.length; i += 1) { - if (split[i] === '') { - return ''; - } - } - return split.join('%2F'); - }; - - /** - * Replace substrings to another strings - * @method recursiveReplace - * @param {string} string The string to do replacement - * @param {array} list_of_replacement An array of couple - * ["substring to select", "selected substring replaced by this string"]. - * @return {string} The replaced string - */ - priv.recursiveReplace = function (string, list_of_replacement) { - var i, split_string = string.split(list_of_replacement[0][0]); - if (list_of_replacement[1]) { - for (i = 0; i < split_string.length; i += 1) { - split_string[i] = priv.recursiveReplace( - split_string[i], - list_of_replacement.slice(1) - ); - } - } - return split_string.join(list_of_replacement[0][1]); - }; - - /** - * Changes / to %2F, % to %25 and . to _. - * @method secureName - * @param {string} name The name to secure - * @return {string} The secured name - */ - priv.secureName = function (name) { - return priv.recursiveReplace(name, [["/", "%2F"], ["%", "%25"]]); - }; - - /** - * Restores the original name from a secured name - * @method restoreName - * @param {string} secured_name The secured name to restore - * @return {string} The original name - */ - priv.restoreName = function (secured_name) { - return priv.recursiveReplace(secured_name, [["%2F", "/"], ["%25", "%"]]); - }; - - /** - * Convert document id and attachment id to a file name - * @method idsToFileName - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id (optional) - * @return {string} The file name - */ - priv.idsToFileName = function (doc_id, attachment_id) { - doc_id = priv.secureName(doc_id).split(".").join("_."); - if (typeof attachment_id === "string") { - attachment_id = priv.secureName(attachment_id).split(".").join("_."); - return doc_id + "." + attachment_id; - } - return doc_id; - }; - - /** - * Convert a file name to a document id (and attachment id if there) - * @method fileNameToIds - * @param {string} file_name The file name to convert - * @return {array} ["document id", "attachment id"] or ["document id"] - */ - priv.fileNameToIds = function (file_name) { - var separator_index = -1, split = file_name.split("."); - split.slice(0, -1).forEach(function (file_name_part, index) { - if (file_name_part.slice(-1) !== "_") { - separator_index = index; - } - }); - if (separator_index === -1) { - return [priv.restoreName(priv.restoreName( - file_name - ).split("_.").join("."))]; - } - return [ - priv.restoreName(priv.restoreName( - split.slice(0, separator_index + 1).join(".") - ).split("_.").join(".")), - priv.restoreName(priv.restoreName( - split.slice(separator_index + 1).join(".") - ).split("_.").join(".")) - ]; - }; - - /** - * Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c" - * @method removeSlashIfLast - * @param {string} string The string to modify - * @return {string} The modified string - */ - priv.removeSlashIfLast = function (string) { - if (string[string.length - 1] === "/") { - return string.slice(0, -1); - } - return string; - }; - - - - that.documentObjectUpdate = function (doc, new_doc) { - var k; - for (k in doc) { - if (doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - delete doc[k]; - } - } - } - for (k in new_doc) { - if (new_doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - doc[k] = new_doc[k]; - } - } - } - }; - - /** - * Checks if an object has no enumerable keys - * @method objectIsEmpty - * @param {object} obj The object - * @return {boolean} true if empty, else false - */ - - that.objectIsEmpty = function (obj) { - var k; - for (k in obj) { - if (obj.hasOwnProperty(k)) { - return false; - } - } - return true; - }; - - // ===================== overrides ====================== - that.specToStore = function () { - return { - "username": priv.username, - "password": priv.password, - "url": priv.url, - "server": priv.server, - "acl": priv.acl - }; - }; - - that.validateState = function () { - // xxx complete error message - // jjj completion below - - if (typeof priv.AWSIdentifier === "string" && priv.AWSIdentifier === '') { - return 'Need at least one parameter "Aws login".'; - } - if (typeof priv.password === "string" && priv.password === '') { - return 'Need at least one parameter "password".'; - } - if (typeof priv.url === "string" && priv.url === '') { - return 'Need at least one parameter "url".'; - } - if (typeof priv.server === "string" && priv.server === '') { - return 'Need at least one parameter "server".'; - } - return ''; - }; - - // =================== S3 Specifics ================= - /** - * Encoding the signature using a stringToSign - * Encoding the policy - * @method buildStringToSign - * @param {string} http_verb The HTTP method - * @param {string} content_md5 The md5 content - * @param {string} content_type The content type - * @param {number} expires The expires time - * @param {string} x_amz_headers The specific amazon headers - * @param {string} path_key The path of the document - * @return {string} The generated signature - */ - - // xxx no need to make it public, use private -> "priv" (not "that") - priv.buildStringToSign = function (http_verb, content_md5, content_type, - expires, x_amz_headers, path_key) { - //example : - // var StringToSign = S3.buildStringToSign(S3.httpVerb,'','','', - // 'x-amz-date:'+S3.requestUTC,'/jio1st/prive.json'); - - var StringToSign = http_verb + '\n' - + content_md5 + '\n'//content-md5 - + content_type + '\n'//content-type - + expires + '\n'//expires - + x_amz_headers + '\n'//x-amz headers - + path_key;//path key - - return StringToSign; - }; - - - - - that.encodePolicy = function (form) { - //generates the policy - //enables the choice for the http response code - var http_code, s3_policy, Signature = ''; - s3_policy = { - "expiration": "2020-01-01T00:00:00Z", - "conditions": [ - {"bucket": priv.server }, - ["starts-with", "$key", ""], - {"acl": priv.acl }, - {"success_action_redirect": ""}, - {"success_action_status": http_code }, - ["starts-with", "$Content-Type", ""], - ["content-length-range", 0, 524288000] - ] - }; - - //base64 encoding of the policy (native base64 js >> - // .btoa() = encode, .atob() = decode) - priv.b64_policy = btoa(JSON.stringify(s3_policy)); - //generates the signature value using the policy and the secret access key - //use of sha1.js to generate the signature - Signature = that.signature(priv.b64_policy); - - }; - - that.signature = function (string) { - var Signature = b64_hmac_sha1(priv.password, string); - return Signature; - }; - - function xhr_onreadystatechange(docId, - command, - obj, - http, - jio, - isAttachment, - callback) { - obj.onreadystatechange = function () { - var response, err = ''; - if (obj.readyState === 4) { - if (this.status === 204 || this.status === 201 || this.status === 200) { - switch (http) { - case "POST": - that.success({ - ok: true, - id: docId - }); - break; - case 'PUT': - if (jio === true) { - that.success({ - ok: true, - id: command.getDocId() - }); - } else { - callback(this.responseText); - } - break; - case 'GET': - if (jio === true) { - if (typeof this.responseText !== 'string') { - response = JSON.parse(this.responseText); - response._attachments = response._attachments || {}; - delete response._attachments; - that.success(JSON.stringify(response)); - } else { - if (isAttachment === true) { - that.success(this.responseText); - } else { - that.success(JSON.parse(this.responseText)); - } - } - } else { - callback(this.responseText); - } - break; - case 'DELETE': - if (jio === true) { - if (isAttachment === false) { - that.success({ - ok: true, - id: command.getDocId() - }); - } else { - that.success({ - ok: true, - id: command.getDocId(), - attachment: command.getAttachmentId() - }); - } - } else { - callback(this.responseText); - } - break; - } - } else { - err = this; - if (this.status === 405) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_allowed"; - that.error(err); - } - if (this.status === 404) { - if (http === 'GET') { - if (jio === true) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.statustext = "not_foud"; - err.reason = "file does not exist"; - err.error = "not_found"; - that.error(err); - } else { - - callback('404'); - } - } else { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_found"; - that.error(err); - } - } - if (this.status === 409) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "already_exists"; - that.error(err); - } - } - } - }; - } - - priv.updateMeta = function (doc, docid, attachid, action, data) { - doc._attachments = doc._attachments || {}; - switch (action) { - case "add": - doc._attachments[attachid] = data; - //nothing happens - doc = JSON.stringify(doc); - break; - case "remove": - if (doc._attachments !== undefined) { - delete doc._attachments[attachid]; - } - doc = JSON.stringify(doc); - break; - case "update": - console.log(doc._attachments); - doc._attachments[attachid] = data; - console.log(doc._attachments); - //update happened in the put request - doc = JSON.stringify(doc); - break; - } - return doc; - }; - - priv.createError = function (status, message, reason) { - var error = { - "status": status, - "message": message, - "reason": reason - }; - switch (status) { - case 404: - error.statusText = "Not found"; - break; - case 405: - error.statusText = "Method Not Allowed"; - break; - case 409: - error.statusText = "Conflicts"; - break; - case 24: - error.statusText = "Corrupted Document"; - break; - } - error.error = error.statusText.toLowerCase().split(" ").join("_"); - return error; - }; - - that.encodeAuthorization = function (key, mime) { - //GET oriented method - var requestUTC, httpVerb, StringToSign, Signature; - requestUTC = new Date().toUTCString(); - httpVerb = "GET"; - StringToSign = priv.buildStringToSign( - httpVerb, - '', - 'application/json', - '', - 'x-amz-date:' + requestUTC, - '/' + priv.server + '/' + key - ); - Signature = b64_hmac_sha1(priv.password, StringToSign); - return Signature; - }; - - that.XHRwrapper = function (command, - docId, - attachId, - http, - mime, - data, - jio, - is_attachment, - callback) { - - var docFile, requestUTC, StringToSign, url, Signature, xhr; - docFile = priv.secureName(priv.idsToFileName(docId, - attachId || undefined)); - - requestUTC = new Date().toUTCString(); - - StringToSign = priv.buildStringToSign( - http, - '', - mime, - '', - 'x-amz-date:' + requestUTC, - '/' + priv.server + '/' + docFile - ); - - url = 'http://s3.amazonaws.com/' + priv.server + '/' + docFile; - - Signature = b64_hmac_sha1(priv.password, StringToSign); - xhr = new XMLHttpRequest(); - - xhr.open(http, url, true); - xhr.setRequestHeader("HTTP-status-code", "100"); - xhr.setRequestHeader("x-amz-date", requestUTC); - xhr.setRequestHeader("Authorization", "AWS " - + priv.AWSIdentifier - + ":" - + Signature); - xhr.setRequestHeader("Content-Type", mime); - xhr.responseType = 'text'; - - xhr_onreadystatechange(docId, - command, - xhr, - http, - jio, - is_attachment, - callback); - - if (http === 'PUT') { - xhr.send(data); - } else { - xhr.send(null); - } - }; - - // ==================== commands ==================== - /** - * Create a document in local storage. - * @method post - * @param {object} command The JIO command - **/ - - that.post = function (command) { - //as S3 encoding key are directly inserted within the FormData(), - //use of XHRwrapper function ain't pertinent - - var doc, doc_id, mime; - doc = command.cloneDoc(); - doc_id = command.getDocId(); - - function postDocument() { - var http_response, fd, Signature, xhr; - doc_id = priv.secureName(priv.idsToFileName(doc_id)); - //Meant to deep-serialize in order to avoid - //conflicts due to the multipart enctype - doc = JSON.stringify(doc); - http_response = ''; - fd = new FormData(); - //virtually builds the form fields - //filename - fd.append('key', doc_id); - //file access authorizations - priv.acl = ""; - fd.append('acl', priv.acl); - //content-type - priv.contenTType = "text/plain"; - fd.append('Content-Type', priv.contenTType); - //allows specification of a success url redirection - fd.append('success_action_redirect', ''); - //allows to specify the http code response if the request is successful - fd.append('success_action_status', http_response); - //login AWS - fd.append('AWSAccessKeyId', priv.AWSIdentifier); - //exchange policy with the amazon s3 service - //can be common to all uploads or specific - that.encodePolicy(fd); - //priv.b64_policy = that.encodePolicy(fd); - fd.append('policy', priv.b64_policy); - //signature through the base64.hmac.sha1(secret key, policy) method - Signature = b64_hmac_sha1(priv.password, priv.b64_policy); - fd.append('signature', Signature); - //uploaded content !!may must be a string rather than an object - fd.append('file', doc); - xhr = new XMLHttpRequest(); - xhr_onreadystatechange(doc_id, command, xhr, 'POST', true, false, ''); - xhr.open('POST', 'https://' + priv.server + '.s3.amazonaws.com/', true); - xhr.send(fd); - } - - if (doc_id === '' || doc_id === undefined) { - doc_id = 'no_document_id_' - + ((Math.random() * 10).toString().split('.'))[1]; - doc._id = doc_id; - } - - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, doc_id, '', 'GET', mime, '', false, false, - function (response) { - if (response === '404') { - postDocument(); - } else { - //si ce n'est pas une 404, - //alors on renvoit une erreur 405 - return that.error(priv.createError( - 409, - "Cannot create document", - "Document already exists" - )); - } - } - ); - }; - - /** - * Get a document or attachment - * @method get - * @param {object} command The JIO command - **/ - - that.get = function (command) { - var docId, attachId, isJIO, mime; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - isJIO = true; - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, false); - }; - - that.getAttachment = function (command) { - var docId, attachId, isJIO, mime; - docId = command.getDocId(); - attachId = command.getAttachmentId(); - isJIO = true; - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, true); - }; - - /** - * Create or update a document in local storage. - * @method put - * @param {object} command The JIO command - **/ - - that.put = function (command) { - var doc, docId, mime; - doc = command.cloneDoc(); - docId = command.getDocId(); - mime = 'text/plain; charset=UTF-8'; - //pas d'attachment dans un put simple - function putDocument() { - var attachId, data, isJIO; - attachId = ''; - data = JSON.stringify(doc); - isJIO = true; - that.XHRwrapper(command, - docId, - attachId, - 'PUT', - mime, - data, - isJIO, - false); - } - - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (response) { - //if (response === '404') {} - if (response._attachments !== undefined) { - doc._attachments = response._attachments; - } - putDocument(); - } - ); - }; - - that.putAttachment = function (command) { - var mon_document, - docId, - attachId, - mime, - attachment_id, - attachment_data, - attachment_md5, - attachment_mimetype, - attachment_length; - - mon_document = null; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - mime = 'text/plain; charset=UTF-8'; - //r茅cup茅ration des variables de l'attachement - - attachment_id = command.getAttachmentId(); - attachment_data = command.getAttachmentData(); - attachment_md5 = command.md5SumAttachmentData(); - attachment_mimetype = command.getAttachmentMimeType(); - attachment_length = command.getAttachmentLength(); - - function putAttachment() { - that.XHRwrapper(command, - docId, - attachId, - 'PUT', - mime, - attachment_data, - false, - true, - function (reponse) { - that.success({ - // response - "ok": true, - "id": docId, - "attachment": attachId - //"rev": current_revision - }); - } - ); - } - - function putDocument() { - var attachment_obj, data, doc; - attachment_obj = { - //"revpos": 3, // optional - "digest": attachment_md5, - "content_type": attachment_mimetype, - "length": attachment_length - }; - data = JSON.parse(mon_document); - - doc = priv.updateMeta(data, docId, attachId, "add", attachment_obj); - - that.XHRwrapper(command, docId, '', 'PUT', mime, doc, false, false, - function (reponse) { - putAttachment(); - } - ); - } - - function getDocument() { - //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (reponse) { - if (reponse === '404') { - return that.error(priv.createError( - 404, - "Cannot find document", - "Document does not exist" - )); - } - mon_document = reponse; - putDocument(); - } - ); - } - getDocument(); - }; - - /** - * Remove a document or attachment - * @method remove - * @param {object} command The JIO command - */ - - that.remove = function (command) { - var docId, mime; - docId = command.getDocId(); - mime = 'text/plain; charset=UTF-8'; - - function deleteDocument() { - that.XHRwrapper(command, docId, '', 'DELETE', mime, '', true, false, - function (reponse) { - that.success({ - // response - "ok": true, - "id": docId - //"rev": current_revision - }); - } - ); - } - - function myCallback(response) { - } - - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (response) { - var attachKeys, keys; - attachKeys = (JSON.parse(response))._attachments; - for (keys in attachKeys) { - if (attachKeys.hasOwnProperty(keys)) { - that.XHRwrapper(command, - docId, - keys, - 'DELETE', - mime, - '', - false, - false, - myCallback - ); - } - } - deleteDocument(); - } - ); - }; - - that.removeAttachment = function (command) { - var mon_document, - docId, - attachId, - mime, - attachment_id, - attachment_data, - attachment_md5, - attachment_mimetype, - attachment_length; - - mon_document = null; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - mime = 'text/plain; charset=UTF-8'; - //r茅cup茅ration des variables de l'attachement - - attachment_id = command.getAttachmentId(); - attachment_data = command.getAttachmentData(); - attachment_md5 = command.md5SumAttachmentData(); - attachment_mimetype = command.getAttachmentMimeType(); - attachment_length = command.getAttachmentLength(); - - function removeAttachment() { - that.XHRwrapper(command, docId, attachId, 'DELETE', mime, '', true, true, - function (reponse) { - } - ); - } - - function putDocument() { - var data, doc; - data = JSON.parse(mon_document); - doc = priv.updateMeta(data, docId, attachId, "remove", ''); - that.XHRwrapper(command, docId, '', 'PUT', mime, doc, - false, false, function (reponse) { - removeAttachment(); - } - ); - } - - function getDocument() { - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (reponse) { - mon_document = reponse; - putDocument(); - } - ); - } - getDocument(); - }; - - /** - * Get all filenames belonging to a user from the document index - * @method allDocs - * @param {object} command The JIO command - **/ - - that.allDocs = function (command) { - var mon_document, mime; - mon_document = null; - mime = 'text/plain; charset=UTF-8'; - - function makeJSON() { - var keys, - resultTable, - counter, - allDocResponse, - count, - countB, - dealCallback, - errCallback, - i, - keyId, - Signature, - callURL, - requestUTC, - parse, - checkCounter; - keys = $(mon_document).find('Key'); - resultTable = []; - counter = 0; - keys.each(function (index) { - var that, filename, docId; - that = $(this); - filename = that.context.textContent; - docId = priv.idsToFileName(priv.fileNameToIds(filename)[0]); - if (counter === 0) { - counter += 1; - resultTable.push(docId); - } else if (docId !== resultTable[counter - 1]) { - counter += 1; - resultTable.push(docId); - } - }); - - allDocResponse = { - // document content will be added to response - "total_rows": resultTable.length, - "offset": 0, - "rows": [] - }; - - //needed to save the index within the $.ajax.success() callback - count = resultTable.length - 1; - countB = 0; - dealCallback = function (i, countB, allDoc) { - return function (doc, statustext, response) { - allDoc.rows[i].doc = response.responseText; - if (count === 0) { - that.success(allDoc); - } else { - count -= 1; - } - }; - }; - - errCallback = function (err) { - if (err.status === 404) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_found"; - that.error(err); - } else { - return that.retry(err); - } - }; - - i = resultTable.length - 1; - if (command.getOption("include_docs") === true) { - for (i; i >= 0; i -= 1) { - keyId = resultTable[i]; - Signature = that.encodeAuthorization(keyId); - callURL = priv.url + keyId; - requestUTC = new Date().toUTCString(); - parse = true; - allDocResponse.rows[i] = { - "id": priv.fileNameToIds(keyId).join(), - "key": keyId, - "value": {} - }; - checkCounter = i; - - $.ajax({ - contentType : '', - crossdomain : true, - url : callURL, - type : 'GET', - headers : { - 'Authorization' : "AWS" - + " " - + priv.AWSIdentifier - + ":" - + Signature, - //'Host' : priv.url, - 'x-amz-date' : requestUTC, - 'Content-Type' : 'application/json' - //'Content-MD5' : '' - //'Content-Length' : , - //'Expect' : , - //'x-amz-security-token' : , - }, - success : dealCallback(i, countB, allDocResponse), - error : errCallback(this) - }); - countB += 1; - } - } else { - for (i; i >= 0; i -= 1) { - keyId = resultTable[i]; - allDocResponse.rows[i] = { - "id": priv.fileNameToIds(keyId).join(), - "key": keyId, - "value": {} - }; - } - that.success(allDocResponse); - } - } - - function getXML() { - //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); - that.XHRwrapper(command, '', '', 'GET', mime, '', false, false, - function (reponse) { - mon_document = reponse; - makeJSON(); - } - ); - } - - getXML(); - //fin alldocs - }; - return that; -}); - - /* - // It is not possible to attach listeners to xhr level 2 events - // AND validate the Qunit tests through sinon.js - // therefore, below methods are deprecated - - var S3specifics = {}; - - S3specifics.uploadProgress = function(evt){ - if (evt.lengthComputable) { - var percentComplete = Math.round(evt.loaded * 100 / evt.total); - console.log(percentComplete.toString() + '%'); - } else { - console.log('Unable to compute.'); - } - }; - - S3specifics.uploadComplete = function(evt){ - var evt_txt = evt.target.responseText; - var parser = new DOMParser(); - var xmlDoc = parser.parseFromString(evt_txt, "text/xml"); - var responseURL = $(xmlDoc.getElementsByTagName('Location'))[0].text(); - console.log(responseURL); - }; - - S3specifics.uploadFailed = function(evt){ - var evt_txt = evt.target.responseText; - console.log("Erreur lors de la tentative d'upload : " + evt_txt); - }; - - S3specifics.uploadCanceled = function(evt){ - console.log("Upload annul茅 par l'utilisateur ou le navigateur."); - }; - - S3specifics.onReadyStateChange = function(req, those, that) { - if (req.readyState === 4 && those.status === 200){ - that.success({ - ok: true, - id: command.getDocId() - }); - } - }; - */ +/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ +/*global jIO: true, btoa: true, b64_hmac_sha1: true */ +/*global XMLHttpRequest: true, XHRwrapper: true, FormData: true, $: true*/ +/** + * JIO S3 Storage. Type = "s3". + * Amazon S3 "database" storage. + */ +jIO.addStorageType("s3", function (spec, my) { + var evt, that, priv = {}; + spec = spec || {}; + that = my.basicStorage(spec, my); + + // attributes + priv.username = spec.username || ''; + priv.AWSIdentifier = spec.AWSIdentifier || ''; + priv.password = spec.password || ''; + priv.server = spec.server || ''; /*|| jiobucket ||*/ + priv.url = spec.url || ''; /*||> https://s3-eu-west-1.amazonaws.com <||*/ + + priv.acl = spec.acl || ''; + + /*||> "private, + public-read, + public-read-write, + authenticated-read, + bucket-owner-read, + bucket-owner-full-control" <||*/ + + priv.actionStatus = spec.actionStatus || ''; + + priv.contenTType = spec.contenTType || ''; + + /** + * Update [doc] the document object and remove [doc] keys + * which are not in [new_doc]. It only changes [doc] keys not starting + * with an underscore. + * ex: doc: {key:value1,_key:value2} with + * new_doc: {key:value3,_key:value4} updates + * doc: {key:value3,_key:value2}. + * @param {object} doc The original document object. + * @param {object} new_doc The new document object + **/ + + priv.secureDocId = function (string) { + var split = string.split('/'), i; + if (split[0] === '') { + split = split.slice(1); + } + for (i = 0; i < split.length; i += 1) { + if (split[i] === '') { + return ''; + } + } + return split.join('%2F'); + }; + + /** + * Replace substrings to another strings + * @method recursiveReplace + * @param {string} string The string to do replacement + * @param {array} list_of_replacement An array of couple + * ["substring to select", "selected substring replaced by this string"]. + * @return {string} The replaced string + */ + priv.recursiveReplace = function (string, list_of_replacement) { + var i, split_string = string.split(list_of_replacement[0][0]); + if (list_of_replacement[1]) { + for (i = 0; i < split_string.length; i += 1) { + split_string[i] = priv.recursiveReplace( + split_string[i], + list_of_replacement.slice(1) + ); + } + } + return split_string.join(list_of_replacement[0][1]); + }; + + /** + * Changes / to %2F, % to %25 and . to _. + * @method secureName + * @param {string} name The name to secure + * @return {string} The secured name + */ + priv.secureName = function (name) { + return priv.recursiveReplace(name, [["/", "%2F"], ["%", "%25"]]); + }; + + /** + * Restores the original name from a secured name + * @method restoreName + * @param {string} secured_name The secured name to restore + * @return {string} The original name + */ + priv.restoreName = function (secured_name) { + return priv.recursiveReplace(secured_name, [["%2F", "/"], ["%25", "%"]]); + }; + + /** + * Convert document id and attachment id to a file name + * @method idsToFileName + * @param {string} doc_id The document id + * @param {string} attachment_id The attachment id (optional) + * @return {string} The file name + */ + priv.idsToFileName = function (doc_id, attachment_id) { + doc_id = priv.secureName(doc_id).split(".").join("_."); + if (typeof attachment_id === "string") { + attachment_id = priv.secureName(attachment_id).split(".").join("_."); + return doc_id + "." + attachment_id; + } + return doc_id; + }; + + /** + * Convert a file name to a document id (and attachment id if there) + * @method fileNameToIds + * @param {string} file_name The file name to convert + * @return {array} ["document id", "attachment id"] or ["document id"] + */ + priv.fileNameToIds = function (file_name) { + var separator_index = -1, split = file_name.split("."); + split.slice(0, -1).forEach(function (file_name_part, index) { + if (file_name_part.slice(-1) !== "_") { + separator_index = index; + } + }); + if (separator_index === -1) { + return [priv.restoreName(priv.restoreName( + file_name + ).split("_.").join("."))]; + } + return [ + priv.restoreName(priv.restoreName( + split.slice(0, separator_index + 1).join(".") + ).split("_.").join(".")), + priv.restoreName(priv.restoreName( + split.slice(separator_index + 1).join(".") + ).split("_.").join(".")) + ]; + }; + + /** + * Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c" + * @method removeSlashIfLast + * @param {string} string The string to modify + * @return {string} The modified string + */ + priv.removeSlashIfLast = function (string) { + if (string[string.length - 1] === "/") { + return string.slice(0, -1); + } + return string; + }; + + + + that.documentObjectUpdate = function (doc, new_doc) { + var k; + for (k in doc) { + if (doc.hasOwnProperty(k)) { + if (k[0] !== '_') { + delete doc[k]; + } + } + } + for (k in new_doc) { + if (new_doc.hasOwnProperty(k)) { + if (k[0] !== '_') { + doc[k] = new_doc[k]; + } + } + } + }; + + /** + * Checks if an object has no enumerable keys + * @method objectIsEmpty + * @param {object} obj The object + * @return {boolean} true if empty, else false + */ + + that.objectIsEmpty = function (obj) { + var k; + for (k in obj) { + if (obj.hasOwnProperty(k)) { + return false; + } + } + return true; + }; + + // ===================== overrides ====================== + that.specToStore = function () { + return { + "username": priv.username, + "password": priv.password, + "url": priv.url, + "server": priv.server, + "acl": priv.acl + }; + }; + + that.validateState = function () { + // xxx complete error message + // jjj completion below + + if (typeof priv.AWSIdentifier === "string" && priv.AWSIdentifier === '') { + return 'Need at least one parameter "Aws login".'; + } + if (typeof priv.password === "string" && priv.password === '') { + return 'Need at least one parameter "password".'; + } + if (typeof priv.url === "string" && priv.url === '') { + return 'Need at least one parameter "url".'; + } + if (typeof priv.server === "string" && priv.server === '') { + return 'Need at least one parameter "server".'; + } + return ''; + }; + + // =================== S3 Specifics ================= + /** + * Encoding the signature using a stringToSign + * Encoding the policy + * @method buildStringToSign + * @param {string} http_verb The HTTP method + * @param {string} content_md5 The md5 content + * @param {string} content_type The content type + * @param {number} expires The expires time + * @param {string} x_amz_headers The specific amazon headers + * @param {string} path_key The path of the document + * @return {string} The generated signature + */ + + // xxx no need to make it public, use private -> "priv" (not "that") + priv.buildStringToSign = function (http_verb, content_md5, content_type, + expires, x_amz_headers, path_key) { + //example : + // var StringToSign = S3.buildStringToSign(S3.httpVerb,'','','', + // 'x-amz-date:'+S3.requestUTC,'/jio1st/prive.json'); + + var StringToSign = http_verb + '\n' + + content_md5 + '\n'//content-md5 + + content_type + '\n'//content-type + + expires + '\n'//expires + + x_amz_headers + '\n'//x-amz headers + + path_key;//path key + + return StringToSign; + }; + + + + + that.encodePolicy = function (form) { + //generates the policy + //enables the choice for the http response code + var http_code, s3_policy, Signature = ''; + s3_policy = { + "expiration": "2020-01-01T00:00:00Z", + "conditions": [ + {"bucket": priv.server }, + ["starts-with", "$key", ""], + {"acl": priv.acl }, + {"success_action_redirect": ""}, + {"success_action_status": http_code }, + ["starts-with", "$Content-Type", ""], + ["content-length-range", 0, 524288000] + ] + }; + + //base64 encoding of the policy (native base64 js >> + // .btoa() = encode, .atob() = decode) + priv.b64_policy = btoa(JSON.stringify(s3_policy)); + //generates the signature value using the policy and the secret access key + //use of sha1.js to generate the signature + Signature = that.signature(priv.b64_policy); + + }; + + that.signature = function (string) { + var Signature = b64_hmac_sha1(priv.password, string); + return Signature; + }; + + function xhr_onreadystatechange(docId, + command, + obj, + http, + jio, + isAttachment, + callback) { + obj.onreadystatechange = function () { + var response, err = ''; + if (obj.readyState === 4) { + if (this.status === 204 || this.status === 201 || this.status === 200) { + switch (http) { + case "POST": + that.success({ + ok: true, + id: docId + }); + break; + case 'PUT': + if (jio === true) { + that.success({ + ok: true, + id: command.getDocId() + }); + } else { + callback(this.responseText); + } + break; + case 'GET': + if (jio === true) { + if (typeof this.responseText !== 'string') { + response = JSON.parse(this.responseText); + response._attachments = response._attachments || {}; + delete response._attachments; + that.success(JSON.stringify(response)); + } else { + if (isAttachment === true) { + that.success(this.responseText); + } else { + that.success(JSON.parse(this.responseText)); + } + } + } else { + callback(this.responseText); + } + break; + case 'DELETE': + if (jio === true) { + if (isAttachment === false) { + that.success({ + ok: true, + id: command.getDocId() + }); + } else { + that.success({ + ok: true, + id: command.getDocId(), + attachment: command.getAttachmentId() + }); + } + } else { + callback(this.responseText); + } + break; + } + } else { + err = this; + if (this.status === 405) { + //status + //statustext "Not Found" + //error + //reason "reason" + //message "did not work" + err.error = "not_allowed"; + that.error(err); + } + if (this.status === 404) { + if (http === 'GET') { + if (jio === true) { + //status + //statustext "Not Found" + //error + //reason "reason" + //message "did not work" + err.statustext = "not_foud"; + err.reason = "file does not exist"; + err.error = "not_found"; + that.error(err); + } else { + + callback('404'); + } + } else { + //status + //statustext "Not Found" + //error + //reason "reason" + //message "did not work" + err.error = "not_found"; + that.error(err); + } + } + if (this.status === 409) { + //status + //statustext "Not Found" + //error + //reason "reason" + //message "did not work" + err.error = "already_exists"; + that.error(err); + } + } + } + }; + } + + priv.updateMeta = function (doc, docid, attachid, action, data) { + doc._attachments = doc._attachments || {}; + switch (action) { + case "add": + doc._attachments[attachid] = data; + //nothing happens + doc = JSON.stringify(doc); + break; + case "remove": + if (doc._attachments !== undefined) { + delete doc._attachments[attachid]; + } + doc = JSON.stringify(doc); + break; + case "update": + console.log(doc._attachments); + doc._attachments[attachid] = data; + console.log(doc._attachments); + //update happened in the put request + doc = JSON.stringify(doc); + break; + } + return doc; + }; + + priv.createError = function (status, message, reason) { + var error = { + "status": status, + "message": message, + "reason": reason + }; + switch (status) { + case 404: + error.statusText = "Not found"; + break; + case 405: + error.statusText = "Method Not Allowed"; + break; + case 409: + error.statusText = "Conflicts"; + break; + case 24: + error.statusText = "Corrupted Document"; + break; + } + error.error = error.statusText.toLowerCase().split(" ").join("_"); + return error; + }; + + that.encodeAuthorization = function (key, mime) { + //GET oriented method + var requestUTC, httpVerb, StringToSign, Signature; + requestUTC = new Date().toUTCString(); + httpVerb = "GET"; + StringToSign = priv.buildStringToSign( + httpVerb, + '', + 'application/json', + '', + 'x-amz-date:' + requestUTC, + '/' + priv.server + '/' + key + ); + Signature = b64_hmac_sha1(priv.password, StringToSign); + return Signature; + }; + + that.XHRwrapper = function (command, + docId, + attachId, + http, + mime, + data, + jio, + is_attachment, + callback) { + + var docFile, requestUTC, StringToSign, url, Signature, xhr; + docFile = priv.secureName(priv.idsToFileName(docId, + attachId || undefined)); + + requestUTC = new Date().toUTCString(); + + StringToSign = priv.buildStringToSign( + http, + '', + mime, + '', + 'x-amz-date:' + requestUTC, + '/' + priv.server + '/' + docFile + ); + + url = 'http://s3.amazonaws.com/' + priv.server + '/' + docFile; + + Signature = b64_hmac_sha1(priv.password, StringToSign); + xhr = new XMLHttpRequest(); + + xhr.open(http, url, true); + xhr.setRequestHeader("HTTP-status-code", "100"); + xhr.setRequestHeader("x-amz-date", requestUTC); + xhr.setRequestHeader("Authorization", "AWS " + + priv.AWSIdentifier + + ":" + + Signature); + xhr.setRequestHeader("Content-Type", mime); + xhr.responseType = 'text'; + + xhr_onreadystatechange(docId, + command, + xhr, + http, + jio, + is_attachment, + callback); + + if (http === 'PUT') { + xhr.send(data); + } else { + xhr.send(null); + } + }; + + // ==================== commands ==================== + /** + * Create a document in local storage. + * @method post + * @param {object} command The JIO command + **/ + + that.post = function (command) { + //as S3 encoding key are directly inserted within the FormData(), + //use of XHRwrapper function ain't pertinent + + var doc, doc_id, mime; + doc = command.cloneDoc(); + doc_id = command.getDocId(); + + function postDocument() { + var http_response, fd, Signature, xhr; + doc_id = priv.secureName(priv.idsToFileName(doc_id)); + //Meant to deep-serialize in order to avoid + //conflicts due to the multipart enctype + doc = JSON.stringify(doc); + http_response = ''; + fd = new FormData(); + //virtually builds the form fields + //filename + fd.append('key', doc_id); + //file access authorizations + priv.acl = ""; + fd.append('acl', priv.acl); + //content-type + priv.contenTType = "text/plain"; + fd.append('Content-Type', priv.contenTType); + //allows specification of a success url redirection + fd.append('success_action_redirect', ''); + //allows to specify the http code response if the request is successful + fd.append('success_action_status', http_response); + //login AWS + fd.append('AWSAccessKeyId', priv.AWSIdentifier); + //exchange policy with the amazon s3 service + //can be common to all uploads or specific + that.encodePolicy(fd); + //priv.b64_policy = that.encodePolicy(fd); + fd.append('policy', priv.b64_policy); + //signature through the base64.hmac.sha1(secret key, policy) method + Signature = b64_hmac_sha1(priv.password, priv.b64_policy); + fd.append('signature', Signature); + //uploaded content !!may must be a string rather than an object + fd.append('file', doc); + xhr = new XMLHttpRequest(); + xhr_onreadystatechange(doc_id, command, xhr, 'POST', true, false, ''); + xhr.open('POST', 'https://' + priv.server + '.s3.amazonaws.com/', true); + xhr.send(fd); + } + + if (doc_id === '' || doc_id === undefined) { + doc_id = 'no_document_id_' + + ((Math.random() * 10).toString().split('.'))[1]; + doc._id = doc_id; + } + + mime = 'text/plain; charset=UTF-8'; + that.XHRwrapper(command, doc_id, '', 'GET', mime, '', false, false, + function (response) { + if (response === '404') { + postDocument(); + } else { + //si ce n'est pas une 404, + //alors on renvoit une erreur 405 + return that.error(priv.createError( + 409, + "Cannot create document", + "Document already exists" + )); + } + } + ); + }; + + /** + * Get a document or attachment + * @method get + * @param {object} command The JIO command + **/ + + that.get = function (command) { + var docId, attachId, isJIO, mime; + docId = command.getDocId(); + attachId = command.getAttachmentId() || ''; + isJIO = true; + mime = 'text/plain; charset=UTF-8'; + that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, false); + }; + + that.getAttachment = function (command) { + var docId, attachId, isJIO, mime; + docId = command.getDocId(); + attachId = command.getAttachmentId(); + isJIO = true; + mime = 'text/plain; charset=UTF-8'; + that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, true); + }; + + /** + * Create or update a document in local storage. + * @method put + * @param {object} command The JIO command + **/ + + that.put = function (command) { + var doc, docId, mime; + doc = command.cloneDoc(); + docId = command.getDocId(); + mime = 'text/plain; charset=UTF-8'; + //pas d'attachment dans un put simple + function putDocument() { + var attachId, data, isJIO; + attachId = ''; + data = JSON.stringify(doc); + isJIO = true; + that.XHRwrapper(command, + docId, + attachId, + 'PUT', + mime, + data, + isJIO, + false); + } + + that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, + function (response) { + //if (response === '404') {} + if (response._attachments !== undefined) { + doc._attachments = response._attachments; + } + putDocument(); + } + ); + }; + + that.putAttachment = function (command) { + var mon_document, + docId, + attachId, + mime, + attachment_id, + attachment_data, + attachment_md5, + attachment_mimetype, + attachment_length; + + mon_document = null; + docId = command.getDocId(); + attachId = command.getAttachmentId() || ''; + mime = 'text/plain; charset=UTF-8'; + //r茅cup茅ration des variables de l'attachement + + attachment_id = command.getAttachmentId(); + attachment_data = command.getAttachmentData(); + attachment_md5 = command.md5SumAttachmentData(); + attachment_mimetype = command.getAttachmentMimeType(); + attachment_length = command.getAttachmentLength(); + + function putAttachment() { + that.XHRwrapper(command, + docId, + attachId, + 'PUT', + mime, + attachment_data, + false, + true, + function (reponse) { + that.success({ + // response + "ok": true, + "id": docId, + "attachment": attachId + //"rev": current_revision + }); + } + ); + } + + function putDocument() { + var attachment_obj, data, doc; + attachment_obj = { + //"revpos": 3, // optional + "digest": attachment_md5, + "content_type": attachment_mimetype, + "length": attachment_length + }; + data = JSON.parse(mon_document); + + doc = priv.updateMeta(data, docId, attachId, "add", attachment_obj); + + that.XHRwrapper(command, docId, '', 'PUT', mime, doc, false, false, + function (reponse) { + putAttachment(); + } + ); + } + + function getDocument() { + //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); + that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, + function (reponse) { + if (reponse === '404') { + return that.error(priv.createError( + 404, + "Cannot find document", + "Document does not exist" + )); + } + mon_document = reponse; + putDocument(); + } + ); + } + getDocument(); + }; + + /** + * Remove a document or attachment + * @method remove + * @param {object} command The JIO command + */ + + that.remove = function (command) { + var docId, mime; + docId = command.getDocId(); + mime = 'text/plain; charset=UTF-8'; + + function deleteDocument() { + that.XHRwrapper(command, docId, '', 'DELETE', mime, '', true, false, + function (reponse) { + that.success({ + // response + "ok": true, + "id": docId + //"rev": current_revision + }); + } + ); + } + + function myCallback(response) { + } + + that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, + function (response) { + var attachKeys, keys; + attachKeys = (JSON.parse(response))._attachments; + for (keys in attachKeys) { + if (attachKeys.hasOwnProperty(keys)) { + that.XHRwrapper(command, + docId, + keys, + 'DELETE', + mime, + '', + false, + false, + myCallback + ); + } + } + deleteDocument(); + } + ); + }; + + that.removeAttachment = function (command) { + var mon_document, + docId, + attachId, + mime, + attachment_id, + attachment_data, + attachment_md5, + attachment_mimetype, + attachment_length; + + mon_document = null; + docId = command.getDocId(); + attachId = command.getAttachmentId() || ''; + mime = 'text/plain; charset=UTF-8'; + //r茅cup茅ration des variables de l'attachement + + attachment_id = command.getAttachmentId(); + attachment_data = command.getAttachmentData(); + attachment_md5 = command.md5SumAttachmentData(); + attachment_mimetype = command.getAttachmentMimeType(); + attachment_length = command.getAttachmentLength(); + + function removeAttachment() { + that.XHRwrapper(command, docId, attachId, 'DELETE', mime, '', true, true, + function (reponse) { + } + ); + } + + function putDocument() { + var data, doc; + data = JSON.parse(mon_document); + doc = priv.updateMeta(data, docId, attachId, "remove", ''); + that.XHRwrapper(command, docId, '', 'PUT', mime, doc, + false, false, function (reponse) { + removeAttachment(); + } + ); + } + + function getDocument() { + that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, + function (reponse) { + mon_document = reponse; + putDocument(); + } + ); + } + getDocument(); + }; + + /** + * Get all filenames belonging to a user from the document index + * @method allDocs + * @param {object} command The JIO command + **/ + + that.allDocs = function (command) { + var mon_document, mime; + mon_document = null; + mime = 'text/plain; charset=UTF-8'; + + function makeJSON() { + var keys, + resultTable, + counter, + allDocResponse, + count, + countB, + dealCallback, + errCallback, + i, + keyId, + Signature, + callURL, + requestUTC, + parse, + checkCounter; + keys = $(mon_document).find('Key'); + resultTable = []; + counter = 0; + keys.each(function (index) { + var that, filename, docId; + that = $(this); + filename = that.context.textContent; + docId = priv.idsToFileName(priv.fileNameToIds(filename)[0]); + if (counter === 0) { + counter += 1; + resultTable.push(docId); + } else if (docId !== resultTable[counter - 1]) { + counter += 1; + resultTable.push(docId); + } + }); + + allDocResponse = { + // document content will be added to response + "total_rows": resultTable.length, + "offset": 0, + "rows": [] + }; + + //needed to save the index within the $.ajax.success() callback + count = resultTable.length - 1; + countB = 0; + dealCallback = function (i, countB, allDoc) { + return function (doc, statustext, response) { + allDoc.rows[i].doc = response.responseText; + if (count === 0) { + that.success(allDoc); + } else { + count -= 1; + } + }; + }; + + errCallback = function (err) { + if (err.status === 404) { + //status + //statustext "Not Found" + //error + //reason "reason" + //message "did not work" + err.error = "not_found"; + that.error(err); + } else { + return that.retry(err); + } + }; + + i = resultTable.length - 1; + if (command.getOption("include_docs") === true) { + for (i; i >= 0; i -= 1) { + keyId = resultTable[i]; + Signature = that.encodeAuthorization(keyId); + callURL = priv.url + keyId; + requestUTC = new Date().toUTCString(); + parse = true; + allDocResponse.rows[i] = { + "id": priv.fileNameToIds(keyId).join(), + "key": keyId, + "value": {} + }; + checkCounter = i; + + $.ajax({ + contentType : '', + crossdomain : true, + url : callURL, + type : 'GET', + headers : { + 'Authorization' : "AWS" + + " " + + priv.AWSIdentifier + + ":" + + Signature, + //'Host' : priv.url, + 'x-amz-date' : requestUTC, + 'Content-Type' : 'application/json' + //'Content-MD5' : '' + //'Content-Length' : , + //'Expect' : , + //'x-amz-security-token' : , + }, + success : dealCallback(i, countB, allDocResponse), + error : errCallback(this) + }); + countB += 1; + } + } else { + for (i; i >= 0; i -= 1) { + keyId = resultTable[i]; + allDocResponse.rows[i] = { + "id": priv.fileNameToIds(keyId).join(), + "key": keyId, + "value": {} + }; + } + that.success(allDocResponse); + } + } + + function getXML() { + //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); + that.XHRwrapper(command, '', '', 'GET', mime, '', false, false, + function (reponse) { + mon_document = reponse; + makeJSON(); + } + ); + } + + getXML(); + //fin alldocs + }; + return that; +}); + + /* + // It is not possible to attach listeners to xhr level 2 events + // AND validate the Qunit tests through sinon.js + // therefore, below methods are deprecated + + var S3specifics = {}; + + S3specifics.uploadProgress = function(evt){ + if (evt.lengthComputable) { + var percentComplete = Math.round(evt.loaded * 100 / evt.total); + console.log(percentComplete.toString() + '%'); + } else { + console.log('Unable to compute.'); + } + }; + + S3specifics.uploadComplete = function(evt){ + var evt_txt = evt.target.responseText; + var parser = new DOMParser(); + var xmlDoc = parser.parseFromString(evt_txt, "text/xml"); + var responseURL = $(xmlDoc.getElementsByTagName('Location'))[0].text(); + console.log(responseURL); + }; + + S3specifics.uploadFailed = function(evt){ + var evt_txt = evt.target.responseText; + console.log("Erreur lors de la tentative d'upload : " + evt_txt); + }; + + S3specifics.uploadCanceled = function(evt){ + console.log("Upload annul茅 par l'utilisateur ou le navigateur."); + }; + + S3specifics.onReadyStateChange = function(req, those, that) { + if (req.readyState === 4 && those.status === 200){ + that.success({ + ok: true, + id: command.getDocId() + }); + } + }; + */