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/title/MediaWikiTitleCodec.php (1 issue)

Labels
Severity

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
 * A codec for %MediaWiki page titles.
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
 * @license GPL 2+
22
 * @author Daniel Kinzler
23
 */
24
use MediaWiki\Linker\LinkTarget;
25
26
/**
27
 * A codec for %MediaWiki page titles.
28
 *
29
 * @note Normalization and validation is applied while parsing, not when formatting.
30
 * It's possible to construct a TitleValue with an invalid title, and use MediaWikiTitleCodec
31
 * to generate an (invalid) title string from it. TitleValues should be constructed only
32
 * via parseTitle() or from a (semi)trusted source, such as the database.
33
 *
34
 * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
35
 * @since 1.23
36
 */
37
class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
38
	/**
39
	 * @var Language
40
	 */
41
	protected $language;
42
43
	/**
44
	 * @var GenderCache
45
	 */
46
	protected $genderCache;
47
48
	/**
49
	 * @var string[]
50
	 */
51
	protected $localInterwikis;
52
53
	/**
54
	 * @param Language $language The language object to use for localizing namespace names.
55
	 * @param GenderCache $genderCache The gender cache for generating gendered namespace names
56
	 * @param string[]|string $localInterwikis
57
	 */
58
	public function __construct( Language $language, GenderCache $genderCache,
59
		$localInterwikis = []
60
	) {
61
		$this->language = $language;
62
		$this->genderCache = $genderCache;
63
		$this->localInterwikis = (array)$localInterwikis;
64
	}
65
66
	/**
67
	 * @see TitleFormatter::getNamespaceName()
68
	 *
69
	 * @param int $namespace
70
	 * @param string $text
71
	 *
72
	 * @throws InvalidArgumentException If the namespace is invalid
73
	 * @return string
74
	 */
75
	public function getNamespaceName( $namespace, $text ) {
76
		if ( $this->language->needsGenderDistinction() &&
77
			MWNamespace::hasGenderDistinction( $namespace )
78
		) {
79
80
			// NOTE: we are assuming here that the title text is a user name!
81
			$gender = $this->genderCache->getGenderOf( $text, __METHOD__ );
82
			$name = $this->language->getGenderNsText( $namespace, $gender );
83
		} else {
84
			$name = $this->language->getNsText( $namespace );
85
		}
86
87
		if ( $name === false ) {
88
			throw new InvalidArgumentException( 'Unknown namespace ID: ' . $namespace );
89
		}
90
91
		return $name;
92
	}
93
94
	/**
95
	 * @see TitleFormatter::formatTitle()
96
	 *
97
	 * @param int|bool $namespace The namespace ID (or false, if the namespace should be ignored)
98
	 * @param string $text The page title. Should be valid. Only minimal normalization is applied.
99
	 *        Underscores will be replaced.
100
	 * @param string $fragment The fragment name (may be empty).
101
	 * @param string $interwiki The interwiki name (may be empty).
102
	 *
103
	 * @throws InvalidArgumentException If the namespace is invalid
104
	 * @return string
105
	 */
106
	public function formatTitle( $namespace, $text, $fragment = '', $interwiki = '' ) {
107
		if ( $namespace !== false ) {
108
			// Try to get a namespace name, but fallback
109
			// to empty string if it doesn't exist
110
			try {
111
				$nsName = $this->getNamespaceName( $namespace, $text );
0 ignored issues
show
It seems like $namespace defined by parameter $namespace on line 106 can also be of type boolean; however, MediaWikiTitleCodec::getNamespaceName() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
112
			} catch ( InvalidArgumentException $e ) {
113
				$nsName = '';
114
			}
115
116
			if ( $namespace !== 0 ) {
117
				$text = $nsName . ':' . $text;
118
			}
119
		}
120
121
		if ( $fragment !== '' ) {
122
			$text = $text . '#' . $fragment;
123
		}
124
125
		if ( $interwiki !== '' ) {
126
			$text = $interwiki . ':' . $text;
127
		}
128
129
		$text = str_replace( '_', ' ', $text );
130
131
		return $text;
132
	}
133
134
	/**
135
	 * Parses the given text and constructs a TitleValue. Normalization
136
	 * is applied according to the rules appropriate for the form specified by $form.
137
	 *
138
	 * @param string $text The text to parse
139
	 * @param int $defaultNamespace Namespace to assume per default (usually NS_MAIN)
140
	 *
141
	 * @throws MalformedTitleException
142
	 * @return TitleValue
143
	 */
144
	public function parseTitle( $text, $defaultNamespace ) {
145
		// NOTE: this is an ugly cludge that allows this class to share the
146
		// code for parsing with the old Title class. The parser code should
147
		// be refactored to avoid this.
148
		$parts = $this->splitTitleString( $text, $defaultNamespace );
149
150
		// Relative fragment links are not supported by TitleValue
151
		if ( $parts['dbkey'] === '' ) {
152
			throw new MalformedTitleException( 'title-invalid-empty', $text );
153
		}
154
155
		return new TitleValue(
156
			$parts['namespace'],
157
			$parts['dbkey'],
158
			$parts['fragment'],
159
			$parts['interwiki']
160
		);
161
	}
162
163
	/**
164
	 * @see TitleFormatter::getText()
165
	 *
166
	 * @param LinkTarget $title
167
	 *
168
	 * @return string $title->getText()
169
	 */
170
	public function getText( LinkTarget $title ) {
171
		return $this->formatTitle( false, $title->getText(), '' );
172
	}
173
174
	/**
175
	 * @see TitleFormatter::getText()
176
	 *
177
	 * @param LinkTarget $title
178
	 *
179
	 * @return string
180
	 */
181
	public function getPrefixedText( LinkTarget $title ) {
182
		return $this->formatTitle(
183
			$title->getNamespace(),
184
			$title->getText(),
185
			'',
186
			$title->getInterwiki()
187
		);
188
	}
189
190
	/**
191
	 * @since 1.27
192
	 * @see TitleFormatter::getPrefixedDBkey()
193
	 * @param LinkTarget $target
194
	 * @return string
195
	 */
196
	public function getPrefixedDBkey( LinkTarget $target ) {
197
		$key = '';
198
		if ( $target->isExternal() ) {
199
			$key .= $target->getInterwiki() . ':';
200
		}
201
		// Try to get a namespace name, but fallback
202
		// to empty string if it doesn't exist
203
		try {
204
			$nsName = $this->getNamespaceName(
205
				$target->getNamespace(),
206
				$target->getText()
207
			);
208
		} catch ( InvalidArgumentException $e ) {
209
			$nsName = '';
210
		}
211
212
		if ( $target->getNamespace() !== 0 ) {
213
			$key .= $nsName . ':';
214
		}
215
216
		$key .= $target->getText();
217
218
		return strtr( $key, ' ', '_' );
219
	}
220
221
	/**
222
	 * @see TitleFormatter::getText()
223
	 *
224
	 * @param LinkTarget $title
225
	 *
226
	 * @return string
227
	 */
228
	public function getFullText( LinkTarget $title ) {
229
		return $this->formatTitle(
230
			$title->getNamespace(),
231
			$title->getText(),
232
			$title->getFragment(),
233
			$title->getInterwiki()
234
		);
235
	}
236
237
	/**
238
	 * Normalizes and splits a title string.
239
	 *
240
	 * This function removes illegal characters, splits off the interwiki and
241
	 * namespace prefixes, sets the other forms, and canonicalizes
242
	 * everything.
243
	 *
244
	 * @todo this method is only exposed as a temporary measure to ease refactoring.
245
	 * It was copied with minimal changes from Title::secureAndSplit().
246
	 *
247
	 * @todo This method should be split up and an appropriate interface
248
	 * defined for use by the Title class.
249
	 *
250
	 * @param string $text
251
	 * @param int $defaultNamespace
252
	 *
253
	 * @throws MalformedTitleException If $text is not a valid title string.
254
	 * @return array A map with the fields 'interwiki', 'fragment', 'namespace',
255
	 *         'user_case_dbkey', and 'dbkey'.
256
	 */
257
	public function splitTitleString( $text, $defaultNamespace = NS_MAIN ) {
258
		$dbkey = str_replace( ' ', '_', $text );
259
260
		# Initialisation
261
		$parts = [
262
			'interwiki' => '',
263
			'local_interwiki' => false,
264
			'fragment' => '',
265
			'namespace' => $defaultNamespace,
266
			'dbkey' => $dbkey,
267
			'user_case_dbkey' => $dbkey,
268
		];
269
270
		# Strip Unicode bidi override characters.
271
		# Sometimes they slip into cut-n-pasted page titles, where the
272
		# override chars get included in list displays.
273
		$dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
274
275
		# Clean up whitespace
276
		# Note: use of the /u option on preg_replace here will cause
277
		# input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
278
		# conveniently disabling them.
279
		$dbkey = preg_replace(
280
			'/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u',
281
			'_',
282
			$dbkey
283
		);
284
		$dbkey = trim( $dbkey, '_' );
285
286
		if ( strpos( $dbkey, UtfNormal\Constants::UTF8_REPLACEMENT ) !== false ) {
287
			# Contained illegal UTF-8 sequences or forbidden Unicode chars.
288
			throw new MalformedTitleException( 'title-invalid-utf8', $text );
289
		}
290
291
		$parts['dbkey'] = $dbkey;
292
293
		# Initial colon indicates main namespace rather than specified default
294
		# but should not create invalid {ns,title} pairs such as {0,Project:Foo}
295 View Code Duplication
		if ( $dbkey !== '' && ':' == $dbkey[0] ) {
296
			$parts['namespace'] = NS_MAIN;
297
			$dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
298
			$dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
299
		}
300
301
		if ( $dbkey == '' ) {
302
			throw new MalformedTitleException( 'title-invalid-empty', $text );
303
		}
304
305
		# Namespace or interwiki prefix
306
		$prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
307
		do {
308
			$m = [];
309
			if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
310
				$p = $m[1];
311
				$ns = $this->language->getNsIndex( $p );
312
				if ( $ns !== false ) {
313
					# Ordinary namespace
314
					$dbkey = $m[2];
315
					$parts['namespace'] = $ns;
316
					# For Talk:X pages, check if X has a "namespace" prefix
317
					if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
318
						if ( $this->language->getNsIndex( $x[1] ) ) {
319
							# Disallow Talk:File:x type titles...
320
							throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
321
						} elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
322
							// TODO: get rid of global state!
323
							# Disallow Talk:Interwiki:x type titles...
324
							throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
325
						}
326
					}
327
				} elseif ( Interwiki::isValidInterwiki( $p ) ) {
328
					# Interwiki link
329
					$dbkey = $m[2];
330
					$parts['interwiki'] = $this->language->lc( $p );
331
332
					# Redundant interwiki prefix to the local wiki
333
					foreach ( $this->localInterwikis as $localIW ) {
334
						if ( 0 == strcasecmp( $parts['interwiki'], $localIW ) ) {
335
							if ( $dbkey == '' ) {
336
								# Empty self-links should point to the Main Page, to ensure
337
								# compatibility with cross-wiki transclusions and the like.
338
								$mainPage = Title::newMainPage();
339
								return [
340
									'interwiki' => $mainPage->getInterwiki(),
341
									'local_interwiki' => true,
342
									'fragment' => $mainPage->getFragment(),
343
									'namespace' => $mainPage->getNamespace(),
344
									'dbkey' => $mainPage->getDBkey(),
345
									'user_case_dbkey' => $mainPage->getUserCaseDBKey()
346
								];
347
							}
348
							$parts['interwiki'] = '';
349
							# local interwikis should behave like initial-colon links
350
							$parts['local_interwiki'] = true;
351
352
							# Do another namespace split...
353
							continue 2;
354
						}
355
					}
356
357
					# If there's an initial colon after the interwiki, that also
358
					# resets the default namespace
359 View Code Duplication
					if ( $dbkey !== '' && $dbkey[0] == ':' ) {
360
						$parts['namespace'] = NS_MAIN;
361
						$dbkey = substr( $dbkey, 1 );
362
					}
363
				}
364
				# If there's no recognized interwiki or namespace,
365
				# then let the colon expression be part of the title.
366
			}
367
			break;
368
		} while ( true );
369
370
		$fragment = strstr( $dbkey, '#' );
371
		if ( false !== $fragment ) {
372
			$parts['fragment'] = str_replace( '_', ' ', substr( $fragment, 1 ) );
373
			$dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
374
			# remove whitespace again: prevents "Foo_bar_#"
375
			# becoming "Foo_bar_"
376
			$dbkey = preg_replace( '/_*$/', '', $dbkey );
377
		}
378
379
		# Reject illegal characters.
380
		$rxTc = self::getTitleInvalidRegex();
381
		$matches = [];
382
		if ( preg_match( $rxTc, $dbkey, $matches ) ) {
383
			throw new MalformedTitleException( 'title-invalid-characters', $text, [ $matches[0] ] );
384
		}
385
386
		# Pages with "/./" or "/../" appearing in the URLs will often be un-
387
		# reachable due to the way web browsers deal with 'relative' URLs.
388
		# Also, they conflict with subpage syntax.  Forbid them explicitly.
389 View Code Duplication
		if (
390
			strpos( $dbkey, '.' ) !== false &&
391
			(
392
				$dbkey === '.' || $dbkey === '..' ||
393
				strpos( $dbkey, './' ) === 0 ||
394
				strpos( $dbkey, '../' ) === 0 ||
395
				strpos( $dbkey, '/./' ) !== false ||
396
				strpos( $dbkey, '/../' ) !== false ||
397
				substr( $dbkey, -2 ) == '/.' ||
398
				substr( $dbkey, -3 ) == '/..'
399
			)
400
		) {
401
			throw new MalformedTitleException( 'title-invalid-relative', $text );
402
		}
403
404
		# Magic tilde sequences? Nu-uh!
405
		if ( strpos( $dbkey, '~~~' ) !== false ) {
406
			throw new MalformedTitleException( 'title-invalid-magic-tilde', $text );
407
		}
408
409
		# Limit the size of titles to 255 bytes. This is typically the size of the
410
		# underlying database field. We make an exception for special pages, which
411
		# don't need to be stored in the database, and may edge over 255 bytes due
412
		# to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
413
		$maxLength = ( $parts['namespace'] != NS_SPECIAL ) ? 255 : 512;
414
		if ( strlen( $dbkey ) > $maxLength ) {
415
			throw new MalformedTitleException( 'title-invalid-too-long', $text,
416
				[ Message::numParam( $maxLength ) ] );
417
		}
418
419
		# Normally, all wiki links are forced to have an initial capital letter so [[foo]]
420
		# and [[Foo]] point to the same place.  Don't force it for interwikis, since the
421
		# other site might be case-sensitive.
422
		$parts['user_case_dbkey'] = $dbkey;
423
		if ( $parts['interwiki'] === '' ) {
424
			$dbkey = Title::capitalize( $dbkey, $parts['namespace'] );
425
		}
426
427
		# Can't make a link to a namespace alone... "empty" local links can only be
428
		# self-links with a fragment identifier.
429
		if ( $dbkey == '' && $parts['interwiki'] === '' ) {
430
			if ( $parts['namespace'] != NS_MAIN ) {
431
				throw new MalformedTitleException( 'title-invalid-empty', $text );
432
			}
433
		}
434
435
		// Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
436
		// IP names are not allowed for accounts, and can only be referring to
437
		// edits from the IP. Given '::' abbreviations and caps/lowercaps,
438
		// there are numerous ways to present the same IP. Having sp:contribs scan
439
		// them all is silly and having some show the edits and others not is
440
		// inconsistent. Same for talk/userpages. Keep them normalized instead.
441
		if ( $parts['namespace'] == NS_USER || $parts['namespace'] == NS_USER_TALK ) {
442
			$dbkey = IP::sanitizeIP( $dbkey );
443
		}
444
445
		// Any remaining initial :s are illegal.
446
		if ( $dbkey !== '' && ':' == $dbkey[0] ) {
447
			throw new MalformedTitleException( 'title-invalid-leading-colon', $text );
448
		}
449
450
		# Fill fields
451
		$parts['dbkey'] = $dbkey;
452
453
		return $parts;
454
	}
455
456
	/**
457
	 * Returns a simple regex that will match on characters and sequences invalid in titles.
458
	 * Note that this doesn't pick up many things that could be wrong with titles, but that
459
	 * replacing this regex with something valid will make many titles valid.
460
	 * Previously Title::getTitleInvalidRegex()
461
	 *
462
	 * @return string Regex string
463
	 * @since 1.25
464
	 */
465
	public static function getTitleInvalidRegex() {
466
		static $rxTc = false;
467
		if ( !$rxTc ) {
468
			# Matching titles will be held as illegal.
469
			$rxTc = '/' .
470
				# Any character not allowed is forbidden...
471
				'[^' . Title::legalChars() . ']' .
472
				# URL percent encoding sequences interfere with the ability
473
				# to round-trip titles -- you can't link to them consistently.
474
				'|%[0-9A-Fa-f]{2}' .
475
				# XML/HTML character references produce similar issues.
476
				'|&[A-Za-z0-9\x80-\xff]+;' .
477
				'|&#[0-9]+;' .
478
				'|&#x[0-9A-Fa-f]+;' .
479
				'/S';
480
		}
481
482
		return $rxTc;
483
	}
484
}
485