Merge "Check validity and availability of usernames during signup via AJAX"
[lhc/web/wiklou.git] / includes / api / ApiRunJobs.php
1 <?php
2 /**
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 * @author Aaron Schulz
21 */
22
23 /**
24 * This is a simple class to handle action=runjobs and is only used internally
25 *
26 * @note: this does not requre "write mode" nor tokens due to the signature check
27 *
28 * @ingroup API
29 */
30 class ApiRunJobs extends ApiBase {
31 public function execute() {
32 if ( wfReadOnly() ) {
33 $this->dieUsage( 'Wiki is in read-only mode', 'read_only', 400 );
34 }
35
36 $params = $this->extractRequestParams();
37 $squery = $this->getRequest()->getValues();
38 unset( $squery['signature'] );
39 $cSig = self::getQuerySignature( $squery );
40 $rSig = $params['signature'];
41
42 // Time-insensitive signature verification
43 if ( strlen( $rSig ) !== strlen( $cSig ) ) {
44 $verified = false;
45 } else {
46 $result = 0;
47 for ( $i = 0; $i < strlen( $cSig ); $i++ ) {
48 $result |= ord( $cSig{$i} ) ^ ord( $rSig{$i} );
49 }
50 $verified = ( $result == 0 );
51 }
52
53 if ( !$verified || $params['sigexpiry'] < time() ) {
54 $this->dieUsage( 'Invalid or stale signature provided', 'bad_signature', 400 );
55 }
56
57 // Client will usually disconnect before checking the response,
58 // but it needs to know when it is safe to disconnect. Until this
59 // reaches ignore_user_abort(), it is not safe as the jobs won't run.
60 ignore_user_abort( true ); // jobs may take a bit of time
61 header( "HTTP/1.0 202 Accepted" );
62 ob_flush();
63 flush();
64 // Once the client receives this response, it can disconnect
65
66 // Do all of the specified tasks...
67 if ( in_array( 'jobs', $params['tasks'] ) ) {
68 self::executeJobs( $params['maxjobs'] );
69 }
70 }
71
72 /**
73 * @param array $query
74 * @return string
75 */
76 public static function getQuerySignature( array $query ) {
77 global $wgSecretKey;
78
79 ksort( $query ); // stable order
80 return hash_hmac( 'sha1', wfArrayToCgi( $query ), $wgSecretKey );
81 }
82
83 /**
84 * Run jobs from the job queue
85 *
86 * @note: also called from Wiki.php
87 *
88 * @param integer $maxJobs Maximum number of jobs to run
89 * @return void
90 */
91 public static function executeJobs( $maxJobs ) {
92 $n = $maxJobs; // number of jobs to run
93 if ( $n < 1 ) {
94 return;
95 }
96 try {
97 $group = JobQueueGroup::singleton();
98 $count = $group->executeReadyPeriodicTasks();
99 if ( $count > 0 ) {
100 wfDebugLog( 'jobqueue', "Executed $count periodic queue task(s)." );
101 }
102
103 do {
104 $job = $group->pop( JobQueueGroup::TYPE_DEFAULT, JobQueueGroup::USE_CACHE ); // job from any queue
105 if ( $job ) {
106 $output = $job->toString() . "\n";
107 $t = - microtime( true );
108 wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
109 $success = $job->run();
110 wfProfileOut( __METHOD__ . '-' . get_class( $job ) );
111 $group->ack( $job ); // done
112 $t += microtime( true );
113 $t = round( $t * 1000 );
114 if ( $success === false ) {
115 $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
116 } else {
117 $output .= "Success, Time: $t ms\n";
118 }
119 wfDebugLog( 'jobqueue', $output );
120 }
121 } while ( --$n && $job );
122 } catch ( MWException $e ) {
123 // We don't want exceptions thrown during job execution to
124 // be reported to the user since the output is already sent.
125 // Instead we just log them.
126 MWExceptionHandler::logException( $e );
127 }
128 }
129
130 public function mustBePosted() {
131 return true;
132 }
133
134 public function getAllowedParams() {
135 return array(
136 'tasks' => array(
137 ApiBase::PARAM_ISMULTI => true,
138 ApiBase::PARAM_TYPE => array( 'jobs' )
139 ),
140 'maxjobs' => array(
141 ApiBase::PARAM_TYPE => 'integer',
142 ApiBase::PARAM_DFLT => 0
143 ),
144 'signature' => array(
145 ApiBase::PROP_TYPE => 'string',
146 ),
147 'sigexpiry' => array(
148 ApiBase::PARAM_TYPE => 'integer',
149 ApiBase::PARAM_DFLT => 0 // ~epoch
150 ),
151 );
152 }
153
154 public function getParamDescription() {
155 return array(
156 'tasks' => 'List of task types to perform',
157 'maxjobs' => 'Maximum number of jobs to run',
158 'signature' => 'HMAC Signature that signs the request',
159 'sigexpiry' => 'HMAC signature expiry as a UNIX timestamp'
160 );
161 }
162
163 public function getDescription() {
164 return 'Perform periodic tasks or run jobs from the queue.';
165 }
166
167 public function getExamples() {
168 return array(
169 'api.php?action=runjobs&tasks=jobs&maxjobs=3' => 'Run up to 3 jobs from the queue',
170 );
171 }
172 }