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