Passed
Push — master ( 9e6d2f...c6b62f )
by Zaahid
06:54
created

attachTransferEncodingFilter()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 9
nop 1
dl 0
loc 18
ccs 15
cts 15
cp 1
crap 6
rs 9.2222
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
use Psr\Http\Message\StreamInterface;
10
use GuzzleHttp\Psr7\CachingStream;
11
use ZBateson\MailMimeParser\Stream\StreamFactory;
12
13
/**
14
 * Manages attached stream filters for a MessagePart's content resource handle.
15
 * 
16
 * The attached stream filters are:
17
 *  o Content-Transfer-Encoding filter to manage decoding from a supported
18
 *    encoding: quoted-printable, base64 and x-uuencode.
19
 *  o Charset conversion filter to convert to UTF-8
20
 *
21
 * @author Zaahid Bateson
22
 */
23
class PartStreamFilterManager
24
{
25
    /**
26
     * @var StreamInterface the content stream after attaching encoding filters
27
     */
28
    protected $filteredStream;
29
    
30
    /**
31
     * @var StreamInterface the underlying content stream without filters
32
     *      applied
33
     */
34
    protected $stream;
35
    
36
    /**
37
     * @var array map of the active encoding filter on the current handle.
38
     */
39
    private $encoding = [
40
        'type' => null,
41
        'filter' => null
42
    ];
43
    
44
    /**
45
     * @var array map of the active charset filter on the current handle.
46
     */
47
    private $charset = [
48
        'from' => null,
49
        'to' => null,
50
        'filter' => null
51
    ];
52
53
    /**
54
     * @var StreamFactory used to apply psr7 stream decorators to the
55
     *      attached StreamInterface based on encoding.
56
     */
57
    private $streamFactory;
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
     * @param StreamFactory $streamFactory
68
     */
69 7
    public function __construct(StreamFactory $streamFactory)
70
    {
71 7
        $this->streamFactory = $streamFactory;
72 7
        $this->charsetConversionFilter = '';
73 7
    }
74
75
    /**
76
     * Sets the URL used to open the content resource handle.
77
     * 
78
     * The function also closes the currently attached handle if any.
79
     * 
80
     * @param StreamInterface $stream
81
     */
82 7
    public function setStream(StreamInterface $stream = null)
83
    {
84 7
        $this->stream = $stream;
85 7
        $this->filteredStream = null;
86 7
    }
87
    
88
    /**
89
     * Returns true if the attached stream filter used for decoding the content
90
     * on the current handle is different from the one passed as an argument.
91
     * 
92
     * @param string $transferEncoding
93
     * @return boolean
94
     */
95 3
    private function isTransferEncodingFilterChanged($transferEncoding)
96
    {
97 3
        return ($transferEncoding !== $this->encoding['type']);
98
    }
99
    
100
    /**
101
     * Returns true if the attached stream filter used for charset conversion on
102
     * the current handle is different from the one needed based on the passed 
103
     * arguments.
104
     * 
105
     * @param string $fromCharset
106
     * @param string $toCharset
107
     * @return boolean
108
     */
109 3
    private function isCharsetFilterChanged($fromCharset, $toCharset)
110
    {
111 3
        return ($fromCharset !== $this->charset['from']
112 3
            || $toCharset !== $this->charset['to']);
113
    }
114
    
115
    /**
116
     * Attaches a decoding filter to the attached content handle, for the passed
117
     * $transferEncoding.
118
     * 
119
     * @param string $transferEncoding
120
     */
121 7
    protected function attachTransferEncodingFilter($transferEncoding)
122
    {
123 7
        if ($this->filteredStream !== null) {
124 7
            $this->encoding['type'] = $transferEncoding;
125 7
            $assign = null;
126
            switch ($transferEncoding) {
127 7
                case 'base64':
128 1
                    $assign = $this->streamFactory->newBase64Stream($this->filteredStream);
129 1
                    break;
130 6
                case 'x-uuencode':
131 2
                    $assign = $this->streamFactory->newUUStream($this->filteredStream);
132 2
                    break;
133 5
                case 'quoted-printable':
134 3
                    $assign = $this->streamFactory->newQuotedPrintableStream($this->filteredStream);
135 3
                    break;
136
            }
137 7
            if ($assign !== null) {
138 5
                $this->filteredStream = new CachingStream($assign);
139
            }
140
        }
141 7
    }
142
    
143
    /**
144
     * Attaches a charset conversion filter to the attached content handle, for
145
     * the passed arguments.
146
     * 
147
     * @param string $fromCharset the character set the content is encoded in
148
     * @param string $toCharset the target encoding to return
149
     */
150 7
    protected function attachCharsetFilter($fromCharset, $toCharset)
151
    {
152 7
        if ($this->filteredStream !== null) {
153 7
            if (!empty($fromCharset) && !empty($toCharset)) {
154 3
                $this->filteredStream = new CachingStream($this->streamFactory->newCharsetStream(
155 3
                    $this->filteredStream,
156 3
                    $fromCharset,
157 3
                    $toCharset
158
                ));
159
            }
160 7
            $this->charset['from'] = $fromCharset;
161 7
            $this->charset['to'] = $toCharset;
162
        }
163 7
    }
164
    
165
    /**
166
     * Closes the attached resource handle, resets mapped encoding and charset
167
     * filters, and reopens the handle seeking back to the current position.
168
     * 
169
     * Note that closing/reopening is done because of the following differences
170
     * discovered between hhvm (up to 3.18 at least) and php:
171
     * 
172
     *  o stream_filter_remove wasn't triggering php_user_filter's onClose
173
     *    callback
174
     *  o read operations performed after stream_filter_remove weren't calling
175
     *    filter on php_user_filter
176
     * 
177
     * It seems stream_filter_remove doesn't work on hhvm, or isn't implemented
178
     * in the same way -- so closing and reopening seems to solve that.
179
     */
180 7
    public function reset()
181
    {
182 7
        $this->encoding = [
183
            'type' => null,
184
            'filter' => null
185
        ];
186 7
        $this->charset = [
187
            'from' => null,
188
            'to' => null,
189
            'filter' => null
190
        ];
191 7
        $this->stream->rewind();
192 7
        $this->filteredStream = $this->stream;
193 7
    }
194
    
195
    /**
196
     * Checks what transfer-encoding decoder filters and charset conversion
197
     * filters are attached on the handle, closing/reopening the handle if
198
     * different, before attaching relevant filters for the passed
199
     * $transferEncoding and charset arguments, and returning a StreamInterface.
200
     * 
201
     * @param string $transferEncoding
202
     * @param string $fromCharset the character set the content is encoded in
203
     * @param string $toCharset the target encoding to return
204
     * @return StreamInterface
205
     */
206 7
    public function getContentStream($transferEncoding, $fromCharset, $toCharset)
207
    {
208 7
        if ($this->stream === null) {
209
            return null;
210
        }
211 7
        if ($this->filteredStream === null
212 3
            || $this->isTransferEncodingFilterChanged($transferEncoding)
213 7
            || $this->isCharsetFilterChanged($fromCharset, $toCharset)) {
214 7
            $this->reset();
215 7
            $this->attachTransferEncodingFilter($transferEncoding);
216 7
            $this->attachCharsetFilter($fromCharset, $toCharset);
217
        }
218 7
        $this->filteredStream->rewind();
219 7
        return $this->filteredStream;
220
    }
221
}
222