Passed
Push — 2.x ( 94f9e8...28b8a0 )
by Terry
01:49
created

FileDriver::doRebuild()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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