This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace MediaWiki\Interwiki; |
||
3 | |||
4 | /** |
||
5 | * InterwikiLookup implementing the "classic" interwiki storage (hardcoded up to MW 1.26). |
||
6 | * |
||
7 | * This program is free software; you can redistribute it and/or modify |
||
8 | * it under the terms of the GNU General Public License as published by |
||
9 | * the Free Software Foundation; either version 2 of the License, or |
||
10 | * (at your option) any later version. |
||
11 | * |
||
12 | * This program is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
15 | * GNU General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU General Public License along |
||
18 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
20 | * http://www.gnu.org/copyleft/gpl.html |
||
21 | * |
||
22 | * @file |
||
23 | */ |
||
24 | use \Cdb\Exception as CdbException; |
||
25 | use \Cdb\Reader as CdbReader; |
||
26 | use Database; |
||
27 | use Hooks; |
||
28 | use Interwiki; |
||
29 | use Language; |
||
30 | use MapCacheLRU; |
||
31 | use WANObjectCache; |
||
32 | |||
33 | /** |
||
34 | * InterwikiLookup implementing the "classic" interwiki storage (hardcoded up to MW 1.26). |
||
35 | * |
||
36 | * This implements two levels of caching (in-process array and a WANObjectCache) |
||
37 | * and tree storage backends (SQL, CDB, and plain PHP arrays). |
||
38 | * |
||
39 | * All information is loaded on creation when called by $this->fetch( $prefix ). |
||
40 | * All work is done on replica DB, because this should *never* change (except during |
||
41 | * schema updates etc, which aren't wiki-related) |
||
42 | * |
||
43 | * @since 1.28 |
||
44 | */ |
||
45 | class ClassicInterwikiLookup implements InterwikiLookup { |
||
46 | |||
47 | /** |
||
48 | * @var MapCacheLRU |
||
49 | */ |
||
50 | private $localCache; |
||
51 | |||
52 | /** |
||
53 | * @var Language |
||
54 | */ |
||
55 | private $contentLanguage; |
||
56 | |||
57 | /** |
||
58 | * @var WANObjectCache |
||
59 | */ |
||
60 | private $objectCache; |
||
61 | |||
62 | /** |
||
63 | * @var int |
||
64 | */ |
||
65 | private $objectCacheExpiry; |
||
66 | |||
67 | /** |
||
68 | * @var bool|array|string |
||
69 | */ |
||
70 | private $cdbData; |
||
71 | |||
72 | /** |
||
73 | * @var int |
||
74 | */ |
||
75 | private $interwikiScopes; |
||
76 | |||
77 | /** |
||
78 | * @var string |
||
79 | */ |
||
80 | private $fallbackSite; |
||
81 | |||
82 | /** |
||
83 | * @var CdbReader|null |
||
84 | */ |
||
85 | private $cdbReader = null; |
||
86 | |||
87 | /** |
||
88 | * @var string|null |
||
89 | */ |
||
90 | private $thisSite = null; |
||
91 | |||
92 | /** |
||
93 | * @param Language $contentLanguage Language object used to convert prefixes to lower case |
||
94 | * @param WANObjectCache $objectCache Cache for interwiki info retrieved from the database |
||
95 | * @param int $objectCacheExpiry Expiry time for $objectCache, in seconds |
||
96 | * @param bool|array|string $cdbData The path of a CDB file, or |
||
97 | * an array resembling the contents of a CDB file, |
||
98 | * or false to use the database. |
||
99 | * @param int $interwikiScopes Specify number of domains to check for messages: |
||
100 | * - 1: Just local wiki level |
||
101 | * - 2: wiki and global levels |
||
102 | * - 3: site level as well as wiki and global levels |
||
103 | * @param string $fallbackSite The code to assume for the local site, |
||
104 | */ |
||
105 | function __construct( |
||
106 | Language $contentLanguage, |
||
107 | WANObjectCache $objectCache, |
||
108 | $objectCacheExpiry, |
||
109 | $cdbData, |
||
110 | $interwikiScopes, |
||
111 | $fallbackSite |
||
112 | ) { |
||
113 | $this->localCache = new MapCacheLRU( 100 ); |
||
114 | |||
115 | $this->contentLanguage = $contentLanguage; |
||
116 | $this->objectCache = $objectCache; |
||
117 | $this->objectCacheExpiry = $objectCacheExpiry; |
||
118 | $this->cdbData = $cdbData; |
||
119 | $this->interwikiScopes = $interwikiScopes; |
||
120 | $this->fallbackSite = $fallbackSite; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Check whether an interwiki prefix exists |
||
125 | * |
||
126 | * @param string $prefix Interwiki prefix to use |
||
127 | * @return bool Whether it exists |
||
128 | */ |
||
129 | public function isValidInterwiki( $prefix ) { |
||
130 | $result = $this->fetch( $prefix ); |
||
131 | |||
132 | return (bool)$result; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Fetch an Interwiki object |
||
137 | * |
||
138 | * @param string $prefix Interwiki prefix to use |
||
139 | * @return Interwiki|null|bool |
||
140 | */ |
||
141 | public function fetch( $prefix ) { |
||
142 | if ( $prefix == '' ) { |
||
143 | return null; |
||
144 | } |
||
145 | |||
146 | $prefix = $this->contentLanguage->lc( $prefix ); |
||
147 | if ( $this->localCache->has( $prefix ) ) { |
||
148 | return $this->localCache->get( $prefix ); |
||
149 | } |
||
150 | |||
151 | if ( $this->cdbData ) { |
||
152 | $iw = $this->getInterwikiCached( $prefix ); |
||
153 | } else { |
||
154 | $iw = $this->load( $prefix ); |
||
155 | if ( !$iw ) { |
||
156 | $iw = false; |
||
157 | } |
||
158 | } |
||
159 | $this->localCache->set( $prefix, $iw ); |
||
160 | |||
161 | return $iw; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Resets locally cached Interwiki objects. This is intended for use during testing only. |
||
166 | * This does not invalidate entries in the persistent cache, as invalidateCache() does. |
||
167 | * @since 1.27 |
||
168 | */ |
||
169 | public function resetLocalCache() { |
||
170 | $this->localCache->clear(); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Purge the in-process and object cache for an interwiki prefix |
||
175 | * @param string $prefix |
||
176 | */ |
||
177 | public function invalidateCache( $prefix ) { |
||
178 | $this->localCache->clear( $prefix ); |
||
179 | |||
180 | $key = $this->objectCache->makeKey( 'interwiki', $prefix ); |
||
181 | $this->objectCache->delete( $key ); |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Fetch interwiki prefix data from local cache in constant database. |
||
186 | * |
||
187 | * @note More logic is explained in DefaultSettings. |
||
188 | * |
||
189 | * @param string $prefix Interwiki prefix |
||
190 | * @return Interwiki |
||
191 | */ |
||
192 | private function getInterwikiCached( $prefix ) { |
||
193 | $value = $this->getInterwikiCacheEntry( $prefix ); |
||
194 | |||
195 | if ( $value ) { |
||
196 | // Split values |
||
197 | list( $local, $url ) = explode( ' ', $value, 2 ); |
||
198 | return new Interwiki( $prefix, $url, '', '', (int)$local ); |
||
199 | } else { |
||
200 | return false; |
||
201 | } |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Get entry from interwiki cache |
||
206 | * |
||
207 | * @note More logic is explained in DefaultSettings. |
||
208 | * |
||
209 | * @param string $prefix Database key |
||
210 | * @return bool|string The interwiki entry or false if not found |
||
211 | */ |
||
212 | private function getInterwikiCacheEntry( $prefix ) { |
||
213 | wfDebug( __METHOD__ . "( $prefix )\n" ); |
||
214 | $value = false; |
||
215 | try { |
||
216 | // Resolve site name |
||
217 | if ( $this->interwikiScopes >= 3 && !$this->thisSite ) { |
||
0 ignored issues
–
show
|
|||
218 | $this->thisSite = $this->getCacheValue( '__sites:' . wfWikiID() ); |
||
219 | if ( $this->thisSite == '' ) { |
||
220 | $this->thisSite = $this->fallbackSite; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | $value = $this->getCacheValue( wfMemcKey( $prefix ) ); |
||
225 | // Site level |
||
226 | if ( $value == '' && $this->interwikiScopes >= 3 ) { |
||
227 | $value = $this->getCacheValue( "_{$this->thisSite}:{$prefix}" ); |
||
228 | } |
||
229 | // Global Level |
||
230 | if ( $value == '' && $this->interwikiScopes >= 2 ) { |
||
231 | $value = $this->getCacheValue( "__global:{$prefix}" ); |
||
232 | } |
||
233 | if ( $value == 'undef' ) { |
||
234 | $value = ''; |
||
235 | } |
||
236 | } catch ( CdbException $e ) { |
||
237 | wfDebug( __METHOD__ . ": CdbException caught, error message was " |
||
238 | . $e->getMessage() ); |
||
239 | } |
||
240 | |||
241 | return $value; |
||
242 | } |
||
243 | |||
244 | private function getCacheValue( $key ) { |
||
245 | if ( $this->cdbReader === null ) { |
||
246 | if ( is_string( $this->cdbData ) ) { |
||
247 | $this->cdbReader = \Cdb\Reader::open( $this->cdbData ); |
||
248 | } elseif ( is_array( $this->cdbData ) ) { |
||
249 | $this->cdbReader = new \Cdb\Reader\Hash( $this->cdbData ); |
||
250 | } else { |
||
251 | $this->cdbReader = false; |
||
0 ignored issues
–
show
It seems like
false of type false is incompatible with the declared type object<Cdb\Reader>|null of property $cdbReader .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
252 | } |
||
253 | } |
||
254 | |||
255 | if ( $this->cdbReader ) { |
||
256 | return $this->cdbReader->get( $key ); |
||
257 | } else { |
||
258 | return false; |
||
259 | } |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Load the interwiki, trying first memcached then the DB |
||
264 | * |
||
265 | * @param string $prefix The interwiki prefix |
||
266 | * @return Interwiki|bool Interwiki if $prefix is valid, otherwise false |
||
267 | */ |
||
268 | private function load( $prefix ) { |
||
269 | $iwData = []; |
||
270 | if ( !Hooks::run( 'InterwikiLoadPrefix', [ $prefix, &$iwData ] ) ) { |
||
271 | return $this->loadFromArray( $iwData ); |
||
272 | } |
||
273 | |||
274 | if ( is_array( $iwData ) ) { |
||
275 | $iw = $this->loadFromArray( $iwData ); |
||
276 | if ( $iw ) { |
||
277 | return $iw; // handled by hook |
||
278 | } |
||
279 | } |
||
280 | |||
281 | $iwData = $this->objectCache->getWithSetCallback( |
||
282 | $this->objectCache->makeKey( 'interwiki', $prefix ), |
||
283 | $this->objectCacheExpiry, |
||
284 | function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) { |
||
285 | $dbr = wfGetDB( DB_REPLICA ); // TODO: inject LoadBalancer |
||
286 | |||
287 | $setOpts += Database::getCacheSetOptions( $dbr ); |
||
288 | |||
289 | $row = $dbr->selectRow( |
||
290 | 'interwiki', |
||
291 | ClassicInterwikiLookup::selectFields(), |
||
292 | [ 'iw_prefix' => $prefix ], |
||
293 | __METHOD__ |
||
294 | ); |
||
295 | |||
296 | return $row ? (array)$row : '!NONEXISTENT'; |
||
297 | } |
||
298 | ); |
||
299 | |||
300 | if ( is_array( $iwData ) ) { |
||
301 | return $this->loadFromArray( $iwData ) ?: false; |
||
302 | } |
||
303 | |||
304 | return false; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Fill in member variables from an array (e.g. memcached result, Database::fetchRow, etc) |
||
309 | * |
||
310 | * @param array $mc Associative array: row from the interwiki table |
||
311 | * @return Interwiki|bool Interwiki object or false if $mc['iw_url'] is not set |
||
312 | */ |
||
313 | private function loadFromArray( $mc ) { |
||
314 | if ( isset( $mc['iw_url'] ) ) { |
||
315 | $url = $mc['iw_url']; |
||
316 | $local = isset( $mc['iw_local'] ) ? $mc['iw_local'] : 0; |
||
317 | $trans = isset( $mc['iw_trans'] ) ? $mc['iw_trans'] : 0; |
||
318 | $api = isset( $mc['iw_api'] ) ? $mc['iw_api'] : ''; |
||
319 | $wikiId = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : ''; |
||
320 | |||
321 | return new Interwiki( null, $url, $api, $wikiId, $local, $trans ); |
||
322 | } |
||
323 | |||
324 | return false; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * Fetch all interwiki prefixes from interwiki cache |
||
329 | * |
||
330 | * @param null|string $local If not null, limits output to local/non-local interwikis |
||
331 | * @return array List of prefixes, where each row is an associative array |
||
332 | */ |
||
333 | private function getAllPrefixesCached( $local ) { |
||
334 | wfDebug( __METHOD__ . "()\n" ); |
||
335 | $data = []; |
||
336 | try { |
||
337 | /* Resolve site name */ |
||
338 | if ( $this->interwikiScopes >= 3 && !$this->thisSite ) { |
||
0 ignored issues
–
show
The expression
$this->thisSite of type string|null is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
339 | $site = $this->getCacheValue( '__sites:' . wfWikiID() ); |
||
340 | |||
341 | if ( $site == '' ) { |
||
342 | $this->thisSite = $this->fallbackSite; |
||
343 | } else { |
||
344 | $this->thisSite = $site; |
||
345 | } |
||
346 | } |
||
347 | |||
348 | // List of interwiki sources |
||
349 | $sources = []; |
||
350 | // Global Level |
||
351 | if ( $this->interwikiScopes >= 2 ) { |
||
352 | $sources[] = '__global'; |
||
353 | } |
||
354 | // Site level |
||
355 | if ( $this->interwikiScopes >= 3 ) { |
||
356 | $sources[] = '_' . $this->thisSite; |
||
357 | } |
||
358 | $sources[] = wfWikiID(); |
||
359 | |||
360 | foreach ( $sources as $source ) { |
||
361 | $list = $this->getCacheValue( '__list:' . $source ); |
||
362 | foreach ( explode( ' ', $list ) as $iw_prefix ) { |
||
363 | $row = $this->getCacheValue( "{$source}:{$iw_prefix}" ); |
||
364 | if ( !$row ) { |
||
365 | continue; |
||
366 | } |
||
367 | |||
368 | list( $iw_local, $iw_url ) = explode( ' ', $row ); |
||
369 | |||
370 | if ( $local !== null && $local != $iw_local ) { |
||
371 | continue; |
||
372 | } |
||
373 | |||
374 | $data[$iw_prefix] = [ |
||
375 | 'iw_prefix' => $iw_prefix, |
||
376 | 'iw_url' => $iw_url, |
||
377 | 'iw_local' => $iw_local, |
||
378 | ]; |
||
379 | } |
||
380 | } |
||
381 | } catch ( CdbException $e ) { |
||
382 | wfDebug( __METHOD__ . ": CdbException caught, error message was " |
||
383 | . $e->getMessage() ); |
||
384 | } |
||
385 | |||
386 | ksort( $data ); |
||
387 | |||
388 | return array_values( $data ); |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Fetch all interwiki prefixes from DB |
||
393 | * |
||
394 | * @param string|null $local If not null, limits output to local/non-local interwikis |
||
395 | * @return array[] Interwiki rows |
||
396 | */ |
||
397 | private function getAllPrefixesDB( $local ) { |
||
398 | $db = wfGetDB( DB_REPLICA ); // TODO: inject DB LoadBalancer |
||
399 | |||
400 | $where = []; |
||
401 | |||
402 | if ( $local !== null ) { |
||
403 | if ( $local == 1 ) { |
||
404 | $where['iw_local'] = 1; |
||
405 | } elseif ( $local == 0 ) { |
||
406 | $where['iw_local'] = 0; |
||
407 | } |
||
408 | } |
||
409 | |||
410 | $res = $db->select( 'interwiki', |
||
411 | $this->selectFields(), |
||
412 | $where, __METHOD__, [ 'ORDER BY' => 'iw_prefix' ] |
||
413 | ); |
||
414 | |||
415 | $retval = []; |
||
416 | foreach ( $res as $row ) { |
||
417 | $retval[] = (array)$row; |
||
418 | } |
||
419 | |||
420 | return $retval; |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * Returns all interwiki prefixes |
||
425 | * |
||
426 | * @param string|null $local If set, limits output to local/non-local interwikis |
||
427 | * @return array[] Interwiki rows, where each row is an associative array |
||
428 | */ |
||
429 | public function getAllPrefixes( $local = null ) { |
||
430 | if ( $this->cdbData ) { |
||
431 | return $this->getAllPrefixesCached( $local ); |
||
432 | } |
||
433 | |||
434 | return $this->getAllPrefixesDB( $local ); |
||
435 | } |
||
436 | |||
437 | /** |
||
438 | * Return the list of interwiki fields that should be selected to create |
||
439 | * a new Interwiki object. |
||
440 | * @return string[] |
||
441 | */ |
||
442 | private static function selectFields() { |
||
443 | return [ |
||
444 | 'iw_prefix', |
||
445 | 'iw_url', |
||
446 | 'iw_api', |
||
447 | 'iw_wikiid', |
||
448 | 'iw_local', |
||
449 | 'iw_trans' |
||
450 | ]; |
||
451 | } |
||
452 | |||
453 | } |
||
454 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: