POP::getEmails()   A
last analyzed

Complexity

Conditions 5
Paths 2

Size

Total Lines 25

Duplication

Lines 16
Ratio 64 %

Importance

Changes 0
Metric Value
cc 5
nc 2
nop 2
dl 16
loc 25
rs 9.2088
c 0
b 0
f 0
1
<?php
2
3
namespace Asymptix\mail;
4
5
/**
6
 * Class for working with POP (Post Office Protocol).
7
 *
8
 * @category Asymptix PHP Framework
9
 * @author Dmytro Zarezenko <[email protected]>
10
 * @copyright (c) 2010 - 2016, Dmytro Zarezenko
11
 *
12
 * @git https://github.com/Asymptix/Framework
13
 * @license http://opensource.org/licenses/MIT
14
 */
15
class POP {
16
17
    /**
18
     * Email regular expression.
19
     */
20
    const EMAIL_REGEX = '/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/i';
21
22
    /**
23
     * Instance of the connection to the POP server.
24
     *
25
     * @var resource
26
     */
27
    public $connection = null;
28
29
    /**
30
     * List of the acceptable for a bounce emails.
31
     *
32
     * @var array
33
     */
34
    public $bouncedWhiteList = [];
35
36
    /**
37
     * List of the acceptable for a bounce email domains.
38
     *
39
     * @var array
40
     */
41
    public $bouncedWhiteDomains = [];
42
43
    /**
44
     * Open connection to POP server.
45
     *
46
     * @param string $host POP server host (default: localhost).
47
     * @return int $port POP server port (default: 110)
48
     * @param string $username User name.
49
     * @param string $password User password.
50
     *
51
     * @return resource Connection to the POP server or throw POPServerException
52
     * @throws POPServerException
53
     */
54
    public function open($host = "localhost", $port = 110, $username = "", $password = "") {
55
        $sock = fsockopen($host, $port);
56
57
        if ($sock) {
58
            fgets($sock, 1024);
59
60
            // Connection accepted
61
            fwrite($sock, "USER " . $username . "\r\n");
62
            $userresponse = fgets($sock, 1024);
63
            if ($userresponse[0] == "+") { // User accepted
64
                fwrite($sock, "PASS " . $password . "\r\n");
65
                $passresponse = fgets($sock, 1024);
66
                if ($passresponse[0] != "+") { // Wrong password
67
                    $passresponse = str_replace("\r", "", str_replace("\n", "", $passresponse));
0 ignored issues
show
Unused Code introduced by
$passresponse is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
68
69
                    throw new POPServerException(
70
                        "Authentication not accepted. Incorrect password.", 1
71
                    );
72
                }
73
            } else { // Invalid username
74
                throw new POPServerException(
75
                    "Username '" . $username . "' not accepted.", 1
76
                );
77
            }
78
        } else {
79
            throw new POPServerException(
80
                "Unable to Connect to " . $host . ". Network Problems could be responsible.", 2
81
            );
82
        }
83
        $this->connection = $sock;
84
85
        return $sock;
86
    }
87
88
    /**
89
     * Close connection to the POP server.
90
     */
91
    public function close() {
92
        fwrite($this->connection, "QUIT\r\n");
93
        $quitresponse = fgets($this->connection, 1024);
0 ignored issues
show
Unused Code introduced by
$quitresponse is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
94
        $quitresponse = "";
0 ignored issues
show
Unused Code introduced by
$quitresponse is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
95
        fclose($this->connection);
96
    }
97
98
    /**
99
     * Delete message from POP server.
100
     *
101
     * @return int $messageId Id of the message.
102
     *
103
     * @return string Response string.
104
     */
105
    public function delete($messageId) {
106
        fwrite($this->connection, "DELE $messageId\r\n");
107
108
        return fgets($this->connection, 1024);
109
    }
110
111
    /**
112
     * Count messages in POP server.
113
     *
114
     * @return type
115
     */
116
    public function countMessages() {
117
        fwrite($this->connection, "STAT\r\n");
118
        $statresponse = fgets($this->connection, 1024);
119
        $avar = explode(" ", $statresponse);
120
121
        return (int)$avar[1];
122
    }
123
124
    /**
125
     * Return message header.
126
     *
127
     * @return int $messageNumber Number of the message.
128
     *
129
     * @return string
130
     */
131
    public function messageHeader($messageNumber) {
132
        fwrite($this->connection, "TOP $messageNumber 0\r\n");
133
        $buffer = "";
134
        $headerReceived = 0;
135
        while ($headerReceived == 0) {
136
            $temp = fgets($this->connection, 1024);
137
            $buffer .= $temp;
138
            if ($temp == ".\r\n") {
139
                $headerReceived = 1;
140
            }
141
        }
142
143
        return $buffer;
144
    }
145
146
    /**
147
     * Return parsed message header.
148
     *
149
     * @return int $messageNumber Number of the message.
150
     *
151
     * @return array
152
     */
153
    public function getParsedMessageHeader($messageNumber) {
154
        return $this->parseHeader(
155
            $this->messageHeader($messageNumber)
156
        );
157
    }
158
159
    /**
160
     * Return message by number.
161
     *
162
     * @return int $messageNumber Number of the message
163
     *
164
     * @return string
165
     */
166
    public function getMessage($messageNumber) {
167
        fwrite($this->connection, "RETR $messageNumber\r\n");
168
        $headerReceived = 0;
169
        $buffer = "";
170
        while ($headerReceived == 0) {
171
            $temp = fgets($this->connection, 1024);
172
            $buffer .= $temp;
173
            if (substr($buffer, 0, 4) === '-ERR') {
174
                return false;
175
            }
176
            if ($temp == ".\r\n") {
177
                $headerReceived = 1;
178
            }
179
        }
180
181
        return $buffer;
182
    }
183
184
    /**
185
     * Return list of the messages in the POP server mail box.
186
     *
187
     * @return array<string>
188
     */
189
    public function getMessages() {
190
        $messages = [];
191
192
        for ($i = 1; ; $i++) {
193
            $message = $this->getMessage($i);
194
            if ($message === false) {
195
                break;
196
            }
197
198
            $messages[] = $message;
199
        }
200
201
        return $messages;
202
    }
203
204
    /**
205
     * Return list of bounced e-mail addresses.
206
     *
207
     * @return array<string>
208
     */
209
    public function getBouncedEmails($delete = true, $number = null) {
210
        $emails = [];
211
212
        for ($i = 1; (is_null($number) ? true : $i <= $number); $i++) {
213
            $message = $this->getMessage($i);
214
            if ($message !== false) {
215
                $markers = [
216
                    'The following address(es) failed:',
217
                    'Delivery to the following recipients failed permanently:',
218
                    'Delivery to the following recipients failed.',
219
                    'Delivery to the following recipient has been delayed:',
220
                    'The following message to <',
221
                    'This is a permanent error; I\'ve given up. Sorry it didn\'t work out.',
222
                    'I\'m sorry to have to inform you that your message could not',
223
                    'This Message was undeliverable due to the following reason:',
224
                    'Delivery has failed to these recipients or groups:',
225
                    '------Transcript of session follows -------',
226
                    'Sorry, we were unable to deliver your message to the following address.',
227
                    'Your message cannot be delivered to the following recipients:',
228
                    'We have tried to deliver your message, but it was rejected by the recipient',
229
                    'These recipients of your message have been processed by the mail server:'
230
                ];
231
                $failSignaturePos = false;
232
                for ($q = 0; $failSignaturePos === false && $q < count($markers); $q++) {
233
                    $failSignaturePos = strpos($message, $markers[$q]);
234
                }
235
236
                $endSignaturePos = strpos($message, '--', $failSignaturePos); //End Position
237 View Code Duplication
                if ($failSignaturePos === false/* || $endSignaturePos === false*/) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
238
                    /*if ($delete) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
239
                        $this->delete($i);
240
                    }*/
241
                    continue;
242
                } else {
243
                    if ($endSignaturePos === false || $endSignaturePos <= $failSignaturePos) {
244
                        $endSignaturePos = strlen($message);
245
                    }
246
                    preg_match_all(
247
                        self::EMAIL_REGEX, substr($message, $failSignaturePos, $endSignaturePos - $failSignaturePos), $emailData
248
                    );
249
250
                    $emails = $this->filterBouncedEmails($emailData);
0 ignored issues
show
Bug introduced by
It seems like $emailData can also be of type null; however, Asymptix\mail\POP::filterBouncedEmails() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251
252
                    if ($delete) {
253
                        $this->delete($i);
254
                    }
255
                }
256
            } else {
257
                break;
258
            }
259
        }
260
261
        return array_unique($emails);
262
    }
263
264
    /**
265
     * Return list of e-mail addresses except white lists.
266
     *
267
     * @return array<string>
268
     */
269
    public function getEmails($delete = true, $number = null) {
270
        $emails = [];
271
272
        for ($i = 1; (is_null($number) ? true : $i <= $number); $i++) {
273
            $message = $this->getMessage($i);
274 View Code Duplication
            if ($message !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
                $failSignaturePos = 0;
276
                $endSignaturePos = strlen($message);
277
278
                preg_match_all(
279
                    self::EMAIL_REGEX, substr($message, $failSignaturePos, $endSignaturePos - $failSignaturePos), $emailData
280
                );
281
282
                $emails = $this->filterBouncedEmails($emailData);
0 ignored issues
show
Bug introduced by
It seems like $emailData can also be of type null; however, Asymptix\mail\POP::filterBouncedEmails() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
283
284
                if ($delete) {
285
                    $this->delete($i);
286
                }
287
            } else {
288
                break;
289
            }
290
        }
291
292
        return array_unique($emails);
293
    }
294
295
    /**
296
     * Returns only bounced e-mails from matches.
297
     *
298
     * @param array $emailData Matches array after preg_math_all funciton.
299
     *
300
     * @return array<string> List of bounced e-mails.
301
     */
302
    private function filterBouncedEmails($emailData) {
303
        $emails = [];
304
305
        if (isset($emailData[0])) {
306
            if (is_array($emailData[0])) {
307
                foreach ($emailData[0] as $email) {
308
                    if ($this->isBouncedEmail($email)) {
309
                        $emails[] = $email;
310
                    }
311
                }
312
            } else {
313
                $email = $emailData[0];
314
                if ($this->isBouncedEmail($email)) {
315
                    $emails[] = $email;
316
                }
317
            }
318
        }
319
320
        return $emails;
321
    }
322
323
    /**
324
     * Detects if email is bounced.
325
     *
326
     * @param string $email Email address.
327
     *
328
     * @return bool
329
     */
330
    private function isBouncedEmail($email) {
331
        $email = trim($email);
332
        if (!empty($email)) {
333
            if (in_array($email, $this->bouncedWhiteList)) {
334
                return false;
335
            }
336
            $domain = substr(strrchr($email, "@"), 1);
337
            if (in_array($domain, $this->bouncedWhiteDomains)) {
338
                return false;
339
            }
340
341
            return true;
342
        }
343
344
        return false;
345
    }
346
347
    /**
348
     * Parse message header.
349
     *
350
     * @param string $header Header of the message.
351
     *
352
     * @return array
353
     */
354
    public function parseHeader($header) {
355
        $avar = explode("\n", $header);
356
        $len = count($avar);
357
        $ret = $L2 = $L3 = null;
0 ignored issues
show
Unused Code introduced by
$L3 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Unused Code introduced by
$L2 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
358
        for ($i = 0; $i < $len; $i++) {
359
            if (isset($avar[$i]) && isset($avar[$i][0]) && isset($avar[$i][1]) && isset($avar[$i][2])) {
360
                $L2 = $avar[$i][0] . $avar[$i][1];
361
                $L3 = $avar[$i][0] . $avar[$i][1] . $avar[$i][2];
362
                if ($L2 != "  " && $L3 != "Rec" && $L2 != "") {
363
                    $avar2 = explode(":", $avar[$i]);
364
                    $temp = str_replace("$avar2[0]:", "", $avar[$i]);
365
                    $ret[$avar2[0]] = $temp;
366
                }
367
            }
368
        }
369
370
        return $ret;
371
    }
372
373
    /**
374
     * Get a unique-id listing for one or all messages.
375
     *
376
     * @return array
377
     */
378
    public function uniqueListing() {
379
        fwrite($this->connection, "UIDL\r\n");
380
        $response = "";
381
382
        while (true) {
383
            $response .= fgets($this->connection, 1024);
384
            if (substr($response, -3) === ".\r\n") {
385
                $response = substr($response, 0, strlen($response) - 3); //the ".\r\n" removed
386
                break;
387
            }
388
        }
389
        $em = explode("\r\n", $response);
390
        $em2 = [];
391
        foreach ($em as $q) {
392
            if (strpos($q, ' ') !== false) {
393
                $t = explode(' ', $q);
394
                $em2[] = $t[1];
395
            }
396
        }
397
398
        return $em2;
399
    }
400
}
401
402
class POPServerException extends \Exception {}
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
403