WinZipAesEncryptionStreamFilter   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 145
Duplicated Lines 0 %

Test Coverage

Coverage 81.36%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 67
dl 0
loc 145
ccs 48
cts 59
cp 0.8136
rs 10
c 1
b 0
f 1
wmc 20

3 Methods

Rating   Name   Duplication   Size   Complexity  
A onCreate() 0 24 6
C filter() 0 79 13
A register() 0 3 1
1
<?php
2
3
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
4
5
use PhpZip\Exception\RuntimeException;
6
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
7
use PhpZip\Model\ZipEntry;
8
9
/**
10
 * Encrypt WinZip AES stream.
11
 */
12
class WinZipAesEncryptionStreamFilter extends \php_user_filter
13
{
14
    const FILTER_NAME = 'phpzip.encryption.winzipaes';
15
16
    /** @var string */
17
    private $buffer;
18
19
    /** @var int */
20
    private $remaining = 0;
21
22
    /** @var ZipEntry */
23
    private $entry;
24
25
    /** @var int */
26
    private $size;
27
28
    /** @var WinZipAesContext|null */
29
    private $context;
30
31
    /**
32
     * @return bool
33
     */
34 9
    public static function register()
35
    {
36 9
        return stream_filter_register(self::FILTER_NAME, __CLASS__);
37
    }
38
39
    /**
40
     * @return bool
41
     *
42
     * @noinspection DuplicatedCode
43
     */
44 9
    public function onCreate()
45
    {
46 9
        if (!isset($this->params['entry'])) {
47
            return false;
48
        }
49
50 9
        if (!($this->params['entry'] instanceof ZipEntry)) {
51
            throw new \RuntimeException('ZipEntry expected');
52
        }
53 9
        $this->entry = $this->params['entry'];
54
55
        if (
56 9
            $this->entry->getPassword() === null ||
57 9
            !$this->entry->isEncrypted() ||
58 9
            !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
59
        ) {
60
            return false;
61
        }
62
63 9
        $this->size = (int) $this->params['size'];
64 9
        $this->context = null;
65 9
        $this->buffer = '';
66
67 9
        return true;
68
    }
69
70
    /**
71
     * @param resource $in
72
     * @param resource $out
73
     * @param int      $consumed
74
     * @param bool     $closing
75
     *
76
     * @return int
77
     */
78 9
    public function filter($in, $out, &$consumed, $closing)
79
    {
80 9
        while ($bucket = stream_bucket_make_writeable($in)) {
81 9
            $this->buffer .= $bucket->data;
82 9
            $this->remaining += $bucket->datalen;
83
84 9
            if ($this->remaining > $this->size) {
85
                $this->buffer = substr($this->buffer, 0, $this->size - $this->remaining);
86
                $this->remaining = $this->size;
87
            }
88
89 9
            $encryptionText = '';
90
91
            // write header
92 9
            if ($this->context === null) {
93
                /**
94
                 * @var WinZipAesExtraField|null $winZipExtra
95
                 */
96 9
                $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
97
98 9
                if ($winZipExtra === null) {
99
                    throw new RuntimeException('$winZipExtra is null');
100
                }
101 9
                $saltSize = $winZipExtra->getSaltSize();
102
103
                try {
104 9
                    $salt = random_bytes($saltSize);
105
                } catch (\Exception $e) {
106
                    throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
107
                }
108 9
                $password = $this->entry->getPassword();
109
110 9
                if ($password === null) {
111
                    throw new RuntimeException('$password is null');
112
                }
113 9
                $this->context = new WinZipAesContext(
114 9
                    $winZipExtra->getEncryptionStrength(),
115
                    $password,
116
                    $salt
117
                );
118
119 9
                $encryptionText .= $salt . $this->context->getPasswordVerifier();
120
            }
121
122
            // encrypt data
123 9
            $offset = 0;
124 9
            $len = \strlen($this->buffer);
125 9
            $remaining = $this->remaining - $this->size;
126
127 9
            if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
128
                return \PSFS_FEED_ME;
129
            }
130 9
            $limit = max($len, $remaining);
131
132 9
            if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
133
                $limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
134
            }
135
136 9
            while ($offset < $limit) {
137 9
                $this->context->updateIv();
138 9
                $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
139 9
                $encryptionText .= $this->context->encrypt(
140 9
                    substr($this->buffer, 0, $length)
141
                );
142 9
                $offset += $length;
143 9
                $this->buffer = substr($this->buffer, $length);
144
            }
145
146 9
            if ($remaining === 0) {
147 9
                $encryptionText .= $this->context->getHmac();
148
            }
149
150 9
            $bucket->data = $encryptionText;
151 9
            $consumed += $bucket->datalen;
152
153 9
            stream_bucket_append($out, $bucket);
154
        }
155
156 9
        return \PSFS_PASS_ON;
157
    }
158
}
159