From ff74ba1807c7a6226d3dab818472f9657b0f178e Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:25:01 +0000 Subject: [PATCH] fix: drop a backslash at the end of the input When the input ended with a backslash, the parser read the end of the stream (-1) as the escaped character and stored it as '\uffff'. So "foo=bar\" at the end of a file without a final newline loaded as "bar\uffff", and a key ending there got the same extra character. java.util.Properties drops such a backslash and loads "bar". The trailing backslash now stays in the raw text, so the file is written back unchanged, but it is left out of the key or value. --- .../org/codejive/properties/PropertiesParser.java | 7 +++++++ .../codejive/properties/TestPropertiesParser.java | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/org/codejive/properties/PropertiesParser.java b/src/main/java/org/codejive/properties/PropertiesParser.java index 8a37551..c1649c5 100644 --- a/src/main/java/org/codejive/properties/PropertiesParser.java +++ b/src/main/java/org/codejive/properties/PropertiesParser.java @@ -272,6 +272,10 @@ private void addChar(int ch) throws IOException { if (ch == '\\') { hasEscapes = true; int ch2 = readChar(); + if (isEof(ch2)) { + // A backslash at the end of the input escapes nothing + return; + } str.append((char) ch2); if (ch2 == 'u') { for (int i = 0; i < 4; i++) { @@ -315,6 +319,9 @@ public static String unescape(String escape) { for (int i = 0; i < escape.length(); i++) { char ch = escape.charAt(i); if (ch == '\\') { + if (i == escape.length() - 1) { + break; + } ch = escape.charAt(++i); switch (ch) { case 't': diff --git a/src/test/java/org/codejive/properties/TestPropertiesParser.java b/src/test/java/org/codejive/properties/TestPropertiesParser.java index f967642..333ae40 100644 --- a/src/test/java/org/codejive/properties/TestPropertiesParser.java +++ b/src/test/java/org/codejive/properties/TestPropertiesParser.java @@ -131,4 +131,16 @@ void testMissingSeparatorAndValueAtEof() throws IOException { new Token(Type.SEPARATOR, ""), new Token(Type.VALUE, "")); } + + @Test + void testBackslashAtEof() throws IOException { + List tokens = + PropertiesParser.tokens(new StringReader("foo=bar\\")).collect(Collectors.toList()); + + assertThat(tokens) + .containsExactly( + new Token(Type.KEY, "foo"), + new Token(Type.SEPARATOR, "="), + new Token(Type.VALUE, "bar\\", "bar")); + } }