Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
133 / 133
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Message
100.00% covered (success)
100.00%
133 / 133
100.00% covered (success)
100.00%
6 / 6
57
100.00% covered (success)
100.00%
1 / 1
 parseMessage
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 parseForm
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
9
 createForm
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
16
 parseHeaders
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 parseBody
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 parsePart
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
16
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Mime;
15
16use Pop\Mime\Part\Header;
17use Pop\Mime\Part\Body;
18
19/**
20 * MIME message class
21 *
22 * @category   Pop
23 * @package    Pop\Mime
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    1.1.0
28 */
29class Message extends Part
30{
31
32    /**
33     * Parse message
34     *
35     * @param  string $messageString
36     * @return Message
37     */
38    public static function parseMessage($messageString)
39    {
40        $headerString = substr($messageString, 0, strpos($messageString, "\r\n\r\n"));
41        $bodyString   = substr($messageString, (strpos($messageString, "\r\n\r\n") + 4));
42
43        $headers  = self::parseHeaders($headerString);
44        $boundary = null;
45        $parts    = [];
46
47        foreach ($headers as $header) {
48            if ($header->hasParameter('boundary')) {
49                $boundary = $header->getParameter('boundary');
50            }
51        }
52
53        $partStrings = self::parseBody($bodyString, $boundary);
54
55        foreach ($partStrings as $partString) {
56            $parts[] = self::parsePart($partString);
57        }
58
59        $message = new self();
60
61        if (!empty($headers)) {
62            $message->addHeaders($headers);
63        }
64        if (!empty($parts)) {
65            $message->addParts($parts);
66        }
67
68        return $message;
69    }
70
71    /**
72     * Parse form data
73     *
74     * @param  string $formString
75     * @return array
76     */
77    public static function parseForm($formString)
78    {
79        $form     = self::parseMessage($formString);
80        $formData = [];
81
82        foreach ($form->getParts() as $part) {
83            if ($part->hasHeader('Content-Disposition')) {
84                $disposition = $part->getHeader('Content-Disposition');
85                if (($disposition->getValue() == 'form-data') && ($disposition->hasParameter('name'))) {
86                    $name     = $disposition->getParameter('name');
87                    $contents = $part->getContents();
88                    $filename = ($disposition->hasParameter('filename')) ? $disposition->getParameter('filename') : null;
89
90                    if (substr($name, -2) == '[]') {
91                        $name = substr($name, 0, -2);
92                        if (!isset($formData[$name])) {
93                            $formData[$name] = [];
94                        }
95                        $formData[$name][] = $contents;
96                    } else {
97                        if (null !== $filename) {
98                            $formData[$name] = [
99                                'filename' => $filename,
100                                'contents' => $contents
101                            ];
102                        } else {
103                            $formData[$name] = $contents;
104                        }
105                    }
106                }
107            }
108        }
109
110        return $formData;
111    }
112
113    /**
114     * Create multipart form object
115     *
116     * @param  array  $fields
117     * @return Message
118     */
119    public static function createForm($fields = [])
120    {
121        $message = new self();
122        $header  = new Header('Content-Type', 'multipart/form-data', ['boundary' => $message->generateBoundary()]);
123        $message->addHeader($header);
124
125        if (!empty($fields)) {
126            foreach ($fields as $name => $value) {
127                if (is_array($value)) {
128                    // Is file
129                    if (isset($value['filename'])) {
130                        $parameters   = ['name' => $name];
131                        $contentType  = null;
132                        $fileContents = null;
133
134                        foreach ($value as $key => $val) {
135                            $key = strtolower($key);
136                            if ($key == 'filename') {
137                                $parameters['filename'] = basename($val);
138                            }
139                            if (($key == 'content-type') || ($key == 'contenttype') ||
140                                ($key == 'mime-type') || ($key == 'mimetype') || ($key == 'mime')) {
141                                $contentType = $val;
142                            }
143                        }
144
145                        if (isset($value['contents'])) {
146                            $fileContents = $value['contents'];
147                        } else if (file_exists($value['filename'])) {
148                            $fileContents = file_get_contents($value['filename']);
149                        }
150
151                        $fieldPart = new Part(new Header('Content-Disposition', 'form-data', $parameters));
152                        if (null !== $contentType) {
153                            $fieldPart->addHeader('Content-Type', $contentType);
154                        }
155                        $fieldPart->setBody(new Body($fileContents));
156                        $message->addPart($fieldPart);
157                    } else {
158                        foreach ($value as $val) {
159                            $fieldPart = new Part(
160                                new Header('Content-Disposition', 'form-data', ['name' => $name . '[]'])
161                            );
162                            $fieldPart->setBody(new Body($val, Body::RAW_URL));
163                            $message->addPart($fieldPart);
164                        }
165                    }
166                } else {
167                    $fieldPart = new Part(new Header('Content-Disposition', 'form-data', ['name' => $name]));
168                    $fieldPart->setBody(new Body($value, Body::RAW_URL));
169                    $message->addPart($fieldPart);
170                }
171            }
172        }
173
174        return $message;
175    }
176
177    /**
178     * Parse message header string
179     *
180     * @param  string $headerString
181     * @return array
182     */
183    public static function parseHeaders($headerString)
184    {
185        $headers = [];
186        $matches = [];
187        preg_match_all('/[a-zA-Z-]+:/', $headerString, $matches, PREG_OFFSET_CAPTURE);
188
189        if (isset($matches[0]) && (count($matches[0]) > 0)) {
190            $length = count($matches[0]);
191            for ($i = 0; $i < $length; $i++) {
192                if (isset($matches[0][$i + 1][1])) {
193                    $start  = $matches[0][$i][1] + strlen($matches[0][$i][0]);
194                    $offset = $matches[0][$i + 1][1];
195                    $value  = substr($headerString, 0, $offset);
196                    $value  = trim(substr($value, $start));
197                } else {
198                    $start  = strpos($headerString, $matches[0][$i][0]) + strlen($matches[0][$i][0]);
199                    $value  = substr($headerString, $start);
200                }
201                $headers[] = Header::parse($matches[0][$i][0] . ' ' . trim($value));
202            }
203        }
204
205        return $headers;
206    }
207
208    /**
209     * Parse message body string
210     *
211     * @param  string $bodyString
212     * @param  string $boundary
213     * @return array
214     */
215    public static function parseBody($bodyString, $boundary = null)
216    {
217        if (strpos($bodyString, '--' . $boundary) !== false) {
218            $parts = explode('--' . $boundary, $bodyString);
219            if ((strpos($bodyString, '--' . $boundary) > 0) && isset($parts[0])) {
220                unset($parts[0]);
221            }
222        } else {
223            $parts = [$bodyString];
224        }
225
226        $parts = array_values(array_filter(array_map('trim', $parts), function ($value) {
227            return (!empty($value) && ($value != '--'));
228        }));
229
230        return $parts;
231    }
232
233    /**
234     * Parse message part string
235     *
236     * @param  string $partString
237     * @return Part|array
238     */
239    public static function parsePart($partString)
240    {
241        $headers = [];
242
243        if (strpos($partString, "\r\n\r\n") !== false) {
244            $headerString = substr($partString, 0, strpos($partString, "\r\n\r\n"));
245            $bodyString   = trim(substr($partString, (strpos($partString, "\r\n\r\n") + 4)));
246            $headers      = self::parseHeaders($headerString);
247        } else {
248            $bodyString   = trim($partString);
249        }
250
251        $part = new Part();
252
253        if (!empty($headers)) {
254            $part->addHeaders($headers);
255        }
256
257        $boundary = null;
258        foreach ($part->getHeaders() as $header) {
259            if ($header->hasParameter('boundary')) {
260                $boundary = $header->getParameter('boundary');
261                break;
262            }
263        }
264
265        if (!empty($bodyString)) {
266            if (null !== $boundary) {
267                $subPartStrings = self::parseBody($bodyString, $boundary);
268                $subParts       = [];
269
270                foreach ($subPartStrings as $subPartString) {
271                    $subParts[] = self::parsePart($subPartString);
272                }
273                return $subParts;
274            } else {
275                $encoding = null;
276                $isFile   = (($part->hasHeader('Content-Disposition')) &&
277                    ($part->getHeader('Content-Disposition')->isAttachment()));
278                $isForm   = (($part->hasHeader('Content-Disposition')) &&
279                    ($part->getHeader('Content-Disposition')->getValue() == 'form-data'));
280                if ($part->hasHeader('Content-Transfer-Encoding')) {
281                    $encodingHeader = strtolower($part->getHeader('Content-Transfer-Encoding')->getValue());
282                    if ($encodingHeader == 'base64') {
283                        $encoding = Body::BASE64;
284                    } else if ($encodingHeader == 'quoted-printable') {
285                        $encoding = Body::QUOTED;
286                    }
287                } else if ($isForm) {
288                    $encoding = Body::RAW_URL;
289                }
290                $body = new Body($bodyString, $encoding);
291                if (null !== $encoding) {
292                    $body->setAsEncoded(true);
293                }
294                if ($isFile) {
295                    $body->setAsFile(true);
296                }
297                $part->setBody($body);
298            }
299        }
300
301        return $part;
302    }
303
304}