Passed
Pull Request — 2.x (#26)
by Viktor
01:57
created

get_microtimestamp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * php version 7.1.0
11
 *
12
 * @category  Web-security
13
 * @package   Shieldon
14
 * @author    Terry Lin <[email protected]>
15
 * @copyright 2019 terrylinooo
16
 * @license   https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT
17
 * @link      https://github.com/terrylinooo/shieldon
18
 * @see       https://shieldon.io
19
 */
20
21
declare(strict_types=1);
22
23
namespace Shieldon\Firewall;
24
25
use Psr\Http\Message\ServerRequestInterface;
26
use Psr\Http\Message\ResponseInterface;
27
use Shieldon\Firewall\Container;
28
use Shieldon\Firewall\Driver\FileDriver;
29
use Shieldon\Firewall\HttpFactory;
30
use Shieldon\Firewall\Session;
31
use function explode;
32
use function file_exists;
33
use function file_put_contents;
34
use function func_get_arg;
35
use function func_num_args;
36
use function implode;
37
use function is_array;
38
use function is_null;
39
use function md5;
40
use function microtime;
41
use function preg_split;
42
use function rand;
43
use function round;
44
use function shell_exec;
45
use function str_repeat;
46
use function str_replace;
47
use function stripos;
48
use function strtoupper;
49
use function substr;
50
use function sys_getloadavg;
51
use function time;
52
use function trim;
53
use const PHP_OS;
54
55
/**
56
 * This global constant will be only displayed on Firewall Panel.
57
 */
58
define('SHIELDON_FIREWALL_VERSION', '2.0');
59
60
const SESSION_ID_SALT = 'ej;1zj47vu;3e;31g642941ek62au/41';
61
62
/**
63
 * Just use PSR-4 autoloader to load those helper functions.
64
 */
65
class Helpers
66
{
67
68
}
69
70
/**
71
 *   Public methods       | Desctiotion
72
 *  ----------------------|---------------------------------------------
73
 *  __                    | Get locale message.
74
 *  _e                    | Echo string from __()
75
 *  get_user_lang         | Get user lang.
76
 *  include_i18n_file     | Include i18n file.
77
 *  mask_string           | Mask strings with asterisks.
78
 *  get_cpu_usage         | Get current CPU usage information.
79
 *  get_memory_usage      | Get current RAM usage information. 
80
 *  get_default_properties| The default settings of Shieldon core.
81
 *  get_request           | Get PSR-7 HTTP server request from container.
82
 *  get_response          | Get PSR-7 HTTP response from container.
83
 *  set_request           | Set PSR-7 HTTP server request to container.
84
 *  set_response          | Set PSR-7 HTTP response to container.
85
 *  unset_global_cookie   | Unset superglobal COOKIE variable.F
86
 *  unset_global_post     | Unset superglobal POST variable.
87
 *  unset_global_get      | Unset superglobal GET variable.
88
 *  unset_global_session  | Unset superglobal SESSION variable.
89
 *  unset_superglobal     | Unset superglobal variables.
90
 *  get_ip                | Get an IP address from container.
91
 *  set_ip                | Set an IP address to container.
92
 *  get_microtimestamp    | Get the microtimesamp.
93
 *  get_session_instance  | Get a session instance.
94
 *  create_new_session_i- | Create a new session instance for current user.
95
 *  n stance              |
96
 *  get_mock_session      | For unit testing purpose.
97
 *  set_session_instance  | Set a session instance to container.
98
 *  get_session_id        | Get session ID from cookie or creating new.
99
 *  create_session_id     | Create a new session ID.
100
 *  ----------------------|---------------------------------------------
101
 */
102
103
/**
104
 * Get locale message.
105
 *
106
 * @return string
107
 */
108
function __(): string
109
{
110
    /**
111
     * Load locale string from i18n files and store them into this array 
112
     * for further use.
113
     * 
114
     * @var array 
115
     */
116
    static $i18n;
117
118
    /**
119
     * Check the file exists for not.
120
     *
121
     * @var array 
122
     */
123
    static $fileChecked;
124
125
    $num = func_num_args();
126
127
    $filename    = func_get_arg(0); // required.
128
    $langcode    = func_get_arg(1); // required.
129
    $placeholder = ($num > 2) ? func_get_arg(2) : '';
130
    $replacement = ($num > 3) ? func_get_arg(3) : [];
131
    $lang        = get_user_lang();
132
133
    if (empty($i18n[$filename]) && empty($fileChecked[$filename])) {
134
        $fileChecked[$filename] = true;
135
        $i18n[$filename] = include_i18n_file($lang, $filename);
136
    }
137
138
    // If we don't get the string from the localization file, use placeholder 
139
    // instead.
140
    $resultString = $placeholder;
141
142
    if (!empty($i18n[$filename][$langcode])) {
143
        $resultString = $i18n[$filename][$langcode];
144
    }
145
146
    if (is_array($replacement)) { 
147
        /**
148
         * Example:
149
         *     __('test', 'example_string', 'Search results: {0} items. Total items: {1}.', [5, 150]);
150
         * 
151
         * Result:
152
         *     Search results: 5 items. Total items: 150.
153
         */
154
        foreach ($replacement as $i => $r) {
155
            $resultString = str_replace('{' . $i . '}', $replacement[$i], $resultString);
156
        }
157
    }
158
    
159
    return str_replace("'", '’', $resultString);
160
}
161
162
/**
163
 * Echo string from __()
164
 *
165
 * @return void
166
 */
167
function _e(): void
168
{
169
    $num = func_num_args();
170
171
    $filename    = func_get_arg(0); // required.
172
    $langcode    = func_get_arg(1); // required.
173
    $placeholder = ($num > 2) ? func_get_arg(2) : '';
174
    $replacement = ($num > 3) ? func_get_arg(3) : [];
175
176
    echo __($filename, $langcode, $placeholder, $replacement);
177
}
178
179
/**
180
 * Get user lang.
181
 * 
182
 * This method is a part of  __()
183
 *
184
 * @return string
185
 */
186
function get_user_lang(): string
187
{
188
    static $lang;
189
190
    if (!$lang) {
191
        $lang = 'en';
192
193
        // Fetch session variables.
194
        $session = get_session_instance();
195
        $panelLang = $session->get('shieldon_panel_lang');
196
        $uiLang = $session->get('shieldon_ui_lang');
197
    
198
        if (!empty($panelLang)) {
199
            $lang = $panelLang;
200
        } elseif (!empty($uiLang)) {
201
            $lang = $uiLang;
202
        }
203
    }
204
205
    return $lang;
206
}
207
208
/**
209
 * Include i18n file.
210
 * 
211
 * This method is a part of  __()
212
 *
213
 * @param string $lang     The language code.
214
 * @param string $filename The i18n language pack file.
215
 *
216
 * @return void
217
 */
218
function include_i18n_file(string $lang, string $filename): array
219
{
220
    $content = [];
221
    $lang = str_replace('-', '_', $lang);
222
223
    if (stripos($lang, 'zh_') !== false) {
224
        $lang = stripos($lang, 'zh_CN') === false ? 'zh' : 'zh_CN';
225
    }
226
227
    $file = sprintf(
228
        '%s/localization/%s/%s.php',
229
        dirname(__DIR__, 2),
230
        $lang,
231
        $filename
232
    );
233
    
234
    if (file_exists($file)) {
235
        $content = include $file;
236
    }
237
238
    return $content;
239
}
240
241
/**
242
 * Mask strings with asterisks.
243
 *
244
 * @param string $str The text.
245
 *
246
 * @return string
247
 */
248
function mask_string($str): string
249
{
250
    if (filter_var($str, FILTER_VALIDATE_IP) === false) {
251
        $masked =  str_repeat('*', strlen($str) - 6) . substr($str, -6);
252
    } else {
253
        $tmp = explode('.', $str);
254
        $tmp[0] = '*';
255
        $tmp[1] = '*';
256
        $masked = implode('.', $tmp);
257
    }
258
259
    return $masked;
260
}
261
262
/**
263
 * Get current CPU usage information.
264
 *
265
 * This function is only used in sending notifications and it is unavailable 
266
 * on Win system. If you are using shared hosting and your hosting provider 
267
 * has disabled `sys_getloadavg` and `shell_exec`, it won't work either.
268
 *
269
 * @return string
270
 */
271
function get_cpu_usage(): string
272
{
273
    $cpuUsage = '';
274
275
    // This feature is not available on Windows platform.
276
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
277
        return $cpuUsage;
278
    }
279
280
    $cpuLoads = @sys_getloadavg();
281
    $cpuCores = trim(@shell_exec("grep -P '^processor' /proc/cpuinfo|wc -l"));
282
283
    if (!empty($cpuCores) && !empty($cpuLoads)) {
284
        $cpuUsage = round($cpuLoads[1] / ($cpuCores + 1) * 100, 0) . '%';
285
    }
286
287
    return $cpuUsage;
288
}
289
290
/**
291
 * Get current RAM usage information. 
292
 *
293
 * If you are using shared hosting and your hosting provider has disabled 
294
 * `shell_exec`, this function may not work as expected.
295
 *
296
 * @return string
297
 */
298
function get_memory_usage(): string
299
{
300
    $memoryUsage = '';
301
302
    // This feature is not available on Windows platform.
303
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
304
        return $memoryUsage;
305
    }
306
307
    $freeResult = explode("\n", trim(@shell_exec('free')));
308
309
    if (!empty($freeResult)) {
310
        $parsed = preg_split("/[\s]+/", $freeResult[1]);
311
        $memoryUsage = round($parsed[2] / $parsed[1] * 100, 0) . '%';
312
    }
313
314
    return $memoryUsage;
315
}
316
317
/**
318
 * The default settings of Shieldon core.
319
 *
320
 * @return array
321
 */
322
function get_default_properties(): array
323
{
324
    return [
325
        'time_unit_quota' => [
326
            's' => 2,
327
            'm' => 10,
328
            'h' => 30,
329
            'd' => 60,
330
        ],
331
332
        'time_reset_limit'       => 3600,
333
        'interval_check_referer' => 5,
334
        'interval_check_session' => 5,
335
        'limit_unusual_behavior' => [
336
            'cookie'  => 5,
337
            'session' => 5,
338
            'referer' => 10,
339
        ],
340
341
        'cookie_name'         => 'ssjd',
342
        'cookie_domain'       => '',
343
        'cookie_value'        => '1',
344
        'display_online_info' => true,
345
        'display_user_info'   => false,
346
        'display_http_code'   => false,
347
        'display_reason_code' => false,
348
        'display_reason_text' => false,
349
350
        /**
351
         * If you set this option enabled, Shieldon will record every CAPTCHA fails 
352
         * in a row, once that user have reached the limitation number, Shieldon will
353
         * put it as a blocked IP in rule table, until the new data cycle begins.
354
         * 
355
         * Once that user have been blocked, they are still access the warning page, 
356
         * it means that they are not humain for sure, so let's throw them into the 
357
         * system firewall and say goodbye to them forever.
358
         */
359
        'deny_attempt_enable' => [
360
            'data_circle'     => false,
361
            'system_firewall' => false,
362
        ],
363
364
        'deny_attempt_notify' => [
365
            'data_circle'     => false,
366
            'system_firewall' => false,
367
        ],
368
369
        'deny_attempt_buffer' => [
370
            'data_circle'     => 10,
371
            'system_firewall' => 10,
372
        ],
373
374
        /**
375
         * To prevent dropping social platform robots into iptables firewall, such 
376
         * as Facebook, Line and others who scrape snapshots from your web pages, 
377
         * you should adjust the values below to fit your needs. (unit: second)
378
         */
379
        'record_attempt_detection_period' => 5, // 5 seconds.
380
381
        // Reset the counter after n second.
382
        'reset_attempt_counter' => 1800, // 30 minutes.
383
384
        // System-layer firewall, ip6table service watches this folder to 
385
        // receive command created by Shieldon Firewall.
386
        'iptables_watching_folder' => '/tmp/',
387
    ];
388
}
389
390
/*
391
|--------------------------------------------------------------------------
392
| PSR-7 helpers.
393
|--------------------------------------------------------------------------
394
*/
395
396
/**
397
 * PSR-7 HTTP server request
398
 *
399
 * @return ServerRequestInterface
400
 */
401
function get_request(): ServerRequestInterface
402
{
403
    $request = Container::get('request');
404
405
    if (is_null($request)) {
406
        $request = HttpFactory::createRequest();
407
        Container::set('request', $request);
408
    }
409
410
    return $request;
411
}
412
413
/**
414
 * PSR-7 HTTP response.
415
 *
416
 * @return ResponseInterface
417
 */
418
function get_response(): ResponseInterface
419
{
420
    $response = Container::get('response');
421
422
    if (is_null($response)) {
423
        $response = HttpFactory::createResponse();
424
        Container::set('response', $response);
425
    }
426
427
    return $response;
428
}
429
430
/**
431
 * Set a PSR-7 HTTP server request into container.
432
 *
433
 * @param ServerRequestInterface $request The PSR-7 server request.
434
 *
435
 * @return void
436
 */
437
function set_request(ServerRequestInterface $request): void
438
{
439
    Container::set('request', $request, true);
440
}
441
442
/**
443
 * Set a PSR-7 HTTP response into container.
444
 *
445
 * @param ResponseInterface $response The PSR-7 server response.
446
 *
447
 * @return void
448
 */
449
function set_response(ResponseInterface $response): void
450
{
451
    Container::set('response', $response, true);
452
}
453
454
/*
455
|--------------------------------------------------------------------------
456
| Superglobal variables.
457
|--------------------------------------------------------------------------
458
*/
459
460
/**
461
 * Unset cookie.
462
 *
463
 * @param string|null $name The name (key) in the array of the superglobal.
464
 *
465
 * @return void
466
 */
467
function unset_global_cookie($name = null): void
468
{
469
    if (empty($name)) {
470
        $cookieParams = get_request()->getCookieParams();
471
        set_request(get_request()->withCookieParams([]));
472
473
        foreach (array_keys($cookieParams) as $name) {
474
            set_response(
475
                get_response()->withHeader(
476
                    'Set-Cookie',
477
                    "{$name}=; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0"
478
                )
479
            );
480
        }
481
        $_COOKIE = [];
482
483
        return;
484
    }
485
486
    $cookieParams = get_request()->getCookieParams();
487
    unset($cookieParams[$name]);
488
489
    set_request(
490
        get_request()->withCookieParams(
491
            $cookieParams
492
        )
493
    );
494
495
    set_response(
496
        get_response()->withHeader(
497
            'Set-Cookie',
498
            "{$name}=; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0"
499
        )
500
    );
501
    // Prevent direct access to superglobal.
502
    unset($_COOKIE[$name]);
503
}
504
505
/**
506
 * Unset post.
507
 *
508
 * @param string|null $name The name (key) in the array of the superglobal.
509
 *
510
 * @return void
511
 */
512
function unset_global_post($name = null): void
513
{
514
    if (empty($name)) {
515
        set_request(get_request()->withParsedBody([]));
516
        $_POST = [];
517
518
        return;
519
    }
520
521
    $postParams = get_request()->getParsedBody();
522
    unset($postParams[$name]);
523
    set_request(get_request()->withParsedBody($postParams));
524
    unset($_POST[$name]);
525
}
526
527
/**
528
 * Unset get.
529
 *
530
 * @param string|null $name The name (key) in the array of the superglobal.
531
 *
532
 * @return void
533
 */
534
function unset_global_get($name = null): void
535
{
536
    if (empty($name)) {
537
        set_request(get_request()->withQueryParams([]));
538
        $_GET = [];
539
540
        return;
541
    }
542
543
    $getParams = get_request()->getQueryParams();
544
    unset($getParams[$name]);
545
    set_request(get_request()->withQueryParams($getParams));
546
    unset($_GET[$name]);
547
}
548
549
/**
550
 * Unset session.
551
 *
552
 * @param string|null $name The name (key) in the array of the superglobal.
553
 *
554
 * @return void
555
 */
556
function unset_global_session($name = null): void
557
{
558
    if (empty($name)) {
559
        get_session_instance()->clear();
560
        get_session_instance()->save();
561
        return;
562
    }
563
564
    get_session_instance()->remove($name);
565
    get_session_instance()->save();
566
}
567
568
/**
569
 * Unset a variable of superglobal.
570
 *
571
 * @param string|null $name The name (key) in the array of the superglobal.
572
 *                          If $name is null that means clear all.
573
 * @param string      $type The type of the superglobal.
574
 *
575
 * @return void
576
 */
577
function unset_superglobal($name, string $type): void
578
{
579
    $types = [
580
        'get',
581
        'post',
582
        'cookie',
583
        'session',
584
    ];
585
586
    if (!in_array($type, $types)) {
587
        return;
588
    }
589
590
    $method = '\Shieldon\Firewall\unset_global_' . $type;
591
    $method($name, $type);
592
}
593
594
/*
595
|--------------------------------------------------------------------------
596
| IP address.
597
|--------------------------------------------------------------------------
598
*/
599
600
/**
601
 * Get an IP address.
602
 *
603
 * @return string
604
 */
605
function get_ip(): string
606
{
607
    $ip = Container::get('ip_address');
608
    
609
    if (empty($ip)) {
610
        $ip = $_SERVER['REMOTE_ADDR'];
611
    }
612
613
    return $ip;
614
}
615
616
/**
617
 * Set an IP address.
618
 *
619
 * @param string $ip An IP address.
620
 *
621
 * @return void
622
 */
623
function set_ip(string $ip)
624
{
625
    Container::set('ip_address', $ip, true);
626
}
627
628
/*
629
|--------------------------------------------------------------------------
630
| Time.
631
|--------------------------------------------------------------------------
632
*/
633
634
/**
635
 * Get the microtimestamp.
636
 * 
637
 * @return string
638
 */
639
function get_microtimestamp()
640
{
641
    $microtimestamp = explode(' ', microtime());
642
    $microtimestamp = $microtimestamp[1] . str_replace('0.', '', $microtimestamp[0]);
643
644
    return $microtimestamp;
645
}
646
647
/*
648
|--------------------------------------------------------------------------
649
| Session.
650
|--------------------------------------------------------------------------
651
*/
652
653
/**
654
 * Session
655
 * 
656
 * @return Session
657
 */
658
function get_session_instance(): Session
659
{
660
    $session = Container::get('session');
661
662
    if (is_null($session)) {
663
        $session = HttpFactory::createSession(get_session_id());
664
        set_session_instance($session);
665
    }
666
667
    return $session;
668
}
669
670
/**
671
 * For unit testing purpose. Not use in production.
672
 * Create new session by specifying a session ID.
673
 * 
674
 * @param string $sessionId A session ID string.
675
 *
676
 * @return void
677
 */
678
function create_new_session_instance(string $sessionId)
679
{
680
    Container::set('session_id', $sessionId, true);
681
    $session = Container::get('session');
682
683
    if ($session instanceof Session) {
684
        $session->setId($sessionId);
685
        set_session_instance($session);
686
    }
687
}
688
689
/**
690
 * For unit testing purpose. Not use in production.
691
 *
692
 * @return Session
693
 */
694
function get_mock_session($sessionId): Session
695
{
696
    Container::set('session_id', $sessionId, true);
697
698
    // Constant BOOTSTRAP_DIR is available in unit testing mode.
699
    $fileDriverStorage = BOOTSTRAP_DIR . '/../tmp/shieldon/data_driver_file';
700
    $dir = $fileDriverStorage . '/shieldon_sessions';
701
    $file = $dir . '/' . $sessionId . '.json';
702
703
    if (!is_dir($dir)) {
704
        $originalUmask = umask(0);
705
706
        if (!is_dir($dir)) {
707
            mkdir($dir, 0777, true);
708
        }
709
710
        umask($originalUmask);
711
    }
712
713
    $session = HttpFactory::createSession($sessionId);
714
715
    $driver = new FileDriver($fileDriverStorage);
716
717
    if (!file_exists($file)) {
718
        $data = [];
719
720
        // Prepare mock data.
721
        $data['id'] = $sessionId;
722
        $data['ip'] = get_ip();
723
        $data['time'] = time();
724
        $data['microtimestamp'] = get_microtimestamp();
725
        $data['data'] = '{}';
726
    
727
        $json = json_encode($data);
728
        
729
        // Check and build the folder.
730
        $originalUmask = umask(0);
731
732
        if (!is_dir($dir)) {
733
            mkdir($dir, 0777, true);
734
        }
735
736
        umask($originalUmask);
737
738
        file_put_contents($file, $json);
739
    }
740
741
    $session->init($driver);
742
743
    Container::set('session', $session, true);
744
745
    return $session;
746
}
747
748
/**
749
 * Set the Session, if exists, it will be overwritten.
750
 *
751
 * @param Session $session The session instance.
752
 * 
753
 * @return void
754
 */
755
function set_session_instance(Session $session): void
756
{
757
    Container::set('session', $session, true);
758
}
759
760
/**
761
 * Get session ID.
762
 *
763
 * @return string
764
 */
765
function get_session_id(): string
766
{
767
    static $sessionId;
768
769
    if (!$sessionId) {
770
        $cookie = get_request()->getCookieParams();
771
        $sessionId = empty($cookie['_shieldon']) ? create_session_id() : $cookie['_shieldon'];
772
    }
773
774
    return $sessionId;
775
}
776
777
/**
778
 * Create a hash code for the Session ID.
779
 *
780
 * @return string
781
 */
782
function create_session_id(): string
783
{
784
    return md5(rand() . SESSION_ID_SALT . time());
785
}
786