Skip to content

Commit 0136b7a

Browse files
Refactored parser and validate names (#311)
1 parent f23d65a commit 0136b7a

File tree

4 files changed

+124
-47
lines changed

4 files changed

+124
-47
lines changed

src/Parser.php

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public static function parse($entry)
3434
*
3535
* @param string $line
3636
*
37+
* @throws \Dotenv\Exception\InvalidFileException
38+
*
3739
* @return array
3840
*/
3941
private static function splitStringIntoParts($line)
@@ -45,6 +47,12 @@ private static function splitStringIntoParts($line)
4547
list($name, $value) = array_map('trim', explode('=', $line, 2));
4648
}
4749

50+
if ($name === '') {
51+
throw new InvalidFileException(
52+
self::getErrorMessage('an unexpected equals', $line)
53+
);
54+
}
55+
4856
return [$name, $value];
4957
}
5058

@@ -53,15 +61,37 @@ private static function splitStringIntoParts($line)
5361
*
5462
* @param string $name
5563
*
64+
* @throws \Dotenv\Exception\InvalidFileException
65+
*
5666
* @return string
5767
*/
5868
private static function sanitiseName($name)
5969
{
60-
return trim(str_replace(['export ', '\'', '"'], '', $name));
70+
$name = trim(str_replace(['export ', '\'', '"'], '', $name));
71+
72+
if (!self::isValidName($name)) {
73+
throw new InvalidFileException(
74+
self::getErrorMessage('an invalid name', $name)
75+
);
76+
}
77+
78+
return $name;
6179
}
6280

6381
/**
64-
* Strips quotes from the environment variable value.
82+
* Is the given variable name valid?
83+
*
84+
* @param string $name
85+
*
86+
* @return bool
87+
*/
88+
private static function isValidName($name)
89+
{
90+
return preg_match('~\A[a-zA-Z0-9_.]+\z~', $name) === 1;
91+
}
92+
93+
/**
94+
* Strips quotes and comments from the environment variable value.
6595
*
6696
* @param string|null $value
6797
*
@@ -75,46 +105,77 @@ private static function sanitiseValue($value)
75105
return $value;
76106
}
77107

78-
if (self::beginsWithAQuote($value)) { // value starts with a quote
79-
$quote = $value[0];
80-
$regexPattern = sprintf(
81-
'/^
82-
%1$s # match a quote at the start of the value
83-
( # capturing sub-pattern used
84-
(?: # we do not need to capture this
85-
[^%1$s\\\\]+ # any character other than a quote or backslash
86-
|\\\\\\\\ # or two backslashes together
87-
|\\\\%1$s # or an escaped quote e.g \"
88-
)* # as many characters that match the previous rules
89-
) # end of the capturing sub-pattern
90-
%1$s # and the closing quote
91-
.*$ # and discard any string after the closing quote
92-
/mx',
93-
$quote
94-
);
95-
$value = preg_replace($regexPattern, '$1', $value);
96-
$value = str_replace("\\$quote", $quote, $value);
97-
$value = str_replace('\\\\', '\\', $value);
98-
} else {
99-
$parts = explode(' #', $value, 2);
100-
$value = $parts[0];
101-
102-
// Unquoted values cannot contain whitespace
103-
if (preg_match('/\s+/', $value) > 0) {
104-
// Check if value is a comment (usually triggered when empty value with comment)
105-
if (preg_match('/^#/', $value) > 0) {
106-
$value = '';
107-
} else {
108-
throw new InvalidFileException(
109-
'Dotenv values containing spaces must be surrounded by quotes.'
110-
);
111-
}
108+
if (self::beginsWithAQuote($value)) {
109+
return self::processQuotedValue($value);
110+
}
111+
112+
// Strip comments from the left
113+
$value = explode(' #', $value, 2)[0];
114+
115+
// Unquoted values cannot contain whitespace
116+
if (preg_match('/\s+/', $value) > 0) {
117+
// Check if value is a comment (usually triggered when empty value with comment)
118+
if (preg_match('/^#/', $value) > 0) {
119+
$value = '';
120+
} else {
121+
throw new InvalidFileException(
122+
self::getErrorMessage('an unexpected space', $value)
123+
);
112124
}
113125
}
114126

115127
return $value;
116128
}
117129

130+
/**
131+
* Strips quotes from the environment variable value.
132+
*
133+
* @param string $value
134+
*
135+
* @return string
136+
*/
137+
private static function processQuotedValue($value)
138+
{
139+
$quote = $value[0];
140+
141+
$pattern = sprintf(
142+
'/^
143+
%1$s # match a quote at the start of the value
144+
( # capturing sub-pattern used
145+
(?: # we do not need to capture this
146+
[^%1$s\\\\]+ # any character other than a quote or backslash
147+
|\\\\\\\\ # or two backslashes together
148+
|\\\\%1$s # or an escaped quote e.g \"
149+
)* # as many characters that match the previous rules
150+
) # end of the capturing sub-pattern
151+
%1$s # and the closing quote
152+
.*$ # and discard any string after the closing quote
153+
/mx',
154+
$quote
155+
);
156+
157+
$value = preg_replace($pattern, '$1', $value);
158+
159+
return str_replace('\\\\', '\\', str_replace("\\$quote", $quote, $value));
160+
}
161+
162+
/**
163+
* Generate a friendly error message.
164+
*
165+
* @param string $cause
166+
* @param string $subject
167+
*
168+
* @return string
169+
*/
170+
private static function getErrorMessage($cause, $subject)
171+
{
172+
return sprintf(
173+
'Failed to parse dotenv file due to %s. Failed at [%s].',
174+
$cause,
175+
strtok($subject, "\n")
176+
);
177+
}
178+
118179
/**
119180
* Determine if the given string begins with a quote.
120181
*

tests/Dotenv/DotenvTest.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,6 @@ public function testQuotedDotenvLoadsEnvironmentVars()
8686
$this->assertSame('test some escaped characters like a quote (") or maybe a backslash (\\)', getenv('QESCAPED'));
8787
}
8888

89-
/**
90-
* @expectedException \Dotenv\Exception\InvalidFileException
91-
* @expectedExceptionMessage Dotenv values containing spaces must be surrounded by quotes.
92-
*/
93-
public function testSpacedValuesWithoutQuotesThrowsException()
94-
{
95-
$dotenv = Dotenv::create(dirname(__DIR__).'/fixtures/env-wrong', 'spaced-wrong.env');
96-
$dotenv->load();
97-
}
98-
9989
public function testExportedDotenvLoadsEnvironmentVars()
10090
{
10191
$dotenv = Dotenv::create($this->fixturesFolder, 'exported.env');

tests/Dotenv/ParserTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,31 @@ public function testExportParse()
1919
{
2020
$this->assertSame(['FOO', 'bar baz'], Parser::parse('export FOO="bar baz"'));
2121
}
22+
23+
/**
24+
* @expectedException \Dotenv\Exception\InvalidFileException
25+
* @expectedExceptionMessage Failed to parse dotenv file due to an unexpected space. Failed at [bar baz].
26+
*/
27+
public function testParseInvalidSpaces()
28+
{
29+
Parser::parse('FOO=bar baz');
30+
}
31+
32+
/**
33+
* @expectedException \Dotenv\Exception\InvalidFileException
34+
* @expectedExceptionMessage Failed to parse dotenv file due to an unexpected equals. Failed at [=].
35+
*/
36+
public function testParseStrayEquals()
37+
{
38+
Parser::parse('=');
39+
}
40+
41+
/**
42+
* @expectedException \Dotenv\Exception\InvalidFileException
43+
* @expectedExceptionMessage Failed to parse dotenv file due to an invalid name. Failed at [FOO_ASD!].
44+
*/
45+
public function testParseInvalidName()
46+
{
47+
Parser::parse('FOO_ASD!=BAZ');
48+
}
2249
}

tests/fixtures/env-wrong/spaced-wrong.env

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)