Issues (180)

Security Analysis    no request data  

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/QueryBridge.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
 * Fwk
4
 *
5
 * Copyright (c) 2011-2012, Julien Ballestracci <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
15
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
21
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
 * POSSIBILITY OF SUCH DAMAGE.
23
 *
24
 * PHP Version 5.3
25
 *
26
 * @package    Fwk
27
 * @subpackage Db
28
 * @author     Julien Ballestracci <[email protected]>
29
 * @copyright  2011-2012 Julien Ballestracci <[email protected]>
30
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
31
 * @link       http://www.phpfwk.com
32
 */
33
namespace Fwk\Db;
34
35
use Fwk\Db\Connection;
36
use Fwk\Db\Query;
37
use Doctrine\DBAL\Query\QueryBuilder;
38
39
/**
40
 * This class transform a Fwk\Db\Query object into a SQL query string
41
 *
42
 */
43
class QueryBridge
44
{
45
    const STATE_INIT    = 0;
46
    const STATE_READY   = 1;
47
    const STATE_ERROR   = 2;
48
49
    /**
50
     * The Connection
51
     *
52
     * @var Connection
53
     */
54
    protected $connection;
55
56
    /**
57
     *
58
     * @var array
59
     */
60
    protected $tablesAliases;
61
62
    /**
63
     *
64
     * @var array
65
     */
66
    protected $columnsAliases;
67
68
    /**
69
     *
70
     * @var QueryBuilder
71
     */
72
    protected $handle;
73
74
    protected $state = self::STATE_INIT;
75
76
    protected $queryString;
77
78
    public function __construct(Connection $connection)
79
    {
80
        $this->connection = $connection;
81
    }
82
83
    /**
84
     *
85
     * @param  Query                             $query
86
     * @param  array                             $params
87
     * @param  array                             $options
88
     * @return \Doctrine\DBAL\Query\QueryBuilder
89
     */
90
    public function execute(Query $query, array $params = array(), array $options = array())
91
    {
92
        $this->queryString = $sql = $this->prepare($query, $options);
93
94
        if ($query->getType() == Query::TYPE_INSERT) {
95
            return $this->connection->getDriver()->executeUpdate($sql, $params);
96
        }
97
98
        $this->handle->setParameters($params);
99
100
        return $this->handle->execute();
101
    }
102
103
    /**
104
     *
105
     * @param Query $query
106
     *
107
     * @return string
108
     */
109
    public function prepare(Query $query)
110
    {
111
        if ($this->state !== self::STATE_INIT) {
112
            return null;
113
        }
114
115
        $this->handle   = $this->connection->getDriver()->createQueryBuilder();
116
        $type           = $query->getType();
117
        switch ($type) {
118
            case Query::TYPE_DELETE:
119
                $parts  = array(
120
                    'delete' => true
121
                );
122
                $call = array($this, 'deleteQuery');
123
                break;
124
125
            case Query::TYPE_INSERT:
126
                $parts  = array(
127
                    'insert' => true,
128
                    'values' => true
129
                );
130
                $call = array($this, 'insertQuery');
131
                break;
132
133
            case Query::TYPE_SELECT:
134
                $parts  = array(
135
                    'select' => true,
136
                    'from' => true
137
                );
138
                $call = array($this, 'selectQuery');
139
                break;
140
141
            case Query::TYPE_UPDATE:
142
                $parts = array(
143
                    'update' => true
144
                );
145
                $call = array($this, 'updateQuery');
146
                break;
147
148
            default:
149
                return null;
150
        }
151
152
        foreach ($parts as $part => $required) {
153
            if ($required == true && !$query->offsetExists($part)) {
154
                throw new Exception(sprintf('Missing required query part "%s"', $part));
155
            }
156
        }
157
158
        $table = $query['from'];
159
        if (!empty($table)) {
160
            if(strpos($table, ' ')) {
161
                list($table, ) = explode(' ', $table);
162
            }
163
164
            $decl = $this->connection->table($table)->getDefaultEntity($table);
165
            if(empty($query['entity']) || $query['entity'] == "\stdClass") {
166
                $query->entity($decl);
167
            }
168
        }
169
170
        if (!empty($query['entity']) && $query['entity'] != "\stdClass") {
171
            $obj        = new $query['entity'];
172
            $access     = new Accessor($obj);
173
            $relations  = $access->getRelations();
174
175
            foreach ($relations as $colName => $relation) {
176
                $relation->prepare($query, $colName);
177
            }
178
        }
179
180
        return trim(\call_user_func($call, $query));
181
    }
182
183
    /**
184
     *
185
     * @return void
186
     */
187
    public function selectQuery(Query $query)
188
    {
189
        $queryJoins = $query['joins'];
190
191
        $this->getSelectFrom($query['from'], $queryJoins);
192
        $this->getSelectColumns($query['select'], $query['from'], $query, $queryJoins);
193
        if (isset($query['joins'])) { $this->getSelectJoins($queryJoins); }
194
        if (isset($query['where'])) { $this->getWhere($query); }
195
        if (isset($query['groupBy'])) { $this->getGroupBy($query['groupBy']); }
196
        if (isset($query['orderBy'])) { $this->getOrderBy($query['orderBy']); }
197
        if (isset($query['limit'])) { $this->getLimit($query['limit']); }
198
199
        return $this->handle->getSQL();
200
    }
201
202
    /**
203
     *
204
     * @return string
205
     */
206
    public function deleteQuery(Query $query)
207
    {
208
        $from = $query['delete'];
209
        if (strpos($from,' ')) {
210
            list($from, $alias) = explode(' ', $from);
211
        } else {
212
            $alias = null;
213
        }
214
215
        $this->handle->delete($from, $alias);
216
        if (isset($query['where'])) { $this->getWhere($query); }
217
        if (isset($query['limit'])) { $this->getLimit($query['limit']); }
218
219
        return $this->handle->getSQL();
220
    }
221
222
    /**
223
     *
224
     * @return string
225
     */
226
    public function updateQuery(Query $query)
227
    {
228
        $update = $query['update'];
229
        if (strpos($update,' ')) {
230
            list($update, $alias) = explode(' ', $update);
231
        } else {
232
            $alias = null;
233
        }
234
        $this->handle->update($update, $alias);
235
        $this->getUpdateSet($query['values']);
236
        if (isset($query['where'])) { $this->getWhere($query); }
237
        if (isset($query['limit'])) { $this->getLimit($query['limit']); }
238
239
        return $this->handle->getSQL();
240
    }
241
242
    /**
243
     *
244
     * @return string
245
     */
246
    public function insertQuery(Query $query)
247
    {
248
        $str = "INSERT INTO";
249
250
        $table = $query['insert'];
251
        if($table instanceof Table)
252
            $table = $table->getName();
253
254
        $vals = $this->getInsertValues($query['values']);
255
256
        $query = implode(' ', array($str, $table, $vals));
257
258
        return $query;
259
    }
260
261
    protected function getOrderBy(array $orderBy)
262
    {
263
        $column = $orderBy['column'];
264
        $order = $orderBy['order'];
265
266
        if (strpos($column, '.') !== false) {
267
            list(, $column) = \explode('.', $column);
268
        }
269
270
        $col = $this->getColumnAlias($column);
271
272
        $this->handle->orderBy($col, $order);
273
    }
274
275
    protected function getSelectJoins(array $joins)
276
    {
277
        foreach ($joins as $join) {
278
279
            $type = $join['type'];
280
            $table = $join['table'];
281
            if (strpos($table, ' ') !== false) {
282
                list($table, ) = explode(' ', $table);
283
            }
284
285
            $keys = array_keys($this->tablesAliases);
286
            $first = array_shift($keys);
287
            $fromAlias = $this->getTableAlias($first);
288
            $alias = $this->getTableAlias($table);
289
            $local = $join['local'];
290
            $foreign = $join['foreign'];
291
292
            if (\strpos($foreign, '.') === false) {
293
                $foreign = $alias .'.'. $foreign;
294
            }
295
296
            if (\strpos($local, '.') === false) {
297
                $local = $fromAlias .'.'. $local;
298
            }
299
300
            $cond = sprintf('%s = %s', $local, $foreign);
301
            if ($type == Query::JOIN_LEFT) {
302
                $this->handle->leftJoin($fromAlias, $table, $alias, $cond);
303
            } else {
304
                $this->handle->join($fromAlias, $table, $alias, $cond);
305
            }
306
        }
307
    }
308
309
    protected function getInsertValues(array $values)
310
    {
311
        $cols = array_keys($values);
312
        $vals = array_values($values);
313
        $str = '(`'. implode('`, `', $cols) .'`) VALUES (';
314
        $final = array();
315
        foreach ($vals as $value) {
316
            $value = $this->getCleanInsertValue($value);
317
            array_push($final, $value);
318
        }
319
320
        return $str . implode(', ', $final) .')';
321
    }
322
323
    protected function getCleanInsertValue($value)
324
    {
325
        $value = trim($value);
326
        if ($value === '?' || strpos($value, ':') === 0) {
327
            return $value;
328
        }
329
330
        /*
331
        if($value instanceof Expression)
332
333
            return (string) $value;
334
        */
335
336
        if ($value === null) {
337
            return 'NULL';
338
        }
339
340
        return $this->connection->getDriver()->quote((string) $value);
341
    }
342
343
    /**
344
     *
345
     * @param Array $values
346
     *
347
     * @return string
348
     */
349
    protected function getUpdateSet(array $values)
350
    {
351
        foreach ($values as $key => $value) {
352
            $this->handle->set($key, $value);
353
        }
354
    }
355
356
    /**
357
     *
358
     * @param mixed $columns
359
     *
360
     * @param mixed $tables
361
     *
362
     * @return void
363
     */
364
    protected function getSelectColumns($columns, $tables, Query $query, $joins = null)
365
    {
366
        if ($query->getFetchMode() != Query::FETCH_SPECIAL) {
367
            $this->handle->select((empty($query['select']) ? '*' : $query['select']));
368
            return;
369
        }
370
371
        if (!$columns || $columns == '*') {
372
            $columns = $this->columnsAliases = $this->getSelectColumnsFromTables($tables, $joins);
373
        } elseif (is_string($columns)) {
374
            $columns = $this->columnsAliases = $this->getSelectColumnsFromString($columns, $tables);
375
        }
376
377
        $final = array();
378
        foreach ($columns as $alias => $column) {
379
            if (!$column['function']) {
380
                array_push($final, $this->getTableAlias($column['table']) .'.'. trim($column['column']) .' AS '. $alias);
381
            } else {
382
                // it's a function
383
                array_push($final, $column['column'] .' AS '. $alias);
384
            }
385
        }
386
387
        $this->handle->select(\implode(', ', $final));
388
    }
389
390
    /**
391
     *
392
     * @return array
393
     */
394
    public function getColumnsAliases()
395
    {
396
        return (is_array($this->columnsAliases) ? $this->columnsAliases : array());
397
    }
398
399
    /**
400
     * @param string $colName
401
     *
402
     * @return int|string
403
     */
404
    public function getColumnAlias($colName)
405
    {
406
        $columns = $this->getColumnsAliases();
407
        foreach ($columns as $alias => $column) {
408
            if ($column['column'] == $colName) {
409
                return $alias;
410
            }
411
        }
412
413
        return $colName;
414
    }
415
416
    /**
417
     *
418
     * @param mixed $tables
419
     * @param array $joins
420
     *
421
     * @return void
422
     */
423
    protected function getSelectFrom($tables, $joins = null)
424
    {
425
        if (is_string($tables) && \strpos($tables, ',') !== false) {
426
            $tables = \explode(',', $tables);
427
        }
428
429
        if (!is_array($tables)) {
430
            $tables = array($tables);
431
        }
432
433
        $joinned = array();
434
        if (is_array($joins)) {
435
            $js = $joins;
436
            foreach ($joins as $k => $join) {
437
                array_push($tables, $join['table']);
438
439
                if (strpos($join['table'], ' ') !== false) {
440
                    list($tble,$alias) = explode(' ', $join['table']);
441
                    $join['table'] = $tble;
442
                } else {
443
                    $tble = $join['table'];
444
                    $alias = 'j'. (is_array($this->tablesAliases) ? count($this->tablesAliases) : '0');
445
                }
446
447
                $js[$k]['table'] = $tble;
448
                $js[$k]['alias'] = $alias;
449
450
                array_push($joinned, $join['table']);
451
            }
452
        }
453
454
        $tbls = array();
455
        foreach ($tables as $table) {
456
            if ($table instanceof Table) {
457
                $table = $table->getName();
458
            }
459
460
            $table = trim($table);
461
            if (\strpos($table, ' ') !== false) {
462
                list($table, $alias) = \explode(' ', $table);
463
            } else {
464
                if (\is_array($this->tablesAliases)) {
465
                    $alias = array_search($table, $this->tablesAliases);
466
                    if (!$alias) {
467
                        $alias = 't'. count($this->tablesAliases);
468
                    }
469
                } else {
470
                    $alias = 't0';
471
                }
472
            }
473
474
            $this->tablesAliases[trim($alias)] = trim($table);
475
476
            if (!in_array($table, $joinned)) {
477
                \array_push($tbls, array('table' => $table, 'alias' => $alias));
478
            }
479
        }
480
481
        foreach ($tbls as $infos) {
482
            $this->handle->from($infos['table'], $infos['alias']);
483
        }
484
    }
485
486
487
    /**
488
     *
489
     * @return string
490
     */
491
    protected function getTableAlias($tableName)
492
    {
493
        if (!is_array($this->tablesAliases)) {
494
           return $tableName;
495
        }
496
497
        $k = \array_search($tableName, $this->tablesAliases);
498
499
        return (false === $k ? $tableName : $k);
500
    }
501
502
503
    /**
504
     *
505
     * @param mixed $tables
506
     *
507
     * @return array
508
     */
509
    protected function getSelectColumnsFromTables($tables, $joins = null)
510
    {
511
        srand();
512
513
        if (is_string($tables) && \strpos($tables, ',') !== false) {
514
            $tables = \explode(',', $tables);
515
        }
516
517
        if (!is_array($tables)) {
518
            $tables = array($tables);
519
        }
520
521
        if (is_array($joins) && count($joins)) {
522
            foreach ($joins as $join) {
523
                array_push($tables, $join['table']);
524
            }
525
        }
526
527
        $columns = array();
528
        foreach ($tables as $table) {
529
            $table = trim($table);
530
531
            if (is_string($table) && \strpos($table, ' ') !== false) {
532
                list($table, ) = \explode(' ', $table);
533
            }
534
535
            $cols = $this->connection->table($table)->getColumns();
536
            foreach ($cols as $column) {
537
                $colName = $column->getName();
538
                $asName = 'c'. \rand(1000, 9999);
539
                if (isset($columns[$asName])) {
540
                    srand();
541
                    $asName = 'c'. \rand(1000, 9999);
542
                }
543
544
                $columns[$asName] = array(
545
                    'table'        => $table,
546
                    'column'       => $colName,
547
                    'function'     => false,
548
                    'alias'        => false
549
                );
550
            }
551
        }
552
553
        return $columns;
554
    }
555
556
    /**
557
     *
558
     * @param string $str
559
     *
560
     * @return array
561
     */
562
    protected function getSelectColumnsFromString($str, $tables)
563
    {
564
        $str = trim($str);
565
        $current = null;
566
        $funcLevel = 0;
567
        $currentIsFunc = false;
568
        $columns = array();
569
        for ($x = 0; $x < strlen($str); $x++) {
570
            $letter = $str{$x};
571
            $current .= $letter;
572
573
            if ($current == '*') {
574
                $wasStar = true;
575
                continue;
576
            }
577
578
            if ($letter == '(') {
579
                $funcLevel++;
580
            } elseif ($letter == ')') {
581
                $funcLevel--;
582
                $currentIsFunc = true;
583
            }
584
585
            $tbls = \array_values ($this->tablesAliases);
586
            $defaultTable = \array_shift ($tbls);
587
588
            if (($letter == ',' || $x == strlen($str) -1) && $funcLevel == 0) {
589
                $column = ($letter == ',' ? substr($current, 0, strlen($current)-1) : $current);
590
591
                if (!$currentIsFunc) {
592
                    if (\strpos($column, '.') !== false) {
593
                        list($table, $column) = \explode ('.', $column);
594
                        if (isset($this->tablesAliases[trim($table)])) {
595
                                $table = $this->tablesAliases[trim($table)];
596
                        }
597
                        if ($column == '*') {
598
                            $columns = array_merge($columns, $this->getSelectColumnsFromTables($table));
599
                            $x++;
600
                            $current = null;
601
                            continue;
602
                        }
603
                    } else {
604
                        $table = $defaultTable;
605
                    }
606
607
                    if (\stripos($column, 'AS') !== false) {
608
                        list($column, $asName) = explode((\strpos($column, 'as') !== false ? ' as ' : ' AS '), $column);
609
                    } else {
610
                        $asName = $column . '__'. \rand(1000, 9999);
611
                    }
612
613
                    $columns[$asName] = array(
614
                        'table'        => trim($table),
615
                        'column'       => trim($column),
616
                        'function'     => false,
617
                        'alias'        => true
618
                    );
619
                } else {
620
                    if (\stripos($column, 'AS') !== false
621
                        && \preg_match_all('/\) AS ([A-Za-z0-9_]+)/i', $column, $matchesarray)
622
                    ) {
623
                        $asName = $matchesarray[1][0];
624
                        $column = \substr($column, 0, strlen($column) - strlen($asName) - 4);
625
                    } else {
626
                        $asName = 'func__'. \rand(1000, 9999);
627
                    }
628
629
                    $columns[$asName] = array(
630
                        'table'        => $defaultTable,
631
                        'column'       => trim($column),
632
                        'function'     => true,
633
                        'alias'        => true
634
                    );
635
                }
636
637
                $current = null;
638
                $currentIsFunc = false;
639
            }
640
        }
641
642
        if (isset($wasStar)) {
643
            $columns = array_merge($columns, $this->getSelectColumnsFromTables($tables));
644
        }
645
646
        return $columns;
647
    }
648
649
    /**
650
     *
651
     * @return string
652
     */
653
    protected function getWhere(Query $query)
654
    {
655
        $where = $query['where'];
656
        $wheres = $query['wheres'];
657
658
        $this->handle->where($where);
659
660
        if(!is_array($wheres) OR !count($wheres))
661
662
            return;
663
664
        foreach ($wheres as $w) {
665
            if ($w['type'] == Query::WHERE_OR) {
666
                $this->handle->orWhere($w['condition']);
667
            } else {
668
                $this->handle->andWhere($w['condition']);
669
            }
670
        }
671
    }
672
673
    /**
674
     *
675
     * @param mixed $limit
676
     *
677
     * @return string
678
     */
679
    protected function getLimit(array $limit)
680
    {
681
        if ($limit['first'] !== null) {
682
            $this->handle->setFirstResult($limit['first']);
683
        }
684
685
        $this->handle->setMaxResults($limit['max']);
686
    }
687
688
    /**
689
     *
690
     * @param mixed $groupBy
691
     *
692
     * @return string
693
     */
694
    protected function getGroupBy($groupBy)
695
    {
696
        $this->handle->groupBy($groupBy);
697
    }
698
699
    /**
700
     *
701
     * @return string
0 ignored issues
show
Should the return type not be null|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...
702
     */
703
    public function getQueryString()
704
    {
705
        return $this->queryString;
706
    }
707
}
708