Net_POP3::connect()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 26
rs 9.8333
1
<?php
2
// +-----------------------------------------------------------------------+
3
// | Copyright (c) 2002, Richard Heyes                                     |
4
// | All rights reserved.                                                  |
5
// |                                                                       |
6
// | Redistribution and use in source and binary forms, with or without    |
7
// | modification, are permitted provided that the following conditions    |
8
// | are met:                                                              |
9
// |                                                                       |
10
// | o Redistributions of source code must retain the above copyright      |
11
// |   notice, this list of conditions and the following disclaimer.       |
12
// | o Redistributions in binary form must reproduce the above copyright   |
13
// |   notice, this list of conditions and the following disclaimer in the |
14
// |   documentation and/or other materials provided with the distribution.|
15
// | o The names of the authors may not be used to endorse or promote      |
16
// |   products derived from this software without specific prior written  |
17
// |   permission.                                                         |
18
// |                                                                       |
19
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
20
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
21
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
23
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
25
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
28
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
30
// |                                                                       |
31
// +-----------------------------------------------------------------------+
32
// | Author: Richard Heyes <[email protected]>                           |
33
// | Co-Author: Damian Fernandez Sosa <[email protected]>               |
34
// +-----------------------------------------------------------------------+
35
//
36
// $Id: POP3.php,v 1.2 2005/02/18 15:29:06 eric_juden Exp $
37
38
require_once XHELP_PEAR_PATH . '/Net/Socket.php';
39
40
/**
41
 *  +----------------------------- IMPORTANT ------------------------------+
42
 *  | Usage of this class compared to native php extensions such as IMAP   |
43
 *  | is slow and may be feature deficient. If available you are STRONGLY  |
44
 *  | recommended to use the php extensions.                               |
45
 *  +----------------------------------------------------------------------+
46
 *
47
 * POP3 Access Class
48
 *
49
 * For usage see the example script
50
 */
51
define('NET_POP3_STATE_DISCONNECTED', 1, true);
52
define('NET_POP3_STATE_AUTHORISATION', 2, true);
53
define('NET_POP3_STATE_TRANSACTION', 4, true);
54
55
/**
56
 * Class Net_POP3
57
 */
58
class Net_POP3
59
{
60
    /**
61
     * Some basic information about the mail drop
62
     * garnered from the STAT command
63
     *
64
     * @var array
65
     */
66
    public $_maildrop;
67
    /**
68
     * Used for APOP to store the timestamp
69
     *
70
     * @var string
71
     */
72
    public $_timestamp;
73
    /**
74
     * Timeout that is passed to the socket object
75
     *
76
     * @var int
77
     */
78
    public $_timeout;
79
    /**
80
     * Socket object
81
     *
82
     * @var object
83
     */
84
    public $_socket;
85
    /**
86
     * Current state of the connection. Used with the
87
     * constants defined above.
88
     *
89
     * @var int
90
     */
91
    public $_state;
92
    /**
93
     * Hostname to connect to
94
     *
95
     * @var string
96
     */
97
    public $_host;
98
    /**
99
     * Port to connect to
100
     *
101
     * @var int
102
     */
103
    public $_port;
104
    /**
105
     * To allow class debuging
106
     * @var bool
107
     */
108
    public $_debug = false;
109
    /**
110
     * The auth methods this class support
111
     * @var array
112
     */
113
    //var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
114
    //Disabling DIGEST-MD5 for now
115
    public $supportedAuthMethods = [/*'CRAM-MD5', 'APOP' ,*/
116
                                    'PLAIN',
117
                                    'LOGIN',
118
                                    'USER',
119
    ];
120
    //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
121
    //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
122
123
    /**
124
     * The auth methods this class support
125
     * @var array
126
     */
127
    public $supportedSASLAuthMethods = ['DIGEST-MD5', 'CRAM-MD5'];
128
    /**
129
     * The capability response
130
     * @var array
131
     */
132
    public $_capability;
133
134
    /**
135
     * Constructor. Sets up the object variables, and instantiates
136
     * the socket object.
137
     *
138
     */
139
140
    public function __construct()
141
    {
142
        $this->_timestamp = ''; // Used for APOP
143
        $this->_maildrop  = [];
144
        $this->_timeout   = 10;
145
        $this->_state     = NET_POP3_STATE_DISCONNECTED;
146
        $this->_socket    = new Net_Socket();
147
        /*
148
         * Include the Auth_SASL package.  If the package is not available,
149
         * we disable the authentication methods that depend upon it.
150
         */
151
        if (false === (@require_once __DIR__ . '/Auth/SASL.php')) {
152
            if ($this->_debug) {
153
                echo "AUTH_SASL NOT PRESENT!\n";
154
            }
155
            foreach ($this->supportedSASLAuthMethods as $SASLMethod) {
156
                $pos = array_search($SASLMethod, $this->supportedAuthMethods, true);
157
                if ($this->_debug) {
158
                    echo "DISABLING METHOD $SASLMethod\n";
159
                }
160
                unset($this->supportedAuthMethods[$pos]);
161
            }
162
        }
163
    }
164
165
    /**
166
     * Handles the errors the class can find
167
     * on the server
168
     *
169
     * @param string $msg
170
     * @param int    $code
171
     * @return PEAR_Error
172
     */
173
    public function _raiseError(string $msg, int $code = -1): PEAR_Error
174
    {
175
        require_once XHELP_PEAR_PATH . '/PEAR.php';
176
177
        return PEAR::raiseError($msg, $code);
0 ignored issues
show
Bug Best Practice introduced by
The method PEAR::raiseError() 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

177
        return PEAR::/** @scrutinizer ignore-call */ raiseError($msg, $code);
Loading history...
Bug introduced by
It seems like $code can also be of type integer; however, parameter $code of PEAR::raiseError() does only seem to accept null, 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

177
        return PEAR::raiseError($msg, /** @scrutinizer ignore-type */ $code);
Loading history...
178
    }
179
180
    /**
181
     * Connects to the given host on the given port.
182
     * Also looks for the timestamp in the greeting
183
     * needed for APOP authentication
184
     *
185
     * @param string $host /IP address to connect to Hostname
186
     * @param int    $port Port to use to connect to on host
187
     * @return bool  Success/Failure
188
     */
189
    public function connect(string $host = 'localhost', int $port = 110): bool
190
    {
191
        $this->_host = $host;
192
        $this->_port = $port;
193
194
        $result = $this->_socket->connect($host, $port, false, $this->_timeout);
195
        if (true === $result) {
196
            $data = $this->_recvLn();
197
198
            if ($this->_checkResponse($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $response of Net_POP3::_checkResponse() does only seem to accept string, 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

198
            if ($this->_checkResponse(/** @scrutinizer ignore-type */ $data)) {
Loading history...
199
                // if the response begins with '+OK' ...
200
                //            if (@substr(strtoupper($data), 0, 3) == '+OK') {
201
                // Check for string matching apop timestamp
202
                if (preg_match('/<.+@.+>/U', $data, $matches)) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $subject of preg_match() does only seem to accept string, 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

202
                if (preg_match('/<.+@.+>/U', /** @scrutinizer ignore-type */ $data, $matches)) {
Loading history...
203
                    $this->_timestamp = $matches[0];
204
                }
205
                $this->_maildrop = [];
206
                $this->_state    = NET_POP3_STATE_AUTHORISATION;
207
208
                return true;
209
            }
210
        }
211
212
        $this->_socket->disconnect();
213
214
        return false;
215
    }
216
217
    /**
218
     * Disconnect function. Sends the QUIT command
219
     * and closes the socket.
220
     *
221
     * @return bool Success/Failure
222
     */
223
    public function disconnect(): bool
224
    {
225
        return $this->_cmdQuit();
226
    }
227
228
    /**
229
     * Performs the login procedure. If there is a timestamp
230
     * stored, APOP will be tried first, then basic USER/PASS.
231
     *
232
     * @param string $user Username to use
233
     * @param string $pass Password to use
234
     * @param bool   $apop Whether to try APOP first
235
     * @return bool|\PEAR_Error  true on Success/ PEAR_ERROR on error
236
     */
237
    public function login(string $user, string $pass, bool $apop = true)
238
    {
239
        if (NET_POP3_STATE_AUTHORISATION == $this->_state) {
240
            if (PEAR::isError($ret = $this->_cmdAuthenticate($user, $pass, $apop))) {
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

240
            if (PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_cmdAuthenticate($user, $pass, $apop))) {
Loading history...
Bug introduced by
$apop of type boolean is incompatible with the type null|string expected by parameter $userMethod of Net_POP3::_cmdAuthenticate(). ( Ignorable by Annotation )

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

240
            if (PEAR::isError($ret = $this->_cmdAuthenticate($user, $pass, /** @scrutinizer ignore-type */ $apop))) {
Loading history...
241
                return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type string which is incompatible with the documented return type PEAR_Error|boolean.
Loading history...
242
            }
243
            if (!PEAR::isError($ret)) {
244
                $this->_state = NET_POP3_STATE_TRANSACTION;
245
246
                return true;
247
            }
248
        }
249
250
        return $this->_raiseError('Generic login error', 1);
251
    }
252
253
    /**
254
     * Parses the response from the capability command. Stores
255
     * the result in $this->_capability
256
     */
257
    public function _parseCapability(): void
258
    {
259
        if (!PEAR::isError($data = $this->_sendCmd('CAPA'))) {
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

259
        if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('CAPA'))) {
Loading history...
260
            $data = $this->_getMultiline();
261
        }
262
        $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
263
264
        foreach ($data as $i => $iValue) {
265
            $capa = '';
266
            if (preg_match('/^([a-z,\-]+)( ((.*))|$)$/i', $iValue, $matches)) {
267
                $capa = \mb_strtolower($matches[1]);
268
                switch ($capa) {
269
                    case 'implementation':
270
                        $this->_capability['implementation'] = $matches[3];
271
                        break;
272
                    case 'sasl':
273
                        $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
274
                        break;
275
                    default:
276
                        $this->_capability[$capa] = $matches[2];
277
                        break;
278
                }
279
            }
280
        }
281
    }
282
283
    /**
284
     * Returns the name of the best authentication method that the server
285
     * has advertised.
286
     *
287
     * @param mixed $userMethod
288
     * @return mixed Returns a string containing the name of the best
289
     *               supported authentication method or a PEAR_Error object
290
     *               if a failure condition is encountered.
291
     * @since  1.0
292
     */
293
    public function _getBestAuthMethod($userMethod = null)
294
    {
295
        /*
296
         return 'USER';
297
         return 'APOP';
298
         return 'DIGEST-MD5';
299
         return 'CRAM-MD5';
300
         */
301
302
        $this->_parseCapability();
303
304
        //unset($this->_capability['sasl']);
305
306
        $serverMethods = $this->_capability['sasl'] ?? [/*'APOP',*/
307
                                                        'USER',
308
            ];
309
310
        if (null !== $userMethod && true !== $userMethod) {
311
            $methods   = [];
312
            $methods[] = $userMethod;
313
314
            return $userMethod;
315
        }
316
        $methods = $this->supportedAuthMethods;
317
318
        if ((null !== $methods) && (null !== $serverMethods)) {
319
            foreach ($methods as $method) {
320
                if (in_array($method, $serverMethods, true)) {
321
                    return $method;
322
                }
323
            }
324
            $serverMethods = implode(',', $serverMethods);
325
            $myMethods     = implode(',', $this->supportedAuthMethods);
326
327
            return $this->_raiseError("$method NOT supported authentication method!. This 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 319. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
328
        }
329
330
        return $this->_raiseError("This server don't support any Auth methods");
331
    }
332
333
    /**
334
     * Handles the authentication using any known method
335
     *
336
     * @param string      $uid        The userid to authenticate as.
337
     * @param string      $pwd        The password to authenticate with.
338
     * @param string|null $userMethod The method to use ( if $userMethod == '' then the class chooses the best method (the stronger is the best ) )
339
     *
340
     * @return \PEAR_Error|string  string or PEAR_Error
341
     *
342
     * @access private
343
     * @since  1.0
344
     */
345
    private function _cmdAuthenticate(string $uid, string $pwd, string $userMethod = null)
346
    {
347
        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

347
        if (PEAR::/** @scrutinizer ignore-call */ isError($method = $this->_getBestAuthMethod($userMethod))) {
Loading history...
348
            return $method;
349
        }
350
        switch ($method) {
351
            case 'DIGEST-MD5':
352
                $result = $this->_authDigest_MD5($uid, $pwd);
353
                break;
354
            case 'CRAM-MD5':
355
                $result = $this->_authCRAM_MD5($uid, $pwd);
356
                break;
357
            case 'LOGIN':
358
                $result = $this->_authLOGIN($uid, $pwd);
359
                break;
360
            case 'PLAIN':
361
                $result = $this->_authPLAIN($uid, $pwd);
362
                break;
363
            case 'APOP':
364
                $result = $this->_cmdApop($uid, $pwd);
365
                // if APOP fails fallback to USER auth
366
                if (false === $result) {
367
                    echo "APOP FAILED!!!\n";
368
                    $result = $this->_authUSER($uid, $pwd);
369
                }
370
                break;
371
            case 'USER':
372
                $result = $this->_authUSER($uid, $pwd);
373
                break;
374
            default:
375
                $result = $this->_authUSER($uid, $pwd);
376
                //$result = $this->_raiseError( "$method is not a supported authentication method" );
377
                break;
378
        }
379
380
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type array|boolean which is incompatible with the documented return type PEAR_Error|string.
Loading history...
381
    }
382
383
    /**
384
     * Authenticates the user using the USER-PASS method.
385
     *
386
     * @param string $user The userid to authenticate as.
387
     * @param string $pass The password to authenticate with.
388
     *
389
     * @return bool|\PEAR_Error    true on success or PEAR_Error on failure
390
     *
391
     * @access private
392
     * @since  1.0
393
     */
394
    private function _authUSER(string $user, string $pass)
395
    {
396
        if (PEAR::isError($ret = $this->_cmdUser($user))) {
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

396
        if (PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_cmdUser($user))) {
Loading history...
397
            return $ret;
398
        }
399
        if (PEAR::isError($ret = $this->_cmdPass($pass))) {
400
            return $ret;
401
        }
402
403
        return true;
404
    }
405
406
    /**
407
     * Authenticates the user using the PLAIN method.
408
     *
409
     * @param string $user The userid to authenticate as.
410
     * @param string $pass The password to authenticate with.
411
     *
412
     * @return array|bool Returns an array containing the response
413
     *
414
     * @access private
415
     * @since  1.0
416
     */
417
    private function _authPLAIN(string $user, string $pass)
418
    {
419
        $cmd = sprintf('AUTH PLAIN %s', base64_encode(chr(0) . $user . chr(0) . $pass));
420
421
        if (PEAR::isError($ret = $this->_send($cmd))) {
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

421
        if (PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_send($cmd))) {
Loading history...
422
            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|boolean.
Loading history...
423
        }
424
        if (PEAR::isError($challenge = $this->_recvLn())) {
425
            return $challenge;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $challenge also could return the type PEAR_Error which is incompatible with the documented return type array|boolean.
Loading history...
426
        }
427
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $response of Net_POP3::_checkResponse() does only seem to accept string, 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

427
        if (PEAR::isError($ret = $this->_checkResponse(/** @scrutinizer ignore-type */ $challenge))) {
Loading history...
428
            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|boolean.
Loading history...
429
        }
430
431
        return true;
432
    }
433
434
    /**
435
     * Authenticates the user using the PLAIN method.
436
     *
437
     * @param string $user The userid to authenticate as.
438
     * @param string $pass The password to authenticate with.
439
     *
440
     * @return array|\PEAR_Error Returns an array containing the response
441
     *
442
     * @access private
443
     * @since  1.0
444
     */
445
    private function _authLOGIN(string $user, string $pass)
446
    {
447
        $this->_send('AUTH LOGIN');
448
449
        if (PEAR::isError($challenge = $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

449
        if (PEAR::/** @scrutinizer ignore-call */ isError($challenge = $this->_recvLn())) {
Loading history...
450
            return $challenge;
451
        }
452
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $response of Net_POP3::_checkResponse() does only seem to accept string, 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

452
        if (PEAR::isError($ret = $this->_checkResponse(/** @scrutinizer ignore-type */ $challenge))) {
Loading history...
453
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
454
        }
455
456
        if (PEAR::isError($ret = $this->_send(sprintf('"%s"', base64_encode($user))))) {
457
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
458
        }
459
460
        if (PEAR::isError($challenge = $this->_recvLn())) {
461
            return $challenge;
462
        }
463
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
464
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
465
        }
466
467
        if (PEAR::isError($ret = $this->_send(sprintf('"%s"', base64_encode($pass))))) {
468
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
469
        }
470
471
        if (PEAR::isError($challenge = $this->_recvLn())) {
472
            return $challenge;
473
        }
474
475
        return $this->_checkResponse($ret);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_checkResponse($ret) also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
Bug introduced by
$ret of type PEAR_Error|true is incompatible with the type string expected by parameter $response of Net_POP3::_checkResponse(). ( Ignorable by Annotation )

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

475
        return $this->_checkResponse(/** @scrutinizer ignore-type */ $ret);
Loading history...
476
    }
477
478
    /**
479
     * Authenticates the user using the CRAM-MD5 method.
480
     *
481
     * @param string $uid The userid to authenticate as.
482
     * @param string $pwd The password to authenticate with.
483
     *
484
     * @return array|\PEAR_Error Returns an array containing the response
485
     *
486
     * @access private
487
     * @since  1.0
488
     */
489
    private function _authCRAM_MD5(string $uid, string $pwd)
490
    {
491
        if (PEAR::isError($ret = $this->_send('AUTH 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

491
        if (PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_send('AUTH CRAM-MD5'))) {
Loading history...
492
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
493
        }
494
495
        if (PEAR::isError($challenge = $this->_recvLn())) {
496
            return $challenge;
497
        }
498
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $response of Net_POP3::_checkResponse() does only seem to accept string, 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

498
        if (PEAR::isError($ret = $this->_checkResponse(/** @scrutinizer ignore-type */ $challenge))) {
Loading history...
499
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
500
        }
501
502
        // remove '+ '
503
504
        $challenge = mb_substr($challenge, 2);
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $string of mb_substr() does only seem to accept string, 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

504
        $challenge = mb_substr(/** @scrutinizer ignore-type */ $challenge, 2);
Loading history...
505
506
        $challenge = base64_decode($challenge, true);
507
508
        $cram     = &Auth_SASL::factory('crammd5');
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...
509
        $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
510
511
        if (PEAR::isError($error = $this->_send($auth_str))) {
512
            return $error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $error also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
513
        }
514
        if (PEAR::isError($ret = $this->_recvLn())) {
515
            return $ret;
516
        }
517
        //echo "RET:$ret\n";
518
        return $this->_checkResponse($ret);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_checkResponse($ret) also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
519
    }
520
521
    /**
522
     * Authenticates the user using the DIGEST-MD5 method.
523
     *
524
     * @param string $uid The userid to authenticate as.
525
     * @param string $pwd The password to authenticate with.
526
     * @param string The efective user
527
     *
528
     * @return array|\PEAR_Error Returns an array containing the response
529
     *
530
     * @access private
531
     * @since  1.0
532
     */
533
    private function _authDigest_MD5(string $uid, string $pwd)
534
    {
535
        if (PEAR::isError($ret = $this->_send('AUTH 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

535
        if (PEAR::/** @scrutinizer ignore-call */ isError($ret = $this->_send('AUTH DIGEST-MD5'))) {
Loading history...
536
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
537
        }
538
539
        if (PEAR::isError($challenge = $this->_recvLn())) {
540
            return $challenge;
541
        }
542
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $response of Net_POP3::_checkResponse() does only seem to accept string, 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

542
        if (PEAR::isError($ret = $this->_checkResponse(/** @scrutinizer ignore-type */ $challenge))) {
Loading history...
543
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
544
        }
545
546
        // remove '+ '
547
        $challenge = mb_substr($challenge, 2);
0 ignored issues
show
Bug introduced by
It seems like $challenge can also be of type PEAR_Error; however, parameter $string of mb_substr() does only seem to accept string, 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

547
        $challenge = mb_substr(/** @scrutinizer ignore-type */ $challenge, 2);
Loading history...
548
549
        $challenge = base64_decode($challenge, true);
550
        $digest    = &Auth_SASL::factory('digestmd5');
551
        $auth_str  = base64_encode($digest->getResponse($uid, $pwd, $challenge, 'localhost', 'pop3'));
552
553
        if (PEAR::isError($error = $this->_send($auth_str))) {
554
            return $error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $error also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
555
        }
556
557
        if (PEAR::isError($challenge = $this->_recvLn())) {
558
            return $challenge;
559
        }
560
        if (PEAR::isError($ret = $this->_checkResponse($challenge))) {
561
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
562
        }
563
        /*
564
         * We don't use the protocol's third step because POP3 doesn't allow
565
         * subsequent authentication, so we just silently ignore it.
566
         */
567
568
        if (PEAR::isError($challenge = $this->_send("\r\n"))) {
569
            return $challenge;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $challenge also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
570
        }
571
572
        if (PEAR::isError($challenge = $this->_recvLn())) {
573
            return $challenge;
574
        }
575
576
        return $this->_checkResponse($challenge);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_checkResponse($challenge) also could return the type true which is incompatible with the documented return type PEAR_Error|array.
Loading history...
577
    }
578
579
    /**
580
     * Sends the APOP command
581
     *
582
     * @param string $user Username to send
583
     * @param string $pass Password to send
584
     * @return bool|\PEAR_Error Success/Failure
585
     */
586
    public function _cmdApop(string $user, string $pass)
587
    {
588
        if (NET_POP3_STATE_AUTHORISATION == $this->_state) {
589
            if (!empty($this->_timestamp)) {
590
                if (PEAR::isError($data = $this->_sendCmd('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)))) {
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

590
                if (PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)))) {
Loading history...
591
                    return $data;
592
                }
593
                $this->_state = NET_POP3_STATE_TRANSACTION;
594
595
                return true;
596
            }
597
        }
598
599
        return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State1');
600
    }
601
602
    /**
603
     * Returns the raw headers of the specified message.
604
     *
605
     * @param int $msg_id Message number
606
     * @return mixed   Either raw headers or false on error
607
     */
608
609
    /**
610
     * @param int $msg_id
611
     * @return bool|string
612
     */
613
    public function getRawHeaders(int $msg_id)
614
    {
615
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
616
            return $this->_cmdTop($msg_id, 0);
617
        }
618
619
        return false;
620
    }
621
622
    /*
623
     * Returns the  headers of the specified message in an
624
     * associative array. Array keys are the header names, array
625
     * values are the header values. In the case of multiple headers
626
     * having the same names, eg Received:, the array value will be
627
     * an indexed array of all the header values.
628
     *
629
     * @param  int $msg_id Message number
630
     * @return mixed   Either array of headers or false on error
631
     */
632
633
    /**
634
     * @param int $msg_id
635
     * @return array|false
636
     */
637
    public function getParsedHeaders(int $msg_id)
638
    {
639
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
640
            $raw_headers = rtrim($this->getRawHeaders($msg_id));
0 ignored issues
show
Bug introduced by
It seems like $this->getRawHeaders($msg_id) can also be of type false; however, parameter $string of rtrim() does only seem to accept string, 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

640
            $raw_headers = rtrim(/** @scrutinizer ignore-type */ $this->getRawHeaders($msg_id));
Loading history...
641
642
            $raw_headers = preg_replace("/\r\n[ \t]+/", ' ', $raw_headers); // Unfold headers
643
            $raw_headers = explode("\r\n", $raw_headers);
644
            foreach ($raw_headers as $value) {
645
                $name  = mb_substr($value, 0, $pos = mb_strpos($value, ':'));
646
                $value = ltrim(mb_substr($value, $pos + 1));
647
                if (isset($headers[$name]) && is_array($headers[$name])) {
648
                    $headers[$name][] = $value;
649
                } elseif (isset($headers[$name])) {
650
                    $headers[$name] = [$headers[$name], $value];
651
                } else {
652
                    $headers[$name] = $value;
653
                }
654
            }
655
656
            return $headers;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $headers does not seem to be defined for all execution paths leading up to this point.
Loading history...
657
        }
658
659
        return false;
660
    }
661
662
    /*
663
     * Returns the body of the message with given message number.
664
     *
665
     * @param  int $msg_id Message number
666
     * @return mixed   Either message body or false on error
667
     */
668
669
    /**
670
     * @param int $msg_id
671
     * @return false|mixed|string
672
     */
673
    public function getBody(int $msg_id)
674
    {
675
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
676
            $msg = $this->_cmdRetr($msg_id);
677
678
            return mb_substr($msg, mb_strpos($msg, "\r\n\r\n") + 4);
0 ignored issues
show
Bug introduced by
It seems like $msg can also be of type false; however, parameter $haystack of mb_strpos() does only seem to accept string, 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

678
            return mb_substr($msg, mb_strpos(/** @scrutinizer ignore-type */ $msg, "\r\n\r\n") + 4);
Loading history...
Bug introduced by
It seems like $msg can also be of type false; however, parameter $string of mb_substr() does only seem to accept string, 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

678
            return mb_substr(/** @scrutinizer ignore-type */ $msg, mb_strpos($msg, "\r\n\r\n") + 4);
Loading history...
679
        }
680
681
        return false;
682
    }
683
684
    /*
685
     * Returns the entire message with given message number.
686
     *
687
     * @param  int $msg_id Message number
688
     * @return mixed   Either entire message or false on error
689
     */
690
691
    /**
692
     * @param int $msg_id
693
     * @return false|string
694
     */
695
    public function getMsg(int $msg_id)
696
    {
697
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
698
            return $this->_cmdRetr($msg_id);
699
        }
700
701
        return false;
702
    }
703
704
    /*
705
     * Returns the size of the maildrop
706
     *
707
     * @return mixed Either size of maildrop or false on error
708
     */
709
710
    /**
711
     * @return false|mixed
712
     */
713
    public function getSize()
714
    {
715
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
716
            if (isset($this->_maildrop['size'])) {
717
                return $this->_maildrop['size'];
718
            }
719
            [, $size] = $this->_cmdStat();
720
721
            return $size;
722
        }
723
724
        return false;
725
    }
726
727
    /*
728
     * Returns number of messages in this maildrop
729
     *
730
     * @return mixed Either number of messages or false on error
731
     */
732
733
    /**
734
     * @return false|mixed
735
     */
736
    public function numMsg()
737
    {
738
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
739
            if (isset($this->_maildrop['num_msg'])) {
740
                return $this->_maildrop['num_msg'];
741
            }
742
            [$num_msg,] = $this->_cmdStat();
743
744
            return $num_msg;
745
        }
746
747
        return false;
748
    }
749
750
    /*
751
     * Marks a message for deletion. Only will be deleted if the
752
     * disconnect() method is called.
753
     *
754
     * @param  Message $msg_id to delete
755
     * @return bool Success/Failure
756
     */
757
758
    /**
759
     * @param int $msg_id
760
     * @return false|mixed|\PEAR_Error
761
     */
762
    public function deleteMsg(int $msg_id)
763
    {
764
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
765
            return $this->_cmdDele($msg_id);
766
        }
767
768
        return false;
769
    }
770
771
    /*
772
     * Combination of LIST/UIDL commands, returns an array
773
     * of data
774
     *
775
     * @param  Optional $msg_id message number
776
     * @return mixed Array of data or false on error
777
     */
778
779
    /**
780
     * @param null $msg_id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $msg_id is correct as it would always require null to be passed?
Loading history...
781
     * @return array|false
782
     */
783
    public function getListing($msg_id = null)
784
    {
785
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
786
            if (isset($msg_id)) {
787
                if ($list = $this->_cmdList($msg_id) and $uidl = $this->_cmdUidl($msg_id)) {
788
                    return array_merge($list, $uidl);
789
                }
790
            } else {
791
                $list = $this->_cmdList();
792
                if ($list) {
793
                    $uidl = $this->_cmdUidl();
794
                    if ($uidl) {
795
                        foreach ($uidl as $i => $value) {
796
                            $list[$i]['uidl'] = $value['uidl'];
797
                        }
798
                    }
799
800
                    return $list;
801
                }
802
            }
803
        }
804
805
        return false;
806
    }
807
808
    /*
809
     * Sends the USER command
810
     *
811
     * @param  Username $user to send
812
     * @return bool  Success/Failure
813
     */
814
815
    /**
816
     * @param string $user
817
     * @return mixed|\PEAR_Error
818
     */
819
    public function _cmdUser(string $user)
820
    {
821
        if (NET_POP3_STATE_AUTHORISATION == $this->_state) {
822
            return $this->_sendCmd('USER ' . $user);
823
        }
824
825
        return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
826
    }
827
828
    /*
829
     * Sends the PASS command
830
     *
831
     * @param  Password $pass to send
832
     * @return bool  Success/Failure
833
     */
834
835
    /**
836
     * @param string $pass
837
     * @return mixed|\PEAR_Error
838
     */
839
    public function _cmdPass($pass)
840
    {
841
        if (NET_POP3_STATE_AUTHORISATION == $this->_state) {
842
            return $this->_sendCmd('PASS ' . $pass);
843
        }
844
845
        return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
846
    }
847
848
    /*
849
     * Sends the STAT command
850
     *
851
     * @return mixed Indexed array of number of messages and
852
     *               maildrop size, or false on error.
853
     */
854
855
    /**
856
     * @return array|false
857
     */
858
    public function _cmdStat()
859
    {
860
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
861
            if (!PEAR::isError($data = $this->_sendCmd('STAT'))) {
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

861
            if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('STAT'))) {
Loading history...
862
                sscanf($data, '+OK %d %d', $msg_num, $size);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $size seems to be never defined.
Loading history...
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $string of sscanf() does only seem to accept string, 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

862
                sscanf(/** @scrutinizer ignore-type */ $data, '+OK %d %d', $msg_num, $size);
Loading history...
863
                $this->_maildrop['num_msg'] = $msg_num;
864
                $this->_maildrop['size']    = $size;
865
866
                return [$msg_num, $size];
867
            }
868
        }
869
870
        return false;
871
    }
872
873
    /*
874
     * Sends the LIST command
875
     *
876
     * @param  Optional $msg_id message number
877
     * @return mixed   Indexed array of msg_id/msg size or
878
     *                 false on error
879
     */
880
881
    /**
882
     * @param null $msg_id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $msg_id is correct as it would always require null to be passed?
Loading history...
883
     * @return array|false
884
     */
885
    public function _cmdList($msg_id = null)
886
    {
887
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
888
            if (isset($msg_id)) {
889
                if (!PEAR::isError($data = $this->_sendCmd('LIST ' . $msg_id))) {
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

889
                if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('LIST ' . $msg_id))) {
Loading history...
890
                    sscanf($data, '+OK %d %d', $msg_id, $size);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $size seems to be never defined.
Loading history...
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $string of sscanf() does only seem to accept string, 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

890
                    sscanf(/** @scrutinizer ignore-type */ $data, '+OK %d %d', $msg_id, $size);
Loading history...
891
892
                    return ['msg_id' => $msg_id, 'size' => $size];
893
                }
894
            } else {
895
                if (!PEAR::isError($data = $this->_sendCmd('LIST'))) {
896
                    $data = $this->_getMultiline();
897
                    $data = explode("\r\n", $data);
898
                    foreach ($data as $line) {
899
                        sscanf($line, '%s %s', $msg_id, $size);
900
                        $return[] = ['msg_id' => $msg_id, 'size' => $size];
901
                    }
902
903
                    return $return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $return seems to be defined by a foreach iteration on line 898. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
904
                }
905
            }
906
        }
907
908
        return false;
909
    }
910
911
    /**
912
     * Sends the RETR command
913
     *
914
     * @param int $msg_id The message number to retrieve
915
     * @return false|string   The message or false on error
916
     */
917
    public function _cmdRetr(int $msg_id)
918
    {
919
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
920
            if (!PEAR::isError($data = $this->_sendCmd('RETR ' . $msg_id))) {
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

920
            if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('RETR ' . $msg_id))) {
Loading history...
921
                $data = $this->_getMultiline();
922
923
                return $data;
924
            }
925
        }
926
927
        return false;
928
    }
929
930
    /**
931
     * Sends the DELE command
932
     *
933
     * @param int $msg_id Message number to mark as deleted
934
     * @return bool Success/Failure
935
     */
936
937
    /**
938
     * @param int $msg_id
939
     * @return false|mixed|\PEAR_Error
940
     */
941
    public function _cmdDele(int $msg_id)
942
    {
943
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
944
            return $this->_sendCmd('DELE ' . $msg_id);
945
        }
946
947
        return false;
948
    }
949
950
    /**
951
     * Sends the NOOP command
952
     *
953
     * @return bool Success/Failure
954
     */
955
    public function _cmdNoop(): bool
956
    {
957
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
958
            if (!PEAR::isError($data = $this->_sendCmd('NOOP'))) {
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

958
            if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('NOOP'))) {
Loading history...
959
                return true;
960
            }
961
        }
962
963
        return false;
964
    }
965
966
    /**
967
     * Sends the RSET command
968
     *
969
     * @return bool Success/Failure
970
     */
971
    public function _cmdRset(): bool
972
    {
973
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
974
            if (!PEAR::isError($data = $this->_sendCmd('RSET'))) {
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

974
            if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('RSET'))) {
Loading history...
975
                return true;
976
            }
977
        }
978
979
        return false;
980
    }
981
982
    /**
983
     * Sends the QUIT command
984
     *
985
     * @return bool Success/Failure
986
     */
987
    public function _cmdQuit(): bool
988
    {
989
        $data         = $this->_sendCmd('QUIT');
990
        $this->_state = NET_POP3_STATE_DISCONNECTED;
991
        $this->_socket->disconnect();
992
993
        return (bool)$data;
994
    }
995
996
    /**
997
     * Sends the TOP command
998
     *
999
     * @param int $msg_id    Message number
1000
     * @param int $num_lines Number of lines to retrieve
1001
     * @return false|string Message data or false on error
1002
     */
1003
    public function _cmdTop(int $msg_id, int $num_lines)
1004
    {
1005
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
1006
            if (!PEAR::isError($data = $this->_sendCmd('TOP ' . $msg_id . ' ' . $num_lines))) {
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

1006
            if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('TOP ' . $msg_id . ' ' . $num_lines))) {
Loading history...
1007
                return $this->_getMultiline();
1008
            }
1009
        }
1010
1011
        return false;
1012
    }
1013
1014
    /**
1015
     * Sends the UIDL command
1016
     *
1017
     * @param int|null $msg_id Message number
1018
     * @return array|false indexed array of msg_id/uidl or false on error
1019
     */
1020
1021
    public function _cmdUidl(int $msg_id = null)
1022
    {
1023
        if (NET_POP3_STATE_TRANSACTION == $this->_state) {
1024
            if (isset($msg_id)) {
1025
                $data = $this->_sendCmd('UIDL ' . $msg_id);
1026
                sscanf($data, '+OK %d %s', $msg_id, $uidl);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $uidl seems to be never defined.
Loading history...
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $string of sscanf() does only seem to accept string, 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

1026
                sscanf(/** @scrutinizer ignore-type */ $data, '+OK %d %s', $msg_id, $uidl);
Loading history...
1027
1028
                return ['msg_id' => $msg_id, 'uidl' => $uidl];
1029
            } else {
1030
                if (!PEAR::isError($data = $this->_sendCmd('UIDL'))) {
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

1030
                if (!PEAR::/** @scrutinizer ignore-call */ isError($data = $this->_sendCmd('UIDL'))) {
Loading history...
1031
                    $data = $this->_getMultiline();
1032
                    $data = explode("\r\n", $data);
1033
                    foreach ($data as $line) {
1034
                        sscanf($line, '%d %s', $msg_id, $uidl);
1035
                        $return[] = ['msg_id' => $msg_id, 'uidl' => $uidl];
1036
                    }
1037
1038
                    return $return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $return seems to be defined by a foreach iteration on line 1033. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1039
                }
1040
            }
1041
        }
1042
1043
        return false;
1044
    }
1045
1046
    /**
1047
     * Sends a command, checks the reponse, and
1048
     * if good returns the reponse, other wise
1049
     * returns false.
1050
     *
1051
     * @param string $cmd Command to send (\r\n will be appended)
1052
     * @return mixed First line of response if successful, otherwise false
1053
     */
1054
    public function _sendCmd(string $cmd)
1055
    {
1056
        $result = $this->_send($cmd);
1057
1058
        if (!PEAR::isError($result) && $result) {
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

1058
        if (!PEAR::/** @scrutinizer ignore-call */ isError($result) && $result) {
Loading history...
1059
            $data = $this->_recvLn();
1060
            if (!PEAR::isError($data) && 0 === mb_stripos($data, '+OK')) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type PEAR_Error; however, parameter $haystack of mb_stripos() does only seem to accept string, 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

1060
            if (!PEAR::isError($data) && 0 === mb_stripos(/** @scrutinizer ignore-type */ $data, '+OK')) {
Loading history...
1061
                return $data;
1062
            }
1063
        }
1064
1065
        return $this->_raiseError($data);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
1066
    }
1067
1068
    /**
1069
     * Reads a multiline reponse and returns the data
1070
     *
1071
     * @return string The reponse.
1072
     */
1073
    public function _getMultiline(): string
1074
    {
1075
        $data = '';
1076
        while (!PEAR::isError($tmp = $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

1076
        while (!PEAR::/** @scrutinizer ignore-call */ isError($tmp = $this->_recvLn())) {
Loading history...
1077
            if ('.' === $tmp) {
1078
                return mb_substr($data, 0, -2);
1079
            }
1080
            if (0 === mb_strpos($tmp, '..')) {
0 ignored issues
show
Bug introduced by
It seems like $tmp can also be of type PEAR_Error; however, parameter $haystack of mb_strpos() does only seem to accept string, 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

1080
            if (0 === mb_strpos(/** @scrutinizer ignore-type */ $tmp, '..')) {
Loading history...
1081
                $tmp = mb_substr($tmp, 1);
0 ignored issues
show
Bug introduced by
It seems like $tmp can also be of type PEAR_Error; however, parameter $string of mb_substr() does only seem to accept string, 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

1081
                $tmp = mb_substr(/** @scrutinizer ignore-type */ $tmp, 1);
Loading history...
1082
            }
1083
            $data .= $tmp . "\r\n";
1084
        }
1085
1086
        return mb_substr($data, 0, -2);
1087
    }
1088
1089
    /**
1090
     * Sets the bebug state
1091
     *
1092
     * @param bool $debug
1093
     */
1094
    public function setDebug(bool $debug = true): void
1095
    {
1096
        $this->_debug = $debug;
1097
    }
1098
1099
    /**
1100
     * Send the given string of data to the server.
1101
     *
1102
     * @param string $data The string of data to send.
1103
     *
1104
     * @return bool|\PEAR_Error True on success or a PEAR_Error object on failure.
1105
     *
1106
     * @since   1.0
1107
     */
1108
    public function _send(string $data)
1109
    {
1110
        if ($this->_debug) {
1111
            echo "C: $data\n";
1112
        }
1113
1114
        if (PEAR::isError($error = $this->_socket->writeLine($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

1114
        if (PEAR::/** @scrutinizer ignore-call */ isError($error = $this->_socket->writeLine($data))) {
Loading history...
1115
            return $this->_raiseError('Failed to write to socket: ' . $error->getMessage());
1116
        }
1117
1118
        return true;
1119
    }
1120
1121
    /**
1122
     * Receive the given string of data from the server.
1123
     *
1124
     * @return mixed a line of response on success or a PEAR_Error object on failure.
1125
     *
1126
     * @since   1.0
1127
     */
1128
    public function _recvLn()
1129
    {
1130
        if (PEAR::isError($lastline = $this->_socket->readLine(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

1130
        if (PEAR::/** @scrutinizer ignore-call */ isError($lastline = $this->_socket->readLine(8192))) {
Loading history...
1131
            return $this->_raiseError('Failed to write to socket: ' . $this->lastline->getMessage());
0 ignored issues
show
Bug Best Practice introduced by
The property lastline does not exist on Net_POP3. Did you maybe forget to declare it?
Loading history...
1132
        }
1133
        if ($this->_debug) {
1134
            // S: means this data was sent by  the POP3 Server
1135
            echo "S:$lastline\n";
1136
        }
1137
1138
        return $lastline;
1139
    }
1140
1141
    /**
1142
     * Checks de server Response
1143
     *
1144
     * @param string $response
1145
     * @return bool|\PEAR_Error true on success or a PEAR_Error object on failure.
1146
     *
1147
     * @since   1.3.3
1148
     */
1149
    public function _checkResponse(string $response)
1150
    {
1151
        if ('+OK' === @mb_substr(mb_strtoupper($response), 0, 3)) {
1152
            return true;
1153
        }
1154
        if ('-ERR' === @mb_substr(mb_strtoupper($response), 0, 4)) {
1155
            return $this->_raiseError($response);
1156
        }
1157
        if ('+ ' === @mb_substr(mb_strtoupper($response), 0, 2)) {
1158
            return true;
1159
        }
1160
1161
        return $this->_raiseError("Unknown Response ($response)");
1162
    }
1163
}
1164