Skip to content

Commit f3aae28

Browse files
Merge pull request #325 from vlucas/parser-backport
[2.6] Backport parser fixes from 3.3.0
2 parents cea7e2e + e918eac commit f3aae28

File tree

5 files changed

+96
-53
lines changed

5 files changed

+96
-53
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
}
1212
],
1313
"require": {
14-
"php": ">=5.3.9"
14+
"php": ">=5.3.9",
15+
"symfony/polyfill-ctype": "^1.9"
1516
},
1617
"require-dev": {
1718
"phpunit/phpunit": "^4.8.35 || ^5.0"

src/Loader.php

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Dotenv;
44

5-
use Dotenv\Exception\InvalidFileException;
65
use Dotenv\Exception\InvalidPathException;
76

87
/**
@@ -235,42 +234,7 @@ protected function sanitiseVariableValue($name, $value)
235234
return array($name, $value);
236235
}
237236

238-
if ($this->beginsWithAQuote($value)) { // value starts with a quote
239-
$quote = $value[0];
240-
$regexPattern = sprintf(
241-
'/^
242-
%1$s # match a quote at the start of the value
243-
( # capturing sub-pattern used
244-
(?: # we do not need to capture this
245-
[^%1$s\\\\]* # any character other than a quote or backslash
246-
|\\\\\\\\ # or two backslashes together
247-
|\\\\%1$s # or an escaped quote e.g \"
248-
)* # as many characters that match the previous rules
249-
) # end of the capturing sub-pattern
250-
%1$s # and the closing quote
251-
.*$ # and discard any string after the closing quote
252-
/mx',
253-
$quote
254-
);
255-
$value = preg_replace($regexPattern, '$1', $value);
256-
$value = str_replace("\\$quote", $quote, $value);
257-
$value = str_replace('\\\\', '\\', $value);
258-
} else {
259-
$parts = explode(' #', $value, 2);
260-
$value = trim($parts[0]);
261-
262-
// Unquoted values cannot contain whitespace
263-
if (preg_match('/\s+/', $value) > 0) {
264-
// Check if value is a comment (usually triggered when empty value with comment)
265-
if (preg_match('/^#/', $value) > 0) {
266-
$value = '';
267-
} else {
268-
throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
269-
}
270-
}
271-
}
272-
273-
return array($name, trim($value));
237+
return array($name, Parser::parseValue($value));
274238
}
275239

276240
/**
@@ -314,21 +278,7 @@ function ($matchedPatterns) use ($loader) {
314278
*/
315279
protected function sanitiseVariableName($name, $value)
316280
{
317-
$name = trim(str_replace(array('export ', '\'', '"'), '', $name));
318-
319-
return array($name, $value);
320-
}
321-
322-
/**
323-
* Determine if the given string begins with a quote.
324-
*
325-
* @param string $value
326-
*
327-
* @return bool
328-
*/
329-
protected function beginsWithAQuote($value)
330-
{
331-
return isset($value[0]) && ($value[0] === '"' || $value[0] === '\'');
281+
return array(Parser::parseName($name), $value);
332282
}
333283

334284
/**

src/Parser.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace Dotenv;
4+
5+
use Dotenv\Exception\InvalidFileException;
6+
7+
class Parser
8+
{
9+
const INITIAL_STATE = 0;
10+
const UNQUOTED_STATE = 1;
11+
const QUOTED_STATE = 2;
12+
const ESCAPE_STATE = 3;
13+
const WHITESPACE_STATE = 4;
14+
const COMMENT_STATE = 5;
15+
16+
/**
17+
* Parse the given variable name.
18+
*
19+
* @param string $name
20+
*
21+
* @return string
22+
*/
23+
public static function parseName($name)
24+
{
25+
return trim(str_replace(array('export ', '\'', '"'), '', $name));
26+
}
27+
28+
/**
29+
* Parse the given variable value.
30+
*
31+
* @param string $value
32+
*
33+
* @throws \Dotenv\Exception\InvalidFileException
34+
*
35+
* @return string
36+
*/
37+
public static function parseValue($value)
38+
{
39+
$data = array_reduce(str_split($value), function ($data, $char) use ($value) {
40+
switch ($data[1]) {
41+
case Parser::INITIAL_STATE:
42+
if ($char === '"') {
43+
return array($data[0], Parser::QUOTED_STATE);
44+
} else {
45+
return array($data[0].$char, Parser::UNQUOTED_STATE);
46+
}
47+
case Parser::UNQUOTED_STATE:
48+
if ($char === '#') {
49+
return array($data[0], Parser::COMMENT_STATE);
50+
} elseif (ctype_space($char)) {
51+
return array($data[0], Parser::WHITESPACE_STATE);
52+
} else {
53+
return array($data[0].$char, Parser::UNQUOTED_STATE);
54+
}
55+
case Parser::QUOTED_STATE:
56+
if ($char === '"') {
57+
return array($data[0], Parser::WHITESPACE_STATE);
58+
} elseif ($char === '\\') {
59+
return array($data[0], Parser::ESCAPE_STATE);
60+
} else {
61+
return array($data[0].$char, Parser::QUOTED_STATE);
62+
}
63+
case Parser::ESCAPE_STATE:
64+
if ($char === '"' || $char === '\\') {
65+
return array($data[0].$char, Parser::QUOTED_STATE);
66+
} else {
67+
return array($data[0].'\\'.$char, Parser::QUOTED_STATE);
68+
}
69+
case Parser::WHITESPACE_STATE:
70+
if ($char === '#') {
71+
return array($data[0], Parser::COMMENT_STATE);
72+
} elseif (!ctype_space($char)) {
73+
if ($data[0] !== '' && $data[0][0] === '#') {
74+
return array('', Parser::COMMENT_STATE);
75+
} else {
76+
throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
77+
}
78+
} else {
79+
return array($data[0], Parser::WHITESPACE_STATE);
80+
}
81+
case Parser::COMMENT_STATE:
82+
return array($data[0], Parser::COMMENT_STATE);
83+
}
84+
}, array('', Parser::INITIAL_STATE));
85+
86+
return trim($data[0]);
87+
}
88+
}

tests/Dotenv/DotenvTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public function testQuotedDotenvLoadsEnvironmentVars()
6565
$this->assertEmpty(getenv('QNULL'));
6666
$this->assertSame('pgsql:host=localhost;dbname=test', getenv('QEQUALS'));
6767
$this->assertSame('test some escaped characters like a quote (") or maybe a backslash (\\)', getenv('QESCAPED'));
68+
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('QSLASH1'));
69+
$this->assertSame('iiiiviiiixiiiiviiii\\n', getenv('QSLASH2'));
6870
}
6971

7072
public function testLargeDotenvLoadsEnvironmentVars()

tests/fixtures/env/quoted.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ QNULL=""
77
QWHITESPACE = "no space"
88

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

0 commit comments

Comments
 (0)