Client   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 329
rs 9.6
c 0
b 0
f 0
wmc 35

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getMessageId() 0 7 1
A deleteMessagesWithSubject() 0 9 3
A quit() 0 3 1
A getMessageHeaders() 0 5 1
A getCount() 0 13 2
A __construct() 0 9 2
A getConnection() 0 7 2
A deleteMessage() 0 5 1
A connect() 0 24 4
A getMessageSubject() 0 22 6
A parseAnyType() 0 20 3
A getData() 0 19 4
A messageWithSubjectExists() 0 13 3
A initConnection() 0 24 2
1
<?php
2
namespace Mezon\Pop3;
3
4
/**
5
 * Class Client
6
 *
7
 * @package Mezon
8
 * @subpackage Pop3Client
9
 * @author Dodonov A.A.
10
 * @version v.1.0 (2019/08/13)
11
 * @copyright Copyright (c) 2019, aeon.org
12
 */
13
14
/**
15
 * POP3 protocol client.
16
 */
17
class Client
18
{
19
20
    /**
21
     * Connection
22
     *
23
     * @var ?resource
24
     */
25
    private $connection = null;
26
27
    /**
28
     * Method returns connection
29
     *
30
     * @return resource connection to server
31
     */
32
    private function getConnection()
33
    {
34
        if ($this->connection === null) {
35
            throw (new \Exception('Connection was not establshed', - 1));
36
        }
37
38
        return $this->connection;
39
    }
40
41
    /**
42
     * Method returns connection
43
     *
44
     * @param string $server
45
     *            server domain
46
     * @param int $timeOut
47
     *            timeout
48
     * @param int $port
49
     *            port number
50
     * @return resource connection
51
     */
52
    protected function initConnection(string $server, int $timeOut = 5, int $port = 110)
53
    {
54
        $errorMessage = '';
55
        $errorCode = 0;
56
57
        $context = stream_context_create([
58
            'ssl' => [
59
                'verify_peer' => false
60
            ]
61
        ]);
62
63
        $connection = stream_socket_client(
64
            $server . ":$port",
65
            $errorCode,
66
            $errorMessage,
67
            $timeOut,
68
            STREAM_CLIENT_CONNECT,
69
            $context);
70
71
        if ($connection === false) {
72
            throw (new \Exception('Connection was not established', - 1));
73
        }
74
75
        return $connection;
76
    }
77
78
    /**
79
     * Method connects to server
80
     *
81
     * @param string $server
82
     *            Server domain
83
     * @param string $login
84
     *            Login
85
     * @param string $password
86
     *            Password
87
     * @param int $timeOut
88
     *            Timeout
89
     * @param int $port
90
     *            Port number
91
     */
92
    public function connect(string $server, string $login, string $password, int $timeOut = 5, int $port = 110): void
93
    {
94
        $this->connection = $this->initConnection($server, $timeOut, $port);
95
96
        $result = fgets($this->getConnection(), 1024);
97
98
        if (substr($result, 0, 3) !== '+OK') {
99
            throw (new \Exception('Connection. ' . $result, 0));
100
        }
101
102
        fputs($this->getConnection(), "USER $login\r\n");
103
104
        $result = fgets($this->getConnection(), 1024);
105
106
        if (substr($result, 0, 3) !== '+OK') {
107
            throw (new \Exception("USER $login " . $result, 0));
108
        }
109
110
        fputs($this->getConnection(), "PASS $password\r\n");
111
112
        $result = fgets($this->getConnection(), 1024);
113
114
        if (substr($result, 0, 3) !== '+OK') {
115
            throw (new \Exception("PASS " . $result . $login, 0));
116
        }
117
    }
118
119
    /**
120
     * Constructor
121
     *
122
     * @param string $server
123
     *            Server domain
124
     * @param string $login
125
     *            Login
126
     * @param string $password
127
     *            Password
128
     * @param int $timeOut
129
     *            Timeout
130
     * @param int $port
131
     *            Port number
132
     */
133
    public function __construct(
134
        string $server = '',
135
        string $login = '',
136
        string $password = '',
137
        int $timeOut = 5,
138
        int $port = 110)
139
    {
140
        if ($server !== '') {
141
            $this->connect($server, $login, $password, $timeOut, $port);
142
        }
143
    }
144
145
    /**
146
     * Method returns emails count.
147
     */
148
    public function getCount(): int
149
    {
150
        fputs($this->getConnection(), "STAT\r\n");
151
152
        $result = fgets($this->getConnection(), 1024);
153
154
        if (substr($result, 0, 3) !== '+OK') {
155
            throw (new \Exception("STAT " . $result, 0));
156
        }
157
158
        $result = explode(' ', $result);
159
160
        return intval($result[1]);
161
    }
162
163
    /**
164
     * Method returns data from connection
165
     *
166
     * @return string Fetched data
167
     */
168
    protected function getData(): string
169
    {
170
        $data = '';
171
172
        while (! feof($this->getConnection())) {
173
            $buffer = chop(fgets($this->getConnection(), 1024));
174
175
            if (strpos($buffer, '-ERR') === 0) {
176
                throw (new \Exception(str_replace('-ERR ', '', $buffer), 0));
177
            }
178
179
            $data .= "$buffer\r\n";
180
181
            if (trim($buffer) == '.') {
182
                break;
183
            }
184
        }
185
186
        return $data;
187
    }
188
189
    /**
190
     * Method returns email's headers
191
     *
192
     * @param int $i
193
     *            Number of the message. Note that numbering is starting from 0
194
     * @return string Headers
195
     */
196
    public function getMessageHeaders(int $i): string
197
    {
198
        fputs($this->getConnection(), "TOP $i 3\r\n");
199
200
        return $this->getData();
201
    }
202
203
    /**
204
     * Method deletes email
205
     *
206
     * @param int $i
207
     *            Number of the message
208
     * @return string Result of the deletion
209
     */
210
    public function deleteMessage($i): string
211
    {
212
        fputs($this->getConnection(), "DELE $i\r\n");
213
214
        return fgets($this->getConnection());
215
    }
216
217
    /**
218
     * Method terminates session
219
     */
220
    public function quit(): void
221
    {
222
        fputs($this->getConnection(), "QUIT\r\n");
223
    }
224
225
    /**
226
     * Method parses subject with any prefix
227
     *
228
     * @param string $line
229
     *            Line of the email
230
     * @param int $i
231
     *            Line cursor
232
     * @param string[] $headers
233
     *            Email headers
234
     * @param string $type
235
     *            Mime type
236
     * @return string Decoded data
237
     */
238
    protected function parseAnyType(string $line, int $i, array $headers, string $type): string
239
    {
240
        $subject = substr($line, 0, strlen($line) - 2);
241
242
        $count = count($headers);
243
        for ($j = $i + 1; $j < $count; $j ++) {
244
            if (substr($headers[$j], 0, 1) == ' ') {
245
                $subject .= str_ireplace([
246
                    ' ' . $type,
247
                    '?='
248
                ], [
249
                    '',
250
                    ''
251
                ], $headers[$j]);
252
            } else {
253
                return str_replace('Subject: ', '', iconv_mime_decode($subject . "?=\r\n", 0, "UTF-8"));
254
            }
255
        }
256
257
        return '';
258
    }
259
260
    /**
261
     * Method returns message's subject
262
     *
263
     * @param int $i
264
     *            Line number
265
     * @return string Decoded data
266
     */
267
    public function getMessageSubject(int $i): string
268
    {
269
        $headers = $this->getMessageHeaders($i);
270
271
        $headers = explode("\r\n", $headers);
272
273
        foreach ($headers as $i => $line) {
274
            if (strpos($line, 'Subject: ') === 0) {
275
                if (stripos($line, '=?UTF-8?Q?') !== false) {
276
                    return $this->parseAnyType($line, $i, $headers, '=?UTF-8?Q?');
277
                } elseif (stripos($line, '=?UTF-8?B?') !== false) {
278
                    return $this->parseAnyType($line, $i, $headers, '=?UTF-8?B?');
279
                } elseif (strpos($line, '=?') === false) {
280
                    // subject is not encoded
281
                    return $line;
282
                } else {
283
                    throw (new \Exception('Subject encoding is not supported yet : ' . $line, - 1));
284
                }
285
            }
286
        }
287
288
        return '';
289
    }
290
291
    /**
292
     * Method returns true if the mail with the specified subject exists
293
     *
294
     * @param string $subject
295
     *            Searching subject
296
     * @return bool Email exists
297
     */
298
    public function messageWithSubjectExists(string $subject): bool
299
    {
300
        $count = $this->getCount();
301
302
        for ($i = 1; $i <= $count; $i ++) {
303
            $mailSubject = $this->getMessageSubject($i);
304
305
            if ($subject == $mailSubject) {
306
                return true;
307
            }
308
        }
309
310
        return false;
311
    }
312
313
    /**
314
     * Method removes all the mails with the specified subject
315
     *
316
     * @param string $subject
317
     *            subject of emails to be deleted
318
     */
319
    public function deleteMessagesWithSubject(string $subject): void
320
    {
321
        $count = $this->getCount();
322
323
        for ($i = 1; $i <= $count; $i ++) {
324
            $mailSubject = $this->getMessageSubject($i);
325
326
            if ($subject == $mailSubject) {
327
                $this->deleteMessage($i);
328
            }
329
        }
330
    }
331
332
    /**
333
     * Method returns Message-ID
334
     *
335
     * @param string $headers
336
     *            email headers
337
     * @return string Message-ID
338
     */
339
    public static function getMessageId(string $headers): string
340
    {
341
        $matches = [];
342
343
        preg_match('/Message-ID: <([0-9a-zA-Z\.@\-]*)>/mi', $headers, $matches);
344
345
        return $matches[1];
346
    }
347
}
348