Skip to content

Commit

Permalink
Merge pull request #325 from vlucas/parser-backport
Browse files Browse the repository at this point in the history
[2.6] Backport parser fixes from 3.3.0
  • Loading branch information
GrahamCampbell authored Jan 28, 2019
2 parents cea7e2e + e918eac commit f3aae28
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 53 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
}
],
"require": {
"php": ">=5.3.9"
"php": ">=5.3.9",
"symfony/polyfill-ctype": "^1.9"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.0"
Expand Down
54 changes: 2 additions & 52 deletions src/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Dotenv;

use Dotenv\Exception\InvalidFileException;
use Dotenv\Exception\InvalidPathException;

/**
Expand Down Expand Up @@ -235,42 +234,7 @@ protected function sanitiseVariableValue($name, $value)
return array($name, $value);
}

if ($this->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 = 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 array($name, trim($value));
return array($name, Parser::parseValue($value));
}

/**
Expand Down Expand Up @@ -314,21 +278,7 @@ function ($matchedPatterns) use ($loader) {
*/
protected function sanitiseVariableName($name, $value)
{
$name = trim(str_replace(array('export ', '\'', '"'), '', $name));

return array($name, $value);
}

/**
* Determine if the given string begins with a quote.
*
* @param string $value
*
* @return bool
*/
protected function beginsWithAQuote($value)
{
return isset($value[0]) && ($value[0] === '"' || $value[0] === '\'');
return array(Parser::parseName($name), $value);
}

/**
Expand Down
88 changes: 88 additions & 0 deletions src/Parser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Dotenv;

use Dotenv\Exception\InvalidFileException;

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;

/**
* Parse the given variable name.
*
* @param string $name
*
* @return string
*/
public static function parseName($name)
{
return trim(str_replace(array('export ', '\'', '"'), '', $name));
}

/**
* Parse the given variable value.
*
* @param string $value
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return string
*/
public static function parseValue($value)
{
$data = array_reduce(str_split($value), function ($data, $char) use ($value) {
switch ($data[1]) {
case Parser::INITIAL_STATE:
if ($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);
}
case Parser::QUOTED_STATE:
if ($char === '"') {
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 === '\\') {
return array($data[0].$char, Parser::QUOTED_STATE);
} else {
return array($data[0].'\\'.$char, Parser::QUOTED_STATE);
}
case Parser::WHITESPACE_STATE:
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.');
}
} else {
return array($data[0], Parser::WHITESPACE_STATE);
}
case Parser::COMMENT_STATE:
return array($data[0], Parser::COMMENT_STATE);
}
}, array('', Parser::INITIAL_STATE));

return trim($data[0]);
}
}
2 changes: 2 additions & 0 deletions tests/Dotenv/DotenvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public function testQuotedDotenvLoadsEnvironmentVars()
$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'));
}

public function testLargeDotenvLoadsEnvironmentVars()
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/env/quoted.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ QNULL=""
QWHITESPACE = "no space"

QESCAPED="test some escaped characters like a quote (\") or maybe a backslash (\\)"
QSLASH1="iiiiviiiixiiiiviiii\n"
QSLASH2="iiiiviiiixiiiiviiii\\n"

0 comments on commit f3aae28

Please sign in to comment.