Passed
Push — 2.x ( 957b8f...e0668e )
by Terry
01:49
created

FileDriver::doFetchAll()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 37
rs 8.9777
cc 6
nc 2
nop 1
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\Driver;
24
25
use Shieldon\Firewall\Driver\DriverProvider;
26
use Shieldon\Firewall\Driver\FileDriverTrait;
27
use RecursiveDirectoryIterator;
28
use RecursiveIteratorIterator;
29
use function file_exists;
30
use function file_get_contents;
31
use function file_put_contents;
32
use function in_array;
33
use function is_dir;
34
use function json_decode;
35
use function ksort;
36
use function rmdir;
37
use function touch;
38
use function unlink;
39
40
/**
41
 * File Driver.
42
 */
43
class FileDriver extends DriverProvider
44
{
45
    use FileDriverTrait;
46
47
    /**
48
     * The directory that data files stored to.
49
     *
50
     * @var string
51
     */
52
    protected $directory = '/tmp/';
53
54
55
    /**
56
     * The file's extension name'.
57
     *
58
     * @var string
59
     */
60
    protected $extension = 'json';
61
62
    /**
63
     * Constructor.
64
     *
65
     * @param string $directory The directory for storing data files.
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
            if ($dbCheck) {
91
                $this->createDirectory();
92
            }
93
        }
94
95
        $this->isInitialized = true;
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     * 
101
     * @param string $type The type of the data table.
102
     * 
103
     * @return array
104
     */
105
    protected function doFetchAll(string $type = 'filter'): array
106
    {
107
        $results = [];
108
109
        $this->assertInvalidDataTable($type);
110
111
        $dir = $this->getDirectory($type);
112
113
        if (is_dir($dir)) {
114
            $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
115
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
116
117
            foreach ($files as $file) {
118
                if ($file->isFile()) {
119
120
                    $filename = $file->getPath() . '/' . $file->getFilename();
121
                    $fileContent = file_get_contents($filename);
122
123
                    if (!empty($fileContent)) {
124
                        $content = json_decode($fileContent, true);
125
126
                        if ($type === 'session') {
127
                            $sort = $content['microtimestamp'] . '.' . $file->getFilename(); 
128
                        } else {
129
                            $sort = $file->getMTime() . '.' . $file->getFilename();
130
                        }
131
                        $results[$sort] = $content;
132
                    }
133
                }
134
            }
135
            unset($it, $files);
136
137
            // Sort by ascending timestamp (microtimestamp).
138
            ksort($results);
139
        }
140
141
        return $results;
142
    }
143
144
    /**
145
     * {@inheritDoc}
146
     * 
147
     * @param string $ip   The data id of the entry to fetch.
148
     * @param string $type The type of the data table.
149
     * 
150
     * @return array
151
     */
152
    protected function doFetch(string $ip, string $type = 'filter'): array
153
    {
154
        $results = [];
155
156
        if (
157
            !file_exists($this->getFilename($ip, $type)) || 
158
            !in_array($type, $this->tableTypes)
159
        ) {
160
            return $results;
161
        }
162
163
        $fileContent = file_get_contents($this->getFilename($ip, $type));
164
        $resultData = json_decode($fileContent, true);
165
166
        // rule | session
167
        if (is_array($resultData)) {
168
            $results = $resultData;
169
        }
170
171
        // filter 
172
        if (!empty($resultData['log_data'])) {
173
            return $resultData['log_data']; 
174
        }
175
176
        return $results;
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     * 
182
     * @param string $ip   The data id of the entry to check for.
183
     * @param string $type The type of the data table.
184
     *
185
     * @return bool
186
     */
187
    protected function checkExist(string $ip, string $type = 'filter'): bool
188
    {
189
        if (file_exists($this->getFilename($ip, $type))) {
190
            return true;
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * {@inheritDoc}
198
     * 
199
     * @param string $ip     The data id.
200
     * @param array  $data   The data.
201
     * @param string $type   The type of the data table.
202
     * @param int    $expire The data will be deleted after expiring.
203
     *
204
     * @return bool
205
     */
206
    protected function doSave(string $ip, array $data, string $type = 'filter', $expire = 0): bool
207
    {
208
        $logData = [];
209
210
        switch ($type) {
211
212
            case 'rule':
213
                $logData = $data;
214
                $logData['log_ip'] = $ip;
215
                break;
216
217
            case 'filter':
218
                $logData['log_ip'] = $ip;
219
                $logData['log_data'] = $data;
220
                break;
221
222
            case 'session':
223
                unset($data['parsed_data']);
224
                $logData = $data;
225
                break;
226
        }
227
228
        $result = file_put_contents($this->getFilename($ip, $type), json_encode($logData));
229
230
        // Update file time.
231
        touch($this->getFilename($ip, $type), time());
232
233
        return ($result > 0) ? true : false;
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     * 
239
     * @param string $ip   The key name of a redis entry.
240
     * @param string $type The type of the data table.
241
     * 
242
     * @return bool
243
     */
244
    protected function doDelete(string $ip, string $type = 'filter'): bool
245
    {
246
        if (in_array($type, ['rule', 'filter', 'session'])) {
247
            return $this->remove($this->getFilename($ip, $type));
248
        }
249
 
250
        return false;
251
    }
252
253
    /**
254
     * {@inheritDoc}
255
     * 
256
     * Rebuild data tables.
257
     *
258
     * @return bool
259
     */
260
    protected function doRebuild(): bool
261
    {
262
        // Those are Shieldon logs directories.
263
        $tables = [
264
            'filter'  => $this->getDirectory('filter'),
265
            'rule'    => $this->getDirectory('rule'),
266
            'session' => $this->getDirectory('session'),
267
        ];
268
269
        // Remove them recursively.
270
        foreach ($tables as $dir) {
271
            if (file_exists($dir)) {
272
                $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
273
                $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
274
    
275
                foreach ($files as $file) {
276
                    if ($file->isDir()) {
277
                        // @codeCoverageIgnoreStart
278
                        rmdir($file->getRealPath());
279
                        // @codeCoverageIgnoreEnd
280
                    } else {
281
                        unlink($file->getRealPath());
282
                    }
283
                }
284
                unset($it, $files);
285
286
                if (is_dir($dir)) {
287
                    rmdir($dir);
288
                }
289
            }
290
        }
291
292
        return $this->createDirectory();
293
    }
294
295
    /**
296
     * Remove a Shieldon log file.
297
     * Removing a log file works as the same as removing a SQL table's row.
298
     * 
299
     * @param string $logFilePath The absolute path of the log file.
300
     *
301
     * @return bool
302
     */
303
    private function remove(string $logFilePath): bool
304
    {
305
        if (file_exists($logFilePath)) {
306
            return unlink($logFilePath);
307
        }
308
        return false;
309
    }
310
}
311