Issues (2)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/MimeParser.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Goetas\Mail\ToSwiftMailParser;
4
5
use Goetas\Mail\ToSwiftMailParser\Exception\InvalidMessageFormatException;
6
use Goetas\Mail\ToSwiftMailParser\Mime\ContentDecoder;
7
use Goetas\Mail\ToSwiftMailParser\Mime\HeaderDecoder;
8
9
class MimeParser
10
{
11
    private const SWIFT_CONTAINER_CACHE_KEY = 'cache';
12
    private const SWIFT_CONTAINER_ID_GENERATOR_KEY = 'mime.idgenerator';
13
14
    protected $removeHeaders = ['Received', 'From', 'X-Original-To', 'MIME-Version', 'Received-SPF', 'Delivered-To'];
15
    protected $allowedHeaders = ['return-path', 'subject'];
16
    /**
17
     * @var ContentDecoder
18
     */
19
    private $contentDecoder;
20
21
    /**
22
     * @var HeaderDecoder
23
     */
24
    private $headerDecoder;
25
26
    /**
27
     * @var \Swift_DependencyContainer
28
     */
29
    private $swiftContainer;
30
31 10
    public function __construct(array $allowedHeaders = [], array $removeHeaders = [])
32
    {
33 10
        $this->contentDecoder = new ContentDecoder();
34 10
        $this->headerDecoder = new HeaderDecoder();
35
36 10
        $this->allowedHeaders = array_merge($this->allowedHeaders, $allowedHeaders);
37 10
        $this->removeHeaders = array_merge($this->removeHeaders, $removeHeaders);
38 10
    }
39
40
    public function setSwiftDependencyContainer(\Swift_DependencyContainer $swiftContainer): void
41
    {
42
        $this->swiftContainer = $swiftContainer;
43
    }
44
45 5
    private function getSwiftDependencyContainer(): \Swift_DependencyContainer
46
    {
47 5
        if ($this->swiftContainer === null) {
48 5
            $this->swiftContainer = \Swift_DependencyContainer::getInstance();
49
        }
50 5
        return $this->swiftContainer;
51
    }
52
53 5
    private function getIdGenertor(): \Swift_IdGenerator
54
    {
55 5
        return $this->getSwiftDependencyContainer()->lookup(self::SWIFT_CONTAINER_ID_GENERATOR_KEY);
56
    }
57
58 5
    private function getCache(): \Swift_KeyCache
59
    {
60 5
        return $this->getSwiftDependencyContainer()->lookup(self::SWIFT_CONTAINER_CACHE_KEY);
61
    }
62
63 1
    public function parseFile(
64
        string $path,
65 1
        bool $fillHeaders = false,
66 1
        \Swift_Mime_SimpleMimeEntity $message = null
67 1
    ): \Swift_Mime_SimpleMimeEntity {
68 1
        $fp = fopen($path, "rb");
69
        $message = $this->parseStream($fp, $fillHeaders, $message);
70
        fclose($fp);
71 1
        return $message;
72
    }
73 1
74 1
    public function parseString(
75 1
        string $string,
76 1
        bool $fillHeaders = false,
77 1
        \Swift_Mime_SimpleMimeEntity $message = null
78 1
    ): \Swift_Mime_SimpleMimeEntity {
79
        $fp = fopen("php://memory", "wb");
80
        fwrite($fp, $string);
81
        rewind($fp);
82
        $message = $this->parseStream($fp, $fillHeaders, $message);
83
        fclose($fp);
84 10
        return $message;
85
    }
86 10
87
    /**
88 10
     * @param resource $stream
89
     */
90 10
    public function parseStream(
91
        $stream,
92 9
        bool $fillHeaders = false,
93 9
        \Swift_Mime_SimpleMimeEntity $message = null
94
    ): \Swift_Mime_SimpleMimeEntity {
95
        $partHeaders = $this->extractHeaders($stream);
96 9
97
        $filteredHeaders = $this->filterHeaders($partHeaders);
98 9
99 9
        $parts = $this->parseParts($stream, $partHeaders);
100 9
101
        if (!$message) {
102
            $message = new \Swift_Message ();
103 9
        }
104
105 9
        $headers = $this->createHeadersSet($filteredHeaders);
106
107
        foreach ($headers->getAll() as $name => $header) {
108 10
            if ($fillHeaders || in_array(strtolower($header->getFieldName()), $this->allowedHeaders, true)) {
109
                $message->getHeaders()->set($header);
110 10
            }
111 10
        }
112 10
        $this->createMessage($parts, $message);
113 10
114 10
        return $message;
115 10
    }
116
117 10
    protected function extractHeaders($stream): array
118 10
    {
119 10
        $headers = [];
120 10
        $hName = null;
121
        while (!feof($stream)) {
122 10
            $row = fgets($stream);
123
            if ($row === "\r\n" || $row === "\n" || $row === "\r") {
124 10
                break;
125
            }
126
            if (preg_match('/^([a-z0-9\-]+)\s*:(.*)/i', $row, $mch)) {
127 10
                $hName = strtolower($mch[1]);
128
                if (!in_array($hName, ["content-type", "content-transfer-encoding"], true)) {
129 10
                    $hName = $mch[1];
130 10
                }
131
                $row = $mch[2];
132 10
            }
133
            if (empty($hName)) {
134
                continue;
135 10
            }
136
            $headers[$hName][] = trim($row);
137 10
        }
138 10
        foreach ($headers as $header => $values) {
139 10
            $headers[$header] = $this->headerDecoder->decode(trim(implode(" ", $values)));
140
        }
141
        return $headers;
142 10
    }
143
144
    private function filterHeaders(array $headers): array
145 10
    {
146
        foreach ($headers as $header => $values) {
147 10
            if (in_array(strtolower($header), $this->removeHeaders, true)
148 10
                && !in_array(strtolower($header), $this->allowedHeaders, true)
149
            ) {
150 10
                unset($headers[$header]);
151 10
            }
152 7
        }
153 7
        return $headers;
154 1
    }
155
156 6
    protected function parseParts($stream, array $partHeaders): array
157
    {
158
        $parts = [];
159
        $contentType = $this->extractValueHeader($this->getContentType($partHeaders));
160
161 9
        $boundary = null;
162 9
        if (stripos($contentType, 'multipart/') !== false) {
163
            $headerParts = $this->extractHeaderParts($this->getContentType($partHeaders));
164 9
            if (empty($headerParts["boundary"])) {
165 9
                throw new InvalidMessageFormatException("The Content-Type header is not well formed, boundary is missing");
166 9
            }
167 9
            $boundary = $headerParts["boundary"];
168
        }
169
170
        try {
171
            // body
172 9
            $this->extractPart($stream, $boundary, $this->getTransferEncoding($partHeaders));
173 6
        } catch (Exception\EndOfPartReachedException $e) {
174 6
            $parts = [
175
                "type" => $contentType,
176 5
                "headers" => $partHeaders,
177 5
                "body" => $e->getData(),
178
                "boundary" => $boundary,
179 5
                "parts" => [],
180
            ];
181
        }
182
183
        if ($boundary) {
184
            $childContentType = null;
185
            while (!feof($stream)) {
186 5
                try {
187
                    $partHeaders = $this->extractHeaders($stream);
188 5
                    $childContentType = $this->extractValueHeader($this->getContentType($partHeaders));
189 5
190 5
                    if (stripos($childContentType, 'multipart/') !== false) {
191 5
                        $parts["parts"][] = $this->parseParts($stream, $partHeaders);
192 5
                        try {
193 5
                            $this->extractPart($stream, $boundary, $this->getTransferEncoding($partHeaders));
194
                        } catch (Exception\EndOfPartReachedException $e) {
195
                        }
196
                    } else {
197 5
                        $this->extractPart($stream, $boundary, $this->getTransferEncoding($partHeaders));
198 5
                    }
199
                } catch (Exception\EndOfPartReachedException $e) {
200
                    $parts["parts"][] = [
201
                        "type" => $childContentType,
202
                        "parent-type" => $contentType,
203 9
                        "headers" => $partHeaders,
204
                        "body" => $e->getData(),
205
                        "parts" => []
206 10
                    ];
207
208 10
                    if ($e instanceof Exception\EndOfMultiPartReachedException) {
209 10
                        break;
210 7
                    }
211
                }
212 8
            }
213
        }
214
        return $parts;
215
    }
216 10
217
    private function extractValueHeader($header): string
218 10
    {
219 10
        $pos = strpos($header, ';');
220
        if ($pos !== false) {
221
            return substr($header, 0, $pos);
222
        }
223
224
        return $header;
225 10
    }
226
227 10
    private function getContentType(array $partHeaders): string
228
    {
229 7
        if (array_key_exists('content-type', $partHeaders)) {
230 7
            return $partHeaders['content-type'];
231 7
        }
232 7
233 7
        return '';
234 7
    }
235
236
    private function extractHeaderParts(string $header): array
237
    {
238 7
        if (strpos($header, ';') !== false) {
239
            $parts = explode(";", $header);
240
            array_shift($parts);
241
            $p = [];
242
            $part = '';
243
            foreach ($parts as $pv) {
244
                if (preg_match('/="[^"]+$/', $pv)) {
245
                    $part = $pv;
246 7
                    continue;
247 6
                }
248
                if ($part !== '') {
249 6
                    $part .= ';' . $pv;
250 6
                    if (preg_match('/="[^"]+$/', $part)) {
251
                        continue;
252 7
                    }
253
254 8
                    $pv = $part;
255
                }
256
                if (strpos($pv, '=') === false) {
257
                    continue;
258
                }
259
                list ($k, $v) = explode("=", trim($pv), 2);
260
                $p[$k] = trim($v, '"');
261
            }
262 9
            return $p;
263
        }
264 9
265 9
        return [];
266 9
    }
267
268 9
    /**
269 6
     * @throws Exception\EndOfMultiPartReachedException
270 5
     * @throws Exception\EndOfPartReachedException
271
     */
272 6
    protected function extractPart($stream, ?string $boundary, string $encoding): void
273 5
    {
274
        $rows = [];
275
        while (!feof($stream)) {
276 9
            $row = fgets($stream);
277
278 4
            if ($boundary !== null) {
279
                if (strpos($row, "--$boundary--") === 0) {
280
                    throw new Exception\EndOfMultiPartReachedException($this->contentDecoder->decode(implode("", $rows), $encoding));
281 9
                }
282
                if (strpos($row, "--$boundary") === 0) {
283 9
                    throw new Exception\EndOfPartReachedException($this->contentDecoder->decode(implode("", $rows), $encoding));
284 2
                }
285
            }
286
            $rows[] = $row;
287 7
        }
288
        throw new Exception\EndOfMultiPartReachedException($this->contentDecoder->decode(implode("", $rows), $encoding));
289
    }
290 9
291
    private function getTransferEncoding(array $partHeaders): string
292 9
    {
293
        if (array_key_exists('content-transfer-encoding', $partHeaders)) {
294 9
            return $partHeaders['content-transfer-encoding'];
295 9
        }
296 9
297 9
        return '';
298 9
    }
299 9
300 9
    protected function createHeadersSet(array $headersRaw): \Swift_Mime_SimpleHeaderSet
301 9
    {
302 1
        $headers = \Swift_DependencyContainer::getInstance()->lookup('mime.headerset');
303 1
304 1
        foreach ($headersRaw as $name => $value) {
305
            switch (strtolower($name)) {
306
                case "content-type":
307 1
                    $parts = $this->extractHeaderParts($value);
308 9
                    unset($parts["boundary"]);
309 1
                    $headers->addParameterizedHeader($name, $this->extractValueHeader($value), $parts);
310 1
                    break;
311 9
                case "return-path":
312 9
                    if (preg_match_all('/([a-z][a-z0-9_\-\.]*@[a-z0-9\.\-]*\.[a-z]{2,5})/i', $value, $mch)) {
313 9
                        foreach ($mch[0] as $k => $mails) {
314 9
                            $headers->addPathHeader($name, $mch[1][$k]);
315 9
                        }
316 9
                    }
317 9
                    break;
318 9
                case "date":
319 9
                    $headers->addDateHeader($name, new \DateTime($value));
320
                    break;
321
                case "to":
322 9
                case "from":
323
                case "bcc":
324
                case "reply-to":
325
                case "cc":
326
                    $adresses = [];
327
                    if (preg_match_all('/(.*?)<([a-z][a-z0-9_\-\.]*@[a-z0-9\.\-]*\.[a-z]{2,5})>\s*[;,]*/i', $value, $mch)) {
328
                        foreach ($mch[0] as $k => $mail) {
329
                            if (!$mch[1][$k]) {
330 9
                                $adresses[$mch[2][$k]] = trim($mch[2][$k]);
331 9
                            } else {
332
                                $adresses[$mch[2][$k]] = trim($mch[1][$k]);
333 9
                            }
334 9
                        }
335
                    } elseif (preg_match_all('/([a-z][a-z0-9_\-\.]*@[a-z0-9\.\-]*\.[a-z]{2,5})/i', $value, $mch)) {
336
                        foreach ($mch[0] as $k => $mails) {
337 9
                            $adresses[$mch[1][$k]] = trim($mch[1][$k]);
338
                        }
339
                    }
340 9
                    $headers->addMailboxHeader($name, $adresses);
341
                    break;
342 9
                default:
343
                    $headers->addTextHeader($name, $value);
344 5
                    break;
345
            }
346 5
        }
347
        return $headers;
348 5
    }
349 5
350
    protected function createMessage(array $message, \Swift_Mime_SimpleMimeEntity $entity): void
351
    {
352
        if (!empty($message["parts"]) && stripos($message["type"], 'multipart/') !== false) {
353
            if (strpos($message["type"], '/alternative')) {
354 5
                $nestingLevel = \Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE;
355 5
            } elseif (strpos($message["type"], '/related')) {
356
                $nestingLevel = \Swift_Mime_SimpleMimeEntity::LEVEL_RELATED;
357 5
            } elseif (strpos($message["type"], '/mixed')) {
358 5
                $nestingLevel = \Swift_Mime_SimpleMimeEntity::LEVEL_MIXED;
359
            } else {
360 5
                $nestingLevel = \Swift_Mime_SimpleMimeEntity::LEVEL_TOP;
361
            }
362
363 5
            $childs = [];
364
            foreach ($message["parts"] as $part) {
365
                $headers = $this->createHeadersSet($part["headers"]);
366 5
                $encoder = $this->getEncoder($this->getTransferEncoding($part["headers"]));
367
368 5
                if (stripos($part["type"], 'multipart/') !== false) {
369 5
                    $newEntity = new \Swift_Mime_MimePart ($headers, $encoder, $this->getCache(), $this->getIdGenertor());
370 5
                } else {
371 5
                    $newEntity = new \Swift_Mime_SimpleMimeEntity ($headers, $encoder, $this->getCache(), $this->getIdGenertor());
372
                }
373 5
374
                $this->createMessage($part, $newEntity);
375
376 5
                $ref = new \ReflectionObject ($newEntity);
377 5
                $m = $ref->getMethod('setNestingLevel');
378
                $m->setAccessible(true);
379 9
                $m->invoke($newEntity, $nestingLevel);
380
381 9
                $childs[] = $newEntity;
382
            }
383 5
384
            $entity->setContentType($part["type"]);
0 ignored issues
show
The variable $part seems to be defined by a foreach iteration on line 364. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
385
            $entity->setChildren($childs);
386 5
        } else {
387
            $entity->setBody($message["body"], $message["type"]);
388 5
        }
389
    }
390 5
391
    protected function getEncoder(string $type): \Swift_Mime_ContentEncoder
392
    {
393 5
        switch ($type) {
394
            case "base64":
395
                return \Swift_DependencyContainer::getInstance()->lookup('mime.base64contentencoder');
396
            case "8bit":
397
                return \Swift_DependencyContainer::getInstance()->lookup('mime.8bitcontentencoder');
398
            case "7bit":
399
                return \Swift_DependencyContainer::getInstance()->lookup('mime.7bitcontentencoder');
400
            default:
401
                return \Swift_DependencyContainer::getInstance()->lookup('mime.qpcontentencoder');
402
        }
403
    }
404
}
405