Passed
Push — master ( 46ed75...ca2387 )
by Zaahid
03:33
created

setStreamPartAndContentEndPos()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 8.8333
cc 7
nc 6
nop 1
crap 7
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 ZBateson\MailMimeParser\Header\IHeader;
11
use ZBateson\MailMimeParser\Header\HeaderConsts;
12
use ZBateson\MailMimeParser\Message\IMessagePart;
13
use Psr\Log\LogLevel;
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 ?string NULL if the current part does not have a boundary, and
36
     *      otherwise contains the value of the boundary parameter of the
37
     *      content-type header if the part contains one.
38
     */
39
    private ?string $mimeBoundary = null;
40
41
    /**
42
     * @var bool FALSE if not queried for in the content-type header of this
43
     *      part and set in $mimeBoundary.
44
     */
45
    private bool $mimeBoundaryQueried = false;
46
47
    /**
48
     * @var bool true once all children of this part have been parsed.
49
     */
50
    protected bool $allChildrenParsed = false;
51
52
    /**
53
     * @var ParserPartProxy[] Array of all parsed children.
54
     */
55
    protected array $children = [];
56
57
    /**
58
     * @var ParserPartProxy[] Parsed children used as a 'first-in-first-out'
59
     *      stack as children are parsed.
60
     */
61
    protected array $childrenStack = [];
62
63
    /**
64
     * @var ParserPartProxy Reference to the last child added to this part.
65
     */
66
    protected ?ParserPartProxy $lastAddedChild = null;
67
68
    /**
69
     * Ensures that the last child added to this part is fully parsed (content
70
     * and children).
71
     */
72 107
    protected function ensureLastChildParsed() : static
73
    {
74 107
        if ($this->lastAddedChild !== null) {
75 77
            $this->lastAddedChild->parseAll();
76
        }
77 107
        return $this;
78
    }
79
80
    /**
81
     * Parses the next child of this part and adds it to the 'stack' of
82
     * children.
83
     */
84 107
    protected function parseNextChild() : static
85
    {
86 107
        if ($this->allChildrenParsed) {
87 20
            return $this;
88
        }
89 107
        $this->parseContent();
90 107
        $this->ensureLastChildParsed();
91 107
        $next = $this->parser->parseNextChild($this);
92 107
        if ($next !== null) {
93 77
            $this->children[] = $next;
94 77
            $this->childrenStack[] = $next;
95 77
            $this->lastAddedChild = $next;
96
        } else {
97 107
            $this->allChildrenParsed = true;
98
        }
99 107
        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 105
    public function popNextChild() : ?IMessagePart
110
    {
111 105
        if (empty($this->childrenStack)) {
112 105
            $this->parseNextChild();
113
        }
114 105
        $proxy = \array_shift($this->childrenStack);
115 105
        return ($proxy !== null) ? $proxy->getPart() : null;
116
    }
117
118
    /**
119
     * Parses all content and children for this part.
120
     */
121 105
    public function parseAll() : static
122
    {
123 105
        $this->parseContent();
124 105
        while (!$this->allChildrenParsed) {
125 27
            $this->parseNextChild();
126
        }
127 105
        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() : ?IHeader
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
     * @return string
144
     */
145 78
    public function getMimeBoundary() : ?string
146
    {
147 78
        if ($this->mimeBoundaryQueried === false) {
148 78
            $this->mimeBoundaryQueried = true;
149 78
            $contentType = $this->getContentType();
150 78
            if ($contentType !== null) {
151 76
                $this->mimeBoundary = $contentType->getValueFor('boundary');
0 ignored issues
show
Bug introduced by
The method getValueFor() does not exist on ZBateson\MailMimeParser\Header\IHeader. Did you maybe mean getValue()? ( Ignorable by Annotation )

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

151
                /** @scrutinizer ignore-call */ 
152
                $this->mimeBoundary = $contentType->getValueFor('boundary');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
152
            }
153
        }
154 78
        return $this->mimeBoundary;
155
    }
156
157
    /**
158
     * Returns true if the passed $line of read input matches this part's mime
159
     * boundary, or any of its parent's mime boundaries for a multipart message.
160
     *
161
     * If the passed $line is the ending boundary for the current part,
162
     * $this->isEndBoundaryFound will return true after.
163
     */
164 76
    public function setEndBoundaryFound(string $line) : bool
165
    {
166 76
        $boundary = $this->getMimeBoundary();
167 76
        if ($this->getParent() !== null && $this->getParent()->setEndBoundaryFound($line)) {
0 ignored issues
show
Bug introduced by
The method setEndBoundaryFound() does not exist on ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy. It seems like you code against a sub-type of ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy such as ZBateson\MailMimeParser\...oxy\ParserMimePartProxy. ( Ignorable by Annotation )

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

167
        if ($this->getParent() !== null && $this->getParent()->/** @scrutinizer ignore-call */ setEndBoundaryFound($line)) {
Loading history...
168 74
            $this->parentBoundaryFound = true;
169 74
            return true;
170 76
        } elseif ($boundary !== null) {
171 74
            if ($line === "--$boundary--") {
172 74
                $this->endBoundaryFound = true;
173 74
                return true;
174 74
            } elseif ($line === "--$boundary") {
175 74
                return true;
176
            }
177
        }
178 38
        return false;
179
    }
180
181
    /**
182
     * Returns true if the parser passed an input line to setEndBoundary that
183
     * matches a parent's mime boundary, and the following input belongs to a
184
     * new part under its parent.
185
     *
186
     */
187 103
    public function isParentBoundaryFound() : bool
188
    {
189 103
        return ($this->parentBoundaryFound);
190
    }
191
192
    /**
193
     * Returns true if an end boundary was found for this part.
194
     *
195
     */
196 76
    public function isEndBoundaryFound() : bool
197
    {
198 76
        return ($this->endBoundaryFound);
199
    }
200
201
    /**
202
     * Called once EOF is reached while reading content.  The method sets the
203
     * flag used by isParentBoundaryFound() to true on this part and all parent
204
     * parts.
205
     *
206
     * @return static
207
     */
208 97
    public function setEof() : static
209
    {
210 97
        $this->parentBoundaryFound = true;
211 97
        if ($this->getParent() !== null) {
212 66
            $this->getParent()->setEof();
0 ignored issues
show
Bug introduced by
The method setEof() does not exist on ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy. It seems like you code against a sub-type of ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy such as ZBateson\MailMimeParser\...oxy\ParserMimePartProxy. ( Ignorable by Annotation )

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

212
            $this->getParent()->/** @scrutinizer ignore-call */ setEof();
Loading history...
213
        }
214 97
        return $this;
215
    }
216
217
    /**
218
     * Overridden to set a 0-length content length, and a stream end pos of -2
219
     * if the passed end pos is before the start pos (can happen if a mime
220
     * end boundary doesn't have an empty line before the next parent start
221
     * boundary).
222
     */
223 104
    public function setStreamPartAndContentEndPos(int $streamContentEndPos) : static
224
    {
225
        // check if we're expecting a boundary and didn't find one
226 104
        if (!$this->endBoundaryFound && !$this->parentBoundaryFound) {
227 104
            if (!empty($this->mimeBoundary) || ($this->getParent() !== null && !empty($this->getParent()->mimeBoundary))) {
228 73
                $this->addError('End boundary for part not found', LogLevel::WARNING);
229
            }
230
        }
231 104
        $start = $this->getStreamContentStartPos();
232 104
        if ($streamContentEndPos - $start < 0) {
233 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

233
            parent::setStreamPartAndContentEndPos(/** @scrutinizer ignore-type */ $start);
Loading history...
234 26
            $this->setStreamPartEndPos($streamContentEndPos);
235
        } else {
236 104
            parent::setStreamPartAndContentEndPos($streamContentEndPos);
237
        }
238 104
        return $this;
239
    }
240
241
    /**
242
     * Sets the length of the last line ending read by MimeParser (e.g. 2 for
243
     * '\r\n', or 1 for '\n').
244
     *
245
     * The line ending may not belong specifically to this part, so
246
     * ParserMimePartProxy simply calls setLastLineEndingLength on its parent,
247
     * which must eventually reach a ParserMessageProxy which actually stores
248
     * the length.
249
     */
250 74
    public function setLastLineEndingLength(int $length) : static
251
    {
252 74
        $this->getParent()->setLastLineEndingLength($length);
0 ignored issues
show
Bug introduced by
The method setLastLineEndingLength() does not exist on ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy. It seems like you code against a sub-type of ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy such as ZBateson\MailMimeParser\...oxy\ParserMimePartProxy. ( Ignorable by Annotation )

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

252
        $this->getParent()->/** @scrutinizer ignore-call */ setLastLineEndingLength($length);
Loading history...
253 74
        return $this;
254
    }
255
256
    /**
257
     * Returns the length of the last line ending read by MimeParser (e.g. 2 for
258
     * '\r\n', or 1 for '\n').
259
     *
260
     * The line ending may not belong specifically to this part, so
261
     * ParserMimePartProxy simply calls getLastLineEndingLength on its parent,
262
     * which must eventually reach a ParserMessageProxy which actually keeps
263
     * the length and returns it.
264
     *
265
     * @return int the length of the last line ending read
266
     */
267 74
    public function getLastLineEndingLength() : int
268
    {
269 74
        return $this->getParent()->getLastLineEndingLength();
0 ignored issues
show
Bug introduced by
The method getLastLineEndingLength() does not exist on ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy. It seems like you code against a sub-type of ZBateson\MailMimeParser\...r\Proxy\ParserPartProxy such as ZBateson\MailMimeParser\...oxy\ParserMimePartProxy. ( Ignorable by Annotation )

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

269
        return $this->getParent()->/** @scrutinizer ignore-call */ getLastLineEndingLength();
Loading history...
270
    }
271
272
    /**
273
     * Returns the last part that was added.
274
     */
275 2
    public function getLastAddedChild() : ?ParserPartProxy
276
    {
277 2
        return $this->lastAddedChild;
278
    }
279
280
    /**
281
     * Returns the added child at the provided index, useful for looking at
282
     * previously parsed children.
283
     */
284 2
    public function getAddedChildAt(int $index) : ?ParserPartProxy
285
    {
286 2
        return $this->children[$index] ?? null;
287
    }
288
}
289