Skip to content

Commit

Permalink
bug #52444 Remove full DSNs from exception messages (nicolas-grekas)
Browse files Browse the repository at this point in the history
This PR was merged into the 5.4 branch.

Discussion
----------

Remove full DSNs from exception messages

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Backporting #49072 to 5.4 as a security-hardening measure.

Commits
-------

14e2f67 Remove full DSNs from exception messages
  • Loading branch information
fabpot committed Nov 3, 2023
2 parents b421063 + 14e2f67 commit 58353d1
Show file tree
Hide file tree
Showing 39 changed files with 98 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Expand Up @@ -140,7 +140,7 @@ public static function createConnection(string $dsn, array $options = [])
return CouchbaseCollectionAdapter::createConnection($dsn, $options);
}

throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn));
throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:" nor "couchbase:".');
}

/**
Expand Down
Expand Up @@ -83,7 +83,7 @@ public static function createConnection($servers, array $options = []): \Couchba

foreach ($servers as $dsn) {
if (0 !== strpos($dsn, 'couchbase:')) {
throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $dsn));
throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".');
}

preg_match($dsnPattern, $dsn, $matches);
Expand Down
Expand Up @@ -79,7 +79,7 @@ public static function createConnection($dsn, array $options = [])

foreach ($dsn as $server) {
if (0 !== strpos($server, 'couchbase:')) {
throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $server));
throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".');
}

preg_match($dsnPattern, $server, $matches);
Expand Down
Expand Up @@ -70,7 +70,7 @@ public function __construct($connOrDsn, string $namespace = '', int $defaultLife
$this->conn = $connOrDsn;
} elseif (\is_string($connOrDsn)) {
if (!class_exists(DriverManager::class)) {
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrDsn));
throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".');
}
if (class_exists(DsnParser::class)) {
$params = (new DsnParser([
Expand Down
8 changes: 4 additions & 4 deletions src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php
Expand Up @@ -109,7 +109,7 @@ public static function createConnection($servers, array $options = [])
continue;
}
if (!str_starts_with($dsn, 'memcached:')) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn));
throw new InvalidArgumentException('Invalid Memcached DSN: it does not start with "memcached:".');
}
$params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
if (!empty($m[2])) {
Expand All @@ -119,15 +119,15 @@ public static function createConnection($servers, array $options = [])
return 'file:'.($m[1] ?? '');
}, $dsn);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Memcached DSN.');
}
$query = $hosts = [];
if (isset($params['query'])) {
parse_str($params['query'], $query);

if (isset($query['host'])) {
if (!\is_array($hosts = $query['host'])) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Memcached DSN: query parameter "host" must be an array.');
}
foreach ($hosts as $host => $weight) {
if (false === $port = strrpos($host, ':')) {
Expand All @@ -146,7 +146,7 @@ public static function createConnection($servers, array $options = [])
}
}
if (!isset($params['host']) && !isset($params['path'])) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Memcached DSN: missing host or path.');
}
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
$params['weight'] = $m[1];
Expand Down
36 changes: 18 additions & 18 deletions src/Symfony/Component/Cache/Traits/RedisTrait.php
Expand Up @@ -96,11 +96,11 @@ public static function createConnection(string $dsn, array $options = [])
} elseif (str_starts_with($dsn, 'rediss:')) {
$scheme = 'rediss';
} else {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn));
throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".');
}

if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn));
throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.');
}

$params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
Expand All @@ -116,7 +116,7 @@ public static function createConnection(string $dsn, array $options = [])
}, $dsn);

if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Redis DSN.');
}

$query = $hosts = [];
Expand All @@ -129,7 +129,7 @@ public static function createConnection(string $dsn, array $options = [])

if (isset($query['host'])) {
if (!\is_array($hosts = $query['host'])) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Redis DSN: query parameter "host" must be an array.');
}
foreach ($hosts as $host => $parameters) {
if (\is_string($parameters)) {
Expand All @@ -153,7 +153,7 @@ public static function createConnection(string $dsn, array $options = [])
$params['dbindex'] = $m[1] ?? '0';
$params['path'] = substr($params['path'], 0, -\strlen($m[0]));
} elseif (isset($params['host'])) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.', $dsn));
throw new InvalidArgumentException('Invalid Redis DSN: query parameter "dbindex" must be a number.');
}
}

Expand All @@ -165,13 +165,13 @@ public static function createConnection(string $dsn, array $options = [])
}

if (!$hosts) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Redis DSN: missing host.');
}

$params += $query + $options + self::$defaultConnectionOptions;

if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) {
throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher: "%s".', $dsn));
throw new CacheException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.');
}

if (isset($params['lazy'])) {
Expand All @@ -180,7 +180,7 @@ public static function createConnection(string $dsn, array $options = [])
$params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN);

if ($params['redis_cluster'] && isset($params['redis_sentinel'])) {
throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn));
throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.');
}

if (null === $params['class'] && \extension_loaded('redis')) {
Expand All @@ -189,15 +189,15 @@ public static function createConnection(string $dsn, array $options = [])
$class = $params['class'] ?? \Predis\Client::class;

if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class)) {
throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found: "%s".', $class, $dsn));
throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found.', $class));
}
}

if (is_a($class, \Redis::class, true)) {
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
$redis = new $class();

$initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts, $tls) {
$initializer = static function ($redis) use ($connect, $params, $auth, $hosts, $tls) {
$hostIndex = 0;
do {
$host = $hosts[$hostIndex]['host'] ?? $hosts[$hostIndex]['path'];
Expand Down Expand Up @@ -243,7 +243,7 @@ public static function createConnection(string $dsn, array $options = [])
} while (++$hostIndex < \count($hosts) && !$address);

if (isset($params['redis_sentinel']) && !$address) {
throw new InvalidArgumentException(sprintf('Failed to retrieve master information from sentinel "%s" and dsn "%s".', $params['redis_sentinel'], $dsn));
throw new InvalidArgumentException(sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']));
}

try {
Expand All @@ -262,22 +262,22 @@ public static function createConnection(string $dsn, array $options = [])
restore_error_handler();
}
if (!$isConnected) {
$error = preg_match('/^Redis::p?connect\(\): (.*)/', $error ?? '', $error) ? sprintf(' (%s)', $error[1]) : '';
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.');
$error = preg_match('/^Redis::p?connect\(\): (.*)/', $error ?? $redis->getLastError() ?? '', $error) ? sprintf(' (%s)', $error[1]) : '';
throw new InvalidArgumentException('Redis connection failed: '.$error.'.');
}

if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.');
throw new InvalidArgumentException('Redis connection failed: '.$e.'.');
}

if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
}
} catch (\RedisException $e) {
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage());
}

return true;
Expand All @@ -302,14 +302,14 @@ public static function createConnection(string $dsn, array $options = [])
try {
$redis = new $class($hosts, $params);
} catch (\RedisClusterException $e) {
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage());
}

if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
}
} elseif (is_a($class, \RedisCluster::class, true)) {
$initializer = static function () use ($class, $params, $dsn, $hosts) {
$initializer = static function () use ($class, $params, $hosts) {
foreach ($hosts as $i => $host) {
switch ($host['scheme']) {
case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break;
Expand All @@ -321,7 +321,7 @@ public static function createConnection(string $dsn, array $options = [])
try {
$redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []);
} catch (\RedisClusterException $e) {
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage());
}

if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
Expand Down
Expand Up @@ -63,7 +63,7 @@ public static function createHandler($connection): AbstractSessionHandler
case str_starts_with($connection, 'rediss:'):
case str_starts_with($connection, 'memcached:'):
if (!class_exists(AbstractAdapter::class)) {
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');
}
$handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
Expand All @@ -72,7 +72,7 @@ public static function createHandler($connection): AbstractSessionHandler

case str_starts_with($connection, 'pdo_oci://'):
if (!class_exists(DriverManager::class)) {
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection));
throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".');
}
$connection[3] = '-';
$params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection];
Expand Down
Expand Up @@ -52,7 +52,7 @@ public function __construct($connOrUrl)
$this->conn = $connOrUrl;
} elseif (\is_string($connOrUrl)) {
if (!class_exists(DriverManager::class)) {
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrUrl));
throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".');
}
if (class_exists(DsnParser::class)) {
$params = (new DsnParser([
Expand Down Expand Up @@ -274,7 +274,7 @@ private function unlockShared(Key $key): void
private function filterDsn(string $dsn): string
{
if (!str_contains($dsn, '://')) {
throw new InvalidArgumentException(sprintf('String "%s" is not a valid DSN for Doctrine DBAL.', $dsn));
throw new InvalidArgumentException('DSN is invalid for Doctrine DBAL.');
}

[$scheme, $rest] = explode(':', $dsn, 2);
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Lock/Store/DoctrineDbalStore.php
Expand Up @@ -69,7 +69,7 @@ public function __construct($connOrUrl, array $options = [], float $gcProbabilit
$this->conn = $connOrUrl;
} elseif (\is_string($connOrUrl)) {
if (!class_exists(DriverManager::class)) {
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrUrl));
throw new InvalidArgumentException('Failed to parse the DSN. Try running "composer require doctrine/dbal".');
}
if (class_exists(DsnParser::class)) {
$params = (new DsnParser([
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Lock/Store/StoreFactory.php
Expand Up @@ -75,7 +75,7 @@ public static function createStore($connection)
case str_starts_with($connection, 'rediss:'):
case str_starts_with($connection, 'memcached:'):
if (!class_exists(AbstractAdapter::class)) {
throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
throw new InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');
}
$storeClass = str_starts_with($connection, 'memcached:') ? MemcachedStore::class : RedisStore::class;
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Lock/Store/ZookeeperStore.php
Expand Up @@ -37,11 +37,11 @@ public function __construct(\Zookeeper $zookeeper)
public static function createConnection(string $dsn): \Zookeeper
{
if (!str_starts_with($dsn, 'zookeeper:')) {
throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn));
throw new InvalidArgumentException('Unsupported DSN for Zookeeper.');
}

if (false === $params = parse_url($dsn)) {
throw new InvalidArgumentException(sprintf('Invalid Zookeeper DSN: "%s".', $dsn));
throw new InvalidArgumentException('Invalid Zookeeper DSN.');
}

$host = $params['host'] ?? '';
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Mailer/Tests/Transport/DsnTest.php
Expand Up @@ -92,17 +92,17 @@ public static function invalidDsnProvider(): iterable
{
yield [
'some://',
'The "some://" mailer DSN is invalid.',
'The mailer DSN is invalid.',
];

yield [
'//sendmail',
'The "//sendmail" mailer DSN must contain a scheme.',
'The mailer DSN must contain a scheme.',
];

yield [
'file:///some/path',
'The "file:///some/path" mailer DSN must contain a host (use "default" by default).',
'The mailer DSN must contain a host (use "default" by default).',
];
}
}
6 changes: 3 additions & 3 deletions src/Symfony/Component/Mailer/Tests/TransportTest.php
Expand Up @@ -90,11 +90,11 @@ public function testFromWrongString(string $dsn, string $error)

public static function fromWrongStringProvider(): iterable
{
yield 'garbage at the end' => ['dummy://a some garbage here', 'The DSN has some garbage at the end: " some garbage here".'];
yield 'garbage at the end' => ['dummy://a some garbage here', 'The mailer DSN has some garbage at the end.'];

yield 'not a valid DSN' => ['something not a dsn', 'The "something" mailer DSN must contain a scheme.'];
yield 'not a valid DSN' => ['something not a dsn', 'The mailer DSN must contain a scheme.'];

yield 'failover not closed' => ['failover(dummy://a', 'The "(dummy://a" mailer DSN must contain a scheme.'];
yield 'failover not closed' => ['failover(dummy://a', 'The mailer DSN must contain a scheme.'];

yield 'not a valid keyword' => ['foobar(dummy://a)', 'The "foobar" keyword is not valid (valid ones are "failover", "roundrobin")'];
}
Expand Down

0 comments on commit 58353d1

Please sign in to comment.