MediaWiki  REL1_23
SpecialRunJobs.php
Go to the documentation of this file.
00001 <?php
00030 class SpecialRunJobs extends UnlistedSpecialPage {
00031     public function __construct() {
00032         parent::__construct( 'RunJobs' );
00033     }
00034 
00035     public function execute( $par = '' ) {
00036         $this->getOutput()->disable();
00037 
00038         if ( wfReadOnly() ) {
00039             header( "HTTP/1.0 423 Locked" );
00040             print 'Wiki is in read-only mode';
00041 
00042             return;
00043         } elseif ( !$this->getRequest()->wasPosted() ) {
00044             header( "HTTP/1.0 400 Bad Request" );
00045             print 'Request must be POSTed';
00046 
00047             return;
00048         }
00049 
00050         $optional = array( 'maxjobs' => 0 );
00051         $required = array_flip( array( 'title', 'tasks', 'signature', 'sigexpiry' ) );
00052 
00053         $params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional );
00054         $missing = array_diff_key( $required, $params );
00055         if ( count( $missing ) ) {
00056             header( "HTTP/1.0 400 Bad Request" );
00057             print 'Missing parameters: ' . implode( ', ', array_keys( $missing ) );
00058 
00059             return;
00060         }
00061 
00062         $squery = $params;
00063         unset( $squery['signature'] );
00064         $cSig = self::getQuerySignature( $squery ); // correct signature
00065         $rSig = $params['signature']; // provided signature
00066 
00067         // Constant-time signature verification
00068         // http://www.emerose.com/timing-attacks-explained
00069         // @todo: make a common method for this
00070         if ( !is_string( $rSig ) || strlen( $rSig ) !== strlen( $cSig ) ) {
00071             $verified = false;
00072         } else {
00073             $result = 0;
00074             for ( $i = 0; $i < strlen( $cSig ); $i++ ) {
00075                 $result |= ord( $cSig[$i] ) ^ ord( $rSig[$i] );
00076             }
00077             $verified = ( $result == 0 );
00078         }
00079         if ( !$verified || $params['sigexpiry'] < time() ) {
00080             header( "HTTP/1.0 400 Bad Request" );
00081             print 'Invalid or stale signature provided';
00082 
00083             return;
00084         }
00085 
00086         // Apply any default parameter values
00087         $params += $optional;
00088 
00089         // Client will usually disconnect before checking the response,
00090         // but it needs to know when it is safe to disconnect. Until this
00091         // reaches ignore_user_abort(), it is not safe as the jobs won't run.
00092         ignore_user_abort( true ); // jobs may take a bit of time
00093         header( "HTTP/1.0 202 Accepted" );
00094         ob_flush();
00095         flush();
00096         // Once the client receives this response, it can disconnect
00097 
00098         // Do all of the specified tasks...
00099         if ( in_array( 'jobs', explode( '|', $params['tasks'] ) ) ) {
00100             self::executeJobs( (int)$params['maxjobs'] );
00101         }
00102     }
00103 
00108     public static function getQuerySignature( array $query ) {
00109         global $wgSecretKey;
00110 
00111         ksort( $query ); // stable order
00112         return hash_hmac( 'sha1', wfArrayToCgi( $query ), $wgSecretKey );
00113     }
00114 
00123     public static function executeJobs( $maxJobs ) {
00124         $n = $maxJobs; // number of jobs to run
00125         if ( $n < 1 ) {
00126             return;
00127         }
00128         try {
00129             $group = JobQueueGroup::singleton();
00130             $count = $group->executeReadyPeriodicTasks();
00131             if ( $count > 0 ) {
00132                 wfDebugLog( 'jobqueue', "Executed $count periodic queue task(s)." );
00133             }
00134 
00135             do {
00136                 $job = $group->pop( JobQueueGroup::TYPE_DEFAULT, JobQueueGroup::USE_CACHE );
00137                 if ( $job ) {
00138                     $output = $job->toString() . "\n";
00139                     $t = -microtime( true );
00140                     wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
00141                     $success = $job->run();
00142                     wfProfileOut( __METHOD__ . '-' . get_class( $job ) );
00143                     $group->ack( $job ); // done
00144                     $t += microtime( true );
00145                     $t = round( $t * 1000 );
00146                     if ( $success === false ) {
00147                         $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
00148                     } else {
00149                         $output .= "Success, Time: $t ms\n";
00150                     }
00151                     wfDebugLog( 'jobqueue', $output );
00152                 }
00153             } while ( --$n && $job );
00154         } catch ( MWException $e ) {
00155             MWExceptionHandler::rollbackMasterChangesAndLog( $e );
00156             // We don't want exceptions thrown during job execution to
00157             // be reported to the user since the output is already sent.
00158             // Instead we just log them.
00159             MWExceptionHandler::logException( $e );
00160         }
00161     }
00162 }