Skip to content

Commit

Permalink
bug #29222 [Dotenv] properly parse backslashes in unquoted env vars (…
Browse files Browse the repository at this point in the history
…xabbuh)

This PR was merged into the 3.4 branch.

Discussion
----------

[Dotenv] properly parse backslashes in unquoted env vars

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #29202
| License       | MIT
| Doc PR        |

Commits
-------

785fff5 properly parse backslashes in unquoted env vars
  • Loading branch information
fabpot committed Nov 24, 2018
2 parents 0f2d577 + 785fff5 commit 7f310b4
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 13 deletions.
34 changes: 21 additions & 13 deletions src/Symfony/Component/Dotenv/Dotenv.php
Expand Up @@ -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 = '';
Expand All @@ -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');
Expand Down Expand Up @@ -350,24 +352,31 @@ private function resolveVariables($value)
}

$regex = '/
(\\\\)? # escaped with a backslash?
(?<!\\\\)
(?P<backslashes>\\\\*) # escaped with a backslash?
\$
(?!\() # no opening parenthesis
(\{)? # optional brace
('.self::VARNAME_REGEX.') # var name
(\})? # optional closing brace
(?!\() # no opening parenthesis
(?P<opening_brace>\{)? # optional brace
(?P<name>'.self::VARNAME_REGEX.')? # var name
(?P<closing_brace>\})? # 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_')) {
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/Symfony/Component/Dotenv/Tests/DotenvTest.php
Expand Up @@ -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')),
Expand Down

0 comments on commit 7f310b4

Please sign in to comment.