Issues (1844)

Security Analysis    not enabled

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

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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.

class/pear/Mail/mimeDecode.php (17 issues)

1
<?php
2
// +-----------------------------------------------------------------------+
3
// | Copyright (c) 2002  Richard Heyes                                     |
4
// | All rights reserved.                                                  |
5
// |                                                                       |
6
// | Redistribution and use in source and binary forms, with or without    |
7
// | modification, are permitted provided that the following conditions    |
8
// | are met:                                                              |
9
// |                                                                       |
10
// | o Redistributions of source code must retain the above copyright      |
11
// |   notice, this list of conditions and the following disclaimer.       |
12
// | o Redistributions in binary form must reproduce the above copyright   |
13
// |   notice, this list of conditions and the following disclaimer in the |
14
// |   documentation and/or other materials provided with the distribution.|
15
// | o The names of the authors may not be used to endorse or promote      |
16
// |   products derived from this software without specific prior written  |
17
// |   permission.                                                         |
18
// |                                                                       |
19
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
20
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
21
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
23
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
25
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
28
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
30
// |                                                                       |
31
// +-----------------------------------------------------------------------+
32
// | Author: Richard Heyes <[email protected]>                           |
33
// +-----------------------------------------------------------------------+
34
35
require_once XHELP_PEAR_PATH . '/PEAR.php';
36
37
/**
38
 *  +----------------------------- IMPORTANT ------------------------------+
39
 *  | Usage of this class compared to native php extensions such as        |
40
 *  | mailparse or imap, is slow and may be feature deficient. If available|
41
 *  | you are STRONGLY recommended to use the php extensions.              |
42
 *  +----------------------------------------------------------------------+
43
 *
44
 * Mime Decoding class
45
 *
46
 * This class will parse a raw mime email and return
47
 * the structure. Returned structure is similar to
48
 * that returned by imap_fetchstructure().
49
 *
50
 * USAGE: (assume $input is your raw email)
51
 *
52
 * $decode = new Mail_mimeDecode($input, "\r\n");
53
 * $structure = $decode->decode();
54
 * print_r($structure);
55
 *
56
 * Or statically:
57
 *
58
 * $params['input'] = $input;
59
 * $structure = Mail_mimeDecode::decode($params);
60
 * print_r($structure);
61
 *
62
 * TODO:
63
 *  - Implement further content types, eg. multipart/parallel,
64
 *    perhaps even message/partial.
65
 *
66
 * @author  Richard Heyes <[email protected]>
67
 * @version $Revision: 1.2 $
68
 */
69
class Mail_mimeDecode extends PEAR
70
{
71
    /**
72
     * The raw email to decode
73
     * @var string
74
     */
75
    public $_input;
76
    /**
77
     * The header part of the input
78
     * @var string
79
     */
80
    public $_header;
81
    /**
82
     * The body part of the input
83
     * @var string
84
     */
85
    public $_body;
86
    /**
87
     * If an error occurs, this is used to store the message
88
     * @var string
89
     */
90
    public $_error;
91
    /**
92
     * Flag to determine whether to include bodies in the
93
     * returned object.
94
     * @var bool
95
     */
96
    public $_include_bodies;
97
    /**
98
     * Flag to determine whether to decode bodies
99
     * @var bool
100
     */
101
    public $_decode_bodies;
102
    /**
103
     * Flag to determine whether to decode headers
104
     * @var bool
105
     */
106
    public $_decode_headers;
107
    /**
108
     * If invoked from a class, $this will be set. This has problematic
109
     * connotations for calling decode() statically. Hence this variable
110
     * is used to determine if we are indeed being called statically or
111
     * via an object.
112
     */
113
    public $mailMimeDecode;
114
115
    /**
116
     * Constructor.
117
     *
118
     * Sets up the object, initialise the variables, and splits and
119
     * stores the header and body of the input.
120
     *
121
     * @param mixed $input
122
     */
123
    public function __construct($input)
124
    {
125
        [$header, $body] = $this->_splitBodyHeader($input);
126
127
        $this->_input          = $input;
128
        $this->_header         = $header;
129
        $this->_body           = $body;
130
        $this->_decode_bodies  = false;
131
        $this->_include_bodies = true;
132
133
        $this->mailMimeDecode = true;
134
    }
135
136
    /**
137
     * Begins the decoding process. If called statically
138
     * it will create an object and call the decode() method
139
     * of it.
140
     *
141
     * @param null|mixed $params
142
     * @return object Decoded results
143
     */
144
    public function decode($params = null)
145
    {
146
        // Have we been called statically? If so, create an object and pass details to that.
147
        if (!isset($this->mailMimeDecode) && isset($params['input'])) {
148
            $obj       = new self($params['input']);
149
            $structure = $obj->decode($params);
150
            // Called statically but no input
151
        } elseif (isset($this->mailMimeDecode)) {
152
            $this->_include_bodies = $params['include_bodies'] ?? false;
153
            $this->_decode_bodies  = $params['decode_bodies'] ?? false;
154
            $this->_decode_headers = $params['decode_headers'] ?? false;
155
156
            $structure = $this->_decode($this->_header, $this->_body);
0 ignored issues
show
$this->_header of type string is incompatible with the type array expected by parameter $headers of Mail_mimeDecode::_decode(). ( Ignorable by Annotation )

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

156
            $structure = $this->_decode(/** @scrutinizer ignore-type */ $this->_header, $this->_body);
Loading history...
157
            if (false === $structure) {
158
                $structure = $this->raiseError($this->_error);
159
            }
160
        } else {
161
            return PEAR::raiseError('Called statically and no input given');
162
            // Called via an object
163
        }
164
165
        return $structure;
166
    }
167
168
    /**
169
     * Performs the decoding. Decodes the body string passed to it
170
     * If it finds certain content-types it will call itself in a
171
     * recursive fashion
172
     *
173
     * @param array  $headers
174
     * @param string $body
175
     * @param string $default_ctype
176
     * @return object|false Results of decoding process
177
     */
178
    public function _decode(array $headers, string $body, string $default_ctype = 'text/plain')
179
    {
180
        $return  = new stdClass();
181
        $headers = $this->_parseHeaders($headers);
182
183
        foreach ($headers as $value) {
184
            if (isset($return->headers[mb_strtolower($value['name'])]) && !is_array($return->headers[mb_strtolower($value['name'])])) {
185
                $return->headers[mb_strtolower($value['name'])]   = [$return->headers[mb_strtolower($value['name'])]];
186
                $return->headers[mb_strtolower($value['name'])][] = $value['value'];
187
            } elseif (isset($return->headers[mb_strtolower($value['name'])])) {
188
                $return->headers[mb_strtolower($value['name'])][] = $value['value'];
189
            } else {
190
                $return->headers[mb_strtolower($value['name'])] = $value['value'];
191
            }
192
        }
193
194
        reset($headers);
195
        //        while (list($key, $value) = each($headers)) {
196
        foreach ($headers as $key => $value) {
197
            $headers[$key]['name'] = \mb_strtolower($value['name']);
198
            switch ($headers[$key]['name']) {
199
                case 'content-type':
200
                    $content_type = $this->_parseHeaderValue($headers[$key]['value']);
201
202
                    if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
203
                        $return->ctype_primary   = $regs[1];
204
                        $return->ctype_secondary = $regs[2];
205
                    }
206
207
                    if (isset($content_type['other'])) {
208
                        foreach ($content_type['other'] as $p_name => $p_value) {
209
                            $return->ctype_parameters[$p_name] = $p_value;
210
                        }
211
                    }
212
                    break;
213
                case 'content-disposition':
214
                    $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
215
                    $return->disposition = $content_disposition['value'];
216
                    if (isset($content_disposition['other'])) {
217
                        foreach ($content_disposition['other'] as $p_name => $p_value) {
218
                            $return->d_parameters[$p_name] = $p_value;
219
                        }
220
                    }
221
                    break;
222
                case 'content-transfer-encoding':
223
                    $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
224
                    break;
225
            }
226
        }
227
228
        if (isset($content_type)) {
229
            switch (mb_strtolower($content_type['value'])) {
230
                case 'text/plain':
231
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
232
                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
233
                    break;
234
                case 'text/html':
235
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
236
                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
237
                    break;
238
                case 'multipart/parallel':
239
                case 'multipart/report': // RFC1892
240
                case 'multipart/signed': // PGP
241
                case 'multipart/digest':
242
                case 'multipart/alternative':
243
                case 'multipart/related':
244
                case 'multipart/mixed':
245
                    if (!isset($content_type['other']['boundary'])) {
246
                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
247
248
                        return false;
249
                    }
250
251
                    $default_ctype = ('multipart/digest' === \mb_strtolower($content_type['value'])) ? 'message/rfc822' : 'text/plain';
252
253
                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
254
                    foreach ($parts as $iValue) {
255
                        [$part_header, $part_body] = $this->_splitBodyHeader($iValue);
256
                        $part = $this->_decode($part_header, $part_body, $default_ctype);
257
                        if (false == $part) {
258
                            $part = $this->raiseError($this->_error);
259
                        }
260
                        $return->parts[] = $part;
261
                    }
262
                    break;
263
                case 'message/rfc822':
264
                    $obj             = new self($body);
265
                    $return->parts[] = $obj->decode(['include_bodies' => $this->_include_bodies]);
266
                    unset($obj);
267
                    break;
268
                default:
269
                    if (!isset($content_transfer_encoding['value'])) {
270
                        $content_transfer_encoding['value'] = '7bit';
271
                    }
272
                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
273
                    break;
274
            }
275
        } else {
276
            $ctype                   = explode('/', $default_ctype);
277
            $return->ctype_primary   = $ctype[0];
278
            $return->ctype_secondary = $ctype[1];
279
            $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
280
        }
281
282
        return $return;
283
    }
284
285
    /**
286
     * Given the output of the above function, this will return an
287
     * array of references to the parts, indexed by mime number.
288
     *
289
     * @param object $structure   The structure to go through
290
     * @param bool   $no_refs
291
     * @param string $mime_number Internal use only.
292
     * @param string $prepend
293
     * @return array  Mime numbers
294
     */
295
    public function &getMimeNumbers(object $structure, bool $no_refs, string $mime_number = '', string $prepend = ''): array
296
    {
297
        $return = [];
298
        if (!empty($structure->parts)) {
299
            if ('' != $mime_number) {
300
                $structure->mime_id              = $prepend . $mime_number;
301
                $return[$prepend . $mime_number] = &$structure;
302
            }
303
            foreach ($structure->parts as $i => $iValue) {
304
                if (!empty($structure->headers['content-type']) && 0 === mb_strpos(mb_strtolower($structure->headers['content-type']), 'message/')) {
305
                    $prepend      .= $mime_number . '.';
306
                    $_mime_number = '';
307
                } else {
308
                    $_mime_number = ('' == $mime_number ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
309
                }
310
311
                $arr = &self::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
0 ignored issues
show
Bug Best Practice introduced by
The method Mail_mimeDecode::getMimeNumbers() is not static, but was called statically. ( Ignorable by Annotation )

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

311
                $arr = &self::/** @scrutinizer ignore-call */ getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
Loading history...
312
                foreach ($arr as $key => $val) {
313
                    $no_refs ? $return[$key] = '' : $return[$key] = &$val;
314
                }
315
            }
316
        } else {
317
            if ('' === $mime_number) {
318
                $mime_number = '1';
319
            }
320
            $structure->mime_id = $prepend . $mime_number;
321
            $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
322
        }
323
324
        return $return;
325
    }
326
327
    /**
328
     * Given a string containing a header and body
329
     * section, this function will split them (at the first
330
     * blank line) and return them.
331
     *
332
     * @param mixed $input
333
     * @return array|false Contains header and body section
334
     */
335
    public function _splitBodyHeader($input)
336
    {
337
        if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
338
            return [$match[1], $match[2]];
339
        }
340
        $this->_error = 'Could not split header and body';
341
342
        return false;
343
    }
344
345
    /**
346
     * Parse headers given in $input and return
347
     * as assoc array.
348
     *
349
     * @param mixed $input
350
     * @return array Contains parsed headers
351
     */
352
    public function _parseHeaders($input): array
353
    {
354
        if ('' !== $input) {
355
            // Unfold the input
356
            $input   = preg_replace("/\r?\n/", "\r\n", $input);
357
            $input   = preg_replace("/\r\n(\t| )+/", ' ', $input);
358
            $headers = explode("\r\n", trim($input));
359
360
            foreach ($headers as $value) {
361
                $hdr_name  = mb_substr($value, 0, $pos = mb_strpos($value, ':'));
362
                $hdr_value = mb_substr($value, $pos + 1);
363
                if (' ' === $hdr_value[0]) {
364
                    $hdr_value = mb_substr($hdr_value, 1);
365
                }
366
367
                $return[] = [
368
                    'name'  => $hdr_name,
369
                    'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value,
370
                ];
371
            }
372
        } else {
373
            $return = [];
374
        }
375
376
        return $return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.
Loading history...
377
    }
378
379
    /**
380
     * Function to parse a header value,
381
     * extract first part, and any secondary
382
     * parts (after ;) This function is not as
383
     * robust as it could be. Eg. header comments
384
     * in the wrong place will probably break it.
385
     *
386
     * @param mixed $input
387
     * @return array Contains parsed result
388
     */
389
    public function _parseHeaderValue($input): array
390
    {
391
        if (false !== ($pos = mb_strpos($input, ';'))) {
392
            $return['value'] = trim(mb_substr($input, 0, $pos));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = array(); before regardless.
Loading history...
393
            $input           = trim(mb_substr($input, $pos + 1));
394
395
            if ('' !== $input) {
396
                // This splits on a semi-colon, if there's no preceeding backslash
397
                // Can't handle if it's in double quotes however. (Of course anyone
398
                // sending that needs a good slap).
399
                $parameters = preg_split('/\s*(?<!\\\\);\s*/i', $input);
400
401
                foreach ($parameters as $i => $iValue) {
402
                    $param_name  = mb_substr($iValue, 0, $pos = mb_strpos($iValue, '='));
403
                    $param_value = mb_substr($iValue, $pos + 1);
404
                    if ('"' === $param_value[0]) {
405
                        $param_value = mb_substr($param_value, 1, -1);
406
                    }
407
                    $return['other'][$param_name]                = $param_value;
408
                    $return['other'][mb_strtolower($param_name)] = $param_value;
409
                }
410
            }
411
        } else {
412
            $return['value'] = trim($input);
413
        }
414
415
        return $return;
416
    }
417
418
    /**
419
     * This function splits the input based
420
     * on the given boundary
421
     *
422
     * @param string $input
423
     * @param string $boundary
424
     * @return array Contains array of resulting mime parts
425
     */
426
    public function _boundarySplit(string $input, string $boundary): array
427
    {
428
        $tmp = explode('--' . $boundary, $input);
429
430
        for ($i = 1; $i < count($tmp) - 1; ++$i) {
431
            $parts[] = $tmp[$i];
432
        }
433
434
        return $parts;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parts does not seem to be defined for all execution paths leading up to this point.
Loading history...
435
    }
436
437
    /**
438
     * Given a header, this function will decode it
439
     * according to RFC2047. Probably not *exactly*
440
     * conformant, but it does pass all the given
441
     * examples (in RFC2047).
442
     *
443
     * @param mixed $input
444
     * @return string Decoded header value
445
     */
446
    public function _decodeHeader($input): string
447
    {
448
        // Remove white space between encoded-words
449
        $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
450
451
        // For each encoded-word...
452
        while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
453
            $encoded  = $matches[1];
454
            $charset  = $matches[2];
0 ignored issues
show
The assignment to $charset is dead and can be removed.
Loading history...
455
            $encoding = $matches[3];
456
            $text     = $matches[4];
457
458
            switch (mb_strtolower($encoding)) {
459
                case 'b':
460
                    $text = base64_decode($text, true);
461
                    break;
462
                case 'q':
463
                    $text = str_replace('_', ' ', $text);
464
                    preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
465
                    foreach ($matches[1] as $value) {
466
                        $text = str_replace('=' . $value, chr(hexdec($value)), $text);
0 ignored issues
show
It seems like hexdec($value) can also be of type double; however, parameter $codepoint of chr() 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

466
                        $text = str_replace('=' . $value, chr(/** @scrutinizer ignore-type */ hexdec($value)), $text);
Loading history...
467
                    }
468
                    break;
469
            }
470
471
            $input = str_replace($encoded, $text, $input);
472
        }
473
474
        return $input;
475
    }
476
477
    /**
478
     * Given a body string and an encoding type,
479
     * this function will decode and return it.
480
     *
481
     * @param mixed $input
482
     * @param mixed $encoding
483
     * @return string Decoded body
484
     */
485
    public function _decodeBody($input, $encoding = '7bit'): string
486
    {
487
        switch ($encoding) {
488
            case '7bit':
489
                return $input;
490
            case 'quoted-printable':
491
                return $this->_quotedPrintableDecode($input);
492
            case 'base64':
493
                return base64_decode($input, true);
494
            default:
495
                return $input;
496
        }
497
    }
498
499
    /**
500
     * Given a quoted-printable string, this
501
     * function will decode and return it.
502
     *
503
     * @param mixed $input
504
     * @return string Decoded body
505
     */
506
    public function _quotedPrintableDecode($input): string
507
    {
508
        // Remove soft line breaks
509
        $input = preg_replace("/=\r?\n/", '', $input);
510
511
        // Replace encoded characters
512
        $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
513
514
        return $input;
515
    }
516
517
    /**
518
     * Checks the input for uuencoded files and returns
519
     * an array of them. Can be called statically, eg:
520
     *
521
     * $files =& Mail_mimeDecode::uudecode($some_text);
522
     *
523
     * It will check for the begin 666 ... end syntax
524
     * however and won't just blindly decode whatever you
525
     * pass it.
526
     *
527
     * @param mixed $input
528
     * @return array Decoded bodies, filenames and permissions
529
     * @author Unknown
530
     */
531
    public function &uudecode($input): array
532
    {
533
        // Find all uuencoded sections
534
        preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
535
536
        for ($j = 0, $jMax = count($matches[3]); $j < $jMax; $j++) {
537
            $str      = $matches[3][$j];
538
            $filename = $matches[2][$j];
539
            $fileperm = $matches[1][$j];
540
541
            $file = '';
542
            $str  = preg_split("/\r?\n/", trim($str));
543
544
            foreach ($str as $i => $iValue) {
545
                $pos = 1;
546
                $d   = 0;
547
                $len = (((ord(mb_substr($iValue, 0, 1)) - 32) - ' ') & 077);
548
549
                while (($d + 3 <= $len) and ($pos + 4 <= mb_strlen($iValue))) {
550
                    $c0   = (ord(mb_substr($iValue, $pos, 1)) ^ 0x20);
551
                    $c1   = (ord(mb_substr($iValue, $pos + 1, 1)) ^ 0x20);
552
                    $c2   = (ord(mb_substr($iValue, $pos + 2, 1)) ^ 0x20);
553
                    $c3   = (ord(mb_substr($iValue, $pos + 3, 1)) ^ 0x20);
554
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
555
556
                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
557
558
                    $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
559
560
                    $pos += 4;
561
                    $d   += 3;
562
                }
563
564
                if (($d + 2 <= $len) && ($pos + 3 <= mb_strlen($iValue))) {
565
                    $c0   = (ord(mb_substr($iValue, $pos, 1)) ^ 0x20);
566
                    $c1   = (ord(mb_substr($iValue, $pos + 1, 1)) ^ 0x20);
567
                    $c2   = (ord(mb_substr($iValue, $pos + 2, 1)) ^ 0x20);
568
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
569
570
                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
571
572
                    $pos += 3;
573
                    $d   += 2;
574
                }
575
576
                if (($d + 1 <= $len) && ($pos + 2 <= mb_strlen($iValue))) {
577
                    $c0   = (ord(mb_substr($iValue, $pos, 1)) ^ 0x20);
578
                    $c1   = (ord(mb_substr($iValue, $pos + 1, 1)) ^ 0x20);
579
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
580
                }
581
            }
582
            $files[] = ['filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file];
583
        }
584
585
        return $files;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $files does not seem to be defined for all execution paths leading up to this point.
Loading history...
586
    }
587
588
    /**
589
     * getSendArray() returns the arguments required for Mail::send()
590
     * used to build the arguments for a mail::send() call
591
     *
592
     * Usage:
593
     * $mailtext = Full email (for example generated by a template)
594
     * $decoder = new Mail_mimeDecode($mailtext);
595
     * $parts =  $decoder->getSendArray();
596
     * if (!PEAR::isError($parts) {
597
     *     list($recipents,$headers,$body) = $parts;
598
     *     $mail = Mail::factory('smtp');
599
     *     $mail->send($recipents,$headers,$body);
600
     * } else {
601
     *     echo $parts->message;
602
     * }
603
     * @return array|\PEAR_Error array of recipeint, headers,body or Pear_Error
604
     * @author Alan Knowles <[email protected]>
605
     */
606
    public function getSendArray()
607
    {
608
        // prevent warning if this is not set
609
        $this->_decode_headers = false;
610
        $headerlist            = $this->_parseHeaders($this->_header);
611
        $to                    = '';
612
        if (!$headerlist) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $headerlist of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
613
            return $this->raiseError('Message did not contain headers');
614
        }
615
        foreach ($headerlist as $item) {
616
            $header[$item['name']] = $item['value'];
617
            switch (mb_strtolower($item['name'])) {
618
                case 'to':
619
                case 'cc':
620
                case 'bcc':
621
                    $to = ',' . $item['value'];
622
                // no break
623
                default:
624
                    break;
625
            }
626
        }
627
        if ('' === $to) {
0 ignored issues
show
The condition '' === $to is always true.
Loading history...
628
            return $this->raiseError('Message did not contain any recipents');
629
        }
630
        $to = mb_substr($to, 1);
631
632
        return [$to, $header, $this->_body];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $header seems to be defined by a foreach iteration on line 615. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
633
    }
634
635
    /**
636
     * Returns a xml copy of the output of
637
     * Mail_mimeDecode::decode. Pass the output in as the
638
     * argument. This function can be called statically. Eg:
639
     *
640
     * $output = $obj->decode();
641
     * $xml    = Mail_mimeDecode::getXML($output);
642
     *
643
     * The DTD used for this should have been in the package. Or
644
     * alternatively you can get it from cvs, or here:
645
     * https://www.phpguru.org/xmail/xmail.dtd.
646
     *
647
     * @param mixed $input
648
     * @return string XML version of input
649
     */
650
    public function getXML($input): string
651
    {
652
        $crlf   = "\r\n";
653
        $output = '<?xml version=\'1.0\'?>' . $crlf . '<!DOCTYPE email SYSTEM "https://www.phpguru.org/xmail/xmail.dtd">' . $crlf . '<email>' . $crlf . self::_getXML($input) . '</email>';
0 ignored issues
show
Bug Best Practice introduced by
The method Mail_mimeDecode::_getXML() is not static, but was called statically. ( Ignorable by Annotation )

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

653
        $output = '<?xml version=\'1.0\'?>' . $crlf . '<!DOCTYPE email SYSTEM "https://www.phpguru.org/xmail/xmail.dtd">' . $crlf . '<email>' . $crlf . self::/** @scrutinizer ignore-call */ _getXML($input) . '</email>';
Loading history...
654
655
        return $output;
656
    }
657
658
    /**
659
     * Function that does the actual conversion to xml. Does a single
660
     * mimepart at a time.
661
     *
662
     * @param mixed $input
663
     * @param mixed $indent
664
     * @return string XML version of input
665
     */
666
    public function _getXML($input, $indent = 1): string
667
    {
668
        $htab    = "\t";
669
        $crlf    = "\r\n";
670
        $output  = '';
671
        $headers = @(array)$input->headers;
672
673
        foreach ($headers as $hdr_name => $hdr_value) {
674
            // Multiple headers with this name
675
            if (is_array($hdr_value)) {
676
                for ($i = 0, $iMax = count($hdr_value); $i < $iMax; ++$i) {
677
                    $output .= self::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
0 ignored issues
show
Bug Best Practice introduced by
The method Mail_mimeDecode::_getXML_helper() is not static, but was called statically. ( Ignorable by Annotation )

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

677
                    $output .= self::/** @scrutinizer ignore-call */ _getXML_helper($hdr_name, $hdr_value[$i], $indent);
Loading history...
678
                }
679
                // Only one header of this sort
680
            } else {
681
                $output .= self::_getXML_helper($hdr_name, $hdr_value, $indent);
682
            }
683
        }
684
685
        if (!empty($input->parts)) {
686
            for ($i = 0, $iMax = count($input->parts); $i < $iMax; ++$i) {
687
                $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf . self::_getXML($input->parts[$i], $indent + 1) . str_repeat($htab, $indent) . '</mimepart>' . $crlf;
0 ignored issues
show
Bug Best Practice introduced by
The method Mail_mimeDecode::_getXML() is not static, but was called statically. ( Ignorable by Annotation )

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

687
                $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf . self::/** @scrutinizer ignore-call */ _getXML($input->parts[$i], $indent + 1) . str_repeat($htab, $indent) . '</mimepart>' . $crlf;
Loading history...
688
            }
689
        } elseif (isset($input->body)) {
690
            $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' . $input->body . ']]></body>' . $crlf;
691
        }
692
693
        return $output;
694
    }
695
696
    /**
697
     * Helper function to _getXML(). Returns xml of a header.
698
     *
699
     * @param mixed $hdr_name
700
     * @param mixed $hdr_value
701
     * @param mixed $indent
702
     * @return string XML version of input
703
     */
704
    public function _getXML_helper($hdr_name, $hdr_value, $indent): string
705
    {
706
        $htab   = "\t";
707
        $crlf   = "\r\n";
708
        $return = '';
0 ignored issues
show
The assignment to $return is dead and can be removed.
Loading history...
709
710
        $new_hdr_value = ('received' !== $hdr_name) ? self::_parseHeaderValue($hdr_value) : ['value' => $hdr_value];
0 ignored issues
show
Bug Best Practice introduced by
The method Mail_mimeDecode::_parseHeaderValue() is not static, but was called statically. ( Ignorable by Annotation )

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

710
        $new_hdr_value = ('received' !== $hdr_name) ? self::/** @scrutinizer ignore-call */ _parseHeaderValue($hdr_value) : ['value' => $hdr_value];
Loading history...
711
        $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
712
713
        // Sort out any parameters
714
        if (!empty($new_hdr_value['other'])) {
715
            foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
716
                $params[] = str_repeat($htab, $indent)
717
                            . $htab
718
                            . '<parameter>'
719
                            . $crlf
720
                            . str_repeat($htab, $indent)
721
                            . $htab
722
                            . $htab
723
                            . '<paramname>'
724
                            . htmlspecialchars($paramname, ENT_QUOTES | ENT_HTML5)
725
                            . '</paramname>'
726
                            . $crlf
727
                            . str_repeat($htab, $indent)
728
                            . $htab
729
                            . $htab
730
                            . '<paramvalue>'
731
                            . htmlspecialchars($paramvalue, ENT_QUOTES | ENT_HTML5)
732
                            . '</paramvalue>'
733
                            . $crlf
734
                            . str_repeat($htab, $indent)
735
                            . $htab
736
                            . '</parameter>'
737
                            . $crlf;
738
            }
739
740
            $params = implode('', $params);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params seems to be defined by a foreach iteration on line 715. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
741
        } else {
742
            $params = '';
743
        }
744
745
        $return = str_repeat($htab, $indent) . '<header>' . $crlf . str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name, ENT_QUOTES | ENT_HTML5) . '</headername>' . $crlf . str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars(
746
                $new_hdr_value['value'],
747
                ENT_QUOTES | ENT_HTML5
748
            ) . '</headervalue>' . $crlf . $params . str_repeat($htab, $indent) . '</header>' . $crlf;
749
750
        return $return;
751
    }
752
} // End of class
753