properly parse backslashes in unquoted env vars

This commit is contained in:
Christian Flothmann 2018-11-14 15:05:20 +01:00
parent b6f9f8d769
commit 785fff56ce
2 changed files with 35 additions and 13 deletions

View File

@ -224,10 +224,11 @@ final class Dotenv
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 @@ final class Dotenv
$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 @@ final class Dotenv
}
$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_')) {
@ -378,15 +387,14 @@ final class Dotenv
$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)

View File

@ -66,6 +66,20 @@ class DotenvTest extends TestCase
$_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')),