Skip to content

Commit

Permalink
Fixed parsing regressions (#338)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamCampbell authored Jan 29, 2019
1 parent f3aae28 commit 2a7dcf7
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 23 deletions.
84 changes: 61 additions & 23 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
class Parser
{
const INITIAL_STATE = 0;
const UNQUOTED_STATE = 1;
const QUOTED_STATE = 2;
const ESCAPE_STATE = 3;
const WHITESPACE_STATE = 4;
const COMMENT_STATE = 5;
const QUOTED_STATE = 1;
const ESCAPE_STATE = 2;
const WHITESPACE_STATE = 3;
const COMMENT_STATE = 4;

/**
* Parse the given variable name.
Expand All @@ -29,39 +28,53 @@ public static function parseName($name)
* Parse the given variable value.
*
* @param string $value
*
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return string
*/
public static function parseValue($value)
{
if ($value === '') {
return '';
} elseif ($value[0] === '"' || $value[0] === '\'') {
return Parser::parseQuotedValue($value);
} else {
return Parser::parseUnquotedValue($value);
}
}

/**
* Parse the given quoted value.
*
* @param string $value
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return string
*/
public static function parseQuotedValue($value)
{
$data = array_reduce(str_split($value), function ($data, $char) use ($value) {
switch ($data[1]) {
case Parser::INITIAL_STATE:
if ($char === '"') {
if ($char === '"' || $char === '\'') {
return array($data[0], Parser::QUOTED_STATE);
} else {
return array($data[0].$char, Parser::UNQUOTED_STATE);
}
case Parser::UNQUOTED_STATE:
if ($char === '#') {
return array($data[0], Parser::COMMENT_STATE);
} elseif (ctype_space($char)) {
return array($data[0], Parser::WHITESPACE_STATE);
} else {
return array($data[0].$char, Parser::UNQUOTED_STATE);
throw new InvalidFileException(
'Expected the value to start with a quote.'
);
}
case Parser::QUOTED_STATE:
if ($char === '"') {
if ($char === $value[0]) {
return array($data[0], Parser::WHITESPACE_STATE);
} elseif ($char === '\\') {
return array($data[0], Parser::ESCAPE_STATE);
} else {
return array($data[0].$char, Parser::QUOTED_STATE);
}
case Parser::ESCAPE_STATE:
if ($char === '"' || $char === '\\') {
if ($char === $value[0] || $char === '\\') {
return array($data[0].$char, Parser::QUOTED_STATE);
} else {
return array($data[0].'\\'.$char, Parser::QUOTED_STATE);
Expand All @@ -70,11 +83,9 @@ public static function parseValue($value)
if ($char === '#') {
return array($data[0], Parser::COMMENT_STATE);
} elseif (!ctype_space($char)) {
if ($data[0] !== '' && $data[0][0] === '#') {
return array('', Parser::COMMENT_STATE);
} else {
throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
}
throw new InvalidFileException(
'Dotenv values containing spaces must be surrounded by quotes.'
);
} else {
return array($data[0], Parser::WHITESPACE_STATE);
}
Expand All @@ -85,4 +96,31 @@ public static function parseValue($value)

return trim($data[0]);
}

/**
* Parse the given unquoted value.
*
* @param string $value
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return string
*/
public static function parseUnquotedValue($value)
{
$parts = explode(' #', $value, 2);
$value = trim($parts[0]);

// Unquoted values cannot contain whitespace
if (preg_match('/\s+/', $value) > 0) {
// Check if value is a comment (usually triggered when empty value with comment)
if (preg_match('/^#/', $value) > 0) {
$value = '';
} else {
throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
}
}

return trim($value);
}
}
8 changes: 8 additions & 0 deletions tests/Dotenv/DotenvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,14 @@ public function testQuotedDotenvLoadsEnvironmentVars()
$this->assertSame('with spaces', getenv('QSPACED'));
$this->assertEmpty(getenv('QNULL'));
$this->assertSame('pgsql:host=localhost;dbname=test', getenv('QEQUALS'));

$this->assertSame('test some escaped characters like a quote (") or maybe a backslash (\\)', getenv('QESCAPED'));
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('QSLASH1'));
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('QSLASH2'));

$this->assertSame('test some escaped characters like a quote (\') or maybe a backslash (\\)', getenv('SQESCAPED'));
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('SQSLASH1'));
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('SQSLASH2'));
}

public function testLargeDotenvLoadsEnvironmentVars()
Expand Down Expand Up @@ -261,6 +266,9 @@ public function testDotenvAllowsSpecialCharacters()
$this->assertSame('jdgEB4{QgEC]HL))&GcXxokB+wqoN+j>xkV7K?m$r', getenv('SPVAR3'));
$this->assertSame('22222:22#2^{', getenv('SPVAR4'));
$this->assertSame('test some escaped characters like a quote " or maybe a backslash \\', getenv('SPVAR5'));
$this->assertSame('secret!@#', getenv('SPVAR6'));
$this->assertSame('secret!@#', getenv('SPVAR7'));
$this->assertSame('secret!@#', getenv('SPVAR8'));
}

public function testDotenvAssertions()
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/env/quoted.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ QWHITESPACE = "no space"
QESCAPED="test some escaped characters like a quote (\") or maybe a backslash (\\)"
QSLASH1="iiiiviiiixiiiiviiii\n"
QSLASH2="iiiiviiiixiiiiviiii\\n"

SQESCAPED='test some escaped characters like a quote (\') or maybe a backslash (\\)'
SQSLASH1='iiiiviiiixiiiiviiii\n'
SQSLASH2='iiiiviiiixiiiiviiii\\n'
3 changes: 3 additions & 0 deletions tests/fixtures/env/specialchars.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ SPVAR2="?BUty3koaV3%GA*hMAwH}B"
SPVAR3="jdgEB4{QgEC]HL))&GcXxokB+wqoN+j>xkV7K?m$r"
SPVAR4="22222:22#2^{"
SPVAR5="test some escaped characters like a quote \" or maybe a backslash \\" # not escaped
SPVAR6=secret!@#
SPVAR7='secret!@#'
SPVAR8="secret!@#"

0 comments on commit 2a7dcf7

Please sign in to comment.