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()
+      });
+    }
+  }; 
+  */