Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/PrefixSearch.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Prefix search of page names.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
/**
24
 * Handles searching prefixes of titles and finding any page
25
 * names that match. Used largely by the OpenSearch implementation.
26
 * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch
27
 *
28
 * @ingroup Search
29
 */
30
abstract class PrefixSearch {
31
	/**
32
	 * Do a prefix search of titles and return a list of matching page names.
33
	 * @deprecated Since 1.23, use TitlePrefixSearch or StringPrefixSearch classes
34
	 *
35
	 * @param string $search
36
	 * @param int $limit
37
	 * @param array $namespaces Used if query is not explicitly prefixed
38
	 * @param int $offset How many results to offset from the beginning
39
	 * @return array Array of strings
40
	 */
41
	public static function titleSearch( $search, $limit, $namespaces = [], $offset = 0 ) {
42
		$prefixSearch = new StringPrefixSearch;
43
		return $prefixSearch->search( $search, $limit, $namespaces, $offset );
44
	}
45
46
	/**
47
	 * Do a prefix search of titles and return a list of matching page names.
48
	 *
49
	 * @param string $search
50
	 * @param int $limit
51
	 * @param array $namespaces Used if query is not explicitly prefixed
52
	 * @param int $offset How many results to offset from the beginning
53
	 * @return array Array of strings or Title objects
54
	 */
55
	public function search( $search, $limit, $namespaces = [], $offset = 0 ) {
56
		$search = trim( $search );
57
		if ( $search == '' ) {
58
			return []; // Return empty result
59
		}
60
61
		$hasNamespace = $this->extractNamespace( $search );
62
		if ( $hasNamespace ) {
63
			list( $namespace, $search ) = $hasNamespace;
64
			$namespaces = [ $namespace ];
65
		} else {
66
			$namespaces = $this->validateNamespaces( $namespaces );
67
			Hooks::run( 'PrefixSearchExtractNamespace', [ &$namespaces, &$search ] );
68
		}
69
70
		return $this->searchBackend( $namespaces, $search, $limit, $offset );
71
	}
72
73
	/**
74
	 * Figure out if given input contains an explicit namespace.
75
	 *
76
	 * @param string $input
77
	 * @return false|array Array of namespace and remaining text, or false if no namespace given.
78
	 */
79
	protected function extractNamespace( $input ) {
80
		if ( strpos( $input, ':' ) === false ) {
81
			return false;
82
		}
83
84
		// Namespace prefix only
85
		$title = Title::newFromText( $input . 'Dummy' );
86 View Code Duplication
		if (
87
			$title &&
88
			$title->getText() === 'Dummy' &&
89
			!$title->inNamespace( NS_MAIN ) &&
90
			!$title->isExternal()
91
		) {
92
			return [ $title->getNamespace(), '' ];
93
		}
94
95
		// Namespace prefix with additional input
96
		$title = Title::newFromText( $input );
97 View Code Duplication
		if (
98
			$title &&
99
			!$title->inNamespace( NS_MAIN ) &&
100
			!$title->isExternal()
101
		) {
102
			// getText provides correct capitalization
103
			return [ $title->getNamespace(), $title->getText() ];
104
		}
105
106
		return false;
107
	}
108
109
	/**
110
	 * Do a prefix search for all possible variants of the prefix
111
	 * @param string $search
112
	 * @param int $limit
113
	 * @param array $namespaces
114
	 * @param int $offset How many results to offset from the beginning
115
	 *
116
	 * @return array
117
	 */
118
	public function searchWithVariants( $search, $limit, array $namespaces, $offset = 0 ) {
119
		$searches = $this->search( $search, $limit, $namespaces, $offset );
120
121
		// if the content language has variants, try to retrieve fallback results
122
		$fallbackLimit = $limit - count( $searches );
123
		if ( $fallbackLimit > 0 ) {
124
			global $wgContLang;
125
126
			$fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
127
			$fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
128
129
			foreach ( $fallbackSearches as $fbs ) {
130
				$fallbackSearchResult = $this->search( $fbs, $fallbackLimit, $namespaces );
131
				$searches = array_merge( $searches, $fallbackSearchResult );
132
				$fallbackLimit -= count( $fallbackSearchResult );
133
134
				if ( $fallbackLimit == 0 ) {
135
					break;
136
				}
137
			}
138
		}
139
		return $searches;
140
	}
141
142
	/**
143
	 * When implemented in a descendant class, receives an array of Title objects and returns
144
	 * either an unmodified array or an array of strings corresponding to titles passed to it.
145
	 *
146
	 * @param array $titles
147
	 * @return array
148
	 */
149
	abstract protected function titles( array $titles );
150
151
	/**
152
	 * When implemented in a descendant class, receives an array of titles as strings and returns
153
	 * either an unmodified array or an array of Title objects corresponding to strings received.
154
	 *
155
	 * @param array $strings
156
	 *
157
	 * @return array
158
	 */
159
	abstract protected function strings( array $strings );
160
161
	/**
162
	 * Do a prefix search of titles and return a list of matching page names.
163
	 * @param array $namespaces
164
	 * @param string $search
165
	 * @param int $limit
166
	 * @param int $offset How many results to offset from the beginning
167
	 * @return array Array of strings
168
	 */
169
	protected function searchBackend( $namespaces, $search, $limit, $offset ) {
170
		if ( count( $namespaces ) == 1 ) {
171
			$ns = $namespaces[0];
172
			if ( $ns == NS_MEDIA ) {
173
				$namespaces = [ NS_FILE ];
174
			} elseif ( $ns == NS_SPECIAL ) {
175
				return $this->titles( $this->specialSearch( $search, $limit, $offset ) );
176
			}
177
		}
178
		$srchres = [];
179
		if ( Hooks::run(
180
			'PrefixSearchBackend',
181
			[ $namespaces, $search, $limit, &$srchres, $offset ]
182
		) ) {
183
			return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit, $offset ) );
184
		}
185
		return $this->strings(
186
			$this->handleResultFromHook( $srchres, $namespaces, $search, $limit, $offset ) );
187
	}
188
189
	private function handleResultFromHook( $srchres, $namespaces, $search, $limit, $offset ) {
190
		if ( $offset === 0 ) {
191
			// Only perform exact db match if offset === 0
192
			// This is still far from perfect but at least we avoid returning the
193
			// same title afain and again when the user is scrolling with a query
194
			// that matches a title in the db.
195
			$rescorer = new SearchExactMatchRescorer();
196
			$srchres = $rescorer->rescore( $search, $namespaces, $srchres, $limit );
197
		}
198
		return $srchres;
199
	}
200
201
	/**
202
	 * Prefix search special-case for Special: namespace.
203
	 *
204
	 * @param string $search Term
205
	 * @param int $limit Max number of items to return
206
	 * @param int $offset Number of items to offset
207
	 * @return array
208
	 */
209
	protected function specialSearch( $search, $limit, $offset ) {
210
		global $wgContLang;
211
212
		$searchParts = explode( '/', $search, 2 );
213
		$searchKey = $searchParts[0];
214
		$subpageSearch = isset( $searchParts[1] ) ? $searchParts[1] : null;
215
216
		// Handle subpage search separately.
217
		if ( $subpageSearch !== null ) {
218
			// Try matching the full search string as a page name
219
			$specialTitle = Title::makeTitleSafe( NS_SPECIAL, $searchKey );
220
			if ( !$specialTitle ) {
221
				return [];
222
			}
223
			$special = SpecialPageFactory::getPage( $specialTitle->getText() );
224
			if ( $special ) {
225
				$subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset );
226
				return array_map( function ( $sub ) use ( $specialTitle ) {
227
					return $specialTitle->getSubpage( $sub );
228
				}, $subpages );
229
			} else {
230
				return [];
231
			}
232
		}
233
234
		# normalize searchKey, so aliases with spaces can be found - bug 25675
235
		$searchKey = str_replace( ' ', '_', $searchKey );
236
		$searchKey = $wgContLang->caseFold( $searchKey );
237
238
		// Unlike SpecialPage itself, we want the canonical forms of both
239
		// canonical and alias title forms...
240
		$keys = [];
241
		foreach ( SpecialPageFactory::getNames() as $page ) {
242
			$keys[$wgContLang->caseFold( $page )] = $page;
243
		}
244
245
		foreach ( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
246
			if ( !in_array( $page, SpecialPageFactory::getNames() ) ) {# bug 20885
247
				continue;
248
			}
249
250
			foreach ( $aliases as $alias ) {
251
				$keys[$wgContLang->caseFold( $alias )] = $alias;
252
			}
253
		}
254
		ksort( $keys );
255
256
		$srchres = [];
257
		$skipped = 0;
258
		foreach ( $keys as $pageKey => $page ) {
259
			if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
260
				// bug 27671: Don't use SpecialPage::getTitleFor() here because it
261
				// localizes its input leading to searches for e.g. Special:All
262
				// returning Spezial:MediaWiki-Systemnachrichten and returning
263
				// Spezial:Alle_Seiten twice when $wgLanguageCode == 'de'
264
				if ( $offset > 0 && $skipped < $offset ) {
265
					$skipped++;
266
					continue;
267
				}
268
				$srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page );
269
			}
270
271
			if ( count( $srchres ) >= $limit ) {
272
				break;
273
			}
274
		}
275
276
		return $srchres;
277
	}
278
279
	/**
280
	 * Unless overridden by PrefixSearchBackend hook...
281
	 * This is case-sensitive (First character may
282
	 * be automatically capitalized by Title::secureAndSpit()
283
	 * later on depending on $wgCapitalLinks)
284
	 *
285
	 * @param array|null $namespaces Namespaces to search in
286
	 * @param string $search Term
287
	 * @param int $limit Max number of items to return
288
	 * @param int $offset Number of items to skip
289
	 * @return Title[] Array of Title objects
290
	 */
291
	public function defaultSearchBackend( $namespaces, $search, $limit, $offset ) {
292
		// Backwards compatability with old code. Default to NS_MAIN if no namespaces provided.
293
		if ( $namespaces === null ) {
294
			$namespaces = [];
295
		}
296
		if ( !$namespaces ) {
297
			$namespaces[] = NS_MAIN;
298
		}
299
300
		// Construct suitable prefix for each namespace. They differ in cases where
301
		// some namespaces always capitalize and some don't.
302
		$prefixes = [];
303
		foreach ( $namespaces as $namespace ) {
304
			// For now, if special is included, ignore the other namespaces
305
			if ( $namespace == NS_SPECIAL ) {
306
				return $this->specialSearch( $search, $limit, $offset );
307
			}
308
309
			$title = Title::makeTitleSafe( $namespace, $search );
310
			// Why does the prefix default to empty?
311
			$prefix = $title ? $title->getDBkey() : '';
312
			$prefixes[$prefix][] = $namespace;
313
		}
314
315
		$dbr = wfGetDB( DB_REPLICA );
316
		// Often there is only one prefix that applies to all requested namespaces,
317
		// but sometimes there are two if some namespaces do not always capitalize.
318
		$conds = [];
319
		foreach ( $prefixes as $prefix => $namespaces ) {
320
			$condition = [
321
				'page_namespace' => $namespaces,
322
				'page_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
323
			];
324
			$conds[] = $dbr->makeList( $condition, LIST_AND );
325
		}
326
327
		$table = 'page';
328
		$fields = [ 'page_id', 'page_namespace', 'page_title' ];
329
		$conds = $dbr->makeList( $conds, LIST_OR );
330
		$options = [
331
			'LIMIT' => $limit,
332
			'ORDER BY' => [ 'page_title', 'page_namespace' ],
333
			'OFFSET' => $offset
334
		];
335
336
		$res = $dbr->select( $table, $fields, $conds, __METHOD__, $options );
337
338
		return iterator_to_array( TitleArray::newFromResult( $res ) );
339
	}
340
341
	/**
342
	 * Validate an array of numerical namespace indexes
343
	 *
344
	 * @param array $namespaces
345
	 * @return array (default: contains only NS_MAIN)
346
	 */
347
	protected function validateNamespaces( $namespaces ) {
348
		global $wgContLang;
349
350
		// We will look at each given namespace against wgContLang namespaces
351
		$validNamespaces = $wgContLang->getNamespaces();
352
		if ( is_array( $namespaces ) && count( $namespaces ) > 0 ) {
353
			$valid = [];
354
			foreach ( $namespaces as $ns ) {
355
				if ( is_numeric( $ns ) && array_key_exists( $ns, $validNamespaces ) ) {
356
					$valid[] = $ns;
357
				}
358
			}
359
			if ( count( $valid ) > 0 ) {
360
				return $valid;
361
			}
362
		}
363
364
		return [ NS_MAIN ];
365
	}
366
}
367
368
/**
369
 * Performs prefix search, returning Title objects
370
 * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch
371
 * @ingroup Search
372
 */
373
class TitlePrefixSearch extends PrefixSearch {
0 ignored issues
show
Deprecated Code introduced by
The class PrefixSearch has been deprecated with message: Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
374
375
	protected function titles( array $titles ) {
376
		return $titles;
377
	}
378
379
	protected function strings( array $strings ) {
380
		$titles = array_map( 'Title::newFromText', $strings );
381
		$lb = new LinkBatch( $titles );
382
		$lb->setCaller( __METHOD__ );
383
		$lb->execute();
384
		return $titles;
385
	}
386
}
387
388
/**
389
 * Performs prefix search, returning strings
390
 * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch
391
 * @ingroup Search
392
 */
393
class StringPrefixSearch extends PrefixSearch {
0 ignored issues
show
Deprecated Code introduced by
The class PrefixSearch has been deprecated with message: Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
394
395
	protected function titles( array $titles ) {
396
		return array_map( function ( Title $t ) {
397
			return $t->getPrefixedText();
398
		}, $titles );
399
	}
400
401
	protected function strings( array $strings ) {
402
		return $strings;
403
	}
404
}
405