This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This program is free software; you can redistribute it and/or modify |
||
4 | * it under the terms of the GNU General Public License as published by |
||
5 | * the Free Software Foundation; either version 2 of the License, or |
||
6 | * (at your option) any later version. |
||
7 | * |
||
8 | * This program is distributed in the hope that it will be useful, |
||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
11 | * GNU General Public License for more details. |
||
12 | * |
||
13 | * You should have received a copy of the GNU General Public License along |
||
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
16 | * http://www.gnu.org/copyleft/gpl.html |
||
17 | * |
||
18 | * @file |
||
19 | */ |
||
20 | |||
21 | class PhpHttpRequest extends MWHttpRequest { |
||
22 | |||
23 | private $fopenErrors = []; |
||
24 | |||
25 | /** |
||
26 | * @param string $url |
||
27 | * @return string |
||
28 | */ |
||
29 | protected function urlToTcp( $url ) { |
||
30 | $parsedUrl = parse_url( $url ); |
||
31 | |||
32 | return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port']; |
||
33 | } |
||
34 | |||
35 | /** |
||
36 | * Returns an array with a 'capath' or 'cafile' key |
||
37 | * that is suitable to be merged into the 'ssl' sub-array of |
||
38 | * a stream context options array. |
||
39 | * Uses the 'caInfo' option of the class if it is provided, otherwise uses the system |
||
40 | * default CA bundle if PHP supports that, or searches a few standard locations. |
||
41 | * @return array |
||
42 | * @throws DomainException |
||
43 | */ |
||
44 | protected function getCertOptions() { |
||
45 | $certOptions = []; |
||
46 | $certLocations = []; |
||
47 | if ( $this->caInfo ) { |
||
48 | $certLocations = [ 'manual' => $this->caInfo ]; |
||
49 | } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) { |
||
50 | // @codingStandardsIgnoreStart Generic.Files.LineLength |
||
51 | // Default locations, based on |
||
52 | // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ |
||
53 | // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves. |
||
54 | // PHP 5.6+ gets the CA location from OpenSSL as long as it is not set manually, |
||
55 | // so we should leave capath/cafile empty there. |
||
56 | // @codingStandardsIgnoreEnd |
||
57 | $certLocations = array_filter( [ |
||
58 | getenv( 'SSL_CERT_DIR' ), |
||
59 | getenv( 'SSL_CERT_PATH' ), |
||
60 | '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al |
||
61 | '/etc/ssl/certs', # Debian et al |
||
62 | '/etc/pki/tls/certs/ca-bundle.trust.crt', |
||
63 | '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', |
||
64 | '/System/Library/OpenSSL', # OSX |
||
65 | ] ); |
||
66 | } |
||
67 | |||
68 | foreach ( $certLocations as $key => $cert ) { |
||
69 | if ( is_dir( $cert ) ) { |
||
70 | $certOptions['capath'] = $cert; |
||
71 | break; |
||
72 | } elseif ( is_file( $cert ) ) { |
||
73 | $certOptions['cafile'] = $cert; |
||
74 | break; |
||
75 | } elseif ( $key === 'manual' ) { |
||
76 | // fail more loudly if a cert path was manually configured and it is not valid |
||
77 | throw new DomainException( "Invalid CA info passed: $cert" ); |
||
78 | } |
||
79 | } |
||
80 | |||
81 | return $certOptions; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Custom error handler for dealing with fopen() errors. |
||
86 | * fopen() tends to fire multiple errors in succession, and the last one |
||
87 | * is completely useless (something like "fopen: failed to open stream") |
||
88 | * so normal methods of handling errors programmatically |
||
89 | * like get_last_error() don't work. |
||
90 | */ |
||
91 | public function errorHandler( $errno, $errstr ) { |
||
92 | $n = count( $this->fopenErrors ) + 1; |
||
93 | $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ]; |
||
94 | } |
||
95 | |||
96 | public function execute() { |
||
97 | |||
98 | parent::execute(); |
||
99 | |||
100 | if ( is_array( $this->postData ) ) { |
||
101 | $this->postData = wfArrayToCgi( $this->postData ); |
||
102 | } |
||
103 | |||
104 | if ( $this->parsedUrl['scheme'] != 'http' |
||
105 | && $this->parsedUrl['scheme'] != 'https' ) { |
||
106 | $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] ); |
||
107 | } |
||
108 | |||
109 | $this->reqHeaders['Accept'] = "*/*"; |
||
110 | $this->reqHeaders['Connection'] = 'Close'; |
||
111 | if ( $this->method == 'POST' ) { |
||
112 | // Required for HTTP 1.0 POSTs |
||
113 | $this->reqHeaders['Content-Length'] = strlen( $this->postData ); |
||
114 | if ( !isset( $this->reqHeaders['Content-Type'] ) ) { |
||
115 | $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded"; |
||
116 | } |
||
117 | } |
||
118 | |||
119 | // Set up PHP stream context |
||
120 | $options = [ |
||
121 | 'http' => [ |
||
122 | 'method' => $this->method, |
||
123 | 'header' => implode( "\r\n", $this->getHeaderList() ), |
||
124 | 'protocol_version' => '1.1', |
||
125 | 'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0, |
||
126 | 'ignore_errors' => true, |
||
127 | 'timeout' => $this->timeout, |
||
128 | // Curl options in case curlwrappers are installed |
||
129 | 'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0, |
||
130 | 'curl_verify_ssl_peer' => $this->sslVerifyCert, |
||
131 | ], |
||
132 | 'ssl' => [ |
||
133 | 'verify_peer' => $this->sslVerifyCert, |
||
134 | 'SNI_enabled' => true, |
||
135 | 'ciphers' => 'HIGH:!SSLv2:!SSLv3:-ADH:-kDH:-kECDH:-DSS', |
||
136 | 'disable_compression' => true, |
||
137 | ], |
||
138 | ]; |
||
139 | |||
140 | if ( $this->proxy ) { |
||
141 | $options['http']['proxy'] = $this->urlToTcp( $this->proxy ); |
||
142 | $options['http']['request_fulluri'] = true; |
||
143 | } |
||
144 | |||
145 | if ( $this->postData ) { |
||
146 | $options['http']['content'] = $this->postData; |
||
147 | } |
||
148 | |||
149 | if ( $this->sslVerifyHost ) { |
||
150 | // PHP 5.6.0 deprecates CN_match, in favour of peer_name which |
||
151 | // actually checks SubjectAltName properly. |
||
152 | if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) { |
||
153 | $options['ssl']['peer_name'] = $this->parsedUrl['host']; |
||
154 | } else { |
||
155 | $options['ssl']['CN_match'] = $this->parsedUrl['host']; |
||
156 | } |
||
157 | } |
||
158 | |||
159 | $options['ssl'] += $this->getCertOptions(); |
||
160 | |||
161 | $context = stream_context_create( $options ); |
||
162 | |||
163 | $this->headerList = []; |
||
164 | $reqCount = 0; |
||
165 | $url = $this->url; |
||
166 | |||
167 | $result = []; |
||
168 | |||
169 | if ( $this->profiler ) { |
||
170 | $profileSection = $this->profiler->scopedProfileIn( |
||
171 | __METHOD__ . '-' . $this->profileName |
||
172 | ); |
||
173 | } |
||
174 | do { |
||
175 | $reqCount++; |
||
176 | $this->fopenErrors = []; |
||
177 | set_error_handler( [ $this, 'errorHandler' ] ); |
||
178 | $fh = fopen( $url, "r", false, $context ); |
||
179 | restore_error_handler(); |
||
180 | |||
181 | if ( !$fh ) { |
||
182 | // HACK for instant commons. |
||
183 | // If we are contacting (commons|upload).wikimedia.org |
||
184 | // try again with CN_match for en.wikipedia.org |
||
185 | // as php does not handle SubjectAltName properly |
||
186 | // prior to "peer_name" option in php 5.6 |
||
187 | if ( isset( $options['ssl']['CN_match'] ) |
||
188 | && ( $options['ssl']['CN_match'] === 'commons.wikimedia.org' |
||
189 | || $options['ssl']['CN_match'] === 'upload.wikimedia.org' ) |
||
190 | ) { |
||
191 | $options['ssl']['CN_match'] = 'en.wikipedia.org'; |
||
192 | $context = stream_context_create( $options ); |
||
193 | continue; |
||
194 | } |
||
195 | break; |
||
196 | } |
||
197 | |||
198 | $result = stream_get_meta_data( $fh ); |
||
199 | $this->headerList = $result['wrapper_data']; |
||
200 | $this->parseHeader(); |
||
201 | |||
202 | if ( !$this->followRedirects ) { |
||
203 | break; |
||
204 | } |
||
205 | |||
206 | # Handle manual redirection |
||
207 | if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) { |
||
208 | break; |
||
209 | } |
||
210 | # Check security of URL |
||
211 | $url = $this->getResponseHeader( "Location" ); |
||
212 | |||
213 | if ( !Http::isValidURI( $url ) ) { |
||
214 | $this->logger->debug( __METHOD__ . ": insecure redirection\n" ); |
||
215 | break; |
||
216 | } |
||
217 | } while ( true ); |
||
218 | if ( $this->profiler ) { |
||
219 | $this->profiler->scopedProfileOut( $profileSection ); |
||
0 ignored issues
–
show
|
|||
220 | } |
||
221 | |||
222 | $this->setStatus(); |
||
223 | |||
224 | if ( $fh === false ) { |
||
225 | if ( $this->fopenErrors ) { |
||
226 | $this->logger->warning( __CLASS__ |
||
227 | . ': error opening connection: {errstr1}', $this->fopenErrors ); |
||
228 | } |
||
229 | $this->status->fatal( 'http-request-error' ); |
||
230 | return $this->status; |
||
231 | } |
||
232 | |||
233 | if ( $result['timed_out'] ) { |
||
234 | $this->status->fatal( 'http-timed-out', $this->url ); |
||
235 | return $this->status; |
||
236 | } |
||
237 | |||
238 | // If everything went OK, or we received some error code |
||
239 | // get the response body content. |
||
240 | if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) { |
||
241 | while ( !feof( $fh ) ) { |
||
242 | $buf = fread( $fh, 8192 ); |
||
243 | |||
244 | if ( $buf === false ) { |
||
245 | $this->status->fatal( 'http-read-error' ); |
||
246 | break; |
||
247 | } |
||
248 | |||
249 | if ( strlen( $buf ) ) { |
||
250 | call_user_func( $this->callback, $fh, $buf ); |
||
251 | } |
||
252 | } |
||
253 | } |
||
254 | fclose( $fh ); |
||
255 | |||
256 | return $this->status; |
||
257 | } |
||
258 | } |
||
259 |
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:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.