Test Failed
Push — 1.0.0 ( d3c3e6...4505d9 )
by Zaahid
04:05
created

PartStreamFilterManager::setHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the ZBateson\MailMimeParser project.
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace ZBateson\MailMimeParser\Message\Part;
8
9
/**
10
 * Manages attached stream filters for a MessagePart's content resource handle.
11
 * 
12
 * The attached stream filters are:
13
 *  o Content-Transfer-Encoding filter to manage decoding from a supported
14
 *    encoding: quoted-printable, base64 and x-uuencode.
15
 *  o Charset conversion filter to convert to UTF-8
16
 *
17
 * @author Zaahid Bateson
18
 */
19
class PartStreamFilterManager
20
{
21
    /**
22
     * @var handle current opened handle if any
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\Message\Part\handle was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
     */
24
    protected $filteredHandle;
25
    
26
    /**
27
     * @var string the URL to open the content stream
28
     */
29
    protected $handle;
30
    
31
    /**
32
     * @var array map of the active encoding filter on the current handle.
33
     */
34
    private $encoding = [
35
        'type' => null,
36
        'filter' => null
37
    ];
38
    
39
    /**
40
     * @var array map of the active charset filter on the current handle.
41
     */
42
    private $charset = [
43
        'from' => null,
44
        'to' => null,
45
        'filter' => null
46
    ];
47
48
    /**
49
     * @var type 
0 ignored issues
show
Bug introduced by
The type ZBateson\MailMimeParser\Message\Part\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
     */
51
    private $streamDecoratorFactory;
52
53
    /**
54
     * @var array mapping Content-Transfer-Encoding header values to available
55
     *      stream filters.
56
     */
57
    private $encodingEncoderMap = [];
58
    
59
    /**
60
     * @var string name of stream filter handling character set conversion
61
     */
62
    private $charsetConversionFilter;
63
    
64
    /**
65
     * Sets up filter names used for stream_filter_append
66
     * 
67 9
     * @param string $quotedPrintableDecodeFilter
68
     * @param string $base64DecodeFilter
69
     * @param string $uudecodeFilter
70
     * @param string $charsetConversionFilter
71
     */
72
    public function __construct($streamDecoratorFactory)
73 9
    {
74 9
        $this->streamDecoratorFactory = $streamDecoratorFactory;
75 9
        $this->encodingEncoderMap = [
76
            'quoted-printable' => '',
77 9
            'base64' => '',
78 9
            'x-uuencode' => ''
79 9
        ];
80
        $this->charsetConversionFilter = '';
81
    }
82
    
83
    /**
84
     * Closes the contentHandle if one is attached.
85
     */
86
    public function __destruct()
87
    {
88
        if (is_resource($this->handle)) {
0 ignored issues
show
introduced by
The condition is_resource($this->handle) can never be true.
Loading history...
89
            fclose($this->handle);
90
        }
91
    }
92
    
93
    /**
94
     * Sets the URL used to open the content resource handle.
95
     * 
96
     * The function also closes the currently attached handle if any.
97
     * 
98
     * @param resource $handle
99 9
     */
100
    public function setHandle($handle)
101 9
    {
102 9
        if (is_resource($this->handle)) {
0 ignored issues
show
introduced by
The condition is_resource($this->handle) can never be true.
Loading history...
103 1
            fclose($this->handle);
104 1
        }
105 1
        $this->handle = $handle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handle of type resource is incompatible with the declared type string of property $handle.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
106 9
        $this->filteredHandle = null;
107
    }
108
    
109
    /**
110
     * Returns true if the attached stream filter used for decoding the content
111
     * on the current handle is different from the one passed as an argument.
112
     * 
113
     * @param string $transferEncoding
114
     * @return boolean
115 4
     */
116
    private function isTransferEncodingFilterChanged($transferEncoding)
117 4
    {
118
        return ($transferEncoding !== $this->encoding['type']);
119
    }
120
    
121
    /**
122
     * Returns true if the attached stream filter used for charset conversion on
123
     * the current handle is different from the one needed based on the passed 
124
     * arguments.
125
     * 
126
     * @param string $fromCharset
127
     * @param string $toCharset
128
     * @return boolean
129 3
     */
130
    private function isCharsetFilterChanged($fromCharset, $toCharset)
131 3
    {
132 3
        return ($fromCharset !== $this->charset['from']
133
            || $toCharset !== $this->charset['to']);
134
    }
135
    
136
    /**
137
     * Attaches a decoding filter to the attached content handle, for the passed
138
     * $transferEncoding.
139
     * 
140
     * @param string $transferEncoding
141 9
     */
142
    protected function attachTransferEncodingFilter($transferEncoding)
143 9
    {
144 9
        if ($this->filteredHandle !== null) {
145 7
            if (!empty($transferEncoding) && isset($this->encodingEncoderMap[$transferEncoding])) {
146 7
                if ($transferEncoding === 'base64') {
147 7
                    $this->filteredHandle = $this->streamDecoratorFactory->newBase64StreamDecorator($this->filteredHandle);
148
                    $this->encoding['type'] = $transferEncoding;
149 7
                    return;
150 7
                } elseif ($transferEncoding === 'x-uuencode') {
151 9
                    $this->filteredHandle = $this->streamDecoratorFactory->newUUStreamDecorator($this->filteredHandle);
152 9
                    $this->encoding['type'] = $transferEncoding;
153 9
                    return;
154
                } elseif ($transferEncoding === 'quoted-printable') {
155
                    $this->filteredHandle = $this->streamDecoratorFactory->newQuotedPrintableStreamDecorator($this->filteredHandle);
156
                    $this->encoding['type'] = $transferEncoding;
157
                    return;
158
                }
159
            }
160
            $this->encoding['type'] = $transferEncoding;
161
        }
162 9
    }
163
    
164 9
    /**
165 9
     * Attaches a charset conversion filter to the attached content handle, for
166 5
     * the passed arguments.
167 5
     * 
168 5
     * @param string $fromCharset the character set the content is encoded in
169 5
     * @param string $toCharset the target encoding to return
170 5
     */
171 5
    protected function attachCharsetFilter($fromCharset, $toCharset)
172 5
    {
173 9
        if ($this->filteredHandle !== null) {
174 9
            if (!empty($fromCharset) && !empty($toCharset)) {
175 9
                $this->filteredHandle = $this->streamDecoratorFactory->newCharsetStreamDecorator(
176 9
                    $this->filteredHandle,
177
                    $fromCharset,
178
                    $toCharset
179
                );
180
            }
181
            $this->charset['from'] = $fromCharset;
182
            $this->charset['to'] = $toCharset;
183
        }
184
    }
185
    
186
    /**
187
     * Closes the attached resource handle, resets mapped encoding and charset
188
     * filters, and reopens the handle seeking back to the current position.
189
     * 
190
     * Note that closing/reopening is done because of the following differences
191
     * discovered between hhvm (up to 3.18 at least) and php:
192
     * 
193 9
     *  o stream_filter_remove wasn't triggering php_user_filter's onClose
194
     *    callback
195 9
     *  o read operations performed after stream_filter_remove weren't calling
196 9
     *    filter on php_user_filter
197 3
     * 
198 3
     * It seems stream_filter_remove doesn't work on hhvm, or isn't implemented
199 3
     * in the same way -- so closing and reopening seems to solve that.
200 3
     */
201 9
    public function reset()
202 9
    {
203
        $pos = 0;
204 9
        if (is_resource($this->filteredHandle)) {
0 ignored issues
show
introduced by
The condition is_resource($this->filteredHandle) can never be true.
Loading history...
205 9
            $pos = ftell($this->handle);
206 9
            $this->filteredHandle = null;
207 9
        }
208
        $this->encoding = [
209 9
            'type' => null,
210 9
            'filter' => null
211 9
        ];
212 9
        $this->charset = [
213 9
            'from' => null,
214 9
            'to' => null,
215
            'filter' => null
216
        ];
217
        if (is_resource($this->handle)) {
0 ignored issues
show
introduced by
The condition is_resource($this->handle) can never be true.
Loading history...
218
            $this->filteredHandle = $this->handle;
219
            fseek($this->filteredHandle, $pos);
220
        }
221
    }
222
    
223
    /**
224
     * Checks what transfer-encoding decoder filters and charset conversion
225
     * filters are attached on the handle, closing/reopening the handle if
226 9
     * different, before attaching relevant filters for the passed
227
     * $transferEncoding and charset arguments, and returning a resource handle.
228 9
     * 
229 9
     * @param string $transferEncoding
230 9
     * @param string $fromCharset the character set the content is encoded in
231 9
     * @param string $toCharset the target encoding to return
232 9
     */
233 9
    public function getContentHandle($transferEncoding, $fromCharset, $toCharset)
234 9
    {
235 9
        if (!is_resource($this->filteredHandle)
0 ignored issues
show
introduced by
The condition ! is_resource($this->fil...romCharset, $toCharset) can never be false.
Loading history...
236
            || $this->isTransferEncodingFilterChanged($transferEncoding)
237
            || $this->isCharsetFilterChanged($fromCharset, $toCharset)) {
238
            $this->reset();
239
            $this->attachTransferEncodingFilter($transferEncoding);
240
            $this->attachCharsetFilter($fromCharset, $toCharset);
241
        }
242
        return $this->filteredHandle;
243
    }
244
}
245