From a41831a93b95c4fb20b110d842b097ba1947c7e8 Mon Sep 17 00:00:00 2001 From: Shubham Mohole <95623059+Ishubhammohole@users.noreply.github.com> Date: Sat, 19 Sep 2026 12:52:22 -0400 Subject: [PATCH] fix: strip matching single quotes from dotenv values --- .../dotenv/internal/DotenvParser.java | 5 +- src/test/java/tests/QuotedValuesTests.java | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/test/java/tests/QuotedValuesTests.java diff --git a/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java b/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java index c244a7b..d94515d 100644 --- a/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java +++ b/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java @@ -36,7 +36,9 @@ public class DotenvParser { private static final Predicate isWhiteSpace = s -> matches(WHITE_SPACE_REGEX, s); private static final Predicate isComment = s -> s.startsWith("#") || s.startsWith("////"); - private static final Predicate isQuoted = s -> s.length() > 1 && s.startsWith("\"") && s.endsWith("\""); + private static final Predicate isQuoted = s -> s.length() > 1 + && ((s.startsWith("\"") && s.endsWith("\"")) + || (s.startsWith("'") && s.endsWith("'"))); private final Function parseLine = s -> matchEntry(DOTENV_ENTRY_REGEX, s); /** @@ -175,4 +177,3 @@ private static String stripQuotes(String input) { } } } - diff --git a/src/test/java/tests/QuotedValuesTests.java b/src/test/java/tests/QuotedValuesTests.java new file mode 100644 index 0000000..c1b8c84 --- /dev/null +++ b/src/test/java/tests/QuotedValuesTests.java @@ -0,0 +1,52 @@ +package tests; + +import io.github.cdimascio.dotenv.Dotenv; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class QuotedValuesTests { + @TempDir + Path directory; + + private Dotenv load(String contents) throws IOException { + Files.writeString(directory.resolve("test.env"), contents); + return Dotenv.configure().directory(directory.toString()).filename("test.env").load(); + } + + @Test + void removesMatchingQuotes() throws IOException { + final var dotenv = load("PLAIN=value\nSINGLE='value'\nDOUBLE=\"value\"\nEMPTY=''\n"); + + assertEquals("value", dotenv.get("PLAIN")); + assertEquals("value", dotenv.get("SINGLE")); + assertEquals("value", dotenv.get("DOUBLE")); + assertEquals("", dotenv.get("EMPTY")); + } + + @Test + void preservesSingleQuotedContents() throws IOException { + final var dotenv = load("SPACES= ' café ' \n" + + "HASH='value#part' # trailing comment\n" + + "DOUBLE_INSIDE='say \"hello\"'\n" + + "BACKSLASH='literal\\nvalue'\n"); + + assertEquals(" café ", dotenv.get("SPACES")); + assertEquals("value#part", dotenv.get("HASH")); + assertEquals("say \"hello\"", dotenv.get("DOUBLE_INSIDE")); + assertEquals("literal\\nvalue", dotenv.get("BACKSLASH")); + } + + @Test + void preservesUnquotedApostrophes() throws IOException { + final var dotenv = load("APOSTROPHE=it's unchanged\nTRAILING=parents'\n"); + + assertEquals("it's unchanged", dotenv.get("APOSTROPHE")); + assertEquals("parents'", dotenv.get("TRAILING")); + } +}