Subversion Repositories oidplus

Rev

Rev 1150 | Rev 1158 | 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,
1050 daniel-mar 63
            "config" => class_exists("\\ViaThinkSoft\\OIDplus\\OIDplus") ? 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);
235
        $res = (isset($ta[$lang]) && isset($ta[$lang][$str])) ? $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
/**
1149 daniel-mar 336
 * @return bool
337
 */
338
function url_post_contents_available(): bool {
339
        return function_exists('curl_init');
340
}
341
 
342
/**
1130 daniel-mar 343
 * @param string $url
1149 daniel-mar 344
 * @param array $params
345
 * @param array $extraHeaders
1130 daniel-mar 346
 * @param string $userAgent
347
 * @return string|false
1149 daniel-mar 348
 * @throws \ViaThinkSoft\OIDplus\OIDplusException
1130 daniel-mar 349
 */
1149 daniel-mar 350
function url_post_contents(string $url, array $params=array(), array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
351
        $postFields = http_build_query($params);
352
 
353
        $headers = array(
354
                "User-Agent: $userAgent",
355
                "Content-Length: ".strlen($postFields)
356
        );
357
 
358
        foreach ($extraHeaders as $name => $val) {
359
                $headers[] = "$name: $val";
360
        }
361
 
653 daniel-mar 362
        if (function_exists('curl_init')) {
363
                $ch = curl_init();
1050 daniel-mar 364
                if (class_exists(OIDplus::class)) {
698 daniel-mar 365
                        if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
366
                }
653 daniel-mar 367
                curl_setopt($ch, CURLOPT_URL, $url);
963 daniel-mar 368
                curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
1149 daniel-mar 369
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
370
                curl_setopt($ch, CURLOPT_POST, true);
371
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
653 daniel-mar 372
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
373
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
374
                curl_setopt($ch, CURLOPT_AUTOREFERER, true);
1006 daniel-mar 375
                $res = @curl_exec($ch);
963 daniel-mar 376
                $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
1006 daniel-mar 377
                @curl_close($ch);
963 daniel-mar 378
                if ($error_code >= 400) return false;
1006 daniel-mar 379
                if ($res === false) return false;
653 daniel-mar 380
        } else {
1149 daniel-mar 381
                throw new OIDplusException(_L('The "%1" PHP extension is not installed at your system. Please enable the PHP extension <code>%2</code>.','CURL','php_curl'));
382
        }
383
        return $res;
384
}
385
 
386
/**
387
 * @param string $url
388
 * @param array $extraHeaders
389
 * @param string $userAgent
390
 * @return string|false
391
 */
392
function url_get_contents(string $url, array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
393
        $headers = array("User-Agent: $userAgent");
394
        foreach ($extraHeaders as $name => $val) {
395
                $headers[] = "$name: $val";
396
        }
397
        if (function_exists('curl_init')) {
398
                $ch = curl_init();
399
                if (class_exists(OIDplus::class)) {
400
                        if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
401
                }
402
                curl_setopt($ch, CURLOPT_URL, $url);
403
                curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
404
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
405
                curl_setopt($ch, CURLOPT_POST, false);
406
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
407
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
408
                curl_setopt($ch, CURLOPT_AUTOREFERER, true);
409
                $res = @curl_exec($ch);
410
                $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
411
                @curl_close($ch);
412
                if ($error_code >= 400) return false;
413
                if ($res === false) return false;
414
        } else {
963 daniel-mar 415
                // Attention: HTTPS only works if OpenSSL extension is enabled.
416
                // Our supplement does not help...
716 daniel-mar 417
                $opts = [
418
                        "http" => [
419
                                "method" => "GET",
1149 daniel-mar 420
                                "header" => implode("\r\n",$headers)."\r\n"
716 daniel-mar 421
                        ]
422
                ];
423
                $context = stream_context_create($opts);
424
                $res = @file_get_contents($url, false, $context);
653 daniel-mar 425
                if ($res === false) return false;
426
        }
427
        return $res;
660 daniel-mar 428
}
778 daniel-mar 429
 
1130 daniel-mar 430
/**
431
 * @return string
432
 */
433
function getSortedQuery(): string {
778 daniel-mar 434
        // https://stackoverflow.com/a/51777249/488539
435
        $url = [];
436
        parse_str($_SERVER['QUERY_STRING'], $url);
437
        ksort($url);
438
        return http_build_query($url);
439
}
1148 daniel-mar 440
 
441
/**
442
* @param array &$rows
443
* @param string $fieldName
444
* @return void
445
*/
446
function natsort_field(&$rows, string $fieldName) {
447
        usort($rows, function($a,$b) use($fieldName) {
448
                if ($a[$fieldName] == $b[$fieldName]) return 0; // equal
449
                $ary = array(
450
                        -1 => $a[$fieldName],
451
                        1 => $b[$fieldName]
452
                );
1150 daniel-mar 453
                natsort($ary);
1148 daniel-mar 454
                $keys = array_keys($ary);
455
                return $keys[0];
456
        });
457
}
1156 daniel-mar 458
 
459
/**
460
 * @param array $ary
461
 * @return \stdClass
462
 */
463
function array_to_stdobj(array $ary): \stdClass {
464
        $obj = new \stdClass;
465
        foreach ($ary as $name => $val) {
466
                $obj->$name = $val;
467
        }
468
        return $obj;
469
}
470
 
471
/**
472
 * @param \stdClass $obj
473
 * @return array
474
 */
475
function stdobj_to_array(\stdClass $obj): array {
476
        $ary = array();
477
        foreach ($obj as $name => $val) {
478
                $ary[$name] = $val;
479
        }
480
        return $ary;
481
}