+ /* File path. */
+ $r_path = $data['tmp_name'];
+ $w_path = VAR_ASYNC . $p . $ref . '_data';
+
+ /* Check that file size is not above upload limit. */
+ if ($max_file_size > 0 &&
+ filesize($r_path) + filesize($w_path) > $max_file_size * 1024 * 1024) {
+ jirafeau_async_delete($ref);
+ return "Error: file size is above upload limit";
+ }
+
+ /* Concatenate data. */
+ $r = fopen($r_path, 'r');
+ $w = fopen($w_path, 'a');
+ while (!feof($r)) {
+ if (fwrite($w, fread($r, 1024)) === false) {
+ fclose($r);
+ fclose($w);
+ jirafeau_async_delete($ref);
+ return "Error: cannot write file";
+ }
+ }
+ fclose($r);
+ fclose($w);
+ unlink($r_path);
+
+ /* Update async file. */
+ $code = jirafeau_gen_random(4);
+ $handle = fopen(VAR_ASYNC . $p . $ref, 'w');
+ fwrite(
+ $handle,
+ $a['file_name'] . NL. $a['mime_type'] . NL. $a['key'] . NL .
+ $a['time'] . NL . $a['onetime'] . NL . $a['ip'] . NL .
+ time() . NL . $code . NL
+ );
+ fclose($handle);
+ return $code;
+}
+
+/**
+ * Finalize 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 a string starting with 'Error'
+ */
+function jirafeau_async_end($ref, $code, $crypt, $link_name_length, $file_hash_method)
+{
+ /* Get async infos. */
+ $a = jirafeau_get_async_ref($ref);
+ if (count($a) == 0
+ || $a['next_code'] != "$code") {
+ return "Error: bad code for ending transfer";
+ }
+
+ /* Generate link infos. */
+ $p = VAR_ASYNC . s2p($ref) . $ref . "_data";
+ if (!file_exists($p)) {
+ return "Error: referenced file does not exist";
+ }
+
+ $crypted = false;
+ $crypt_key = '';
+ if ($crypt == true && extension_loaded('sodium') == true) {
+ $crypt_key = jirafeau_encrypt_file($p, $p.'.crypt');
+ if (strlen($crypt_key) > 0) {
+ if (rename($p.'.crypt', $p) === true) {
+ $crypted = true;
+ }
+ }
+ }
+
+ $hash = jirafeau_hash_file($file_hash_method, $p);
+ $size = filesize($p);
+ $np = s2p($hash);
+ $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 . $hash)) {
+ rename($p, VAR_FILES . $np . $hash);
+ }
+
+ /* Increment or create count file. */
+ $counter = 0;
+ if (file_exists(VAR_FILES . $np . $hash . '_count')) {
+ $content = file(VAR_FILES . $np . $hash. '_count');
+ $counter = trim($content[0]);
+ }
+ $counter++;
+ $handle = fopen(VAR_FILES . $np . $hash. '_count', 'w');
+ fwrite($handle, $counter);
+ fclose($handle);
+
+ /* Create link. */
+ $link_tmp_name = VAR_LINKS . $hash . 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 . $hash . NL . $a['onetime'] . NL .
+ time() . NL . $a['ip'] . NL . $delete_link_code . NL . ($crypted ? 'C2' : 'O')
+ );
+ fclose($handle);
+ $hash_link = substr(base_16_to_64(md5_file($link_tmp_name)), 0, $link_name_length);
+ $l = s2p("$hash_link");
+ if (!@mkdir(VAR_LINKS . $l, 0755, true)) {
+ return "Error: cannot create folder in LINKS";
+ }
+ if (!rename($link_tmp_name, VAR_LINKS . $l . $hash_link)) {
+ return "Error: cannot rename file in LINKS";
+ }
+
+ /* Clean async upload. */
+ jirafeau_async_delete($ref);
+ return $hash_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('sodium') == true)) {
+ return '';
+ }
+
+ /* Generate key. */
+ $crypt_key = bin2hex(random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES / 2));
+ /* Init module. */
+ [$crypt_state, $crypt_header] = sodium_crypto_secretstream_xchacha20poly1305_init_push($crypt_key);
+ /* Crypt file. */
+ $r = fopen($fp_src, 'rb');
+ $w = fopen($fp_dst, 'wb');
+ fwrite($w, $crypt_header);
+
+ for ($i = 0; $i < $fs; $i += JIRAFEAU_SODIUM_CHUNKSIZE) {
+ $to_enc = fread($r, JIRAFEAU_SODIUM_CHUNKSIZE);
+ $enc = sodium_crypto_secretstream_xchacha20poly1305_push($crypt_state, $to_enc);
+
+ if (fwrite($w, $enc) === false) {
+ return '';
+ }
+ }
+
+ fclose($r);
+ fclose($w);
+
+ /* Cleanup. */
+ sodium_memzero($crypt_state);
+
+ 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('sodium') == false) {
+ return false;
+ }
+
+ /* Decrypt file. */
+ $r = fopen(VAR_FILES . $p . $link['hash'], 'rb');
+
+ $crypt_header = fread($r, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
+
+ /* Init module. */
+ $crypt_state = sodium_crypto_secretstream_xchacha20poly1305_init_pull($crypt_header, $crypt_key);
+
+ /* Decrypt file. */
+
+ for ($i = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES; $i < $fs; $i += JIRAFEAU_SODIUM_CHUNKSIZE + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
+ $to_dec = fread($r, JIRAFEAU_SODIUM_CHUNKSIZE + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
+ [$dec, $crypt_tag] = sodium_crypto_secretstream_xchacha20poly1305_pull($crypt_state, $to_dec);
+ echo $dec;
+ }
+
+ fclose($r);
+
+ /* Cleanup. */
+ sodium_memzero($crypt_state);
+
+ return true;
+}
+
+/**
+ * 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_legacy($fp_src, $fp_dst, $k)
+{
+ $fs = filesize($fp_src);
+ if ($fs === false || $fs == 0 || extension_loaded('mcrypt') == false) {
+ return false;
+ }
+
+ /* Init module */
+ $m = mcrypt_module_open('rijndael-256', '', 'ofb', '');
+ /* Extract key and iv. */
+ $crypt_key = $k;
+ $hash_key = md5($crypt_key);
+ $iv = jirafeau_crypt_create_iv($hash_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;
+}
+
+/**
+ * Test if the given IP is whitelisted by the given list.
+ *
+ * @param $allowedIpList array of allowed IPs
+ * @param $challengedIp IP to be challenged
+ * @return true if IP is authorized, false otherwise.
+ */
+function jirafeau_challenge_ip($allowedIpList, $challengedIp)
+{
+ foreach ($allowedIpList as $i) {
+ if ($i == $challengedIp) {
+ return true;
+ }
+ // CIDR test for IPv4 only.
+ if (strpos($i, '/') !== false) {
+ list($subnet, $mask) = explode('/', $i);
+ if ((ip2long($challengedIp) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Check if Jirafeau has a restriction on the IP address for uploading.
+ * @return true if uploading is IP restricted, false otherwise.
+ */
+function jirafeau_upload_has_ip_restriction($cfg)
+{
+ return count($cfg['upload_ip']) > 0;
+}
+
+/**
+ * Test if visitor's IP is authorized to upload at all.
+ *
+ * @param $cfg configuration
+ * @param $challengedIp IP to be challenged
+ * @return true if IP is authorized, false otherwise.
+ */
+function jirafeau_challenge_upload_ip($cfg, $challengedIp)
+{
+ // If no IP address have been listed, allow upload from any IP
+ if (!jirafeau_upload_has_ip_restriction($cfg)) {
+ return true;
+ }
+ return jirafeau_challenge_ip($cfg['upload_ip'], $challengedIp);
+}
+
+/**
+ * Test if visitor's IP is authorized to upload without a password.
+ *
+ * @param $cfg configuration
+ * @param $challengedIp IP to be challenged
+ * @return true if IP is authorized, false otherwise.
+ */
+function jirafeau_challenge_upload_ip_without_password($cfg, $challengedIp)
+{
+ return jirafeau_challenge_ip($cfg['upload_ip_nopassword'], $challengedIp);
+}
+
+/**
+ * Test if visitor's IP is authorized or password is supplied and authorized
+ * @param $ip IP to be challenged
+ * @param $password password to be challenged
+ * @return true if access is valid, false otherwise.
+ */
+function jirafeau_challenge_upload($cfg, $ip, $password)
+{
+ return jirafeau_challenge_upload_ip_without_password($cfg, $ip) ||
+ (!jirafeau_has_upload_password($cfg) && !jirafeau_upload_has_ip_restriction($cfg)) ||
+ (jirafeau_challenge_upload_password($cfg, $password) && jirafeau_challenge_upload_ip($cfg, $ip));
+}
+
+/**
+ * Check if Jirafeau has a restriction on the IP address for accessing the admin interface.
+ * @return true if admin interface is IP restricted, false otherwise.
+ */
+function jirafeau_admin_has_ip_restriction($cfg)
+{
+ return count($cfg['admin_ip']) > 0;
+}
+
+/**
+ * Test if visitor's IP is authorized to access the admin interface.
+ *
+ * @param $cfg configuration
+ * @param $challengedIp IP to be challenged
+ * @return true if IP is authorized, false otherwise.
+ */
+function jirafeau_challenge_admin_ip($cfg, $challengedIp)
+{
+ // If no IP address have been listed, allow upload from any IP
+ if (!jirafeau_admin_has_ip_restriction($cfg)) {
+ return true;
+ }
+ return jirafeau_challenge_ip($cfg['admin_ip'], $challengedIp);
+}
+
+/** Tell if we have some HTTP headers generated by a proxy */
+function has_http_forwarded()
+{
+ return
+ !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ||
+ !empty($_SERVER['http_X_forwarded_for']);
+}
+
+/**
+ * Generate IP list from HTTP headers generated by a proxy
+ * @return array of IP strings
+ */
+function get_ip_list_http_forwarded()
+{
+ $ip_list = array();
+ if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $l = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+ if ($l === false) {
+ return array();
+ }
+ foreach ($l as $ip) {
+ array_push($ip_list, preg_replace('/\s+/', '', $ip));
+ }
+ }
+ if (!empty($_SERVER['http_X_forwarded_for'])) {
+ $l = explode(',', $_SERVER['http_X_forwarded_for']);
+ foreach ($l as $ip) {
+ // Separate IP from port
+ $ipa = explode(':', $ip);
+ if ($ipa === false) {
+ continue;
+ }
+ $ip = $ipa[0];
+ array_push($ip_list, preg_replace('/\s+/', '', $ip));
+ }
+ }
+ return $ip_list;
+}
+
+/**
+ * Get the ip address of the client from REMOTE_ADDR
+ * or from HTTP_X_FORWARDED_FOR if behind a proxy
+ * @returns the client ip address
+ */
+function get_ip_address($cfg)
+{
+ $remote = $_SERVER['REMOTE_ADDR'];
+ if (count($cfg['proxy_ip']) == 0 || !has_http_forwarded()) {
+ return $remote;
+ }
+
+ $ip_list = get_ip_list_http_forwarded();
+ if (count($ip_list) == 0) {
+ return $remote;
+ }
+
+ foreach ($cfg['proxy_ip'] as $proxy_ip) {
+ if ($remote != $proxy_ip) {