Passed
Push — master ( 499ec4...24bb0d )
by Zaahid
03:12
created

PartStreamFilterManager::getContentStream()   B

Complexity

Conditions 9
Paths 5

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 15
nc 5
nop 3
dl 0
loc 21
ccs 16
cts 16
cp 1
crap 9
rs 8.0555
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 underlying content stream without filters
27
     *      applied
28
     */
29
    protected $stream;
30
31
    /**
32
     * @var StreamInterface the content stream after attaching transfer encoding
33
     *      streams to $stream.
34
     */
35
    protected $decodedStream;
36
37
    /**
38
     * @var StreamInterface the content stream after attaching charset streams
39
     *      to $binaryStream
40
     */
41
    protected $charsetStream;
42
43
    /**
44
     * @var array map of the active encoding filter on the current handle.
45
     */
46
    private $encoding = [
47
        'type' => null,
48
        'filter' => null
49
    ];
50
    
51
    /**
52
     * @var array map of the active charset filter on the current handle.
53
     */
54
    private $charset = [
55
        'from' => null,
56
        'to' => null,
57
        'filter' => null
58
    ];
59
60
    /**
61
     * @var StreamFactory used to apply psr7 stream decorators to the
62
     *      attached StreamInterface based on encoding.
63
     */
64
    private $streamFactory;
65
    
66
    /**
67
     * Sets up filter names used for stream_filter_append
68
     * 
69
     * @param StreamFactory $streamFactory
70
     */
71 8
    public function __construct(StreamFactory $streamFactory)
72
    {
73 8
        $this->streamFactory = $streamFactory;
74 8
    }
75
76
    /**
77
     * Sets the URL used to open the content resource handle.
78
     * 
79
     * The function also closes the currently attached handle if any.
80
     * 
81
     * @param StreamInterface $stream
82
     */
83 8
    public function setStream(StreamInterface $stream = null)
84
    {
85 8
        $this->stream = $stream;
86 8
        $this->decodedStream = null;
87 8
        $this->charsetStream = null;
88 8
    }
89
    
90
    /**
91
     * Returns true if the attached stream filter used for decoding the content
92
     * on the current handle is different from the one passed as an argument.
93
     * 
94
     * @param string $transferEncoding
95
     * @return boolean
96
     */
97 4
    private function isTransferEncodingFilterChanged($transferEncoding)
98
    {
99 4
        return ($transferEncoding !== $this->encoding['type']);
100
    }
101
    
102
    /**
103
     * Returns true if the attached stream filter used for charset conversion on
104
     * the current handle is different from the one needed based on the passed 
105
     * arguments.
106
     * 
107
     * @param string $fromCharset
108
     * @param string $toCharset
109
     * @return boolean
110
     */
111 2
    private function isCharsetFilterChanged($fromCharset, $toCharset)
112
    {
113 2
        return ($fromCharset !== $this->charset['from']
114 2
            || $toCharset !== $this->charset['to']);
115
    }
116
    
117
    /**
118
     * Attaches a decoding filter to the attached content handle, for the passed
119
     * $transferEncoding.
120
     * 
121
     * @param string $transferEncoding
122
     */
123 8
    protected function attachTransferEncodingFilter($transferEncoding)
124
    {
125 8
        if ($this->decodedStream !== null) {
126 8
            $this->encoding['type'] = $transferEncoding;
127 8
            $assign = null;
128 8
            switch ($transferEncoding) {
129 2
                case 'base64':
130 1
                    $assign = $this->streamFactory->newBase64Stream($this->decodedStream);
131 1
                    break;
132 2
                case 'x-uuencode':
133 3
                    $assign = $this->streamFactory->newUUStream($this->decodedStream);
134 3
                    break;
135 2
                case 'quoted-printable':
136 4
                    $assign = $this->streamFactory->newQuotedPrintableStream($this->decodedStream);
137 4
                    break;
138
            }
139 8
            if ($assign !== null) {
140 6
                $this->decodedStream = new CachingStream($assign);
141
            }
142
        }
143 8
    }
144
    
145
    /**
146
     * Attaches a charset conversion filter to the attached content handle, for
147
     * the passed arguments.
148
     * 
149
     * @param string $fromCharset the character set the content is encoded in
150
     * @param string $toCharset the target encoding to return
151
     */
152 3
    protected function attachCharsetFilter($fromCharset, $toCharset)
153
    {
154 3
        if ($this->charsetStream !== null) {
155 3
            $this->charsetStream = new CachingStream($this->streamFactory->newCharsetStream(
156 3
                $this->charsetStream,
157 3
                $fromCharset,
158 3
                $toCharset
159
            ));
160 3
            $this->charset['from'] = $fromCharset;
161 3
            $this->charset['to'] = $toCharset;
162
        }
163 3
    }
164
    
165
    /**
166
     * Resets just the charset stream, and rewinds the decodedStream.
167
     */
168 3
    private function resetCharsetStream()
169
    {
170 3
        $this->charset = [
171
            'from' => null,
172
            'to' => null,
173
            'filter' => null
174
        ];
175 3
        $this->decodedStream->rewind();
176 3
        $this->charsetStream = $this->decodedStream;
177 3
    }
178
179
    /**
180
     * Resets cached encoding and charset streams, and rewinds the stream.
181
     */
182 8
    public function reset()
183
    {
184 8
        $this->encoding = [
185
            'type' => null,
186
            'filter' => null
187
        ];
188 8
        $this->charset = [
189
            'from' => null,
190
            'to' => null,
191
            'filter' => null
192
        ];
193 8
        $this->stream->rewind();
194 8
        $this->decodedStream = $this->stream;
195 8
        $this->charsetStream = $this->stream;
196 8
    }
197
    
198
    /**
199
     * Checks what transfer-encoding decoder stream and charset conversion
200
     * stream are currently attached on the underlying stream, and resets them
201
     * if the requested arguments differ from the currently assigned ones.
202
     * 
203
     * @param string $transferEncoding
204
     * @param string $fromCharset the character set the content is encoded in
205
     * @param string $toCharset the target encoding to return
206
     * @return StreamInterface
207
     */
208 7
    public function getContentStream($transferEncoding, $fromCharset, $toCharset)
209
    {
210 7
        if ($this->stream === null) {
211 1
            return null;
212
        }
213 7
        if (empty($fromCharset) || empty($toCharset)) {
214 4
            return $this->getBinaryStream($transferEncoding);
215
        }
216 3
        if ($this->charsetStream === null
217 2
            || $this->isTransferEncodingFilterChanged($transferEncoding)
218 3
            || $this->isCharsetFilterChanged($fromCharset, $toCharset)) {
219 3
            if ($this->charsetStream === null
220 3
                || $this->isTransferEncodingFilterChanged($transferEncoding)) {
221 3
                $this->reset();
222 3
                $this->attachTransferEncodingFilter($transferEncoding);
223
            }
224 3
            $this->resetCharsetStream();
225 3
            $this->attachCharsetFilter($fromCharset, $toCharset);
226
        }
227 3
        $this->charsetStream->rewind();
228 3
        return $this->charsetStream;
229
    }
230
231
    /**
232
     * Checks what transfer-encoding decoder stream is attached on the
233
     * underlying stream, and resets it if the requested arguments differ.
234
     *
235
     * @param string $transferEncoding
236
     * @return StreamInterface
237
     */
238 5
    public function getBinaryStream($transferEncoding)
239
    {
240 5
        if ($this->stream === null) {
241
            return null;
242
        }
243 5
        if ($this->decodedStream === null
244 5
            || $this->isTransferEncodingFilterChanged($transferEncoding)) {
245 5
            $this->reset();
246 5
            $this->attachTransferEncodingFilter($transferEncoding);
247
        }
248 5
        $this->decodedStream->rewind();
249 5
        return $this->decodedStream;
250
    }
251
}
252