r61357 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r61356‎ | r61357 | r61358 >
Date:07:50, 22 January 2010
Author:mah
Status:resolved (Comments)
Tags:
Comment:
Follow up 61352, address TimStarling's comments.
Modified paths:
  • /trunk/phase3/includes/HttpFunctions.php (modified) (history)
  • /trunk/phase3/maintenance/parserTests.inc (modified) (history)
  • /trunk/phase3/tests/HttpTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/parserTests.inc
@@ -1618,8 +1618,6 @@
16191619 }
16201620
16211621 function post( $url, $data ) {
1622 - // @fixme: for whatever reason, I get a 417 fail when using CURL's multipart form submit.
1623 - // If we do form URL encoding ourselves, though, it should work.
1624 - return Http::post( $url, array( 'postdata' => wfArrayToCGI( $data ) ) );
 1622+ return Http::post( $url, array( 'postData' => $data) );
16251623 }
16261624 }
Index: trunk/phase3/tests/HttpTest.php
@@ -52,10 +52,10 @@
5353 self::$content["POST $u"] = file_get_contents( $content );
5454 self::$headers["POST $u"] = file_get_contents( $headers );
5555 }
56 - foreach ( $this->test_posturl as $u => $postdata ) {
57 - system( "curl -0 -s -X POST -d '$postdata' -D $headers '$u' -o $content" );
58 - self::$content["POST $u => $postdata"] = file_get_contents( $content );
59 - self::$headers["POST $u => $postdata"] = file_get_contents( $headers );
 56+ foreach ( $this->test_posturl as $u => $postData ) {
 57+ system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
 58+ self::$content["POST $u => $postData"] = file_get_contents( $content );
 59+ self::$headers["POST $u => $postData"] = file_get_contents( $headers );
6060 }
6161 unlink( $content );
6262 unlink( $headers );
@@ -138,7 +138,7 @@
139139 $opt['proxy'] = $proxy;
140140 }
141141
142 - /* no postdata here because the only request I could find in code so far didn't have any */
 142+ /* no postData here because the only request I could find in code so far didn't have any */
143143 foreach ( $this->test_requesturl as $u ) {
144144 $r = Http::request( "POST", $u, $opt );
145145 $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with $wgHTTPEngine" );
@@ -253,7 +253,7 @@
254254 self::runHTTPGets();
255255 }
256256
257 - /* ./phase3/maintenance/parserTests.inc:1618: return Http::post( $url, array( 'postdata' => wfArrayToCGI( $data ) ) ); */
 257+ /* ./phase3/maintenance/parserTests.inc:1618: return Http::post( $url, array( 'postData' => wfArrayToCGI( $data ) ) ); */
258258 function runHTTPPosts($proxy=null) {
259259 global $wgHTTPEngine;
260260 $opt = array();
@@ -262,11 +262,11 @@
263263 $opt['proxy'] = $proxy;
264264 }
265265
266 - foreach ( $this->test_posturl as $u => $postdata ) {
267 - $opt['postdata'] = $postdata;
 266+ foreach ( $this->test_posturl as $u => $postData ) {
 267+ $opt['postData'] = $postData;
268268 $r = Http::post( $u, $opt );
269 - $this->assertEquals( self::$content["POST $u => $postdata"], "$r",
270 - "POST $u (postdata=$postdata) with $wgHTTPEngine" );
 269+ $this->assertEquals( self::$content["POST $u => $postData"], "$r",
 270+ "POST $u (postData=$postData) with $wgHTTPEngine" );
271271 }
272272 }
273273
Index: trunk/phase3/includes/HttpFunctions.php
@@ -12,18 +12,18 @@
1313 * Perform an HTTP request
1414 * @param $method string HTTP method. Usually GET/POST
1515 * @param $url string Full URL to act on
16 - * @param $opts options to pass to HttpRequest object
 16+ * @param $options options to pass to HttpRequest object
1717 * @returns mixed (bool)false on failure or a string on success
1818 */
19 - public static function request( $method, $url, $opts = array() ) {
20 - $opts['method'] = strtoupper( $method );
21 - if ( !array_key_exists( 'timeout', $opts ) ) {
22 - $opts['timeout'] = 'default';
 19+ public static function request( $method, $url, $options = array() ) {
 20+ $options['method'] = strtoupper( $method );
 21+ if ( !isset( $options['timeout'] ) ) {
 22+ $options['timeout'] = 'default';
2323 }
24 - $req = HttpRequest::factory( $url, $opts );
 24+ $req = HttpRequest::factory( $url, $options );
2525 $status = $req->execute();
2626 if ( $status->isOK() ) {
27 - return $req;
 27+ return $req->getContent();
2828 } else {
2929 return false;
3030 }
@@ -33,17 +33,17 @@
3434 * Simple wrapper for Http::request( 'GET' )
3535 * @see Http::request()
3636 */
37 - public static function get( $url, $timeout = 'default', $opts = array() ) {
38 - $opts['timeout'] = $timeout;
39 - return Http::request( 'GET', $url, $opts );
 37+ public static function get( $url, $timeout = 'default', $options = array() ) {
 38+ $options['timeout'] = $timeout;
 39+ return Http::request( 'GET', $url, $options );
4040 }
4141
4242 /**
4343 * Simple wrapper for Http::request( 'POST' )
4444 * @see Http::request()
4545 */
46 - public static function post( $url, $opts = array() ) {
47 - return Http::request( 'POST', $url, $opts );
 46+ public static function post( $url, $options = array() ) {
 47+ return Http::request( 'POST', $url, $options );
4848 }
4949
5050 /**
@@ -111,14 +111,16 @@
112112 protected $content;
113113 protected $timeout = 'default';
114114 protected $headersOnly = null;
115 - protected $postdata = null;
 115+ protected $postData = null;
116116 protected $proxy = null;
117 - protected $no_proxy = false;
 117+ protected $noProxy = false;
118118 protected $sslVerifyHost = true;
119119 protected $caInfo = null;
120120 protected $method = "GET";
 121+ protected $reqHeaders = array();
121122 protected $url;
122 - protected $parsed_url;
 123+ protected $parsedUrl;
 124+ protected $callback;
123125 public $status;
124126
125127 /**
@@ -129,295 +131,304 @@
130132 * timeout
131133 * targetFilePath
132134 * requestKey
133 - * headersOnly
134 - * postdata
 135+ * postData
135136 * proxy
136 - * no_proxy
 137+ * noProxy
137138 * sslVerifyHost
138139 * caInfo
139140 */
140 - function __construct( $url = null, $opt = array() ) {
141 - global $wgHTTPTimeout, $wgTitle;
 141+ function __construct( $url, $options = array() ) {
 142+ global $wgHTTPTimeout;
142143
143144 $this->url = $url;
144 - $this->parsed_url = parse_url( $url );
 145+ $this->parsedUrl = parse_url( $url );
145146
146 - if ( !ini_get( 'allow_url_fopen' ) ) {
147 - throw new MWException( 'allow_url_fopen needs to be enabled for http requests to work' );
148 - } elseif ( !Http::isValidURI( $this->url ) ) {
149 - throw new MWException( 'Invalid URL' );
 147+ if ( !Http::isValidURI( $this->url ) ) {
 148+ $this->status = Status::newFromFatal('http-invalid-url');
150149 } else {
151150 $this->status = Status::newGood( 100 ); // continue
152151 }
153152
154 - if ( array_key_exists( 'timeout', $opt ) && $opt['timeout'] != 'default' ) {
155 - $this->timeout = $opt['timeout'];
 153+ if ( isset($options['timeout']) && $options['timeout'] != 'default' ) {
 154+ $this->timeout = $options['timeout'];
156155 } else {
157156 $this->timeout = $wgHTTPTimeout;
158157 }
159158
160 - $members = array( "targetFilePath", "requestKey", "headersOnly", "postdata",
161 - "proxy", "no_proxy", "sslVerifyHost", "caInfo", "method" );
 159+ $members = array( "targetFilePath", "requestKey", "postData",
 160+ "proxy", "noProxy", "sslVerifyHost", "caInfo", "method" );
162161 foreach ( $members as $o ) {
163 - if ( array_key_exists( $o, $opt ) ) {
164 - $this->$o = $opt[$o];
 162+ if ( isset($options[$o]) ) {
 163+ $this->$o = $options[$o];
165164 }
166165 }
167 -
168 - if ( is_array( $this->postdata ) ) {
169 - $this->postdata = wfArrayToCGI( $this->postdata );
170 - }
171 -
172 - $this->initRequest();
173 -
174 - if ( !$this->no_proxy ) {
175 - $this->proxySetup();
176 - }
177 -
178 - # Set the referer to $wgTitle, even in command-line mode
179 - # This is useful for interwiki transclusion, where the foreign
180 - # server wants to know what the referring page is.
181 - # $_SERVER['REQUEST_URI'] gives a less reliable indication of the
182 - # referring page.
183 - if ( is_object( $wgTitle ) ) {
184 - $this->setReferrer( $wgTitle->getFullURL() );
185 - }
186166 }
187167
188168 /**
189 - * For backwards compatibility, we provide a __toString method so
190 - * that any code that expects a string result from Http::Get()
191 - * will see the content of the request.
192 - */
193 - function __toString() {
194 - return $this->content;
195 - }
196 -
197 - /**
198169 * Generate a new request object
199170 * @see HttpRequest::__construct
200171 */
201 - public static function factory( $url, $opt ) {
 172+ public static function factory( $url, $options ) {
202173 global $wgHTTPEngine;
203174 $engine = $wgHTTPEngine;
204175
205176 if ( !$wgHTTPEngine ) {
206177 $wgHTTPEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
207178 } elseif ( $wgHTTPEngine == 'curl' && !function_exists( 'curl_init' ) ) {
208 - throw new MWException( 'FIXME' );
 179+ throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but $wgHTTPEngine is set to "curl"' );
209180 }
210181
211182 switch( $wgHTTPEngine ) {
212183 case 'curl':
213 - return new CurlHttpRequest( $url, $opt );
 184+ return new CurlHttpRequest( $url, $options );
214185 case 'php':
215 - return new PhpHttpRequest( $url, $opt );
 186+ if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
 187+ throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP http requests to work. '.
 188+ 'If possible, curl should be used instead. See http://php.net/curl.' );
 189+ }
 190+ return new PhpHttpRequest( $url, $options );
216191 default:
217 - throw new MWException( 'The setting of $wgHTTPEngine is not valid.' );
 192+ throw new MWException( __METHOD__.': The setting of $wgHTTPEngine is not valid.' );
218193 }
219194 }
220195
 196+ /**
 197+ * Get the body, or content, of the response to the request
 198+ * @return string
 199+ */
221200 public function getContent() {
222201 return $this->content;
223202 }
224203
225 - public function initRequest() { }
226 - public function proxySetup() { }
227 - public function setReferrer( $url ) { }
228 - public function setCallback( $cb ) { }
229 - public function read( $fh, $content ) { }
230 - public function getCode() { }
231 - public function execute() { }
232 -}
 204+ /**
 205+ * Take care of setting up the proxy
 206+ * (override in subclass)
 207+ * @return string
 208+ */
 209+ public function proxySetup() {
 210+ global $wgHTTPProxy;
233211
234 -/**
235 - * HttpRequest implemented using internal curl compiled into PHP
236 - */
237 -class CurlHttpRequest extends HttpRequest {
238 - protected $curlHandle;
239 - protected $curlCBSet;
240212
241 - public function initRequest() {
242 - $this->curlHandle = curl_init( $this->url );
 213+ if ( $this->proxy ) {
 214+ return;
 215+ }
 216+ if ( Http::isLocalURL( $this->url ) ) {
 217+ $this->proxy = 'http://localhost:80/';
 218+ } elseif ( $wgHTTPProxy ) {
 219+ $this->proxy = $wgHTTPProxy ;
 220+ }
243221 }
244222
245 - public function proxySetup() {
246 - global $wgHTTPProxy;
 223+ /**
 224+ * Set the refererer header
 225+ */
 226+ public function setReferer( $url ) {
 227+ $this->setHeader('Referer', $url);
 228+ }
247229
248 - if ( is_string( $this->proxy ) ) {
249 - curl_setopt( $this->curlHandle, CURLOPT_PROXY, $this->proxy );
250 - } else if ( Http::isLocalURL( $this->url ) ) { /* Not sure this makes any sense. */
251 - curl_setopt( $this->curlHandle, CURLOPT_PROXY, 'localhost:80' );
252 - } else if ( $wgHTTPProxy ) {
253 - curl_setopt( $this->curlHandle, CURLOPT_PROXY, $wgHTTPProxy );
 230+ /**
 231+ * Set the user agent
 232+ */
 233+ public function setUserAgent( $UA ) {
 234+ $this->setHeader('User-Agent', $UA);
 235+ }
 236+
 237+ /**
 238+ * Set an arbitrary header
 239+ */
 240+ public function setHeader($name, $value) {
 241+ // I feel like I should normalize the case here...
 242+ $this->reqHeaders[$name] = $value;
 243+ }
 244+
 245+ /**
 246+ * Get an array of the headers
 247+ */
 248+ public function getHeaderList() {
 249+ $list = array();
 250+
 251+ foreach($this->reqHeaders as $name => $value) {
 252+ $list[] = "$name: $value";
254253 }
 254+ return $list;
255255 }
256256
257 - public function setCallback( $cb ) {
258 - if ( !$this->curlCBSet ) {
259 - $this->curlCBSet = true;
260 - curl_setopt( $this->curlHandle, CURLOPT_WRITEFUNCTION, $cb );
 257+ /**
 258+ * Set the callback
 259+ * @param $callback callback
 260+ */
 261+ public function setCallback( $callback ) {
 262+ $this->callback = $callback;
 263+ }
 264+
 265+ /**
 266+ * A generic callback to read in the response from a remote server
 267+ * @param $fh handle
 268+ * @param $content string
 269+ */
 270+ public function read( $fh, $content ) {
 271+ $this->content .= $content;
 272+ return strlen( $content );
 273+ }
 274+
 275+ /**
 276+ * Take care of whatever is necessary to perform the URI request.
 277+ * @return Status
 278+ */
 279+ public function execute() {
 280+ global $wgTitle;
 281+
 282+ if( strtoupper($this->method) == "HEAD" ) {
 283+ $this->headersOnly = true;
261284 }
 285+
 286+ if ( is_array( $this->postData ) ) {
 287+ $this->postData = wfArrayToCGI( $this->postData );
 288+ }
 289+
 290+ if ( is_object( $wgTitle ) && !isset($this->reqHeaders['Referer']) ) {
 291+ $this->setReferer( $wgTitle->getFullURL() );
 292+ }
 293+
 294+ if ( !$this->noProxy ) {
 295+ $this->proxySetup();
 296+ }
 297+
 298+ if ( !$this->callback ) {
 299+ $this->setCallback( array( $this, 'read' ) );
 300+ }
 301+
 302+ if ( !isset($this->reqHeaders['User-Agent']) ) {
 303+ $this->setUserAgent(Http::userAgent());
 304+ }
262305 }
 306+}
263307
 308+/**
 309+ * HttpRequest implemented using internal curl compiled into PHP
 310+ */
 311+class CurlHttpRequest extends HttpRequest {
 312+ protected $curlOptions = array();
 313+
264314 public function execute() {
 315+ parent::execute();
265316 if ( !$this->status->isOK() ) {
266317 return $this->status;
267318 }
268319
269 - $this->setCallback( array( $this, 'read' ) );
 320+ // A lot of the action up front should probably be in
 321+ // set* methods, but we'll leave that for another time.
270322
271 - curl_setopt( $this->curlHandle, CURLOPT_TIMEOUT, $this->timeout );
272 - curl_setopt( $this->curlHandle, CURLOPT_USERAGENT, Http::userAgent() );
273 - curl_setopt( $this->curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
 323+ $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
 324+ $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
 325+ $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
 326+ $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
274327
 328+ /* not sure these two are actually necessary */
 329+ if(isset($this->reqHeaders['Referer'])) {
 330+ $this->curlOptions[CURLOPT_REFERER] = $this->reqHeaders['Referer'];
 331+ }
 332+ $this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
 333+
275334 if ( $this->sslVerifyHost ) {
276 - curl_setopt( $this->curlHandle, CURLOPT_SSL_VERIFYHOST, $this->sslVerifyHost );
 335+ $this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost;
277336 }
278337
279338 if ( $this->caInfo ) {
280 - curl_setopt( $this->curlHandle, CURLOPT_CAINFO, $this->caInfo );
 339+ $this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
281340 }
282341
283342 if ( $this->headersOnly ) {
284 - curl_setopt( $this->curlHandle, CURLOPT_NOBODY, true );
285 - curl_setopt( $this->curlHandle, CURLOPT_HEADER, true );
 343+ $this->curlOptions[CURLOPT_NOBODY] = true;
 344+ $this->curlOptions[CURLOPT_HEADER] = true;
286345 } elseif ( $this->method == 'POST' ) {
287 - curl_setopt( $this->curlHandle, CURLOPT_POST, true );
288 - curl_setopt( $this->curlHandle, CURLOPT_POSTFIELDS, $this->postdata );
 346+ $this->curlOptions[CURLOPT_POST] = true;
 347+ $this->curlOptions[CURLOPT_POSTFIELDS] = $this->postData;
289348 // Suppress 'Expect: 100-continue' header, as some servers
290349 // will reject it with a 417 and Curl won't auto retry
291350 // with HTTP 1.0 fallback
292 - curl_setopt( $this->curlHandle, CURLOPT_HTTPHEADER, array( 'Expect:' ) );
 351+ $this->reqHeaders['Expect'] = '';
293352 } else {
294 - curl_setopt( $this->curlHandle, CURLOPT_CUSTOMREQUEST, $this->method );
 353+ $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
295354 }
296355
297 - try {
298 - if ( false === curl_exec( $this->curlHandle ) ) {
299 - $this->status->fatal( 'Error sending request (#$1): $2',
300 - curl_errno( $this->curlHandle ),
301 - curl_error( $this->curlHandle ) );
302 - }
303 - } catch ( Exception $e ) {
304 - $errno = curl_errno( $this->curlHandle );
305 - if ( $errno != CURLE_OK ) {
306 - $errstr = curl_error( $this->curlHandle );
307 - $this->status->fatal( 'CURL error code $1: $2', $errno, $errstr );
308 - }
 356+ $this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
 357+
 358+ $curlHandle = curl_init( $this->url );
 359+ curl_setopt_array( $curlHandle, $this->curlOptions );
 360+
 361+ if ( false === curl_exec( $curlHandle ) ) {
 362+ // re-using already translated error messages
 363+ $this->status->fatal( 'upload-curl-error'.curl_errno( $curlHandle ).'-text' );
309364 }
310365
311 - curl_close( $this->curlHandle );
 366+ curl_close( $curlHandle );
312367
313368 return $this->status;
314369 }
315 -
316 - public function read( $curlH, $content ) {
317 - $this->content .= $content;
318 - return strlen( $content );
319 - }
320 -
321 - public function getCode() {
322 - # Don't return truncated output
323 - $code = curl_getinfo( $this->curlHandle, CURLINFO_HTTP_CODE );
324 - if ( $code < 400 ) {
325 - $this->status->setResult( true, $code );
326 - } else {
327 - $this->status->setResult( false, $code );
328 - }
329 - }
330370 }
331371
332372 class PhpHttpRequest extends HttpRequest {
333 - private $reqHeaders;
334 - private $callback;
335373 private $fh;
336374
337 - public function initRequest() {
338 - $this->setCallback( array( $this, 'read' ) );
339 -
340 - $this->reqHeaders[] = "User-Agent: " . Http::userAgent();
341 - $this->reqHeaders[] = "Accept: */*";
342 - if ( $this->method == 'POST' ) {
343 - // Required for HTTP 1.0 POSTs
344 - $this->reqHeaders[] = "Content-Length: " . strlen( $this->postdata );
345 - $this->reqHeaders[] = "Content-type: application/x-www-form-urlencoded";
346 - }
347 -
348 - if ( $this->parsed_url['scheme'] != 'http' ) {
349 - $this->status->fatal( "Only http:// is supported currently." );
350 - }
351 - }
352 -
353375 protected function urlToTcp( $url ) {
354 - $parsed_url = parse_url( $url );
 376+ $parsedUrl = parse_url( $url );
355377
356 - return 'tcp://' . $parsed_url['host'] . ':' . $parsed_url['port'];
 378+ return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
357379 }
358380
359 - public function proxySetup() {
360 - global $wgHTTPProxy;
 381+ public function execute() {
 382+ if ( $this->parsedUrl['scheme'] != 'http' ) {
 383+ $this->status->fatal( 'http-invalid-scheme', $this->parsedURL['scheme'] );
 384+ }
361385
362 - if ( Http::isLocalURL( $this->url ) ) {
363 - $this->proxy = 'http://localhost:80/';
364 - } elseif ( $wgHTTPProxy ) {
365 - $this->proxy = $wgHTTPProxy ;
 386+ parent::execute();
 387+ if ( !$this->status->isOK() ) {
 388+ return $this->status;
366389 }
367 - }
368390
369 - public function setReferrer( $url ) {
370 - $this->reqHeaders[] = "Referer: $url";
371 - }
 391+ // A lot of the action up front should probably be in
 392+ // set* methods, but we'll leave that for another time.
372393
373 - public function setCallback( $cb ) {
374 - $this->callback = $cb;
375 - }
376 -
377 - public function read( $fh, $contents ) {
378 - if ( $this->headersOnly ) {
379 - return false;
 394+ $this->reqHeaders['Accept'] = "*/*";
 395+ if ( $this->method == 'POST' ) {
 396+ // Required for HTTP 1.0 POSTs
 397+ $this->reqHeaders['Content-Length'] = strlen( $this->postData );
 398+ $this->reqHeaders['Content-type'] = "application/x-www-form-urlencoded";
380399 }
381 - $this->content .= $contents;
382400
383 - return strlen( $contents );
384 - }
385 -
386 - public function execute() {
387 - if ( !$this->status->isOK() ) {
388 - return $this->status;
 401+ $options = array();
 402+ if ( $this->proxy && !$this->noProxy ) {
 403+ $options['proxy'] = $this->urlToTCP( $this->proxy );
 404+ $options['request_fulluri'] = true;
389405 }
390406
391 - $opts = array();
392 - if ( $this->proxy && !$this->no_proxy ) {
393 - $opts['proxy'] = $this->urlToTCP( $this->proxy );
394 - $opts['request_fulluri'] = true;
395 - }
396 -
397 - $opts['method'] = $this->method;
398 - $opts['timeout'] = $this->timeout;
399 - $opts['header'] = implode( "\r\n", $this->reqHeaders );
 407+ $options['method'] = $this->method;
 408+ $options['timeout'] = $this->timeout;
 409+ $options['header'] = implode("\r\n", $this->getHeaderList());
400410 // FOR NOW: Force everyone to HTTP 1.0
401411 /* if ( version_compare( "5.3.0", phpversion(), ">" ) ) { */
402 - $opts['protocol_version'] = "1.0";
 412+ $options['protocol_version'] = "1.0";
403413 /* } else { */
404 - /* $opts['protocol_version'] = "1.1"; */
 414+ /* $options['protocol_version'] = "1.1"; */
405415 /* } */
406416
407 - if ( $this->postdata ) {
408 - $opts['content'] = $this->postdata;
 417+ if ( $this->postData ) {
 418+ $options['content'] = $this->postData;
409419 }
410420
411 - $context = stream_context_create( array( 'http' => $opts ) );
 421+ $context = stream_context_create( array( 'http' => $options ) );
412422 try {
413423 $this->fh = fopen( $this->url, "r", false, $context );
414424 } catch ( Exception $e ) {
415 - $this->status->fatal( $e->getMessage() );
 425+ $this->status->fatal( $e->getMessage() ); /* need some l10n help */
416426 return $this->status;
417427 }
418428
419429 $result = stream_get_meta_data( $this->fh );
420430 if ( $result['timed_out'] ) {
421 - $this->status->error( 'The request timed out' );
 431+ $this->status->fatal( 'http-timed-out', $this->url );
 432+ return $this->status;
422433 }
423434
424435 $this->headers = $result['wrapper_data'];
@@ -425,7 +436,10 @@
426437 $end = false;
427438 while ( !$end ) {
428439 $contents = fread( $this->fh, 8192 );
429 - $size = call_user_func_array( $this->callback, array( $this->fh, $contents ) );
 440+ $size = 0;
 441+ if ( $contents ) {
 442+ $size = call_user_func_array( $this->callback, array( $this->fh, $contents ) );
 443+ }
430444 $end = ( $size == 0 ) || feof( $this->fh );
431445 }
432446 fclose( $this->fh );

Follow-up revisions

RevisionCommit summaryAuthorDate
r61358Messages for r61357mah07:52, 22 January 2010
r61655follow up r61357...mah07:25, 29 January 2010
r62563Fix call to nonexistent function from r61357tstarling01:06, 16 February 2010
r101229Disable proxy for local URLs instead of using a local proxy (which might not ...churchofemacs23:02, 28 October 2011

Comments

#Comment by Tim Starling (talk | contribs)   07:29, 28 January 2010

Thanks for that, it's a big improvement.

parse_url() still has no wfSuppressWarnings()/wfRestoreWarnings(). Also you don't check to see if it returns false, so it will produce lots of PHP warnings in that case in subsequent lines.

 		} catch ( Exception $e ) {
+			$this->status->fatal( $e->getMessage() ); /* need some l10n help */

If there actually was an exception, you could display the message to the user with something like:

$this->status->fatal( 'http-fopen-exception', $e->getMessage() );

and in MessagesEn.php

'http-fopen-exception' => 'Error opening HTTP stream: $1',

However, the issue won't come up, because fopen() doesn't throw exceptions. This is PHP not Python. It'll output a few warnings and then return false.

$ php -d allow_url_fopen=1 eval.php
> $f = fopen('http://blah/','r')
Warning: fopen(): php_network_getaddresses: getaddrinfo failed: Name or service not known in .../maintenance/eval.php(60) : eval()'d code on line 1
Warning: fopen(http://blah/): failed to open stream: php_network_getaddresses: getaddrinfo failed: Name or service not known in .../maintenance/eval.php(60) : eval()'d code on line 1

> var_dump($f)
bool(false)

You should remove the try/catch block and have it check if $this->fh is false instead. If it is, you can return a generic error message. Or if you're feeling really ambitious, you can try to trap the PHP warnings and return them to the user, like what Database::installErrorHandler() and Database::restoreErrorHandler() do.

For the fread() loop, I was more thinking of something like:

while ( !feof( $this->fh ) ) {
    $buf = fread( $this->fh, 8192 );
    if ( $buf === false ) {
        $this->status->fatal( 'http-read-error' );
        break;
    }
    if ( strlen( $buf ) ) {
        call_user_func( $this->callback, $this->fh, $buf );
    }
}

You can't use "if ( $contents )" because that will omit strings like "00000".

Status & tagging log