MediaWiki  REL1_23
HttpFunctions.php
Go to the documentation of this file.
00001 <?php
00032 class Http {
00033     static public $httpEngine = false;
00034 
00060     public static function request( $method, $url, $options = array() ) {
00061         wfDebug( "HTTP: $method: $url\n" );
00062         wfProfileIn( __METHOD__ . "-$method" );
00063 
00064         $options['method'] = strtoupper( $method );
00065 
00066         if ( !isset( $options['timeout'] ) ) {
00067             $options['timeout'] = 'default';
00068         }
00069         if ( !isset( $options['connectTimeout'] ) ) {
00070             $options['connectTimeout'] = 'default';
00071         }
00072 
00073         $req = MWHttpRequest::factory( $url, $options );
00074         $status = $req->execute();
00075 
00076         $content = false;
00077         if ( $status->isOK() ) {
00078             $content = $req->getContent();
00079         }
00080         wfProfileOut( __METHOD__ . "-$method" );
00081         return $content;
00082     }
00083 
00093     public static function get( $url, $timeout = 'default', $options = array() ) {
00094         $options['timeout'] = $timeout;
00095         return Http::request( 'GET', $url, $options );
00096     }
00097 
00106     public static function post( $url, $options = array() ) {
00107         return Http::request( 'POST', $url, $options );
00108     }
00109 
00116     public static function isLocalURL( $url ) {
00117         global $wgCommandLineMode, $wgConf;
00118 
00119         if ( $wgCommandLineMode ) {
00120             return false;
00121         }
00122 
00123         // Extract host part
00124         $matches = array();
00125         if ( preg_match( '!^http://([\w.-]+)[/:].*$!', $url, $matches ) ) {
00126             $host = $matches[1];
00127             // Split up dotwise
00128             $domainParts = explode( '.', $host );
00129             // Check if this domain or any superdomain is listed in $wgConf as a local virtual host
00130             $domainParts = array_reverse( $domainParts );
00131 
00132             $domain = '';
00133             $countParts = count( $domainParts );
00134             for ( $i = 0; $i < $countParts; $i++ ) {
00135                 $domainPart = $domainParts[$i];
00136                 if ( $i == 0 ) {
00137                     $domain = $domainPart;
00138                 } else {
00139                     $domain = $domainPart . '.' . $domain;
00140                 }
00141 
00142                 if ( $wgConf->isLocalVHost( $domain ) ) {
00143                     return true;
00144                 }
00145             }
00146         }
00147 
00148         return false;
00149     }
00150 
00155     public static function userAgent() {
00156         global $wgVersion;
00157         return "MediaWiki/$wgVersion";
00158     }
00159 
00172     public static function isValidURI( $uri ) {
00173         return preg_match(
00174             '/^https?:\/\/[^\/\s]\S*$/D',
00175             $uri
00176         );
00177     }
00178 }
00179 
00187 class MWHttpRequest {
00188     const SUPPORTS_FILE_POSTS = false;
00189 
00190     protected $content;
00191     protected $timeout = 'default';
00192     protected $headersOnly = null;
00193     protected $postData = null;
00194     protected $proxy = null;
00195     protected $noProxy = false;
00196     protected $sslVerifyHost = true;
00197     protected $sslVerifyCert = true;
00198     protected $caInfo = null;
00199     protected $method = "GET";
00200     protected $reqHeaders = array();
00201     protected $url;
00202     protected $parsedUrl;
00203     protected $callback;
00204     protected $maxRedirects = 5;
00205     protected $followRedirects = false;
00206 
00210     protected $cookieJar;
00211 
00212     protected $headerList = array();
00213     protected $respVersion = "0.9";
00214     protected $respStatus = "200 Ok";
00215     protected $respHeaders = array();
00216 
00217     public $status;
00218 
00223     protected function __construct( $url, $options = array() ) {
00224         global $wgHTTPTimeout, $wgHTTPConnectTimeout;
00225 
00226         $this->url = wfExpandUrl( $url, PROTO_HTTP );
00227         $this->parsedUrl = wfParseUrl( $this->url );
00228 
00229         if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
00230             $this->status = Status::newFatal( 'http-invalid-url' );
00231         } else {
00232             $this->status = Status::newGood( 100 ); // continue
00233         }
00234 
00235         if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
00236             $this->timeout = $options['timeout'];
00237         } else {
00238             $this->timeout = $wgHTTPTimeout;
00239         }
00240         if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) {
00241             $this->connectTimeout = $options['connectTimeout'];
00242         } else {
00243             $this->connectTimeout = $wgHTTPConnectTimeout;
00244         }
00245         if ( isset( $options['userAgent'] ) ) {
00246             $this->setUserAgent( $options['userAgent'] );
00247         }
00248 
00249         $members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
00250                 "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" );
00251 
00252         foreach ( $members as $o ) {
00253             if ( isset( $options[$o] ) ) {
00254                 // ensure that MWHttpRequest::method is always
00255                 // uppercased. Bug 36137
00256                 if ( $o == 'method' ) {
00257                     $options[$o] = strtoupper( $options[$o] );
00258                 }
00259                 $this->$o = $options[$o];
00260             }
00261         }
00262 
00263         if ( $this->noProxy ) {
00264             $this->proxy = ''; // noProxy takes precedence
00265         }
00266     }
00267 
00273     public static function canMakeRequests() {
00274         return function_exists( 'curl_init' ) || wfIniGetBool( 'allow_url_fopen' );
00275     }
00276 
00285     public static function factory( $url, $options = null ) {
00286         if ( !Http::$httpEngine ) {
00287             Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
00288         } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
00289             throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
00290                 ' Http::$httpEngine is set to "curl"' );
00291         }
00292 
00293         switch ( Http::$httpEngine ) {
00294             case 'curl':
00295                 return new CurlHttpRequest( $url, $options );
00296             case 'php':
00297                 if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
00298                     throw new MWException( __METHOD__ . ': allow_url_fopen ' .
00299                         'needs to be enabled for pure PHP http requests to ' .
00300                         'work. If possible, curl should be used instead. See ' .
00301                         'http://php.net/curl.'
00302                     );
00303                 }
00304                 return new PhpHttpRequest( $url, $options );
00305             default:
00306                 throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
00307         }
00308     }
00309 
00315     public function getContent() {
00316         return $this->content;
00317     }
00318 
00325     public function setData( $args ) {
00326         $this->postData = $args;
00327     }
00328 
00334     public function proxySetup() {
00335         global $wgHTTPProxy;
00336 
00337         // If there is an explicit proxy set and proxies are not disabled, then use it
00338         if ( $this->proxy && !$this->noProxy ) {
00339             return;
00340         }
00341 
00342         // Otherwise, fallback to $wgHTTPProxy/http_proxy (when set) if this is not a machine
00343         // local URL and proxies are not disabled
00344         if ( Http::isLocalURL( $this->url ) || $this->noProxy ) {
00345             $this->proxy = '';
00346         } elseif ( $wgHTTPProxy ) {
00347             $this->proxy = $wgHTTPProxy;
00348         } elseif ( getenv( "http_proxy" ) ) {
00349             $this->proxy = getenv( "http_proxy" );
00350         }
00351     }
00352 
00357     public function setUserAgent( $UA ) {
00358         $this->setHeader( 'User-Agent', $UA );
00359     }
00360 
00366     public function setHeader( $name, $value ) {
00367         // I feel like I should normalize the case here...
00368         $this->reqHeaders[$name] = $value;
00369     }
00370 
00375     public function getHeaderList() {
00376         $list = array();
00377 
00378         if ( $this->cookieJar ) {
00379             $this->reqHeaders['Cookie'] =
00380                 $this->cookieJar->serializeToHttpRequest(
00381                     $this->parsedUrl['path'],
00382                     $this->parsedUrl['host']
00383                 );
00384         }
00385 
00386         foreach ( $this->reqHeaders as $name => $value ) {
00387             $list[] = "$name: $value";
00388         }
00389 
00390         return $list;
00391     }
00392 
00411     public function setCallback( $callback ) {
00412         if ( !is_callable( $callback ) ) {
00413             throw new MWException( 'Invalid MwHttpRequest callback' );
00414         }
00415         $this->callback = $callback;
00416     }
00417 
00426     public function read( $fh, $content ) {
00427         $this->content .= $content;
00428         return strlen( $content );
00429     }
00430 
00436     public function execute() {
00437         wfProfileIn( __METHOD__ );
00438 
00439         $this->content = "";
00440 
00441         if ( strtoupper( $this->method ) == "HEAD" ) {
00442             $this->headersOnly = true;
00443         }
00444 
00445         $this->proxySetup(); // set up any proxy as needed
00446 
00447         if ( !$this->callback ) {
00448             $this->setCallback( array( $this, 'read' ) );
00449         }
00450 
00451         if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
00452             $this->setUserAgent( Http::userAgent() );
00453         }
00454 
00455         wfProfileOut( __METHOD__ );
00456     }
00457 
00463     protected function parseHeader() {
00464         wfProfileIn( __METHOD__ );
00465 
00466         $lastname = "";
00467 
00468         foreach ( $this->headerList as $header ) {
00469             if ( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
00470                 $this->respVersion = $match[1];
00471                 $this->respStatus = $match[2];
00472             } elseif ( preg_match( "#^[ \t]#", $header ) ) {
00473                 $last = count( $this->respHeaders[$lastname] ) - 1;
00474                 $this->respHeaders[$lastname][$last] .= "\r\n$header";
00475             } elseif ( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
00476                 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
00477                 $lastname = strtolower( $match[1] );
00478             }
00479         }
00480 
00481         $this->parseCookies();
00482 
00483         wfProfileOut( __METHOD__ );
00484     }
00485 
00494     protected function setStatus() {
00495         if ( !$this->respHeaders ) {
00496             $this->parseHeader();
00497         }
00498 
00499         if ( (int)$this->respStatus > 399 ) {
00500             list( $code, $message ) = explode( " ", $this->respStatus, 2 );
00501             $this->status->fatal( "http-bad-status", $code, $message );
00502         }
00503     }
00504 
00512     public function getStatus() {
00513         if ( !$this->respHeaders ) {
00514             $this->parseHeader();
00515         }
00516 
00517         return (int)$this->respStatus;
00518     }
00519 
00525     public function isRedirect() {
00526         if ( !$this->respHeaders ) {
00527             $this->parseHeader();
00528         }
00529 
00530         $status = (int)$this->respStatus;
00531 
00532         if ( $status >= 300 && $status <= 303 ) {
00533             return true;
00534         }
00535 
00536         return false;
00537     }
00538 
00547     public function getResponseHeaders() {
00548         if ( !$this->respHeaders ) {
00549             $this->parseHeader();
00550         }
00551 
00552         return $this->respHeaders;
00553     }
00554 
00561     public function getResponseHeader( $header ) {
00562         if ( !$this->respHeaders ) {
00563             $this->parseHeader();
00564         }
00565 
00566         if ( isset( $this->respHeaders[strtolower( $header )] ) ) {
00567             $v = $this->respHeaders[strtolower( $header )];
00568             return $v[count( $v ) - 1];
00569         }
00570 
00571         return null;
00572     }
00573 
00579     public function setCookieJar( $jar ) {
00580         $this->cookieJar = $jar;
00581     }
00582 
00588     public function getCookieJar() {
00589         if ( !$this->respHeaders ) {
00590             $this->parseHeader();
00591         }
00592 
00593         return $this->cookieJar;
00594     }
00595 
00605     public function setCookie( $name, $value = null, $attr = null ) {
00606         if ( !$this->cookieJar ) {
00607             $this->cookieJar = new CookieJar;
00608         }
00609 
00610         $this->cookieJar->setCookie( $name, $value, $attr );
00611     }
00612 
00616     protected function parseCookies() {
00617         wfProfileIn( __METHOD__ );
00618 
00619         if ( !$this->cookieJar ) {
00620             $this->cookieJar = new CookieJar;
00621         }
00622 
00623         if ( isset( $this->respHeaders['set-cookie'] ) ) {
00624             $url = parse_url( $this->getFinalUrl() );
00625             foreach ( $this->respHeaders['set-cookie'] as $cookie ) {
00626                 $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
00627             }
00628         }
00629 
00630         wfProfileOut( __METHOD__ );
00631     }
00632 
00649     public function getFinalUrl() {
00650         $headers = $this->getResponseHeaders();
00651 
00652         //return full url (fix for incorrect but handled relative location)
00653         if ( isset( $headers['location'] ) ) {
00654             $locations = $headers['location'];
00655             $domain = '';
00656             $foundRelativeURI = false;
00657             $countLocations = count( $locations );
00658 
00659             for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
00660                 $url = parse_url( $locations[$i] );
00661 
00662                 if ( isset( $url['host'] ) ) {
00663                     $domain = $url['scheme'] . '://' . $url['host'];
00664                     break; //found correct URI (with host)
00665                 } else {
00666                     $foundRelativeURI = true;
00667                 }
00668             }
00669 
00670             if ( $foundRelativeURI ) {
00671                 if ( $domain ) {
00672                     return $domain . $locations[$countLocations - 1];
00673                 } else {
00674                     $url = parse_url( $this->url );
00675                     if ( isset( $url['host'] ) ) {
00676                         return $url['scheme'] . '://' . $url['host'] .
00677                             $locations[$countLocations - 1];
00678                     }
00679                 }
00680             } else {
00681                 return $locations[$countLocations - 1];
00682             }
00683         }
00684 
00685         return $this->url;
00686     }
00687 
00693     public function canFollowRedirects() {
00694         return true;
00695     }
00696 }
00697 
00701 class CurlHttpRequest extends MWHttpRequest {
00702     const SUPPORTS_FILE_POSTS = true;
00703 
00704     protected $curlOptions = array();
00705     protected $headerText = "";
00706 
00712     protected function readHeader( $fh, $content ) {
00713         $this->headerText .= $content;
00714         return strlen( $content );
00715     }
00716 
00717     public function execute() {
00718         wfProfileIn( __METHOD__ );
00719 
00720         parent::execute();
00721 
00722         if ( !$this->status->isOK() ) {
00723             wfProfileOut( __METHOD__ );
00724             return $this->status;
00725         }
00726 
00727         $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
00728         $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
00729 
00730         // Only supported in curl >= 7.16.2
00731         if ( defined( 'CURLOPT_CONNECTTIMEOUT_MS' ) ) {
00732             $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000;
00733         }
00734 
00735         $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
00736         $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
00737         $this->curlOptions[CURLOPT_HEADERFUNCTION] = array( $this, "readHeader" );
00738         $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
00739         $this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
00740 
00741         $this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
00742 
00743         $this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost ? 2 : 0;
00744         $this->curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->sslVerifyCert;
00745 
00746         if ( $this->caInfo ) {
00747             $this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
00748         }
00749 
00750         if ( $this->headersOnly ) {
00751             $this->curlOptions[CURLOPT_NOBODY] = true;
00752             $this->curlOptions[CURLOPT_HEADER] = true;
00753         } elseif ( $this->method == 'POST' ) {
00754             $this->curlOptions[CURLOPT_POST] = true;
00755             $this->curlOptions[CURLOPT_POSTFIELDS] = $this->postData;
00756             // Suppress 'Expect: 100-continue' header, as some servers
00757             // will reject it with a 417 and Curl won't auto retry
00758             // with HTTP 1.0 fallback
00759             $this->reqHeaders['Expect'] = '';
00760         } else {
00761             $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
00762         }
00763 
00764         $this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
00765 
00766         $curlHandle = curl_init( $this->url );
00767 
00768         if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
00769             wfProfileOut( __METHOD__ );
00770             throw new MWException( "Error setting curl options." );
00771         }
00772 
00773         if ( $this->followRedirects && $this->canFollowRedirects() ) {
00774             wfSuppressWarnings();
00775             if ( ! curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
00776                 wfDebug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
00777                     "Probably safe_mode or open_basedir is set.\n" );
00778                 // Continue the processing. If it were in curl_setopt_array,
00779                 // processing would have halted on its entry
00780             }
00781             wfRestoreWarnings();
00782         }
00783 
00784         $curlRes = curl_exec( $curlHandle );
00785         if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
00786             $this->status->fatal( 'http-timed-out', $this->url );
00787         } elseif ( $curlRes === false ) {
00788             $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
00789         } else {
00790             $this->headerList = explode( "\r\n", $this->headerText );
00791         }
00792 
00793         curl_close( $curlHandle );
00794 
00795         $this->parseHeader();
00796         $this->setStatus();
00797 
00798         wfProfileOut( __METHOD__ );
00799 
00800         return $this->status;
00801     }
00802 
00806     public function canFollowRedirects() {
00807         if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) {
00808             wfDebug( "Cannot follow redirects in safe mode\n" );
00809             return false;
00810         }
00811 
00812         if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) {
00813             wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
00814             return false;
00815         }
00816 
00817         return true;
00818     }
00819 }
00820 
00821 class PhpHttpRequest extends MWHttpRequest {
00822 
00827     protected function urlToTcp( $url ) {
00828         $parsedUrl = parse_url( $url );
00829 
00830         return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
00831     }
00832 
00833     public function execute() {
00834         wfProfileIn( __METHOD__ );
00835 
00836         parent::execute();
00837 
00838         if ( is_array( $this->postData ) ) {
00839             $this->postData = wfArrayToCgi( $this->postData );
00840         }
00841 
00842         if ( $this->parsedUrl['scheme'] != 'http'
00843             && $this->parsedUrl['scheme'] != 'https' ) {
00844             $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
00845         }
00846 
00847         $this->reqHeaders['Accept'] = "*/*";
00848         $this->reqHeaders['Connection'] = 'Close';
00849         if ( $this->method == 'POST' ) {
00850             // Required for HTTP 1.0 POSTs
00851             $this->reqHeaders['Content-Length'] = strlen( $this->postData );
00852             if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
00853                 $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
00854             }
00855         }
00856 
00857         // Set up PHP stream context
00858         $options = array(
00859             'http' => array(
00860                 'method' => $this->method,
00861                 'header' => implode( "\r\n", $this->getHeaderList() ),
00862                 'protocol_version' => '1.1',
00863                 'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0,
00864                 'ignore_errors' => true,
00865                 'timeout' => $this->timeout,
00866                 // Curl options in case curlwrappers are installed
00867                 'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0,
00868                 'curl_verify_ssl_peer' => $this->sslVerifyCert,
00869             ),
00870             'ssl' => array(
00871                 'verify_peer' => $this->sslVerifyCert,
00872                 'SNI_enabled' => true,
00873             ),
00874         );
00875 
00876         if ( $this->proxy ) {
00877             $options['http']['proxy'] = $this->urlToTCP( $this->proxy );
00878             $options['http']['request_fulluri'] = true;
00879         }
00880 
00881         if ( $this->postData ) {
00882             $options['http']['content'] = $this->postData;
00883         }
00884 
00885         if ( $this->sslVerifyHost ) {
00886             $options['ssl']['CN_match'] = $this->parsedUrl['host'];
00887         }
00888 
00889         if ( is_dir( $this->caInfo ) ) {
00890             $options['ssl']['capath'] = $this->caInfo;
00891         } elseif ( is_file( $this->caInfo ) ) {
00892             $options['ssl']['cafile'] = $this->caInfo;
00893         } elseif ( $this->caInfo ) {
00894             throw new MWException( "Invalid CA info passed: {$this->caInfo}" );
00895         }
00896 
00897         $context = stream_context_create( $options );
00898 
00899         $this->headerList = array();
00900         $reqCount = 0;
00901         $url = $this->url;
00902 
00903         $result = array();
00904 
00905         do {
00906             $reqCount++;
00907             wfSuppressWarnings();
00908             $fh = fopen( $url, "r", false, $context );
00909             wfRestoreWarnings();
00910 
00911             if ( !$fh ) {
00912                 break;
00913             }
00914 
00915             $result = stream_get_meta_data( $fh );
00916             $this->headerList = $result['wrapper_data'];
00917             $this->parseHeader();
00918 
00919             if ( !$this->followRedirects ) {
00920                 break;
00921             }
00922 
00923             # Handle manual redirection
00924             if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
00925                 break;
00926             }
00927             # Check security of URL
00928             $url = $this->getResponseHeader( "Location" );
00929 
00930             if ( !Http::isValidURI( $url ) ) {
00931                 wfDebug( __METHOD__ . ": insecure redirection\n" );
00932                 break;
00933             }
00934         } while ( true );
00935 
00936         $this->setStatus();
00937 
00938         if ( $fh === false ) {
00939             $this->status->fatal( 'http-request-error' );
00940             wfProfileOut( __METHOD__ );
00941             return $this->status;
00942         }
00943 
00944         if ( $result['timed_out'] ) {
00945             $this->status->fatal( 'http-timed-out', $this->url );
00946             wfProfileOut( __METHOD__ );
00947             return $this->status;
00948         }
00949 
00950         // If everything went OK, or we received some error code
00951         // get the response body content.
00952         if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
00953             while ( !feof( $fh ) ) {
00954                 $buf = fread( $fh, 8192 );
00955 
00956                 if ( $buf === false ) {
00957                     $this->status->fatal( 'http-read-error' );
00958                     break;
00959                 }
00960 
00961                 if ( strlen( $buf ) ) {
00962                     call_user_func( $this->callback, $fh, $buf );
00963                 }
00964             }
00965         }
00966         fclose( $fh );
00967 
00968         wfProfileOut( __METHOD__ );
00969 
00970         return $this->status;
00971     }
00972 }