Issues (48)

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.

src/Db/SqliteDb.php (1 issue)

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
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db;
9
10
use PDO;
11
12
/**
13
 * A {@link Db} class for connecting to SQLite.
14
 */
15
class SqliteDb extends MySqlDb {
16
    /**
17
     * {@inheritdoc}
18
     */
19 4
    protected function alterTable($tablename, array $alterdef, array $options = []) {
20 4
        $this->alterTableMigrate($tablename, $alterdef, $options);
21 4
    }
22
23
    /**
24
     * Alter a table by creating a new table and copying the old table's data to it.
25
     *
26
     * @param string $tablename The table to alter.
27
     * @param array $alterDef The new definition.
28
     * @param array $options An array of options for the migration.
29
     */
30 4
    protected function alterTableMigrate($tablename, array $alterDef, array $options = []) {
31 4
        $currentDef = $this->getTableDef($tablename);
32
33
        // Merge the table definitions if we aren't dropping stuff.
34 4
        if (!val(Db::OPTION_DROP, $options)) {
35 3
            $tableDef = $this->mergeTableDefs($currentDef, $alterDef);
36 3
        } else {
37 1
            $tableDef = $alterDef['def'];
38
        }
39
40
        // Drop all of the indexes on the current table.
41 4
        foreach (val('indexes', $currentDef, []) as $indexDef) {
42 4
            if (val('type', $indexDef, Db::INDEX_IX) === Db::INDEX_IX) {
43 2
                $this->dropIndex($indexDef['name']);
44 2
            }
45 4
        }
46
47 4
        $tmpTablename = $tablename.'_'.time();
48
49
        // Rename the current table.
50 4
        $this->renameTable($tablename, $tmpTablename);
51
52
        // Create the new table.
53 4
        $this->createTable($tablename, $tableDef, $options);
54
55
        // Figure out the columns that we can insert.
56 4
        $columns = array_keys(array_intersect_key($tableDef['columns'], $currentDef['columns']));
57
58
        // Build the insert/select statement.
59 4
        $sql = 'insert into '.$this->backtick($this->px.$tablename)."\n".
60 4
            $this->bracketList($columns, '`')."\n".
61 4
            $this->buildSelect($tmpTablename, [], ['columns' => $columns]);
62
63 4
        $this->query($sql, Db::QUERY_WRITE);
64
65
        // Drop the temp table.
66 4
        $this->dropTable($tmpTablename);
67 4
    }
68
69
    /**
70
     * Rename a table.
71
     *
72
     * @param string $oldname The old name of the table.
73
     * @param string $newname The new name of the table.
74
     */
75 4
    protected function renameTable($oldname, $newname) {
76
        $renameSql = 'alter table '.
77 4
            $this->backtick($this->px.$oldname).
78 4
            ' rename to '.
79 4
            $this->backtick($this->px.$newname);
80 4
        $this->query($renameSql, Db::QUERY_WRITE);
81 4
    }
82
83
    /**
84
     * Merge a table def with its alter def so that no columns/indexes are lost in an alter.
85
     *
86
     * @param array $tableDef The table def.
87
     * @param array $alterDef The alter def.
88
     * @return array The new table def.
89
     */
90 3
    protected function mergeTableDefs(array $tableDef, array $alterDef) {
91 3
        $result = $tableDef;
92
93 3
        $result['columns'] = array_merge($result['columns'], $alterDef['def']['columns']);
94 3
        $result['indexes'] = array_merge($result['indexes'], $alterDef['add']['indexes']);
95
96 3
        return $result;
97
    }
98
99
    /**
100
     * Drop an index.
101
     *
102
     * @param string $indexName The name of the index to drop.
103
     */
104 2
    protected function dropIndex($indexName) {
105
        $sql = 'drop index if exists '.
106 2
            $this->backtick($indexName);
107 2
        $this->query($sql, Db::QUERY_DEFINE);
108 2
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 24
    protected function buildInsert($tablename, array $row, $quotevals = true, $options = []) {
114 24
        if (val(Db::OPTION_UPSERT, $options)) {
115
            throw new \Exception("Upsert is not supported.");
116 24
        } elseif (val(Db::OPTION_IGNORE, $options)) {
117 2
            $sql = 'insert or ignore into ';
118 24
        } elseif (val(Db::OPTION_REPLACE, $options)) {
119 2
            $sql = 'insert or replace into ';
120 2
        } else {
121 22
            $sql = 'insert into ';
122
        }
123 24
        $sql .= $this->backtick($this->px.$tablename);
124
125
        // Add the list of values.
126
        $sql .=
127 24
            "\n".$this->bracketList(array_keys($row), '`').
128 24
            "\nvalues".$this->bracketList($row, $quotevals ? "'" : '');
129
130 24
        return $sql;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 1
    protected function buildLike($column, $value, $quotevals) {
137 1
        return "$column like ".$this->quoteVal($value, $quotevals)." escape '\\'";
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 4 View Code Duplication
    protected function buildUpdate($tablename, array $set, array $where, $quotevals = true, array $options = []) {
144
        $sql = 'update '.
145 4
            (val(Db::OPTION_IGNORE, $options) ? 'or ignore ' : '').
146 4
            $this->backtick($this->px.$tablename).
147 4
            "\nset\n  ";
148
149 4
        $parts = [];
150 4
        foreach ($set as $key => $value) {
151 4
            $parts[] = $this->backtick($key).' = '.$this->quoteVal($value, $quotevals);
152 4
        }
153 4
        $sql .= implode(",\n  ", $parts);
154
155 4
        if (!empty($where)) {
156 4
            $sql .= "\nwhere ".$this->buildWhere($where, Db::OP_AND, $quotevals);
157 4
        }
158
159 4
        return $sql;
160
    }
161
162
    /**
163
     * Construct a column definition string from an array defintion.
164
     *
165
     * @param string $name The name of the column.
166
     * @param array $def The column definition.
167
     * @return string Returns a string representing the column definition.
168
     */
169 8
    protected function columnDefString($name, array $def) {
170
        // Auto-increments MUST be of type integer.
171 8
        if (val('autoincrement', $def)) {
172 2
            $def['type'] = 'integer';
173 2
        }
174
175 8
        $result = $this->backtick($name).' '.$this->columnTypeString($def['type']);
176
177 8
        if (val('primary', $def) && val('autoincrement', $def)) {
178
//            if (val('autoincrement', $def)) {
179 2
                $result .= ' primary key autoincrement';
180 2
                $def['primary'] = true;
181
//            }
182 8
        } elseif (isset($def['default'])) {
183 5
            $result .= ' default '.$this->quoteVal($def['default']);
184 8
        } elseif (val('required', $def)) {
185 7
            $result .= ' not null';
186 7
        }
187
188 8
        return $result;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 8
    protected function createTable($tablename, array $tabledef, array $options = []) {
195 8
        $parts = [];
196
197
        // Make sure the primary key columns are defined first and in order.
198 8
        $autoinc = false;
199 8
        if (isset($tabledef['indexes']['primary'])) {
200 5
            $pkIndex = $tabledef['indexes']['primary'];
201 5
            foreach ($pkIndex['columns'] as $column) {
202 5
                $cdef = $tabledef['columns'][$column];
203 5
                $parts[] = $this->columnDefString($column, $cdef);
204 5
                $autoinc |= val('autoincrement', $cdef, false);
205 5
                unset($tabledef['columns'][$column]);
206 5
            }
207 5
        }
208
209 8
        foreach ($tabledef['columns'] as $name => $cdef) {
210 7
            $parts[] = $this->columnDefString($name, $cdef);
211 8
        }
212
213
        // Add the prinary key index.
214 8
        if (isset($pkIndex) && !$autoinc) {
215 3
            $parts[] = 'primary key '.$this->bracketList($pkIndex['columns'], '`');
216 3
        }
217
218 8
        $fullTablename = $this->backtick($this->px.$tablename);
219 8
        $sql = "create table $fullTablename (\n  ".
220 8
            implode(",\n  ", $parts).
221 8
            "\n)";
222
223 8
        $this->query($sql, Db::QUERY_DEFINE);
224
225
        // Add the rest of the indexes.
226 8
        foreach (val('indexes', $tabledef, []) as $index) {
227 8
            if (val('type', $index, Db::INDEX_IX) !== Db::INDEX_PK) {
228 5
                $this->createIndex($tablename, $index, $options);
229 5
            }
230 8
        }
231 8
    }
232
233
    /**
234
     * Create an index.
235
     *
236
     * @param string $tablename The name of the table to create the index on.
237
     * @param array $indexDef The index definition.
238
     * @param array $options Additional options for the index creation.
239
     */
240 5
    public function createIndex($tablename, array $indexDef, $options = []) {
241
        $sql = 'create '.
242 5
            (val('type', $indexDef) === Db::INDEX_UNIQUE ? 'unique ' : '').
243 5
            'index '.
244 5
            (val(Db::OPTION_IGNORE, $options) ? 'if not exists ' : '').
245 5
            $this->buildIndexName($tablename, $indexDef).
246 5
            ' on '.
247 5
            $this->backtick($this->px.$tablename).
248 5
            $this->bracketList($indexDef['columns'], '`');
249
250 5
        $this->query($sql, Db::QUERY_DEFINE);
251 5
    }
252
253
    /**
254
     * Force a value into the appropriate php type based on its Sqlite type.
255
     *
256
     * @param mixed $value The value to force.
257
     * @param string $type The sqlite type name.
258
     * @return mixed Returns $value cast to the appropriate type.
259
     */
260 View Code Duplication
    protected function forceType($value, $type) {
261
        $type = strtolower($type);
262
263
        if ($type === 'null') {
264
            return null;
265
        } elseif (in_array($type, ['int', 'integer', 'tinyint', 'smallint',
266
            'mediumint', 'bigint', 'unsigned big int', 'int2', 'int8', 'boolean'])) {
267
            return force_int($value);
268
        } elseif (in_array($type, ['real', 'double', 'double precision', 'float',
269
            'numeric', 'decimal(10,5)'])) {
270
            return floatval($value);
271
        } else {
272
            return (string)$value;
273
        }
274
    }
275
276
    /**
277
     * Get the columns for tables and put them in {MySqlDb::$tables}.
278
     *
279
     * @param string $tablename The table to get the columns for or blank for all columns.
280
     * @return array|null Returns an array of columns if {@link $tablename} is specified, or null otherwise.
281
     */
282 5
    protected function getColumns($tablename = '') {
283 5
        if (!$tablename) {
284 1
            $tablenames = $this->getTablenames();
285 1
            foreach ($tablenames as $tablename) {
286 1
                $this->getColumns($tablename);
287 1
            }
288 1
        }
289
290 5
        $cdefs = (array)$this->query('pragma table_info('.$this->quoteVal($this->px.$tablename).')');
291 5
        if (empty($cdefs)) {
292 3
            return null;
293
        }
294
295 5
        $columns = [];
296 5
        $pk = [];
297 5
        foreach ($cdefs as $cdef) {
298
            $column = [
299 5
                'type' => $this->columnTypeString($cdef['type']),
300 5
                'required' => force_bool($cdef['notnull']),
301 5
            ];
302 5
            if ($cdef['pk']) {
303 3
                $pk[] = $cdef['name'];
304 3
                if (strcasecmp($cdef['type'], 'integer') === 0) {
305 1
                    $column['autoincrement'] = true;
306 1
                } else {
307 3
                    $column['primary'] = true;
308
                }
309 3
            }
310 5
            if ($cdef['dflt_value'] !== null) {
311 3
                $column['default'] = $cdef['dflt_value'];
312 3
            }
313 5
            $columns[$cdef['name']] = $column;
314 5
        }
315 5
        $tdef = ['columns' => $columns];
316 5
        if (!empty($pk)) {
317 3
            $tdef['indexes'][Db::INDEX_PK] = [
318 3
                'columns' => $pk,
319
                'type' => Db::INDEX_PK
320 3
            ];
321 3
        }
322 5
        $this->tables[$tablename] = $tdef;
323 5
        return $columns;
324
    }
325
326
    /**
327
     * Get the indexes from the database.
328
     *
329
     * @param string $tablename The name of the table to get the indexes for or an empty string to get all indexes.
330
     * @return array|null
331
     */
332 5
    protected function getIndexes($tablename = '') {
333 5
        if (!$tablename) {
334 1
            $tablenames = $this->getTablenames();
335 1
            foreach ($tablenames as $tablename) {
336 1
                $this->getIndexes($tablename);
337 1
            }
338 1
        }
339
340 5
        $pk = valr(['indexes', Db::INDEX_PK], $this->tables[$tablename]);
341
342
        // Reset the index list for the table.
343 5
        $this->tables[$tablename]['indexes'] = [];
344
345 5
        if ($pk) {
346 3
            $this->tables[$tablename]['indexes'][Db::INDEX_PK] = $pk;
347 3
        }
348
349 5
        $indexInfos = (array)$this->query('pragma index_list('.$this->quoteVal($this->px.$tablename).')');
350 5
        foreach ($indexInfos as $row) {
351 5
            $indexName = $row['name'];
352 5
            if ($row['unique']) {
353 3
                $type = Db::INDEX_UNIQUE;
354 3
            } else {
355 3
                $type = Db::INDEX_IX;
356
            }
357
358
            // Query the columns in the index.
359 5
            $columns = (array)$this->query('pragma index_info('.$this->quoteVal($indexName).')');
360
361
            $index = [
362 5
                'name' => $indexName,
363 5
                'columns' => array_column($columns, 'name'),
364
                'type' => $type
365 5
            ];
366 5
            $this->tables[$tablename]['indexes'][] = $index;
367 5
        }
368
369 5
        return $this->tables[$tablename]['indexes'];
370
    }
371
372
    /**
373
     * Get the primary or secondary keys from the given rows.
374
     *
375
     * @param string $tablename The name of the table.
376
     * @param array $row The row to examine.
377
     * @param bool $quick Whether or not to quickly look for <tablename>ID for the primary key.
378
     * @return array|null Returns the primary keys and values from {@link $rows} or null if the primary key isn't found.
379
     */
380 2
    protected function getPKValue($tablename, array $row, $quick = false) {
381 2
        if ($quick && isset($row[$tablename.'ID'])) {
382 1
            return [$tablename.'ID' => $row[$tablename.'ID']];
383
        }
384
385 1
        $tdef = $this->getTableDef($tablename);
386 1
        if (isset($tdef['indexes'][Db::INDEX_PK]['columns'])) {
387 1
            $pkColumns = array_flip($tdef['indexes'][Db::INDEX_PK]['columns']);
388 1
            $cols = array_intersect_key($row, $pkColumns);
389 1
            if (count($cols) === count($pkColumns)) {
390 1
                return $cols;
391
            }
392
        }
393
394
        return null;
395
    }
396
397
    /**
398
     * Get the all of tablenames in the database.
399
     *
400
     * @return array Returns an array of table names with prefixes stripped.
401
     */
402 1 View Code Duplication
    protected function getTablenames() {
403
        // Get the table names.
404 1
        $tables = (array)$this->get(
405 1
            'sqlite_master',
406
            [
407 1
                'type' => 'table',
408 1
                'name' => [Db::OP_LIKE => addcslashes($this->px, '_%').'%']
409 1
            ],
410
            [
411 1
                'columns' => ['name'],
412
                'escapeTable' => false
413 1
            ]
414 1
        );
415
416
        // Strip the table prefixes.
417 1
        $tables = array_map(function ($name) {
418 1
            return ltrim_substr($name, $this->px);
419 1
        }, array_column($tables, 'name'));
420
421 1
        return $tables;
422
    }
423
424
    /**
425
     * {@inheritdoc}
426
     */
427 7
    public function insert($tablename, array $rows, array $options = []) {
428
        // Sqlite doesn't support upsert so do upserts manually.
429 7
        if (val(Db::OPTION_UPSERT, $options)) {
430 2
            unset($options[Db::OPTION_UPSERT]);
431
432 2
            $keys = $this->getPKValue($tablename, $rows, true);
433 2
            if (!$keys) {
434
                throw new \Exception("Cannot upsert with no key.", 500);
435
            }
436
            // Try updating first.
437 2
            $updated = $this->update(
438 2
                $tablename,
439 2
                array_diff_key($rows, $keys),
440 2
                $keys,
441
                $options
442 2
            );
443 2
            if ($updated) {
444
                // Updated.
445 2
                if (count($keys) === 1) {
446 1
                    return array_pop($keys);
447
                } else {
448 1
                    return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method Garden\Db\MySqlDb::insert of type integer|string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
449
                }
450
            }
451 1
        }
452
453 7
        $result = parent::insert($tablename, $rows, $options);
454 7
        return $result;
455
    }
456
457
    /**
458
     * Gets the {@link PDO} object for this connection.
459
     *
460
     * @return \PDO
461
     */
462 31
    public function pdo() {
463 31
        $dsn = 'sqlite:'.$this->config['path'];
464
465 31
        if (!isset($this->pdo)) {
466
            $this->pdo = new PDO($dsn, val('username', $this->config, null), val('password', $this->config, null));
467
        }
468 31
        return $this->pdo;
469
    }
470
471
    /**
472
     * Optionally quote a where value.
473
     *
474
     * @param mixed $value The value to quote.
475
     * @param bool $quote Whether or not to quote the value.
476
     * @return string Returns the value, optionally quoted.
477
     */
478 21
    public function quoteVal($value, $quote = true) {
479 21
        if ($value instanceof Literal) {
480
            /* @var Literal $value */
481
            return $value->getValue('mysql');
482 21
        } elseif (in_array(gettype($value), ['integer', 'double'])) {
483 20
            return (string)$value;
484 9
        } elseif ($value === true) {
485
            return '1';
486 9
        } elseif ($value === false) {
487
            return '0';
488 9
        } elseif ($quote) {
489 9
            return $this->pdo()->quote($value);
490
        } else {
491
            return $value;
492
        }
493
    }
494
}
495