Passed
Branch master (25d5dd)
by Alexey
02:30
created

WinZipAesDecryptionStreamFilter::filter()   C

Complexity

Conditions 15
Paths 45

Size

Total Lines 87
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 15.1084

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 15
eloc 51
nc 45
nop 4
dl 0
loc 87
ccs 47
cts 51
cp 0.9216
crap 15.1084
rs 5.9166
c 1
b 0
f 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
4
5
use PhpZip\Exception\RuntimeException;
6
use PhpZip\Exception\ZipAuthenticationException;
7
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
8
use PhpZip\Model\ZipEntry;
9
10
/**
11
 * Decrypt WinZip AES stream.
12
 */
13
class WinZipAesDecryptionStreamFilter extends \php_user_filter
14
{
15
    const FILTER_NAME = 'phpzip.decryption.winzipaes';
16
17
    /** @var string */
18
    private $buffer;
19
20
    /** @var string */
21
    private $authenticationCode;
22
23
    /** @var int */
24
    private $encBlockPosition = 0;
25
26
    /** @var int */
27
    private $encBlockLength = 0;
28
29
    /** @var int */
30
    private $readLength = 0;
31
32
    /** @var ZipEntry */
33
    private $entry;
34
35
    /** @var WinZipAesContext|null */
36
    private $context;
37
38
    /**
39
     * @return bool
40
     */
41 8
    public static function register()
42
    {
43 8
        return stream_filter_register(self::FILTER_NAME, __CLASS__);
44
    }
45
46
    /**
47
     * @return bool
48
     *
49
     * @noinspection DuplicatedCode
50
     */
51 8
    public function onCreate()
52
    {
53 8
        if (!isset($this->params['entry'])) {
54
            return false;
55
        }
56
57 8
        if (!($this->params['entry'] instanceof ZipEntry)) {
58
            throw new \RuntimeException('ZipEntry expected');
59
        }
60 8
        $this->entry = $this->params['entry'];
61
62
        if (
63 8
            $this->entry->getPassword() === null ||
64 8
            !$this->entry->isEncrypted() ||
65 8
            !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
66
        ) {
67
            return false;
68
        }
69
70 8
        $this->buffer = '';
71
72 8
        return true;
73
    }
74
75
    /**
76
     * @param resource $in
77
     * @param resource $out
78
     * @param int      $consumed
79
     * @param bool     $closing
80
     *
81
     * @throws ZipAuthenticationException
82
     *
83
     * @return int
84
     */
85 8
    public function filter($in, $out, &$consumed, $closing)
86
    {
87 8
        while ($bucket = stream_bucket_make_writeable($in)) {
88 8
            $this->buffer .= $bucket->data;
89 8
            $this->readLength += $bucket->datalen;
90
91 8
            if ($this->readLength > $this->entry->getCompressedSize()) {
92 8
                $this->buffer = substr($this->buffer, 0, $this->entry->getCompressedSize() - $this->readLength);
93
            }
94
95
            // read header
96 8
            if ($this->context === null) {
97
                /**
98
                 * @var WinZipAesExtraField|null $winZipExtra
99
                 */
100 8
                $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
101
102 8
                if ($winZipExtra === null) {
103
                    throw new RuntimeException('$winZipExtra is null');
104
                }
105 8
                $saltSize = $winZipExtra->getSaltSize();
106 8
                $headerSize = $saltSize + WinZipAesContext::PASSWORD_VERIFIER_SIZE;
107
108 8
                if (\strlen($this->buffer) < $headerSize) {
109
                    return \PSFS_FEED_ME;
110
                }
111
112 8
                $salt = substr($this->buffer, 0, $saltSize);
113 8
                $passwordVerifier = substr($this->buffer, $saltSize, WinZipAesContext::PASSWORD_VERIFIER_SIZE);
114 8
                $password = $this->entry->getPassword();
115
116 8
                if ($password === null) {
117
                    throw new RuntimeException('$password is null');
118
                }
119 8
                $this->context = new WinZipAesContext($winZipExtra->getEncryptionStrength(), $password, $salt);
120 8
                unset($password);
121
122
                // Verify password.
123 8
                if ($passwordVerifier !== $this->context->getPasswordVerifier()) {
124 1
                    throw new ZipAuthenticationException('Invalid password');
125
                }
126
127 8
                $this->encBlockPosition = 0;
128 8
                $this->encBlockLength = $this->entry->getCompressedSize() - $headerSize - WinZipAesContext::FOOTER_SIZE;
129
130 8
                $this->buffer = substr($this->buffer, $headerSize);
131
            }
132
133
            // encrypt data
134 8
            $plainText = '';
135 8
            $offset = 0;
136 8
            $len = \strlen($this->buffer);
137 8
            $remaining = $this->encBlockLength - $this->encBlockPosition;
138
139 8
            if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
140
                return \PSFS_FEED_ME;
141
            }
142 8
            $limit = min($len, $remaining);
143
144 8
            if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
145 1
                $limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
146
            }
147
148 8
            while ($offset < $limit) {
149 8
                $this->context->updateIv();
150 8
                $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
151 8
                $data = substr($this->buffer, 0, $length);
152 8
                $plainText .= $this->context->decryption($data);
153 8
                $offset += $length;
154 8
                $this->buffer = substr($this->buffer, $length);
155
            }
156 8
            $this->encBlockPosition += $offset;
157
158
            if (
159 8
                $this->encBlockPosition === $this->encBlockLength &&
160 8
                \strlen($this->buffer) === WinZipAesContext::FOOTER_SIZE
161
            ) {
162 8
                $this->authenticationCode = $this->buffer;
163 8
                $this->buffer = '';
164
            }
165
166 8
            $bucket->data = $plainText;
167 8
            $consumed += $bucket->datalen;
168 8
            stream_bucket_append($out, $bucket);
169
        }
170
171 8
        return \PSFS_PASS_ON;
172
    }
173
174
    /**
175
     * @see http://php.net/manual/en/php-user-filter.onclose.php
176
     *
177
     * @throws ZipAuthenticationException
178
     */
179 8
    public function onClose()
180
    {
181 8
        $this->buffer = '';
182
183 8
        if ($this->context !== null) {
184 8
            $this->context->checkAuthCode($this->authenticationCode);
185
        }
186 8
    }
187
}
188