diff --git a/src/Parser/Lines.php b/src/Parser/Lines.php index de290c5a..0950c4a3 100644 --- a/src/Parser/Lines.php +++ b/src/Parser/Lines.php @@ -43,6 +43,14 @@ public static function process(array $lines) } } + // If the input ends while still inside a multiline value, the value + // was never closed. Emit the buffered content as a final entry + // instead of silently discarding it, so the parser reports the same + // missing-closing-quote error the single-quoted case already does. + if ($multiline && $multilineBuffer !== []) { + $output[] = \implode("\n", $multilineBuffer); + } + return $output; } diff --git a/tests/Dotenv/Parser/LinesTest.php b/tests/Dotenv/Parser/LinesTest.php index 261029a6..a81eaaab 100644 --- a/tests/Dotenv/Parser/LinesTest.php +++ b/tests/Dotenv/Parser/LinesTest.php @@ -50,4 +50,20 @@ public function testProcessQuotes() self::assertSame($expected, Lines::process($result->success()->get())); } + + public function testProcessUnterminatedMultilineIsEmittedNotDropped() + { + $result = Regex::split("/(\r\n|\n|\r)/", "FOO=\"bar"); + self::assertTrue($result->success()->isDefined()); + + self::assertSame(['FOO="bar'], Lines::process($result->success()->get())); + } + + public function testProcessUnterminatedMultilineDoesNotSwallowFollowingLines() + { + $result = Regex::split("/(\r\n|\n|\r)/", "A=\"oops\nB=keep"); + self::assertTrue($result->success()->isDefined()); + + self::assertSame(["A=\"oops\nB=keep"], Lines::process($result->success()->get())); + } }