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/Net/IMAPProtocol.php (79 issues)

1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PHP Version 4                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1997-2003 The PHP Group                                |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to version 2.02 of the PHP license,      |
9
// | that is bundled with this package in the file LICENSE, and is        |
10
// | available at through the world-wide-web at                           |
11
// | https://www.php.net/license/2_02.txt.                                 |
12
// | If you did not receive a copy of the PHP license and are unable to   |
13
// | obtain it through the world-wide-web, please send a note to          |
14
// | [email protected] so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
// | Author: Damian Alejandro Fernandez Sosa <[email protected]>       |
17
// +----------------------------------------------------------------------+
18
require_once XHELP_PEAR_PATH . '/Net/Socket.php';
19
20
/**
21
 * Provides an implementation of the IMAP protocol using PEAR's
22
 * Net_Socket:: class.
23
 *
24
 * @author  Damian Alejandro Fernandez Sosa <[email protected]>
25
 */
26
class Net_IMAPProtocol
27
{
28
    /**
29
     * The auth methods this class support
30
     * @var array
31
     */
32
    public $supportedAuthMethods = ['DIGEST-MD5', 'CRAM-MD5', 'LOGIN'];
33
    /**
34
     * The auth methods this class support
35
     * @var array
36
     */
37
    public $supportedSASLAuthMethods = ['DIGEST-MD5', 'CRAM-MD5'];
38
    /**
39
     * _serverAuthMethods
40
     * @var bool
41
     */
42
    public $_serverAuthMethods = null;
43
    /**
44
     * The the current mailbox
45
     * @var string
46
     */
47
    public $currentMailbox = 'INBOX';
48
    /**
49
     * The socket resource being used to connect to the IMAP server.
50
     * @var resource
51
     */
52
    public $_socket = null;
53
    /**
54
     * To allow class debuging
55
     * @var bool
56
     */
57
    public $_debug    = false;
58
    public $dbgDialog = '';
59
    /**
60
     * Command Number
61
     * @var int
62
     */
63
    public $_cmd_counter = 1;
64
    /**
65
     * Command Number for IMAP commands
66
     * @var int
67
     */
68
    public $_lastCmdID = 1;
69
    /**
70
     * Command Number
71
     * @var bool
72
     */
73
    public $_unParsedReturn = false;
74
    /**
75
     * _connected: checks if there is a connection made to a imap server or not
76
     * @var bool
77
     */
78
    public $_connected = false;
79
    /**
80
     * Capabilities
81
     * @var bool
82
     */
83
    public $_serverSupportedCapabilities = null;
84
    /**
85
     * Use UTF-7 funcionallity
86
     * @var bool
87
     */
88
    //var $_useUTF_7 = false;
89
    public $_useUTF_7 = true;
90
91
    /**
92
     * Constructor
93
     *
94
     * Instantiates a new Net_IMAP object.
95
     *
96
     * @since  1.0
97
     */
98
    public function __construct()
99
    {
100
        $this->_socket = new Net_Socket();
0 ignored issues
show
Documentation Bug introduced by
It seems like new Net_Socket() of type Net_Socket is incompatible with the declared type resource of property $_socket.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
101
102
        /*
103
         * Include the Auth_SASL package.  If the package is not available,
104
         * we disable the authentication methods that depend upon it.
105
         */
106
107
        if (false === (@require_once __DIR__ . '/Auth/SASL.php')) {
108
            foreach ($this->supportedSASLAuthMethods as $SASLMethod) {
109
                $pos = array_search($SASLMethod, $this->supportedAuthMethods, true);
110
                unset($this->supportedAuthMethods[$pos]);
111
            }
112
        }
113
    }
114
115
    /**
116
     * Attempt to connect to the IMAP server.
117
     *
118
     * @param string $host
119
     * @param int    $port
120
     * @return bool|\PEAR_Error Returns a PEAR_Error with an error message on any
121
     *               kind of failure, or true on success.
122
     * @since  1.0
123
     */
124
    public function cmdConnect(string $host = 'localhost', int $port = 143)
125
    {
126
        if ($this->_connected) {
127
            return new PEAR_Error('already connected, logout first!');
128
        }
129
        if (PEAR::isError($this->_socket->connect($host, $port))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

129
        if (PEAR::/** @scrutinizer ignore-call */ isError($this->_socket->connect($host, $port))) {
Loading history...
130
            return new PEAR_Error('unable to open socket');
131
        }
132
        if (PEAR::isError($this->_getRawResponse())) {
133
            return new PEAR_Error('unable to open socket');
134
        }
135
        $this->_connected = true;
136
137
        return true;
138
    }
139
140
    /**
141
     * get the cmd ID
142
     *
143
     * @return string Returns the CmdID and increment the counter
144
     *
145
     * @since  1.0
146
     */
147
    public function _getCmdId()
148
    {
149
        $this->_lastCmdID = 'A000' . $this->_cmd_counter;
0 ignored issues
show
Documentation Bug introduced by
The property $_lastCmdID was declared of type integer, but 'A000' . $this->_cmd_counter is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
150
        $this->_cmd_counter++;
151
152
        return $this->_lastCmdID;
153
    }
154
155
    /**
156
     * get the last cmd ID
157
     *
158
     * @return int Returns the last cmdId
159
     *
160
     * @since  1.0
161
     */
162
    public function getLastCmdId(): int
163
    {
164
        return $this->_lastCmdID;
165
    }
166
167
    /**
168
     * get current mailbox name
169
     *
170
     * @return string Returns the current mailbox
171
     *
172
     * @since  1.0
173
     */
174
    public function getCurrentMailbox(): string
175
    {
176
        return $this->currentMailbox;
177
    }
178
179
    /**
180
     * Sets the debuging information on or off
181
     *
182
     * @param mixed $debug
183
     *
184
     * @since  1.0
185
     */
186
    public function setDebug($debug = true): void
187
    {
188
        $this->_debug = $debug;
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    public function getDebugDialog(): string
195
    {
196
        return $this->dbgDialog;
197
    }
198
199
    /**
200
     * Send the given string of data to the server.
201
     *
202
     * @param string $data The string of data to send.
203
     *
204
     * @return bool|\PEAR_Error True on success or a PEAR_Error object on failure.
205
     *
206
     * @since   1.0
207
     */
208
    public function _send(string $data)
209
    {
210
        if ($this->_socket->eof()) {
211
            return new PEAR_Error('Failed to write to socket: (connection lost!) ');
212
        }
213
        if (PEAR::isError($error = $this->_socket->write($data))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

213
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_socket->write($data))) {
Loading history...
214
            return new PEAR_Error('Failed to write to socket: ' . $error->getMessage());
215
        }
216
217
        if ($this->_debug) {
218
            // C: means this data was sent by  the client (this class)
219
            echo "C: $data";
220
            $this->dbgDialog .= "C: $data";
221
        }
222
223
        return true;
224
    }
225
226
    /**
227
     * Receive the given string of data from the server.
228
     *
229
     * @return mixed a line of response on success or a PEAR_Error object on failure.
230
     *
231
     * @since   1.0
232
     */
233
    public function _recvLn()
234
    {
235
        if (PEAR::isError($this->lastline = $this->_socket->gets(8192))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

235
        if (PEAR::/** @scrutinizer ignore-call */ isError($this->lastline = $this->_socket->gets(8192))) {
Loading history...
Bug Best Practice introduced by
The property lastline does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
236
            return new PEAR_Error('Failed to write to socket: ' . $this->lastline->getMessage());
237
        }
238
        if ($this->_debug) {
239
            // S: means this data was sent by  the IMAP Server
240
            echo 'S: ' . $this->lastline . '';
241
            $this->dbgDialog .= 'S: ' . $this->lastline . '';
242
        }
243
        if ('' === $this->lastline) {
244
            return new PEAR_Error('Failed to receive from the  socket: ');
245
        }
246
247
        return $this->lastline;
248
    }
249
250
    /**
251
     * Send a command to the server with an optional string of arguments.
252
     * A carriage return / linefeed (CRLF) sequence will be appended to each
253
     * command string before it is sent to the IMAP server.
254
     *
255
     * @param string $commandId The IMAP cmdID to send to the server.
256
     * @param string $command   The IMAP command to send to the server.
257
     * @param string $args      A string of optional arguments to append
258
     *                          to the command.
259
     *
260
     * @return bool|\PEAR_Error The result of the _send() call.
261
     *
262
     * @since   1.0
263
     */
264
    public function _putCMD(string $commandId, string $command, string $args = '')
265
    {
266
        if (!empty($args)) {
267
            return $this->_send($commandId . ' ' . $command . ' ' . $args . "\r\n");
268
        }
269
270
        return $this->_send($commandId . ' ' . $command . "\r\n");
271
    }
272
273
    /**
274
     * Get a response from the server with an optional string of commandID.
275
     * A carriage return / linefeed (CRLF) sequence will be appended to each
276
     * command string before it is sent to the IMAP server.
277
     *
278
     * @param string $commandId
279
     * @return string The result response.
280
     */
281
    public function _getRawResponse(string $commandId = '*'): string
282
    {
283
        $arguments = '';
284
        while (!PEAR::isError($this->_recvLn())) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

284
        while (!PEAR::/** @scrutinizer ignore-call */ isError($this->_recvLn())) {
Loading history...
285
            $reply_code = strtok($this->lastline, ' ');
286
            $arguments  .= $this->lastline;
287
            if (!strcmp($commandId, $reply_code)) {
288
                return $arguments;
289
            }
290
        }
291
292
        return $arguments;
293
    }
294
295
    /**
296
     * get the "returning of the unparsed response" feature status
297
     *
298
     * @return bool return if the unparsed response is returned or not
299
     *
300
     * @since  1.0
301
     */
302
    public function getUnparsedResponse(): bool
303
    {
304
        return $this->_unParsedReturn;
305
    }
306
307
    /**
308
     * set the "returning of the unparsed response" feature on or off
309
     *
310
     * @param bool $status : true: feature is on
311
     *
312
     * @since  1.0
313
     */
314
    public function setUnparsedResponse(bool $status): void
315
    {
316
        $this->_unParsedReturn = $status;
317
    }
318
319
    /**
320
     * Attempt to login to the iMAP server.
321
     *
322
     * @param mixed $uid
323
     * @param mixed $pwd
324
     *
325
     * @return array Returns an array containing the response
326
     *
327
     * @since  1.0
328
     */
329
    public function cmdLogin($uid, $pwd)
330
    {
331
        $param = "\"$uid\" \"$pwd\"";
332
333
        return $this->_genericCommand('LOGIN', $param);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCommand('LOGIN', $param) also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
334
    }
335
336
    /**
337
     * Attempt to authenticate to the iMAP server.
338
     * @param mixed      $uid
339
     * @param mixed      $pwd
340
     * @param null|mixed $userMethod
341
     *
342
     * @return array|\PEAR_Error Returns an array containing the response
343
     *
344
     * @since  1.0
345
     */
346
    public function cmdAuthenticate($uid, $pwd, $userMethod = null)
347
    {
348
        if (!$this->_connected) {
349
            return new PEAR_Error('not connected!');
350
        }
351
352
        $cmdid = $this->_getCmdId();
353
354
        if (PEAR::isError($method = $this->_getBestAuthMethod($userMethod))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

354
        if (PEAR::/** @scrutinizer ignore-call */ isError($method = $this->_getBestAuthMethod($userMethod))) {
Loading history...
355
            return $method;
356
        }
357
358
        switch ($method) {
359
            case 'DIGEST-MD5':
360
                $result = $this->_authDigest_MD5($uid, $pwd, $cmdid);
0 ignored issues
show
The assignment to $result is dead and can be removed.
Loading history...
$cmdid of type string is incompatible with the type integer expected by parameter $cmdid of Net_IMAPProtocol::_authDigest_MD5(). ( Ignorable by Annotation )

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

360
                $result = $this->_authDigest_MD5($uid, $pwd, /** @scrutinizer ignore-type */ $cmdid);
Loading history...
361
                break;
362
            case 'CRAM-MD5':
363
                $result = $this->_authCRAM_MD5($uid, $pwd, $cmdid);
0 ignored issues
show
$cmdid of type string is incompatible with the type integer expected by parameter $cmdid of Net_IMAPProtocol::_authCRAM_MD5(). ( Ignorable by Annotation )

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

363
                $result = $this->_authCRAM_MD5($uid, $pwd, /** @scrutinizer ignore-type */ $cmdid);
Loading history...
364
                break;
365
            case 'LOGIN':
366
                $result = $this->_authLOGIN($uid, $pwd, $cmdid);
0 ignored issues
show
$cmdid of type string is incompatible with the type integer expected by parameter $cmdid of Net_IMAPProtocol::_authLOGIN(). ( Ignorable by Annotation )

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

366
                $result = $this->_authLOGIN($uid, $pwd, /** @scrutinizer ignore-type */ $cmdid);
Loading history...
367
                break;
368
            default:
369
                $result = new PEAR_Error("$method is not a supported authentication method");
370
                break;
371
        }
372
373
        $args = $this->_getRawResponse($cmdid);
374
375
        return $this->_genericImapResponseParser($args, $cmdid);
376
    }
377
378
    /* Authenticates the user using the DIGEST-MD5 method.
379
     *
380
     * @param string $uid The userid to authenticate as.
381
     * @param string $pwd The password to authenticate with.
382
     * @param string $cmdid The cmdID.
383
     *
384
     * @return array Returns an array containing the response
385
     *
386
     * @access private
387
     * @since  1.0
388
     */
389
390
    /**
391
     * @param int $uid
392
     * @param string $pwd
393
     * @param int $cmdid
394
     * @return bool|mixed|\PEAR_Error
395
     */
396
    public function _authDigest_MD5(int $uid, string $pwd, int $cmdid)
397
    {
398
        if (PEAR::isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'DIGEST-MD5'))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

398
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'DIGEST-MD5'))) {
Loading history...
399
            return $error;
400
        }
401
402
        if (PEAR::isError($args = $this->_recvLn())) {
403
            return $args;
404
        }
405
406
        $this->_getNextToken($args, $plus);
407
408
        $this->_getNextToken($args, $space);
409
410
        $this->_getNextToken($args, $challenge);
411
412
        $challenge = base64_decode($challenge, true);
413
414
        $digest = &Auth_SASL::factory('digestmd5');
0 ignored issues
show
The type Auth_SASL was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
415
416
        $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, 'localhost', 'imap'));
417
418
        if (PEAR::isError($error = $this->_send("$auth_str\r\n"))) {
419
            return $error;
420
        }
421
422
        if (PEAR::isError($args = $this->_recvLn())) {
423
            return $args;
424
        }
425
        /*
426
         * We don't use the protocol's third step because IMAP doesn't allow
427
         * subsequent authentication, so we just silently ignore it.
428
         */
429
        if (PEAR::isError($error = $this->_send("\r\n"))) {
430
            return $error;
431
        }
432
    }
433
434
    /* Authenticates the user using the CRAM-MD5 method.
435
     *
436
     * @param string $uid The userid to authenticate as.
437
     * @param string $pwd The password to authenticate with.
438
     * @param string $cmdid The cmdID.
439
     *
440
     * @return array Returns an array containing the response
441
     *
442
     * @access private
443
     * @since  1.0
444
     */
445
446
    /**
447
     * @param int $uid
448
     * @param string $pwd
449
     * @param int $cmdid
450
     * @return bool|mixed|\PEAR_Error
451
     */
452
    public function _authCRAM_MD5(int $uid, string $pwd, int $cmdid)
453
    {
454
        if (PEAR::isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'CRAM-MD5'))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

454
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'CRAM-MD5'))) {
Loading history...
455
            return $error;
456
        }
457
458
        if (PEAR::isError($args = $this->_recvLn())) {
459
            return $args;
460
        }
461
462
        $this->_getNextToken($args, $plus);
463
464
        $this->_getNextToken($args, $space);
465
466
        $this->_getNextToken($args, $challenge);
467
468
        $challenge = base64_decode($challenge, true);
469
470
        $cram = &Auth_SASL::factory('crammd5');
471
472
        $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
473
474
        if (PEAR::isError($error = $this->_send($auth_str . "\r\n"))) {
475
            return $error;
476
        }
477
    }
478
479
    /* Authenticates the user using the LOGIN method.
480
     *
481
     * @param string $uid The userid to authenticate as.
482
     * @param string $pwd The password to authenticate with.
483
     * @param string $cmdid The cmdID.
484
     *
485
     * @return array Returns an array containing the response
486
     *
487
     * @access private
488
     * @since  1.0
489
     */
490
491
    /**
492
     * @param int $uid
493
     * @param string $pwd
494
     * @param int $cmdid
495
     * @return bool|mixed|\PEAR_Error
496
     */
497
    public function _authLOGIN(int $uid, string $pwd, int $cmdid)
498
    {
499
        if (PEAR::isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'LOGIN'))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

499
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_putCMD($cmdid, 'AUTHENTICATE', 'LOGIN'))) {
Loading history...
500
            return $error;
501
        }
502
503
        if (PEAR::isError($args = $this->_recvLn())) {
504
            return $args;
505
        }
506
507
        $this->_getNextToken($args, $plus);
508
509
        $this->_getNextToken($args, $space);
510
511
        $this->_getNextToken($args, $challenge);
512
513
        $challenge = base64_decode($challenge, true);
0 ignored issues
show
The assignment to $challenge is dead and can be removed.
Loading history...
514
515
        $auth_str = base64_encode((string)$uid);
516
517
        if (PEAR::isError($error = $this->_send($auth_str . "\r\n"))) {
518
            return $error;
519
        }
520
521
        if (PEAR::isError($args = $this->_recvLn())) {
522
            return $args;
523
        }
524
525
        $auth_str = base64_encode((string)$pwd);
526
527
        if (PEAR::isError($error = $this->_send($auth_str . "\r\n"))) {
528
            return $error;
529
        }
530
    }
531
532
    /**
533
     * Returns the name of the best authentication method that the server
534
     * has advertised.
535
     *
536
     * @param string|null $userMethod
537
     * @return mixed Returns a string containing the name of the best
538
     *               supported authentication method or a PEAR_Error object
539
     *               if a failure condition is encountered.
540
     * @since  1.0
541
     */
542
    public function _getBestAuthMethod(string $userMethod = null)
543
    {
544
        $this->cmdCapability();
545
546
        if (null !== $userMethod) {
547
            $methods = [];
548
549
            $methods[] = $userMethod;
550
        } else {
551
            $methods = $this->supportedAuthMethods;
552
        }
553
554
        if ((null !== $methods) && (null !== $this->_serverAuthMethods)) {
555
            foreach ($methods as $method) {
556
                if (in_array($method, $this->_serverAuthMethods, true)) {
0 ignored issues
show
$this->_serverAuthMethods of type boolean is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

556
                if (in_array($method, /** @scrutinizer ignore-type */ $this->_serverAuthMethods, true)) {
Loading history...
557
                    return $method;
558
                }
559
            }
560
            $serverMethods = implode(',', $this->_serverAuthMethods);
0 ignored issues
show
$this->_serverAuthMethods of type boolean is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

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

560
            $serverMethods = implode(',', /** @scrutinizer ignore-type */ $this->_serverAuthMethods);
Loading history...
561
            $myMethods     = implode(',', $this->supportedAuthMethods);
562
563
            return new PEAR_Error("$method NOT supported authentication method!. This IMAP server " . "supports these methods: $serverMethods, but I support $myMethods");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $method seems to be defined by a foreach iteration on line 555. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
564
        }
565
566
        return new PEAR_Error("This IMAP server don't support any Auth methods");
567
    }
568
569
    /**
570
     * Attempt to disconnect from the iMAP server.
571
     *
572
     * @return array|string Returns an array containing the response
573
     *
574
     * @since  1.0
575
     */
576
    public function cmdLogout()
577
    {
578
        if (!$this->_connected) {
579
            return new PEAR_Error('not connected!');
0 ignored issues
show
Bug Best Practice introduced by
The expression return new PEAR_Error('not connected!') returns the type PEAR_Error which is incompatible with the documented return type array|string.
Loading history...
580
        }
581
582
        $cmdid = $this->_getCmdId();
583
        if (PEAR::isError($error = $this->_putCMD($cmdid, 'LOGOUT'))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

583
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_putCMD($cmdid, 'LOGOUT'))) {
Loading history...
584
            return $error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $error returns the type PEAR_Error|true which is incompatible with the documented return type array|string.
Loading history...
585
        }
586
        if (PEAR::isError($args = $this->_getRawResponse())) {
587
            return $args;
588
        }
589
        if (PEAR::isError($this->_socket->disconnect())) {
590
            return new PEAR_Error('socket disconnect failed');
0 ignored issues
show
Bug Best Practice introduced by
The expression return new PEAR_Error('socket disconnect failed') returns the type PEAR_Error which is incompatible with the documented return type array|string.
Loading history...
591
        }
592
593
        return $args;
594
        // not for now
595
        //return $this->_genericImapResponseParser($args,$cmdid);
596
    }
597
598
    /**
599
     * Send the NOOP command.
600
     *
601
     * @return array Returns an array containing the response
602
     *
603
     * @since  1.0
604
     */
605
    public function cmdNoop()
606
    {
607
        return $this->_genericCommand('NOOP');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCommand('NOOP') also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
608
    }
609
610
    /**
611
     * Send the CHECK command.
612
     *
613
     * @return array Returns an array containing the response
614
     *
615
     * @since  1.0
616
     */
617
    public function cmdCheck()
618
    {
619
        return $this->_genericCommand('CHECK');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCommand('CHECK') also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
620
    }
621
622
    /**
623
     * Send the  Select Mailbox Command
624
     *
625
     * @param mixed $mailbox
626
     *
627
     * @return array Returns an array containing the response
628
     *
629
     * @since  1.0
630
     */
631
    public function cmdSelect($mailbox)
632
    {
633
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
634
        if (!PEAR::isError($ret = $this->_genericCommand('SELECT', $mailbox_name))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

634
        if (!PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_genericCommand('SELECT', $mailbox_name))) {
Loading history...
635
            $this->currentMailbox = $mailbox;
636
        }
637
638
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
639
    }
640
641
    /**
642
     * Send the  EXAMINE  Mailbox Command
643
     *
644
     * @param mixed $mailbox
645
     * @return array Returns an array containing the response
646
     *
647
     * @since  1.0
648
     */
649
    public function cmdExamine($mailbox): array
650
    {
651
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
652
        $ret          = $this->_genericCommand('EXAMINE', $mailbox_name);
653
        $parsed       = '';
654
        if (isset($ret['PARSED'])) {
655
            foreach ($ret['PARSED'] as $i => $iValue) {
656
                $command               = $ret['PARSED'][$i]['EXT'];
657
                $parsed[key($command)] = $command[key($command)];
658
            }
659
        }
660
661
        return ['PARSED' => $parsed, 'RESPONSE' => $ret['RESPONSE']];
662
    }
663
664
    /**
665
     * Send the  CREATE Mailbox Command
666
     *
667
     * @param mixed $mailbox
668
     * @return array Returns an array containing the response
669
     *
670
     * @since  1.0
671
     */
672
    public function cmdCreate($mailbox)
673
    {
674
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
675
676
        return $this->_genericCommand('CREATE', $mailbox_name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCo...CREATE', $mailbox_name) also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
677
    }
678
679
    /**
680
     * Send the  RENAME Mailbox Command
681
     *
682
     * @param mixed $mailbox
683
     * @param mixed $new_mailbox
684
     *
685
     * @return array Returns an array containing the response
686
     *
687
     * @since  1.0
688
     */
689
    public function cmdRename($mailbox, $new_mailbox)
690
    {
691
        $mailbox_name     = sprintf('"%s"', $this->utf_7_encode($mailbox));
692
        $new_mailbox_name = sprintf('"%s"', $this->utf_7_encode($new_mailbox));
693
694
        return $this->_genericCommand('RENAME', "$mailbox_name $new_mailbox_name");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCo....' '.$new_mailbox_name) also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
695
    }
696
697
    /**
698
     * Send the  DELETE Mailbox Command
699
     *
700
     * @param mixed $mailbox
701
     *
702
     * @return array Returns an array containing the response
703
     *
704
     * @since  1.0
705
     */
706
    public function cmdDelete($mailbox)
707
    {
708
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
709
710
        return $this->_genericCommand('DELETE', $mailbox_name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCo...DELETE', $mailbox_name) also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
711
    }
712
713
    /**
714
     * Send the  SUSCRIBE  Mailbox Command
715
     *
716
     * @param mixed $mailbox
717
     *
718
     * @return array Returns an array containing the response
719
     *
720
     * @since  1.0
721
     */
722
    public function cmdSubscribe($mailbox)
723
    {
724
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
725
726
        return $this->_genericCommand('SUBSCRIBE', $mailbox_name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_genericCo...SCRIBE', $mailbox_name) also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
727
    }
728
729
    /**
730
     * Send the  UNSUSCRIBE  Mailbox Command
731
     *
732
     * @param string $mailbox
733
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
734
     *               kind of failure, or true on success.
735
     * @since  1.0
736
     */
737
    public function cmdUnsubscribe(string $mailbox)
738
    {
739
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
740
741
        return $this->_genericCommand('UNSUBSCRIBE', $mailbox_name);
742
    }
743
744
    /**
745
     * Send the  FETCH Command
746
     *
747
     * @param mixed $msgset
748
     * @param mixed $fetchparam
749
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
750
     *               kind of failure, or true on success.
751
     * @since  1.0
752
     */
753
    public function cmdFetch($msgset, $fetchparam)
754
    {
755
        return $this->_genericCommand('FETCH', "$msgset $fetchparam");
756
    }
757
758
    /**
759
     * Send the  CAPABILITY Command
760
     *
761
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
762
     *               kind of failure, or true on success.
763
     * @since  1.0
764
     */
765
    public function cmdCapability()
766
    {
767
        $ret = $this->_genericCommand('CAPABILITY');
768
769
        if (isset($ret['PARSED'])) {
770
            $ret['PARSED'] = $ret['PARSED'][0]['EXT']['CAPABILITY'];
771
            //fill the $this->_serverAuthMethods and $this->_serverSupportedCapabilities arrays
772
            foreach ($ret['PARSED']['CAPABILITIES'] as $auth_method) {
773
                if (0 === mb_stripos($auth_method, 'AUTH=')) {
774
                    $this->_serverAuthMethods[] = mb_substr($auth_method, 5);
775
                }
776
            }
777
            // Keep the capabilities response to use ir later
778
            $this->_serverSupportedCapabilities = $ret['PARSED']['CAPABILITIES'];
779
        }
780
781
        return $ret;
782
    }
783
784
    /**
785
     * Send the  STATUS Mailbox Command
786
     *
787
     * @param string $mailbox  the mailbox name
788
     * @param string $request  the request status it could be:
789
     *                         MESSAGES | RECENT | UIDNEXT
790
     *                         UIDVALIDITY | UNSEEN
791
     * @return array  Returns a Parsed Response
792
     *
793
     * @since  1.0
794
     */
795
    public function cmdStatus(string $mailbox, string $request)
796
    {
797
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
798
799
        if ('MESSAGES' !== $request && 'RECENT' !== $request && 'UIDNEXT' !== $request
800
            && 'UIDVALIDITY' !== $request
801
            && 'UNSEEN' !== $request) {
802
            // TODO:  fix this error!
803
            $this->_prot_error("request '$request' is invalid! see RFC2060!!!!", __LINE__, __FILE__, false);
804
        }
805
        $ret = $this->_genericCommand('STATUS', "$mailbox_name ($request)");
806
        if (isset($ret['PARSED'])) {
807
            $ret['PARSED'] = $ret['PARSED'][count($ret['PARSED']) - 1]['EXT'];
808
        }
809
810
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
811
    }
812
813
    /**
814
     * Send the  LIST  Command
815
     *
816
     * @param string $mailbox_base
817
     * @param string $mailbox
818
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
819
     *               kind of failure, or true on success.
820
     * @since  1.0
821
     */
822
    public function cmdList(string $mailbox_base, string $mailbox)
823
    {
824
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
825
        $mailbox_base = sprintf('"%s"', $this->utf_7_encode($mailbox_base));
826
827
        return $this->_genericCommand('LIST', "$mailbox_base $mailbox_name");
828
    }
829
830
    /**
831
     * Send the  LSUB  Command
832
     *
833
     * @param string $mailbox_base
834
     * @param string $mailbox
835
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
836
     *               kind of failure, or true on success.
837
     * @since  1.0
838
     */
839
    public function cmdLsub(string $mailbox_base, string $mailbox)
840
    {
841
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
842
        $mailbox_base = sprintf('"%s"', $this->utf_7_encode($mailbox_base));
843
844
        return $this->_genericCommand('LSUB', "$mailbox_base $mailbox_name");
845
    }
846
847
    /**
848
     * Send the  APPEND  Command
849
     *
850
     * @param string $mailbox
851
     * @param string $msg
852
     * @param string $flags_list
853
     * @param string $time
854
     * @return mixed Returns a PEAR_Error with an error message on any
855
     *               kind of failure, or true on success.
856
     * @since  1.0
857
     */
858
    public function cmdAppend(string $mailbox, string $msg, string $flags_list = '', string $time = '')
859
    {
860
        if (!$this->_connected) {
861
            return new PEAR_Error('not connected!');
862
        }
863
864
        $cmdid    = $this->_getCmdId();
865
        $msg_size = mb_strlen($msg);
866
867
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
868
        // TODO:
869
        // Falta el codigo para que flags list y time hagan algo!!
870
        if (true === $this->hasCapability('LITERAL+')) {
871
            $param = sprintf("%s %s%s{%s+}\r\n%s", $mailbox_name, $flags_list, $time, $msg_size, $msg);
872
            if (PEAR::isError($error = $this->_putCMD($cmdid, 'APPEND', $param))) {
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::isError() 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

872
            if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_putCMD($cmdid, 'APPEND', $param))) {
Loading history...
873
                return $error;
874
            }
875
        } else {
876
            $param = sprintf("%s %s%s{%s}\r\n", $mailbox_name, $flags_list, $time, $msg_size);
877
            if (PEAR::isError($error = $this->_putCMD($cmdid, 'APPEND', $param))) {
878
                return $error;
879
            }
880
            if (PEAR::isError($error = $this->_recvLn())) {
881
                return $error;
882
            }
883
884
            if (PEAR::isError($error = $this->_send($msg))) {
885
                return $error;
886
            }
887
        }
888
889
        $args = $this->_getRawResponse($cmdid);
890
        $ret  = $this->_genericImapResponseParser($args, $cmdid);
891
892
        return $ret;
893
    }
894
895
    /**
896
     * Send the CLOSE command.
897
     *
898
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
899
     *               kind of failure, or true on success.
900
     * @since  1.0
901
     */
902
    public function cmdClose()
903
    {
904
        return $this->_genericCommand('CLOSE');
905
    }
906
907
    /**
908
     * Send the EXPUNGE command.
909
     *
910
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
911
     *               kind of failure, or true on success.
912
     * @since  1.0
913
     */
914
    public function cmdExpunge()
915
    {
916
        $ret = $this->_genericCommand('EXPUNGE');
917
918
        if (isset($ret['PARSED'])) {
919
            $parsed = $ret['PARSED'];
920
            unset($ret['PARSED']);
921
            foreach ($parsed as $command) {
922
                if ('EXPUNGE' === \mb_strtoupper($command['COMMAND'])) {
923
                    $ret['PARSED'][$command['COMMAND']][] = $command['NRO'];
924
                } else {
925
                    $ret['PARSED'][$command['COMMAND']] = $command['NRO'];
926
                }
927
            }
928
        }
929
930
        return $ret;
931
    }
932
933
    /**
934
     * Send the SEARCH command.
935
     *
936
     * @param string $search_cmd
937
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
938
     *               kind of failure, or true on success.
939
     * @since  1.0
940
     */
941
    public function cmdSearch($search_cmd)
942
    {
943
        /*        if($_charset != '' )
944
         $_charset = "[$_charset] ";
945
         $param=sprintf("%s%s",$charset,$search_cmd);
946
         */
947
        $ret = $this->_genericCommand('SEARCH', $search_cmd);
948
        if (isset($ret['PARSED'])) {
949
            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
950
        }
951
952
        return $ret;
953
    }
954
955
    /**
956
     * Send the STORE command.
957
     *
958
     * @param string $message_set  the sessage_set
959
     * @param string $dataitem     :   the way we store the flags
960
     *                             FLAGS: replace the flags whith $value
961
     *                             FLAGS.SILENT: replace the flags whith $value but don't return untagged responses
962
     *
963
     *          +FLAGS: Add the flags whith $value
964
     *          +FLAGS.SILENT: Add the flags whith $value but don't return untagged responses
965
     *
966
     *          -FLAGS: Remove the flags whith $value
967
     *          -FLAGS.SILENT: Remove the flags whith $value but don't return untagged responses
968
     *
969
     * @param string $value
970
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
971
     *                             kind of failure, or true on success.
972
     * @since  1.0
973
     */
974
    public function cmdStore(string $message_set, string $dataitem, string $value)
975
    {
976
        /* As said in RFC2060...
977
         C: A003 STORE 2:4 +FLAGS (\Deleted)
978
         S: * 2 FETCH FLAGS (\Deleted \Seen)
979
         S: * 3 FETCH FLAGS (\Deleted)
980
         S: * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
981
         S: A003 OK STORE completed
982
         */
983
        if ('FLAGS' !== $dataitem && 'FLAGS.SILENT' !== $dataitem && '+FLAGS' !== $dataitem
984
            && '+FLAGS.SILENT' !== $dataitem
985
            && '-FLAGS' !== $dataitem
986
            && '-FLAGS.SILENT' !== $dataitem) {
987
            $this->_prot_error("dataitem '$dataitem' is invalid! see RFC2060!!!!", __LINE__, __FILE__);
988
        }
989
        $param = sprintf('%s %s (%s)', $message_set, $dataitem, $value);
990
991
        return $this->_genericCommand('STORE', $param);
992
    }
993
994
    /**
995
     * Send the COPY command.
996
     *
997
     * @param string $message_set
998
     * @param string $mailbox
999
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
1000
     *               kind of failure, or true on success.
1001
     * @since  1.0
1002
     */
1003
    public function cmdCopy(string $message_set, string $mailbox)
1004
    {
1005
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
1006
1007
        return $this->_genericCommand('COPY', sprintf('%s %s', $message_set, $mailbox_name));
1008
    }
1009
1010
    /**
1011
     * @param string $msgset
1012
     * @param string $fetchparam
1013
     * @return array|\PEAR_Error
1014
     */
1015
    public function cmdUidFetch($msgset, $fetchparam)
1016
    {
1017
        return $this->_genericCommand('UID FETCH', sprintf('%s %s', $msgset, $fetchparam));
1018
    }
1019
1020
    /**
1021
     * @param string $message_set
1022
     * @param string $mailbox
1023
     * @return array|\PEAR_Error
1024
     */
1025
    public function cmdUidCopy(string $message_set, string $mailbox)
1026
    {
1027
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox));
1028
1029
        return $this->_genericCommand('UID COPY', sprintf('%s %s', $message_set, $mailbox_name));
1030
    }
1031
1032
    /**
1033
     * Send the UID STORE command.
1034
     *
1035
     * @param string $message_set  the sessage_set
1036
     * @param string $dataitem     :   the way we store the flags
1037
     *                             FLAGS: replace the flags whith $value
1038
     *                             FLAGS.SILENT: replace the flags whith $value but don't return untagged responses
1039
     *
1040
     *          +FLAGS: Add the flags whith $value
1041
     *          +FLAGS.SILENT: Add the flags whith $value but don't return untagged responses
1042
     *
1043
     *          -FLAGS: Remove the flags whith $value
1044
     *          -FLAGS.SILENT: Remove the flags whith $value but don't return untagged responses
1045
     *
1046
     * @param string $value
1047
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
1048
     *                             kind of failure, or true on success.
1049
     * @since  1.0
1050
     */
1051
    public function cmdUidStore(string $message_set, string $dataitem, string $value)
1052
    {
1053
        /* As said in RFC2060...
1054
         C: A003 STORE 2:4 +FLAGS (\Deleted)
1055
         S: * 2 FETCH FLAGS (\Deleted \Seen)
1056
         S: * 3 FETCH FLAGS (\Deleted)
1057
         S: * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
1058
         S: A003 OK STORE completed
1059
         */
1060
        if ('FLAGS' !== $dataitem && 'FLAGS.SILENT' !== $dataitem && '+FLAGS' !== $dataitem
1061
            && '+FLAGS.SILENT' !== $dataitem
1062
            && '-FLAGS' !== $dataitem
1063
            && '-FLAGS.SILENT' !== $dataitem) {
1064
            $this->_prot_error("dataitem '$dataitem' is invalid! see RFC2060!!!!", __LINE__, __FILE__);
1065
        }
1066
1067
        return $this->_genericCommand('UID STORE', sprintf('%s %s (%s)', $message_set, $dataitem, $value));
1068
    }
1069
1070
    /**
1071
     * Send the SEARCH command.
1072
     *
1073
     * @param string $search_cmd
1074
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
1075
     *               kind of failure, or true on success.
1076
     * @since  1.0
1077
     */
1078
    public function cmdUidSearch(string $search_cmd)
1079
    {
1080
        $ret = $this->_genericCommand('UID SEARCH', sprintf('%s', $search_cmd));
1081
        if (isset($ret['PARSED'])) {
1082
            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1083
        }
1084
1085
        return $ret;
1086
    }
1087
1088
    /**
1089
     * Send the X command.
1090
     *
1091
     * @param string $atom
1092
     * @param string $parameters
1093
     * @return array|\PEAR_Error Returns a PEAR_Error with an error message on any
1094
     *               kind of failure, or true on success.
1095
     * @since  1.0
1096
     */
1097
    public function cmdX($atom, $parameters)
1098
    {
1099
        return $this->_genericCommand("X$atom", $parameters);
1100
    }
1101
1102
    /********************************************************************
1103
     ***
1104
     ***             HERE ENDS the RFC2060 IMAPS FUNCTIONS
1105
     ***             AND BEGIN THE EXTENSIONS FUNCTIONS
1106
     ***
1107
     ********************************************************************/
1108
1109
    /********************************************************************
1110
     ***             RFC2087 IMAP4 QUOTA extension BEGINS HERE
1111
     ********************************************************************/
1112
1113
    /**
1114
     * Send the GETQUOTA command.
1115
     *
1116
     * @param string $mailbox_name  the mailbox name to query for quota data
1117
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
1118
     *                              kind of failure, or quota data on success
1119
     * @since  1.0
1120
     */
1121
    public function cmdGetQuota(string $mailbox_name)
1122
    {
1123
        //Check if the IMAP server has QUOTA support
1124
        if (!$this->hasQuotaSupport()) {
1125
            return new PEAR_Error("This IMAP server does not support QUOTA's! ");
1126
        }
1127
        $mailbox_name = sprintf('%s', $this->utf_7_encode($mailbox_name));
1128
        $ret          = $this->_genericCommand('GETQUOTA', $mailbox_name);
1129
        if (isset($ret['PARSED'])) {
1130
            // remove the array index because the quota response returns only 1 line of output
1131
            $ret['PARSED'] = $ret['PARSED'][0];
1132
        }
1133
1134
        return $ret;
1135
    }
1136
1137
    /**
1138
     * Send the GETQUOTAROOT command.
1139
     *
1140
     * @param string $mailbox_name  the mailbox name to query for quota data
1141
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
1142
     *                              kind of failure, or quota data on success
1143
     * @since  1.0
1144
     */
1145
    public function cmdGetQuotaRoot(string $mailbox_name)
1146
    {
1147
        //Check if the IMAP server has QUOTA support
1148
        if (!$this->hasQuotaSupport()) {
1149
            return new PEAR_Error("This IMAP server does not support QUOTA's! ");
1150
        }
1151
        $mailbox_name = sprintf('%s', $this->utf_7_encode($mailbox_name));
1152
        $ret          = $this->_genericCommand('GETQUOTAROOT', $mailbox_name);
1153
1154
        if (isset($ret['PARSED'])) {
1155
            // remove the array index because the quota response returns only 1 line of output
1156
            $ret['PARSED'] = $ret['PARSED'][0];
1157
        }
1158
1159
        return $ret;
1160
    }
1161
1162
    /**
1163
     * Send the SETQUOTA command.
1164
     *
1165
     * @param string   $mailbox_name  the mailbox name to query for quota data
1166
     * @param int|null $storageQuota  sets the max number of bytes this mailbox can handle
1167
     * @param int|null $messagesQuota sets the max number of messages this mailbox can handle
1168
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
1169
     *                                kind of failure, or quota data on success
1170
     * @since  1.0
1171
     */
1172
    // TODO:  implement the quota by number of emails!!
1173
    public function cmdSetQuota(string $mailbox_name, int $storageQuota = null, int $messagesQuota = null)
1174
    {
1175
        //Check if the IMAP server has QUOTA support
1176
        if (!$this->hasQuotaSupport()) {
1177
            return new PEAR_Error("This IMAP server does not support QUOTA's! ");
1178
        }
1179
1180
        if ((null === $messagesQuota) && (null === $storageQuota)) {
1181
            return new PEAR_Error('$storageQuota and $messagesQuota parameters can\'t be both null if you want to use quota');
1182
        }
1183
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1184
        //Make the command request
1185
        $param = sprintf('%s (', $mailbox_name);
1186
        if (null !== $storageQuota) {
1187
            $param = sprintf('%sSTORAGE %s', $param, $storageQuota);
1188
            if (null !== $messagesQuota) {
1189
                //if we have both types of quota on the same call we must append an space between
1190
                // those parameters
1191
                $param = sprintf('%s ', $param);
1192
            }
1193
        }
1194
        if (null !== $messagesQuota) {
1195
            $param = sprintf('%sMESSAGES %s', $param, $messagesQuota);
1196
        }
1197
        $param = sprintf('%s)', $param);
1198
1199
        return $this->_genericCommand('SETQUOTA', $param);
1200
    }
1201
1202
    /**
1203
     * Send the SETQUOTAROOT command.
1204
     *
1205
     * @param string $mailbox_name   the mailbox name to query for quota data
1206
     * @param null   $storageQuota   sets the max number of bytes this mailbox can handle
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $storageQuota is correct as it would always require null to be passed?
Loading history...
1207
     * @param null   $messagesQuota  sets the max number of messages this mailbox can handle
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $messagesQuota is correct as it would always require null to be passed?
Loading history...
1208
     * @return array|\PEAR_Error  Returns a PEAR_Error with an error message on any
1209
     *                               kind of failure, or quota data on success
1210
     * @since  1.0
1211
     */
1212
    public function cmdSetQuotaRoot(string $mailbox_name, $storageQuota = null, $messagesQuota = null)
1213
    {
1214
        //Check if the IMAP server has QUOTA support
1215
        if (!$this->hasQuotaSupport()) {
1216
            return new PEAR_Error("This IMAP server does not support QUOTA's! ");
1217
        }
1218
1219
        if ((null === $messagesQuota) && (null === $storageQuota)) {
0 ignored issues
show
The condition null === $storageQuota is always true.
Loading history...
1220
            return new PEAR_Error('$storageQuota and $messagesQuota parameters can\'t be both null if you want to use quota');
1221
        }
1222
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1223
        //Make the command request
1224
        $param = sprintf('%s (', $mailbox_name);
1225
        if (null !== $storageQuota) {
1226
            $param = sprintf('%sSTORAGE %s', $param, $storageQuota);
1227
            if (null !== $messagesQuota) {
1228
                //if we have both types of quota on the same call we must append an space between
1229
                // those parameters
1230
                $param = sprintf('%s ', $param);
1231
            }
1232
        }
1233
        if (null !== $messagesQuota) {
1234
            $param = sprintf('%sMESSAGES %s', $param, $messagesQuota);
1235
        }
1236
        $param = sprintf('%s)', $param);
1237
1238
        return $this->_genericCommand('SETQUOTAROOT', $param);
1239
    }
1240
1241
    /********************************************************************
1242
     ***             RFC2087 IMAP4 QUOTA extension ENDS HERE
1243
     ********************************************************************/
1244
1245
    /********************************************************************
1246
     ***             RFC2086 IMAP4 ACL extension BEGINS HERE
1247
     *******************************************************************
1248
     * @param string       $mailbox_name
1249
     * @param string       $user
1250
     * @param array|string $acl
1251
     * @return array|\PEAR_Error
1252
     */
1253
1254
    public function cmdSetACL(string $mailbox_name, string $user, $acl)
1255
    {
1256
        //Check if the IMAP server has ACL support
1257
        if (!$this->hasAclSupport()) {
1258
            return new PEAR_Error("This IMAP server does not support ACL's! ");
1259
        }
1260
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1261
        $user_name    = sprintf('"%s"', $this->utf_7_encode($user));
1262
        if (is_array($acl)) {
1263
            $acl = implode('', $acl);
1264
        }
1265
1266
        return $this->_genericCommand('SETACL', sprintf('%s %s "%s"', $mailbox_name, $user_name, $acl));
1267
    }
1268
1269
    /**
1270
     * @param string $mailbox_name
1271
     * @param string $user
1272
     * @return array|\PEAR_Error
1273
     */
1274
    public function cmdDeleteACL(string $mailbox_name, string $user)
1275
    {
1276
        //Check if the IMAP server has ACL support
1277
        if (!$this->hasAclSupport()) {
1278
            return new PEAR_Error("This IMAP server does not support ACL's! ");
1279
        }
1280
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1281
1282
        return $this->_genericCommand('DELETEACL', sprintf('%s "%s"', $mailbox_name, $user));
1283
    }
1284
1285
    /**
1286
     * @param string $mailbox_name
1287
     * @return array|\PEAR_Error
1288
     */
1289
    public function cmdGetACL(string $mailbox_name)
1290
    {
1291
        //Check if the IMAP server has ACL support
1292
        if (!$this->hasAclSupport()) {
1293
            return new PEAR_Error("This IMAP server does not support ACL's! ");
1294
        }
1295
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1296
        $ret          = $this->_genericCommand('GETACL', sprintf('%s', $mailbox_name));
1297
        if (isset($ret['PARSED'])) {
1298
            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1299
        }
1300
1301
        return $ret;
1302
    }
1303
1304
    /**
1305
     * @param string $mailbox_name
1306
     * @param string $user
1307
     * @return array|\PEAR_Error
1308
     */
1309
    public function cmdListRights(string $mailbox_name, string $user)
1310
    {
1311
        //Check if the IMAP server has ACL support
1312
        if (!$this->hasAclSupport()) {
1313
            return new PEAR_Error("This IMAP server does not support ACL's! ");
1314
        }
1315
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1316
        $ret          = $this->_genericCommand('LISTRIGHTS', sprintf('%s "%s"', $mailbox_name, $user));
1317
        if (isset($ret['PARSED'])) {
1318
            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1319
        }
1320
1321
        return $ret;
1322
    }
1323
1324
    /**
1325
     * @param string $mailbox_name
1326
     * @return array|\PEAR_Error
1327
     */
1328
    public function cmdMyRights(string $mailbox_name)
1329
    {
1330
        //Check if the IMAP server has ACL support
1331
        if (!$this->hasAclSupport()) {
1332
            return new PEAR_Error("This IMAP server does not support ACL's! ");
1333
        }
1334
        $mailbox_name = sprintf('"%s"', $this->utf_7_encode($mailbox_name));
1335
        $ret          = $this->_genericCommand('MYRIGHTS', sprintf('%s', $mailbox_name));
1336
        if (isset($ret['PARSED'])) {
1337
            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1338
        }
1339
1340
        return $ret;
1341
    }
1342
1343
    /********************************************************************
1344
     ***             RFC2086 IMAP4 ACL extension ENDs HERE
1345
     ********************************************************************/
1346
1347
    /*******************************************************************************
1348
     ***  draft-daboo-imap-annotatemore-05 IMAP4 ANNOTATEMORE extension BEGINS HERE
1349
     *******************************************************************************
1350
     * @param string $mailbox_name
1351
     * @param string $entry
1352
     * @param array  $values
1353
     * @return array|\PEAR_Error
1354
     */
1355
1356
    public function cmdSetAnnotation(string $mailbox_name, string $entry, array $values)
1357
    {
1358
        // Check if the IMAP server has ANNOTATEMORE support
1359
        if (!$this->hasAnnotateMoreSupport()) {
1360
            return new PEAR_Error('This IMAP server does not support the ANNOTATEMORE extension!');
1361
        }
1362
        if (!is_array($values)) {
0 ignored issues
show
The condition is_array($values) is always true.
Loading history...
1363
            return new PEAR_Error('Invalid $values argument passed to cmdSetAnnotation');
1364
        }
1365
1366
        $vallist = '';
1367
        foreach ($values as $name => $value) {
1368
            $vallist .= "\"$name\" \"$value\" ";
1369
        }
1370
        $vallist = rtrim($vallist);
1371
1372
        return $this->_genericCommand('SETANNOTATION', sprintf('"%s" "%s" (%s)', $mailbox_name, $entry, $vallist));
1373
    }
1374
1375
    /**
1376
     * @param string $mailbox_name
1377
     * @param string $entry
1378
     * @param array  $values
1379
     * @return array|\PEAR_Error
1380
     */
1381
    public function cmdDeleteAnnotation(string $mailbox_name, string $entry, array $values)
1382
    {
1383
        // Check if the IMAP server has ANNOTATEMORE support
1384
        if (!$this->hasAnnotateMoreSupport()) {
1385
            return new PEAR_Error('This IMAP server does not support the ANNOTATEMORE extension!');
1386
        }
1387
        if (!is_array($values)) {
0 ignored issues
show
The condition is_array($values) is always true.
Loading history...
1388
            return new PEAR_Error('Invalid $values argument passed to cmdDeleteAnnotation');
1389
        }
1390
1391
        $vallist = '';
1392
        foreach ($values as $name) {
1393
            $vallist .= "\"$name\" NIL ";
1394
        }
1395
        $vallist = rtrim($vallist);
1396
1397
        return $this->_genericCommand('SETANNOTATION', sprintf('"%s" "%s" (%s)', $mailbox_name, $entry, $vallist));
1398
    }
1399
1400
    /**
1401
     * @param string $mailbox_name
1402
     * @param array  $entries
1403
     * @param array  $values
1404
     * @return array|\PEAR_Error
1405
     */
1406
    public function cmdGetAnnotation(string $mailbox_name, array $entries, array $values)
1407
    {
1408
        // Check if the IMAP server has ANNOTATEMORE support
1409
        if (!$this->hasAnnotateMoreSupport()) {
1410
            return new PEAR_Error('This IMAP server does not support the ANNOTATEMORE extension!');
1411
        }
1412
1413
        $entlist = '';
1414
1415
        if (!is_array($entries)) {
0 ignored issues
show
The condition is_array($entries) is always true.
Loading history...
1416
            $entries = [$entries];
1417
        }
1418
1419
        foreach ($entries as $name) {
1420
            $entlist .= "\"$name\" ";
1421
        }
1422
        $entlist = rtrim($entlist);
1423
        if (count($entries) > 1) {
1424
            $entlist = "($entlist)";
1425
        }
1426
1427
        $vallist = '';
1428
        if (!is_array($values)) {
0 ignored issues
show
The condition is_array($values) is always true.
Loading history...
1429
            $values = [$values];
1430
        }
1431
1432
        foreach ($values as $name) {
1433
            $vallist .= "\"$name\" ";
1434
        }
1435
        $vallist = rtrim($vallist);
1436
        if (count($values) > 1) {
1437
            $vallist = "($vallist)";
1438
        }
1439
1440
        return $this->_genericCommand('GETANNOTATION', sprintf('"%s" %s %s', $mailbox_name, $entlist, $vallist));
1441
    }
1442
1443
    /*****************************************************************************
1444
     ***  draft-daboo-imap-annotatemore-05 IMAP4 ANNOTATEMORE extension ENDs HERE
1445
     ******************************************************************************/
1446
1447
    /********************************************************************
1448
     ***
1449
     ***             HERE ENDS THE EXTENSIONS FUNCTIONS
1450
     ***             AND BEGIN THE AUXILIARY FUNCTIONS
1451
     ***
1452
     ********************************************************************/
1453
1454
    /**
1455
     * tell if the server has capability $capability
1456
     *
1457
     * @return true or false
1458
     *
1459
     * @since  1.0
1460
     */
1461
    public function getServerAuthMethods(): ?bool
1462
    {
1463
        if (null === $this->_serverAuthMethods) {
1464
            $this->cmdCapability();
1465
1466
            return $this->_serverAuthMethods;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_serverAuthMethods returns the type void which is incompatible with the type-hinted return boolean|null.
Loading history...
1467
        }
1468
1469
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
1470
    }
1471
1472
    /**
1473
     * tell if the server has capability $capability
1474
     *
1475
     * @param string $capability
1476
     * @return true or false
1477
     *
1478
     * @since  1.0
1479
     */
1480
    public function hasCapability(string $capability): bool
1481
    {
1482
        if (null === $this->_serverSupportedCapabilities) {
1483
            $this->cmdCapability();
1484
        }
1485
        if (null !== $this->_serverSupportedCapabilities) {
1486
            if (in_array($capability, $this->_serverSupportedCapabilities, true)) {
1487
                return true;
1488
            }
1489
        }
1490
1491
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
1492
    }
1493
1494
    /**
1495
     * tell if the server has Quota support
1496
     *
1497
     * @return true or false
1498
     *
1499
     * @since  1.0
1500
     */
1501
    public function hasQuotaSupport(): bool
1502
    {
1503
        return $this->hasCapability('QUOTA');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasCapability('QUOTA') returns the type boolean which is incompatible with the documented return type true.
Loading history...
1504
    }
1505
1506
    /**
1507
     * tell if the server has Quota support
1508
     *
1509
     * @return true or false
1510
     *
1511
     * @since  1.0
1512
     */
1513
    public function hasAclSupport(): bool
1514
    {
1515
        return $this->hasCapability('ACL');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasCapability('ACL') returns the type boolean which is incompatible with the documented return type true.
Loading history...
1516
    }
1517
1518
    /**
1519
     * tell if the server has support for the ANNOTATEMORE extension
1520
     *
1521
     * @return true or false
1522
     *
1523
     * @since  1.0
1524
     */
1525
    public function hasAnnotateMoreSupport(): bool
1526
    {
1527
        return $this->hasCapability('ANNOTATEMORE');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasCapability('ANNOTATEMORE') returns the type boolean which is incompatible with the documented return type true.
Loading history...
1528
    }
1529
1530
    /**
1531
     * Parses the responses like RFC822.SIZE and INTERNALDATE
1532
     *
1533
     * @param string $str
1534
     * @param string $line
1535
     * @param string $file
1536
     * @return string containing  the parsed response
1537
     * @since  1.0
1538
     */
1539
    public function _parseOneStringResponse(&$str, $line, $file): string
1540
    {
1541
        $this->_parseSpace($str, $line, $file);
1542
        $size = $this->_getNextToken($str, $uid);
0 ignored issues
show
The assignment to $size is dead and can be removed.
Loading history...
1543
1544
        return $uid;
1545
    }
1546
1547
    /**
1548
     * Parses the FLAG response
1549
     *
1550
     * @param mixed $str
1551
     *
1552
     * @return array containing  the parsed  response
1553
     * @since  1.0
1554
     */
1555
    public function _parseFLAGSresponse(&$str): array
1556
    {
1557
        $this->_parseSpace($str, __LINE__, __FILE__);
1558
        $params_arr[] = $this->_arrayfy_content($str);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params_arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params_arr = array(); before regardless.
Loading history...
1559
        $flags_arr    = [];
1560
        foreach ($params_arr[0] as $iValue) {
1561
            $flags_arr[] = $iValue;
1562
        }
1563
1564
        return $flags_arr;
1565
    }
1566
1567
    /**
1568
     * Parses the BODY response
1569
     *
1570
     * @param string $str
1571
     * @param string $command
1572
     * @return array containing  the parsed  response
1573
     * @since  1.0
1574
     */
1575
    public function _parseBodyResponse(&$str, $command): array
0 ignored issues
show
The parameter $command is not used and could be removed. ( Ignorable by Annotation )

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

1575
    public function _parseBodyResponse(&$str, /** @scrutinizer ignore-unused */ $command): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1576
    {
1577
        $this->_parseSpace($str, __LINE__, __FILE__);
1578
        while (')' !== $str[0] && '' != $str) {
1579
            $params_arr[] = $this->_arrayfy_content($str);
1580
        }
1581
1582
        return $params_arr;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params_arr does not seem to be defined for all execution paths leading up to this point.
Loading history...
1583
    }
1584
1585
    /**
1586
     * Makes the content an Array
1587
     *
1588
     * @param mixed $str
1589
     *
1590
     * @return array containing  the parsed  response
1591
     * @since  1.0
1592
     */
1593
    public function _arrayfy_content(&$str): array
1594
    {
1595
        $params_arr = [];
1596
        $this->_getNextToken($str, $params);
1597
        if ('(' !== $params) {
1598
            return $params;
1599
        }
1600
        $this->_getNextToken($str, $params, false, false);
1601
        while ('' != $str && ')' !== $params) {
1602
            if ('' != $params) {
1603
                if ('(' === $params[0]) {
1604
                    $params = $this->_arrayfy_content($params);
1605
                }
1606
                if (' ' !== $params) {
1607
                    //I don't remove the colons (") to handle the case of retriving " "
1608
                    // If I remove the colons the parser will interpret this field as an imap separator (space)
1609
                    // instead of a valid field so I remove the colons here
1610
                    if ('""' === $params) {
1611
                        $params = '';
1612
                    } else {
1613
                        if ('"' === $params[0]) {
1614
                            $params = mb_substr($params, 1, -2);
1615
                        }
1616
                    }
1617
                    $params_arr[] = $params;
1618
                }
1619
            } else {
1620
                //if params if empty (for example i'm parsing 2 quotes ("")
1621
                // I'll append an array entry to mantain compatibility
1622
                $params_arr[] = $params;
1623
            }
1624
            $this->_getNextToken($str, $params, false, false);
1625
        }
1626
1627
        return $params_arr;
1628
    }
1629
1630
    /**
1631
     * Parses the BODY[],BODY[TEXT],.... responses
1632
     *
1633
     * @param string $str
1634
     * @param string $command
1635
     * @return array containing  the parsed  response
1636
     * @since  1.0
1637
     */
1638
    public function _parseContentresponse(&$str, $command): array
0 ignored issues
show
The parameter $command is not used and could be removed. ( Ignorable by Annotation )

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

1638
    public function _parseContentresponse(&$str, /** @scrutinizer ignore-unused */ $command): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1639
    {
1640
        $content = '';
1641
        $this->_parseSpace($str, __LINE__, __FILE__);
1642
        $size = $this->_getNextToken($str, $content);
1643
1644
        return ['CONTENT' => $content, 'CONTENT_SIZE' => $size];
1645
    }
1646
1647
    /**
1648
     * Parses the ENVELOPE response
1649
     *
1650
     * @param mixed $str
1651
     *
1652
     * @return array containing  the parsed  response
1653
     * @since  1.0
1654
     */
1655
    public function _parseENVELOPEresponse(&$str): array
1656
    {
1657
        $content = '';
0 ignored issues
show
The assignment to $content is dead and can be removed.
Loading history...
1658
        $this->_parseSpace($str, __LINE__, __FILE__);
1659
1660
        $this->_getNextToken($str, $parenthesis);
1661
        if ('(' !== $parenthesis) {
1662
            $this->_prot_error("must be a '(' but is a '$parenthesis' !!!!", __LINE__, __FILE__);
1663
        }
1664
        // Get the email's Date
1665
        $this->_getNextToken($str, $date);
1666
1667
        $this->_parseSpace($str, __LINE__, __FILE__);
1668
1669
        // Get the email's Subject:
1670
        $this->_getNextToken($str, $subject);
1671
        //$subject=$this->decode($subject);
1672
1673
        $this->_parseSpace($str, __LINE__, __FILE__);
1674
1675
        //FROM LIST;
1676
        $from_arr = $this->_getAddressList($str);
1677
1678
        $this->_parseSpace($str, __LINE__, __FILE__);
1679
1680
        //"SENDER LIST\n";
1681
        $sender_arr = $this->_getAddressList($str);
1682
1683
        $this->_parseSpace($str, __LINE__, __FILE__);
1684
1685
        //"REPLY-TO LIST\n";
1686
        $reply_to_arr = $this->_getAddressList($str);
1687
1688
        $this->_parseSpace($str, __LINE__, __FILE__);
1689
1690
        //"TO LIST\n";
1691
        $to_arr = $this->_getAddressList($str);
1692
1693
        $this->_parseSpace($str, __LINE__, __FILE__);
1694
1695
        //"CC LIST\n";
1696
        $cc_arr = $this->_getAddressList($str);
1697
1698
        $this->_parseSpace($str, __LINE__, __FILE__);
1699
1700
        //"BCC LIST|$str|\n";
1701
        $bcc_arr = $this->_getAddressList($str);
1702
1703
        $this->_parseSpace($str, __LINE__, __FILE__);
1704
1705
        $this->_getNextToken($str, $in_reply_to);
1706
1707
        $this->_parseSpace($str, __LINE__, __FILE__);
1708
1709
        $this->_getNextToken($str, $message_id);
1710
1711
        $this->_getNextToken($str, $parenthesis);
1712
1713
        if (')' !== $parenthesis) {
1714
            $this->_prot_error("must be a ')' but is a '$parenthesis' !!!!", __LINE__, __FILE__);
1715
        }
1716
1717
        return [
1718
            'DATE'        => $date,
1719
            'SUBJECT'     => $subject,
1720
            'FROM'        => $from_arr,
1721
            'SENDER'      => $sender_arr,
1722
            'REPLY_TO'    => $reply_to_arr,
1723
            'TO'          => $to_arr,
1724
            'CC'          => $cc_arr,
1725
            'BCC'         => $bcc_arr,
1726
            'IN_REPLY_TO' => $in_reply_to,
1727
            'MESSAGE_ID'  => $message_id,
1728
        ];
1729
    }
1730
1731
    /**
1732
     * Parses the ARRDLIST as defined in RFC
1733
     *
1734
     * @param mixed $str
1735
     *
1736
     * @return array containing  the parsed  response
1737
     * @since  1.0
1738
     */
1739
    public function _getAddressList(&$str): array
1740
    {
1741
        $params_arr = $this->_arrayfy_content($str);
1742
        if (!isset($params_arr)) {
1743
            return $params_arr;
1744
        }
1745
1746
        if (is_array($params_arr)) {
0 ignored issues
show
The condition is_array($params_arr) is always true.
Loading history...
1747
            $personal_name  = $params_arr[0][0];
1748
            $at_domain_list = $params_arr[0][1];
1749
            $mailbox_name   = $params_arr[0][2];
1750
            $host_name      = $params_arr[0][3];
1751
            if ('' != $mailbox_name && '' != $host_name) {
1752
                $email = $mailbox_name . '@' . $host_name;
1753
            } else {
1754
                $email = false;
1755
            }
1756
            if (false !== $email) {
1757
                if (isset($personal_name)) {
1758
                    $rfc822_email = '"' . $personal_name . '" <' . $email . '>';
1759
                } else {
1760
                    $rfc822_email = '<' . $email . '>';
1761
                }
1762
            } else {
1763
                $rfc822_email = false;
1764
            }
1765
            $email_arr[] = [
0 ignored issues
show
Comprehensibility Best Practice introduced by
$email_arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $email_arr = array(); before regardless.
Loading history...
1766
                'PERSONAL_NAME'  => $personal_name,
1767
                'AT_DOMAIN_LIST' => $at_domain_list,
1768
                'MAILBOX_NAME'   => $this->utf_7_decode($mailbox_name),
1769
                'HOST_NAME'      => $host_name,
1770
                'EMAIL'          => $email,
1771
                'RFC822_EMAIL'   => $rfc822_email,
1772
            ];
1773
1774
            return $email_arr;
1775
        }
1776
1777
        return [];
1778
    }
1779
1780
    /**
1781
     * Utility funcion to find the closing parenthesis ")" Position it takes care of quoted ones
1782
     *
1783
     * @param        $str_line
1784
     * @param string $startDelim
1785
     * @param string $stopDelim
1786
     * @return int containing  the pos of the closing parenthesis ")"
1787
     * @since  1.0
1788
     */
1789
    public function _getClosingBracesPos($str_line, string $startDelim = '(', string $stopDelim = ')')
1790
    {
1791
        $len = mb_strlen($str_line);
1792
        $pos = 0;
1793
        // ignore all extra characters
1794
        // If inside of a string, skip string -- Boundary IDs and other
1795
        // things can have ) in them.
1796
        if ($str_line[$pos] != $startDelim) {
1797
            $this->_prot_error("_getClosingParenthesisPos: must start with a '(' but is a '" . $str_line[$pos] . "'!!!!\n" . "STR_LINE:$str_line|size:$len|POS: $pos\n", __LINE__, __FILE__);
1798
1799
            return $len;
1800
        }
1801
        for ($pos = 1; $pos < $len; $pos++) {
1802
            if ($str_line[$pos] == $stopDelim) {
1803
                break;
1804
            }
1805
            if ('"' === $str_line[$pos]) {
1806
                $pos++;
1807
                while ('"' !== $str_line[$pos] && $pos < $len) {
1808
                    if ('\\' === $str_line[$pos] && '"' === $str_line[$pos + 1]) {
1809
                        $pos++;
1810
                    }
1811
                    if ('\\' === $str_line[$pos] && '\\' === $str_line[$pos + 1]) {
1812
                        $pos++;
1813
                    }
1814
                    $pos++;
1815
                }
1816
            }
1817
            if ($str_line[$pos] == $startDelim) {
1818
                $str_line_aux = mb_substr($str_line, $pos);
1819
                $pos_aux      = $this->_getClosingBracesPos($str_line_aux);
1820
                $pos          += $pos_aux;
1821
            }
1822
        }
1823
        if ($str_line[$pos] != $stopDelim) {
1824
            $this->_prot_error("_getClosingBracesPos: must be a $stopDelim but is a '" . $str_line[$pos] . "'|POS:$pos|STR_LINE:$str_line!!!!", __LINE__, __FILE__);
1825
        }
1826
1827
        if ($pos >= $len) {
1828
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1829
        }
1830
1831
        return $pos;
1832
    }
1833
1834
    /**
1835
     * Utility funcion to get from here to the end of the line
1836
     *
1837
     * @param mixed $str
1838
     * @param bool  $including
1839
     * @return string containing  the string to the end of the line
1840
     * @since  1.0
1841
     */
1842
    public function _getToEOL(&$str, bool $including = true): string
1843
    {
1844
        $len = mb_strlen($str);
1845
        if ($including) {
1846
            for ($i = 0; $i < $len; ++$i) {
1847
                if ("\n" === $str[$i]) {
1848
                    break;
1849
                }
1850
            }
1851
            $content = mb_substr($str, 0, $i + 1);
1852
            $str     = mb_substr($str, $i + 1);
1853
1854
            return $content;
1855
        }
1856
        for ($i = 0; $i < $len; ++$i) {
1857
            if ("\n" === $str[$i] || "\r" === $str[$i]) {
1858
                break;
1859
            }
1860
        }
1861
        $content = mb_substr($str, 0, $i);
1862
        $str     = mb_substr($str, $i);
1863
1864
        return $content;
1865
    }
1866
1867
    /**
1868
     * Fetches the next IMAP token or parenthesis
1869
     *
1870
     * @param bool  $parenthesisIsToken
1871
     * @param bool  $colonIsToken
1872
     * @param mixed $str
1873
     * @param mixed $content
1874
     * @return int containing  the content size
1875
     * @since  1.0
1876
     */
1877
    public function _getNextToken(&$str, &$content, bool $parenthesisIsToken = true, bool $colonIsToken = true)
1878
    {
1879
        $len          = mb_strlen($str);
1880
        $pos          = 0;
1881
        $content_size = false;
1882
        $content      = false;
1883
        if ('' === $str || $len < 2) {
1884
            $content = $str;
1885
1886
            return $len;
1887
        }
1888
        switch ($str[0]) {
1889
            case '{':
1890
                if (false === ($posClosingBraces = $this->_getClosingBracesPos($str, '{', '}'))) {
0 ignored issues
show
The condition false === $posClosingBra...acesPos($str, '{', '}') is always false.
Loading history...
1891
                    $this->_prot_error('_getClosingBracesPos() error!!!', __LINE__, __FILE__);
1892
                }
1893
                if (!is_numeric($strBytes = mb_substr($str, 1, $posClosingBraces - 1))) {
1894
                    $this->_prot_error("must be a number but is a '" . $strBytes . "'!!!!", __LINE__, __FILE__);
1895
                }
1896
                if ('}' !== $str[$posClosingBraces]) {
1897
                    $this->_prot_error("must be a '}'  but is a '" . $str[$posClosingBraces] . "'!!!!", __LINE__, __FILE__);
1898
                }
1899
                if ("\r" !== $str[$posClosingBraces + 1]) {
1900
                    $this->_prot_error("must be a '\\r'  but is a '" . $str[$posClosingBraces + 1] . "'!!!!", __LINE__, __FILE__);
1901
                }
1902
                if ("\n" !== $str[$posClosingBraces + 2]) {
1903
                    $this->_prot_error("must be a '\\n'  but is a '" . $str[$posClosingBraces + 2] . "'!!!!", __LINE__, __FILE__);
1904
                }
1905
                $content = mb_substr($str, $posClosingBraces + 3, $strBytes);
0 ignored issues
show
$strBytes of type string is incompatible with the type integer|null expected by parameter $length of mb_substr(). ( Ignorable by Annotation )

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

1905
                $content = mb_substr($str, $posClosingBraces + 3, /** @scrutinizer ignore-type */ $strBytes);
Loading history...
1906
                if (mb_strlen($content) != $strBytes) {
1907
                    $this->_prot_error('content size is ' . mb_strlen($content) . " but the string reports a size of $strBytes!!!\n", __LINE__, __FILE__);
1908
                }
1909
                $content_size = $strBytes;
1910
                //Advance the string
1911
                $str = mb_substr($str, $posClosingBraces + $strBytes + 3);
1912
                break;
1913
            case '"':
1914
                if ($colonIsToken) {
1915
                    for ($pos = 1; $pos < $len; $pos++) {
1916
                        if ('"' === $str[$pos]) {
1917
                            break;
1918
                        }
1919
                        if ('\\' === $str[$pos] && '"' === $str[$pos + 1]) {
1920
                            $pos++;
1921
                        }
1922
                        if ('\\' === $str[$pos] && '\\' === $str[$pos + 1]) {
1923
                            $pos++;
1924
                        }
1925
                    }
1926
                    if ('"' !== $str[$pos]) {
1927
                        $this->_prot_error("must be a '\"'  but is a '" . $str[$pos] . "'!!!!", __LINE__, __FILE__);
1928
                    }
1929
                    $content_size = $pos;
1930
                    $content      = mb_substr($str, 1, $pos - 1);
1931
                    //Advance the string
1932
                    $str = mb_substr($str, $pos + 1);
1933
                } else {
1934
                    for ($pos = 1; $pos < $len; $pos++) {
1935
                        if ('"' === $str[$pos]) {
1936
                            break;
1937
                        }
1938
                        if ('\\' === $str[$pos] && '"' === $str[$pos + 1]) {
1939
                            $pos++;
1940
                        }
1941
                        if ('\\' === $str[$pos] && '\\' === $str[$pos + 1]) {
1942
                            $pos++;
1943
                        }
1944
                    }
1945
                    if ('"' !== $str[$pos]) {
1946
                        $this->_prot_error("must be a '\"'  but is a '" . $str[$pos] . "'!!!!", __LINE__, __FILE__);
1947
                    }
1948
                    $content_size = $pos;
1949
                    $content      = mb_substr($str, 0, $pos + 1);
1950
                    //Advance the string
1951
                    $str = mb_substr($str, $pos + 1);
1952
                }
1953
                break;
1954
            case "\r":
1955
                $pos = 1;
1956
                if ("\n" === $str[1]) {
1957
                    $pos++;
1958
                }
1959
                $content_size = $pos;
1960
                $content      = mb_substr($str, 0, $pos);
1961
                $str          = mb_substr($str, $pos);
1962
                break;
1963
            case "\n":
1964
                $pos          = 1;
1965
                $content_size = $pos;
1966
                $content      = mb_substr($str, 0, $pos);
1967
                $str          = mb_substr($str, $pos);
1968
                break;
1969
            case '(':
1970
                if ($parenthesisIsToken) {
1971
                    $pos          = 1;
1972
                    $content_size = $pos;
1973
                    $content      = mb_substr($str, 0, $pos);
1974
                    $str          = mb_substr($str, $pos);
1975
                } else {
1976
                    $pos          = $this->_getClosingBracesPos($str);
1977
                    $content_size = $pos + 1;
1978
                    $content      = mb_substr($str, 0, $pos + 1);
1979
                    $str          = mb_substr($str, $pos + 1);
1980
                }
1981
                break;
1982
            case ')':
1983
                $pos          = 1;
1984
                $content_size = $pos;
1985
                $content      = mb_substr($str, 0, $pos);
1986
                $str          = mb_substr($str, $pos);
1987
                break;
1988
            case ' ':
1989
                $pos          = 1;
1990
                $content_size = $pos;
1991
                $content      = mb_substr($str, 0, $pos);
1992
                $str          = mb_substr($str, $pos);
1993
                break;
1994
            default:
1995
                for ($pos = 0; $pos < $len; $pos++) {
1996
                    if (' ' === $str[$pos] || "\r" === $str[$pos] || ')' === $str[$pos] || '(' === $str[$pos] || "\n" === $str[$pos]) {
1997
                        break;
1998
                    }
1999
                    if ('\\' === $str[$pos] && ' ' === $str[$pos + 1]) {
2000
                        $pos++;
2001
                    }
2002
                    if ('\\' === $str[$pos] && '\\' === $str[$pos + 1]) {
2003
                        $pos++;
2004
                    }
2005
                }
2006
                //Advance the string
2007
                if (0 == $pos) {
2008
                    $content_size = 1;
2009
                    $content      = mb_substr($str, 0, 1);
2010
                    $str          = mb_substr($str, 1);
2011
                } else {
2012
                    $content_size = $pos;
2013
                    $content      = mb_substr($str, 0, $pos);
2014
                    if ($pos < $len) {
2015
                        $str = mb_substr($str, $pos);
2016
                    } else {
2017
                        //if this is the end of the string... exit the switch
2018
                        break;
2019
                    }
2020
                }
2021
                break;
2022
        }
2023
2024
        return $content_size;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $content_size also could return the type string which is incompatible with the documented return type integer.
Loading history...
2025
    }
2026
2027
    /**
2028
     * Utility funcion to display to console the protocol errors
2029
     *
2030
     * @param      $str
2031
     * @param      $line
2032
     * @param      $file
2033
     * @param bool $printError
2034
     * @return string containing  the error
2035
     * @since  1.0
2036
     */
2037
    public function _prot_error($str, $line, $file, bool $printError = true): string
2038
    {
2039
        if ($printError) {
2040
            echo "$line,$file,PROTOCOL ERROR!:$str\n";
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
2041
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 2039 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
2042
    }
2043
2044
    /**
2045
     * @param string $startDelim
2046
     * @param string $stopDelim
2047
     * @param mixed  $str
2048
     * @return array
2049
     */
2050
    public function _getEXTarray(&$str, string $startDelim = '(', string $stopDelim = ')'): array
2051
    {
2052
        /* I let choose the $startDelim  and $stopDelim to allow parsing
2053
         the OK response  so I also can parse a response like this
2054
         * OK [UIDNEXT 150] Predicted next UID
2055
         */
2056
        $this->_getNextToken($str, $parenthesis);
2057
        if ($parenthesis != $startDelim) {
2058
            $this->_prot_error("must be a '$startDelim' but is a '$parenthesis' !!!!", __LINE__, __FILE__);
2059
        }
2060
        $parenthesis = '';
2061
        $struct_arr  = [];
2062
        while ($parenthesis != $stopDelim && '' != $str) {
2063
            // The command
2064
            $this->_getNextToken($str, $token);
2065
            $token = \mb_strtoupper($token);
2066
2067
            if (false !== ($ret = $this->_retrParsedResponse($str, $token))) {
2068
                //$struct_arr[$token] = $ret;
2069
                $struct_arr = array_merge($struct_arr, $ret);
0 ignored issues
show
It seems like $ret can also be of type string; however, parameter $arrays of array_merge() does only seem to accept array, 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

2069
                $struct_arr = array_merge($struct_arr, /** @scrutinizer ignore-type */ $ret);
Loading history...
2070
            }
2071
2072
            $parenthesis = $token;
2073
        }//While
2074
2075
        if ($parenthesis != $stopDelim) {
2076
            $this->_prot_error("1_must be a '$stopDelim' but is a '$parenthesis' !!!!", __LINE__, __FILE__);
2077
        }
2078
2079
        return $struct_arr;
2080
    }
2081
2082
    /**
2083
     * @param       $token
2084
     * @param null  $previousToken
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $previousToken is correct as it would always require null to be passed?
Loading history...
2085
     * @param mixed $str
2086
     * @return array|array[]|false|null[]|string|string[]|\string[][]
2087
     */
2088
    public function _retrParsedResponse(&$str, $token, $previousToken = null)
2089
    {
2090
        //echo "\n\nTOKEN:$token\r\n";
2091
        switch ($token) {
2092
            case 'RFC822.SIZE':
2093
                return [$token => $this->_parseOneStringResponse($str, __LINE__, __FILE__)];
2094
            //        case "RFC822.TEXT" :
2095
2096
            //        case "RFC822.HEADER" :
2097
2098
            case 'RFC822':
2099
                return [$token => $this->_parseContentresponse($str, $token)];
2100
            case 'FLAGS':
2101
2102
            case 'PERMANENTFLAGS':
2103
                return [$token => $this->_parseFLAGSresponse($str)];
2104
            case 'ENVELOPE':
2105
                return [$token => $this->_parseENVELOPEresponse($str)];
2106
            case 'EXPUNGE':
2107
                return false;
2108
            case 'UID':
2109
2110
            case 'UIDNEXT':
2111
2112
            case 'UIDVALIDITY':
2113
2114
            case 'UNSEEN':
2115
2116
            case 'MESSAGES':
2117
2118
            case 'UIDNEXT':
2119
2120
            case 'UIDVALIDITY':
2121
2122
            case 'UNSEEN':
2123
2124
            case 'INTERNALDATE':
2125
                return [$token => $this->_parseOneStringResponse($str, __LINE__, __FILE__)];
2126
            case 'BODY':
2127
2128
            case 'BODYSTRUCTURE':
2129
                return [$token => $this->_parseBodyResponse($str, $token)];
2130
            case 'RECENT':
2131
                if (null !== $previousToken) {
0 ignored issues
show
The condition null !== $previousToken is always false.
Loading history...
2132
                    $aux['RECENT'] = $previousToken;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$aux was never initialized. Although not strictly required by PHP, it is generally a good practice to add $aux = array(); before regardless.
Loading history...
2133
2134
                    return $aux;
2135
                }
2136
2137
                return [$token => $this->_parseOneStringResponse($str, __LINE__, __FILE__)];
2138
            case 'EXISTS':
2139
                return [$token => $previousToken];
2140
            case 'READ-WRITE':
2141
2142
            case 'READ-ONLY':
2143
                return [$token => $token];
2144
            case 'QUOTA':
2145
                /*
2146
                 A tipical GETQUOTA DIALOG IS AS FOLLOWS
2147
2148
                 C: A0004 GETQUOTA user.damian
2149
                 S: * QUOTA user.damian (STORAGE 1781460 4000000)
2150
                 S: A0004 OK Completed
2151
                 */
2152
2153
                $mailbox = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2154
                $this->_parseSpace($str, __LINE__, __FILE__);
2155
                $this->_parseString($str, '(', __LINE__, __FILE__);
2156
2157
                $ret_aux = ['MAILBOX' => $this->utf_7_decode($mailbox)];
2158
                $this->_getNextToken($str, $quota_resp);
2159
                if (false === ($ext = $this->_retrParsedResponse($str, $quota_resp))) {
2160
                    $this->_prot_error('bogus response!!!!', __LINE__, __FILE__);
2161
                }
2162
                $ret_aux = array_merge($ret_aux, $ext);
0 ignored issues
show
It seems like $ext can also be of type false and string; however, parameter $arrays of array_merge() does only seem to accept array, 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

2162
                $ret_aux = array_merge($ret_aux, /** @scrutinizer ignore-type */ $ext);
Loading history...
2163
2164
                $this->_getNextToken($str, $separator);
2165
                if (')' === $separator) {
2166
                    return [$token => $ret_aux];
2167
                }
2168
2169
                $this->_parseSpace($str, __LINE__, __FILE__);
2170
2171
                $this->_getNextToken($str, $quota_resp);
2172
                if (false === ($ext = $this->_retrParsedResponse($str, $quota_resp))) {
2173
                    $this->_prot_error('bogus response!!!!', __LINE__, __FILE__);
2174
                }
2175
                $ret_aux = array_merge($ret_aux, $ext);
2176
2177
                $this->_parseString($str, ')', __LINE__, __FILE__);
2178
2179
                return [$token => $ret_aux];
2180
            case 'QUOTAROOT':
2181
                /*
2182
                 A tipical GETQUOTA DIALOG IS AS FOLLOWS
2183
2184
                 C: A0004 GETQUOTA user.damian
2185
                 S: * QUOTA user.damian (STORAGE 1781460 4000000)
2186
                 S: A0004 OK Completed
2187
                 */ $mailbox = $this->utf_7_decode($this->_parseOneStringResponse($str, __LINE__, __FILE__));
2188
2189
                $str_line = rtrim(mb_substr($this->_getToEOL($str, false), 0));
2190
2191
                $quotaroot = $this->_parseOneStringResponse($str_line, __LINE__, __FILE__);
2192
                $ret       = @['MAILBOX' => $this->utf_7_decode($mailbox), $token => $quotaroot];
2193
2194
                return [$token => $ret];
2195
            case 'STORAGE':
2196
                $used = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2197
                $qmax = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2198
2199
                return [$token => ['USED' => $used, 'QMAX' => $qmax]];
2200
            case 'MESSAGE':
2201
                $mused = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2202
                $mmax  = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2203
2204
                return [$token => ['MUSED' => $mused, 'MMAX' => $mmax]];
2205
            case 'FETCH':
2206
                $this->_parseSpace($str, __LINE__, __FILE__);
2207
                // Get the parsed pathenthesis
2208
                $struct_arr = $this->_getEXTarray($str);
2209
2210
                return $struct_arr;
2211
            case 'CAPABILITY':
2212
                $this->_parseSpace($str, __LINE__, __FILE__);
2213
                $str_line                   = rtrim(mb_substr($this->_getToEOL($str, false), 0));
2214
                $struct_arr['CAPABILITIES'] = explode(' ', $str_line);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$struct_arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $struct_arr = array(); before regardless.
Loading history...
2215
2216
                return [$token => $struct_arr];
2217
            case 'STATUS':
2218
                $mailbox = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
2219
                $this->_parseSpace($str, __LINE__, __FILE__);
2220
                $ext                      = $this->_getEXTarray($str);
2221
                $struct_arr['MAILBOX']    = $this->utf_7_decode($mailbox);
2222
                $struct_arr['ATTRIBUTES'] = $ext;
2223
2224
                return [$token => $struct_arr];
2225
            case 'LIST':
2226
                $this->_parseSpace($str, __LINE__, __FILE__);
2227
                $params_arr = $this->_arrayfy_content($str);
2228
2229
                $this->_parseSpace($str, __LINE__, __FILE__);
2230
                $this->_getNextToken($str, $hierarchydelim);
2231
2232
                $this->_parseSpace($str, __LINE__, __FILE__);
2233
                $this->_getNextToken($str, $mailbox_name);
2234
2235
                $result_array = ['NAME_ATTRIBUTES' => $params_arr, 'HIERACHY_DELIMITER' => $hierarchydelim, 'MAILBOX_NAME' => $this->utf_7_decode($mailbox_name)];
2236
2237
                return [$token => $result_array];
2238
            case 'LSUB':
2239
                $this->_parseSpace($str, __LINE__, __FILE__);
2240
                $params_arr = $this->_arrayfy_content($str);
2241
2242
                $this->_parseSpace($str, __LINE__, __FILE__);
2243
                $this->_getNextToken($str, $hierarchydelim);
2244
2245
                $this->_parseSpace($str, __LINE__, __FILE__);
2246
                $this->_getNextToken($str, $mailbox_name);
2247
2248
                $result_array = ['NAME_ATTRIBUTES' => $params_arr, 'HIERACHY_DELIMITER' => $hierarchydelim, 'MAILBOX_NAME' => $this->utf_7_decode($mailbox_name)];
2249
2250
                return [$token => $result_array];
2251
            case 'SEARCH':
2252
                $str_line                  = rtrim(mb_substr($this->_getToEOL($str, false), 1));
2253
                $struct_arr['SEARCH_LIST'] = explode(' ', $str_line);
2254
                if (1 == count($struct_arr['SEARCH_LIST']) && '' == $struct_arr['SEARCH_LIST'][0]) {
2255
                    $struct_arr['SEARCH_LIST'] = null;
2256
                }
2257
2258
                return [$token => $struct_arr];
2259
            case 'OK':
2260
                /* TODO:
2261
                 parse the [ .... ] part of the response, use the method
2262
                 _getEXTarray(&$str,'[',$stopDelim=']')
2263
2264
                 */ $str_line = rtrim(mb_substr($this->_getToEOL($str, false), 1));
2265
                if ('[' === $str_line[0]) {
2266
                    $braceLen = $this->_getClosingBracesPos($str_line, '[', ']');
2267
                    $str_aux  = '(' . mb_substr($str_line, 1, $braceLen - 1) . ')';
2268
                    $ext_arr  = $this->_getEXTarray($str_aux);
2269
                    //$ext_arr=array($token=>$this->_getEXTarray($str_aux));
2270
                } else {
2271
                    $ext_arr = $str_line;
2272
                    //$ext_arr=array($token=>$str_line);
2273
                }
2274
                $result_array = $ext_arr;
2275
2276
                return $result_array;
2277
            case 'NO':
2278
                /* TODO:
2279
                 parse the [ .... ] part of the response, use the method
2280
                 _getEXTarray(&$str,'[',$stopDelim=']')
2281
2282
                 */
2283
2284
                $str_line       = rtrim(mb_substr($this->_getToEOL($str, false), 1));
2285
                $result_array[] = @['COMMAND' => $token, 'EXT' => $str_line];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result_array was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result_array = array(); before regardless.
Loading history...
2286
2287
                return $result_array;
2288
            case 'BAD':
2289
                /* TODO:
2290
                 parse the [ .... ] part of the response, use the method
2291
                 _getEXTarray(&$str,'[',$stopDelim=']')
2292
2293
                 */
2294
2295
                $str_line       = rtrim(mb_substr($this->_getToEOL($str, false), 1));
2296
                $result_array[] = ['COMMAND' => $token, 'EXT' => $str_line];
2297
2298
                return $result_array;
2299
            case 'BYE':
2300
                /* TODO:
2301
                 parse the [ .... ] part of the response, use the method
2302
                 _getEXTarray(&$str,'[',$stopDelim=']')
2303
2304
                 */
2305
2306
                $str_line       = rtrim(mb_substr($this->_getToEOL($str, false), 1));
2307
                $result_array[] = ['COMMAND' => $command, 'EXT' => $str_line];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $command seems to be never defined.
Loading history...
2308
2309
                return $result_array;
2310
            case 'LISTRIGHTS':
2311
                $this->_parseSpace($str, __LINE__, __FILE__);
2312
                $this->_getNextToken($str, $mailbox);
2313
                $this->_parseSpace($str, __LINE__, __FILE__);
2314
                $this->_getNextToken($str, $user);
2315
                $this->_parseSpace($str, __LINE__, __FILE__);
2316
                $this->_getNextToken($str, $granted);
2317
2318
                $ungranted = explode(' ', rtrim(mb_substr($this->_getToEOL($str, false), 1)));
2319
2320
                $result_array = @['MAILBOX' => $this->utf_7_decode($mailbox), 'USER' => $user, 'GRANTED' => $granted, 'UNGRANTED' => $ungranted];
2321
2322
                return $result_array;
2323
            case 'MYRIGHTS':
2324
                $this->_parseSpace($str, __LINE__, __FILE__);
2325
                $this->_getNextToken($str, $mailbox);
2326
                $this->_parseSpace($str, __LINE__, __FILE__);
2327
                $this->_getNextToken($str, $granted);
2328
2329
                $result_array = ['MAILBOX' => $this->utf_7_decode($mailbox), 'GRANTED' => $granted];
2330
2331
                return $result_array;
2332
            case 'ACL':
2333
                $this->_parseSpace($str, __LINE__, __FILE__);
2334
                $this->_getNextToken($str, $mailbox);
2335
                $this->_parseSpace($str, __LINE__, __FILE__);
2336
                $acl_arr = explode(' ', rtrim(mb_substr($this->_getToEOL($str, false), 0)));
2337
2338
                for ($i = 0, $iMax = count($acl_arr); $i < $iMax; $i += 2) {
2339
                    $arr[] = ['USER' => $acl_arr[$i], 'RIGHTS' => $acl_arr[$i + 1]];
2340
                }
2341
2342
                $result_array = ['MAILBOX' => $this->utf_7_decode($mailbox), 'USERS' => $arr];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $arr does not seem to be defined for all execution paths leading up to this point.
Loading history...
2343
2344
                return $result_array;
2345
            case 'ANNOTATION':
2346
                $this->_parseSpace($str, __LINE__, __FILE__);
2347
                $this->_getNextToken($str, $mailbox);
2348
2349
                $this->_parseSpace($str, __LINE__, __FILE__);
2350
                $this->_getNextToken($str, $entry);
2351
2352
                $this->_parseSpace($str, __LINE__, __FILE__);
2353
                $attrs = $this->_arrayfy_content($str);
2354
2355
                $result_array = ['MAILBOX' => $mailbox, 'ENTRY' => $entry, 'ATTRIBUTES' => $attrs];
2356
2357
                return $result_array;
2358
            case '':
2359
                $this->_prot_error('PROTOCOL ERROR!:str empty!!', __LINE__, __FILE__);
2360
                break;
2361
            case '(':
2362
                $this->_prot_error('OPENING PARENTHESIS ERROR!!!!!!!!!!!!!!!!!', __LINE__, __FILE__);
2363
                break;
2364
            case ')':
2365
                //"CLOSING PARENTHESIS BREAK!!!!!!!"
2366
                break;
2367
            case "\r\n":
2368
                $this->_prot_error('BREAK!!!!!!!!!!!!!!!!!', __LINE__, __FILE__);
2369
                break;
2370
            case ' ':
2371
                // this can happen and we just ignore it
2372
                // This happens when - for example - fetch returns more than 1 parammeter
2373
                // for example you ask to get RFC822.SIZE and UID
2374
                //$this->_prot_error("SPACE BREAK!!!!!!!!!!!!!!!!!" , __LINE__ , __FILE__ );
2375
                break;
2376
            default:
2377
                $body_token = \mb_strtoupper(mb_substr($token, 0, 5));
2378
                //echo "BODYYYYYYY: $body_token\n";
2379
                $rfc822_token = \mb_strtoupper(mb_substr($token, 0, 7));
2380
                //echo "BODYYYYYYY: $rfc822_token|$token\n";
2381
2382
                if ('BODY[' === $body_token || 'BODY.' === $body_token || 'RFC822.' === $rfc822_token) {
2383
                    //echo "TOKEN:$token\n";
2384
                    //$this->_getNextToken( $str , $mailbox );
2385
                    return [$token => $this->_parseContentresponse($str, $token)];
2386
                }
2387
                $this->_prot_error("UNIMPLEMMENTED! I don't know the parameter '$token' !!!", __LINE__, __FILE__);
2388
2389
                break;
2390
        }
2391
2392
        return false;
2393
    }
2394
2395
    /*
2396
     * Verifies that the next character IS a space
2397
     */
2398
2399
    /**
2400
     * @param       $line
2401
     * @param       $file
2402
     * @param bool  $printError
2403
     * @param mixed $str
2404
     * @return mixed|string
2405
     */
2406
    public function _parseSpace(&$str, $line, $file, bool $printError = true)
2407
    {
2408
        /*
2409
         This code repeats a lot in this class
2410
         so i make it a function to make all the code shorter
2411
         */
2412
        $this->_getNextToken($str, $space);
2413
        if (' ' !== $space) {
2414
            $this->_prot_error("must be a ' ' but is a '$space' !!!!", $line, $file, $printError);
2415
        }
2416
2417
        return $space;
2418
    }
2419
2420
    /**
2421
     * @param string $str
2422
     * @param string $char
2423
     * @param string $line
2424
     * @param string $file
2425
     * @return mixed
2426
     */
2427
    public function _parseString(&$str, $char, $line, $file)
2428
    {
2429
        /*
2430
         This code repeats a lot in this class
2431
         so i make it a function to make all the code shorter
2432
         */
2433
        $this->_getNextToken($str, $char_aux);
2434
        if (mb_strtoupper($char_aux) != \mb_strtoupper($char)) {
2435
            $this->_prot_error("must be a $char but is a '$char_aux' !!!!", $line, $file);
2436
        }
2437
2438
        return $char_aux;
2439
    }
2440
2441
    /**
2442
     * @param null  $cmdid
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $cmdid is correct as it would always require null to be passed?
Loading history...
2443
     * @param mixed $str
2444
     * @return array
2445
     */
2446
    public function _genericImapResponseParser(&$str, $cmdid = null): array
2447
    {
2448
        $result_array = [];
2449
        if ($this->_unParsedReturn) {
2450
            $unparsed_str = $str;
2451
        }
2452
2453
        $this->_getNextToken($str, $token);
2454
2455
        while ($token != $cmdid && '' != $str) {
2456
            if ('+' == $token) {
2457
                //if the token  is + ignore the line
2458
                // TODO: verify that this is correct!!!
2459
                $this->_getToEOL($str);
2460
                $this->_getNextToken($str, $token);
2461
            }
2462
2463
            $this->_parseString($str, ' ', __LINE__, __FILE__);
2464
2465
            $this->_getNextToken($str, $token);
2466
            if ('+' == $token) {
2467
                $this->_getToEOL($str);
2468
                $this->_getNextToken($str, $token);
2469
            } elseif (is_numeric($token)) {
2470
                // The token is a NUMBER so I store it
2471
                $msg_nro = $token;
2472
                $this->_parseSpace($str, __LINE__, __FILE__);
2473
2474
                // I get the command
2475
                $this->_getNextToken($str, $command);
2476
2477
                if (false === ($ext_arr = $this->_retrParsedResponse($str, $command, $msg_nro))) {
2478
                    //  if this bogus response cis a FLAGS () or EXPUNGE response
2479
                    // the ignore it
2480
                    if ('FLAGS' !== $command && 'EXPUNGE' !== $command) {
2481
                        $this->_prot_error('bogus response!!!!', __LINE__, __FILE__, false);
2482
                    }
2483
                }
2484
                $result_array[] = ['COMMAND' => $command, 'NRO' => $msg_nro, 'EXT' => $ext_arr];
2485
            } else {
2486
                // OK the token is not a NUMBER so it MUST be a COMMAND
2487
                $command = $token;
2488
2489
                /* Call the parser return the array
2490
                 take care of bogus responses!
2491
                 */
2492
2493
                if (false === ($ext_arr = $this->_retrParsedResponse($str, $command))) {
2494
                    $this->_prot_error("bogus response!!!! (COMMAND:$command)", __LINE__, __FILE__);
2495
                }
2496
                $result_array[] = ['COMMAND' => $command, 'EXT' => $ext_arr];
2497
            }
2498
2499
            $this->_getNextToken($str, $token);
2500
2501
            $token = \mb_strtoupper($token);
2502
            if ("\r\n" !== $token && '' != $token) {
2503
                $this->_prot_error("PARSE ERROR!!! must be a '\\r\\n' here  but is a '$token'!!!! (getting the next line)|STR:|$str|", __LINE__, __FILE__);
2504
            }
2505
            $this->_getNextToken($str, $token);
2506
2507
            if ('+' == $token) {
2508
                //if the token  is + ignore the line
2509
                // TODO: verify that this is correct!!!
2510
                $this->_getToEOL($str);
2511
                $this->_getNextToken($str, $token);
2512
            }
2513
        }//While
2514
        // OK we finish the UNTAGGED Response now we must parse the FINAL TAGGED RESPONSE
2515
        //TODO: make this a litle more elegant!
2516
2517
        $this->_parseSpace($str, __LINE__, __FILE__, false);
2518
2519
        $this->_getNextToken($str, $cmd_status);
2520
2521
        $str_line = rtrim(mb_substr($this->_getToEOL($str), 1));
2522
2523
        $response['RESPONSE'] = ['CODE' => $cmd_status, 'STR_CODE' => $str_line, 'CMDID' => $cmdid];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
2524
2525
        $ret = $response;
2526
        if (!empty($result_array)) {
2527
            $ret = array_merge($ret, ['PARSED' => $result_array]);
2528
        }
2529
2530
        if ($this->_unParsedReturn) {
2531
            $unparsed['UNPARSED'] = $unparsed_str;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $unparsed_str does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
$unparsed was never initialized. Although not strictly required by PHP, it is generally a good practice to add $unparsed = array(); before regardless.
Loading history...
2532
            $ret                  = array_merge($ret, $unparsed);
2533
        }
2534
2535
        if (isset($status_arr)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $status_arr does not exist. Did you maybe mean $status?
Loading history...
2536
            $status['STATUS'] = $status_arr;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$status was never initialized. Although not strictly required by PHP, it is generally a good practice to add $status = array(); before regardless.
Loading history...
2537
            $ret              = array_merge($ret, $status);
2538
        }
2539
2540
        return $ret;
2541
    }
2542
2543
    /**
2544
     * @param string $command
2545
     * @param string $params
2546
     * @return array|\PEAR_Error
2547
     */
2548
    public function _genericCommand($command, string $params = '')
2549
    {
2550
        if (!$this->_connected) {
2551
            return new PEAR_Error("not connected! (CMD:$command)");
2552
        }
2553
        $cmdid = $this->_getCmdId();
2554
        $this->_putCMD($cmdid, $command, $params);
2555
        $args = $this->_getRawResponse($cmdid);
2556
2557
        return $this->_genericImapResponseParser($args, $cmdid);
2558
    }
2559
2560
    /**
2561
     * @param string $str
2562
     * @return \PEAR_Error|string
2563
     */
2564
    public function utf_7_encode(string $str)
2565
    {
2566
        if (!$this->_useUTF_7) {
2567
            return $str;
2568
        }
2569
        //return imap_utf7_encode($str);
2570
2571
        $encoded_utf7 = '';
2572
        $base64_part  = '';
2573
        if (is_array($str)) {
0 ignored issues
show
The condition is_array($str) is always false.
Loading history...
2574
            return new PEAR_Error('error');
2575
        }
2576
2577
        for ($i = 0, $iMax = mb_strlen($str); $i < $iMax; ++$i) {
2578
            //those chars should be base64 encoded
2579
            if (((ord($str[$i]) >= 39) && (ord($str[$i]) <= 126)) || ((ord($str[$i]) >= 32) && (ord($str[$i]) <= 37))) {
2580
                if ($base64_part) {
2581
                    $encoded_utf7 = sprintf('%s&%s-', $encoded_utf7, str_replace('=', '', base64_encode($base64_part)));
2582
                    $base64_part  = '';
2583
                }
2584
                $encoded_utf7 = sprintf('%s%s', $encoded_utf7, $str[$i]);
2585
            } else {
2586
                //handle &
2587
                if (38 == ord($str[$i])) {
2588
                    if ($base64_part) {
2589
                        $encoded_utf7 = sprintf('%s&%s-', $encoded_utf7, str_replace('=', '', base64_encode($base64_part)));
2590
                        $base64_part  = '';
2591
                    }
2592
                    $encoded_utf7 = sprintf('%s&-', $encoded_utf7);
2593
                } else {
2594
                    $base64_part = sprintf('%s%s', $base64_part, $str[$i]);
2595
                    //$base64_part = sprintf("%s%s%s",$base64_part , chr(0) , $str[$i]);
2596
                }
2597
            }
2598
        }
2599
        if ($base64_part) {
2600
            $encoded_utf7 = sprintf('%s&%s-', $encoded_utf7, str_replace('=', '', base64_encode($base64_part)));
2601
            $base64_part  = '';
0 ignored issues
show
The assignment to $base64_part is dead and can be removed.
Loading history...
2602
        }
2603
2604
        return $encoded_utf7;
2605
    }
2606
2607
    /**
2608
     * @param string $str
2609
     * @return string
2610
     */
2611
    public function utf_7_decode($str): string
2612
    {
2613
        if (!$this->_useUTF_7) {
2614
            return $str;
2615
        }
2616
2617
        //return imap_utf7_decode($str);
2618
2619
        $base64_part  = '';
2620
        $decoded_utf7 = '';
2621
2622
        for ($i = 0, $iMax = mb_strlen($str); $i < $iMax; ++$i) {
2623
            if ('' !== $base64_part) {
2624
                if ('-' == $str[$i]) {
2625
                    if ('&' === $base64_part) {
2626
                        $decoded_utf7 = sprintf('%s&', $decoded_utf7);
2627
                    } else {
2628
                        $next_part_decoded = base64_decode(mb_substr($base64_part, 1), true);
2629
                        $decoded_utf7      = sprintf('%s%s', $decoded_utf7, $next_part_decoded);
2630
                    }
2631
                    $base64_part = '';
2632
                } else {
2633
                    $base64_part = sprintf('%s%s', $base64_part, $str[$i]);
2634
                }
2635
            } else {
2636
                if ('&' === $str[$i]) {
2637
                    $base64_part = '&';
2638
                } else {
2639
                    $decoded_utf7 = sprintf('%s%s', $decoded_utf7, $str[$i]);
2640
                }
2641
            }
2642
        }
2643
2644
        return $decoded_utf7;
2645
    }
2646
}//Class
2647