+
+/**
+  * Finalyze an asynchronous upload.
+  * @param $ref asynchronous upload reference
+  * @param $code client code for this operation
+  * @param $crypt boolean asking to crypt or not
+  * @param $link_name_length link name length
+  * @return  a string containing the download reference followed by a delete code or the string "Error"
+  */
+function
+jirafeau_async_end ($ref, $code, $crypt, $link_name_length)
+{
+    /* Get async infos. */
+    $a = jirafeau_get_async_ref ($ref);
+    if (count ($a) == 0
+        || $a['next_code'] != "$code")
+        return "Error";
+    
+    /* Generate link infos. */
+    $p = VAR_ASYNC . s2p ($ref) . $ref . "_data";
+    if (!file_exists($p))
+        return "Error";
+
+    $crypted = false;
+    $crypt_key = '';
+    if ($crypt == true && extension_loaded('mcrypt') == true)
+    {
+        $crypt_key = jirafeau_encrypt_file ($p, $p);
+        if (strlen($crypt_key) > 0)
+            $crypted = true;
+    }
+
+    $md5 = md5_file ($p);
+    $size = filesize($p);
+    $np = s2p ($md5);
+    $delete_link_code = jirafeau_gen_random (5);
+    
+    /* File already exist ? */ 
+    if (!file_exists (VAR_FILES . $np))
+        @mkdir (VAR_FILES . $np, 0755, true);
+    if (!file_exists (VAR_FILES . $np . $md5))
+        rename ($p, VAR_FILES . $np . $md5);
+    
+    /* Increment or create count file. */
+    $counter = 0;
+    if (file_exists (VAR_FILES . $np . $md5 . '_count'))
+    {
+        $content = file (VAR_FILES . $np . $md5. '_count');
+        $counter = trim ($content[0]);
+    }
+    $counter++;
+    $handle = fopen (VAR_FILES . $np . $md5. '_count', 'w');
+    fwrite ($handle, $counter);
+    fclose ($handle);
+    
+    /* Create link. */
+    $link_tmp_name =  VAR_LINKS . $md5 . rand (0, 10000) . '.tmp';
+    $handle = fopen ($link_tmp_name, 'w');
+    fwrite ($handle,
+            $a['file_name'] . NL . $a['mime_type'] . NL . $size . NL .
+            $a['key'] . NL . $a['time'] . NL . $md5 . NL . $a['onetime'] . NL .
+            date ('U') . NL . $a['ip'] . NL . $delete_link_code . NL . ($crypted ? 'C' : 'O'));
+    fclose ($handle);
+    $md5_link = substr(base_16_to_64 (md5_file ($link_tmp_name)), 0, $link_name_length);
+    $l = s2p ("$md5_link");
+    if (!@mkdir (VAR_LINKS . $l, 0755, true) ||
+        !rename ($link_tmp_name,  VAR_LINKS . $l . $md5_link))
+        echo "Error";
+    
+    /* Clean async upload. */
+    jirafeau_async_delete ($ref);
+    return $md5_link . NL . $delete_link_code . NL . urlencode($crypt_key);
+}
+
+function
+jirafeau_crypt_create_iv($base, $size)
+{
+    $iv = '';
+    while (strlen ($iv) < $size)
+        $iv = $iv . $base;
+    $iv = substr($iv, 0, $size);
+    return $iv;
+}
+
+/**
+ * Crypt file and returns decrypt key.
+ * @param $fp_src file path to the file to crypt.
+ * @param $fp_dst file path to the file to write crypted file (could be the same).
+ * @return decrypt key composed of the key and the iv separated by a point ('.')
+ */
+function
+jirafeau_encrypt_file ($fp_src, $fp_dst)
+{
+    $fs = filesize ($fp_src);
+    if ($fs === false || $fs == 0 || !(extension_loaded('mcrypt') == true))
+        return '';
+
+    /* Prepare module. */
+    $m = mcrypt_module_open('rijndael-256', '', 'ofb', '');
+    /* Generate key. */
+    $crypt_key = jirafeau_gen_random (10);
+    $md5_key = md5($crypt_key);
+    $iv = jirafeau_crypt_create_iv ($md5_key, mcrypt_enc_get_iv_size($m));
+    /* Init module. */
+    mcrypt_generic_init($m, $md5_key, $iv);
+    /* Crypt file. */
+    $r = fopen ($fp_src, 'r');
+    $w = fopen ($fp_dst, 'c');
+    while (!feof ($r))
+    {
+        $enc = mcrypt_generic($m, fread ($r, 1024));
+        if (fwrite ($w, $enc) === false)
+            return '';
+    }
+    fclose ($r);
+    fclose ($w);
+    /* Cleanup. */
+    mcrypt_generic_deinit($m);
+    mcrypt_module_close($m);
+    return $crypt_key;
+}
+
+/**
+ * Decrypt file.
+ * @param $fp_src file path to the file to decrypt.
+ * @param $fp_dst file path to the file to write decrypted file (could be the same).
+ * @param $k string composed of the key and the iv separated by a point ('.')
+ * @return key used to decrypt. a string of length 0 is returned if failed.
+ */
+function
+jirafeau_decrypt_file ($fp_src, $fp_dst, $k)
+{
+    $fs = filesize ($fp_src);
+    if ($fs === false || $fs == 0 || !(extension_loaded('mcrypt') == true))
+        return false;
+
+    /* Init module */
+    $m = mcrypt_module_open('rijndael-256', '', 'ofb', '');
+    /* Extract key and iv. */
+    $crypt_key = $k;
+    $md5_key = md5($crypt_key);
+    $iv = jirafeau_crypt_create_iv ($md5_key, mcrypt_enc_get_iv_size($m));
+    /* Decrypt file. */
+    $r = fopen ($fp_src, 'r');
+    $w = fopen ($fp_dst, 'c');
+    while (!feof ($r))
+    {
+        $dec = mdecrypt_generic($m, fread ($r, 1024));
+        if (fwrite ($w, $dec) === false)
+            return false;
+    }
+    fclose ($r);
+    fclose ($w);
+    /* Cleanup. */
+    mcrypt_generic_deinit($m);
+    mcrypt_module_close($m);
+    return true;
+}
+
+/**
+ * Check if Jirafeau is password protected for visitors.
+ * @return true if Jirafeau is password protected, false otherwise.
+ */
+function jirafeau_has_upload_password ($cfg)
+{
+    return count ($cfg['upload_password']) > 0;
+}
+
+/**
+ * Challenge password for a visitor.
+ * @param $password password to be challenged
+ * @return true if password is valid, false otherwise.
+ */
+function jirafeau_challenge_upload_password ($cfg, $password)
+{
+    if (!jirafeau_has_upload_password($cfg))
+        return false;
+    forEach ($cfg['upload_password'] as $p)
+        if ($password == $p)
+            return true;
+    return false;
+}
+