Passed
Push — 3.0 ( 5e1ed3...5a5495 )
by Rubén
04:22
created

FileHandler::getFileSize()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2018, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Storage\File;
26
27
use SP\Util\Util;
28
29
/**
30
 * Class FileHandler
31
 *
32
 * @package SP\Storage\File;
33
 */
34
final class FileHandler
35
{
36
    const CHUNK_LENGTH = 8192;
37
    const CHUNK_FACTOR = 3;
38
    /**
39
     * @var string
40
     */
41
    protected $file;
42
    /**
43
     * @var resource
44
     */
45
    protected $handle;
46
    /**
47
     * @var bool
48
     */
49
    private $locked = false;
50
51
    /**
52
     * FileHandler constructor.
53
     *
54
     * @param string $file
55
     */
56
    public function __construct(string $file)
57
    {
58
        $this->file = $file;
59
    }
60
61
    /**
62
     * Writes data into file
63
     *
64
     * @param mixed $data
65
     *
66
     * @return FileHandler
67
     * @throws FileException
68
     */
69
    public function write($data)
70
    {
71
        if (!is_resource($this->handle)) {
72
            $this->open('wb');
73
        }
74
75
        if (@fwrite($this->handle, $data) === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->handle can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, 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

75
        if (@fwrite(/** @scrutinizer ignore-type */ $this->handle, $data) === false) {
Loading history...
76
            throw new FileException(sprintf(__('Unable to read/write the file (%s)'), $this->file));
77
        }
78
79
        return $this;
80
    }
81
82
    /**
83
     * Opens the file
84
     *
85
     * @param string $mode
86
     *
87
     * @param bool   $lock
88
     *
89
     * @return resource
90
     * @throws FileException
91
     */
92
    public function open($mode = 'r', $lock = false)
93
    {
94
        $this->handle = @fopen($this->file, $mode);
0 ignored issues
show
Documentation Bug introduced by
It seems like @fopen($this->file, $mode) can also be of type false. However, the property $handle is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
95
96
        if ($lock && $this->locked === false) {
97
            $this->lock();
98
        }
99
100
        if ($this->handle === false) {
101
            throw new FileException(sprintf(__('Unable to open the file (%s)'), $this->file));
102
        }
103
104
        return $this->handle;
105
    }
106
107
    /**
108
     * Lock the file
109
     *
110
     * @param int $mode
111
     *
112
     * @throws FileException
113
     */
114
    private function lock($mode = LOCK_EX)
115
    {
116
        $this->locked = flock($this->handle, $mode);
117
118
        if (!$this->locked) {
119
            throw new FileException(sprintf(__('Unable to obtain a lock (%s)'), $this->file));
120
        }
121
122
        logger(sprintf('File locked: %s', $this->file));
123
    }
124
125
    /**
126
     * Reads data from file into a string
127
     *
128
     * @return string Data read from file
129
     * @throws FileException
130
     */
131
    public function readToString(): string
132
    {
133
        if (($data = file_get_contents($this->file)) === false) {
134
            throw new FileException(sprintf(__('Unable to read from file (%s)'), $this->file));
135
        }
136
137
        return $data;
138
    }
139
140
    /**
141
     * Reads data from file into an array
142
     *
143
     * @throws FileException
144
     */
145
    public function readToArray(): array
146
    {
147
        if (($data = @file($this->file, FILE_SKIP_EMPTY_LINES)) === false) {
148
            throw new FileException(sprintf(__('Unable to read from file (%s)'), $this->file));
149
        }
150
151
        return $data;
152
    }
153
154
    /**
155
     * Reads data from file into a string
156
     *
157
     * @param string $data Data to write into file
158
     *
159
     * @return FileHandler
160
     * @throws FileException
161
     */
162
    public function save($data)
163
    {
164
        if (file_put_contents($this->file, $data, LOCK_EX) === false) {
165
            throw new FileException(sprintf(__('Unable to read/write the file (%s)'), $this->file));
166
        }
167
168
        return $this;
169
    }
170
171
    /**
172
     * Reads data from file
173
     *
174
     * @return string Data read from file
175
     * @throws FileException
176
     */
177
    public function read()
178
    {
179
        if (!is_resource($this->handle)) {
180
            $this->open('rb');
181
        }
182
183
        $data = '';
184
185
        while (!feof($this->handle)) {
0 ignored issues
show
Bug introduced by
It seems like $this->handle can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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

185
        while (!feof(/** @scrutinizer ignore-type */ $this->handle)) {
Loading history...
186
            $data .= fread($this->handle, self::CHUNK_LENGTH);
0 ignored issues
show
Bug introduced by
It seems like $this->handle can also be of type false; however, parameter $handle of fread() does only seem to accept resource, 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

186
            $data .= fread(/** @scrutinizer ignore-type */ $this->handle, self::CHUNK_LENGTH);
Loading history...
187
        }
188
189
        $this->close();
190
191
        return $data;
192
    }
193
194
    /**
195
     * Closes the file
196
     *
197
     * @throws FileException
198
     * @return FileHandler
199
     */
200
    public function close()
201
    {
202
        if ($this->locked) {
203
            $this->unlock();
204
        }
205
206
        if (!is_resource($this->handle) || @fclose($this->handle) === false) {
207
            throw new FileException(sprintf(__('Unable to close the file (%s)'), $this->file));
208
        }
209
210
        return $this;
211
    }
212
213
    /**
214
     * Unlock the file
215
     */
216
    private function unlock()
217
    {
218
        $this->locked = !flock($this->handle, LOCK_UN);
219
    }
220
221
    /**
222
     * @param callable $chunker
223
     * @param float    $rate
224
     *
225
     * @throws FileException
226
     */
227
    public function readChunked(callable $chunker = null, float $rate = null)
228
    {
229
        $maxRate = Util::getMaxDownloadChunk() / self::CHUNK_FACTOR;
230
231
        if ($rate === null || $rate > $maxRate) {
232
            $rate = $maxRate;
233
        }
234
235
        if (!is_resource($this->handle)) {
236
            $this->open('rb');
237
        }
238
239
        while (!feof($this->handle)) {
0 ignored issues
show
Bug introduced by
It seems like $this->handle can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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

239
        while (!feof(/** @scrutinizer ignore-type */ $this->handle)) {
Loading history...
240
            if ($chunker !== null) {
241
                $chunker(fread($this->handle, round($rate)));
0 ignored issues
show
Bug introduced by
It seems like $this->handle can also be of type false; however, parameter $handle of fread() does only seem to accept resource, 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

241
                $chunker(fread(/** @scrutinizer ignore-type */ $this->handle, round($rate)));
Loading history...
Bug introduced by
round($rate) of type double is incompatible with the type integer expected by parameter $length of fread(). ( Ignorable by Annotation )

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

241
                $chunker(fread($this->handle, /** @scrutinizer ignore-type */ round($rate)));
Loading history...
242
            } else {
243
                print fread($this->handle, round($rate));
244
                ob_flush();
245
                flush();
246
            }
247
        }
248
249
        $this->close();
250
    }
251
252
    /**
253
     * Checks if the file is writable
254
     *
255
     * @throws FileException
256
     * @return FileHandler
257
     */
258
    public function checkIsWritable()
259
    {
260
        if (!is_writable($this->file) && @touch($this->file) === false) {
261
            throw new FileException(sprintf(__('Unable to write in file (%s)'), $this->file));
262
        }
263
264
        return $this;
265
    }
266
267
    /**
268
     * Checks if the file exists
269
     *
270
     * @throws FileException
271
     * @return FileHandler
272
     */
273
    public function checkFileExists()
274
    {
275
        if (!file_exists($this->file)) {
276
            throw new FileException(sprintf(__('File not found (%s)'), $this->file));
277
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * @return string
284
     */
285
    public function getFile(): string
286
    {
287
        return $this->file;
288
    }
289
290
    /**
291
     * @param bool $isExceptionOnZero
292
     *
293
     * @return int
294
     * @throws FileException
295
     */
296
    public function getFileSize($isExceptionOnZero = false): int
297
    {
298
        $size = filesize($this->file);
299
300
        if ($size === false || ($isExceptionOnZero === true && $size === 0)) {
301
            throw new FileException(sprintf(__('Unable to read/write file (%s)'), $this->file));
302
        }
303
304
        return $size;
305
    }
306
307
    /**
308
     * Clears the stat cache for the given file
309
     *
310
     * @return FileHandler
311
     */
312
    public function clearCache()
313
    {
314
        clearstatcache(true, $this->file);
315
316
        return $this;
317
    }
318
319
    /**
320
     * Deletes a file
321
     *
322
     * @return FileHandler
323
     * @throws FileException
324
     */
325
    public function delete()
326
    {
327
        if (@unlink($this->file) === false) {
328
            throw new FileException(sprintf(__('Unable to delete file (%s)'), $this->file));
329
        }
330
331
        return $this;
332
    }
333
334
    /**
335
     * Returns the content type in MIME format
336
     *
337
     * @return string
338
     * @throws FileException
339
     */
340
    public function getFileType(): string
341
    {
342
        $this->checkIsReadable();
343
344
        return mime_content_type($this->file);
345
    }
346
347
    /**
348
     * Checks if the file is readable
349
     *
350
     * @throws FileException
351
     * @return FileHandler
352
     */
353
    public function checkIsReadable()
354
    {
355
        if (!is_readable($this->file)) {
356
            throw new FileException(sprintf(__('Unable to read/write file (%s)'), $this->file));
357
        }
358
359
        return $this;
360
    }
361
362
    /**
363
     * @return int
364
     * @throws FileException
365
     */
366
    public function getFileTime(): int
367
    {
368
        $this->checkIsReadable();
369
370
        return filemtime($this->file) ?: 0;
371
    }
372
}