Test Failed
Push — master ( e258e4...a626ba )
by Zaahid
15:25
created

getTransferEncodingDecoratorForStream()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 21
ccs 17
cts 17
cp 1
rs 9.7
cc 4
nc 4
nop 1
crap 4
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
8
namespace ZBateson\MailMimeParser\Stream;
9
10
use GuzzleHttp\Psr7;
11
use GuzzleHttp\Psr7\AppendStream;
12
use Psr\Http\Message\StreamInterface;
13
use SplObserver;
14
use SplSubject;
15
use ZBateson\MailMimeParser\Header\HeaderConsts;
16
use ZBateson\MailMimeParser\MailMimeParser;
17
use ZBateson\MailMimeParser\Message\IMessagePart;
18
use ZBateson\MailMimeParser\Message\IMimePart;
19
use ZBateson\MbWrapper\UnsupportedCharsetException;
20
21
/**
22
 * Provides a readable stream for a MessagePart.
23
 *
24
 * @author Zaahid Bateson
25
 */
26
class MessagePartStream extends MessagePartStreamDecorator implements SplObserver, StreamInterface
27
{
28
    /**
29
     * @var StreamFactory For creating needed stream decorators.
30
     */
31
    protected StreamFactory $streamFactory;
32
33
    /**
34
     * @var IMessagePart The part to read from.
35
     */
36
    protected IMessagePart $part;
37
38
    /**
39
     * @var bool if false, saving a content stream with an unsupported charset
40
     *      will be written in the default charset, otherwise the stream will be
41
     *      created with the unsupported charset, and an exception will be
42
     *      thrown when read from.
43
     */
44
    protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets;
45
46
    /**
47
     * @var ?AppendStream
48
     */
49
    protected ?AppendStream $appendStream = null;
50
51 109
    public function __construct(StreamFactory $sdf, IMessagePart $part, bool $throwExceptionReadingPartContentFromUnsupportedCharsets)
52
    {
53 109
        parent::__construct($part);
54 109
        $this->streamFactory = $sdf;
55 109
        $this->part = $part;
56
        $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets;
57
        $part->attach($this);
58
59 109
        // unsetting the property forces the first access to go through
60
        // __get().
61
        unset($this->stream);
62 5
    }
63
64 5
    public function __destruct()
65
    {
66
        $this->part->detach($this);
67 95
    }
68
69 95
    public function update(SplSubject $subject) : void
70
    {
71 3
        if ($this->appendStream !== null) {
72 3
            // unset forces recreation in StreamDecoratorTrait with a call to __get
73
            unset($this->stream);
74
            $this->appendStream = null;
75
        }
76
    }
77
78
    /**
79
     * Attaches and returns a CharsetStream decorator to the passed $stream.
80
     *
81
     * If the current attached IMessagePart doesn't specify a charset, $stream
82
     * is returned as-is.
83 97
     */
84
    private function getCharsetDecoratorForStream(StreamInterface $stream) : StreamInterface
85 97
    {
86 97
        $charset = $this->part->getCharset();
87 76
        if (!empty($charset)) {
88 76
            if (!$this->throwExceptionReadingPartContentFromUnsupportedCharsets) {
89 76
                $test = $this->streamFactory->newCharsetStream(
90 76
                    Psr7\Utils::streamFor(),
91 76
                    $charset,
92
                    MailMimeParser::DEFAULT_CHARSET
93 97
                );
94
                try {
95
                    $test->write('t');
96
                } catch (UnsupportedCharsetException $e) {
97
                    return $stream;
98
                } finally {
99
                    $test->close();
100
                }
101
            }
102
            $stream = $this->streamFactory->newCharsetStream(
103
                $stream,
104
                $charset,
105
                MailMimeParser::DEFAULT_CHARSET
106
            );
107
        }
108 97
        return $stream;
109
    }
110 97
111 97
    /**
112
     * Creates an array of streams based on the attached part's mime boundary
113 97
     * and child streams.
114 49
     *
115 49
     * @param IMimePart $part passed in because $this->part is declared
116 82
     *        as IMessagePart
117 40
     * @return StreamInterface[]
118 40
     */
119 40
    protected function getBoundaryAndChildStreams(IMimePart $part) : array
120 40
    {
121 69
        $boundary = $part->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'boundary');
122 9
        if ($boundary === null) {
123 9
            return \array_map(
124 9
                function($child) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
125
                    return $child->getStream();
126 67
                },
127
                $part->getChildParts()
128 74
            );
129
        }
130
        $streams = [];
131
        foreach ($part->getChildParts() as $i => $child) {
132
            if ($i !== 0 || $part->hasContent()) {
133
                $streams[] = Psr7\Utils::streamFor("\r\n");
134
            }
135 97
            $streams[] = Psr7\Utils::streamFor("--$boundary\r\n");
136
            $streams[] = $child->getStream();
137 97
        }
138 97
        $streams[] = Psr7\Utils::streamFor("\r\n--$boundary--\r\n");
139 97
140 97
        return $streams;
141 97
    }
142 97
143 97
    /**
144
     * Returns an array of Psr7 Streams representing the attached part and it's
145 97
     * direct children.
146
     *
147
     * @return StreamInterface[]
148
     */
149
    protected function getStreamsArray() : array
150
    {
151
        $contentStream = $this->part->getContentStream();
152
        if ($contentStream !== null) {
153
            // wrapping in a SeekingLimitStream because the underlying
154
            // ContentStream could be rewound, etc...
155
            $contentStream = $this->streamFactory->newDecoratedCachingStream(
156 67
                $this->streamFactory->newSeekingStream($contentStream),
157
                function ($stream) {
158 67
                    $es = $this->streamFactory->getTransferEncodingDecoratedStream(
159 67
                        $stream,
160 2
                        $this->part->getContentTransferEncoding(),
161 2
                        $this->part->getFilename()
162 2
                    );
163 2
                    $cs = $this->getCharsetDecoratorForStream($es);
164 2
                    return $cs;
165 2
                }
166
            );
167 65
        }
168 65
        
169 65
        $streams = [$this->streamFactory->newHeaderStream($this->part), $contentStream ?: Psr7\Utils::streamFor()];
170 65
        
171
        if ($this->part instanceof IMimePart && $this->part->getChildCount() > 0) {
0 ignored issues
show
Bug introduced by
The method getChildCount() does not exist on ZBateson\MailMimeParser\Message\IMessagePart. It seems like you code against a sub-type of ZBateson\MailMimeParser\Message\IMessagePart such as ZBateson\MailMimeParser\Message\IMultiPart or ZBateson\MailMimeParser\Message\MultiPart. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
        if ($this->part instanceof IMimePart && $this->part->/** @scrutinizer ignore-call */ getChildCount() > 0) {
Loading history...
172 65
            $streams = \array_merge($streams, $this->getBoundaryAndChildStreams($this->part));
173 65
        }
174
175 65
        return $streams;
176
    }
177 65
178
    /**
179
     * Creates the underlying stream lazily when required.
180
     */
181
    protected function createStream() : StreamInterface
182
    {
183
        if ($this->appendStream === null) {
184
            $this->appendStream = new AppendStream($this->getStreamsArray());
185
        }
186 97
        return $this->appendStream;
187
    }
188
}
189