This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git

commit 2dcd1ed28b976eb4d1ddbb1775255196696a5fd2
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Mon Mar 31 14:13:36 2025 -0400

    FileTimes.toNtfsTime(*) methods can overflow result values
---
 src/changes/changes.xml                            |  1 +
 .../commons/io/file/attribute/FileTimes.java       | 20 ++++++++--
 .../commons/io/file/attribute/FileTimesTest.java   | 45 ++++++++++++++++++++--
 3 files changed, 58 insertions(+), 8 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c26a92878..c592022cc 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -69,6 +69,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="fix" issue="IO-871" due-to="Éamonn McManus, 
Gary Gregory">IOUtils.contentEquals is incorrect when InputStream.available 
under-reports.</action>
       <action dev="ggregory" type="fix" issue="IO-873" due-to="Gary 
Gregory">java.lang.ArithmeticException: long overflow 
java.lang.Math.addExact(Math.java:932) at 
org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToFileTime(FileTimes.java:164).
 See also https://issues.apache.org/jira/browse/MDEP-978.</action>
       <action dev="ggregory" type="fix" issue="IO-873" due-to="Gary 
Gregory">java.lang.ArithmeticException: long overflow 
java.lang.Math.addExact(Math.java:932) at 
org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToDate(long).</action>
+      <action dev="ggregory" type="fix"                due-to="Gary 
Gregory">FileTimes.toNtfsTime(*) methods can overflow result values.</action>
       <!-- ADD -->
       <action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, 
Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, 
ChronoUnit).</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add IOIterable.</action>
diff --git a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java 
b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
index 3d54b5116..d811c54e4 100644
--- a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
+++ b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
@@ -40,6 +40,10 @@
  */
 public final class FileTimes {
 
+    private static final BigDecimal LONG_MIN_VALUE_BD = 
BigDecimal.valueOf(Long.MIN_VALUE);
+
+    private static final BigDecimal LONG_MAX_VALUE_BD = 
BigDecimal.valueOf(Long.MAX_VALUE);
+
     private static final MathContext MATH_CONTEXT = new MathContext(0, 
RoundingMode.FLOOR);
 
     /**
@@ -74,6 +78,8 @@ public final class FileTimes {
      */
     static final long HUNDRED_NANOS_PER_MILLISECOND = 
TimeUnit.MILLISECONDS.toNanos(1) / 100;
 
+    static final BigDecimal HUNDRED_NANOS_PER_MILLISECOND_BD = 
BigDecimal.valueOf(HUNDRED_NANOS_PER_MILLISECOND);
+
     private static final long HUNDRED = 100L;
 
     private static final BigDecimal HUNDRED_BD = BigDecimal.valueOf(HUNDRED);
@@ -274,8 +280,7 @@ public static FileTime toFileTime(final Date date) {
      * @return the NTFS time, 100-nanosecond units since 1 January 1601.
      */
     public static long toNtfsTime(final Date date) {
-        final long javaHundredNanos = date.getTime() * 
HUNDRED_NANOS_PER_MILLISECOND;
-        return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
+        return toNtfsTime(date.getTime());
     }
 
     /**
@@ -308,8 +313,15 @@ static long toNtfsTime(final Instant instant) {
      * @since 2.16.0
      */
     public static long toNtfsTime(final long javaTime) {
-        final long javaHundredNanos = javaTime * HUNDRED_NANOS_PER_MILLISECOND;
-        return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
+        final BigDecimal javaHundredNanos = 
BigDecimal.valueOf(javaTime).multiply(HUNDRED_NANOS_PER_MILLISECOND_BD);
+        final BigDecimal ntfsTime = 
javaHundredNanos.subtract(UNIX_TO_NTFS_OFFSET_BD);
+        if (ntfsTime.compareTo(LONG_MAX_VALUE_BD) >= 0) {
+            return Long.MAX_VALUE;
+        }
+        if (ntfsTime.compareTo(LONG_MIN_VALUE_BD) <= 0) {
+            return Long.MIN_VALUE;
+        }
+        return ntfsTime.longValue();
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java 
b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
index 2c074d5a3..2673c062f 100644
--- a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
+++ b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
@@ -102,11 +102,19 @@ public void testDateToFileTime(final String instant, 
final long ignored) {
 
     @ParameterizedTest
     @MethodSource("fileTimeNanoUnitsToNtfsProvider")
-    public void testDateToNtfsTime(final String instant, final long ntfsTime) {
+    public void testDateToNtfsTime(final String instantStr, final long 
ntfsTime) {
         final long ntfsMillis = Math.floorDiv(ntfsTime, 
FileTimes.HUNDRED_NANOS_PER_MILLISECOND) * 
FileTimes.HUNDRED_NANOS_PER_MILLISECOND;
-        final Date parsed = Date.from(Instant.parse(instant));
-        assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed));
-        assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime()));
+        final Instant instant = Instant.parse(instantStr);
+        final Date parsed = Date.from(instant);
+        final long ntfsTime2 = FileTimes.toNtfsTime(parsed);
+        if (ntfsTime2 == Long.MIN_VALUE || ntfsTime2 == Long.MAX_VALUE) {
+            // toNtfsTime returns max long instead of overflowing
+        } else {
+            assertEquals(ntfsMillis, ntfsTime2);
+            assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime()));
+            assertEquals(ntfsMillis, 
FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime).toEpochMilli()));
+        }
+        assertEquals(ntfsTime, 
FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime)));
     }
 
     @Test
@@ -163,6 +171,35 @@ public void testIsUnixTimeLong(final String instant, final 
boolean isUnixTime) {
         assertEquals(isUnixTime, 
FileTimes.isUnixTime(Instant.parse(instant).getEpochSecond()));
     }
 
+    @Test
+    public void testMaxJavaTime() {
+        final long javaTime = Long.MAX_VALUE;
+        final Instant instant = Instant.ofEpochMilli(javaTime);
+        assertEquals(javaTime, instant.toEpochMilli()); // sanity check
+        final long ntfsTime = FileTimes.toNtfsTime(javaTime);
+        final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime);
+        if (ntfsTime == Long.MAX_VALUE) {
+            // toNtfsTime returns max long instead of overflowing
+        } else {
+            assertEquals(javaTime, instant2.toEpochMilli());
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("fileTimeNanoUnitsToNtfsProvider")
+    public void testMaxJavaTimeParam(final String instantStr, final long 
javaTime) {
+        // final long javaTime = Long.MAX_VALUE;
+        final Instant instant = Instant.ofEpochMilli(javaTime);
+        assertEquals(javaTime, instant.toEpochMilli()); // sanity check
+        final long ntfsTime = FileTimes.toNtfsTime(javaTime);
+        final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime);
+        if (ntfsTime == Long.MIN_VALUE || ntfsTime == Long.MAX_VALUE) {
+            // toNtfsTime returns min or max long instead of overflowing
+        } else {
+            assertEquals(javaTime, instant2.toEpochMilli());
+        }
+    }
+
     @Test
     public void testMinusMillis() {
         final int millis = 2;

Reply via email to