Passed
Push — 2.x ( 3c49c1...b272d3 )
by Terry
02:18
created

__()   B

Complexity

Conditions 8
Paths 32

Size

Total Lines 52
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 52
rs 8.4444
cc 8
nc 32
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 value will be only displayed on Firewall Panel.
57
 */
58
define('SHIELDON_FIREWALL_VERSION', '2.0');
59
60
/**
61
 * Just use PSR-4 autoloader to load those helper functions.
62
 */
63
class Helpers
64
{
65
66
}
67
68
/**
69
 *   Public methods       | Desctiotion
70
 *  ----------------------|---------------------------------------------
71
 *  __                    | Get locale message.
72
 *  _e                    | Echo string from __()
73
 *  get_user_lang         | Get user lang.
74
 *  include_i18n_file     | Include i18n file.
75
 *  mask_string           | Mask strings with asterisks.
76
 *  get_cpu_usage         | Get current CPU usage information.
77
 *  get_memory_usage      | Get current RAM usage information. 
78
 *  get_default_properties| The default settings of Shieldon core.
79
 *  get_request           | Get PSR-7 HTTP server request from container.
80
 *  get_response          | Get PSR-7 HTTP response from container.
81
 *  set_request           | Set PSR-7 HTTP server request to container.
82
 *  set_response          | Set PSR-7 HTTP response to container.
83
 *  unset_global_cookie   | Unset superglobal COOKIE variable.F
84
 *  unset_global_post     | Unset superglobal POST variable.
85
 *  unset_global_get      | Unset superglobal GET variable.
86
 *  unset_global_session  | Unset superglobal SESSION variable.
87
 *  unset_superglobal     | Unset superglobal variables.
88
 *  get_ip                | Get an IP address from container.
89
 *  set_ip                | Set an IP address to container.
90
 *  get_microtimesamp     | Get the microtimesamp.
91
 *  get_session_instance  | Get a session instance.
92
 *  create_new_session_i- | Create a new session instance for current user.
93
 *  n stance              |
94
 *  get_mock_session      | For unit testing purpose.
95
 *  set_session_instance  | Set a session instance to container.
96
 *  get_session_id        | Get session ID from cookie or creating new.
97
 *  create_session_id     | Create a new session ID.
98
 *  ----------------------|---------------------------------------------
99
 */
100
101
/**
102
 * Get locale message.
103
 *
104
 * @return string
105
 */
106
function __(): string
107
{
108
    /**
109
     * Load locale string from i18n files and store them into this array 
110
     * for further use.
111
     * 
112
     * @var array 
113
     */
114
    static $i18n;
115
116
    /**
117
     * Check the file exists for not.
118
     *
119
     * @var array 
120
     */
121
    static $fileChecked;
122
123
    $num = func_num_args();
124
125
    $filename    = func_get_arg(0); // required.
126
    $langcode    = func_get_arg(1); // required.
127
    $placeholder = ($num > 2) ? func_get_arg(2) : '';
128
    $replacement = ($num > 3) ? func_get_arg(3) : [];
129
    $lang        = get_user_lang();
130
131
    if (empty($i18n[$filename]) && empty($fileChecked[$filename])) {
132
        $fileChecked[$filename] = true;
133
        $i18n[$filename] = include_i18n_file($lang, $filename);
134
    }
135
136
    // If we don't get the string from the localization file, use placeholder 
137
    // instead.
138
    $resultString = $placeholder;
139
140
    if (!empty($i18n[$filename][$langcode])) {
141
        $resultString = $i18n[$filename][$langcode];
142
    }
143
144
    if (is_array($replacement)) { 
145
        /**
146
         * Example:
147
         *     __('test', 'example_string', 'Search results: {0} items. Total items: {1}.', [5, 150]);
148
         * 
149
         * Result:
150
         *     Search results: 5 items. Total items: 150.
151
         */
152
        foreach ($replacement as $i => $r) {
153
            $resultString = str_replace('{' . $i . '}', $replacement[$i], $resultString);
154
        }
155
    }
156
    
157
    return str_replace("'", '’', $resultString);
158
}
159
160
/**
161
 * Echo string from __()
162
 *
163
 * @return void
164
 */
165
function _e(): void
166
{
167
    $num = func_num_args();
168
169
    $filename    = func_get_arg(0); // required.
170
    $langcode    = func_get_arg(1); // required.
171
    $placeholder = ($num > 2) ? func_get_arg(2) : '';
172
    $replacement = ($num > 3) ? func_get_arg(3) : [];
173
174
    echo __($filename, $langcode, $placeholder, $replacement);
175
}
176
177
/**
178
 * Get user lang.
179
 * 
180
 * This method is a part of  __()
181
 *
182
 * @return string
183
 */
184
function get_user_lang(): string
185
{
186
    static $lang;
187
188
    if (!$lang) {
189
        $lang = 'en';
190
191
        // Fetch session variables.
192
        $session = get_session_instance();
193
        $panelLang = $session->get('shieldon_panel_lang');
194
        $uiLang = $session->get('shieldon_ui_lang');
195
    
196
        if (!empty($panelLang)) {
197
            $lang = $panelLang;
198
        } elseif (!empty($uiLang)) {
199
            $lang = $uiLang;
200
        }
201
    }
202
203
    return $lang;
204
}
205
206
/**
207
 * Include i18n file.
208
 * 
209
 * This method is a part of  __()
210
 *
211
 * @param string $lang     The language code.
212
 * @param string $filename The i18n language pack file.
213
 *
214
 * @return void
215
 */
216
function include_i18n_file(string $lang, string $filename): array
217
{
218
    $content = [];
219
    $lang = str_replace('-', '_', $lang);
220
221
    if (stripos($lang, 'zh_') !== false) {
222
        if (stripos($lang, 'zh_CN') !== false) {
223
            $lang = 'zh_CN';
224
        } else {
225
            $lang = 'zh';
226
        }
227
    }
228
229
    $file = __DIR__ . '/../../localization/' . $lang . '/' . $filename . '.php';
230
    
231
    if (file_exists($file)) {
232
        $content = include $file;
233
    }
234
235
    return $content;
236
}
237
238
/**
239
 * Mask strings with asterisks.
240
 *
241
 * @param string $str The text.
242
 *
243
 * @return string
244
 */
245
function mask_string($str): string
246
{
247
    if (filter_var($str, FILTER_VALIDATE_IP) !== false) {
248
        $tmp = explode('.', $str);
249
        $tmp[0] = '*';
250
        $tmp[1] = '*';
251
        $masked = implode('.', $tmp);
252
253
    } else {
254
        $masked =  str_repeat('*', strlen($str) - 6) . substr($str, -6);
255
    }
256
257
    return $masked;
258
}
259
260
/**
261
 * Get current CPU usage information.
262
 *
263
 * This function is only used in sending notifications and it is unavailable 
264
 * on Win system. If you are using shared hosting and your hosting provider 
265
 * has disabled `sys_getloadavg` and `shell_exec`, it won't work either.
266
 *
267
 * @return string
268
 */
269
function get_cpu_usage(): string
270
{
271
    $return = '';
272
273
    // This feature is not available on Windows platform.
274
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
275
        return $return;
276
    }
277
278
    $cpuLoads = @sys_getloadavg();
279
    $cpuCores = trim(@shell_exec("grep -P '^processor' /proc/cpuinfo|wc -l"));
280
281
    if (!empty($cpuCores) && !empty($cpuLoads)) {
282
        $return = round($cpuLoads[1] / ($cpuCores + 1) * 100, 0) . '%';
283
    }
284
    return $return;
285
}
286
287
/**
288
 * Get current RAM usage information. 
289
 *
290
 * If you are using shared hosting and your hosting provider has disabled 
291
 * `shell_exec`, this function may not work as expected.
292
 *
293
 * @return string
294
 */
295
function get_memory_usage(): string
296
{
297
    $return = '';
298
299
    // This feature is not available on Windows platform.
300
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
301
        return $return;
302
    }
303
304
    $freeResult = explode("\n", trim(@shell_exec('free')));
305
306
    if (!empty($freeResult)) {
307
        $parsed = preg_split("/[\s]+/", $freeResult[1]);
308
        $return = round($parsed[2] / $parsed[1] * 100, 0) . '%';
309
    }
310
    return $return;
311
}
312
313
/**
314
 * The default settings of Shieldon core.
315
 *
316
 * @return array
317
 */
318
function get_default_properties(): array
319
{
320
    return [
321
322
        'time_unit_quota' => [
323
            's' => 2,
324
            'm' => 10,
325
            'h' => 30,
326
            'd' => 60
327
        ],
328
329
        'time_reset_limit'       => 3600,
330
        'interval_check_referer' => 5,
331
        'interval_check_session' => 5,
332
        'limit_unusual_behavior' => [
333
            'cookie'  => 5,
334
            'session' => 5,
335
            'referer' => 10
336
        ],
337
338
        'cookie_name'         => 'ssjd',
339
        'cookie_domain'       => '',
340
        'cookie_value'        => '1',
341
        'display_online_info' => true,
342
        'display_user_info'   => false,
343
        'display_http_code'   => false,
344
        'display_reason_code' => false,
345
        'display_reason_text' => false,
346
347
        /**
348
         * If you set this option enabled, Shieldon will record every CAPTCHA fails 
349
         * in a row, once that user have reached the limitation number, Shieldon will
350
         * put it as a blocked IP in rule table, until the new data cycle begins.
351
         * 
352
         * Once that user have been blocked, they are still access the warning page, 
353
         * it means that they are not humain for sure, so let's throw them into the 
354
         * system firewall and say goodbye to them forever.
355
         */
356
        'deny_attempt_enable' => [
357
            'data_circle'     => false,
358
            'system_firewall' => false,
359
        ],
360
361
        'deny_attempt_notify' => [
362
            'data_circle'     => false,
363
            'system_firewall' => false,
364
        ],
365
366
        'deny_attempt_buffer' => [
367
            'data_circle'     => 10,
368
            'system_firewall' => 10,
369
        ],
370
371
        /**
372
         * To prevent dropping social platform robots into iptables firewall, such 
373
         * as Facebook, Line and others who scrape snapshots from your web pages, 
374
         * you should adjust the values below to fit your needs. (unit: second)
375
         */
376
        'record_attempt_detection_period' => 5, // 5 seconds.
377
378
        // Reset the counter after n second.
379
        'reset_attempt_counter' => 1800, // 30 minutes.
380
381
        // System-layer firewall, ip6table service watches this folder to 
382
        // receive command created by Shieldon Firewall.
383
        'iptables_watching_folder' => '/tmp/',
384
    ];
385
}
386
387
/*
388
|--------------------------------------------------------------------------
389
| PSR-7 helpers.
390
|--------------------------------------------------------------------------
391
*/
392
393
/**
394
 * PSR-7 HTTP server request
395
 *
396
 * @return ServerRequestInterface
397
 */
398
function get_request(): ServerRequestInterface
399
{
400
    $request = Container::get('request');
401
402
    if (is_null($request)) {
403
        $request = HttpFactory::createRequest();
404
        Container::set('request', $request);
405
    }
406
407
    return $request;
408
}
409
410
/**
411
 * PSR-7 HTTP response.
412
 *
413
 * @return ResponseInterface
414
 */
415
function get_response(): ResponseInterface
416
{
417
    $response = Container::get('response');
418
419
    if (is_null($response)) {
420
        $response = HttpFactory::createResponse();
421
        Container::set('response', $response);
422
    }
423
424
    return $response;
425
}
426
427
/**
428
 * Set a PSR-7 HTTP server request into container.
429
 *
430
 * @param ServerRequestInterface $request The PSR-7 server request.
431
 *
432
 * @return void
433
 */
434
function set_request(ServerRequestInterface $request): void
435
{
436
    Container::set('request', $request, true);
437
}
438
439
/**
440
 * Set a PSR-7 HTTP response into container.
441
 *
442
 * @param ResponseInterface $response The PSR-7 server response.
443
 *
444
 * @return void
445
 */
446
function set_response(ResponseInterface $response): void
447
{
448
    Container::set('response', $response, true);
449
}
450
451
/*
452
|--------------------------------------------------------------------------
453
| Superglobal variables.
454
|--------------------------------------------------------------------------
455
*/
456
457
/**
458
 * Unset cookie.
459
 *
460
 * @param string|null $name The name (key) in the array of the superglobal.
461
 *
462
 * @return void
463
 */
464
function unset_global_cookie($name = null): void
465
{
466
    if (empty($name)) {
467
        $cookieParams = get_request()->getCookieParams();
468
        set_request(get_request()->withCookieParams([]));
469
470
        foreach (array_keys($cookieParams) as $name) {
471
            set_response(
472
                get_response()->withHeader(
473
                    'Set-Cookie',
474
                    "$name=; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0"
475
                )
476
            );
477
        }
478
        $_COOKIE = [];
479
480
        return;
481
    }
482
483
    $cookieParams = get_request()->getCookieParams();
484
    unset($cookieParams[$name]);
485
486
    set_request(
487
        get_request()->withCookieParams(
488
            $cookieParams
489
        )
490
    );
491
492
    set_response(
493
        get_response()->withHeader(
494
            'Set-Cookie',
495
            "$name=; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0"
496
        )
497
    );
498
    // Prevent direct access to superglobal.
499
    unset($_COOKIE[$name]);
500
}
501
502
/**
503
 * Unset post.
504
 *
505
 * @param string|null $name The name (key) in the array of the superglobal.
506
 *
507
 * @return void
508
 */
509
function unset_global_post($name = null): void
510
{
511
    if (empty($name)) {
512
        set_request(get_request()->withParsedBody([]));
513
        $_POST = [];
514
515
        return;
516
    }
517
518
    $postParams = get_request()->getParsedBody();
519
    unset($postParams[$name]);
520
    set_request(get_request()->withParsedBody($postParams));
521
    unset($_POST[$name]);
522
}
523
524
/**
525
 * Unset get.
526
 *
527
 * @param string|null $name The name (key) in the array of the superglobal.
528
 *
529
 * @return void
530
 */
531
function unset_global_get($name = null): void
532
{
533
    if (empty($name)) {
534
        set_request(get_request()->withQueryParams([]));
535
        $_GET = [];
536
537
        return;
538
    }
539
540
    $getParams = get_request()->getQueryParams();
541
    unset($getParams[$name]);
542
    set_request(get_request()->withQueryParams($getParams));
543
    unset($_GET[$name]);
544
}
545
546
/**
547
 * Unset session.
548
 *
549
 * @param string|null $name The name (key) in the array of the superglobal.
550
 *
551
 * @return void
552
 */
553
function unset_global_session($name = null): void
554
{
555
    if (empty($name)) {
556
        get_session_instance()->clear();
557
        return;
558
    }
559
560
    get_session_instance()->remove($name);
561
}
562
563
/**
564
 * Unset a variable of superglobal.
565
 *
566
 * @param string|null $name The name (key) in the array of the superglobal.
567
 *                          If $name is null that means clear all.
568
 * @param string      $type The type of the superglobal.
569
 *
570
 * @return void
571
 */
572
function unset_superglobal($name, string $type): void
573
{
574
    $types = [
575
        'get',
576
        'post',
577
        'cookie',
578
        'session',
579
    ];
580
581
    if (!in_array($type, $types)) {
582
        return;
583
    }
584
585
    $method = '\Shieldon\Firewall\unset_global_' . $type;
586
    $method($name, $type);
587
}
588
589
/*
590
|--------------------------------------------------------------------------
591
| IP address.
592
|--------------------------------------------------------------------------
593
*/
594
595
/**
596
 * Get an IP address.
597
 *
598
 * @return string
599
 */
600
function get_ip(): string
601
{
602
    $ip = Container::get('ip_address');
603
    
604
    if (empty($ip)) {
605
        $ip = $_SERVER['REMOTE_ADDR'];
606
    }
607
608
    return $ip;
609
}
610
611
/**
612
 * Set an IP address.
613
 *
614
 * @param string $ip An IP address.
615
 *
616
 * @return void
617
 */
618
function set_ip(string $ip)
619
{
620
    Container::set('ip_address', $ip, true);
621
}
622
623
/*
624
|--------------------------------------------------------------------------
625
| Time.
626
|--------------------------------------------------------------------------
627
*/
628
629
/**
630
 * Get the microtimesamp.
631
 * 
632
 * @return string
633
 */
634
function get_microtimesamp()
635
{
636
    $microtimesamp = explode(' ', microtime());
637
    $microtimesamp = $microtimesamp[1] . str_replace('0.', '', $microtimesamp[0]);
638
639
    return $microtimesamp;
640
}
641
642
/*
643
|--------------------------------------------------------------------------
644
| Session.
645
|--------------------------------------------------------------------------
646
*/
647
648
/**
649
 * Session
650
 * 
651
 * @return Session
652
 */
653
function get_session_instance(): Session
654
{
655
    $session = Container::get('session');
656
657
    if (is_null($session)) {
658
        $session = HttpFactory::createSession(get_session_id());
659
        set_session_instance($session);
660
    }
661
662
    return $session;
663
}
664
665
/**
666
 * For unit testing purpose. Not use in production.
667
 * Create new session by specifying a session ID.
668
 * 
669
 * @param string $sessionId A session ID string.
670
 *
671
 * @return void
672
 */
673
function create_new_session_instance(string $sessionId, $driver)
0 ignored issues
show
Unused Code introduced by
The parameter $driver is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

673
function create_new_session_instance(string $sessionId, /** @scrutinizer ignore-unused */ $driver)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
674
{
675
    Container::set('session_id', $sessionId, true);
676
    $session = Container::get('session');
677
    $session->setId($sessionId);
678
    set_session_instance($session);
0 ignored issues
show
Bug introduced by
It seems like $session can also be of type null; however, parameter $session of Shieldon\Firewall\set_session_instance() does only seem to accept Shieldon\Firewall\Session, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

678
    set_session_instance(/** @scrutinizer ignore-type */ $session);
Loading history...
679
}
680
681
/**
682
 * For unit testing purpose. Not use in production.
683
 *
684
 * @return Session
685
 */
686
function get_mock_session($sessionId): Session
687
{
688
    Container::set('session_id', $sessionId, true);
689
690
    // Constant BOOTSTRAP_DIR is available in unit testing mode.
691
    $fileDriverStorage = BOOTSTRAP_DIR . '/../tmp/shieldon/data_driver_file';
692
    $dir = $fileDriverStorage . '/shieldon_sessions';
693
    $file = $dir . '/' . $sessionId . '.json';
694
695
    if (!is_dir($dir)) {
696
        $originalUmask = umask(0);
697
698
        if (!is_dir($dir)) {
699
            mkdir($dir, 0777, true);
700
        }
701
702
        umask($originalUmask);
703
    }
704
705
    $session = HttpFactory::createSession($sessionId);
706
707
    $driver = new FileDriver($fileDriverStorage);
708
709
    if (!file_exists($file)) {
710
711
        // Prepare mock data.
712
        $data['id'] = $sessionId;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
713
        $data['ip'] = get_ip();
714
        $data['time'] = time();
715
        $data['microtimesamp'] = get_microtimesamp();
716
        $data['data'] = '{}';
717
    
718
        $json = json_encode($data);
719
        
720
        // Check and build the folder.
721
        $originalUmask = umask(0);
722
723
        if (!is_dir($dir)) {
724
            mkdir($dir, 0777, true);
725
        }
726
727
        umask($originalUmask);
728
729
        file_put_contents($file, $json);
730
    }
731
732
    $session->init($driver);
733
734
    Container::set('session', $session, true);
735
736
    return $session;
737
}
738
739
/**
740
 * Set the Session, if exists, it will be overwritten.
741
 *
742
 * @param Session $session The session instance.
743
 * 
744
 * @return void
745
 */
746
function set_session_instance(Session $session): void
747
{
748
    Container::set('session', $session, true);
749
}
750
751
/**
752
 * Get session ID.
753
 *
754
 * @return string
755
 */
756
function get_session_id(): string
757
{
758
    static $sessionId;
759
760
    if (!$sessionId) {
761
        $cookie = get_request()->getCookieParams();
762
763
        if (!empty($cookie['_shieldon'])) {
764
            $sessionId = $cookie['_shieldon'];
765
        } else {
766
            $sessionId = create_session_id();
767
        }
768
    }
769
770
    return $sessionId;
771
}
772
773
/**
774
 * Create a hash code for the Session ID.
775
 *
776
 * @return string
777
 */
778
function create_session_id(): string
779
{
780
    $hash =  rand() . 'ej;1zj47vu;3e;31g642941ek62au/41' . time();
781
782
    return md5($hash);
783
}