Passed
Pull Request — master (#171)
by Zaahid
07:22 queued 03:34
created

MessagePartStream::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 1
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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\Stream;
8
9
use ZBateson\MailMimeParser\MailMimeParser;
10
use ZBateson\MailMimeParser\Header\HeaderConsts;
11
use ZBateson\MailMimeParser\Message\IMessagePart;
12
use ZBateson\MailMimeParser\Message\IMimePart;
13
use ZBateson\MailMimeParser\Stream\StreamFactory;
14
use GuzzleHttp\Psr7;
15
use GuzzleHttp\Psr7\AppendStream;
16
use GuzzleHttp\Psr7\StreamDecoratorTrait;
17
use Psr\Http\Message\StreamInterface;
18
use SplObserver;
19
use SplSubject;
20
21
/**
22
 * Provides a readable stream for a MessagePart.
23
 *
24
 * @author Zaahid Bateson
25
 */
26
class MessagePartStream implements StreamInterface, SplObserver
27
{
28
    use StreamDecoratorTrait;
29
30
    /**
31
     * @var StreamFactory For creating needed stream decorators.
32
     */
33
    protected $streamFactory;
34
35
    /**
36
     * @var IMessagePart The part to read from.
37
     */
38
    protected $part;
39
40
    protected $appendStream = null;
41
42
    /**
43
     * Constructor
44
     * 
45
     * @param StreamFactory $sdf
46
     * @param IMessagePart $part
47
     */
48 108
    public function __construct(StreamFactory $sdf, IMessagePart $part)
49
    {
50 108
        $this->streamFactory = $sdf;
51 108
        $this->part = $part;
52 108
        $part->attach($this);
53 108
    }
54
55 6
    public function __destruct()
56
    {
57 6
        if ($this->part !== null) {
58 6
            $this->part->detach($this);
59
        }
60 6
    }
61
62 94
    public function update(SplSubject $subject)
63
    {
64 94
        if ($this->appendStream !== null) {
65
            // unset forces recreation in StreamDecoratorTrait with a call to __get
66 3
            unset($this->stream);
67 3
            $this->appendStream = null;
68
        }
69 94
    }
70
71
    /**
72
     * Attaches and returns a CharsetStream decorator to the passed $stream.
73
     *
74
     * If the current attached IMessagePart doesn't specify a charset, $stream
75
     * is returned as-is.
76
     *
77
     * @param StreamInterface $stream
78
     * @return StreamInterface
79
     */
80 96
    private function getCharsetDecoratorForStream(StreamInterface $stream)
81
    {
82 96
        $charset = $this->part->getCharset();
83 96
        if (!empty($charset)) {
84 76
            $stream = $this->streamFactory->newCharsetStream(
85 76
                $stream,
86 76
                $charset,
87 76
                MailMimeParser::DEFAULT_CHARSET
88
            );
89
        }
90 96
        return $stream;
91
    }
92
    
93
    /**
94
     * Attaches and returns a transfer encoding stream decorator to the passed
95
     * $stream.
96
     *
97
     * The attached stream decorator is based on the attached part's returned
98
     * value from MessagePart::getContentTransferEncoding, using one of the
99
     * following stream decorators as appropriate:
100
     *
101
     * o QuotedPrintableStream
102
     * o Base64Stream
103
     * o UUStream
104
     *
105
     * @param StreamInterface $stream
106
     * @return StreamInterface
107
     */
108 96
    private function getTransferEncodingDecoratorForStream(StreamInterface $stream)
109
    {
110 96
        $encoding = $this->part->getContentTransferEncoding();
111 96
        $decorator = null;
112 96
        switch ($encoding) {
113 96
            case 'quoted-printable':
114 49
                $decorator = $this->streamFactory->newQuotedPrintableStream($stream);
115 49
                break;
116 81
            case 'base64':
117 40
                $decorator = $this->streamFactory->newBase64Stream(
118 40
                    $this->streamFactory->newChunkSplitStream($stream));
119 40
                break;
120 72
            case 'x-uuencode':
121 9
                $decorator = $this->streamFactory->newUUStream($stream);
122 9
                $decorator->setFilename($this->part->getFilename());
123 9
                break;
124
            default:
125 70
                return $stream;
126
        }
127 74
        return $decorator;
128
    }
129
130
    /**
131
     * Writes out the content portion of the attached mime part to the passed
132
     * $stream.
133
     *
134
     * @param StreamInterface $stream
135
     */
136 96
    private function writePartContentTo(StreamInterface $stream)
137
    {
138 96
        $contentStream = $this->part->getContentStream();
139 96
        if ($contentStream !== null) {
140 96
            $copyStream = $this->streamFactory->newNonClosingStream($stream);
141 96
            $es = $this->getTransferEncodingDecoratorForStream($copyStream);
142 96
            $cs = $this->getCharsetDecoratorForStream($es);
143 96
            Psr7\Utils::copyToStream($contentStream, $cs);
144 96
            $cs->close();
145
        }
146 96
    }
147
148
    /**
149
     * Creates an array of streams based on the attached part's mime boundary
150
     * and child streams.
151
     *
152
     * @param IMimePart $part passed in because $this->part is declared
153
     *        as IMessagePart
154
     * @return StreamInterface[]
155
     */
156 66
    protected function getBoundaryAndChildStreams(IMimePart $part)
157
    {
158 66
        $boundary = $part->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'boundary');
159 66
        if ($boundary === null) {
160 2
            return array_map(
161
                function ($child) {
162 2
                    return $child->getStream();
163 2
                },
164 2
                $part->getChildParts()
165
            );
166
        }
167 64
        $streams = [];
168 64
        foreach ($part->getChildParts() as $i => $child) {
169 64
            if ($i !== 0 || $part->hasContent()) {
170 64
                $streams[] = Psr7\Utils::streamFor("\r\n");
171
            }
172 64
            $streams[] = Psr7\Utils::streamFor("--$boundary\r\n");
173 64
            $streams[] = $child->getStream();
174
        }
175 64
        $streams[] = Psr7\Utils::streamFor("\r\n--$boundary--\r\n");
176
        
177 64
        return $streams;
178
    }
179
180
    /**
181
     * Returns an array of Psr7 Streams representing the attached part and it's
182
     * direct children.
183
     *
184
     * @return StreamInterface[]
185
     */
186 96
    protected function getStreamsArray()
187
    {
188 96
        $content = Psr7\Utils::streamFor();
189 96
        $this->writePartContentTo($content);
190 96
        $content->rewind();
191 96
        $streams = [ $this->streamFactory->newHeaderStream($this->part), $content ];
192
193 96
        if ($this->part instanceof IMimePart && $this->part->getChildCount() > 0) {
194 66
            $streams = array_merge($streams, $this->getBoundaryAndChildStreams($this->part));
195
        }
196
197 96
        return $streams;
198
    }
199
200
    /**
201
     * Creates the underlying stream lazily when required.
202
     *
203
     * @return StreamInterface
204
     */
205 96
    protected function createStream()
206
    {
207 96
        if ($this->appendStream === null) {
208 96
            $this->appendStream = new AppendStream($this->getStreamsArray());
209
        }
210 96
        return $this->appendStream;
211
    }
212
}
213