Passed
Push — 2.x ( 5968ae...19b2bf )
by Terry
02:34
created

FileDriver::doFetch()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 5
nop 2
dl 0
loc 25
rs 9.5555
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
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall\Driver;
14
15
use Shieldon\Firewall\Driver\DriverProvider;
16
use RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use RuntimeException;
19
20
use function file_exists;
21
use function file_get_contents;
22
use function file_put_contents;
23
use function in_array;
24
use function is_dir;
25
use function json_decode;
26
use function ksort;
27
use function mkdir;
28
use function rmdir;
29
use function str_replace;
30
use function touch;
31
use function umask;
32
use function unlink;
33
34
/**
35
 * File Driver.
36
 */
37
class FileDriver extends DriverProvider
38
{
39
    /**
40
     * The directory that data files stored to.
41
     *
42
     * @var string
43
     */
44
    protected $directory = '/tmp/';
45
46
47
    /**
48
     * The file's extension name'.
49
     *
50
     * @var string
51
     */
52
    protected $extension = 'json';
53
54
55
    /**
56
     * A file that confirms the required dictories have been created.
57
     *
58
     * @var string
59
     */
60
    private $checkPoint = 'shieldon_check_exist.txt';
61
62
    /**
63
     * Constructor.
64
     *
65
     * @param string $directory
66
     */
67
    public function __construct(string $directory = '')
68
    {
69
        if ('' !== $directory) {
70
            $this->directory = $directory;
71
        }
72
    }
73
74
    /**
75
     * Initialize data tables.
76
     *
77
     * @param bool $dbCheck This is for creating data tables automatically
78
     *                      Turn it off, if you don't want to check data tables every pageview.
79
     *
80
     * @return void
81
     */
82
    protected function doInitialize(bool $dbCheck = true): void
83
    {
84
        if (!$this->isInitialized) {
85
            if (!empty($this->channel)) {
86
                $this->setChannel($this->channel);
87
            }
88
89
            // Check the directory where data files write into.
90
            $this->createDirectory();
91
        }
92
93
        $this->isInitialized = true;
94
    }
95
96
    /**
97
     * {@inheritDoc}
98
     */
99
    protected function doFetchAll(string $type = 'filter'): array
100
    {
101
        $results = [];
102
103
        if (!in_array($type, $this->tableTypes)) {
104
            return $results;
105
        }
106
107
        $dir = $this->getDirectory($type);
108
109
        if (is_dir($dir)) {
110
            $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
111
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
112
113
            foreach($files as $file) {
114
                if ($file->isFile()) {
115
116
                    $content = json_decode(file_get_contents($file->getPath() . '/' . $file->getFilename()), true);
117
118
                    if ($type === 'session') {
119
                        $sort = $content['microtimesamp'] . '.' . $file->getFilename(); 
120
                    } else {
121
                        $sort = $file->getMTime() . '.' . $file->getFilename();
122
                    }
123
                    $results[$sort] = $content;
124
                }
125
            }
126
            unset($it, $files);
127
128
            // Sort by ascending timesamp (microtimesamp).
129
            ksort($results);
130
        }
131
132
        return $results;
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     */
138
    protected function doFetch(string $ip, string $type = 'filter'): array
139
    {
140
        $results = [];
141
142
        if (
143
            !file_exists($this->getFilename($ip, $type)) || 
144
            !in_array($type, $this->tableTypes)
145
        ) {
146
            return $results;
147
        }
148
149
        $fileContent = file_get_contents($this->getFilename($ip, $type));
150
        $resultData = json_decode($fileContent, true);
151
152
        // rule | session
153
        if (is_array($resultData)) {
154
            $results = $resultData;
155
        }
156
157
        // filter 
158
        if (!empty($resultData['log_data'])) {
159
            return $resultData['log_data']; 
160
        }
161
162
        return $results;
163
    }
164
165
    /**
166
     * {@inheritDoc}
167
     */
168
    protected function checkExist(string $ip, string $type = 'filter'): bool
169
    {
170
        if (file_exists($this->getFilename($ip, $type))) {
171
            return true;
172
        }
173
174
        return false;
175
    }
176
177
    /**
178
     * {@inheritDoc}
179
     */
180
    protected function doSave(string $ip, array $data, string $type = 'filter', $expire = 0): bool
181
    {
182
        switch ($type) {
183
184
            case 'rule':
185
                $logData = $data;
186
                $logData['log_ip'] = $ip;
187
                break;
188
189
            case 'filter':
190
                $logData['log_ip'] = $ip;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$logData was never initialized. Although not strictly required by PHP, it is generally a good practice to add $logData = array(); before regardless.
Loading history...
191
                $logData['log_data'] = $data;
192
                break;
193
194
            case 'session':
195
                $logData = $data;
196
                break;
197
        }
198
199
        $result = file_put_contents($this->getFilename($ip, $type), json_encode($logData));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $logData does not seem to be defined for all execution paths leading up to this point.
Loading history...
200
201
        // Update file time.
202
        touch($this->getFilename($ip, $type), time());
203
204
        return ($result > 0) ? true : false;
205
    }
206
207
    /**
208
     * {@inheritDoc}
209
     */
210
    protected function doDelete(string $ip, string $type = 'filter'): bool
211
    {
212
        switch ($type) {
213
            case 'rule':
214
                // no break
215
            case 'filter':
216
                // no break
217
            case 'session':
218
                return $this->remove($this->getFilename($ip, $type));
219
        }
220
221
        return false;
222
    }
223
224
    /**
225
     * {@inheritDoc}
226
     */
227
    protected function doRebuild(): bool
228
    {
229
        // Those are Shieldon logs directories.
230
        $removeDirs = [
231
            $this->getDirectory('filter'),
232
            $this->getDirectory('rule'),
233
            $this->getDirectory('session'),
234
        ];
235
        
236
        // Remove them recursively.
237
        foreach ($removeDirs as $dir) {
238
            if (file_exists($dir)) {
239
                $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
240
                $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
241
    
242
                foreach($files as $file) {
243
                    if ($file->isDir()) {
244
                        // @codeCoverageIgnoreStart
245
                        rmdir($file->getRealPath());
246
                        // @codeCoverageIgnoreEnd
247
                    } else {
248
                        unlink($file->getRealPath());
249
                    }
250
                }
251
                unset($it, $files);
252
253
                if (is_dir($dir)) {
254
                    rmdir($dir);
255
                }
256
            }
257
        }
258
259
        $checkingFile = $this->directory . '/' . $this->channel . '_' . $this->checkPoint;
260
261
        if (file_exists($checkingFile)) {
262
            unlink($checkingFile);
263
        }
264
265
        $conA = !is_dir($this->getDirectory('filter'));
266
        $conB = !is_dir($this->getDirectory('rule'));
267
        $conC = !is_dir($this->getDirectory('session'));
268
269
        // Check if are Shieldon directories removed or not.
270
        $result = ($conA && $conB && $conC);
271
272
        $this->createDirectory();
273
274
        return $result;
275
    }
276
277
    /**
278
     * Create a directory for storing data files.
279
     *
280
     * @return bool
281
     */
282
    protected function createDirectory(): bool
283
    {
284
        $conA = $resultB = $resultC = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $resultC is dead and can be removed.
Loading history...
Unused Code introduced by
The assignment to $resultB is dead and can be removed.
Loading history...
285
286
        $checkingFile = $this->directory . '/' . $this->channel . '_' . $this->checkPoint;
287
288
        if (!file_exists($checkingFile)) {
289
            $originalUmask = umask(0);
290
291
            if (!is_dir($this->getDirectory('filter'))) {
292
                $conA = @mkdir($this->getDirectory('filter'), 0777, true);
293
            }
294
    
295
            if (!is_dir($this->getDirectory('rule'))) {
296
                $conB = @mkdir($this->getDirectory('rule'), 0777, true);
297
            }
298
    
299
            if (!is_dir($this->getDirectory('session'))) {
300
                $conC = @mkdir($this->getDirectory('session'), 0777, true);
301
            }
302
303
            if (!($conA && $conB && $conC)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $conB does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $conC does not seem to be defined for all execution paths leading up to this point.
Loading history...
304
                return false;
305
            }
306
307
            file_put_contents($checkingFile, ' ');
308
            umask($originalUmask);
309
        }
310
311
        return true;
312
    }
313
314
    /**
315
     * Check the directory if is writable.
316
     *
317
     * @return bool
318
     */
319
    protected function checkDirectory(): bool
320
    {
321
        if (!is_dir($this->directory) || !is_writable($this->directory)) {
322
            throw new RuntimeException(
323
                'The directory defined by File Driver must be writable. (' . $this->directory . ')'
324
            );
325
        }
326
327
        return true;
328
    }
329
330
    /**
331
     * Remove a Shieldon log file.
332
     * 
333
     * @param $logFilePath The absolute path of the log file.
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\Driver\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
334
     *
335
     * @return bool
336
     */
337
    private function remove(string $logFilePath): bool
338
    {
339
        if (file_exists($logFilePath)) {
340
            return unlink($logFilePath);
341
        }
342
        return false;
343
    }
344
345
    /**
346
     * Get filename.
347
     *
348
     * @param string $ip   IP address.
349
     * @param string $type The table name of the data cycle.
350
     *
351
     * @return string
352
     */
353
    private function getFilename(string $ip, string $type = 'filter'): string
354
    {
355
        $ip = str_replace(':', '-', $ip);
356
        $path = [];
357
358
        $path['filter'] = $this->directory . '/' . $this->tableFilterLogs . '/' . $ip . '.' . $this->extension;
359
        $path['session'] = $this->directory . '/' . $this->tableSessions   . '/' . $ip . '.' . $this->extension;
360
        $path['rule'] = $this->directory . '/' . $this->tableRuleList   . '/' . $ip . '.' . $this->extension;
361
362
        return $path[$type] ?? '';
363
    }
364
365
    /**
366
     * Get directory.
367
     *
368
     * @param string $type The table name of the data cycle.
369
     *
370
     * @return string
371
     */
372
    private function getDirectory(string $type = 'filter'): string
373
    {
374
        $path = [];
375
376
        $path['filter'] = $this->directory . '/' . $this->tableFilterLogs;
377
        $path['session'] = $this->directory . '/' . $this->tableSessions;
378
        $path['rule'] = $this->directory . '/' . $this->tableRuleList;
379
380
        return $path[$type] ?? '';
381
    }
382
}
383
384