Net_IMAPProtocol::cmdRename()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 6
rs 10
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
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
Bug introduced by
$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
Bug introduced by
$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
Bug introduced by
$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
Bug introduced by
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
Unused Code introduced by
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
Bug introduced by
$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
Bug introduced by
$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
introduced by
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
introduced by
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
introduced by
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
introduced by
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
introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
introduced by
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
introduced by
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
Bug introduced by
$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
Bug introduced by
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
introduced by
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
Bug introduced by
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
introduced by
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
Unused Code introduced by
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