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/specials/SpecialProtectedpages.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
 * Implements Special:Protectedpages
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
 * @ingroup SpecialPage
22
 */
23
24
use MediaWiki\Linker\LinkRenderer;
25
26
/**
27
 * A special page that lists protected pages
28
 *
29
 * @ingroup SpecialPage
30
 */
31
class SpecialProtectedpages extends SpecialPage {
32
	protected $IdLevel = 'level';
33
	protected $IdType = 'type';
34
35
	public function __construct() {
36
		parent::__construct( 'Protectedpages' );
37
	}
38
39
	public function execute( $par ) {
40
		$this->setHeaders();
41
		$this->outputHeader();
42
		$this->getOutput()->addModuleStyles( 'mediawiki.special' );
43
44
		$request = $this->getRequest();
45
		$type = $request->getVal( $this->IdType );
46
		$level = $request->getVal( $this->IdLevel );
47
		$sizetype = $request->getVal( 'sizetype' );
48
		$size = $request->getIntOrNull( 'size' );
49
		$ns = $request->getIntOrNull( 'namespace' );
50
		$indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
51
		$cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0;
52
		$noRedirect = $request->getBool( 'noredirect' ) ? 1 : 0;
53
54
		$pager = new ProtectedPagesPager(
55
			$this,
56
			[],
57
			$type,
58
			$level,
59
			$ns,
60
			$sizetype,
61
			$size,
62
			$indefOnly,
63
			$cascadeOnly,
64
			$noRedirect,
65
			$this->getLinkRenderer()
66
		);
67
68
		$this->getOutput()->addHTML( $this->showOptions(
69
			$ns,
70
			$type,
71
			$level,
72
			$sizetype,
73
			$size,
74
			$indefOnly,
75
			$cascadeOnly,
76
			$noRedirect
77
		) );
78
79
		if ( $pager->getNumRows() ) {
80
			$this->getOutput()->addParserOutputContent( $pager->getFullOutput() );
81
		} else {
82
			$this->getOutput()->addWikiMsg( 'protectedpagesempty' );
83
		}
84
	}
85
86
	/**
87
	 * @param int $namespace
88
	 * @param string $type Restriction type
89
	 * @param string $level Restriction level
90
	 * @param string $sizetype "min" or "max"
91
	 * @param int $size
92
	 * @param bool $indefOnly Only indefinite protection
93
	 * @param bool $cascadeOnly Only cascading protection
94
	 * @param bool $noRedirect Don't show redirects
95
	 * @return string Input form
96
	 */
97
	protected function showOptions( $namespace, $type = 'edit', $level, $sizetype,
98
		$size, $indefOnly, $cascadeOnly, $noRedirect
99
	) {
100
		$title = $this->getPageTitle();
101
102
		return Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) .
103
			Xml::openElement( 'fieldset' ) .
104
			Xml::element( 'legend', [], $this->msg( 'protectedpages' )->text() ) .
105
			Html::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" .
106
			$this->getNamespaceMenu( $namespace ) . "\n" .
107
			$this->getTypeMenu( $type ) . "\n" .
108
			$this->getLevelMenu( $level ) . "\n" .
109
			"<br />\n" .
110
			$this->getExpiryCheck( $indefOnly ) . "\n" .
111
			$this->getCascadeCheck( $cascadeOnly ) . "\n" .
112
			$this->getRedirectCheck( $noRedirect ) . "\n" .
113
			"<br />\n" .
114
			$this->getSizeLimit( $sizetype, $size ) . "\n" .
115
			Xml::submitButton( $this->msg( 'protectedpages-submit' )->text() ) . "\n" .
116
			Xml::closeElement( 'fieldset' ) .
117
			Xml::closeElement( 'form' );
118
	}
119
120
	/**
121
	 * Prepare the namespace filter drop-down; standard namespace
122
	 * selector, sans the MediaWiki namespace
123
	 *
124
	 * @param string|null $namespace Pre-select namespace
125
	 * @return string
126
	 */
127
	protected function getNamespaceMenu( $namespace = null ) {
128
		return Html::rawElement( 'span', [ 'class' => 'mw-input-with-label' ],
129
			Html::namespaceSelector(
130
				[
131
					'selected' => $namespace,
132
					'all' => '',
133
					'label' => $this->msg( 'namespace' )->text()
134
				], [
135
					'name' => 'namespace',
136
					'id' => 'namespace',
137
					'class' => 'namespaceselector',
138
				]
139
			)
140
		);
141
	}
142
143
	/**
144
	 * @param bool $indefOnly
145
	 * @return string Formatted HTML
146
	 */
147
	protected function getExpiryCheck( $indefOnly ) {
148
		return '<span class="mw-input-with-label">' . Xml::checkLabel(
149
			$this->msg( 'protectedpages-indef' )->text(),
150
			'indefonly',
151
			'indefonly',
152
			$indefOnly
153
		) . "</span>\n";
154
	}
155
156
	/**
157
	 * @param bool $cascadeOnly
158
	 * @return string Formatted HTML
159
	 */
160
	protected function getCascadeCheck( $cascadeOnly ) {
161
		return '<span class="mw-input-with-label">' . Xml::checkLabel(
162
			$this->msg( 'protectedpages-cascade' )->text(),
163
			'cascadeonly',
164
			'cascadeonly',
165
			$cascadeOnly
166
		) . "</span>\n";
167
	}
168
169
	/**
170
	 * @param bool $noRedirect
171
	 * @return string Formatted HTML
172
	 */
173
	protected function getRedirectCheck( $noRedirect ) {
174
		return '<span class="mw-input-with-label">' . Xml::checkLabel(
175
			$this->msg( 'protectedpages-noredirect' )->text(),
176
			'noredirect',
177
			'noredirect',
178
			$noRedirect
179
		) . "</span>\n";
180
	}
181
182
	/**
183
	 * @param string $sizetype "min" or "max"
184
	 * @param mixed $size
185
	 * @return string Formatted HTML
186
	 */
187
	protected function getSizeLimit( $sizetype, $size ) {
188
		$max = $sizetype === 'max';
189
190
		return '<span class="mw-input-with-label">' . Xml::radioLabel(
191
			$this->msg( 'minimum-size' )->text(),
192
			'sizetype',
193
			'min',
194
			'wpmin',
195
			!$max
196
		) .
197
			' ' .
198
			Xml::radioLabel(
199
				$this->msg( 'maximum-size' )->text(),
200
				'sizetype',
201
				'max',
202
				'wpmax',
203
				$max
204
			) .
205
			' ' .
206
			Xml::input( 'size', 9, $size, [ 'id' => 'wpsize' ] ) .
207
			' ' .
208
			Xml::label( $this->msg( 'pagesize' )->text(), 'wpsize' ) . "</span>\n";
209
	}
210
211
	/**
212
	 * Creates the input label of the restriction type
213
	 * @param string $pr_type Protection type
214
	 * @return string Formatted HTML
215
	 */
216
	protected function getTypeMenu( $pr_type ) {
217
		$m = []; // Temporary array
218
		$options = [];
219
220
		// First pass to load the log names
221
		foreach ( Title::getFilteredRestrictionTypes( true ) as $type ) {
222
			// Messages: restriction-edit, restriction-move, restriction-create, restriction-upload
223
			$text = $this->msg( "restriction-$type" )->text();
224
			$m[$text] = $type;
225
		}
226
227
		// Third pass generates sorted XHTML content
228
		foreach ( $m as $text => $type ) {
229
			$selected = ( $type == $pr_type );
230
			$options[] = Xml::option( $text, $type, $selected ) . "\n";
231
		}
232
233
		return '<span class="mw-input-with-label">' .
234
			Xml::label( $this->msg( 'restriction-type' )->text(), $this->IdType ) . ' ' .
235
			Xml::tags( 'select',
236
				[ 'id' => $this->IdType, 'name' => $this->IdType ],
237
				implode( "\n", $options ) ) . "</span>";
238
	}
239
240
	/**
241
	 * Creates the input label of the restriction level
242
	 * @param string $pr_level Protection level
243
	 * @return string Formatted HTML
244
	 */
245
	protected function getLevelMenu( $pr_level ) {
246
		// Temporary array
247
		$m = [ $this->msg( 'restriction-level-all' )->text() => 0 ];
248
		$options = [];
249
250
		// First pass to load the log names
251 View Code Duplication
		foreach ( $this->getConfig()->get( 'RestrictionLevels' ) as $type ) {
252
			// Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed'
253
			if ( $type != '' && $type != '*' ) {
254
				$text = $this->msg( "restriction-level-$type" )->text();
255
				$m[$text] = $type;
256
			}
257
		}
258
259
		// Third pass generates sorted XHTML content
260 View Code Duplication
		foreach ( $m as $text => $type ) {
261
			$selected = ( $type == $pr_level );
262
			$options[] = Xml::option( $text, $type, $selected );
263
		}
264
265
		return '<span class="mw-input-with-label">' .
266
			Xml::label( $this->msg( 'restriction-level' )->text(), $this->IdLevel ) . ' ' .
267
			Xml::tags( 'select',
268
				[ 'id' => $this->IdLevel, 'name' => $this->IdLevel ],
269
				implode( "\n", $options ) ) . "</span>";
270
	}
271
272
	protected function getGroupName() {
273
		return 'maintenance';
274
	}
275
}
276
277
/**
278
 * @todo document
279
 * @ingroup Pager
280
 */
281
class ProtectedPagesPager extends TablePager {
282
	public $mForm, $mConds;
0 ignored issues
show
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
283
	private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
0 ignored issues
show
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
284
285
	/**
286
	 * @var LinkRenderer
287
	 */
288
	private $linkRenderer;
289
290
	/**
291
	 * @param SpecialProtectedpages $form
292
	 * @param array $conds
293
	 * @param $type
294
	 * @param $level
295
	 * @param $namespace
296
	 * @param string $sizetype
297
	 * @param int $size
298
	 * @param bool $indefonly
299
	 * @param bool $cascadeonly
300
	 * @param bool $noredirect
301
	 * @param LinkRenderer $linkRenderer
302
	 */
303
	function __construct( $form, $conds = [], $type, $level, $namespace,
304
		$sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false, $noredirect = false,
305
		LinkRenderer $linkRenderer
306
	) {
307
		$this->mForm = $form;
308
		$this->mConds = $conds;
309
		$this->type = ( $type ) ? $type : 'edit';
310
		$this->level = $level;
311
		$this->namespace = $namespace;
312
		$this->sizetype = $sizetype;
313
		$this->size = intval( $size );
314
		$this->indefonly = (bool)$indefonly;
315
		$this->cascadeonly = (bool)$cascadeonly;
316
		$this->noredirect = (bool)$noredirect;
317
		$this->linkRenderer = $linkRenderer;
318
		parent::__construct( $form->getContext() );
319
	}
320
321
	function preprocessResults( $result ) {
322
		# Do a link batch query
323
		$lb = new LinkBatch;
324
		$userids = [];
325
326
		foreach ( $result as $row ) {
327
			$lb->add( $row->page_namespace, $row->page_title );
328
			// field is nullable, maybe null on old protections
329
			if ( $row->log_user !== null ) {
330
				$userids[] = $row->log_user;
331
			}
332
		}
333
334
		// fill LinkBatch with user page and user talk
335
		if ( count( $userids ) ) {
336
			$userCache = UserCache::singleton();
337
			$userCache->doQuery( $userids, [], __METHOD__ );
338
			foreach ( $userids as $userid ) {
339
				$name = $userCache->getProp( $userid, 'name' );
340
				if ( $name !== false ) {
341
					$lb->add( NS_USER, $name );
342
					$lb->add( NS_USER_TALK, $name );
343
				}
344
			}
345
		}
346
347
		$lb->execute();
348
	}
349
350
	function getFieldNames() {
351
		static $headers = null;
352
353
		if ( $headers == [] ) {
354
			$headers = [
355
				'log_timestamp' => 'protectedpages-timestamp',
356
				'pr_page' => 'protectedpages-page',
357
				'pr_expiry' => 'protectedpages-expiry',
358
				'log_user' => 'protectedpages-performer',
359
				'pr_params' => 'protectedpages-params',
360
				'log_comment' => 'protectedpages-reason',
361
			];
362
			foreach ( $headers as $key => $val ) {
363
				$headers[$key] = $this->msg( $val )->text();
364
			}
365
		}
366
367
		return $headers;
368
	}
369
370
	/**
371
	 * @param string $field
372
	 * @param string $value
373
	 * @return string HTML
374
	 * @throws MWException
375
	 */
376
	function formatValue( $field, $value ) {
377
		/** @var $row object */
378
		$row = $this->mCurrentRow;
379
380
		switch ( $field ) {
381
			case 'log_timestamp':
382
				// when timestamp is null, this is a old protection row
383
				if ( $value === null ) {
384
					$formatted = Html::rawElement(
385
						'span',
386
						[ 'class' => 'mw-protectedpages-unknown' ],
387
						$this->msg( 'protectedpages-unknown-timestamp' )->escaped()
388
					);
389
				} else {
390
					$formatted = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
391
						$value, $this->getUser() ) );
392
				}
393
				break;
394
395
			case 'pr_page':
396
				$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
397
				if ( !$title ) {
398
					$formatted = Html::element(
399
						'span',
400
						[ 'class' => 'mw-invalidtitle' ],
401
						Linker::getInvalidTitleDescription(
402
							$this->getContext(),
403
							$row->page_namespace,
404
							$row->page_title
405
						)
406
					);
407
				} else {
408
					$formatted = $this->linkRenderer->makeLink( $title );
409
				}
410
				if ( !is_null( $row->page_len ) ) {
411
					$formatted .= $this->getLanguage()->getDirMark() .
412
						' ' . Html::rawElement(
413
						'span',
414
						[ 'class' => 'mw-protectedpages-length' ],
415
						Linker::formatRevisionSize( $row->page_len )
416
					);
417
				}
418
				break;
419
420
			case 'pr_expiry':
421
				$formatted = htmlspecialchars( $this->getLanguage()->formatExpiry(
422
					$value, /* User preference timezone */true ) );
423
				$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
424
				if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
425
					$changeProtection = $this->linkRenderer->makeKnownLink(
426
						$title,
427
						$this->msg( 'protect_change' )->text(),
428
						[],
429
						[ 'action' => 'unprotect' ]
430
					);
431
					$formatted .= ' ' . Html::rawElement(
432
						'span',
433
						[ 'class' => 'mw-protectedpages-actions' ],
434
						$this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped()
435
					);
436
				}
437
				break;
438
439
			case 'log_user':
440
				// when timestamp is null, this is a old protection row
441
				if ( $row->log_timestamp === null ) {
442
					$formatted = Html::rawElement(
443
						'span',
444
						[ 'class' => 'mw-protectedpages-unknown' ],
445
						$this->msg( 'protectedpages-unknown-performer' )->escaped()
446
					);
447
				} else {
448
					$username = UserCache::singleton()->getProp( $value, 'name' );
449
					if ( LogEventsList::userCanBitfield(
450
						$row->log_deleted,
451
						LogPage::DELETED_USER,
452
						$this->getUser()
453
					) ) {
454
						if ( $username === false ) {
455
							$formatted = htmlspecialchars( $value );
456
						} else {
457
							$formatted = Linker::userLink( $value, $username )
458
								. Linker::userToolLinks( $value, $username );
459
						}
460
					} else {
461
						$formatted = $this->msg( 'rev-deleted-user' )->escaped();
462
					}
463
					if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
464
						$formatted = '<span class="history-deleted">' . $formatted . '</span>';
465
					}
466
				}
467
				break;
468
469
			case 'pr_params':
470
				$params = [];
471
				// Messages: restriction-level-sysop, restriction-level-autoconfirmed
472
				$params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
473
				if ( $row->pr_cascade ) {
474
					$params[] = $this->msg( 'protect-summary-cascade' )->escaped();
475
				}
476
				$formatted = $this->getLanguage()->commaList( $params );
477
				break;
478
479
			case 'log_comment':
480
				// when timestamp is null, this is an old protection row
481
				if ( $row->log_timestamp === null ) {
482
					$formatted = Html::rawElement(
483
						'span',
484
						[ 'class' => 'mw-protectedpages-unknown' ],
485
						$this->msg( 'protectedpages-unknown-reason' )->escaped()
486
					);
487
				} else {
488
					if ( LogEventsList::userCanBitfield(
489
						$row->log_deleted,
490
						LogPage::DELETED_COMMENT,
491
						$this->getUser()
492
					) ) {
493
						$formatted = Linker::formatComment( $value !== null ? $value : '' );
494
					} else {
495
						$formatted = $this->msg( 'rev-deleted-comment' )->escaped();
496
					}
497
					if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
498
						$formatted = '<span class="history-deleted">' . $formatted . '</span>';
499
					}
500
				}
501
				break;
502
503
			default:
504
				throw new MWException( "Unknown field '$field'" );
505
		}
506
507
		return $formatted;
508
	}
509
510
	function getQueryInfo() {
511
		$conds = $this->mConds;
512
		$conds[] = 'pr_expiry > ' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
513
			' OR pr_expiry IS NULL';
514
		$conds[] = 'page_id=pr_page';
515
		$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
516
517
		if ( $this->sizetype == 'min' ) {
518
			$conds[] = 'page_len>=' . $this->size;
519
		} elseif ( $this->sizetype == 'max' ) {
520
			$conds[] = 'page_len<=' . $this->size;
521
		}
522
523
		if ( $this->indefonly ) {
524
			$infinity = $this->mDb->addQuotes( $this->mDb->getInfinity() );
525
			$conds[] = "pr_expiry = $infinity OR pr_expiry IS NULL";
526
		}
527
		if ( $this->cascadeonly ) {
528
			$conds[] = 'pr_cascade = 1';
529
		}
530
		if ( $this->noredirect ) {
531
			$conds[] = 'page_is_redirect = 0';
532
		}
533
534
		if ( $this->level ) {
535
			$conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level );
536
		}
537 View Code Duplication
		if ( !is_null( $this->namespace ) ) {
538
			$conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace );
539
		}
540
541
		return [
542
			'tables' => [ 'page', 'page_restrictions', 'log_search', 'logging' ],
543
			'fields' => [
544
				'pr_id',
545
				'page_namespace',
546
				'page_title',
547
				'page_len',
548
				'pr_type',
549
				'pr_level',
550
				'pr_expiry',
551
				'pr_cascade',
552
				'log_timestamp',
553
				'log_user',
554
				'log_comment',
555
				'log_deleted',
556
			],
557
			'conds' => $conds,
558
			'join_conds' => [
559
				'log_search' => [
560
					'LEFT JOIN', [
561
						'ls_field' => 'pr_id', 'ls_value = ' . $this->mDb->buildStringCast( 'pr_id' )
562
					]
563
				],
564
				'logging' => [
565
					'LEFT JOIN', [
566
						'ls_log_id = log_id'
567
					]
568
				]
569
			]
570
		];
571
	}
572
573
	protected function getTableClass() {
574
		return parent::getTableClass() . ' mw-protectedpages';
575
	}
576
577
	function getIndexField() {
578
		return 'pr_id';
579
	}
580
581
	function getDefaultSort() {
582
		return 'pr_id';
583
	}
584
585
	function isFieldSortable( $field ) {
586
		// no index for sorting exists
587
		return false;
588
	}
589
}
590