Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1103 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
2 daniel-mar 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
 
1050 daniel-mar 20
use ViaThinkSoft\OIDplus\OIDplus;
1149 daniel-mar 21
use ViaThinkSoft\OIDplus\OIDplusException;
1050 daniel-mar 22
 
1130 daniel-mar 23
/**
24
 * @param string $privKey
25
 * @return bool
26
 */
27
function is_privatekey_encrypted(string $privKey): bool {
830 daniel-mar 28
        return strpos($privKey,'BEGIN ENCRYPTED PRIVATE KEY') !== false;
29
}
30
 
1130 daniel-mar 31
/**
32
 * @param string $privKey
33
 * @param string $pubKey
34
 * @return bool
35
 */
36
function verify_private_public_key(string $privKey, string $pubKey): bool {
721 daniel-mar 37
        if (!function_exists('openssl_public_encrypt')) return false;
74 daniel-mar 38
        try {
39
                if (empty($privKey)) return false;
40
                if (empty($pubKey)) return false;
453 daniel-mar 41
                $data = generateRandomString(25);
386 daniel-mar 42
                $encrypted = '';
43
                $decrypted = '';
74 daniel-mar 44
                if (!@openssl_public_encrypt($data, $encrypted, $pubKey)) return false;
45
                if (!@openssl_private_decrypt($encrypted, $decrypted, $privKey)) return false;
46
                return $decrypted == $data;
1050 daniel-mar 47
        } catch (\Exception $e) {
74 daniel-mar 48
                return false;
49
        }
50
}
51
 
1130 daniel-mar 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) {
830 daniel-mar 59
        $pkey_config = array(
60
            //"digest_alg" => "sha512",
61
            //"private_key_bits" => 2048,
62
            //"private_key_type" => OPENSSL_KEYTYPE_RSA,
1158 daniel-mar 63
            "config" => class_exists(OIDplus::class) ? OIDplus::getOpenSslCnf() : @getenv('OPENSSL_CONF')
830 daniel-mar 64
        );
65
        $privKeyNew = @openssl_pkey_get_private($privKeyOld, $passphrase_old);
66
        if ($privKeyNew === false) return false;
1116 daniel-mar 67
        if (!@openssl_pkey_export($privKeyNew, $privKeyNewExport, $passphrase_new, $pkey_config)) return false;
68
        if ($privKeyNewExport === "") return false;
69
        return "$privKeyNewExport";
830 daniel-mar 70
}
71
 
1130 daniel-mar 72
/**
73
 * @param string $privKey
74
 * @param string $passphrase
75
 * @return false|string
76
 */
77
function decrypt_private_key(string $privKey, string $passphrase) {
830 daniel-mar 78
        return change_private_key_passphrase($privKey, $passphrase, null);
79
}
80
 
1130 daniel-mar 81
/**
82
 * @param string $privKey
83
 * @param string $passphrase
84
 * @return false|string
85
 */
86
function encrypt_private_key(string $privKey, string $passphrase) {
830 daniel-mar 87
        return change_private_key_passphrase($privKey, null, $passphrase);
88
}
89
 
1130 daniel-mar 90
/**
91
 * @param string $data
92
 * @return int
93
 */
94
function smallhash(string $data): int { // get 31 bits from SHA1. Values 0..2147483647
250 daniel-mar 95
        return (hexdec(substr(sha1($data),-4*2)) & 0x7FFFFFFF);
74 daniel-mar 96
}
180 daniel-mar 97
 
1130 daniel-mar 98
/**
99
 * @param string $name
100
 * @return array
101
 */
102
function split_firstname_lastname(string $name): array {
182 daniel-mar 103
        $ary = explode(' ', $name);
104
        $last_name = array_pop($ary);
105
        $first_name = implode(' ', $ary);
106
        return array($first_name, $last_name);
107
}
108
 
1130 daniel-mar 109
/**
110
 * @return void
111
 */
180 daniel-mar 112
function originHeaders() {
113
        // CORS
114
        // Author: Till Wehowski
426 daniel-mar 115
        // TODO: add to class OIDplus
182 daniel-mar 116
 
180 daniel-mar 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
}
236 daniel-mar 137
 
346 daniel-mar 138
if (!function_exists('mb_wordwrap')) {
1130 daniel-mar 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 {
346 daniel-mar 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
}
355 daniel-mar 179
 
1130 daniel-mar 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='') {
379 daniel-mar 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)) {
1068 daniel-mar 191
                if (PHP_SAPI != 'cli') @http_response_code(304); // 304 Not Modified
379 daniel-mar 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
 
1130 daniel-mar 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);
360 daniel-mar 214
}
215
 
1130 daniel-mar 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 {
401 daniel-mar 224
        static $translation_array = array();
225
        static $translation_loaded = null;
226
 
506 daniel-mar 227
        $str = trim($str);
228
 
1050 daniel-mar 229
        if (!class_exists(OIDplus::class)) {
438 daniel-mar 230
                return my_vsprintf($str, $sprintfArgs);
231
        }
232
 
360 daniel-mar 233
        $lang = OIDplus::getCurrentLang();
468 daniel-mar 234
        $ta = OIDplus::getTranslationArray($lang);
1162 daniel-mar 235
        $res = $ta[$lang][$str] ?? $str;
360 daniel-mar 236
 
237
        $res = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $res);
238
 
1130 daniel-mar 239
        return my_vsprintf($res, $sprintfArgs);
370 daniel-mar 240
}
386 daniel-mar 241
 
1130 daniel-mar 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) {
1050 daniel-mar 250
        if (class_exists(OIDplusException::class)) {
698 daniel-mar 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
        }
552 daniel-mar 255
}
256
 
1130 daniel-mar 257
/**
258
 * @param string $cont
259
 * @return array
260
 */
261
function extractHtmlContents(string $cont): array {
721 daniel-mar 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
 
386 daniel-mar 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);
392 daniel-mar 288
}
289
 
1130 daniel-mar 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 {
1103 daniel-mar 297
        if (hash_supported_natively('sha3-512')) {
400 daniel-mar 298
                return hash('sha3-512', $password, $raw_output);
392 daniel-mar 299
        } else {
1021 daniel-mar 300
                return \bb\Sha3\Sha3::hash($password, 512, $raw_output);
392 daniel-mar 301
        }
302
}
486 daniel-mar 303
 
1130 daniel-mar 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 {
710 daniel-mar 311
        // RFC 2104 HMAC
1103 daniel-mar 312
        if (hash_hmac_supported_natively('sha3-512')) {
847 daniel-mar 313
                return hash_hmac('sha3-512', $message, $key, $raw_output);
314
        } else {
1021 daniel-mar 315
                return \bb\Sha3\Sha3::hash_hmac($message, $key, 512, $raw_output);
847 daniel-mar 316
        }
710 daniel-mar 317
}
318
 
1130 daniel-mar 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 {
1103 daniel-mar 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
 
1130 daniel-mar 335
/**
1181 daniel-mar 336
 * @param bool $require_ssl
337
 * @param string|null $reason
1149 daniel-mar 338
 * @return bool
1181 daniel-mar 339
 * @throws OIDplusException
340
 * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
1149 daniel-mar 341
 */
1181 daniel-mar 342
function url_post_contents_available(bool $require_ssl=true, string &$reason=null): bool {
1182 daniel-mar 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
 
1181 daniel-mar 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
        }
1149 daniel-mar 356
}
357
 
358
/**
1130 daniel-mar 359
 * @param string $url
1149 daniel-mar 360
 * @param array $params
361
 * @param array $extraHeaders
1130 daniel-mar 362
 * @param string $userAgent
363
 * @return string|false
1149 daniel-mar 364
 * @throws \ViaThinkSoft\OIDplus\OIDplusException
1130 daniel-mar 365
 */
1149 daniel-mar 366
function url_post_contents(string $url, array $params=array(), array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
1182 daniel-mar 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
 
1149 daniel-mar 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
 
653 daniel-mar 383
        if (function_exists('curl_init')) {
384
                $ch = curl_init();
1050 daniel-mar 385
                if (class_exists(OIDplus::class)) {
698 daniel-mar 386
                        if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
387
                }
653 daniel-mar 388
                curl_setopt($ch, CURLOPT_URL, $url);
963 daniel-mar 389
                curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
1149 daniel-mar 390
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
391
                curl_setopt($ch, CURLOPT_POST, true);
392
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
653 daniel-mar 393
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
394
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
395
                curl_setopt($ch, CURLOPT_AUTOREFERER, true);
1006 daniel-mar 396
                $res = @curl_exec($ch);
963 daniel-mar 397
                $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
1006 daniel-mar 398
                @curl_close($ch);
963 daniel-mar 399
                if ($error_code >= 400) return false;
1006 daniel-mar 400
                if ($res === false) return false;
653 daniel-mar 401
        } else {
1182 daniel-mar 402
                $res = false;
403
                assert(false);
1149 daniel-mar 404
        }
1182 daniel-mar 405
 
1149 daniel-mar 406
        return $res;
407
}
408
 
409
/**
1181 daniel-mar 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 {
1182 daniel-mar 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
 
1181 daniel-mar 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
/**
1149 daniel-mar 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') {
1182 daniel-mar 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
 
1149 daniel-mar 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 {
963 daniel-mar 476
                // Attention: HTTPS only works if OpenSSL extension is enabled.
477
                // Our supplement does not help...
716 daniel-mar 478
                $opts = [
479
                        "http" => [
480
                                "method" => "GET",
1149 daniel-mar 481
                                "header" => implode("\r\n",$headers)."\r\n"
716 daniel-mar 482
                        ]
483
                ];
484
                $context = stream_context_create($opts);
485
                $res = @file_get_contents($url, false, $context);
653 daniel-mar 486
                if ($res === false) return false;
487
        }
488
        return $res;
660 daniel-mar 489
}
778 daniel-mar 490
 
1130 daniel-mar 491
/**
492
 * @return string
493
 */
494
function getSortedQuery(): string {
778 daniel-mar 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
}
1148 daniel-mar 501
 
502
/**
503
* @param array &$rows
504
* @param string $fieldName
505
* @return void
506
*/
1158 daniel-mar 507
function natsort_field(array &$rows, string $fieldName) {
1148 daniel-mar 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
                );
1150 daniel-mar 514
                natsort($ary);
1148 daniel-mar 515
                $keys = array_keys($ary);
516
                return $keys[0];
517
        });
518
}
1156 daniel-mar 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
}
1180 daniel-mar 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
}