From 785fff56ce76428e13a158d0fe938d9f1470767d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 14 Nov 2018 15:05:20 +0100 Subject: [PATCH] properly parse backslashes in unquoted env vars --- src/Symfony/Component/Dotenv/Dotenv.php | 34 ++++++++++++------- .../Component/Dotenv/Tests/DotenvTest.php | 14 ++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 025206326a8b..e5f5cd7e236b 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -224,10 +224,11 @@ private function lexValue() throw $this->createFormatException('Missing quote to end the value'); } ++$this->cursor; - $value = str_replace(array('\\\\', '\\"', '\r', '\n'), array('\\', '"', "\r", "\n"), $value); + $value = str_replace(array('\\"', '\r', '\n'), array('"', "\r", "\n"), $value); $resolvedValue = $value; $resolvedValue = $this->resolveVariables($resolvedValue); $resolvedValue = $this->resolveCommands($resolvedValue); + $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); $v .= $resolvedValue; } else { $value = ''; @@ -250,6 +251,7 @@ private function lexValue() $resolvedValue = $value; $resolvedValue = $this->resolveVariables($resolvedValue); $resolvedValue = $this->resolveCommands($resolvedValue); + $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); if ($resolvedValue === $value && preg_match('/\s+/', $value)) { throw $this->createFormatException('A value containing spaces must be surrounded by quotes'); @@ -350,24 +352,31 @@ private function resolveVariables($value) } $regex = '/ - (\\\\)? # escaped with a backslash? + (?\\\\*) # escaped with a backslash? \$ - (?!\() # no opening parenthesis - (\{)? # optional brace - ('.self::VARNAME_REGEX.') # var name - (\})? # optional closing brace + (?!\() # no opening parenthesis + (?P\{)? # optional brace + (?P'.self::VARNAME_REGEX.')? # var name + (?P\})? # optional closing brace /x'; $value = preg_replace_callback($regex, function ($matches) { - if ('\\' === $matches[1]) { + // odd number of backslashes means the $ character is escaped + if (1 === \strlen($matches['backslashes']) % 2) { return substr($matches[0], 1); } - if ('{' === $matches[2] && !isset($matches[4])) { + // unescaped $ not followed by variable name + if (!isset($matches['name'])) { + return $matches[0]; + } + + if ('{' === $matches['opening_brace'] && !isset($matches['closing_brace'])) { throw $this->createFormatException('Unclosed braces on variable expansion'); } - $name = $matches[3]; + $name = $matches['name']; if (isset($this->values[$name])) { $value = $this->values[$name]; } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { @@ -378,15 +387,14 @@ private function resolveVariables($value) $value = (string) getenv($name); } - if (!$matches[2] && isset($matches[4])) { + if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { $value .= '}'; } - return $value; + return $matches['backslashes'].$value; }, $value); - // unescape $ - return str_replace('\\$', '$', $value); + return $value; } private function moveCursor($text) diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index 4492c8e9e08e..332c20527b0d 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -66,6 +66,20 @@ public function getEnvData() $_ENV['REMOTE'] = 'remote'; $tests = array( + // backslashes + array('FOO=foo\\\\bar', array('FOO' => 'foo\\bar')), + array("FOO='foo\\\\bar'", array('FOO' => 'foo\\\\bar')), + array('FOO="foo\\\\bar"', array('FOO' => 'foo\\bar')), + + // escaped backslash in front of variable + array("BAR=bar\nFOO=foo\\\\\$BAR", array('BAR' => 'bar', 'FOO' => 'foo\\bar')), + array("BAR=bar\nFOO='foo\\\\\$BAR'", array('BAR' => 'bar', 'FOO' => 'foo\\\\$BAR')), + array("BAR=bar\nFOO=\"foo\\\\\$BAR\"", array('BAR' => 'bar', 'FOO' => 'foo\\bar')), + + array('FOO=foo\\\\\\$BAR', array('FOO' => 'foo\\$BAR')), + array('FOO=\'foo\\\\\\$BAR\'', array('FOO' => 'foo\\\\\\$BAR')), + array('FOO="foo\\\\\\$BAR"', array('FOO' => 'foo\\$BAR')), + // spaces array('FOO=bar', array('FOO' => 'bar')), array(' FOO=bar ', array('FOO' => 'bar')),