1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace TelegramBot\Api\Http; |
4
|
|
|
|
5
|
|
|
use TelegramBot\Api\HttpException; |
6
|
|
|
use TelegramBot\Api\InvalidJsonException; |
7
|
|
|
|
8
|
|
|
class CurlHttpClient extends AbstractHttpClient |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* HTTP codes |
12
|
|
|
* |
13
|
|
|
* @var array |
14
|
|
|
*/ |
15
|
|
|
private static $codes = [ |
16
|
|
|
// Informational 1xx |
17
|
|
|
100 => 'Continue', |
18
|
|
|
101 => 'Switching Protocols', |
19
|
|
|
102 => 'Processing', // RFC2518 |
20
|
|
|
// Success 2xx |
21
|
|
|
200 => 'OK', |
22
|
|
|
201 => 'Created', |
23
|
|
|
202 => 'Accepted', |
24
|
|
|
203 => 'Non-Authoritative Information', |
25
|
|
|
204 => 'No Content', |
26
|
|
|
205 => 'Reset Content', |
27
|
|
|
206 => 'Partial Content', |
28
|
|
|
207 => 'Multi-Status', // RFC4918 |
29
|
|
|
208 => 'Already Reported', // RFC5842 |
30
|
|
|
226 => 'IM Used', // RFC3229 |
31
|
|
|
// Redirection 3xx |
32
|
|
|
300 => 'Multiple Choices', |
33
|
|
|
301 => 'Moved Permanently', |
34
|
|
|
302 => 'Found', // 1.1 |
35
|
|
|
303 => 'See Other', |
36
|
|
|
304 => 'Not Modified', |
37
|
|
|
305 => 'Use Proxy', |
38
|
|
|
// 306 is deprecated but reserved |
39
|
|
|
307 => 'Temporary Redirect', |
40
|
|
|
308 => 'Permanent Redirect', // RFC7238 |
41
|
|
|
// Client Error 4xx |
42
|
|
|
400 => 'Bad Request', |
43
|
|
|
401 => 'Unauthorized', |
44
|
|
|
402 => 'Payment Required', |
45
|
|
|
403 => 'Forbidden', |
46
|
|
|
404 => 'Not Found', |
47
|
|
|
405 => 'Method Not Allowed', |
48
|
|
|
406 => 'Not Acceptable', |
49
|
|
|
407 => 'Proxy Authentication Required', |
50
|
|
|
408 => 'Request Timeout', |
51
|
|
|
409 => 'Conflict', |
52
|
|
|
410 => 'Gone', |
53
|
|
|
411 => 'Length Required', |
54
|
|
|
412 => 'Precondition Failed', |
55
|
|
|
413 => 'Payload Too Large', |
56
|
|
|
414 => 'URI Too Long', |
57
|
|
|
415 => 'Unsupported Media Type', |
58
|
|
|
416 => 'Range Not Satisfiable', |
59
|
|
|
417 => 'Expectation Failed', |
60
|
|
|
422 => 'Unprocessable Entity', // RFC4918 |
61
|
|
|
423 => 'Locked', // RFC4918 |
62
|
|
|
424 => 'Failed Dependency', // RFC4918 |
63
|
|
|
425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 |
64
|
|
|
426 => 'Upgrade Required', // RFC2817 |
65
|
|
|
428 => 'Precondition Required', // RFC6585 |
66
|
|
|
429 => 'Too Many Requests', // RFC6585 |
67
|
|
|
431 => 'Request Header Fields Too Large', // RFC6585 |
68
|
|
|
// Server Error 5xx |
69
|
|
|
500 => 'Internal Server Error', |
70
|
|
|
501 => 'Not Implemented', |
71
|
|
|
502 => 'Bad Gateway', |
72
|
|
|
503 => 'Service Unavailable', |
73
|
|
|
504 => 'Gateway Timeout', |
74
|
|
|
505 => 'HTTP Version Not Supported', |
75
|
|
|
506 => 'Variant Also Negotiates (Experimental)', // RFC2295 |
76
|
|
|
507 => 'Insufficient Storage', // RFC4918 |
77
|
|
|
508 => 'Loop Detected', // RFC5842 |
78
|
|
|
510 => 'Not Extended', // RFC2774 |
79
|
|
|
511 => 'Network Authentication Required', // RFC6585 |
80
|
|
|
]; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Default http status code |
84
|
|
|
*/ |
85
|
|
|
const DEFAULT_STATUS_CODE = 200; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Not Modified http status code |
89
|
|
|
*/ |
90
|
|
|
const NOT_MODIFIED_STATUS_CODE = 304; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* CURL object |
94
|
|
|
* |
95
|
|
|
* @var resource |
96
|
|
|
*/ |
97
|
|
|
private $curl; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @var array |
101
|
|
|
*/ |
102
|
|
|
private $options; |
103
|
|
|
|
104
|
|
|
public function __construct(array $options = []) |
105
|
|
|
{ |
106
|
|
|
$this->curl = curl_init(); |
|
|
|
|
107
|
|
|
$this->options = $options; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @inheritDoc |
112
|
|
|
*/ |
113
|
|
|
protected function doRequest($url, array $data = null) |
114
|
|
|
{ |
115
|
|
|
$options = $this->options + [ |
116
|
|
|
CURLOPT_URL => $url, |
117
|
|
|
CURLOPT_RETURNTRANSFER => true, |
118
|
|
|
]; |
119
|
|
|
|
120
|
|
|
if ($data) { |
121
|
|
|
$options[CURLOPT_POST] = true; |
122
|
|
|
$options[CURLOPT_POSTFIELDS] = $data; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return self::jsonValidate($this->execute($options)); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @inheritDoc |
130
|
|
|
*/ |
131
|
|
|
protected function doDownload($url) |
132
|
|
|
{ |
133
|
|
|
$options = [ |
134
|
|
|
CURLOPT_HEADER => false, |
135
|
|
|
CURLOPT_HTTPGET => true, |
136
|
|
|
CURLOPT_RETURNTRANSFER => true, |
137
|
|
|
CURLOPT_URL => $url, |
138
|
|
|
]; |
139
|
|
|
|
140
|
|
|
return $this->execute($options); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @param array $options |
145
|
|
|
* @return string |
146
|
|
|
* @throws HttpException |
147
|
|
|
*/ |
148
|
|
|
private function execute(array $options) |
149
|
|
|
{ |
150
|
|
|
curl_setopt_array($this->curl, $options); |
151
|
|
|
|
152
|
|
|
/** @var string|false $result */ |
153
|
|
|
$result = curl_exec($this->curl); |
154
|
|
|
if ($result === false) { |
|
|
|
|
155
|
|
|
throw new HttpException(curl_error($this->curl), curl_errno($this->curl)); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
self::curlValidate($this->curl, $result); |
159
|
|
|
|
160
|
|
|
return $result; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @param string $jsonString |
165
|
|
|
* @return array |
166
|
|
|
* @throws InvalidJsonException |
167
|
|
|
*/ |
168
|
|
|
private static function jsonValidate($jsonString) |
169
|
|
|
{ |
170
|
|
|
/** @var array $json */ |
171
|
|
|
$json = json_decode($jsonString, true); |
172
|
|
|
|
173
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) { |
174
|
|
|
throw new InvalidJsonException(json_last_error_msg(), json_last_error()); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $json; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param resource $curl |
182
|
|
|
* @param string|null $response |
183
|
|
|
* @return void |
184
|
|
|
* @throws HttpException |
185
|
|
|
*/ |
186
|
|
|
private static function curlValidate($curl, $response = null) |
187
|
|
|
{ |
188
|
|
|
$json = json_decode((string) $response, true) ?: []; |
189
|
|
|
|
190
|
|
|
if (($httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE)) |
191
|
|
|
&& !in_array($httpCode, [self::DEFAULT_STATUS_CODE, self::NOT_MODIFIED_STATUS_CODE]) |
192
|
|
|
) { |
193
|
|
|
$errorDescription = array_key_exists('description', $json) ? $json['description'] : self::$codes[$httpCode]; |
194
|
|
|
$errorParameters = array_key_exists('parameters', $json) ? $json['parameters'] : []; |
195
|
|
|
|
196
|
|
|
throw new HttpException($errorDescription, $httpCode, null, $errorParameters); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Enable proxy for curl requests. Empty string will disable proxy. |
202
|
|
|
* |
203
|
|
|
* @param string $proxyString |
204
|
|
|
* @param bool $socks5 |
205
|
|
|
* @return void |
206
|
|
|
*/ |
207
|
|
|
public function setProxy($proxyString = '', $socks5 = false) |
208
|
|
|
{ |
209
|
|
|
if (empty($proxyString)) { |
210
|
|
|
unset($this->options[CURLOPT_PROXY], $this->options[CURLOPT_HTTPPROXYTUNNEL], $this->options[CURLOPT_PROXYTYPE]); |
211
|
|
|
|
212
|
|
|
return; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$this->options[CURLOPT_PROXY] = $proxyString; |
216
|
|
|
$this->options[CURLOPT_HTTPPROXYTUNNEL] = true; |
217
|
|
|
|
218
|
|
|
if ($socks5) { |
219
|
|
|
$this->options[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* @param string $option |
225
|
|
|
* @param string|int|bool $value |
226
|
|
|
* @return void |
227
|
|
|
*/ |
228
|
|
|
public function setOption($option, $value) |
229
|
|
|
{ |
230
|
|
|
$this->options[$option] = $value; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* @param string $option |
235
|
|
|
* @return void |
236
|
|
|
*/ |
237
|
|
|
public function unsetOption($option) |
238
|
|
|
{ |
239
|
|
|
unset($this->options[$option]); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @return void |
244
|
|
|
*/ |
245
|
|
|
public function resetOptions() |
246
|
|
|
{ |
247
|
|
|
$this->options = []; |
248
|
|
|
} |
249
|
|
|
} |
250
|
|
|
|
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 theid
property of an instance of theAccount
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.