Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 97 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
ApiScribuntoConsole | |
0.00% |
0 / 97 |
|
0.00% |
0 / 7 |
420 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
156 | |||
runConsole | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
newSession | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
needsToken | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isInternal | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllowedParams | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Scribunto; |
4 | |
5 | use ApiBase; |
6 | use ApiMain; |
7 | use MediaWiki\Html\Html; |
8 | use MediaWiki\Title\Title; |
9 | use ObjectCache; |
10 | use Parser; |
11 | use ParserFactory; |
12 | use ParserOptions; |
13 | use Wikimedia\ParamValidator\ParamValidator; |
14 | |
15 | /** |
16 | * API module for serving debug console requests on the edit page |
17 | */ |
18 | class ApiScribuntoConsole extends ApiBase { |
19 | private const SC_MAX_SIZE = 500000; |
20 | private const SC_SESSION_EXPIRY = 3600; |
21 | private ParserFactory $parserFactory; |
22 | |
23 | /** |
24 | * @param ApiMain $main |
25 | * @param string $action |
26 | * @param ParserFactory $parserFactory |
27 | */ |
28 | public function __construct( |
29 | ApiMain $main, |
30 | $action, |
31 | ParserFactory $parserFactory |
32 | ) { |
33 | parent::__construct( $main, $action ); |
34 | $this->parserFactory = $parserFactory; |
35 | } |
36 | |
37 | /** |
38 | * @suppress PhanTypePossiblyInvalidDimOffset |
39 | */ |
40 | public function execute() { |
41 | $params = $this->extractRequestParams(); |
42 | |
43 | $title = Title::newFromText( $params['title'] ); |
44 | if ( !$title ) { |
45 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] ); |
46 | } |
47 | |
48 | if ( $params['session'] ) { |
49 | $sessionId = $params['session']; |
50 | } else { |
51 | $sessionId = mt_rand( 0, 0x7fffffff ); |
52 | } |
53 | |
54 | $cache = ObjectCache::getInstance( CACHE_ANYTHING ); |
55 | $sessionKey = $cache->makeKey( 'scribunto-console', $this->getUser()->getId(), $sessionId ); |
56 | $session = null; |
57 | $sessionIsNew = false; |
58 | if ( $params['session'] ) { |
59 | $session = $cache->get( $sessionKey ); |
60 | } |
61 | if ( !isset( $session['version'] ) ) { |
62 | $session = $this->newSession(); |
63 | $sessionIsNew = true; |
64 | } |
65 | |
66 | // Create a variable holding the session which will be stored if there |
67 | // are no errors. If there are errors, we don't want to store the current |
68 | // question to the state builder array, since that will cause subsequent |
69 | // requests to fail. |
70 | $newSession = $session; |
71 | |
72 | if ( !empty( $params['clear'] ) ) { |
73 | $newSession['size'] -= strlen( implode( '', $newSession['questions'] ) ); |
74 | $newSession['questions'] = []; |
75 | $session['questions'] = []; |
76 | } |
77 | if ( strlen( $params['question'] ) ) { |
78 | $newSession['size'] += strlen( $params['question'] ); |
79 | $newSession['questions'][] = $params['question']; |
80 | } |
81 | if ( $params['content'] ) { |
82 | $newSession['size'] += strlen( $params['content'] ) - strlen( $newSession['content'] ); |
83 | $newSession['content'] = $params['content']; |
84 | } |
85 | |
86 | if ( $newSession['size'] > self::SC_MAX_SIZE ) { |
87 | $this->dieWithError( 'scribunto-console-too-large' ); |
88 | } |
89 | $result = $this->runConsole( [ |
90 | 'title' => $title, |
91 | 'content' => $newSession['content'], |
92 | 'prevQuestions' => $session['questions'], |
93 | 'question' => $params['question'], |
94 | ] ); |
95 | |
96 | if ( $result['type'] === 'error' ) { |
97 | // Restore the questions array |
98 | $newSession['questions'] = $session['questions']; |
99 | } |
100 | $cache->set( $sessionKey, $newSession, self::SC_SESSION_EXPIRY ); |
101 | $result['session'] = $sessionId; |
102 | $result['sessionSize'] = $newSession['size']; |
103 | $result['sessionMaxSize'] = self::SC_MAX_SIZE; |
104 | if ( $sessionIsNew ) { |
105 | $result['sessionIsNew'] = ''; |
106 | } |
107 | foreach ( $result as $key => $value ) { |
108 | $this->getResult()->addValue( null, $key, $value ); |
109 | } |
110 | } |
111 | |
112 | /** |
113 | * Execute the console |
114 | * @param array $params |
115 | * - 'title': (Title) Module being processed |
116 | * - 'content': (string) New module text |
117 | * - 'prevQuestions': (string[]) Previous values for 'question' in this session. |
118 | * - 'question': (string) Lua code to run. |
119 | * @return array Result data |
120 | */ |
121 | protected function runConsole( array $params ) { |
122 | $parser = $this->parserFactory->getInstance(); |
123 | $options = new ParserOptions( $this->getUser() ); |
124 | $parser->startExternalParse( $params['title'], $options, Parser::OT_HTML, true ); |
125 | $engine = Scribunto::getParserEngine( $parser ); |
126 | try { |
127 | $result = $engine->runConsole( $params ); |
128 | } catch ( ScribuntoException $e ) { |
129 | $trace = $e->getScriptTraceHtml(); |
130 | $message = $e->getMessage(); |
131 | $html = Html::element( 'p', [], $message ); |
132 | if ( $trace !== false ) { |
133 | $html .= Html::element( 'p', |
134 | [], |
135 | $this->msg( 'scribunto-common-backtrace' )->inContentLanguage()->text() |
136 | ) . $trace; |
137 | } |
138 | |
139 | return [ |
140 | 'type' => 'error', |
141 | 'html' => $html, |
142 | 'message' => $message, |
143 | 'messagename' => $e->getMessageName() ]; |
144 | } |
145 | return [ |
146 | 'type' => 'normal', |
147 | 'print' => strval( $result['print'] ), |
148 | 'return' => strval( $result['return'] ) |
149 | ]; |
150 | } |
151 | |
152 | /** |
153 | * @return array |
154 | */ |
155 | protected function newSession() { |
156 | return [ |
157 | 'content' => '', |
158 | 'questions' => [], |
159 | 'size' => 0, |
160 | 'version' => 1, |
161 | ]; |
162 | } |
163 | |
164 | /** @inheritDoc */ |
165 | public function needsToken() { |
166 | return 'csrf'; |
167 | } |
168 | |
169 | /** @inheritDoc */ |
170 | public function isInternal() { |
171 | return true; |
172 | } |
173 | |
174 | /** @inheritDoc */ |
175 | public function getAllowedParams() { |
176 | return [ |
177 | 'title' => [ |
178 | ParamValidator::PARAM_TYPE => 'string', |
179 | ], |
180 | 'content' => [ |
181 | ParamValidator::PARAM_TYPE => 'text' |
182 | ], |
183 | 'session' => [ |
184 | ParamValidator::PARAM_TYPE => 'integer', |
185 | ], |
186 | 'question' => [ |
187 | ParamValidator::PARAM_TYPE => 'text', |
188 | ParamValidator::PARAM_REQUIRED => true, |
189 | ], |
190 | 'clear' => [ |
191 | ParamValidator::PARAM_TYPE => 'boolean', |
192 | ], |
193 | ]; |
194 | } |
195 | } |