Issues (1240)

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.

system/libraries/drivers/Database.php (17 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 defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * Database API driver
4
 *
5
 * $Id: Database.php 4343 2009-05-08 17:04:48Z jheathco $
6
 *
7
 * @package    Core
8
 * @author     Kohana Team
9
 * @copyright  (c) 2007-2008 Kohana Team
10
 * @license    http://kohanaphp.com/license.html
11
 */
12
abstract class Database_Driver
13
{
14
    protected $query_cache;
15
16
    /**
17
     * Connect to our database.
18
     * Returns FALSE on failure or a MySQL resource.
19
     *
20
     * @return mixed
21
     */
22
    abstract public function connect();
23
24
    /**
25
     * Perform a query based on a manually written query.
26
     *
27
     * @param  string  SQL query to execute
28
     * @return Database_Result
29
     */
30
    abstract public function query($sql);
31
32
    /**
33
     * Builds a DELETE query.
34
     *
35
     * @param   string  table name
36
     * @param   array   where clause
37
     * @return  string
38
     */
39
    public function delete($table, $where)
40
    {
41
        return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where);
42
    }
43
44
    /**
45
     * Builds an UPDATE query.
46
     *
47
     * @param   string  table name
48
     * @param   array   key => value pairs
49
     * @param   array   where clause
50
     * @return  string
51
     */
52 View Code Duplication
    public function update($table, $values, $where)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
    {
54
        foreach ($values as $key => $val) {
55
            $valstr[] = $this->escape_column($key).' = '.$val;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$valstr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $valstr = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
56
        }
57
        return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ', $where);
0 ignored issues
show
The variable $valstr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
58
    }
59
60
    /**
61
     * Set the charset using 'SET NAMES <charset>'.
62
     *
63
     * @param  string  character set to use
64
     */
65
    public function set_charset($charset)
66
    {
67
        throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
68
    }
69
70
    /**
71
     * Wrap the tablename in backticks, has support for: table.field syntax.
72
     *
73
     * @param   string  table name
74
     * @return  string
75
     */
76
    abstract public function escape_table($table);
77
78
    /**
79
     * Escape a column/field name, has support for special commands.
80
     *
81
     * @param   string  column name
82
     * @return  string
83
     */
84
    abstract public function escape_column($column);
85
86
    /**
87
     * Builds a WHERE portion of a query.
88
     *
89
     * @param   mixed    key
90
     * @param   string   value
91
     * @param   string   type
92
     * @param   int      number of where clauses
93
     * @param   boolean  escape the value
94
     * @return  string
95
     */
96
    public function where($key, $value, $type, $num_wheres, $quote)
97
    {
98
        $prefix = ($num_wheres == 0) ? '' : $type;
99
100
        if ($quote === -1) {
101
            $value = '';
102
        } else {
103
            if ($value === null) {
104
                if (! $this->has_operator($key)) {
105
                    $key .= ' IS';
106
                }
107
108
                $value = ' NULL';
109
            } elseif (is_bool($value)) {
110
                if (! $this->has_operator($key)) {
111
                    $key .= ' =';
112
                }
113
114
                $value = ($value == true) ? ' 1' : ' 0';
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
115
            } else {
116
                if (! $this->has_operator($key) and ! empty($key)) {
117
                    $key = $this->escape_column($key).' =';
118
                } else {
119
                    preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
120
                    if (isset($matches[1]) and isset($matches[2])) {
121
                        $key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
122
                    }
123
                }
124
125
                $value = ' '.(($quote == true) ? $this->escape($value) : $value);
126
            }
127
        }
128
129
        return $prefix.$key.$value;
130
    }
131
132
    /**
133
     * Builds a LIKE portion of a query.
134
     *
135
     * @param   mixed    field name
136
     * @param   string   value to match with field
137
     * @param   boolean  add wildcards before and after the match
138
     * @param   string   clause type (AND or OR)
139
     * @param   int      number of likes
140
     * @return  string
141
     */
142 View Code Duplication
    public function like($field, $match, $auto, $type, $num_likes)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
    {
144
        $prefix = ($num_likes == 0) ? '' : $type;
145
146
        $match = $this->escape_str($match);
147
148
        if ($auto === true) {
149
            // Add the start and end quotes
150
            $match = '%'.str_replace('%', '\\%', $match).'%';
151
        }
152
153
        return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
154
    }
155
156
    /**
157
     * Builds a NOT LIKE portion of a query.
158
     *
159
     * @param   mixed   field name
160
     * @param   string  value to match with field
161
     * @param   string  clause type (AND or OR)
162
     * @param   int     number of likes
163
     * @return  string
164
     */
165 View Code Duplication
    public function notlike($field, $match, $auto, $type, $num_likes)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        $prefix = ($num_likes == 0) ? '' : $type;
168
169
        $match = $this->escape_str($match);
170
171
        if ($auto === true) {
172
            // Add the start and end quotes
173
            $match = '%'.$match.'%';
174
        }
175
176
        return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
177
    }
178
179
    /**
180
     * Builds a REGEX portion of a query.
181
     *
182
     * @param   string   field name
183
     * @param   string   value to match with field
184
     * @param   string   clause type (AND or OR)
185
     * @param   integer  number of regexes
186
     * @return  string
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
187
     */
188
    public function regex($field, $match, $type, $num_regexs)
189
    {
190
        throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
191
    }
192
193
    /**
194
     * Builds a NOT REGEX portion of a query.
195
     *
196
     * @param   string   field name
197
     * @param   string   value to match with field
198
     * @param   string   clause type (AND or OR)
199
     * @param   integer  number of regexes
200
     * @return  string
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
201
     */
202
    public function notregex($field, $match, $type, $num_regexs)
203
    {
204
        throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
205
    }
206
207
    /**
208
     * Builds an INSERT query.
209
     *
210
     * @param   string  table name
211
     * @param   array   keys
212
     * @param   array   values
213
     * @return  string
214
     */
215 View Code Duplication
    public function insert($table, $keys, $values)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
    {
217
        // Escape the column names
218
        foreach ($keys as $key => $value) {
219
            $keys[$key] = $this->escape_column($value);
220
        }
221
        return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
222
    }
223
224
    /**
225
     * Builds a MERGE portion of a query.
226
     *
227
     * @param   string  table name
228
     * @param   array   keys
229
     * @param   array   values
230
     * @return  string
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
231
     */
232
    public function merge($table, $keys, $values)
233
    {
234
        throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
235
    }
236
237
    /**
238
     * Builds a LIMIT portion of a query.
239
     *
240
     * @param   integer  limit
241
     * @param   integer  offset
242
     * @return  string
243
     */
244
    abstract public function limit($limit, $offset = 0);
245
246
    /**
247
     * Creates a prepared statement.
248
     *
249
     * @param   string  SQL query
250
     * @return  Database_Stmt
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
251
     */
252
    public function stmt_prepare($sql = '')
0 ignored issues
show
The parameter $sql is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
253
    {
254
        throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
255
    }
256
257
    /**
258
     *  Compiles the SELECT statement.
259
     *  Generates a query string based on which functions were used.
260
     *  Should not be called directly, the get() function calls it.
261
     *
262
     * @param   array   select query values
263
     * @return  string
264
     */
265
    abstract public function compile_select($database);
266
267
    /**
268
     * Determines if the string has an arithmetic operator in it.
269
     *
270
     * @param   string   string to check
271
     * @return  boolean
272
     */
273
    public function has_operator($str)
274
    {
275
        return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str));
276
    }
277
278
    /**
279
     * Escapes any input value.
280
     *
281
     * @param   mixed   value to escape
282
     * @return  string
283
     */
284
    public function escape($value)
285
    {
286
        if (! $this->db_config['escape']) {
0 ignored issues
show
The property db_config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
287
            return $value;
288
        }
289
290
        switch (gettype($value)) {
291
            case 'string':
292
                $value = '\''.$this->escape_str($value).'\'';
293
            break;
294
            case 'boolean':
295
                $value = (int) $value;
296
            break;
297
            case 'double':
298
                // Convert to non-locale aware float to prevent possible commas
299
                $value = sprintf('%F', $value);
300
            break;
301
            default:
302
                $value = ($value === null) ? 'NULL' : $value;
303
            break;
304
        }
305
306
        return (string) $value;
307
    }
308
309
    /**
310
     * Escapes a string for a query.
311
     *
312
     * @param   mixed   value to escape
313
     * @return  string
314
     */
315
    abstract public function escape_str($str);
316
317
    /**
318
     * Lists all tables in the database.
319
     *
320
     * @return  array
321
     */
322
    abstract public function list_tables();
323
324
    /**
325
     * Lists all fields in a table.
326
     *
327
     * @param   string  table name
328
     * @return  array
329
     */
330
    abstract public function list_fields($table);
331
332
    /**
333
     * Returns the last database error.
334
     *
335
     * @return  string
336
     */
337
    abstract public function show_error();
338
339
    /**
340
     * Returns field data about a table.
341
     *
342
     * @param   string  table name
343
     * @return  array
344
     */
345
    abstract public function field_data($table);
346
347
    /**
348
     * Fetches SQL type information about a field, in a generic format.
349
     *
350
     * @param   string  field datatype
351
     * @return  string
352
     */
353
    protected function sql_type($str)
354
    {
355
        static $sql_types;
356
357
        if ($sql_types === null) {
358
            // Load SQL data types
359
            $sql_types = Kohana::config('sql_types');
360
        }
361
362
        $str = strtolower(trim($str));
363
364
        if (($open  = strpos($str, '(')) !== false) {
365
            // Find closing bracket
366
            $close = strpos($str, ')', $open) - 1;
367
368
            // Find the type without the size
369
            $type = substr($str, 0, $open);
370
        } else {
371
            // No length
372
            $type = $str;
373
        }
374
375
        empty($sql_types[$type]) and exit(
0 ignored issues
show
Coding Style Compatibility introduced by
The method sql_type() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
376
            'Unknown field type: '.$type.'. '.
377
            'Please report this: http://trac.kohanaphp.com/newticket'
378
        );
379
380
        // Fetch the field definition
381
        $field = $sql_types[$type];
382
383
        switch ($field['type']) {
384
            case 'string':
385
            case 'float':
386
                if (isset($close)) {
387
                    // Add the length to the field info
388
                    $field['length'] = substr($str, $open + 1, $close - $open);
389
                }
390
            break;
391
            case 'int':
392
                // Add unsigned value
393
                $field['unsigned'] = (strpos($str, 'unsigned') !== false);
394
            break;
395
        }
396
397
        return $field;
398
    }
399
400
    /**
401
     * Clears the internal query cache.
402
     *
403
     * @param  string  SQL query
404
     */
405
    public function clear_cache($sql = null)
406
    {
407
        if (empty($sql)) {
408
            $this->query_cache = array();
409
        } else {
410
            unset($this->query_cache[$this->query_hash($sql)]);
411
        }
412
413
        Kohana::log('debug', 'Database cache cleared: '.get_class($this));
414
    }
415
416
    /**
417
     * Creates a hash for an SQL query string. Replaces newlines with spaces,
418
     * trims, and hashes.
419
     *
420
     * @param   string  SQL query
421
     * @return  string
422
     */
423
    protected function query_hash($sql)
424
    {
425
        return sha1(str_replace("\n", ' ', trim($sql)));
426
    }
427
} // End Database Driver Interface
428
429
/**
430
 * Database_Result
431
 *
432
 */
433
abstract class Database_Result implements ArrayAccess, Iterator, Countable
434
{
435
436
    // Result resource, insert id, and SQL
437
    protected $result;
438
    protected $insert_id;
439
    protected $sql;
440
441
    // Current and total rows
442
    protected $current_row = 0;
443
    protected $total_rows  = 0;
444
445
    // Fetch function and return type
446
    protected $fetch_type;
447
    protected $return_type;
448
449
    /**
450
     * Returns the SQL used to fetch the result.
451
     *
452
     * @return  string
453
     */
454
    public function sql()
455
    {
456
        return $this->sql;
457
    }
458
459
    /**
460
     * Returns the insert id from the result.
461
     *
462
     * @return  integer
0 ignored issues
show
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
463
     */
464
    public function insert_id()
465
    {
466
        return $this->insert_id;
467
    }
468
469
    /**
470
     * Prepares the query result.
471
     *
472
     * @param   boolean   return rows as objects
473
     * @param   mixed     type
474
     * @return  Database_Result
475
     */
476
    abstract public function result($object = true, $type = false);
477
478
    /**
479
     * Builds an array of query results.
480
     *
481
     * @param   boolean   return rows as objects
482
     * @param   mixed     type
483
     * @param boolean $object
0 ignored issues
show
Should the type for parameter $object not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
484
     * @return  array
485
     */
486
    abstract public function result_array($object = null, $type = false);
487
488
    /**
489
     * Gets the fields of an already run query.
490
     *
491
     * @return  array
492
     */
493
    abstract public function list_fields();
494
495
    /**
496
     * Seek to an offset in the results.
497
     *
498
     * @return  boolean
499
     */
500
    abstract public function seek($offset);
501
502
    /**
503
     * Countable: count
504
     */
505
    public function count()
506
    {
507
        return $this->total_rows;
508
    }
509
510
    /**
511
     * ArrayAccess: offsetExists
512
     */
513
    public function offsetExists($offset)
514
    {
515
        if ($this->total_rows > 0) {
516
            $min = 0;
517
            $max = $this->total_rows - 1;
518
519
            return ! ($offset < $min or $offset > $max);
520
        }
521
522
        return false;
523
    }
524
525
    /**
526
     * ArrayAccess: offsetGet
527
     */
528 View Code Duplication
    public function offsetGet($offset)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
    {
530
        if (! $this->seek($offset)) {
531
            return false;
532
        }
533
534
        // Return the row by calling the defined fetching callback
535
        return call_user_func($this->fetch_type, $this->result, $this->return_type);
536
    }
537
538
    /**
539
     * ArrayAccess: offsetSet
540
     *
541
     * @throws  Kohana_Database_Exception
542
     */
543
    final public function offsetSet($offset, $value)
544
    {
545
        throw new Kohana_Database_Exception('database.result_read_only');
546
    }
547
548
    /**
549
     * ArrayAccess: offsetUnset
550
     *
551
     * @throws  Kohana_Database_Exception
552
     */
553
    final public function offsetUnset($offset)
554
    {
555
        throw new Kohana_Database_Exception('database.result_read_only');
556
    }
557
558
    /**
559
     * Iterator: current
560
     */
561
    public function current()
562
    {
563
        return $this->offsetGet($this->current_row);
564
    }
565
566
    /**
567
     * Iterator: key
568
     */
569
    public function key()
570
    {
571
        return $this->current_row;
572
    }
573
574
    /**
575
     * Iterator: next
576
     */
577
    public function next()
578
    {
579
        ++$this->current_row;
580
        return $this;
581
    }
582
583
    /**
584
     * Iterator: prev
585
     */
586
    public function prev()
587
    {
588
        --$this->current_row;
589
        return $this;
590
    }
591
592
    /**
593
     * Iterator: rewind
594
     */
595
    public function rewind()
596
    {
597
        $this->current_row = 0;
598
        return $this;
599
    }
600
601
    /**
602
     * Iterator: valid
603
     */
604
    public function valid()
605
    {
606
        return $this->offsetExists($this->current_row);
607
    }
608
} // End Database Result Interface
609