dieUsage( 'Wiki is in read-only mode', 'read_only', 400 ); } $params = $this->extractRequestParams(); $squery = $this->getRequest()->getValues(); unset( $squery['signature'] ); $cSig = self::getQuerySignature( $squery ); $rSig = $params['signature']; // Time-insensitive signature verification if ( strlen( $rSig ) !== strlen( $cSig ) ) { $verified = false; } else { $result = 0; for ( $i = 0; $i < strlen( $cSig ); $i++ ) { $result |= ord( $cSig{$i} ) ^ ord( $rSig{$i} ); } $verified = ( $result == 0 ); } if ( !$verified || $params['sigexpiry'] < time() ) { $this->dieUsage( 'Invalid or stale signature provided', 'bad_signature', 400 ); } // Client will usually disconnect before checking the response, // but it needs to know when it is safe to disconnect. Until this // reaches ignore_user_abort(), it is not safe as the jobs won't run. ignore_user_abort( true ); // jobs may take a bit of time header( "HTTP/1.0 202 Accepted" ); ob_flush(); flush(); // Once the client receives this response, it can disconnect // Do all of the specified tasks... if ( in_array( 'jobs', $params['tasks'] ) ) { self::executeJobs( $params['maxjobs'] ); } } /** * @param array $query * @return string */ public static function getQuerySignature( array $query ) { global $wgSecretKey; ksort( $query ); // stable order return hash_hmac( 'sha1', wfArrayToCgi( $query ), $wgSecretKey ); } /** * Run jobs from the job queue * * @note: also called from Wiki.php * * @param integer $maxJobs Maximum number of jobs to run * @return void */ public static function executeJobs( $maxJobs ) { $n = $maxJobs; // number of jobs to run if ( $n < 1 ) { return; } try { $group = JobQueueGroup::singleton(); $count = $group->executeReadyPeriodicTasks(); if ( $count > 0 ) { wfDebugLog( 'jobqueue', "Executed $count periodic queue task(s)." ); } do { $job = $group->pop( JobQueueGroup::TYPE_DEFAULT, JobQueueGroup::USE_CACHE ); // job from any queue if ( $job ) { $output = $job->toString() . "\n"; $t = - microtime( true ); wfProfileIn( __METHOD__ . '-' . get_class( $job ) ); $success = $job->run(); wfProfileOut( __METHOD__ . '-' . get_class( $job ) ); $group->ack( $job ); // done $t += microtime( true ); $t = round( $t * 1000 ); if ( $success === false ) { $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n"; } else { $output .= "Success, Time: $t ms\n"; } wfDebugLog( 'jobqueue', $output ); } } while ( --$n && $job ); } catch ( MWException $e ) { // We don't want exceptions thrown during job execution to // be reported to the user since the output is already sent. // Instead we just log them. MWExceptionHandler::logException( $e ); } } public function mustBePosted() { return true; } public function getAllowedParams() { return array( 'tasks' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => array( 'jobs' ) ), 'maxjobs' => array( ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_DFLT => 0 ), 'signature' => array( ApiBase::PROP_TYPE => 'string', ), 'sigexpiry' => array( ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_DFLT => 0 // ~epoch ), ); } public function getParamDescription() { return array( 'tasks' => 'List of task types to perform', 'maxjobs' => 'Maximum number of jobs to run', 'signature' => 'HMAC Signature that signs the request', 'sigexpiry' => 'HMAC signature expiry as a UNIX timestamp' ); } public function getDescription() { return 'Perform periodic tasks or run jobs from the queue.'; } public function getExamples() { return array( 'api.php?action=runjobs&tasks=jobs&maxjobs=3' => 'Run up to 3 jobs from the queue', ); } }