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 | 113 | public function __construct(StreamFactory $sdf, IMessagePart $part, bool $throwExceptionReadingPartContentFromUnsupportedCharsets) |
|||
52 | { |
||||
53 | 113 | parent::__construct($part); |
|||
54 | 113 | $this->streamFactory = $sdf; |
|||
55 | 113 | $this->part = $part; |
|||
56 | 113 | $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; |
|||
57 | 113 | $part->attach($this); |
|||
58 | |||||
59 | // unsetting the property forces the first access to go through |
||||
60 | // __get(). |
||||
61 | 113 | unset($this->stream); |
|||
62 | } |
||||
63 | |||||
64 | 11 | public function __destruct() |
|||
65 | { |
||||
66 | 11 | $this->part->detach($this); |
|||
67 | } |
||||
68 | |||||
69 | 96 | public function update(SplSubject $subject) : void |
|||
70 | { |
||||
71 | 96 | if ($this->appendStream !== null) { |
|||
72 | // unset forces recreation in StreamDecoratorTrait with a call to __get |
||||
73 | 3 | unset($this->stream); |
|||
74 | 3 | $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 | */ |
||||
84 | 100 | private function getCharsetDecoratorForStream(StreamInterface $stream) : StreamInterface |
|||
85 | { |
||||
86 | 100 | $charset = $this->part->getCharset(); |
|||
87 | 100 | if (!empty($charset)) { |
|||
88 | 79 | if (!$this->throwExceptionReadingPartContentFromUnsupportedCharsets) { |
|||
89 | 77 | $test = $this->streamFactory->newCharsetStream( |
|||
90 | 77 | Psr7\Utils::streamFor(), |
|||
91 | 77 | $charset, |
|||
92 | 77 | MailMimeParser::DEFAULT_CHARSET |
|||
93 | 77 | ); |
|||
94 | try { |
||||
95 | 77 | $test->write('t'); |
|||
96 | 2 | } catch (UnsupportedCharsetException $e) { |
|||
97 | 2 | return $stream; |
|||
98 | } finally { |
||||
99 | 77 | $test->close(); |
|||
100 | } |
||||
101 | } |
||||
102 | 77 | $stream = $this->streamFactory->newCharsetStream( |
|||
103 | 77 | $stream, |
|||
104 | 77 | $charset, |
|||
105 | 77 | MailMimeParser::DEFAULT_CHARSET |
|||
106 | 77 | ); |
|||
107 | } |
||||
108 | 98 | return $stream; |
|||
109 | } |
||||
110 | |||||
111 | /** |
||||
112 | * Creates an array of streams based on the attached part's mime boundary |
||||
113 | * and child streams. |
||||
114 | * |
||||
115 | * @param IMimePart $part passed in because $this->part is declared |
||||
116 | * as IMessagePart |
||||
117 | * @return StreamInterface[] |
||||
118 | */ |
||||
119 | 67 | protected function getBoundaryAndChildStreams(IMimePart $part) : array |
|||
120 | { |
||||
121 | 67 | $boundary = $part->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'boundary'); |
|||
122 | 67 | if ($boundary === null) { |
|||
123 | 2 | return \array_map( |
|||
124 | 2 | function($child) { |
|||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||||
125 | 2 | return $child->getStream(); |
|||
126 | 2 | }, |
|||
127 | 2 | $part->getChildParts() |
|||
128 | 2 | ); |
|||
129 | } |
||||
130 | 65 | $streams = []; |
|||
131 | 65 | foreach ($part->getChildParts() as $i => $child) { |
|||
132 | 65 | if ($i !== 0 || $part->hasContent()) { |
|||
133 | 65 | $streams[] = Psr7\Utils::streamFor("\r\n"); |
|||
134 | } |
||||
135 | 65 | $streams[] = Psr7\Utils::streamFor("--$boundary\r\n"); |
|||
136 | 65 | $streams[] = $child->getStream(); |
|||
137 | } |
||||
138 | 65 | $streams[] = Psr7\Utils::streamFor("\r\n--$boundary--\r\n"); |
|||
139 | |||||
140 | 65 | return $streams; |
|||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * Returns an array of Psr7 Streams representing the attached part and it's |
||||
145 | * direct children. |
||||
146 | * |
||||
147 | * @return StreamInterface[] |
||||
148 | */ |
||||
149 | 100 | protected function getStreamsArray() : array |
|||
150 | { |
||||
151 | 100 | $contentStream = $this->part->getContentStream(); |
|||
152 | 100 | if ($contentStream !== null) { |
|||
153 | // wrapping in a SeekingLimitStream because the underlying |
||||
154 | // ContentStream could be rewound, etc... |
||||
155 | 100 | $contentStream = $this->streamFactory->newDecoratedCachingStream( |
|||
156 | 100 | $this->streamFactory->newSeekingStream($contentStream), |
|||
157 | 100 | function($stream) { |
|||
0 ignored issues
–
show
|
|||||
158 | 100 | $es = $this->streamFactory->getTransferEncodingDecoratedStream( |
|||
159 | 100 | $stream, |
|||
160 | 100 | $this->part->getContentTransferEncoding(), |
|||
161 | 100 | $this->part->getFilename() |
|||
162 | 100 | ); |
|||
163 | 100 | $cs = $this->getCharsetDecoratorForStream($es); |
|||
164 | 100 | return $cs; |
|||
165 | 100 | } |
|||
166 | 100 | ); |
|||
167 | } |
||||
168 | |||||
169 | 100 | $streams = [$this->streamFactory->newHeaderStream($this->part), $contentStream ?: Psr7\Utils::streamFor()]; |
|||
170 | |||||
171 | 100 | if ($this->part instanceof IMimePart && $this->part->getChildCount() > 0) { |
|||
0 ignored issues
–
show
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
![]() |
|||||
172 | 67 | $streams = \array_merge($streams, $this->getBoundaryAndChildStreams($this->part)); |
|||
173 | } |
||||
174 | |||||
175 | 100 | return $streams; |
|||
176 | } |
||||
177 | |||||
178 | /** |
||||
179 | * Creates the underlying stream lazily when required. |
||||
180 | */ |
||||
181 | 100 | protected function createStream() : StreamInterface |
|||
182 | { |
||||
183 | 100 | if ($this->appendStream === null) { |
|||
184 | 100 | $this->appendStream = new AppendStream($this->getStreamsArray()); |
|||
185 | } |
||||
186 | 100 | return $this->appendStream; |
|||
187 | } |
||||
188 | } |
||||
189 |