Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.00% covered (success)
96.00%
24 / 25
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
EncryptedFile
96.00% covered (success)
96.00%
24 / 25
75.00% covered (warning)
75.00%
3 / 4
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 putChunk
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 copyTo
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
 getChunk
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3/**
4 * Encrypted file handler class.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ\Attachment\Filesystem\File
11 * @author    Anatoliy Belsky <ab@php.net>
12 * @copyright 2009-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2009-08-21
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Attachment\Filesystem\File;
21
22use phpMyFAQ\Attachment\Filesystem\AbstractFile;
23use phpseclib3\Crypt\AES;
24
25/**
26 * Class Encrypted
27 *
28 * @package phpMyFAQ\Attachment\Filesystem\File
29 */
30class EncryptedFile extends AbstractFile
31{
32    /**
33     * Chunk delimiter.
34     */
35    private const string CHUNK_DELIMITER = 'ฒૐᥤ';
36
37    /**
38     * AES instance.
39     */
40    protected AES $aes;
41
42    public function __construct(string $filepath, string $mode, string $key)
43    {
44        $this->aes = new AES('cbc');
45        $this->aes->setKey($key);
46        $this->aes->setIV(substr(hash('sha256', $key, binary: true), offset: 0, length: 16));
47
48        parent::__construct($filepath, $mode);
49    }
50
51    public function putChunk(string $chunk): int|false
52    {
53        $content = $this->aes->encrypt($chunk) . self::CHUNK_DELIMITER;
54
55        return fwrite($this->handle, $content);
56    }
57
58    /**
59     * @throws FileException
60     */
61    public function copyTo(object|string $target): bool
62    {
63        $return = false;
64
65        if (is_string($target)) {
66            $target = new VanillaFile($target, AbstractFile::MODE_WRITE);
67        }
68
69        if (!$target instanceof AbstractFile) {
70            throw new FileException('copyTo() expects a file path or an AbstractFile instance.');
71        }
72
73        $target->setMode(AbstractFile::MODE_WRITE);
74
75        if ($target->isOk()) {
76            while (!$this->eof()) {
77                $target->putChunk($this->getChunk());
78            }
79
80            $return = true;
81        }
82
83        return $return;
84    }
85
86    public function getChunk(): string
87    {
88        $readEnd = false;
89        $chunk = '';
90        $chunkDelimLen = strlen(self::CHUNK_DELIMITER);
91
92        while (!$readEnd && !$this->eof()) {
93            $chunk .= (string) fread($this->handle, length: 1);
94            $readEnd = self::CHUNK_DELIMITER === substr($chunk, -$chunkDelimLen);
95        }
96
97        $chunk = substr($chunk, offset: 0, length: -$chunkDelimLen);
98
99        return $chunk === '' || $chunk === '0' ? '' : $this->aes->decrypt($chunk);
100    }
101}