ParserMimePartProxy::getMimeBoundary()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 3
nop 0
crap 3
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\Parser\Proxy;
9
10
use Psr\Log\LogLevel;
11
use ZBateson\MailMimeParser\Header\HeaderConsts;
12
use ZBateson\MailMimeParser\Header\ParameterHeader;
13
use ZBateson\MailMimeParser\Message\IMessagePart;
14
15
/**
16
 * A bi-directional parser-to-part proxy for MimeParser and IMimeParts.
17
 *
18
 * @author Zaahid Bateson
19
 */
20
class ParserMimePartProxy extends ParserPartProxy
21
{
22
    /**
23
     * @var bool set to true once the end boundary of the currently-parsed
24
     *      part is found.
25
     */
26
    protected bool $endBoundaryFound = false;
27
28
    /**
29
     * @var bool set to true once a boundary belonging to this parent's part
30
     *      is found.
31
     */
32
    protected bool $parentBoundaryFound = false;
33
34
    /**
35
     * @var bool true once all children of this part have been parsed.
36
     */
37
    protected bool $allChildrenParsed = false;
38
39
    /**
40
     * @var ParserPartProxy[] Array of all parsed children.
41
     */
42
    protected array $children = [];
43
44
    /**
45
     * @var ParserPartProxy[] Parsed children used as a 'first-in-first-out'
46
     *      stack as children are parsed.
47
     */
48
    protected array $childrenStack = [];
49
50
    /**
51
     * @var ParserPartProxy Reference to the last child added to this part.
52
     */
53
    protected ?ParserPartProxy $lastAddedChild = null;
54
55
    /**
56
     * @var ?string NULL if the current part does not have a boundary, and
57
     *      otherwise contains the value of the boundary parameter of the
58
     *      content-type header if the part contains one.
59
     */
60
    private ?string $mimeBoundary = null;
61
62
    /**
63
     * @var bool FALSE if not queried for in the content-type header of this
64
     *      part and set in $mimeBoundary.
65
     */
66
    private bool $mimeBoundaryQueried = false;
67
68
    /**
69
     * Ensures that the last child added to this part is fully parsed (content
70
     * and children).
71
     */
72 108
    protected function ensureLastChildParsed() : static
73
    {
74 108
        if ($this->lastAddedChild !== null) {
75 77
            $this->lastAddedChild->parseAll();
76
        }
77 108
        return $this;
78
    }
79
80
    /**
81
     * Parses the next child of this part and adds it to the 'stack' of
82
     * children.
83
     */
84 108
    protected function parseNextChild() : static
85
    {
86 108
        if ($this->allChildrenParsed) {
87 20
            return $this;
88
        }
89 108
        $this->parseContent();
90 108
        $this->ensureLastChildParsed();
91 108
        $next = $this->parser->parseNextChild($this);
92 108
        if ($next !== null) {
93 77
            $this->children[] = $next;
94 77
            $this->childrenStack[] = $next;
95 77
            $this->lastAddedChild = $next;
96
        } else {
97 108
            $this->allChildrenParsed = true;
98
        }
99 108
        return $this;
100
    }
101
102
    /**
103
     * Returns the next child part if one exists, popping it from the internal
104
     * 'stack' of children, attempting to parse a new one if the stack is empty,
105
     * and returning null if there are no more children.
106
     *
107
     * @return ?IMessagePart the child part.
108
     */
109 106
    public function popNextChild() : ?IMessagePart
110
    {
111 106
        if (empty($this->childrenStack)) {
112 106
            $this->parseNextChild();
113
        }
114 106
        $proxy = \array_shift($this->childrenStack);
115 106
        return ($proxy !== null) ? $proxy->getPart() : null;
116
    }
117
118
    /**
119
     * Parses all content and children for this part.
120
     */
121 106
    public function parseAll() : static
122
    {
123 106
        $this->parseContent();
124 106
        while (!$this->allChildrenParsed) {
125 27
            $this->parseNextChild();
126
        }
127 106
        return $this;
128
    }
129
130
    /**
131
     * Returns a ParameterHeader representing the parsed Content-Type header for
132
     * this part.
133
     */
134 79
    public function getContentType() : ?ParameterHeader
135
    {
136 79
        return $this->getHeaderContainer()->get(HeaderConsts::CONTENT_TYPE);
137
    }
138
139
    /**
140
     * Returns the parsed boundary parameter of the Content-Type header if set
141
     * for a multipart message part.
142
     *
143
     */
144 78
    public function getMimeBoundary() : ?string
145
    {
146 78
        if ($this->mimeBoundaryQueried === false) {
147 78
            $this->mimeBoundaryQueried = true;
148 78
            $contentType = $this->getContentType();
149 78
            if ($contentType !== null) {
150 76
                $this->mimeBoundary = $contentType->getValueFor('boundary');
151
            }
152
        }
153 78
        return $this->mimeBoundary;
154
    }
155
156
    /**
157
     * Returns true if the passed $line of read input matches this part's mime
158
     * boundary, or any of its parent's mime boundaries for a multipart message.
159
     *
160
     * If the passed $line is the ending boundary for the current part,
161
     * $this->isEndBoundaryFound will return true after.
162
     */
163 76
    public function setEndBoundaryFound(string $line) : bool
164
    {
165 76
        $boundary = $this->getMimeBoundary();
166 76
        if ($this->getParent() !== null && $this->getParent()->setEndBoundaryFound($line)) {
167 74
            $this->parentBoundaryFound = true;
168 74
            return true;
169 76
        } elseif ($boundary !== null) {
170 74
            if ($line === "--$boundary--") {
171 74
                $this->endBoundaryFound = true;
172 74
                return true;
173 74
            } elseif ($line === "--$boundary") {
174 74
                return true;
175
            }
176
        }
177 38
        return false;
178
    }
179
180
    /**
181
     * Returns true if the parser passed an input line to setEndBoundary that
182
     * matches a parent's mime boundary, and the following input belongs to a
183
     * new part under its parent.
184
     *
185
     */
186 104
    public function isParentBoundaryFound() : bool
187
    {
188 104
        return ($this->parentBoundaryFound);
189
    }
190
191
    /**
192
     * Returns true if an end boundary was found for this part.
193
     *
194
     */
195 76
    public function isEndBoundaryFound() : bool
196
    {
197 76
        return ($this->endBoundaryFound);
198
    }
199
200
    /**
201
     * Called once EOF is reached while reading content.  The method sets the
202
     * flag used by isParentBoundaryFound() to true on this part and all parent
203
     * parts.
204
     *
205
     */
206 99
    public function setEof() : static
207
    {
208 99
        $this->parentBoundaryFound = true;
209 99
        if ($this->getParent() !== null) {
210 66
            $this->getParent()->setEof();
211
        }
212 99
        return $this;
213
    }
214
215
    /**
216
     * Overridden to set a 0-length content length, and a stream end pos of -2
217
     * if the passed end pos is before the start pos (can happen if a mime
218
     * end boundary doesn't have an empty line before the next parent start
219
     * boundary).
220
     */
221 106
    public function setStreamPartAndContentEndPos(int $streamContentEndPos) : static
222
    {
223
        // check if we're expecting a boundary and didn't find one
224 106
        if (!$this->endBoundaryFound && !$this->parentBoundaryFound) {
225 106
            if (!empty($this->mimeBoundary) || ($this->getParent() !== null && !empty($this->getParent()->mimeBoundary))) {
226 73
                $this->addError('End boundary for part not found', LogLevel::WARNING);
227
            }
228
        }
229 106
        $start = $this->getStreamContentStartPos();
230 106
        if ($streamContentEndPos - $start < 0) {
231 26
            parent::setStreamPartAndContentEndPos($start);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type null; however, parameter $streamContentEndPos of ZBateson\MailMimeParser\...mPartAndContentEndPos() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

231
            parent::setStreamPartAndContentEndPos(/** @scrutinizer ignore-type */ $start);
Loading history...
232 26
            $this->setStreamPartEndPos($streamContentEndPos);
233
        } else {
234 106
            parent::setStreamPartAndContentEndPos($streamContentEndPos);
235
        }
236 106
        return $this;
237
    }
238
239
    /**
240
     * Sets the length of the last line ending read by MimeParser (e.g. 2 for
241
     * '\r\n', or 1 for '\n').
242
     *
243
     * The line ending may not belong specifically to this part, so
244
     * ParserMimePartProxy simply calls setLastLineEndingLength on its parent,
245
     * which must eventually reach a ParserMessageProxy which actually stores
246
     * the length.
247
     */
248 74
    public function setLastLineEndingLength(int $length) : static
249
    {
250 74
        $this->getParent()->setLastLineEndingLength($length);
251 74
        return $this;
252
    }
253
254
    /**
255
     * Returns the length of the last line ending read by MimeParser (e.g. 2 for
256
     * '\r\n', or 1 for '\n').
257
     *
258
     * The line ending may not belong specifically to this part, so
259
     * ParserMimePartProxy simply calls getLastLineEndingLength on its parent,
260
     * which must eventually reach a ParserMessageProxy which actually keeps
261
     * the length and returns it.
262
     *
263
     * @return int the length of the last line ending read
264
     */
265 74
    public function getLastLineEndingLength() : int
266
    {
267 74
        return $this->getParent()->getLastLineEndingLength();
268
    }
269
270
    /**
271
     * Returns the last part that was added.
272
     */
273 2
    public function getLastAddedChild() : ?ParserPartProxy
274
    {
275 2
        return $this->lastAddedChild;
276
    }
277
278
    /**
279
     * Returns the added child at the provided index, useful for looking at
280
     * previously parsed children.
281
     */
282 2
    public function getAddedChildAt(int $index) : ?ParserPartProxy
283
    {
284 2
        return $this->children[$index] ?? null;
285
    }
286
}
287