Passed
Branch 1.0.0 (a1adee)
by Zaahid
08:34
created

MessageParser::readPartContent()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 13
cts 13
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 2
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
namespace ZBateson\MailMimeParser\Message;
8
9
use ZBateson\MailMimeParser\Stream\PartStreamRegistry;
10
use ZBateson\MailMimeParser\Message\Part\PartBuilder;
11
use ZBateson\MailMimeParser\Message\Part\PartBuilderFactory;
12
use ZBateson\MailMimeParser\Message\Part\PartFactoryService;
13
14
/**
15
 * Parses a mail mime message into its component parts.  To invoke, call
16
 * MailMimeParser::parse.
17
 *
18
 * @author Zaahid Bateson
19
 */
20
class MessageParser
21
{
22
    /**
23
     * @var PartFactoryService service instance used to create MimePartFactory
24
     *      objects.
25
     */
26
    protected $partFactoryService;
27
    
28
    /**
29
     * @var PartBuilderFactory used to create PartBuilders
30
     */
31
    protected $partBuilderFactory;
32
    
33
    /**
34
     * @var PartStreamRegistry used for registering message part streams.
35
     */
36
    protected $partStreamRegistry;
37
    
38
    /**
39
     * @var int maintains the character length of the last line separator,
40
     *      typically 2 for CRLF, to keep track of the correct 'end' position
41
     *      for a part because the CRLF before a boundary is considered part of
42
     *      the boundary.
43
     */
44
    private $lastLineSeparatorLength = 0;
45
    
46
    /**
47
     * Sets up the parser with its dependencies.
48
     * 
49
     * @param \ZBateson\MailMimeParser\Message\Part\PartFactoryService $pfs
50
     * @param \ZBateson\MailMimeParser\Message\Part\PartBuilderFactory $pbf
51
     * @param PartStreamRegistry $psr
52
     */
53 6
    public function __construct(
54
        PartFactoryService $pfs,
55
        PartBuilderFactory $pbf,
56
        PartStreamRegistry $psr
57
    ) {
58 6
        $this->partFactoryService = $pfs;
59 6
        $this->partBuilderFactory = $pbf;
60 6
        $this->partStreamRegistry = $psr;
61 6
    }
62
    
63
    /**
64
     * Parses the passed stream handle into a ZBateson\MailMimeParser\Message
65
     * object and returns it.
66
     * 
67
     * @param resource $fhandle the resource handle to the input stream of the
68
     *        mime message
69
     * @return \ZBateson\MailMimeParser\Message
70
     */
71 6
    public function parse($fhandle)
72
    {
73 6
        $messageObjectId = uniqid();
74 6
        $this->partStreamRegistry->register($messageObjectId, $fhandle);
75 6
        $partBuilder = $this->read($fhandle);
76 6
        return $partBuilder->createMessagePart($messageObjectId);
77
    }
78
    
79
    /**
80
     * Ensures the header isn't empty and contains a colon separator character,
81
     * then splits it and calls $partBuilder->addHeader.
82
     * 
83
     * @param string $header
84
     * @param PartBuilder $partBuilder
85
     */
86 5
    private function addRawHeaderToPart($header, PartBuilder $partBuilder)
87
    {
88 5
        if ($header !== '' && strpos($header, ':') !== false) {
89 5
            $a = explode(':', $header, 2);
90 5
            $partBuilder->addHeader($a[0], trim($a[1]));
91 5
        }
92 5
    }
93
    
94
    /**
95
     * Reads header lines up to an empty line, adding them to the passed
96
     * $partBuilder.
97
     * 
98
     * @param resource $handle the resource handle to read from
99
     * @param PartBuilder $partBuilder the current part to add headers to
100
     */
101 5
    protected function readHeaders($handle, PartBuilder $partBuilder)
102
    {
103 5
        $header = '';
104
        do {
105 5
            $line = fgets($handle, 1000);
106 5
            if (empty($line) || $line[0] !== "\t" && $line[0] !== ' ') {
107 5
                $this->addRawHeaderToPart($header, $partBuilder);
108 5
                $header = '';
109 5
            } else {
110 1
                $line = "\r\n" . $line;
111
            }
112 5
            $header .= rtrim($line, "\r\n");
113 5
        } while ($header !== '');
114 5
    }
115
    
116
    /**
117
     * Reads lines from the passed $handle, calling
118
     * $partBuilder->setEndBoundaryFound with the passed line until it returns
119
     * true or the stream is at EOF.
120
     * 
121
     * setEndBoundaryFound returns true if the passed line matches a boundary
122
     * for the $partBuilder itself or any of its parents.
123
     * 
124
     * Once a boundary is found, setStreamPartAndContentEndPos is called with
125
     * the passed $handle's read pos before the boundary and its line separator
126
     * were read.
127
     * 
128
     * @param resource $handle
129
     * @param PartBuilder $partBuilder
130
     */
131 3
    private function findContentBoundary($handle, PartBuilder $partBuilder)
132
    {
133
        // last separator before a boundary belongs to the boundary, and is not
134
        // part of the current part
135 3
        while (!feof($handle)) {
136 3
            $endPos = ftell($handle) - $this->lastLineSeparatorLength;
137 3
            $line = fgets($handle);
138 3
            $test = rtrim($line, "\r\n");
139 3
            $this->lastLineSeparatorLength = strlen($line) - strlen($test);
140 3
            if ($partBuilder->setEndBoundaryFound($test)) {
141 2
                $partBuilder->setStreamPartAndContentEndPos($endPos);
142 2
                return;
143
            }
144 3
        }
145 3
        $partBuilder->setStreamPartAndContentEndPos(ftell($handle));
146 3
        $partBuilder->setEof();
147 3
    }
148
    
149
    /**
150
     * Reads content for a non-mime message.  If there are uuencoded attachment
151
     * parts in the message (denoted by 'begin' lines), those parts are read and
152
     * added to the passed $partBuilder as children.
153
     * 
154
     * @param resource $handle
155
     * @param PartBuilder $partBuilder
156
     * @return string
157
     */
158 3
    protected function readUUEncodedOrPlainTextMessage($handle, PartBuilder $partBuilder)
159
    {
160 3
        $partBuilder->setStreamContentStartPos(ftell($handle));
161 3
        $part = $partBuilder;
162 3
        while (!feof($handle)) {
163 2
            $start = ftell($handle);
164 2
            $line = trim(fgets($handle));
165 2
            if (preg_match('/^begin ([0-7]{3}) (.*)$/', $line, $matches)) {
166 1
                $part = $this->partBuilderFactory->newPartBuilder(
167 1
                    $this->partFactoryService->getUUEncodedPartFactory()
168 1
                );
169 1
                $part->setStreamPartStartPos($start);
170
                // 'begin' line is part of the content
171 1
                $part->setStreamContentStartPos($start);
172 1
                $part->setProperty('mode', $matches[1]);
173 1
                $part->setProperty('filename', $matches[2]);
174 1
                $partBuilder->addChild($part);
175 1
            }
176 2
            $part->setStreamPartAndContentEndPos(ftell($handle));
177 2
        }
178 3
        $partBuilder->setStreamPartEndPos(ftell($handle));
179 3
    }
180
    
181
    /**
182
     * Reads content for a single part of a MIME message.
183
     * 
184
     * If the part being read is in turn a multipart part, readPart is called on
185
     * it recursively to read its headers and content.
186
     * 
187
     * The start/end positions of the part's content are set on the passed
188
     * $partBuilder, which in turn sets the end position of the part and its
189
     * parents.
190
     * 
191
     * @param resource $handle
192
     * @param PartBuilder $partBuilder
193
     */
194 3
    private function readPartContent($handle, PartBuilder $partBuilder)
195
    {
196 3
        $partBuilder->setStreamContentStartPos(ftell($handle));
197 3
        $this->findContentBoundary($handle, $partBuilder);
198 3
        if ($partBuilder->isMultiPart()) {
199 2
            while (!$partBuilder->isParentBoundaryFound()) {
200 2
                $child = $this->partBuilderFactory->newPartBuilder(
201 2
                    $this->partFactoryService->getMimePartFactory()
202 2
                );
203 2
                $partBuilder->addChild($child);
204 2
                $this->readPart($handle, $child);
205 2
            }
206 2
        }
207 3
    }
208
    
209
    /**
210
     * Reads a part and any of its children, into the passed $partBuilder,
211
     * either by calling readUUEncodedOrPlainTextMessage or readPartContent
212
     * after reading headers.
213
     * 
214
     * @param resource $handle
215
     * @param PartBuilder $partBuilder
216
     * @param boolean $isMessage
0 ignored issues
show
Bug introduced by
There is no parameter named $isMessage. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
217
     */
218 6
    protected function readPart($handle, PartBuilder $partBuilder)
219
    {
220 6
        $partBuilder->setStreamPartStartPos(ftell($handle));
221
        
222 6
        if ($partBuilder->canHaveHeaders()) {
223 5
            $this->readHeaders($handle, $partBuilder);
224 5
            $this->lastLineSeparatorLength = 0;
225 5
        }
226 6
        if ($partBuilder->getParent() === null && !$partBuilder->isMime()) {
227 3
            $this->readUUEncodedOrPlainTextMessage($handle, $partBuilder);
228 3
        } else {
229 3
            $this->readPartContent($handle, $partBuilder);
230
        }
231 6
    }
232
    
233
    /**
234
     * Reads the message from the input stream $handle and returns a PartBuilder
235
     * representing it.
236
     * 
237
     * @param resource $handle
238
     * @return PartBuilder
239
     */
240 6
    protected function read($handle)
241
    {
242 6
        $partBuilder = $this->partBuilderFactory->newPartBuilder(
243 6
            $this->partFactoryService->getMessageFactory()
244 6
        );
245 6
        $this->readPart($handle, $partBuilder);
246 6
        return $partBuilder;
247
    }
248
}
249