From f0292f23be63e82110748fbebbd9eef9cefb8513 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Sun, 31 Dec 2023 10:34:28 +0100 Subject: [PATCH] [Cache][DependencyInjection][Lock][Mailer][Messenger][Notifier][Translation] Url decode username and passwords from `parse_url()` results --- .../Cache/Adapter/MemcachedAdapter.php | 2 ++ .../DependencyInjection/EnvVarProcessor.php | 15 ++++++---- .../Component/Lock/Store/MongoDbStore.php | 4 +-- .../Component/Mailer/Transport/Dsn.php | 16 +++++----- .../Bridge/AmazonSqs/Transport/Connection.php | 26 ++++++++-------- .../Bridge/Amqp/Transport/Connection.php | 20 ++++++------- .../Bridge/Doctrine/Transport/Connection.php | 8 ++--- .../Bridge/Redis/Transport/Connection.php | 30 +++++++++---------- .../Component/Notifier/Transport/Dsn.php | 20 ++++++------- .../Component/Translation/Provider/Dsn.php | 20 ++++++------- 10 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php index 2f953aa79b62e..6d63e5a370506 100644 --- a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php @@ -114,6 +114,8 @@ public static function createConnection($servers, array $options = []) $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { if (!empty($m[2])) { [$username, $password] = explode(':', $m[2], 2) + [1 => null]; + $username = rawurldecode($username); + $password = null !== $password ? rawurldecode($password) : null; } return 'file:'.($m[1] ?? ''); diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index 818174b3970c7..39c558445bcc4 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -253,15 +253,15 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) } if ('url' === $prefix) { - $parsedEnv = parse_url($env); + $params = parse_url($env); - if (false === $parsedEnv) { + if (false === $params) { throw new RuntimeException(sprintf('Invalid URL in env var "%s".', $name)); } - if (!isset($parsedEnv['scheme'], $parsedEnv['host'])) { + if (!isset($params['scheme'], $params['host'])) { throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.', $name, $env)); } - $parsedEnv += [ + $params += [ 'port' => null, 'user' => null, 'pass' => null, @@ -270,10 +270,13 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) 'fragment' => null, ]; + $params['user'] = null !== $params['user'] ? rawurldecode($params['user']) : null; + $params['pass'] = null !== $params['pass'] ? rawurldecode($params['pass']) : null; + // remove the '/' separator - $parsedEnv['path'] = '/' === ($parsedEnv['path'] ?? '/') ? '' : substr($parsedEnv['path'], 1); + $params['path'] = '/' === ($params['path'] ?? '/') ? '' : substr($params['path'], 1); - return $parsedEnv; + return $params; } if ('query_string' === $prefix) { diff --git a/src/Symfony/Component/Lock/Store/MongoDbStore.php b/src/Symfony/Component/Lock/Store/MongoDbStore.php index d645a01932416..f8683c887e903 100644 --- a/src/Symfony/Component/Lock/Store/MongoDbStore.php +++ b/src/Symfony/Component/Lock/Store/MongoDbStore.php @@ -137,10 +137,10 @@ public function __construct($mongo, array $options = [], float $initialTtl = 300 */ private function skimUri(string $uri): string { - if (false === $parsedUrl = parse_url($uri)) { + if (false === $params = parse_url($uri)) { throw new InvalidArgumentException(sprintf('The given MongoDB Connection URI "%s" is invalid.', $uri)); } - $pathDb = ltrim($parsedUrl['path'] ?? '', '/') ?: null; + $pathDb = ltrim($params['path'] ?? '', '/') ?: null; if (null !== $pathDb) { $this->options['database'] = $pathDb; } diff --git a/src/Symfony/Component/Mailer/Transport/Dsn.php b/src/Symfony/Component/Mailer/Transport/Dsn.php index cef6041ef4c20..108a9df39e520 100644 --- a/src/Symfony/Component/Mailer/Transport/Dsn.php +++ b/src/Symfony/Component/Mailer/Transport/Dsn.php @@ -37,24 +37,24 @@ public function __construct(string $scheme, string $host, string $user = null, s public static function fromString(string $dsn): self { - if (false === $parsedDsn = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException('The mailer DSN is invalid.'); } - if (!isset($parsedDsn['scheme'])) { + if (!isset($params['scheme'])) { throw new InvalidArgumentException('The mailer DSN must contain a scheme.'); } - if (!isset($parsedDsn['host'])) { + if (!isset($params['host'])) { throw new InvalidArgumentException('The mailer DSN must contain a host (use "default" by default).'); } - $user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null; - $password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null; - $port = $parsedDsn['port'] ?? null; - parse_str($parsedDsn['query'] ?? '', $query); + $user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null; + $password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null; + $port = $params['port'] ?? null; + parse_str($params['query'] ?? '', $query); - return new self($parsedDsn['scheme'], $parsedDsn['host'], $user, $password, $port, $query); + return new self($params['scheme'], $params['host'], $user, $password, $port, $query); } public function getScheme(): string diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php index 3588ab323a5db..55dc57c2e5329 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php @@ -103,13 +103,13 @@ public function __destruct() */ public static function fromDsn(string $dsn, array $options = [], HttpClientInterface $client = null, LoggerInterface $logger = null): self { - if (false === $parsedUrl = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException('The given Amazon SQS DSN is invalid.'); } $query = []; - if (isset($parsedUrl['query'])) { - parse_str($parsedUrl['query'], $query); + if (isset($params['query'])) { + parse_str($params['query'], $query); } // check for extra keys in options @@ -136,24 +136,24 @@ public static function fromDsn(string $dsn, array $options = [], HttpClientInter $clientConfiguration = [ 'region' => $options['region'], - 'accessKeyId' => urldecode($parsedUrl['user'] ?? '') ?: $options['access_key'] ?? self::DEFAULT_OPTIONS['access_key'], - 'accessKeySecret' => urldecode($parsedUrl['pass'] ?? '') ?: $options['secret_key'] ?? self::DEFAULT_OPTIONS['secret_key'], + 'accessKeyId' => rawurldecode($params['user'] ?? '') ?: $options['access_key'] ?? self::DEFAULT_OPTIONS['access_key'], + 'accessKeySecret' => rawurldecode($params['pass'] ?? '') ?: $options['secret_key'] ?? self::DEFAULT_OPTIONS['secret_key'], ]; if (isset($options['debug'])) { $clientConfiguration['debug'] = $options['debug']; } unset($query['region']); - if ('default' !== ($parsedUrl['host'] ?? 'default')) { - $clientConfiguration['endpoint'] = sprintf('%s://%s%s', ($query['sslmode'] ?? null) === 'disable' ? 'http' : 'https', $parsedUrl['host'], ($parsedUrl['port'] ?? null) ? ':'.$parsedUrl['port'] : ''); - if (preg_match(';^sqs\.([^\.]++)\.amazonaws\.com$;', $parsedUrl['host'], $matches)) { + if ('default' !== ($params['host'] ?? 'default')) { + $clientConfiguration['endpoint'] = sprintf('%s://%s%s', ($query['sslmode'] ?? null) === 'disable' ? 'http' : 'https', $params['host'], ($params['port'] ?? null) ? ':'.$params['port'] : ''); + if (preg_match(';^sqs\.([^\.]++)\.amazonaws\.com$;', $params['host'], $matches)) { $clientConfiguration['region'] = $matches[1]; } } elseif (self::DEFAULT_OPTIONS['endpoint'] !== $options['endpoint'] ?? self::DEFAULT_OPTIONS['endpoint']) { $clientConfiguration['endpoint'] = $options['endpoint']; } - $parsedPath = explode('/', ltrim($parsedUrl['path'] ?? '/', '/')); + $parsedPath = explode('/', ltrim($params['path'] ?? '/', '/')); if (\count($parsedPath) > 0 && !empty($queueName = end($parsedPath))) { $configuration['queue_name'] = $queueName; } @@ -163,11 +163,11 @@ public static function fromDsn(string $dsn, array $options = [], HttpClientInter // https://sqs.REGION.amazonaws.com/ACCOUNT/QUEUE $queueUrl = null; if ( - 'https' === $parsedUrl['scheme'] - && ($parsedUrl['host'] ?? 'default') === "sqs.{$clientConfiguration['region']}.amazonaws.com" - && ($parsedUrl['path'] ?? '/') === "/{$configuration['account']}/{$configuration['queue_name']}" + 'https' === $params['scheme'] + && ($params['host'] ?? 'default') === "sqs.{$clientConfiguration['region']}.amazonaws.com" + && ($params['path'] ?? '/') === "/{$configuration['account']}/{$configuration['queue_name']}" ) { - $queueUrl = 'https://'.$parsedUrl['host'].$parsedUrl['path']; + $queueUrl = 'https://'.$params['host'].$params['path']; } return new self($configuration, new SqsClient($clientConfiguration, null, $client, $logger), $queueUrl); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 0357575e3edfb..4fc653c6aab90 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -177,24 +177,24 @@ public function __construct(array $connectionOptions, array $exchangeOptions, ar */ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self { - if (false === $parsedUrl = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { // this is a valid URI that parse_url cannot handle when you want to pass all parameters as options if (!\in_array($dsn, ['amqp://', 'amqps://'])) { throw new InvalidArgumentException('The given AMQP DSN is invalid.'); } - $parsedUrl = []; + $params = []; } $useAmqps = 0 === strpos($dsn, 'amqps://'); - $pathParts = isset($parsedUrl['path']) ? explode('/', trim($parsedUrl['path'], '/')) : []; + $pathParts = isset($params['path']) ? explode('/', trim($params['path'], '/')) : []; $exchangeName = $pathParts[1] ?? 'messages'; - parse_str($parsedUrl['query'] ?? '', $parsedQuery); + parse_str($params['query'] ?? '', $parsedQuery); $port = $useAmqps ? 5671 : 5672; $amqpOptions = array_replace_recursive([ - 'host' => $parsedUrl['host'] ?? 'localhost', - 'port' => $parsedUrl['port'] ?? $port, + 'host' => $params['host'] ?? 'localhost', + 'port' => $params['port'] ?? $port, 'vhost' => isset($pathParts[0]) ? urldecode($pathParts[0]) : '/', 'exchange' => [ 'name' => $exchangeName, @@ -203,12 +203,12 @@ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $am self::validateOptions($amqpOptions); - if (isset($parsedUrl['user'])) { - $amqpOptions['login'] = urldecode($parsedUrl['user']); + if (isset($params['user'])) { + $amqpOptions['login'] = rawurldecode($params['user']); } - if (isset($parsedUrl['pass'])) { - $amqpOptions['password'] = urldecode($parsedUrl['pass']); + if (isset($params['pass'])) { + $amqpOptions['password'] = rawurldecode($params['pass']); } if (!isset($amqpOptions['queues'])) { diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index ed9a57a0ce568..4d83fa4ca3245 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -86,16 +86,16 @@ public function getConfiguration(): array public static function buildConfiguration(string $dsn, array $options = []): array { - if (false === $components = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException('The given Doctrine Messenger DSN is invalid.'); } $query = []; - if (isset($components['query'])) { - parse_str($components['query'], $query); + if (isset($params['query'])) { + parse_str($params['query'], $query); } - $configuration = ['connection' => $components['host']]; + $configuration = ['connection' => $params['host']]; $configuration += $query + $options + static::DEFAULT_OPTIONS; $configuration['auto_setup'] = filter_var($configuration['auto_setup'], \FILTER_VALIDATE_BOOLEAN); diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index df1edaae55774..16633a354fcfe 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -155,7 +155,7 @@ private static function initializeRedisCluster(?\RedisCluster $redis, array $hos public static function fromDsn(string $dsn, array $redisOptions = [], $redis = null): self { if (false === strpos($dsn, ',')) { - $parsedUrl = self::parseDsn($dsn, $redisOptions); + $params = self::parseDsn($dsn, $redisOptions); } else { $dsns = explode(',', $dsn); $parsedUrls = array_map(function ($dsn) use (&$redisOptions) { @@ -163,10 +163,10 @@ public static function fromDsn(string $dsn, array $redisOptions = [], $redis = n }, $dsns); // Merge all the URLs, the last one overrides the previous ones - $parsedUrl = array_merge(...$parsedUrls); + $params = array_merge(...$parsedUrls); // Regroup all the hosts in an array interpretable by RedisCluster - $parsedUrl['host'] = array_map(function ($parsedUrl) { + $params['host'] = array_map(function ($parsedUrl) { if (!isset($parsedUrl['host'])) { throw new InvalidArgumentException('Missing host in DSN, it must be defined when using Redis Cluster.'); } @@ -209,7 +209,7 @@ public static function fromDsn(string $dsn, array $redisOptions = [], $redis = n unset($redisOptions['dbindex']); } - $tls = 'rediss' === $parsedUrl['scheme']; + $tls = 'rediss' === $params['scheme']; if (\array_key_exists('tls', $redisOptions)) { trigger_deprecation('symfony/redis-messenger', '5.3', 'Providing "tls" parameter is deprecated, use "rediss://" DSN scheme instead'); $tls = filter_var($redisOptions['tls'], \FILTER_VALIDATE_BOOLEAN); @@ -242,17 +242,17 @@ public static function fromDsn(string $dsn, array $redisOptions = [], $redis = n 'claim_interval' => $claimInterval, ]; - if (isset($parsedUrl['host'])) { - $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; - $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; + if (isset($params['host'])) { + $user = isset($params['user']) && '' !== $params['user'] ? rawurldecode($params['user']) : null; + $pass = isset($params['pass']) && '' !== $params['pass'] ? rawurldecode($params['pass']) : null; $connectionCredentials = [ - 'host' => $parsedUrl['host'] ?? '127.0.0.1', - 'port' => $parsedUrl['port'] ?? 6379, + 'host' => $params['host'], + 'port' => $params['port'] ?? 6379, // See: https://github.com/phpredis/phpredis/#auth 'auth' => $redisOptions['auth'] ?? (null !== $pass && null !== $user ? [$user, $pass] : ($pass ?? $user)), ]; - $pathParts = explode('/', rtrim($parsedUrl['path'] ?? '', '/')); + $pathParts = explode('/', rtrim($params['path'] ?? '', '/')); $configuration['stream'] = $pathParts[1] ?? $configuration['stream']; $configuration['group'] = $pathParts[2] ?? $configuration['group']; @@ -262,7 +262,7 @@ public static function fromDsn(string $dsn, array $redisOptions = [], $redis = n } } else { $connectionCredentials = [ - 'host' => $parsedUrl['path'], + 'host' => $params['path'], 'port' => 0, ]; } @@ -279,15 +279,15 @@ private static function parseDsn(string $dsn, array &$redisOptions): array $url = str_replace($scheme.':', 'file:', $dsn); } - if (false === $parsedUrl = parse_url($url)) { + if (false === $params = parse_url($url)) { throw new InvalidArgumentException('The given Redis DSN is invalid.'); } - if (isset($parsedUrl['query'])) { - parse_str($parsedUrl['query'], $dsnOptions); + if (isset($params['query'])) { + parse_str($params['query'], $dsnOptions); $redisOptions = array_merge($redisOptions, $dsnOptions); } - return $parsedUrl; + return $params; } private static function validateOptions(array $options): void diff --git a/src/Symfony/Component/Notifier/Transport/Dsn.php b/src/Symfony/Component/Notifier/Transport/Dsn.php index 6f4c3577a8c1a..667b7f80b7306 100644 --- a/src/Symfony/Component/Notifier/Transport/Dsn.php +++ b/src/Symfony/Component/Notifier/Transport/Dsn.php @@ -33,25 +33,25 @@ public function __construct(string $dsn) { $this->originalDsn = $dsn; - if (false === $parsedDsn = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException('The notifier DSN is invalid.'); } - if (!isset($parsedDsn['scheme'])) { + if (!isset($params['scheme'])) { throw new InvalidArgumentException('The notifier DSN must contain a scheme.'); } - $this->scheme = $parsedDsn['scheme']; + $this->scheme = $params['scheme']; - if (!isset($parsedDsn['host'])) { + if (!isset($params['host'])) { throw new InvalidArgumentException('The notifier DSN must contain a host (use "default" by default).'); } - $this->host = $parsedDsn['host']; + $this->host = $params['host']; - $this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null; - $this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null; - $this->port = $parsedDsn['port'] ?? null; - $this->path = $parsedDsn['path'] ?? null; - parse_str($parsedDsn['query'] ?? '', $this->options); + $this->user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null; + $this->password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null; + $this->port = $params['port'] ?? null; + $this->path = $params['path'] ?? null; + parse_str($params['query'] ?? '', $this->options); } public function getScheme(): string diff --git a/src/Symfony/Component/Translation/Provider/Dsn.php b/src/Symfony/Component/Translation/Provider/Dsn.php index 792b8dc1dcc8a..4b88c74dbb8c7 100644 --- a/src/Symfony/Component/Translation/Provider/Dsn.php +++ b/src/Symfony/Component/Translation/Provider/Dsn.php @@ -33,25 +33,25 @@ public function __construct(string $dsn) { $this->originalDsn = $dsn; - if (false === $parsedDsn = parse_url($dsn)) { + if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException('The translation provider DSN is invalid.'); } - if (!isset($parsedDsn['scheme'])) { + if (!isset($params['scheme'])) { throw new InvalidArgumentException('The translation provider DSN must contain a scheme.'); } - $this->scheme = $parsedDsn['scheme']; + $this->scheme = $params['scheme']; - if (!isset($parsedDsn['host'])) { + if (!isset($params['host'])) { throw new InvalidArgumentException('The translation provider DSN must contain a host (use "default" by default).'); } - $this->host = $parsedDsn['host']; + $this->host = $params['host']; - $this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null; - $this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null; - $this->port = $parsedDsn['port'] ?? null; - $this->path = $parsedDsn['path'] ?? null; - parse_str($parsedDsn['query'] ?? '', $this->options); + $this->user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null; + $this->password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null; + $this->port = $params['port'] ?? null; + $this->path = $params['path'] ?? null; + parse_str($params['query'] ?? '', $this->options); } public function getScheme(): string