Lines 93.84% 61 / 65
Methods 75.00% 12 / 16
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 4 / 4 100.00% 1 / 1 1
 putChunk 100.00% 2 / 2 100.00% 1 / 1 1
 copyTo 90.90% 10 / 11 0.00% 0 / 1 5.02
 getChunk 100.00% 8 / 8 100.00% 1 / 1 5
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] __destruct 100.00% 2 / 2 100.00% 1 / 1 2
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] eof 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] delete 83.33% 5 / 6 0.00% 0 / 1 5.12
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] getMode 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] setMode 100.00% 6 / 6 100.00% 1 / 1 2
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] copyToSimple 66.66% 2 / 3 0.00% 0 / 1 2.15
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] isOk 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractFile] deleteDir 93.75% 15 / 16 0.00% 0 / 1 9.02
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] moveTo 100.00% 1 / 1 100.00% 1 / 1 2
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] isEncrypted 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] getPath 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] __toString 100.00% 1 / 1 100.00% 1 / 1 1
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}

Inherited from phpMyFAQ\Attachment\Filesystem\AbstractFile

67    public function __destruct()
68    {
69        if (is_resource($this->handle)) {
70            fclose($this->handle);
71        }
72    }
77    public function eof(): bool
78    {
79        return feof($this->handle);
80    }
100    public function delete(): bool
101    {
102        if ($this->handle) {
103            fclose($this->handle);
104        }
105
106        // Read the uploaded temp path directly from $_FILES instead of
107        // rebuilding a Symfony Request. Request::createFromGlobals() eagerly
108        // constructs an UploadedFile with path validation, which throws a
109        // FileNotFoundException here because move_uploaded_file() has already
110        // moved the temp file away during the preceding copyTo() call.
111        $uploadedTmpName = $_FILES['userfile']['tmp_name'] ?? null;
112        if (is_string($uploadedTmpName) && $this->path !== $uploadedTmpName && file_exists($this->path)) {
113            return $this->deleteDir(dirname($this->path));
114        }
115
116        return true;
117    }
122    public function getMode(): string
123    {
124        return $this->mode;
125    }
132    public function setMode($mode): bool
133    {
134        $retval = false;
135
136        if (in_array($mode, [self::MODE_WRITE, self::MODE_READ, self::MODE_APPEND], strict: true)) {
137            fclose($this->handle);
138            $this->handle = fopen($this->path, $mode);
139
140            $retval = is_resource($this->handle);
141        }
142
143        return $retval;
144    }
151    public function copyToSimple($target): bool
152    {
153        if (is_uploaded_file($this->path)) {
154            return move_uploaded_file($this->path, $target);
155        }
156
157        return copy($this->path, $target);
158    }
163    public function isOk(): bool
164    {
165        return is_resource($this->handle);
166    }
175    public function deleteDir($path): bool
176    {
177        if (!file_exists($path)) {
178            throw new FileException(sprintf("Directory %s doesn't exist.", $path));
179        }
180
181        $it = new RecursiveIteratorIterator(
182            new RecursiveDirectoryIterator($path),
183            RecursiveIteratorIterator::CHILD_FIRST,
184        );
185
186        foreach ($it as $file) {
187            if (!$file instanceof \SplFileInfo || in_array($file->getBasename(), ['.', '..'], strict: true)) {
188                continue;
189            }
190
191            if ($file->isDir()) {
192                rmdir($file->getPathname());
193            }
194
195            if ($file->isFile() || $file->isLink()) {
196                if (!is_writable($file->getPathname())) {
197                    throw new FileException("File can't be deleted.");
198                }
199
200                unlink($file->getPathname());
201            }
202        }
203
204        return rmdir($path);
205    }

Inherited from phpMyFAQ\Attachment\Filesystem\AbstractEntry

46    public function moveTo(object|string $entry): bool
47    {
48        return $this->copyTo($entry) && $this->delete();
49    }
66    public function isEncrypted(): bool
67    {
68        return false;
69    }
74    public function getPath(): string
75    {
76        return $this->path;
77    }
82    public function __toString(): string
83    {
84        return $this->path;
85    }