This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Sqlite-specific installer. |
||
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 Deployment |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * Class for setting up the MediaWiki database using SQLLite. |
||
26 | * |
||
27 | * @ingroup Deployment |
||
28 | * @since 1.17 |
||
29 | */ |
||
30 | class SqliteInstaller extends DatabaseInstaller { |
||
31 | const MINIMUM_VERSION = '3.3.7'; |
||
32 | |||
33 | /** |
||
34 | * @var DatabaseSqlite |
||
35 | */ |
||
36 | public $db; |
||
37 | |||
38 | protected $globalNames = [ |
||
39 | 'wgDBname', |
||
40 | 'wgSQLiteDataDir', |
||
41 | ]; |
||
42 | |||
43 | public function getName() { |
||
44 | return 'sqlite'; |
||
45 | } |
||
46 | |||
47 | public function isCompiled() { |
||
48 | return self::checkExtension( 'pdo_sqlite' ); |
||
49 | } |
||
50 | |||
51 | /** |
||
52 | * |
||
53 | * @return Status |
||
54 | */ |
||
55 | public function checkPrerequisites() { |
||
56 | $result = Status::newGood(); |
||
57 | // Bail out if SQLite is too old |
||
58 | $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); |
||
59 | if ( version_compare( $db->getServerVersion(), self::MINIMUM_VERSION, '<' ) ) { |
||
60 | $result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), self::MINIMUM_VERSION ); |
||
61 | } |
||
62 | // Check for FTS3 full-text search module |
||
63 | if ( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) { |
||
64 | $result->warning( 'config-no-fts3' ); |
||
65 | } |
||
66 | |||
67 | return $result; |
||
68 | } |
||
69 | |||
70 | public function getGlobalDefaults() { |
||
0 ignored issues
–
show
|
|||
71 | $defaults = parent::getGlobalDefaults(); |
||
72 | if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) { |
||
73 | $path = str_replace( |
||
74 | [ '/', '\\' ], |
||
75 | DIRECTORY_SEPARATOR, |
||
76 | dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data' |
||
77 | ); |
||
78 | |||
79 | $defaults['wgSQLiteDataDir'] = $path; |
||
80 | } |
||
81 | return $defaults; |
||
82 | } |
||
83 | |||
84 | public function getConnectForm() { |
||
85 | return $this->getTextBox( |
||
86 | 'wgSQLiteDataDir', |
||
87 | 'config-sqlite-dir', [], |
||
88 | $this->parent->getHelpBox( 'config-sqlite-dir-help' ) |
||
89 | ) . |
||
90 | $this->getTextBox( |
||
91 | 'wgDBname', |
||
92 | 'config-db-name', |
||
93 | [], |
||
94 | $this->parent->getHelpBox( 'config-sqlite-name-help' ) |
||
95 | ); |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Safe wrapper for PHP's realpath() that fails gracefully if it's unable to canonicalize the path. |
||
100 | * |
||
101 | * @param string $path |
||
102 | * |
||
103 | * @return string |
||
104 | */ |
||
105 | private static function realpath( $path ) { |
||
106 | $result = realpath( $path ); |
||
107 | if ( !$result ) { |
||
108 | return $path; |
||
109 | } |
||
110 | |||
111 | return $result; |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * @return Status |
||
116 | */ |
||
117 | public function submitConnectForm() { |
||
118 | $this->setVarsFromRequest( [ 'wgSQLiteDataDir', 'wgDBname' ] ); |
||
119 | |||
120 | # Try realpath() if the directory already exists |
||
121 | $dir = self::realpath( $this->getVar( 'wgSQLiteDataDir' ) ); |
||
122 | $result = self::dataDirOKmaybeCreate( $dir, true /* create? */ ); |
||
123 | if ( $result->isOK() ) { |
||
124 | # Try expanding again in case we've just created it |
||
125 | $dir = self::realpath( $dir ); |
||
126 | $this->setVar( 'wgSQLiteDataDir', $dir ); |
||
127 | } |
||
128 | # Table prefix is not used on SQLite, keep it empty |
||
129 | $this->setVar( 'wgDBprefix', '' ); |
||
130 | |||
131 | return $result; |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * @param string $dir |
||
136 | * @param bool $create |
||
137 | * @return Status |
||
138 | */ |
||
139 | private static function dataDirOKmaybeCreate( $dir, $create = false ) { |
||
140 | if ( !is_dir( $dir ) ) { |
||
141 | if ( !is_writable( dirname( $dir ) ) ) { |
||
142 | $webserverGroup = Installer::maybeGetWebserverPrimaryGroup(); |
||
143 | if ( $webserverGroup !== null ) { |
||
144 | return Status::newFatal( |
||
145 | 'config-sqlite-parent-unwritable-group', |
||
146 | $dir, dirname( $dir ), basename( $dir ), |
||
147 | $webserverGroup |
||
148 | ); |
||
149 | } else { |
||
150 | return Status::newFatal( |
||
151 | 'config-sqlite-parent-unwritable-nogroup', |
||
152 | $dir, dirname( $dir ), basename( $dir ) |
||
153 | ); |
||
154 | } |
||
155 | } |
||
156 | |||
157 | # Called early on in the installer, later we just want to sanity check |
||
158 | # if it's still writable |
||
159 | if ( $create ) { |
||
160 | MediaWiki\suppressWarnings(); |
||
161 | $ok = wfMkdirParents( $dir, 0700, __METHOD__ ); |
||
162 | MediaWiki\restoreWarnings(); |
||
163 | if ( !$ok ) { |
||
164 | return Status::newFatal( 'config-sqlite-mkdir-error', $dir ); |
||
165 | } |
||
166 | # Put a .htaccess file in in case the user didn't take our advice |
||
167 | file_put_contents( "$dir/.htaccess", "Deny from all\n" ); |
||
168 | } |
||
169 | } |
||
170 | if ( !is_writable( $dir ) ) { |
||
171 | return Status::newFatal( 'config-sqlite-dir-unwritable', $dir ); |
||
172 | } |
||
173 | |||
174 | # We haven't blown up yet, fall through |
||
175 | return Status::newGood(); |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * @return Status |
||
180 | */ |
||
181 | public function openConnection() { |
||
182 | $status = Status::newGood(); |
||
183 | $dir = $this->getVar( 'wgSQLiteDataDir' ); |
||
184 | $dbName = $this->getVar( 'wgDBname' ); |
||
185 | try { |
||
186 | # @todo FIXME: Need more sensible constructor parameters, e.g. single associative array |
||
187 | $db = Database::factory( 'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ] ); |
||
188 | $status->value = $db; |
||
189 | } catch ( DBConnectionError $e ) { |
||
190 | $status->fatal( 'config-sqlite-connection-error', $e->getMessage() ); |
||
191 | } |
||
192 | |||
193 | return $status; |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * @return bool |
||
198 | */ |
||
199 | public function needsUpgrade() { |
||
200 | $dir = $this->getVar( 'wgSQLiteDataDir' ); |
||
201 | $dbName = $this->getVar( 'wgDBname' ); |
||
202 | // Don't create the data file yet |
||
203 | if ( !file_exists( DatabaseSqlite::generateFileName( $dir, $dbName ) ) ) { |
||
204 | return false; |
||
205 | } |
||
206 | |||
207 | // If the data file exists, look inside it |
||
208 | return parent::needsUpgrade(); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * @return Status |
||
213 | */ |
||
214 | public function setupDatabase() { |
||
215 | $dir = $this->getVar( 'wgSQLiteDataDir' ); |
||
216 | |||
217 | # Sanity check. We checked this before but maybe someone deleted the |
||
218 | # data dir between then and now |
||
219 | $dir_status = self::dataDirOKmaybeCreate( $dir, false /* create? */ ); |
||
220 | if ( !$dir_status->isOK() ) { |
||
221 | return $dir_status; |
||
222 | } |
||
223 | |||
224 | $db = $this->getVar( 'wgDBname' ); |
||
225 | |||
226 | # Make the main and cache stub DB files |
||
227 | $status = Status::newGood(); |
||
228 | $status->merge( $this->makeStubDBFile( $dir, $db ) ); |
||
229 | $status->merge( $this->makeStubDBFile( $dir, "wikicache" ) ); |
||
230 | if ( !$status->isOK() ) { |
||
231 | return $status; |
||
232 | } |
||
233 | |||
234 | # Nuke the unused settings for clarity |
||
235 | $this->setVar( 'wgDBserver', '' ); |
||
236 | $this->setVar( 'wgDBuser', '' ); |
||
237 | $this->setVar( 'wgDBpassword', '' ); |
||
238 | $this->setupSchemaVars(); |
||
239 | |||
240 | # Create the global cache DB |
||
241 | try { |
||
242 | $conn = Database::factory( 'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] ); |
||
243 | # @todo: don't duplicate objectcache definition, though it's very simple |
||
244 | $sql = |
||
245 | <<<EOT |
||
246 | CREATE TABLE IF NOT EXISTS objectcache ( |
||
247 | keyname BLOB NOT NULL default '' PRIMARY KEY, |
||
248 | value BLOB, |
||
249 | exptime TEXT |
||
250 | ) |
||
251 | EOT; |
||
252 | $conn->query( $sql ); |
||
253 | $conn->query( "CREATE INDEX IF NOT EXISTS exptime ON objectcache (exptime)" ); |
||
254 | $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent |
||
255 | $conn->close(); |
||
256 | } catch ( DBConnectionError $e ) { |
||
257 | return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() ); |
||
258 | } |
||
259 | |||
260 | # Open the main DB |
||
261 | return $this->getConnection(); |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * @param $dir |
||
266 | * @param $db |
||
267 | * @return Status |
||
268 | */ |
||
269 | protected function makeStubDBFile( $dir, $db ) { |
||
270 | $file = DatabaseSqlite::generateFileName( $dir, $db ); |
||
271 | if ( file_exists( $file ) ) { |
||
272 | if ( !is_writable( $file ) ) { |
||
273 | return Status::newFatal( 'config-sqlite-readonly', $file ); |
||
274 | } |
||
275 | } else { |
||
276 | if ( file_put_contents( $file, '' ) === false ) { |
||
277 | return Status::newFatal( 'config-sqlite-cant-create-db', $file ); |
||
278 | } |
||
279 | } |
||
280 | |||
281 | return Status::newGood(); |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * @return Status |
||
286 | */ |
||
287 | public function createTables() { |
||
288 | $status = parent::createTables(); |
||
289 | |||
290 | return $this->setupSearchIndex( $status ); |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * @param Status $status |
||
295 | * @return Status |
||
296 | */ |
||
297 | public function setupSearchIndex( &$status ) { |
||
298 | global $IP; |
||
299 | |||
300 | $module = DatabaseSqlite::getFulltextSearchModule(); |
||
301 | $fts3tTable = $this->db->checkForEnabledSearch(); |
||
302 | if ( $fts3tTable && !$module ) { |
||
303 | $status->warning( 'config-sqlite-fts3-downgrade' ); |
||
304 | $this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-no-fts.sql" ); |
||
305 | } elseif ( !$fts3tTable && $module == 'FTS3' ) { |
||
306 | $this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-fts3.sql" ); |
||
307 | } |
||
308 | |||
309 | return $status; |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * @return string |
||
314 | */ |
||
315 | public function getLocalSettings() { |
||
316 | $dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) ); |
||
317 | |||
318 | return "# SQLite-specific settings |
||
319 | \$wgSQLiteDataDir = \"{$dir}\"; |
||
320 | \$wgObjectCaches[CACHE_DB] = [ |
||
321 | 'class' => 'SqlBagOStuff', |
||
322 | 'loggroup' => 'SQLBagOStuff', |
||
323 | 'server' => [ |
||
324 | 'type' => 'sqlite', |
||
325 | 'dbname' => 'wikicache', |
||
326 | 'tablePrefix' => '', |
||
327 | 'dbDirectory' => \$wgSQLiteDataDir, |
||
328 | 'flags' => 0 |
||
329 | ] |
||
330 | ];"; |
||
331 | } |
||
332 | } |
||
333 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: