MediaWiki REL1_34
PhpHttpRequest.php
Go to the documentation of this file.
1<?php
22
23 private $fopenErrors = [];
24
25 public function __construct() {
26 if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
27 throw new RuntimeException( __METHOD__ . ': allow_url_fopen needs to be enabled for ' .
28 'pure PHP http requests to work. If possible, curl should be used instead. See ' .
29 'https://www.php.net/curl.'
30 );
31 }
32
33 parent::__construct( ...func_get_args() );
34 }
35
40 protected function urlToTcp( $url ) {
41 $parsedUrl = parse_url( $url );
42
43 return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
44 }
45
55 protected function getCertOptions() {
56 $certOptions = [];
57 $certLocations = [];
58 if ( $this->caInfo ) {
59 $certLocations = [ 'manual' => $this->caInfo ];
60 }
61
62 foreach ( $certLocations as $key => $cert ) {
63 if ( is_dir( $cert ) ) {
64 $certOptions['capath'] = $cert;
65 break;
66 } elseif ( is_file( $cert ) ) {
67 $certOptions['cafile'] = $cert;
68 break;
69 } elseif ( $key === 'manual' ) {
70 // fail more loudly if a cert path was manually configured and it is not valid
71 throw new DomainException( "Invalid CA info passed: $cert" );
72 }
73 }
74
75 return $certOptions;
76 }
77
88 public function errorHandler( $errno, $errstr ) {
89 $n = count( $this->fopenErrors ) + 1;
90 $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ];
91 }
92
98 public function execute() {
99 $this->prepare();
100
101 if ( is_array( $this->postData ) ) {
102 $this->postData = wfArrayToCgi( $this->postData );
103 }
104
105 if ( $this->parsedUrl['scheme'] != 'http'
106 && $this->parsedUrl['scheme'] != 'https' ) {
107 $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
108 }
109
110 $this->reqHeaders['Accept'] = "*/*";
111 $this->reqHeaders['Connection'] = 'Close';
112 if ( $this->method == 'POST' ) {
113 // Required for HTTP 1.0 POSTs
114 $this->reqHeaders['Content-Length'] = strlen( $this->postData );
115 if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
116 $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
117 }
118 }
119
120 // Set up PHP stream context
121 $options = [
122 'http' => [
123 'method' => $this->method,
124 'header' => implode( "\r\n", $this->getHeaderList() ),
125 'protocol_version' => '1.1',
126 'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0,
127 'ignore_errors' => true,
128 'timeout' => $this->timeout,
129 // Curl options in case curlwrappers are installed
130 'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0,
131 'curl_verify_ssl_peer' => $this->sslVerifyCert,
132 ],
133 'ssl' => [
134 'verify_peer' => $this->sslVerifyCert,
135 'SNI_enabled' => true,
136 'ciphers' => 'HIGH:!SSLv2:!SSLv3:-ADH:-kDH:-kECDH:-DSS',
137 'disable_compression' => true,
138 ],
139 ];
140
141 if ( $this->proxy ) {
142 $options['http']['proxy'] = $this->urlToTcp( $this->proxy );
143 $options['http']['request_fulluri'] = true;
144 }
145
146 if ( $this->postData ) {
147 $options['http']['content'] = $this->postData;
148 }
149
150 if ( $this->sslVerifyHost ) {
151 $options['ssl']['peer_name'] = $this->parsedUrl['host'];
152 }
153
154 $options['ssl'] += $this->getCertOptions();
155
156 $context = stream_context_create( $options );
157
158 $this->headerList = [];
159 $reqCount = 0;
160 $url = $this->url;
161
162 $result = [];
163
164 if ( $this->profiler ) {
165 $profileSection = $this->profiler->scopedProfileIn(
166 __METHOD__ . '-' . $this->profileName
167 );
168 }
169 do {
170 $reqCount++;
171 $this->fopenErrors = [];
172 set_error_handler( [ $this, 'errorHandler' ] );
173 $fh = fopen( $url, "r", false, $context );
174 restore_error_handler();
175
176 if ( !$fh ) {
177 break;
178 }
179
180 $result = stream_get_meta_data( $fh );
181 $this->headerList = $result['wrapper_data'];
182 $this->parseHeader();
183
184 if ( !$this->followRedirects ) {
185 break;
186 }
187
188 # Handle manual redirection
189 if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
190 break;
191 }
192 # Check security of URL
193 $url = $this->getResponseHeader( "Location" );
194
195 if ( !Http::isValidURI( $url ) ) {
196 $this->logger->debug( __METHOD__ . ": insecure redirection\n" );
197 break;
198 }
199 } while ( true );
200 if ( $this->profiler ) {
201 $this->profiler->scopedProfileOut( $profileSection );
202 }
203
204 $this->setStatus();
205
206 if ( $fh === false ) {
207 if ( $this->fopenErrors ) {
208 $this->logger->warning( __CLASS__
209 . ': error opening connection: {errstr1}', $this->fopenErrors );
210 }
211 $this->status->fatal( 'http-request-error' );
212 return Status::wrap( $this->status ); // TODO B/C; move this to callers
213 }
214
215 if ( $result['timed_out'] ) {
216 $this->status->fatal( 'http-timed-out', $this->url );
217 return Status::wrap( $this->status ); // TODO B/C; move this to callers
218 }
219
220 // If everything went OK, or we received some error code
221 // get the response body content.
222 if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
223 while ( !feof( $fh ) ) {
224 $buf = fread( $fh, 8192 );
225
226 if ( $buf === false ) {
227 $this->status->fatal( 'http-read-error' );
228 break;
229 }
230
231 if ( $buf !== '' ) {
232 call_user_func( $this->callback, $fh, $buf );
233 }
234 }
235 }
236 fclose( $fh );
237
238 return Status::wrap( $this->status ); // TODO B/C; move this to callers
239 }
240}
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
static prepare( $text, $lang)
Backward-compatibility shim for extensions.
This wrapper class will call out to curl (if available) or fallback to regular PHP if necessary for h...
isRedirect()
Returns true if the last status code was a redirect.
setStatus()
Sets HTTPRequest status member to a fatal value with the error message if the returned integer value ...
parseHeader()
Parses the headers, including the HTTP status code and any Set-Cookie headers.
getResponseHeader( $header)
Returns the value of the given response header.
getHeaderList()
Get an array of the headers.
getCertOptions()
Returns an array with a 'capath' or 'cafile' key that is suitable to be merged into the 'ssl' sub-arr...
errorHandler( $errno, $errstr)
Custom error handler for dealing with fopen() errors.
$context
Definition load.php:45
$fh