Skip to content

Commit 8848bf4

Browse files
Backport new parser design, keeping BC
1 parent d7f715a commit 8848bf4

File tree

3 files changed

+92
-53
lines changed

3 files changed

+92
-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
/**
@@ -229,42 +228,7 @@ protected function sanitiseVariableValue($name, $value)
229228
return array($name, $value);
230229
}
231230

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

270234
/**
@@ -308,21 +272,7 @@ function ($matchedPatterns) use ($loader) {
308272
*/
309273
protected function sanitiseVariableName($name, $value)
310274
{
311-
$name = trim(str_replace(array('export ', '\'', '"'), '', $name));
312-
313-
return array($name, $value);
314-
}
315-
316-
/**
317-
* Determine if the given string begins with a quote.
318-
*
319-
* @param string $value
320-
*
321-
* @return bool
322-
*/
323-
protected function beginsWithAQuote($value)
324-
{
325-
return isset($value[0]) && ($value[0] === '"' || $value[0] === '\'');
275+
return array(Parser::parseName($name), $value);
326276
}
327277

328278
/**

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 self::INITIAL_STATE:
42+
if ($char === '"') {
43+
return array($data[0], self::QUOTED_STATE);
44+
} else {
45+
return array($data[0].$char, self::UNQUOTED_STATE);
46+
}
47+
case self::UNQUOTED_STATE:
48+
if ($char === '#') {
49+
return array($data[0], self::COMMENT_STATE);
50+
} elseif (ctype_space($char)) {
51+
return array($data[0], self::WHITESPACE_STATE);
52+
} else {
53+
return array($data[0].$char, self::UNQUOTED_STATE);
54+
}
55+
case self::QUOTED_STATE:
56+
if ($char === '"') {
57+
return array($data[0], self::WHITESPACE_STATE);
58+
} elseif ($char === '\\') {
59+
return array($data[0], self::ESCAPE_STATE);
60+
} else {
61+
return array($data[0].$char, self::QUOTED_STATE);
62+
}
63+
case self::ESCAPE_STATE:
64+
if ($char === '"' || $char === '\\') {
65+
return array($data[0].$char, self::QUOTED_STATE);
66+
} else {
67+
return array($data[0].'\\'.$char, self::QUOTED_STATE);
68+
}
69+
case self::WHITESPACE_STATE:
70+
if ($char === '#') {
71+
return array($data[0], self::COMMENT_STATE);
72+
} elseif (!ctype_space($char)) {
73+
if ($data[0] !== '' && $data[0][0] === '#') {
74+
return array('', self::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], self::WHITESPACE_STATE);
80+
}
81+
case self::COMMENT_STATE:
82+
return array($data[0], self::COMMENT_STATE);
83+
}
84+
}, array('', self::INITIAL_STATE));
85+
86+
return trim($data[0]);
87+
}
88+
}

0 commit comments

Comments
 (0)