From d9fc4c8fdfe937f991f92b63d6259778418ccfda Mon Sep 17 00:00:00 2001 From: farkhalit rida Date: Thu, 23 Jul 2026 13:48:55 +0530 Subject: [PATCH 1/2] reject trailing text after the date in Converter.DATE --- .../java/org/apache/commons/cli/Converter.java | 18 ++++++++++++++---- .../org/apache/commons/cli/ConverterTests.java | 7 +++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/commons/cli/Converter.java b/src/main/java/org/apache/commons/cli/Converter.java index fcb9c43b5..8b8690d77 100644 --- a/src/main/java/org/apache/commons/cli/Converter.java +++ b/src/main/java/org/apache/commons/cli/Converter.java @@ -22,6 +22,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; @@ -82,15 +83,24 @@ public interface Converter { final SimpleDateFormat format = new SimpleDateFormat(pattern); // reject out-of-range fields (for example "Feb 30") instead of silently rolling them over. format.setLenient(false); - try { - return format.parse(s); - } catch (final java.text.ParseException e) { + // SimpleDateFormat.parse(String) stops at the first character it cannot use and ignores any + // trailing text, so " garbage" would be accepted. Parse from an explicit position + // and reject the value unless the whole string is consumed. + final ParsePosition pos = new ParsePosition(0); + Date date = format.parse(s, pos); + if (date == null || pos.getIndex() != s.length()) { // Date.toString() always emits English month/day names, so fall back to Locale.ENGLISH // when the default locale rejects the documented format. final SimpleDateFormat englishFormat = new SimpleDateFormat(pattern, Locale.ENGLISH); englishFormat.setLenient(false); - return englishFormat.parse(s); + final ParsePosition englishPos = new ParsePosition(0); + date = englishFormat.parse(s, englishPos); + if (date == null || englishPos.getIndex() != s.length()) { + final int errorIndex = englishPos.getErrorIndex() >= 0 ? englishPos.getErrorIndex() : englishPos.getIndex(); + throw new java.text.ParseException(String.format("Unparseable date: \"%s\"", s), errorIndex); + } } + return date; }; /** diff --git a/src/test/java/org/apache/commons/cli/ConverterTests.java b/src/test/java/org/apache/commons/cli/ConverterTests.java index 1f5efad55..f9c6e9e76 100644 --- a/src/test/java/org/apache/commons/cli/ConverterTests.java +++ b/src/test/java/org/apache/commons/cli/ConverterTests.java @@ -86,6 +86,13 @@ void testDate() throws Exception { assertThrows(java.text.ParseException.class, () -> Converter.DATE.apply("Jun 06 17:48:57 EDT 2002")); } + @Test + void testDateRejectsTrailingText() throws Exception { + final Date expected = new Date(1023400137000L); + final String formatted = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").format(expected); + assertThrows(java.text.ParseException.class, () -> Converter.DATE.apply(formatted + " trailing")); + } + @Test @DefaultLocale(language = "de", country = "DE") void testDateLocaleDe() throws Exception { From 73b594d6184a2927c94e33b43503d17835c23ccd Mon Sep 17 00:00:00 2001 From: farkhalit rida Date: Wed, 29 Jul 2026 11:12:36 +0530 Subject: [PATCH 2/2] Only fall back to English locale when the date fails to parse Restrict the English retry to when the default-locale parse matches nothing; a partial match is a trailing-text failure and now throws with the parse position instead of retrying and reporting a misleading offset. --- .../java/org/apache/commons/cli/Converter.java | 18 ++++++++++-------- .../org/apache/commons/cli/ConverterTests.java | 11 +++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/commons/cli/Converter.java b/src/main/java/org/apache/commons/cli/Converter.java index 8b8690d77..1faf78174 100644 --- a/src/main/java/org/apache/commons/cli/Converter.java +++ b/src/main/java/org/apache/commons/cli/Converter.java @@ -88,17 +88,19 @@ public interface Converter { // and reject the value unless the whole string is consumed. final ParsePosition pos = new ParsePosition(0); Date date = format.parse(s, pos); - if (date == null || pos.getIndex() != s.length()) { + if (date == null) { // Date.toString() always emits English month/day names, so fall back to Locale.ENGLISH - // when the default locale rejects the documented format. + // when the default locale rejects the documented format. Only retry when the default + // locale matched nothing; a partial match is a trailing-text failure, handled below. final SimpleDateFormat englishFormat = new SimpleDateFormat(pattern, Locale.ENGLISH); englishFormat.setLenient(false); - final ParsePosition englishPos = new ParsePosition(0); - date = englishFormat.parse(s, englishPos); - if (date == null || englishPos.getIndex() != s.length()) { - final int errorIndex = englishPos.getErrorIndex() >= 0 ? englishPos.getErrorIndex() : englishPos.getIndex(); - throw new java.text.ParseException(String.format("Unparseable date: \"%s\"", s), errorIndex); - } + pos.setIndex(0); + pos.setErrorIndex(-1); + date = englishFormat.parse(s, pos); + } + if (date == null || pos.getIndex() != s.length()) { + final int errorIndex = pos.getErrorIndex() >= 0 ? pos.getErrorIndex() : pos.getIndex(); + throw new java.text.ParseException(String.format("Unparseable date: \"%s\"", s), errorIndex); } return date; }; diff --git a/src/test/java/org/apache/commons/cli/ConverterTests.java b/src/test/java/org/apache/commons/cli/ConverterTests.java index f9c6e9e76..64052c41d 100644 --- a/src/test/java/org/apache/commons/cli/ConverterTests.java +++ b/src/test/java/org/apache/commons/cli/ConverterTests.java @@ -93,6 +93,17 @@ void testDateRejectsTrailingText() throws Exception { assertThrows(java.text.ParseException.class, () -> Converter.DATE.apply(formatted + " trailing")); } + @Test + @DefaultLocale(language = "de", country = "DE") + void testDateRejectsTrailingTextLocaleDe() throws Exception { + // Trailing text must be rejected even when a non-English default locale parses the date, and + // the reported error position should point past the parsed date rather than at index 0. + final Date expected = new Date(1023400137000L); + final String formatted = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").format(expected); + final java.text.ParseException e = assertThrows(java.text.ParseException.class, () -> Converter.DATE.apply(formatted + " trailing")); + assertEquals(formatted.length(), e.getErrorOffset()); + } + @Test @DefaultLocale(language = "de", country = "DE") void testDateLocaleDe() throws Exception {