Passed
Push — master ( 3c92b8...da7b59 )
by Zaahid
08:15 queued 04:35
created

MimeParser::parseNextChild()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 8
ccs 6
cts 6
cp 1
rs 10
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\Parser;
8
9
use ZBateson\MailMimeParser\Message\PartHeaderContainer;
10
use ZBateson\MailMimeParser\Message\Factory\PartHeaderContainerFactory;
11
use ZBateson\MailMimeParser\Parser\Proxy\ParserMessageProxyFactory;
12
use ZBateson\MailMimeParser\Parser\Proxy\ParserMimePartProxyFactory;
13
use ZBateson\MailMimeParser\Parser\Proxy\ParserMimePartProxy;
14
use ZBateson\MailMimeParser\Parser\Proxy\ParserPartProxy;
15
16
/**
17
 * Parses content and children of MIME parts.
18
 *
19
 * @author Zaahid Bateson
20
 */
21
class MimeParser extends AbstractParser
22
{
23
    /**
24
     * @var PartHeaderContainerFactory Factory service for creating
25
     *      PartHeaderContainers for headers.
26
     */
27
    protected $partHeaderContainerFactory;
28
29
    /**
30
     * @var HeaderParser The HeaderParser service.
31
     */
32
    protected $headerParser;
33
34 12
    public function __construct(
35
        ParserMessageProxyFactory $parserMessageProxyFactory,
36
        ParserMimePartProxyFactory $parserMimePartProxyFactory,
37
        PartBuilderFactory $partBuilderFactory,
38
        PartHeaderContainerFactory $partHeaderContainerFactory,
39
        HeaderParser $headerParser
40
    ) {
41 12
        parent::__construct($parserMessageProxyFactory, $parserMimePartProxyFactory, $partBuilderFactory);
42 12
        $this->partHeaderContainerFactory = $partHeaderContainerFactory;
43 12
        $this->headerParser = $headerParser;
44 12
    }
45
46
    /**
47
     * Returns true if the passed PartBuilder::isMime() method returns true.
48
     *
49
     * @param PartBuilder $part
50
     * @return bool
51
     */
52 105
    public function canParse(PartBuilder $part)
53
    {
54 105
        return $part->isMime();
55
    }
56
57
    /**
58
     * Reads up to 2048 bytes of input from the passed resource handle,
59
     * discarding portions of a line that are longer than that, and returning
60
     * the read portions of the line.
61
     * 
62
     * The method also calls $proxy->setLastLineEndingLength which is used in
63
     * findContentBoundary() to set the exact end byte of a part.
64
     *
65
     * @param resource $handle
66
     * @param ParserMimePartProxy $proxy
67
     * @return string
68
     */
69 108
    private function readBoundaryLine($handle, ParserMimePartProxy $proxy)
70
    {
71 108
        $size = 2048;
72 108
        $isCut = false;
73 108
        $line = fgets($handle, $size);
74 108
        while (strlen($line) === $size - 1 && substr($line, -1) !== "\n") {
75 1
            $line = fgets($handle, $size);
76 1
            $isCut = true;
77
        }
78 108
        $ret = rtrim($line, "\r\n");
79 108
        $proxy->setLastLineEndingLength(strlen($line) - strlen($ret));
80 108
        return ($isCut) ? '' : $ret;
81
    }
82
83
    /**
84
     * Reads 2048-byte lines from the passed $handle, calling
85
     * $partBuilder->setEndBoundaryFound with the passed line until it returns
86
     * true or the stream is at EOF.
87
     *
88
     * setEndBoundaryFound returns true if the passed line matches a boundary
89
     * for the $partBuilder itself or any of its parents.
90
     *
91
     * Lines longer than 2048 bytes are returned as single lines of 2048 bytes,
92
     * the longer line is not returned separately but is simply discarded.
93
     *
94
     * Once a boundary is found, setStreamPartAndContentEndPos is called with
95
     * the passed $handle's read pos before the boundary and its line separator
96
     * were read.
97
     *
98
     * @param PartBuilder $partBuilder
99
     */
100 108
    private function findContentBoundary(ParserMimePartProxy $proxy)
101
    {
102 108
        $handle = $proxy->getMessageResourceHandle();
103
        // last separator before a boundary belongs to the boundary, and is not
104
        // part of the current part
105 108
        while (!feof($handle)) {
106 108
            $endPos = ftell($handle) - $proxy->getLastLineEndingLength();
107 108
            $line = $this->readBoundaryLine($handle, $proxy);
108 108
            if (substr($line, 0, 2) === '--' && $proxy->setEndBoundaryFound($line)) {
109 74
                $proxy->setStreamPartAndContentEndPos($endPos);
110 74
                return;
111
            }
112
        }
113 100
        $proxy->setStreamPartAndContentEndPos(ftell($handle));
114 100
        $proxy->setEof();
115 100
    }
116
117 108
    public function parseContent(ParserPartProxy $proxy)
118
    {
119 108
        $proxy->setStreamContentStartPos($proxy->getMessageResourceHandlePos());
120 108
        $this->findContentBoundary($proxy);
121 108
    }
122
123
    /**
124
     * Calls the header parser to fill the passed $headerContainer, then calls
125
     * $this->parserManager->createParserProxyFor($child);
126
     *
127
     * The method first checks though if the 'part' represents hidden content
128
     * passed a MIME end boundary, which some messages like to include, for
129
     * instance:
130
     *
131
     * ```
132
     * --outer-boundary--
133
     * --boundary
134
     * content
135
     * --boundary--
136
     * some hidden information
137
     * --outer-boundary--
138
     * ```
139
     *
140
     * In this case, $this->parserPartProxyFactory is called directly to create
141
     * a part, $this->parseContent is called immediately to parse it and discard
142
     * it, and null is returned.
143
     *
144
     * @param ParserMimePartProxy $parent
145
     * @param PartHeaderContainer $headerContainer
146
     * @param PartBuilder $child
147
     * @return ParserPartProxy|null
148
     */
149 74
    private function createPart(ParserMimePartProxy $parent, PartHeaderContainer $headerContainer, PartBuilder $child)
150
    {
151 74
        if (!$parent->isEndBoundaryFound()) {
152 73
            $this->headerParser->parse(
153 73
                $child->getMessageResourceHandle(),
154 73
                $headerContainer
155
            );
156 73
            $parserProxy = $this->parserManager->createParserProxyFor($child);
157 73
            return $parserProxy;
158
        } else {
159
            // reads content past an end boundary if there is any
160 70
            $parserProxy = $this->parserPartProxyFactory->newInstance($child, $this);
161 70
            $this->parseContent($parserProxy);
162 70
            return null;
163
        }
164
    }
165
166 102
    public function parseNextChild(ParserMimePartProxy $proxy)
167
    {
168 102
        if ($proxy->isParentBoundaryFound()) {
169 100
            return null;
170
        }
171 74
        $headerContainer = $this->partHeaderContainerFactory->newInstance();
172 74
        $child = $this->partBuilderFactory->newChildPartBuilder($headerContainer, $proxy);
173 74
        return $this->createPart($proxy, $headerContainer, $child);
174
    }
175
}
176