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/MagicWordArray.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
/**
4
 * See docs/magicword.txt.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along
17
 * with this program; if not, write to the Free Software Foundation, Inc.,
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 * http://www.gnu.org/copyleft/gpl.html
20
 *
21
 * @file
22
 * @ingroup Parser
23
 */
24
25
use MediaWiki\Logger\LoggerFactory;
26
27
/**
28
 * Class for handling an array of magic words
29
 * @ingroup Parser
30
 */
31
class MagicWordArray {
32
	/** @var array */
33
	public $names = [];
34
35
	/** @var array */
36
	private $hash;
37
38
	private $baseRegex;
39
40
	private $regex;
41
42
	/**
43
	 * @param array $names
44
	 */
45
	public function __construct( $names = [] ) {
46
		$this->names = $names;
47
	}
48
49
	/**
50
	 * Add a magic word by name
51
	 *
52
	 * @param string $name
53
	 */
54
	public function add( $name ) {
55
		$this->names[] = $name;
56
		$this->hash = $this->baseRegex = $this->regex = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->baseRegex = $this->regex = null of type null is incompatible with the declared type array of property $hash.

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..

Loading history...
57
	}
58
59
	/**
60
	 * Add a number of magic words by name
61
	 *
62
	 * @param array $names
63
	 */
64
	public function addArray( $names ) {
65
		$this->names = array_merge( $this->names, array_values( $names ) );
66
		$this->hash = $this->baseRegex = $this->regex = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->baseRegex = $this->regex = null of type null is incompatible with the declared type array of property $hash.

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..

Loading history...
67
	}
68
69
	/**
70
	 * Get a 2-d hashtable for this array
71
	 * @return array
72
	 */
73
	public function getHash() {
74
		if ( is_null( $this->hash ) ) {
75
			global $wgContLang;
76
			$this->hash = [ 0 => [], 1 => [] ];
77
			foreach ( $this->names as $name ) {
78
				$magic = MagicWord::get( $name );
79
				$case = intval( $magic->isCaseSensitive() );
80
				foreach ( $magic->getSynonyms() as $syn ) {
81
					if ( !$case ) {
82
						$syn = $wgContLang->lc( $syn );
83
					}
84
					$this->hash[$case][$syn] = $name;
85
				}
86
			}
87
		}
88
		return $this->hash;
89
	}
90
91
	/**
92
	 * Get the base regex
93
	 * @return array
94
	 */
95
	public function getBaseRegex() {
96
		if ( is_null( $this->baseRegex ) ) {
97
			$this->baseRegex = [ 0 => '', 1 => '' ];
98
			foreach ( $this->names as $name ) {
99
				$magic = MagicWord::get( $name );
100
				$case = intval( $magic->isCaseSensitive() );
101
				foreach ( $magic->getSynonyms() as $i => $syn ) {
102
					// Group name must start with a non-digit in PCRE 8.34+
103
					$it = strtr( $i, '0123456789', 'abcdefghij' );
104
					$group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
105
					if ( $this->baseRegex[$case] === '' ) {
106
						$this->baseRegex[$case] = $group;
107
					} else {
108
						$this->baseRegex[$case] .= '|' . $group;
109
					}
110
				}
111
			}
112
		}
113
		return $this->baseRegex;
114
	}
115
116
	/**
117
	 * Get an unanchored regex that does not match parameters
118
	 * @return array
119
	 */
120
	public function getRegex() {
121
		if ( is_null( $this->regex ) ) {
122
			$base = $this->getBaseRegex();
123
			$this->regex = [ '', '' ];
124 View Code Duplication
			if ( $this->baseRegex[0] !== '' ) {
125
				$this->regex[0] = "/{$base[0]}/iuS";
126
			}
127 View Code Duplication
			if ( $this->baseRegex[1] !== '' ) {
128
				$this->regex[1] = "/{$base[1]}/S";
129
			}
130
		}
131
		return $this->regex;
132
	}
133
134
	/**
135
	 * Get a regex for matching variables with parameters
136
	 *
137
	 * @return string
138
	 */
139
	public function getVariableRegex() {
140
		return str_replace( "\\$1", "(.*?)", $this->getRegex() );
141
	}
142
143
	/**
144
	 * Get a regex anchored to the start of the string that does not match parameters
145
	 *
146
	 * @return array
147
	 */
148
	public function getRegexStart() {
149
		$base = $this->getBaseRegex();
150
		$newRegex = [ '', '' ];
151
		if ( $base[0] !== '' ) {
152
			$newRegex[0] = "/^(?:{$base[0]})/iuS";
153
		}
154
		if ( $base[1] !== '' ) {
155
			$newRegex[1] = "/^(?:{$base[1]})/S";
156
		}
157
		return $newRegex;
158
	}
159
160
	/**
161
	 * Get an anchored regex for matching variables with parameters
162
	 *
163
	 * @return array
164
	 */
165
	public function getVariableStartToEndRegex() {
166
		$base = $this->getBaseRegex();
167
		$newRegex = [ '', '' ];
168 View Code Duplication
		if ( $base[0] !== '' ) {
169
			$newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
170
		}
171 View Code Duplication
		if ( $base[1] !== '' ) {
172
			$newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
173
		}
174
		return $newRegex;
175
	}
176
177
	/**
178
	 * @since 1.20
179
	 * @return array
180
	 */
181
	public function getNames() {
182
		return $this->names;
183
	}
184
185
	/**
186
	 * Parse a match array from preg_match
187
	 * Returns array(magic word ID, parameter value)
188
	 * If there is no parameter value, that element will be false.
189
	 *
190
	 * @param array $m
191
	 *
192
	 * @throws MWException
193
	 * @return array
194
	 */
195
	public function parseMatch( $m ) {
196
		reset( $m );
197
		while ( list( $key, $value ) = each( $m ) ) {
198
			if ( $key === 0 || $value === '' ) {
199
				continue;
200
			}
201
			$parts = explode( '_', $key, 2 );
202
			if ( count( $parts ) != 2 ) {
203
				// This shouldn't happen
204
				// continue;
205
				throw new MWException( __METHOD__ . ': bad parameter name' );
206
			}
207
			list( /* $synIndex */, $magicName ) = $parts;
208
			$paramValue = next( $m );
209
			return [ $magicName, $paramValue ];
210
		}
211
		// This shouldn't happen either
212
		throw new MWException( __METHOD__ . ': parameter not found' );
213
	}
214
215
	/**
216
	 * Match some text, with parameter capture
217
	 * Returns an array with the magic word name in the first element and the
218
	 * parameter in the second element.
219
	 * Both elements are false if there was no match.
220
	 *
221
	 * @param string $text
222
	 *
223
	 * @return array
224
	 */
225
	public function matchVariableStartToEnd( $text ) {
226
		$regexes = $this->getVariableStartToEndRegex();
227
		foreach ( $regexes as $regex ) {
228
			if ( $regex !== '' ) {
229
				$m = [];
230
				if ( preg_match( $regex, $text, $m ) ) {
231
					return $this->parseMatch( $m );
232
				}
233
			}
234
		}
235
		return [ false, false ];
236
	}
237
238
	/**
239
	 * Match some text, without parameter capture
240
	 * Returns the magic word name, or false if there was no capture
241
	 *
242
	 * @param string $text
243
	 *
244
	 * @return string|bool False on failure
245
	 */
246
	public function matchStartToEnd( $text ) {
247
		$hash = $this->getHash();
248
		if ( isset( $hash[1][$text] ) ) {
249
			return $hash[1][$text];
250
		}
251
		global $wgContLang;
252
		$lc = $wgContLang->lc( $text );
253
		if ( isset( $hash[0][$lc] ) ) {
254
			return $hash[0][$lc];
255
		}
256
		return false;
257
	}
258
259
	/**
260
	 * Returns an associative array, ID => param value, for all items that match
261
	 * Removes the matched items from the input string (passed by reference)
262
	 *
263
	 * @param string $text
264
	 *
265
	 * @return array
266
	 */
267
	public function matchAndRemove( &$text ) {
268
		$found = [];
269
		$regexes = $this->getRegex();
270
		foreach ( $regexes as $regex ) {
271
			if ( $regex === '' ) {
272
				continue;
273
			}
274
			$matches = [];
275
			$res = preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
276
			if ( $res === false ) {
277
				LoggerFactory::getInstance( 'parser' )->warning( 'preg_match_all returned false', [
278
					'code' => preg_last_error(),
279
					'regex' => $regex,
280
					'text' => $text,
281
				] );
282
			} elseif ( $res ) {
283
				foreach ( $matches as $m ) {
284
					list( $name, $param ) = $this->parseMatch( $m );
285
					$found[$name] = $param;
286
				}
287
			}
288
			$res = preg_replace( $regex, '', $text );
289
			if ( $res === null ) {
290
				LoggerFactory::getInstance( 'parser' )->warning( 'preg_replace returned null', [
291
					'code' => preg_last_error(),
292
					'regex' => $regex,
293
					'text' => $text,
294
				] );
295
			}
296
			$text = $res;
297
		}
298
		return $found;
299
	}
300
301
	/**
302
	 * Return the ID of the magic word at the start of $text, and remove
303
	 * the prefix from $text.
304
	 * Return false if no match found and $text is not modified.
305
	 * Does not match parameters.
306
	 *
307
	 * @param string $text
308
	 *
309
	 * @return int|bool False on failure
310
	 */
311
	public function matchStartAndRemove( &$text ) {
312
		$regexes = $this->getRegexStart();
313
		foreach ( $regexes as $regex ) {
314
			if ( $regex === '' ) {
315
				continue;
316
			}
317
			if ( preg_match( $regex, $text, $m ) ) {
318
				list( $id, ) = $this->parseMatch( $m );
319 View Code Duplication
				if ( strlen( $m[0] ) >= strlen( $text ) ) {
320
					$text = '';
321
				} else {
322
					$text = substr( $text, strlen( $m[0] ) );
323
				}
324
				return $id;
325
			}
326
		}
327
		return false;
328
	}
329
}
330