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/search/SearchOracle.php (5 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
 * Oracle search engine
4
 *
5
 * Copyright © 2004 Brion Vibber <[email protected]>
6
 * https://www.mediawiki.org/
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License along
19
 * with this program; if not, write to the Free Software Foundation, Inc.,
20
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
 * http://www.gnu.org/copyleft/gpl.html
22
 *
23
 * @file
24
 * @ingroup Search
25
 */
26
27
/**
28
 * Search engine hook base class for Oracle (ConText).
29
 * @ingroup Search
30
 */
31
class SearchOracle extends SearchDatabase {
32
	private $reservedWords = [
33
		'ABOUT' => 1,
34
		'ACCUM' => 1,
35
		'AND' => 1,
36
		'BT' => 1,
37
		'BTG' => 1,
38
		'BTI' => 1,
39
		'BTP' => 1,
40
		'FUZZY' => 1,
41
		'HASPATH' => 1,
42
		'INPATH' => 1,
43
		'MINUS' => 1,
44
		'NEAR' => 1,
45
		'NOT' => 1,
46
		'NT' => 1,
47
		'NTG' => 1,
48
		'NTI' => 1,
49
		'NTP' => 1,
50
		'OR' => 1,
51
		'PT' => 1,
52
		'RT' => 1,
53
		'SQE' => 1,
54
		'SYN' => 1,
55
		'TR' => 1,
56
		'TRSYN' => 1,
57
		'TT' => 1,
58
		'WITHIN' => 1,
59
	];
60
61
	/**
62
	 * Perform a full text search query and return a result set.
63
	 *
64
	 * @param string $term Raw search term
65
	 * @return SqlSearchResultSet
66
	 */
67 View Code Duplication
	function searchText( $term ) {
68
		if ( $term == '' ) {
69
			return new SqlSearchResultSet( false, '' );
70
		}
71
72
		$resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) );
73
		return new SqlSearchResultSet( $resultSet, $this->searchTerms );
0 ignored issues
show
It seems like $resultSet defined by $this->db->query($this->...->filter($term), true)) on line 72 can also be of type boolean; however, SqlSearchResultSet::__construct() does only seem to accept object<ResultWrapper>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
74
	}
75
76
	/**
77
	 * Perform a title-only search query and return a result set.
78
	 *
79
	 * @param string $term Raw search term
80
	 * @return SqlSearchResultSet
81
	 */
82 View Code Duplication
	function searchTitle( $term ) {
83
		if ( $term == '' ) {
84
			return new SqlSearchResultSet( false, '' );
85
		}
86
87
		$resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) );
88
		return new SqlSearchResultSet( $resultSet, $this->searchTerms );
0 ignored issues
show
It seems like $resultSet defined by $this->db->query($this->...>filter($term), false)) on line 87 can also be of type boolean; however, SqlSearchResultSet::__construct() does only seem to accept object<ResultWrapper>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
89
	}
90
91
	/**
92
	 * Return a partial WHERE clause to limit the search to the given namespaces
93
	 * @return string
94
	 */
95 View Code Duplication
	function queryNamespaces() {
96
		if ( is_null( $this->namespaces ) ) {
97
			return '';
98
		}
99
		if ( !count( $this->namespaces ) ) {
100
			$namespaces = '0';
101
		} else {
102
			$namespaces = $this->db->makeList( $this->namespaces );
103
		}
104
		return 'AND page_namespace IN (' . $namespaces . ')';
105
	}
106
107
	/**
108
	 * Return a LIMIT clause to limit results on the query.
109
	 *
110
	 * @param string $sql
111
	 *
112
	 * @return string
113
	 */
114
	function queryLimit( $sql ) {
115
		return $this->db->limitResult( $sql, $this->limit, $this->offset );
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface IDatabase as the method limitResult() does only exist in the following implementations of said interface: Database, DatabaseMysql, DatabaseMysqlBase, DatabaseMysqli, DatabasePostgres, DatabaseSqlite.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
116
	}
117
118
	/**
119
	 * Does not do anything for generic search engine
120
	 * subclasses may define this though
121
	 *
122
	 * @param string $filteredTerm
123
	 * @param bool $fulltext
124
	 * @return string
125
	 */
126
	function queryRanking( $filteredTerm, $fulltext ) {
127
		return ' ORDER BY score(1)';
128
	}
129
130
	/**
131
	 * Construct the full SQL query to do the search.
132
	 * The guts shoulds be constructed in queryMain()
133
	 * @param string $filteredTerm
134
	 * @param bool $fulltext
135
	 * @return string
136
	 */
137
	function getQuery( $filteredTerm, $fulltext ) {
138
		return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
139
			$this->queryNamespaces() . ' ' .
140
			$this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
141
	}
142
143
	/**
144
	 * Picks which field to index on, depending on what type of query.
145
	 * @param bool $fulltext
146
	 * @return string
147
	 */
148
	function getIndexField( $fulltext ) {
149
		return $fulltext ? 'si_text' : 'si_title';
150
	}
151
152
	/**
153
	 * Get the base part of the search query.
154
	 *
155
	 * @param string $filteredTerm
156
	 * @param bool $fulltext
157
	 * @return string
158
	 */
159 View Code Duplication
	function queryMain( $filteredTerm, $fulltext ) {
160
		$match = $this->parseQuery( $filteredTerm, $fulltext );
161
		$page = $this->db->tableName( 'page' );
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface IDatabase as the method tableName() does only exist in the following implementations of said interface: Database, DatabaseMysql, DatabaseMysqlBase, DatabaseMysqli, DatabasePostgres, DatabaseSqlite.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
162
		$searchindex = $this->db->tableName( 'searchindex' );
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface IDatabase as the method tableName() does only exist in the following implementations of said interface: Database, DatabaseMysql, DatabaseMysqlBase, DatabaseMysqli, DatabasePostgres, DatabaseSqlite.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
163
		return 'SELECT page_id, page_namespace, page_title ' .
164
			"FROM $page,$searchindex " .
165
			'WHERE page_id=si_page AND ' . $match;
166
	}
167
168
	/**
169
	 * Parse a user input search string, and return an SQL fragment to be used
170
	 * as part of a WHERE clause
171
	 * @param string $filteredText
172
	 * @param bool $fulltext
173
	 * @return string
174
	 */
175
	function parseQuery( $filteredText, $fulltext ) {
176
		global $wgContLang;
177
		$lc = $this->legalSearchChars();
178
		$this->searchTerms = [];
179
180
		# @todo FIXME: This doesn't handle parenthetical expressions.
181
		$m = [];
182
		$searchon = '';
183
		if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
184
				$filteredText, $m, PREG_SET_ORDER ) ) {
185
			foreach ( $m as $terms ) {
186
				// Search terms in all variant forms, only
187
				// apply on wiki with LanguageConverter
188
				$temp_terms = $wgContLang->autoConvertToAllVariants( $terms[2] );
189
				if ( is_array( $temp_terms ) ) {
190
					$temp_terms = array_unique( array_values( $temp_terms ) );
191
					foreach ( $temp_terms as $t ) {
192
						$searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $t );
193
					}
194
				} else {
195
					$searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $terms[2] );
196
				}
197 View Code Duplication
				if ( !empty( $terms[3] ) ) {
198
					$regexp = preg_quote( $terms[3], '/' );
199
					if ( $terms[4] ) {
200
						$regexp .= "[0-9A-Za-z_]+";
201
					}
202
				} else {
203
					$regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
204
				}
205
				$this->searchTerms[] = $regexp;
206
			}
207
		}
208
209
		$searchon = $this->db->addQuotes( ltrim( $searchon, ' &' ) );
210
		$field = $this->getIndexField( $fulltext );
211
		return " CONTAINS($field, $searchon, 1) > 0 ";
212
	}
213
214
	private function escapeTerm( $t ) {
215
		global $wgContLang;
216
		$t = $wgContLang->normalizeForSearch( $t );
217
		$t = isset( $this->reservedWords[strtoupper( $t )] ) ? '{' . $t . '}' : $t;
218
		$t = preg_replace( '/^"(.*)"$/', '($1)', $t );
219
		$t = preg_replace( '/([-&|])/', '\\\\$1', $t );
220
		return $t;
221
	}
222
223
	/**
224
	 * Create or update the search index record for the given page.
225
	 * Title and text should be pre-processed.
226
	 *
227
	 * @param int $id
228
	 * @param string $title
229
	 * @param string $text
230
	 */
231
	function update( $id, $title, $text ) {
232
		$dbw = wfGetDB( DB_MASTER );
233
		$dbw->replace( 'searchindex',
234
			[ 'si_page' ],
235
			[
236
				'si_page' => $id,
237
				'si_title' => $title,
238
				'si_text' => $text
239
			], 'SearchOracle::update' );
240
241
		// Sync the index
242
		// We need to specify the DB name (i.e. user/schema) here so that
243
		// it can work from the installer, where
244
		//     ALTER SESSION SET CURRENT_SCHEMA = ...
245
		// was used.
246
		$dbw->query( "CALL ctx_ddl.sync_index(" .
247
			$dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_text_idx', 'raw' ) ) . ")" );
248
		$dbw->query( "CALL ctx_ddl.sync_index(" .
249
			$dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', 'raw' ) ) . ")" );
250
	}
251
252
	/**
253
	 * Update a search index record's title only.
254
	 * Title should be pre-processed.
255
	 *
256
	 * @param int $id
257
	 * @param string $title
258
	 */
259
	function updateTitle( $id, $title ) {
260
		$dbw = wfGetDB( DB_MASTER );
261
262
		$dbw->update( 'searchindex',
263
			[ 'si_title' => $title ],
264
			[ 'si_page' => $id ],
265
			'SearchOracle::updateTitle',
266
			[] );
267
	}
268
269
	public static function legalSearchChars() {
270
		return "\"" . parent::legalSearchChars();
271
	}
272
}
273