Passed
Push — master ( 3b7697...25d5dd )
by Alexey
03:48 queued 29s
created

PKEncryptionStreamFilter::onCreate()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 44
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 25
nc 12
nop 0
dl 0
loc 44
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
namespace PhpZip\IO\Filter\Cipher\Pkware;
4
5
use PhpZip\Exception\RuntimeException;
6
use PhpZip\Model\ZipEntry;
7
8
/**
9
 * Encryption PKWARE Traditional Encryption.
10
 */
11
class PKEncryptionStreamFilter extends \php_user_filter
12
{
13
    const FILTER_NAME = 'phpzip.encryption.pkware';
14
15
    /** @var int */
16
    private $size;
17
18
    /** @var string */
19
    private $headerBytes;
20
21
    /** @var int */
22
    private $writeLength;
23
24
    /** @var bool */
25
    private $writeHeader;
26
27
    /** @var PKCryptContext */
28
    private $context;
29
30
    /**
31
     * @return bool
32
     */
33
    public static function register()
34
    {
35
        return stream_filter_register(self::FILTER_NAME, __CLASS__);
36
    }
37
38
    /**
39
     * @see https://php.net/manual/en/php-user-filter.oncreate.php
40
     *
41
     * @return bool
42
     */
43
    public function onCreate()
44
    {
45
        if (\PHP_INT_SIZE === 4) {
46
            throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
47
        }
48
49
        if (!isset($this->params['entry'], $this->params['size'])) {
50
            return false;
51
        }
52
53
        if (!($this->params['entry'] instanceof ZipEntry)) {
54
            throw new \RuntimeException('ZipEntry expected');
55
        }
56
        /** @var ZipEntry $entry */
57
        $entry = $this->params['entry'];
58
        $password = $entry->getPassword();
59
60
        if ($password === null) {
61
            return false;
62
        }
63
64
        $this->size = (int) $this->params['size'];
65
66
        // init keys
67
        $this->context = new PKCryptContext($password);
68
69
        $crc = $entry->isDataDescriptorRequired() || $entry->getCrc() === ZipEntry::UNKNOWN ?
70
            ($entry->getDosTime() & 0x0000ffff) << 16 :
71
            $entry->getCrc();
72
73
        try {
74
            $headerBytes = random_bytes(PKCryptContext::STD_DEC_HDR_SIZE);
75
        } catch (\Exception $e) {
76
            throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
77
        }
78
79
        $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
80
        $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
81
82
        $this->headerBytes = $headerBytes;
83
        $this->writeLength = 0;
84
        $this->writeHeader = false;
85
86
        return true;
87
    }
88
89
    /**
90
     * Encryption filter.
91
     *
92
     * @param resource $in
93
     * @param resource $out
94
     * @param int      $consumed
95
     * @param bool     $closing
96
     *
97
     * @return int
98
     *
99
     * @todo USE FFI in php 7.4
100
     */
101
    public function filter($in, $out, &$consumed, $closing)
102
    {
103
        while ($bucket = stream_bucket_make_writeable($in)) {
104
            $buffer = $bucket->data;
105
            $this->writeLength += $bucket->datalen;
106
107
            if ($this->writeLength > $this->size) {
108
                $buffer = substr($buffer, 0, $this->size - $this->writeLength);
109
            }
110
111
            $data = '';
112
113
            if (!$this->writeHeader) {
114
                $data .= $this->context->encryptString($this->headerBytes);
115
                $this->writeHeader = true;
116
            }
117
118
            $data .= $this->context->encryptString($buffer);
119
120
            $bucket->data = $data;
121
122
            $consumed += $bucket->datalen;
123
            stream_bucket_append($out, $bucket);
124
        }
125
126
        return \PSFS_PASS_ON;
127
    }
128
}
129