|
1
|
|
|
<?php |
|
2
|
|
|
namespace Rmccue\Requests\Transport; |
|
3
|
|
|
|
|
4
|
|
|
use Rmccue\Requests as Requests; |
|
5
|
|
|
use Rmccue\Requests\Exception as Exception; |
|
6
|
|
|
use Rmccue\Requests\SSL as SSL; |
|
7
|
|
|
use Rmccue\Requests\Transport as Transport; |
|
8
|
|
|
use Rmccue\Requests\Utility\CaseInsensitiveDictionary as CaseInsensitiveDictionary; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* fsockopen HTTP transport |
|
12
|
|
|
* |
|
13
|
|
|
* @package Rmccue\Requests |
|
14
|
|
|
* @subpackage Transport |
|
15
|
|
|
*/ |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* fsockopen HTTP transport |
|
19
|
|
|
* |
|
20
|
|
|
* @package Rmccue\Requests |
|
21
|
|
|
* @subpackage Transport |
|
22
|
|
|
*/ |
|
23
|
|
|
class fsockopen implements Transport { |
|
24
|
|
|
/** |
|
25
|
|
|
* Second to microsecond conversion |
|
26
|
|
|
* |
|
27
|
|
|
* @var integer |
|
28
|
|
|
*/ |
|
29
|
|
|
const SECOND_IN_MICROSECONDS = 1000000; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* Raw HTTP data |
|
33
|
|
|
* |
|
34
|
|
|
* @var string |
|
35
|
|
|
*/ |
|
36
|
|
|
public $headers = ''; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Stream metadata |
|
40
|
|
|
* |
|
41
|
|
|
* @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data} |
|
42
|
|
|
*/ |
|
43
|
|
|
public $info; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* What's the maximum number of bytes we should keep? |
|
47
|
|
|
* |
|
48
|
|
|
* @var int|bool Byte count, or false if no limit. |
|
49
|
|
|
*/ |
|
50
|
|
|
protected $max_bytes = false; |
|
51
|
|
|
|
|
52
|
|
|
protected $connect_error = ''; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* Perform a request |
|
56
|
|
|
* |
|
57
|
|
|
* @throws Rmccue\Requests\Exception On failure to connect to socket (`fsockopenerror`) |
|
58
|
|
|
* @throws Rmccue\Requests\Exception On socket timeout (`timeout`) |
|
59
|
|
|
* |
|
60
|
|
|
* @param string $url URL to request |
|
61
|
|
|
* @param array $headers Associative array of request headers |
|
62
|
|
|
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD |
|
63
|
|
|
* @param array $options Request options, see {@see Rmccue\Requests::response()} for documentation |
|
64
|
|
|
* @return string Raw HTTP result |
|
65
|
|
|
*/ |
|
66
|
|
|
public function request($url, $headers = array(), $data = array(), $options = array()) { |
|
67
|
|
|
$options['hooks']->dispatch('fsockopen.before_request'); |
|
68
|
|
|
|
|
69
|
|
|
$url_parts = parse_url($url); |
|
70
|
|
|
if (empty($url_parts)) { |
|
71
|
|
|
throw new Exception('Invalid URL.', 'invalidurl', $url); |
|
72
|
|
|
} |
|
73
|
|
|
$host = $url_parts['host']; |
|
74
|
|
|
$context = stream_context_create(); |
|
75
|
|
|
$verifyname = false; |
|
76
|
|
|
$case_insensitive_headers = new CaseInsensitiveDictionary($headers); |
|
77
|
|
|
|
|
78
|
|
|
// HTTPS support |
|
79
|
|
|
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { |
|
80
|
|
|
$remote_socket = 'ssl://' . $host; |
|
81
|
|
|
if (!isset($url_parts['port'])) { |
|
82
|
|
|
$url_parts['port'] = 443; |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
$context_options = array( |
|
86
|
|
|
'verify_peer' => true, |
|
87
|
|
|
// 'CN_match' => $host, |
|
|
|
|
|
|
88
|
|
|
'capture_peer_cert' => true |
|
89
|
|
|
); |
|
90
|
|
|
$verifyname = true; |
|
91
|
|
|
|
|
92
|
|
|
// SNI, if enabled (OpenSSL >=0.9.8j) |
|
|
|
|
|
|
93
|
|
|
if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { |
|
94
|
|
|
$context_options['SNI_enabled'] = true; |
|
95
|
|
View Code Duplication |
if (isset($options['verifyname']) && $options['verifyname'] === false) { |
|
|
|
|
|
|
96
|
|
|
$context_options['SNI_enabled'] = false; |
|
97
|
|
|
} |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
if (isset($options['verify'])) { |
|
101
|
|
|
if ($options['verify'] === false) { |
|
102
|
|
|
$context_options['verify_peer'] = false; |
|
103
|
|
|
} |
|
104
|
|
|
elseif (is_string($options['verify'])) { |
|
105
|
|
|
$context_options['cafile'] = $options['verify']; |
|
106
|
|
|
} |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
View Code Duplication |
if (isset($options['verifyname']) && $options['verifyname'] === false) { |
|
|
|
|
|
|
110
|
|
|
$context_options['verify_peer_name'] = false; |
|
111
|
|
|
$verifyname = false; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
stream_context_set_option($context, array('ssl' => $context_options)); |
|
115
|
|
|
} |
|
116
|
|
|
else { |
|
117
|
|
|
$remote_socket = 'tcp://' . $host; |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
$this->max_bytes = $options['max_bytes']; |
|
121
|
|
|
|
|
122
|
|
|
if (!isset($url_parts['port'])) { |
|
123
|
|
|
$url_parts['port'] = 80; |
|
124
|
|
|
} |
|
125
|
|
|
$remote_socket .= ':' . $url_parts['port']; |
|
126
|
|
|
|
|
127
|
|
|
set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE); |
|
128
|
|
|
|
|
129
|
|
|
$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket)); |
|
130
|
|
|
|
|
131
|
|
|
$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); |
|
|
|
|
|
|
132
|
|
|
|
|
133
|
|
|
restore_error_handler(); |
|
134
|
|
|
|
|
135
|
|
|
if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { |
|
136
|
|
|
throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
if (!$socket) { |
|
140
|
|
|
if ($errno === 0) { |
|
141
|
|
|
// Connection issue |
|
142
|
|
|
throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
throw new Exception($errstr, 'fsockopenerror', null, $errno); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
$data_format = $options['data_format']; |
|
149
|
|
|
|
|
150
|
|
|
if ($data_format === 'query') { |
|
151
|
|
|
$path = self::format_get($url_parts, $data); |
|
|
|
|
|
|
152
|
|
|
$data = ''; |
|
153
|
|
|
} |
|
154
|
|
|
else { |
|
155
|
|
|
$path = self::format_get($url_parts, array()); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url)); |
|
159
|
|
|
|
|
160
|
|
|
$request_body = ''; |
|
161
|
|
|
$out = sprintf("%s %s HTTP/%.1f\r\n", $options['type'], $path, $options['protocol_version']); |
|
162
|
|
|
|
|
163
|
|
|
if ($options['type'] !== Requests::TRACE) { |
|
164
|
|
|
if (is_array($data)) { |
|
165
|
|
|
$request_body = http_build_query($data, null, '&'); |
|
166
|
|
|
} |
|
167
|
|
|
else { |
|
168
|
|
|
$request_body = $data; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
if (!empty($data)) { |
|
172
|
|
|
if (!isset($case_insensitive_headers['Content-Length'])) { |
|
173
|
|
|
$headers['Content-Length'] = strlen($request_body); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
if (!isset($case_insensitive_headers['Content-Type'])) { |
|
177
|
|
|
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
if (!isset($case_insensitive_headers['Host'])) { |
|
183
|
|
|
$out .= sprintf('Host: %s', $url_parts['host']); |
|
184
|
|
|
|
|
185
|
|
|
if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) { |
|
|
|
|
|
|
186
|
|
|
$out .= ':' . $url_parts['port']; |
|
187
|
|
|
} |
|
188
|
|
|
$out .= "\r\n"; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
if (!isset($case_insensitive_headers['User-Agent'])) { |
|
192
|
|
|
$out .= sprintf("User-Agent: %s\r\n", $options['useragent']); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
$accept_encoding = $this->accept_encoding(); |
|
196
|
|
|
if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) { |
|
197
|
|
|
$out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
$headers = Requests::flatten($headers); |
|
201
|
|
|
|
|
202
|
|
|
if (!empty($headers)) { |
|
203
|
|
|
$out .= implode($headers, "\r\n") . "\r\n"; |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
$options['hooks']->dispatch('fsockopen.after_headers', array(&$out)); |
|
207
|
|
|
|
|
208
|
|
|
if (substr($out, -2) !== "\r\n") { |
|
209
|
|
|
$out .= "\r\n"; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
if (!isset($case_insensitive_headers['Connection'])) { |
|
213
|
|
|
$out .= "Connection: Close\r\n"; |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
$out .= "\r\n" . $request_body; |
|
217
|
|
|
|
|
218
|
|
|
$options['hooks']->dispatch('fsockopen.before_send', array(&$out)); |
|
219
|
|
|
|
|
220
|
|
|
fwrite($socket, $out); |
|
221
|
|
|
$options['hooks']->dispatch('fsockopen.after_send', array($out)); |
|
222
|
|
|
|
|
223
|
|
|
if (!$options['blocking']) { |
|
224
|
|
|
fclose($socket); |
|
225
|
|
|
$fake_headers = ''; |
|
226
|
|
|
$options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers)); |
|
227
|
|
|
return ''; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
$timeout_sec = (int) floor($options['timeout']); |
|
231
|
|
|
if ($timeout_sec == $options['timeout']) { |
|
232
|
|
|
$timeout_msec = 0; |
|
233
|
|
|
} |
|
234
|
|
|
else { |
|
235
|
|
|
$timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS; |
|
236
|
|
|
} |
|
237
|
|
|
stream_set_timeout($socket, $timeout_sec, $timeout_msec); |
|
238
|
|
|
|
|
239
|
|
|
$response = $body = $headers = ''; |
|
240
|
|
|
$this->info = stream_get_meta_data($socket); |
|
241
|
|
|
$size = 0; |
|
242
|
|
|
$doingbody = false; |
|
243
|
|
|
$download = false; |
|
244
|
|
|
if ($options['filename']) { |
|
245
|
|
|
$download = fopen($options['filename'], 'wb'); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
while (!feof($socket)) { |
|
249
|
|
|
$this->info = stream_get_meta_data($socket); |
|
250
|
|
|
if ($this->info['timed_out']) { |
|
251
|
|
|
throw new Exception('fsocket timed out', 'timeout'); |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
$block = fread($socket, Requests::BUFFER_SIZE); |
|
255
|
|
|
if (!$doingbody) { |
|
256
|
|
|
$response .= $block; |
|
257
|
|
|
if (strpos($response, "\r\n\r\n")) { |
|
258
|
|
|
list($headers, $block) = explode("\r\n\r\n", $response, 2); |
|
259
|
|
|
$doingbody = true; |
|
260
|
|
|
} |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
// Are we in body mode now? |
|
264
|
|
|
if ($doingbody) { |
|
265
|
|
|
$options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes)); |
|
266
|
|
|
$data_length = strlen($block); |
|
267
|
|
|
if ($this->max_bytes) { |
|
268
|
|
|
// Have we already hit a limit? |
|
269
|
|
|
if ($size === $this->max_bytes) { |
|
270
|
|
|
continue; |
|
271
|
|
|
} |
|
272
|
|
|
if (($size + $data_length) > $this->max_bytes) { |
|
273
|
|
|
// Limit the length |
|
274
|
|
|
$limited_length = ($this->max_bytes - $size); |
|
275
|
|
|
$block = substr($block, 0, $limited_length); |
|
276
|
|
|
} |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
$size += strlen($block); |
|
280
|
|
|
if ($download) { |
|
281
|
|
|
fwrite($download, $block); |
|
282
|
|
|
} |
|
283
|
|
|
else { |
|
284
|
|
|
$body .= $block; |
|
285
|
|
|
} |
|
286
|
|
|
} |
|
287
|
|
|
} |
|
288
|
|
|
$this->headers = $headers; |
|
289
|
|
|
|
|
290
|
|
|
if ($download) { |
|
291
|
|
|
fclose($download); |
|
292
|
|
|
} |
|
293
|
|
|
else { |
|
294
|
|
|
$this->headers .= "\r\n\r\n" . $body; |
|
295
|
|
|
} |
|
296
|
|
|
fclose($socket); |
|
297
|
|
|
|
|
298
|
|
|
$options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info)); |
|
299
|
|
|
return $this->headers; |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* Send multiple requests simultaneously |
|
304
|
|
|
* |
|
305
|
|
|
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Rmccue\Requests\Transport::request} |
|
|
|
|
|
|
306
|
|
|
* @param array $options Global options, see {@see Requests::response()} for documentation |
|
307
|
|
|
* @return array Array of Rmccue\Requests\Response objects (may contain Rmccue\Requests\Exception or string responses as well) |
|
|
|
|
|
|
308
|
|
|
*/ |
|
309
|
|
|
public function request_multiple($requests, $options) { |
|
310
|
|
|
$responses = array(); |
|
311
|
|
|
$class = get_class($this); |
|
312
|
|
|
foreach ($requests as $id => $request) { |
|
313
|
|
|
try { |
|
314
|
|
|
$handler = new $class(); |
|
315
|
|
|
$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); |
|
316
|
|
|
|
|
317
|
|
|
$request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request)); |
|
318
|
|
|
} |
|
319
|
|
|
catch (Exception $e) { |
|
320
|
|
|
$responses[$id] = $e; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
View Code Duplication |
if (!is_string($responses[$id])) { |
|
324
|
|
|
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id)); |
|
325
|
|
|
} |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
return $responses; |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
/** |
|
332
|
|
|
* Retrieve the encodings we can accept |
|
333
|
|
|
* |
|
334
|
|
|
* @return string Accept-Encoding header value |
|
335
|
|
|
*/ |
|
336
|
|
|
protected static function accept_encoding() { |
|
337
|
|
|
$type = array(); |
|
338
|
|
|
if (function_exists('gzinflate')) { |
|
339
|
|
|
$type[] = 'deflate;q=1.0'; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
if (function_exists('gzuncompress')) { |
|
343
|
|
|
$type[] = 'compress;q=0.5'; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
$type[] = 'gzip;q=0.5'; |
|
347
|
|
|
|
|
348
|
|
|
return implode(', ', $type); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Format a URL given GET data |
|
353
|
|
|
* |
|
354
|
|
|
* @param array $url_parts |
|
355
|
|
|
* @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} |
|
356
|
|
|
* @return string URL with data |
|
357
|
|
|
*/ |
|
358
|
|
|
protected static function format_get($url_parts, $data) { |
|
359
|
|
|
if (!empty($data)) { |
|
360
|
|
|
if (empty($url_parts['query'])) { |
|
361
|
|
|
$url_parts['query'] = ''; |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
$url_parts['query'] .= '&' . http_build_query($data, null, '&'); |
|
365
|
|
|
$url_parts['query'] = trim($url_parts['query'], '&'); |
|
366
|
|
|
} |
|
367
|
|
|
if (isset($url_parts['path'])) { |
|
368
|
|
|
if (isset($url_parts['query'])) { |
|
369
|
|
|
$get = $url_parts['path'] . '?' . $url_parts['query']; |
|
370
|
|
|
} |
|
371
|
|
|
else { |
|
372
|
|
|
$get = $url_parts['path']; |
|
373
|
|
|
} |
|
374
|
|
|
} |
|
375
|
|
|
else { |
|
376
|
|
|
$get = '/'; |
|
377
|
|
|
} |
|
378
|
|
|
return $get; |
|
379
|
|
|
} |
|
380
|
|
|
|
|
381
|
|
|
/** |
|
382
|
|
|
* Error handler for stream_socket_client() |
|
383
|
|
|
* |
|
384
|
|
|
* @param int $errno Error number (e.g. E_WARNING) |
|
385
|
|
|
* @param string $errstr Error message |
|
386
|
|
|
*/ |
|
387
|
|
|
public function connect_error_handler($errno, $errstr) { |
|
388
|
|
|
// Double-check we can handle it |
|
389
|
|
|
if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { |
|
390
|
|
|
// Return false to indicate the default error handler should engage |
|
391
|
|
|
return false; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
$this->connect_error .= $errstr . "\n"; |
|
395
|
|
|
return true; |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
/** |
|
399
|
|
|
* Verify the certificate against common name and subject alternative names |
|
400
|
|
|
* |
|
401
|
|
|
* Unfortunately, PHP doesn't check the certificate against the alternative |
|
402
|
|
|
* names, leading things like 'https://www.github.com/' to be invalid. |
|
403
|
|
|
* Instead |
|
404
|
|
|
* |
|
405
|
|
|
* @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 |
|
406
|
|
|
* |
|
407
|
|
|
* @throws Rmccue\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) |
|
408
|
|
|
* @throws Rmccue\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) |
|
409
|
|
|
* @param string $host Host name to verify against |
|
410
|
|
|
* @param resource $context Stream context |
|
411
|
|
|
* @return bool |
|
412
|
|
|
*/ |
|
413
|
|
|
public function verify_certificate_from_context($host, $context) { |
|
414
|
|
|
$meta = stream_context_get_options($context); |
|
415
|
|
|
|
|
416
|
|
|
// If we don't have SSL options, then we couldn't make the connection at |
|
417
|
|
|
// all |
|
418
|
|
|
if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { |
|
419
|
|
|
throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
$cert = openssl_x509_parse($meta['ssl']['peer_certificate']); |
|
423
|
|
|
|
|
424
|
|
|
return SSL::verify_certificate($host, $cert); |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* Whether this transport is valid |
|
429
|
|
|
* |
|
430
|
|
|
* @codeCoverageIgnore |
|
431
|
|
|
* @return boolean True if the transport is valid, false otherwise. |
|
432
|
|
|
*/ |
|
433
|
|
|
public static function test($capabilities = array()) { |
|
434
|
|
|
if (!function_exists('fsockopen')) { |
|
435
|
|
|
return false; |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
// If needed, check that streams support SSL |
|
439
|
|
|
if (isset($capabilities['ssl']) && $capabilities['ssl']) { |
|
440
|
|
|
if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { |
|
441
|
|
|
return false; |
|
442
|
|
|
} |
|
443
|
|
|
|
|
444
|
|
|
// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156 |
|
445
|
|
|
if (defined('HHVM_VERSION')) { |
|
446
|
|
|
return false; |
|
447
|
|
|
} |
|
448
|
|
|
} |
|
449
|
|
|
|
|
450
|
|
|
return true; |
|
451
|
|
|
} |
|
452
|
|
|
} |
|
453
|
|
|
|
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.