Completed
Push — master ( 014d21...1b6ebf )
by Alex
02:06
created

Client.php (1 issue)

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 connects to server
29
     *
30
     * @param string $server
31
     *            Server domain
32
     * @param string $login
33
     *            Login
34
     * @param string $password
35
     *            Password
36
     * @param int $timeOut
37
     *            Timeout
38
     * @param int $port
39
     *            Port number
40
     */
41
    public function connect(string $server, string $login, string $password, int $timeOut = 5, int $port = 110)
42
    {
43
        try {
44
            $errorMessage = '';
45
            $errorCode = 0;
46
47
            $context = stream_context_create([
48
                'ssl' => [
49
                    'verify_peer' => false
50
                ]
51
            ]);
52
53
            $this->connection = stream_socket_client(
0 ignored issues
show
Documentation Bug introduced by
It seems like stream_socket_client($se...IENT_CONNECT, $context) can also be of type false. However, the property $connection is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
54
                $server . ":$port",
55
                $errorCode,
56
                $errorMessage,
57
                $timeOut,
58
                STREAM_CLIENT_CONNECT,
59
                $context);
60
61
            if ($this->connection === false) {
62
                throw (new \Exception('Connection was not established', - 1));
63
            }
64
65
            $result = fgets($this->connection, 1024);
66
67
            if (substr($result, 0, 3) !== '+OK') {
68
                throw (new \Exception('Connection. ' . $result, 0));
69
            }
70
71
            fputs($this->connection, "USER $login\r\n");
72
73
            $result = fgets($this->connection, 1024);
74
75
            if (substr($result, 0, 3) !== '+OK') {
76
                throw (new \Exception("USER $login " . $result, 0));
77
            }
78
79
            fputs($this->connection, "PASS $password\r\n");
80
81
            $result = fgets($this->connection, 1024);
82
83
            if (substr($result, 0, 3) !== '+OK') {
84
                throw (new \Exception("PASS " . $result . $login, 0));
85
            }
86
        } catch (\Exception $e) {
87
            throw ($e);
88
        }
89
    }
90
91
    /**
92
     * Constructor
93
     *
94
     * @param string $server
95
     *            Server domain
96
     * @param string $login
97
     *            Login
98
     * @param string $password
99
     *            Password
100
     * @param int $timeOut
101
     *            Timeout
102
     * @param int $port
103
     *            Port number
104
     */
105
    public function __construct(string $server, string $login, string $password, int $timeOut = 5, int $port = 110)
106
    {
107
        $this->connect($server, $login, $password, $timeOut, $port);
108
    }
109
110
    /**
111
     * Method returns emails count.
112
     */
113
    public function getCount(): int
114
    {
115
        fputs($this->connection, "STAT\r\n");
116
117
        $result = fgets($this->connection, 1024);
118
119
        if (substr($result, 0, 3) !== '+OK') {
120
            throw (new \Exception("STAT " . $result, 0));
121
        }
122
123
        $result = explode(' ', $result);
124
125
        return intval($result[1]);
126
    }
127
128
    /**
129
     * Method returns data from connection
130
     *
131
     * @return string Fetched data
132
     */
133
    protected function getData(): string
134
    {
135
        $data = '';
136
137
        while (! feof($this->connection)) {
138
            $buffer = chop(fgets($this->connection, 1024));
139
140
            if (strpos($buffer, '-ERR') === 0) {
141
                throw (new \Exception(str_replace('-ERR ', '', $buffer), 0));
142
            }
143
144
            $data .= "$buffer\r\n";
145
146
            if (trim($buffer) == '.') {
147
                break;
148
            }
149
        }
150
151
        return $data;
152
    }
153
154
    /**
155
     * Method returns email's headers
156
     *
157
     * @param int $i
158
     *            Number of the message
159
     * @return string Headers
160
     */
161
    public function getMessageHeaders(int $i): string
162
    {
163
        fputs($this->connection, "TOP $i 3\r\n");
164
165
        return $this->getData();
166
    }
167
168
    /**
169
     * Method deletes email
170
     *
171
     * @param int $i
172
     *            Number of the message
173
     * @return string Result of the deletion
174
     */
175
    public function deleteMessage($i): string
176
    {
177
        fputs($this->connection, "DELE $i\r\n");
178
179
        return fgets($this->connection);
180
    }
181
182
    /**
183
     * Method terminates session
184
     */
185
    public function quit()
186
    {
187
        fputs($this->connection, "QUIT\r\n");
188
    }
189
190
    /**
191
     * Method parses subject with any prefix
192
     *
193
     * @param string $line
194
     *            Line of the email
195
     * @param int $i
196
     *            Line cursor
197
     * @param array $headers
198
     *            Email headers
199
     * @param string $type
200
     *            Mime type
201
     * @return string Decoded data
202
     */
203
    protected function parseAnyType(string $line, int $i, array $headers, string $type): string
204
    {
205
        $subject = substr($line, 0, strlen($line) - 2);
206
207
        $count = count($headers);
208
        for ($j = $i + 1; $j < $count; $j ++) {
209
            if (substr($headers[$j], 0, 1) == ' ') {
210
                $subject .= str_replace([
211
                    ' ' . $type,
212
                    '?='
213
                ], [
214
                    '',
215
                    ''
216
                ], $headers[$j]);
217
            } else {
218
                return str_replace('Subject: ', '', iconv_mime_decode($subject . "?=\r\n", 0, "UTF-8"));
219
            }
220
        }
221
222
        return '';
223
    }
224
225
    /**
226
     * Method returns message's subject
227
     *
228
     * @param int $i
229
     *            Line number
230
     * @return string Decoded data
231
     */
232
    public function getMessageSubject(int $i): string
233
    {
234
        $headers = $this->getMessageHeaders($i);
235
236
        $headers = explode("\r\n", $headers);
237
238
        foreach ($headers as $i => $line) {
239
            if (strpos($line, 'Subject: ') === 0) {
240
                if (strpos($line, '=?UTF-8?Q?') !== false) {
241
                    return $this->parseAnyType($line, $i, $headers, '=?UTF-8?Q?');
242
                } elseif (strpos($line, '=?UTF-8?B?') !== false) {
243
                    return $this->parseAnyType($line, $i, $headers, '=?UTF-8?B?');
244
                }
245
            }
246
        }
247
248
        return '';
249
    }
250
251
    /**
252
     * Method returns true if the mail with the specified subject exists
253
     *
254
     * @param string $subject
255
     *            Searching subject
256
     * @return bool Email exists
257
     */
258
    public function messageWithSubjectExists(string $subject): bool
259
    {
260
        $count = $this->getCount();
261
262
        for ($i = 1; $i <= $count; $i ++) {
263
            $mailSubject = $this->getMessageSubject($i);
264
265
            if ($subject == $mailSubject) {
266
                return true;
267
            }
268
        }
269
270
        return false;
271
    }
272
273
    /**
274
     * Method removes all the mails with the specified subject
275
     *
276
     * @param string $subject
277
     *            subject of emails to be deleted
278
     */
279
    public function deleteMessagesWithSubject(string $subject)
280
    {
281
        $count = $this->getCount();
282
283
        for ($i = 1; $i <= $count; $i ++) {
284
            $mailSubject = $this->getMessageSubject($i);
285
286
            if ($subject == $mailSubject) {
287
                $this->deleteMessage($i);
288
            }
289
        }
290
    }
291
292
    /**
293
     * Method returns Message-ID
294
     *
295
     * @param string $headers
296
     *            email headers
297
     * @return string Message-ID
298
     */
299
    public static function getMessageId(string $headers): string
300
    {
301
        $matches = [];
302
303
        preg_match('/Message-ID: <([0-9a-zA-Z\.@\-]*)>/mi', $headers, $matches);
304
305
        return $matches[1];
306
    }
307
}
308