Issues (12)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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/Crate/PDO/PDOCrateDB.php (2 issues)

1
<?php
2
/**
3
 * Licensed to CRATE Technology GmbH("Crate") under one or more contributor
4
 * license agreements.  See the NOTICE file distributed with this work for
5
 * additional information regarding copyright ownership.  Crate licenses
6
 * this file to you under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.  You may
8
 * obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
15
 * License for the specific language governing permissions and limitations
16
 * under the License.
17
 *
18
 * However, if you have executed another commercial license agreement
19
 * with Crate these terms will supersede the license and you may use the
20
 * software solely pursuant to the terms of the relevant commercial agreement.
21
 */
22
23
declare(strict_types=1);
24
25
namespace Crate\PDO;
26
27
use Crate\PDO\Exception\InvalidArgumentException;
28
use Crate\PDO\Exception\PDOException;
29
use Crate\PDO\Http\ServerInterface;
30
use Crate\PDO\Http\ServerPool;
31
use Crate\Stdlib\ArrayUtils;
32
use PDO as BasePDO;
33
34
use const PHP_VERSION_ID;
35
36
class PDOCrateDB extends BasePDO implements PDOInterface
37
{
38
    use PDOImplementation;
39
40
    public const VERSION     = '2.2.2';
41
    public const DRIVER_NAME = 'crate';
42
43
    public const DSN_REGEX = '/^(?:crate:)(?:((?:[\w\.-]+:\d+\,?)+))\/?([\w]+)?$/';
44
45
    public const CRATE_ATTR_HTTP_BASIC_AUTH = 1000;
46
    public const CRATE_ATTR_DEFAULT_SCHEMA  = 1001;
47
48
    public const CRATE_ATTR_SSL_MODE                                       = 1008;
49
    public const CRATE_ATTR_SSL_MODE_DISABLED                              = 1;
50
    public const CRATE_ATTR_SSL_MODE_ENABLED_BUT_WITHOUT_HOST_VERIFICATION = 2;
51
    public const CRATE_ATTR_SSL_MODE_REQUIRED                              = 3;
52
53
    public const CRATE_ATTR_SSL_KEY_PATH      = 1002;
54
    public const CRATE_ATTR_SSL_KEY_PASSWORD  = 1003;
55
    public const CRATE_ATTR_SSL_CERT_PATH     = 1004;
56
    public const CRATE_ATTR_SSL_CERT_PASSWORD = 1005;
57
    public const CRATE_ATTR_SSL_CA_PATH       = 1006;
58
    public const CRATE_ATTR_SSL_CA_PASSWORD   = 1007;
59
60
    public const PARAM_FLOAT     = 6;
61
    public const PARAM_DOUBLE    = 7;
62
    public const PARAM_LONG      = 8;
63
    public const PARAM_ARRAY     = 9;
64
    public const PARAM_OBJECT    = 10;
65
    public const PARAM_TIMESTAMP = 11;
66
    public const PARAM_IP        = 12;
67
68
    /**
69
     * @var array
70
     */
71
    private $attributes = [
72
        'defaultFetchMode' => self::FETCH_BOTH,
73
        'errorMode'        => self::ERRMODE_SILENT,
74
        'sslMode'          => self::CRATE_ATTR_SSL_MODE_DISABLED,
75
        'statementClass'   => PDOStatement::class,
76
        'timeout'          => 0.0,
77
        'auth'             => [],
78
        'defaultSchema'    => 'doc',
79
        'bulkMode'         => false,
80
    ];
81
82
    /**
83
     * @var Http\ServerInterface
84
     */
85
    private $server;
86
87
    /**
88
     * @var PDOStatement|null
89
     */
90
    private $lastStatement;
91
92
    /**
93
     * @var callable
94
     */
95
    private $request;
96
97
    /**
98
     * {@inheritDoc}
99
     *
100
     * @param string     $dsn      The HTTP endpoint to call
101
     * @param null       $username Username for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $username is correct as it would always require null to be passed?
Loading history...
102
     * @param null       $passwd   Password for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $passwd is correct as it would always require null to be passed?
Loading history...
103
     * @param null|array $options  Attributes to set on the PDO
104
     */
105 36
    public function __construct($dsn, $username = null, $passwd = null, $options = [])
106
    {
107
108 36
        if (PHP_VERSION_ID < 80000) {
109
            trigger_error(
110
                "`crate/crate-pdo` will stop supporting PHP7 on one of the upcoming " .
111
                "releases. Please upgrade to PHP8.",
112
                E_USER_DEPRECATED
113
            );
114
        }
115
116 36
        $dsnParts = self::parseDSN($dsn);
117 36
        $servers  = self::serversFromDsnParts($dsnParts);
118
119 36
        $this->setServer(new ServerPool($servers));
120
121 36
        foreach ((array)$options as $attribute => $value) {
122 2
            $this->setAttribute($attribute, $value);
123
        }
124
125 36
        if (!empty($username)) {
126 4
            $this->setAttribute(self::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
127
        }
128
129 36
        if (!empty($dsnParts[1])) {
130
            $this->setAttribute(self::CRATE_ATTR_DEFAULT_SCHEMA, $dsnParts[1]);
131
        }
132
133
        // Define a callback that will be used in the PDOStatements
134
        // This way we don't expose this as a public api to the end users.
135 36
        $this->request = function (PDOStatement $statement, $sql, array $parameters) {
136
137 2
            $this->lastStatement = $statement;
138
139
            try {
140 2
                if ($statement->isBulkMode()) {
141
                    return $this->server->executeBulk($sql, $parameters);
142
                } else {
143 2
                    return $this->server->execute($sql, $parameters);
144
                }
145
            } catch (Exception\RuntimeException $e) {
146
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_EXCEPTION) {
147
                    throw new Exception\PDOException($e->getMessage(), $e->getCode());
148
                }
149
150
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_WARNING) {
151
                    trigger_error(sprintf('[%d] %s', $e->getCode(), $e->getMessage()), E_USER_WARNING);
152
                }
153
154
                // should probably wrap this in a error object ?
155
                return [
156
                    'code'    => $e->getCode(),
157
                    'message' => $e->getMessage(),
158
                ];
159
            }
160 36
        };
161
    }
162
163
    /**
164
     * Change the server implementation
165
     *
166
     * @param ServerInterface $server
167
     */
168 30
    public function setServer(ServerInterface $server): void
169
    {
170 30
        $this->server = $server;
171 30
        $this->server->configure($this);
172
    }
173
174
    /**
175
     * Extract servers and optional custom schema from DSN string
176
     *
177
     * @param string $dsn The DSN string
178
     *
179
     * @throws \Crate\PDO\Exception\PDOException on an invalid DSN string
180
     *
181
     * @return array An array of ['host:post,host:port,...', 'schema']
182
     */
183 118
    private static function parseDSN($dsn)
184
    {
185 118
        $matches = [];
186
187 118
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
188 8
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
189
        }
190
191 110
        return array_slice($matches, 1);
192
    }
193
194
    /**
195
     * Extract host:port pairs out of the DSN parts
196
     *
197
     * @param array $dsnParts The parts of the parsed DSN string
198
     *
199
     * @return array An array of host:port strings
200
     */
201 108
    private static function serversFromDsnParts($dsnParts)
202
    {
203 108
        return explode(',', trim($dsnParts[0], ','));
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 4
    #[\ReturnTypeWillChange]
210
    public function prepare(string $statement, array $options = [])
211
    {
212 4
        $options = ArrayUtils::toArray($options);
213
214 4
        if (isset($options[self::ATTR_CURSOR])) {
215
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
216
217
            return true;
218
        }
219
220 4
        $className = $this->attributes['statementClass'];
221
222 4
        return new $className($this, $this->request, $statement, $options);
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228 2
    public function beginTransaction(): bool
229
    {
230 2
        return true;
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236 2
    public function commit(): bool
237
    {
238 2
        return true;
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 2
    public function rollBack(): bool
245
    {
246 2
        throw new Exception\UnsupportedException;
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     */
252 2
    public function inTransaction(): bool
253
    {
254 2
        return false;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260
    #[\ReturnTypeWillChange]
261
    public function exec($statement)
262
    {
263
        $statement = $this->prepare($statement);
264
        $result    = $statement->execute();
265
266
        return $result === false ? false : $statement->rowCount();
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 2
    public function doQuery($statement)
273
    {
274 2
        $statement = $this->prepare($statement);
275 2
        $result    = $statement->execute();
276
277 2
        return $result === false ? false : $statement;
278
    }
279
280
    /**
281
     * {@inheritDoc}
282
     */
283 2
    public function lastInsertId(?string $name = null): string
284
    {
285 2
        throw new Exception\UnsupportedException;
286
    }
287
288
    /**
289
     * {@inheritDoc}
290
     */
291
    public function errorCode(): ?string
292
    {
293
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299
    public function errorInfo(): array
300
    {
301
        return $this->lastStatement === null ? ["00000", null, null] : $this->lastStatement->errorInfo();
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     *
307
     * @throws \Crate\PDO\Exception\PDOException
308
     * @throws \Crate\PDO\Exception\InvalidArgumentException
309
     */
310 32
    #[\ReturnTypeWillChange]
311
    public function setAttribute($attribute, $value)
312
    {
313
        switch ($attribute) {
314 32
            case self::ATTR_DEFAULT_FETCH_MODE:
315 2
                $this->attributes['defaultFetchMode'] = $value;
316 2
                break;
317
318 30
            case self::ATTR_ERRMODE:
319 2
                $this->attributes['errorMode'] = $value;
320 2
                break;
321
322 28
            case self::ATTR_STATEMENT_CLASS:
323 2
                $this->attributes['statementClass'] = $value;
324 2
                break;
325
326 26
            case self::ATTR_TIMEOUT:
327 4
                $this->attributes['timeout'] = (int)$value;
328 4
                break;
329
330 24
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
331 4
                if (!is_array($value) && $value !== null) {
332
                    throw new InvalidArgumentException(
333
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
334
                    );
335
                }
336
337 4
                $this->attributes['auth'] = $value;
338 4
                break;
339
340 24
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
341 4
                $this->attributes['defaultSchema'] = $value;
342 4
                break;
343
344 20
            case self::CRATE_ATTR_SSL_MODE:
345 18
                $this->attributes['sslMode'] = $value;
346 18
                break;
347
348 14
            case self::CRATE_ATTR_SSL_CA_PATH:
349 4
                $this->attributes['sslCa'] = $value;
350 4
                break;
351
352 12
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
353 2
                $this->attributes['sslCaPassword'] = $value;
354 2
                break;
355
356 10
            case self::CRATE_ATTR_SSL_CERT_PATH:
357 4
                $this->attributes['sslCert'] = $value;
358 4
                break;
359
360 8
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
361 2
                $this->attributes['sslCertPassword'] = $value;
362 2
                break;
363
364 6
            case self::CRATE_ATTR_SSL_KEY_PATH:
365 4
                $this->attributes['sslKey'] = $value;
366 4
                break;
367
368 4
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
369 2
                $this->attributes['sslKeyPassword'] = $value;
370 2
                break;
371
372
            default:
373 2
                throw new Exception\PDOException('Unsupported driver attribute');
374
        }
375
376
        // A setting changed so we need to reconfigure the server pool
377 30
        $this->server->configure($this);
378 30
        return true;
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     *
384
     * @throws \Crate\PDO\Exception\PDOException
385
     */
386 52
    #[\ReturnTypeWillChange]
387
    public function getAttribute($attribute)
388
    {
389
        switch ($attribute) {
390 52
            case self::ATTR_PREFETCH:
391 52
            case self::ATTR_PERSISTENT:
392 4
                return false;
393
394 52
            case self::ATTR_CLIENT_VERSION:
395 2
                return self::VERSION;
396
397 52
            case self::ATTR_SERVER_VERSION:
398
                return $this->server->getServerVersion();
399
400 52
            case self::ATTR_SERVER_INFO:
401
                return $this->server->getServerInfo();
402
403 52
            case self::ATTR_TIMEOUT:
404 52
                return $this->attributes['timeout'];
405
406 52
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
407 52
                return $this->attributes['auth'];
408
409 52
            case self::ATTR_DEFAULT_FETCH_MODE:
410 4
                return $this->attributes['defaultFetchMode'];
411
412 52
            case self::ATTR_ERRMODE:
413 2
                return $this->attributes['errorMode'];
414
415 52
            case self::ATTR_DRIVER_NAME:
416 2
                return static::DRIVER_NAME;
417
418 52
            case self::ATTR_STATEMENT_CLASS:
419 2
                return [$this->attributes['statementClass']];
420
421 52
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
422 52
                return $this->attributes['defaultSchema'];
423
424 52
            case self::CRATE_ATTR_SSL_MODE:
425 52
                return $this->attributes['sslMode'];
426
427 52
            case self::CRATE_ATTR_SSL_CA_PATH:
428 52
                return $this->attributes['sslCa'] ?? null;
429
430 52
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
431 52
                return $this->attributes['sslCaPassword'] ?? null;
432
433 52
            case self::CRATE_ATTR_SSL_CERT_PATH:
434 52
                return $this->attributes['sslCert'] ?? null;
435
436 52
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
437 52
                return $this->attributes['sslCertPassword'] ?? null;
438
439 52
            case self::CRATE_ATTR_SSL_KEY_PATH:
440 52
                return $this->attributes['sslKey'] ?? null;
441
442 52
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
443 52
                return $this->attributes['sslKeyPassword'] ?? null;
444
445
            default:
446
                // PHP Switch is a lose comparison
447 4
                if ($attribute === self::ATTR_AUTOCOMMIT) {
448 2
                    return true;
449
                }
450
451 2
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
452
        }
453
    }
454
455
    /**
456
     * {@inheritDoc}
457
     */
458 8
    #[\ReturnTypeWillChange]
459
    public function quote(string $string, int $parameter_type = self::PARAM_STR)
460
    {
461
        switch ($parameter_type) {
462 8
            case self::PARAM_INT:
463 2
                return (int)$string;
464
465 8
            case self::PARAM_BOOL:
466 2
                return (bool)$string;
467
468 8
            case self::PARAM_NULL:
469 2
                return null;
470
471 6
            case self::PARAM_LOB:
472 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
473
474 4
            case self::PARAM_STR:
475 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
476
477
            default:
478 2
                throw new Exception\InvalidArgumentException('Unknown param type');
479
        }
480
    }
481
482
    /**
483
     * {@inheritDoc}
484
     */
485 2
    public static function getAvailableDrivers(): array
486
    {
487 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
488
    }
489
490
    public function getServerVersion(): string
491
    {
492
        return $this->server->getServerVersion();
493
    }
494
495
    public function getServerInfo(): string
496
    {
497
        return $this->getServerVersion();
498
    }
499
}
500