Skip to content

Commit

Permalink
Refactored parser and validate names (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamCampbell committed Jan 3, 2019
1 parent f23d65a commit 0136b7a
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 47 deletions.
133 changes: 97 additions & 36 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public static function parse($entry)
*
* @param string $line
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return array
*/
private static function splitStringIntoParts($line)
Expand All @@ -45,6 +47,12 @@ private static function splitStringIntoParts($line)
list($name, $value) = array_map('trim', explode('=', $line, 2));
}

if ($name === '') {
throw new InvalidFileException(
self::getErrorMessage('an unexpected equals', $line)
);
}

return [$name, $value];
}

Expand All @@ -53,15 +61,37 @@ private static function splitStringIntoParts($line)
*
* @param string $name
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return string
*/
private static function sanitiseName($name)
{
return trim(str_replace(['export ', '\'', '"'], '', $name));
$name = trim(str_replace(['export ', '\'', '"'], '', $name));

if (!self::isValidName($name)) {
throw new InvalidFileException(
self::getErrorMessage('an invalid name', $name)
);
}

return $name;
}

/**
* Strips quotes from the environment variable value.
* Is the given variable name valid?
*
* @param string $name
*
* @return bool
*/
private static function isValidName($name)
{
return preg_match('~\A[a-zA-Z0-9_.]+\z~', $name) === 1;
}

/**
* Strips quotes and comments from the environment variable value.
*
* @param string|null $value
*
Expand All @@ -75,46 +105,77 @@ private static function sanitiseValue($value)
return $value;
}

if (self::beginsWithAQuote($value)) { // value starts with a quote
$quote = $value[0];
$regexPattern = sprintf(
'/^
%1$s # match a quote at the start of the value
( # capturing sub-pattern used
(?: # we do not need to capture this
[^%1$s\\\\]+ # any character other than a quote or backslash
|\\\\\\\\ # or two backslashes together
|\\\\%1$s # or an escaped quote e.g \"
)* # as many characters that match the previous rules
) # end of the capturing sub-pattern
%1$s # and the closing quote
.*$ # and discard any string after the closing quote
/mx',
$quote
);
$value = preg_replace($regexPattern, '$1', $value);
$value = str_replace("\\$quote", $quote, $value);
$value = str_replace('\\\\', '\\', $value);
} else {
$parts = explode(' #', $value, 2);
$value = $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.'
);
}
if (self::beginsWithAQuote($value)) {
return self::processQuotedValue($value);
}

// Strip comments from the left
$value = explode(' #', $value, 2)[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(
self::getErrorMessage('an unexpected space', $value)
);
}
}

return $value;
}

/**
* Strips quotes from the environment variable value.
*
* @param string $value
*
* @return string
*/
private static function processQuotedValue($value)
{
$quote = $value[0];

$pattern = sprintf(
'/^
%1$s # match a quote at the start of the value
( # capturing sub-pattern used
(?: # we do not need to capture this
[^%1$s\\\\]+ # any character other than a quote or backslash
|\\\\\\\\ # or two backslashes together
|\\\\%1$s # or an escaped quote e.g \"
)* # as many characters that match the previous rules
) # end of the capturing sub-pattern
%1$s # and the closing quote
.*$ # and discard any string after the closing quote
/mx',
$quote
);

$value = preg_replace($pattern, '$1', $value);

return str_replace('\\\\', '\\', str_replace("\\$quote", $quote, $value));
}

/**
* Generate a friendly error message.
*
* @param string $cause
* @param string $subject
*
* @return string
*/
private static function getErrorMessage($cause, $subject)
{
return sprintf(
'Failed to parse dotenv file due to %s. Failed at [%s].',
$cause,
strtok($subject, "\n")
);
}

/**
* Determine if the given string begins with a quote.
*
Expand Down
10 changes: 0 additions & 10 deletions tests/Dotenv/DotenvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,6 @@ public function testQuotedDotenvLoadsEnvironmentVars()
$this->assertSame('test some escaped characters like a quote (") or maybe a backslash (\\)', getenv('QESCAPED'));
}

/**
* @expectedException \Dotenv\Exception\InvalidFileException
* @expectedExceptionMessage Dotenv values containing spaces must be surrounded by quotes.
*/
public function testSpacedValuesWithoutQuotesThrowsException()
{
$dotenv = Dotenv::create(dirname(__DIR__).'/fixtures/env-wrong', 'spaced-wrong.env');
$dotenv->load();
}

public function testExportedDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::create($this->fixturesFolder, 'exported.env');
Expand Down
27 changes: 27 additions & 0 deletions tests/Dotenv/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,31 @@ public function testExportParse()
{
$this->assertSame(['FOO', 'bar baz'], Parser::parse('export FOO="bar baz"'));
}

/**
* @expectedException \Dotenv\Exception\InvalidFileException
* @expectedExceptionMessage Failed to parse dotenv file due to an unexpected space. Failed at [bar baz].
*/
public function testParseInvalidSpaces()
{
Parser::parse('FOO=bar baz');
}

/**
* @expectedException \Dotenv\Exception\InvalidFileException
* @expectedExceptionMessage Failed to parse dotenv file due to an unexpected equals. Failed at [=].
*/
public function testParseStrayEquals()
{
Parser::parse('=');
}

/**
* @expectedException \Dotenv\Exception\InvalidFileException
* @expectedExceptionMessage Failed to parse dotenv file due to an invalid name. Failed at [FOO_ASD!].
*/
public function testParseInvalidName()
{
Parser::parse('FOO_ASD!=BAZ');
}
}
1 change: 0 additions & 1 deletion tests/fixtures/env-wrong/spaced-wrong.env

This file was deleted.

0 comments on commit 0136b7a

Please sign in to comment.