Subversion Repositories oidplus

Rev

Rev 1181 | Rev 1190 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. use ViaThinkSoft\OIDplus\OIDplus;
  21. use ViaThinkSoft\OIDplus\OIDplusException;
  22.  
  23. /**
  24.  * @param string $privKey
  25.  * @return bool
  26.  */
  27. function is_privatekey_encrypted(string $privKey): bool {
  28.         return strpos($privKey,'BEGIN ENCRYPTED PRIVATE KEY') !== false;
  29. }
  30.  
  31. /**
  32.  * @param string $privKey
  33.  * @param string $pubKey
  34.  * @return bool
  35.  */
  36. function verify_private_public_key(string $privKey, string $pubKey): bool {
  37.         if (!function_exists('openssl_public_encrypt')) return false;
  38.         try {
  39.                 if (empty($privKey)) return false;
  40.                 if (empty($pubKey)) return false;
  41.                 $data = generateRandomString(25);
  42.                 $encrypted = '';
  43.                 $decrypted = '';
  44.                 if (!@openssl_public_encrypt($data, $encrypted, $pubKey)) return false;
  45.                 if (!@openssl_private_decrypt($encrypted, $decrypted, $privKey)) return false;
  46.                 return $decrypted == $data;
  47.         } catch (\Exception $e) {
  48.                 return false;
  49.         }
  50. }
  51.  
  52. /**
  53.  * @param string $privKeyOld
  54.  * @param string|null $passphrase_old
  55.  * @param string|null $passphrase_new
  56.  * @return false|string
  57.  */
  58. function change_private_key_passphrase(string $privKeyOld, string $passphrase_old=null, string $passphrase_new=null) {
  59.         $pkey_config = array(
  60.             //"digest_alg" => "sha512",
  61.             //"private_key_bits" => 2048,
  62.             //"private_key_type" => OPENSSL_KEYTYPE_RSA,
  63.             "config" => class_exists(OIDplus::class) ? OIDplus::getOpenSslCnf() : @getenv('OPENSSL_CONF')
  64.         );
  65.         $privKeyNew = @openssl_pkey_get_private($privKeyOld, $passphrase_old);
  66.         if ($privKeyNew === false) return false;
  67.         if (!@openssl_pkey_export($privKeyNew, $privKeyNewExport, $passphrase_new, $pkey_config)) return false;
  68.         if ($privKeyNewExport === "") return false;
  69.         return "$privKeyNewExport";
  70. }
  71.  
  72. /**
  73.  * @param string $privKey
  74.  * @param string $passphrase
  75.  * @return false|string
  76.  */
  77. function decrypt_private_key(string $privKey, string $passphrase) {
  78.         return change_private_key_passphrase($privKey, $passphrase, null);
  79. }
  80.  
  81. /**
  82.  * @param string $privKey
  83.  * @param string $passphrase
  84.  * @return false|string
  85.  */
  86. function encrypt_private_key(string $privKey, string $passphrase) {
  87.         return change_private_key_passphrase($privKey, null, $passphrase);
  88. }
  89.  
  90. /**
  91.  * @param string $data
  92.  * @return int
  93.  */
  94. function smallhash(string $data): int { // get 31 bits from SHA1. Values 0..2147483647
  95.         return (hexdec(substr(sha1($data),-4*2)) & 0x7FFFFFFF);
  96. }
  97.  
  98. /**
  99.  * @param string $name
  100.  * @return array
  101.  */
  102. function split_firstname_lastname(string $name): array {
  103.         $ary = explode(' ', $name);
  104.         $last_name = array_pop($ary);
  105.         $first_name = implode(' ', $ary);
  106.         return array($first_name, $last_name);
  107. }
  108.  
  109. /**
  110.  * @return void
  111.  */
  112. function originHeaders() {
  113.         // CORS
  114.         // Author: Till Wehowski
  115.         // TODO: add to class OIDplus
  116.  
  117.         header("Access-Control-Allow-Credentials: true");
  118.         header("Access-Control-Allow-Origin: ".strip_tags(((isset($_SERVER['HTTP_ORIGIN'])) ? $_SERVER['HTTP_ORIGIN'] : "*")));
  119.  
  120.         header("Access-Control-Allow-Headers: If-None-Match, X-Requested-With, Origin, X-Frdlweb-Bugs, Etag, X-Forgery-Protection-Token, X-CSRF-Token");
  121.  
  122.         if (isset($_SERVER['HTTP_ORIGIN'])) {
  123.                 header('X-Frame-Options: ALLOW-FROM '.$_SERVER['HTTP_ORIGIN']);
  124.         } else {
  125.                 header_remove("X-Frame-Options");
  126.         }
  127.  
  128.         $expose = array('Etag', 'X-CSRF-Token');
  129.         foreach (headers_list() as $num => $header) {
  130.                 $h = explode(':', $header);
  131.                 $expose[] = trim($h[0]);
  132.         }
  133.         header("Access-Control-Expose-Headers: ".implode(',',$expose));
  134.  
  135.         header("Vary: Origin");
  136. }
  137.  
  138. if (!function_exists('mb_wordwrap')) {
  139.         /**
  140.          * @param string $str
  141.          * @param int $width
  142.          * @param string $break
  143.          * @param bool $cut
  144.          * @return string
  145.          */
  146.         function mb_wordwrap(string $str, int $width = 75, string $break = "\n", bool $cut = false): string {
  147.                 // https://stackoverflow.com/a/4988494/488539
  148.                 $lines = explode($break, $str);
  149.                 foreach ($lines as &$line) {
  150.                         $line = rtrim($line);
  151.                         if (mb_strlen($line) <= $width) {
  152.                                 continue;
  153.                         }
  154.                         $words = explode(' ', $line);
  155.                         $line = '';
  156.                         $actual = '';
  157.                         foreach ($words as $word) {
  158.                                 if (mb_strlen($actual.$word) <= $width) {
  159.                                         $actual .= $word.' ';
  160.                                 } else {
  161.                                         if ($actual != '') {
  162.                                                 $line .= rtrim($actual).$break;
  163.                                         }
  164.                                         $actual = $word;
  165.                                         if ($cut) {
  166.                                                 while (mb_strlen($actual) > $width) {
  167.                                                         $line .= mb_substr($actual, 0, $width).$break;
  168.                                                         $actual = mb_substr($actual, $width);
  169.                                                 }
  170.                                         }
  171.                                         $actual .= ' ';
  172.                                 }
  173.                         }
  174.                         $line .= trim($actual);
  175.                 }
  176.                 return implode($break, $lines);
  177.         }
  178. }
  179.  
  180. /**
  181.  * @param string $out
  182.  * @param string $contentType
  183.  * @param string $filename
  184.  * @return void
  185.  */
  186. function httpOutWithETag(string $out, string $contentType, string $filename='') {
  187.         $etag = md5($out);
  188.         header("Etag: $etag");
  189.         header("Content-MD5: $etag"); // RFC 2616 clause 14.15
  190.         if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && (trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag)) {
  191.                 if (PHP_SAPI != 'cli') @http_response_code(304); // 304 Not Modified
  192.         } else {
  193.                 header("Content-Type: $contentType");
  194.                 if (!empty($filename)) {
  195.                         header('Content-Disposition:inline; filename="'.$filename.'"');
  196.                 }
  197.                 echo $out;
  198.         }
  199.         die();
  200. }
  201.  
  202. /**
  203.  * @param string $str
  204.  * @param array $args
  205.  * @return string
  206.  */
  207. function my_vsprintf(string $str, array $args): string {
  208.         $n = 1;
  209.         foreach ($args as $val) {
  210.                 $str = str_replace("%$n", $val, $str);
  211.                 $n++;
  212.         }
  213.         return str_replace("%%", "%", $str);
  214. }
  215.  
  216. /**
  217.  * @param string $str
  218.  * @param mixed ...$sprintfArgs
  219.  * @return string
  220.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  221.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  222.  */
  223. function _L(string $str, ...$sprintfArgs): string {
  224.         static $translation_array = array();
  225.         static $translation_loaded = null;
  226.  
  227.         $str = trim($str);
  228.  
  229.         if (!class_exists(OIDplus::class)) {
  230.                 return my_vsprintf($str, $sprintfArgs);
  231.         }
  232.  
  233.         $lang = OIDplus::getCurrentLang();
  234.         $ta = OIDplus::getTranslationArray($lang);
  235.         $res = $ta[$lang][$str] ?? $str;
  236.  
  237.         $res = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $res);
  238.  
  239.         return my_vsprintf($res, $sprintfArgs);
  240. }
  241.  
  242. /**
  243.  * @param array $params
  244.  * @param string $key
  245.  * @return void
  246.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  247.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  248.  */
  249. function _CheckParamExists(array $params, string $key) {
  250.         if (class_exists(OIDplusException::class)) {
  251.                 if (!isset($params[$key])) throw new OIDplusException(_L('Parameter %1 is missing', $key));
  252.         } else {
  253.                 if (!isset($params[$key])) throw new Exception(_L('Parameter %1 is missing', $key));
  254.         }
  255. }
  256.  
  257. /**
  258.  * @param string $cont
  259.  * @return array
  260.  */
  261. function extractHtmlContents(string $cont): array {
  262.         // make sure the program works even if the user provided HTML is not UTF-8
  263.         $cont = convert_to_utf8_no_bom($cont);
  264.  
  265.         $out_js = '';
  266.         $m = array();
  267.         preg_match_all('@<script[^>]*>(.+)</script>@ismU', $cont, $m);
  268.         foreach ($m[1] as $x) {
  269.                 $out_js = $x . "\n\n";
  270.         }
  271.  
  272.         $out_css = '';
  273.         $m = array();
  274.         preg_match_all('@<style[^>]*>(.+)</style>@ismU', $cont, $m);
  275.         foreach ($m[1] as $x) {
  276.                 $out_css = $x . "\n\n";
  277.         }
  278.  
  279.         $out_html = $cont;
  280.         $out_html = preg_replace('@^(.+)<body[^>]*>@isU', '', $out_html);
  281.         $out_html = preg_replace('@</body>.+$@isU', '', $out_html);
  282.         $out_html = preg_replace('@<title>.+</title>@isU', '', $out_html);
  283.         $out_html = preg_replace('@<h1>.+</h1>@isU', '', $out_html, 1);
  284.         $out_html = preg_replace('@<script[^>]*>(.+)</script>@ismU', '', $out_html);
  285.         $out_html = preg_replace('@<style[^>]*>(.+)</style>@ismU', '', $out_html);
  286.  
  287.         return array($out_html, $out_js, $out_css);
  288. }
  289.  
  290. /**
  291.  * @param string $password
  292.  * @param bool $raw_output
  293.  * @return string
  294.  * @throws Exception
  295.  */
  296. function sha3_512(string $password, bool $raw_output=false): string {
  297.         if (hash_supported_natively('sha3-512')) {
  298.                 return hash('sha3-512', $password, $raw_output);
  299.         } else {
  300.                 return \bb\Sha3\Sha3::hash($password, 512, $raw_output);
  301.         }
  302. }
  303.  
  304. /**
  305.  * @param string $message
  306.  * @param string $key
  307.  * @param bool $raw_output
  308.  * @return string
  309.  */
  310. function sha3_512_hmac(string $message, string $key, bool $raw_output=false): string {
  311.         // RFC 2104 HMAC
  312.         if (hash_hmac_supported_natively('sha3-512')) {
  313.                 return hash_hmac('sha3-512', $message, $key, $raw_output);
  314.         } else {
  315.                 return \bb\Sha3\Sha3::hash_hmac($message, $key, 512, $raw_output);
  316.         }
  317. }
  318.  
  319. /**
  320.  * @param string $password
  321.  * @param string $salt
  322.  * @param int $iterations
  323.  * @param int $length
  324.  * @param bool $binary
  325.  * @return string
  326.  */
  327. function sha3_512_pbkdf2(string $password, string $salt, int $iterations, int $length=0, bool $binary=false): string {
  328.         if (hash_pbkdf2_supported_natively('sha3-512')) {
  329.                 return hash_pbkdf2('sha3-512', $password, $salt, $iterations, $length, $binary);
  330.         } else {
  331.                 return \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, 512, $length, $binary);
  332.         }
  333. }
  334.  
  335. /**
  336.  * @param bool $require_ssl
  337.  * @param string|null $reason
  338.  * @return bool
  339.  * @throws OIDplusException
  340.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  341.  */
  342. function url_post_contents_available(bool $require_ssl=true, string &$reason=null): bool {
  343.         if (class_exists(OIDplus::class)) {
  344.                 if (OIDplus::baseConfig()->getValue('OFFLINE_MODE', false)) {
  345.                         $reason = _L('OIDplus is running in offline mode due to the base configuration setting %1.', 'OFFLINE_MODE');
  346.                         return false;
  347.                 }
  348.         }
  349.  
  350.         if (function_exists('curl_init')) {
  351.                 return true;
  352.         } else {
  353.                 $reason = _L('Please install the PHP extension %1, so that OIDplus can connect to the Internet.', '<code>php_curl</code>');
  354.                 return false;
  355.         }
  356. }
  357.  
  358. /**
  359.  * @param string $url
  360.  * @param array $params
  361.  * @param array $extraHeaders
  362.  * @param string $userAgent
  363.  * @return string|false
  364.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  365.  */
  366. function url_post_contents(string $url, array $params=array(), array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
  367.         $require_ssl = str_starts_with(strtolower($url),'https:');
  368.         if (!url_post_contents_available($require_ssl, $reason)) {
  369.                 throw new OIDplusException(_L('This feature is not available, because OIDplus cannot connect to the Internet.').' '.$reason);
  370.         }
  371.  
  372.         $postFields = http_build_query($params);
  373.  
  374.         $headers = array(
  375.                 "User-Agent: $userAgent",
  376.                 "Content-Length: ".strlen($postFields)
  377.         );
  378.  
  379.         foreach ($extraHeaders as $name => $val) {
  380.                 $headers[] = "$name: $val";
  381.         }
  382.  
  383.         if (function_exists('curl_init')) {
  384.                 $ch = curl_init();
  385.                 if (class_exists(OIDplus::class)) {
  386.                         if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
  387.                 }
  388.                 curl_setopt($ch, CURLOPT_URL, $url);
  389.                 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
  390.                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  391.                 curl_setopt($ch, CURLOPT_POST, true);
  392.                 curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
  393.                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  394.                 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  395.                 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
  396.                 $res = @curl_exec($ch);
  397.                 $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
  398.                 @curl_close($ch);
  399.                 if ($error_code >= 400) return false;
  400.                 if ($res === false) return false;
  401.         } else {
  402.                 $res = false;
  403.                 assert(false);
  404.         }
  405.  
  406.         return $res;
  407. }
  408.  
  409. /**
  410.  * @param bool $require_ssl
  411.  * @param string|null $reason
  412.  * @return bool
  413.  * @throws OIDplusException
  414.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  415.  */
  416. function url_get_contents_available(bool $require_ssl=true, string &$reason=null): bool {
  417.         if (class_exists(OIDplus::class)) {
  418.                 if (OIDplus::baseConfig()->getValue('OFFLINE_MODE', false)) {
  419.                         $reason = _L('OIDplus is running in offline mode due to the base configuration setting %1.', 'OFFLINE_MODE');
  420.                         return false;
  421.                 }
  422.         }
  423.  
  424.         if (function_exists('curl_init')) {
  425.                 // Via cURL
  426.                 return true;
  427.         } else {
  428.                 // Via file_get_contents()
  429.                 if (!ini_get('allow_url_fopen')) {
  430.                         $reason = _L('Please install the PHP extension %1 and/or enable %2 in your PHP configuration, so that OIDplus can connect to the Internet.', '<code>php_curl</code>', '<code>allow_url_fopen</code>');
  431.                         return false;
  432.                 }
  433.                 // Use extension_loaded() instead of function_exists(), because our supplement does not help...
  434.                 if ($require_ssl && !extension_loaded('openssl')) {
  435.                         $reason = _L('Please install the PHP extension %1 and/or %2, so that OIDplus can connect to the Internet.', '<code>php_curl</code>', '<code>php_openssl</code>');
  436.                         return false;
  437.                 }
  438.                 return true;
  439.         }
  440. }
  441.  
  442. /**
  443.  * @param string $url
  444.  * @param array $extraHeaders
  445.  * @param string $userAgent
  446.  * @return string|false
  447.  */
  448. function url_get_contents(string $url, array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
  449.         $require_ssl = str_starts_with(strtolower($url),'https:');
  450.         if (!url_get_contents_available($require_ssl, $reason)) {
  451.                 throw new OIDplusException(_L('This feature is not available, because OIDplus cannot connect to the Internet.').' '.$reason);
  452.         }
  453.  
  454.         $headers = array("User-Agent: $userAgent");
  455.         foreach ($extraHeaders as $name => $val) {
  456.                 $headers[] = "$name: $val";
  457.         }
  458.         if (function_exists('curl_init')) {
  459.                 $ch = curl_init();
  460.                 if (class_exists(OIDplus::class)) {
  461.                         if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
  462.                 }
  463.                 curl_setopt($ch, CURLOPT_URL, $url);
  464.                 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
  465.                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  466.                 curl_setopt($ch, CURLOPT_POST, false);
  467.                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  468.                 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  469.                 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
  470.                 $res = @curl_exec($ch);
  471.                 $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
  472.                 @curl_close($ch);
  473.                 if ($error_code >= 400) return false;
  474.                 if ($res === false) return false;
  475.         } else {
  476.                 // Attention: HTTPS only works if OpenSSL extension is enabled.
  477.                 // Our supplement does not help...
  478.                 $opts = [
  479.                         "http" => [
  480.                                 "method" => "GET",
  481.                                 "header" => implode("\r\n",$headers)."\r\n"
  482.                         ]
  483.                 ];
  484.                 $context = stream_context_create($opts);
  485.                 $res = @file_get_contents($url, false, $context);
  486.                 if ($res === false) return false;
  487.         }
  488.         return $res;
  489. }
  490.  
  491. /**
  492.  * @return string
  493.  */
  494. function getSortedQuery(): string {
  495.         // https://stackoverflow.com/a/51777249/488539
  496.         $url = [];
  497.         parse_str($_SERVER['QUERY_STRING'], $url);
  498.         ksort($url);
  499.         return http_build_query($url);
  500. }
  501.  
  502. /**
  503. * @param array &$rows
  504. * @param string $fieldName
  505. * @return void
  506. */
  507. function natsort_field(array &$rows, string $fieldName) {
  508.         usort($rows, function($a,$b) use($fieldName) {
  509.                 if ($a[$fieldName] == $b[$fieldName]) return 0; // equal
  510.                 $ary = array(
  511.                         -1 => $a[$fieldName],
  512.                         1 => $b[$fieldName]
  513.                 );
  514.                 natsort($ary);
  515.                 $keys = array_keys($ary);
  516.                 return $keys[0];
  517.         });
  518. }
  519.  
  520. /**
  521.  * @param array $ary
  522.  * @return \stdClass
  523.  */
  524. function array_to_stdobj(array $ary): \stdClass {
  525.         $obj = new \stdClass;
  526.         foreach ($ary as $name => $val) {
  527.                 $obj->$name = $val;
  528.         }
  529.         return $obj;
  530. }
  531.  
  532. /**
  533.  * @param \stdClass $obj
  534.  * @return array
  535.  */
  536. function stdobj_to_array(\stdClass $obj): array {
  537.         $ary = array();
  538.         foreach ($obj as $name => $val) {
  539.                 $ary[$name] = $val;
  540.         }
  541.         return $ary;
  542. }
  543.  
  544. /**
  545.  * @return string|false
  546.  */
  547. function get_own_username() {
  548.         $current_user = exec('whoami');
  549.         if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  550.                 try {
  551.                         if (function_exists('mb_convert_encoding')) {
  552.                                 $current_user = @mb_convert_encoding($current_user, "UTF-8", "cp850");
  553.                         } else if (function_exists('iconv')) {
  554.                                 $current_user = @iconv("cp850", "UTF-8", $current_user);
  555.                         }
  556.                 } catch (\Exception $e) {}
  557.                 if (function_exists('mb_strtoupper')) {
  558.                         $current_user = mb_strtoupper($current_user); // just cosmetics
  559.                 }
  560.         }
  561.         if (!$current_user) {
  562.                 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  563.                         // Windows on an IIS server:
  564.                         //     getenv('USERNAME')     MARSCHALL$                (That is the "machine account", see https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#accessing-the-network )
  565.                         //     get_current_user()     DefaultAppPool
  566.                         //     exec('whoami')         iis apppool\defaultapppool
  567.                         // Windows with XAMPP:
  568.                         //     getenv('USERNAME')     dmarschall
  569.                         //     get_current_user()     dmarschall               (even if script has a different NTFS owner!)
  570.                         //     exec('whoami')         hickelsoft\dmarschall
  571.                         $current_user = get_current_user();
  572.                         if (!$current_user) {
  573.                                 $current_user = getenv('USERNAME');
  574.                                 $current_user = mb_strtoupper($current_user); // just cosmetics
  575.                         }
  576.                 } else {
  577.                         // On Linux:
  578.                         $current_user = exec('id -un');
  579.                         if (!$current_user) {
  580.                                 // PHP'S get_current_user() will get the owner of the PHP script, not the process owner!
  581.                                 // We want the process owner, so we use posix_geteuid() preferably.
  582.                                 if (function_exists('posix_geteuid')) {
  583.                                         $uid = posix_geteuid();
  584.                                 } else {
  585.                                         $temp_file = tempnam(sys_get_temp_dir(), 'TMP');
  586.                                         if ($temp_file !== false) {
  587.                                                 $uid = fileowner($temp_file);
  588.                                                 if ($uid === false) $uid = -1;
  589.                                                 @unlink($temp_file);
  590.                                         } else {
  591.                                                 $uid = -1;
  592.                                         }
  593.                                 }
  594.                                 if ($uid >= 0) {
  595.                                         $current_user = '#' . $uid;
  596.                                         if (function_exists('posix_getpwuid')) {
  597.                                                 $userinfo = posix_getpwuid($uid); // receive username from the UID (requires read access to /etc/passwd)
  598.                                                 if ($userinfo !== false) $current_user = $userinfo['name'];
  599.                                         }
  600.                                 } else {
  601.                                         $current_user = get_current_user();
  602.                                 }
  603.                         }
  604.                 }
  605.         }
  606.         return $current_user ?: false;
  607. }
  608.  
  609. /**
  610.  * @param string $path
  611.  * @return bool
  612.  */
  613. function isFileOrPathWritable(string $path): bool {
  614.         if ($writable_file = (file_exists($path) && is_writable($path))) return true;
  615.         if ($writable_directory = (!file_exists($path) && is_writable(dirname($path)))) return true;
  616.         return false;
  617. }