diff --git a/pom.xml b/pom.xml index ae21e3f..1b12f04 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.github.cdimascio dotenv-java - 3.0.1 + 3.0.2 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 38ad017..d1dc881 100644 --- a/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java +++ b/src/main/java/io/github/cdimascio/dotenv/internal/DotenvParser.java @@ -34,15 +34,16 @@ public class DotenvParser { private final boolean throwIfMissing; private final boolean throwIfMalformed; - private final Predicate isWhiteSpace = s -> matches(WHITE_SPACE_REGEX, s); - private final Predicate isComment = s -> s.startsWith("#") || s.startsWith("////"); - private final Predicate isQuoted = s -> s.startsWith("\"") && s.endsWith("\""); + 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 final Function parseLine = s -> matchEntry(DOTENV_ENTRY_REGEX, s); /** * Creates a dotenv parser - * @param reader the dotenv reader - * @param throwIfMissing if true, throws when the .env file is missing + * + * @param reader the dotenv reader + * @param throwIfMissing if true, throws when the .env file is missing * @param throwIfMalformed if true, throws when the .env file is malformed */ public DotenvParser(final DotenvReader reader, final boolean throwIfMissing, final boolean throwIfMalformed) { @@ -53,6 +54,7 @@ public DotenvParser(final DotenvReader reader, final boolean throwIfMissing, fin /** * (Internal) parse the .env file + * * @return a list of DotenvEntries * @throws DotenvException if an error is encountered during the parse */ @@ -77,8 +79,13 @@ private void addNewEntry(final List entries, final String line) { return; } + if (!QuotedStringValidator.isValid(entry.getValue())) { + if (throwIfMalformed) + throw new DotenvException("Malformed entry, unmatched quotes " + line); + return; + } final var key = entry.getKey(); - final var value = normalizeValue(entry.getValue()); + final var value = QuotedStringValidator.stripQuotes(entry.getValue()); entries.add(new DotenvEntry(key, value)); } @@ -94,11 +101,6 @@ private List lines() throws DotenvException { } } - private String normalizeValue(final String value) { - final var tr = value.trim(); - return isQuoted.test(tr) ? tr.substring(1, value.length() - 1) : tr; - } - private static boolean matches(final Pattern regex, final String text) { return regex.matcher(text).matches(); } @@ -114,4 +116,40 @@ private static DotenvEntry matchEntry(final Pattern regex, final String text) { private static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); } + + /** + * Internal: Validates quoted strings + */ + private static class QuotedStringValidator { + private static boolean isValid(String input) { + final var s = input.trim(); + if (!s.startsWith("\"") && !s.endsWith("\"")) { + // not quoted, its valid + return true; + } + if (input.length() == 1 || !(s.startsWith("\"") && s.endsWith("\""))) { + // doesn't start and end with quote + return false; + } + // remove start end quote + var content = s.substring(1, s.length() - 1); + var quotePattern = Pattern.compile("\""); + var matcher = quotePattern.matcher(content); + + // Check for unescaped quotes + while (matcher.find()) { + int quoteIndex = matcher.start(); + // Check if the quote is escaped + if (quoteIndex == 0 || content.charAt(quoteIndex - 1) != '\\') { + return false; // unescaped quote found + } + } + return true; // No unescaped quotes found + } + private static String stripQuotes(String input) { + var tr = input.trim(); + return isQuoted.test(tr) ? tr.substring(1, input.length() - 1) : tr; + } + } } + diff --git a/src/test/java/tests/DotenvTests.java b/src/test/java/tests/DotenvTests.java index b6f6314..6f89936 100644 --- a/src/test/java/tests/DotenvTests.java +++ b/src/test/java/tests/DotenvTests.java @@ -101,4 +101,16 @@ void configureWithIgnoreMissingAndMalformed() { assertNotNull(dotenv.get("PATH")); } + + @Test + void malformedWithUncloseQuote() { + final var dotenv = Dotenv.configure() + .directory("/unclosed.quote") + .ignoreIfMalformed() + .load(); + + assertNull(dotenv.get("FOO")); + assertEquals(dotenv.get("BAR"), "bar"); + assertNull(dotenv.get("BAZ"), "baz"); + } } diff --git a/src/test/resources/unclosed.quote/.env b/src/test/resources/unclosed.quote/.env new file mode 100644 index 0000000..5a1c3f6 --- /dev/null +++ b/src/test/resources/unclosed.quote/.env @@ -0,0 +1,3 @@ +FOO=" +BAR="bar" +BAZ="baz