From f9de7dfaa52fb034825cab0f4603c72220b54659 Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:33:29 +0800 Subject: [PATCH 1/6] fix: preserve compressed input and indexed conversion --- hitool-core/src/main/java/hitool/core/compress/GZipUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hitool-core/src/main/java/hitool/core/compress/GZipUtils.java b/hitool-core/src/main/java/hitool/core/compress/GZipUtils.java index 391b0bb..0542b8c 100644 --- a/hitool-core/src/main/java/hitool/core/compress/GZipUtils.java +++ b/hitool-core/src/main/java/hitool/core/compress/GZipUtils.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; @@ -194,6 +195,9 @@ public static void decompress(File srcFile, boolean delete) throws IOException { public static void decompress(File srcFile, File destDir) throws IOException { File destFile = new File(destDir, FilenameUtils.getBaseName(srcFile.getName())); + if (Files.exists(destFile.toPath()) && Files.isSameFile(srcFile.toPath(), destFile.toPath())) { + throw new IOException("Decompression output must differ from compressed input: " + srcFile); + } try (InputStream input = new FileInputStream(srcFile); OutputStream output = new FileOutputStream(destFile);){ GZipUtils.decompress(input, output); From b8e7eda72bd11bda63a56d2ff96dfe065a34a357 Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:33:33 +0800 Subject: [PATCH 2/6] fix: preserve compressed input and indexed conversion --- .../src/main/java/hitool/core/compress/BZip2Utils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hitool-core/src/main/java/hitool/core/compress/BZip2Utils.java b/hitool-core/src/main/java/hitool/core/compress/BZip2Utils.java index 5534a64..2a3ae46 100644 --- a/hitool-core/src/main/java/hitool/core/compress/BZip2Utils.java +++ b/hitool-core/src/main/java/hitool/core/compress/BZip2Utils.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; @@ -176,6 +177,9 @@ public static void decompress(File srcFile, boolean delete) throws IOException { public static void decompress(File srcFile, File destDir) throws IOException { File destFile = new File(destDir, FilenameUtils.getBaseName(srcFile.getName())); + if (Files.exists(destFile.toPath()) && Files.isSameFile(srcFile.toPath(), destFile.toPath())) { + throw new IOException("Decompression output must differ from compressed input: " + srcFile); + } try(InputStream input = new FileInputStream(srcFile); OutputStream output = new FileOutputStream(destFile);) { BZip2Utils.decompress(input, output); From c1cfbe751f81da89c3c668f25a30966977f2ee2b Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:33:37 +0800 Subject: [PATCH 3/6] fix: preserve compressed input and indexed conversion --- .../hitool/core/beanutils/BeanPropertyUtils.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/hitool-core/src/main/java/hitool/core/beanutils/BeanPropertyUtils.java b/hitool-core/src/main/java/hitool/core/beanutils/BeanPropertyUtils.java index e407c0f..171e78f 100644 --- a/hitool-core/src/main/java/hitool/core/beanutils/BeanPropertyUtils.java +++ b/hitool-core/src/main/java/hitool/core/beanutils/BeanPropertyUtils.java @@ -248,12 +248,14 @@ public static void setProperty(Object bean, String name, Object value, Class fieldType = getPropertyUtils().getPropertyType(propertyVlaue, name3); if (fieldType != null && fieldType.isArray()) { int length = current == null ? 0 : Array.getLength(current); - Object array = Array.newInstance(fieldType.getComponentType(), Math.max(length, index + 1)); - if (length > 0) { - System.arraycopy(current, 0, array, 0, length); + if (length <= index) { + Object array = Array.newInstance(fieldType.getComponentType(), index + 1); + if (length > 0) { + System.arraycopy(current, 0, array, 0, length); + } + getPropertyUtils().setProperty(propertyVlaue, name3, array); } - Array.set(array, index, value); - getPropertyUtils().setProperty(propertyVlaue, name3, array); + BeanUtils.setProperty(propertyVlaue, name2, value); } else if (current instanceof List) { List list = (List) current; while (list.size() <= index) { From be16ea42fc401efbf30ba5e940ca44d27d877941 Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:33:40 +0800 Subject: [PATCH 4/6] test: cover same-file decompression and array conversion --- .../CompressionSelfOverwriteTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 hitool-core/src/test/java/hitool/core/compress/CompressionSelfOverwriteTest.java diff --git a/hitool-core/src/test/java/hitool/core/compress/CompressionSelfOverwriteTest.java b/hitool-core/src/test/java/hitool/core/compress/CompressionSelfOverwriteTest.java new file mode 100644 index 0000000..cb42c54 --- /dev/null +++ b/hitool-core/src/test/java/hitool/core/compress/CompressionSelfOverwriteTest.java @@ -0,0 +1,79 @@ +package hitool.core.compress; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +public class CompressionSelfOverwriteTest { + @Test + public void gzipRejectsExtensionlessInputAndSameFileAlias() throws Exception { + byte[] compressed = GZipUtils.compress("payload".getBytes(StandardCharsets.UTF_8)); + Path dir = Files.createTempDirectory("hitool-gzip-safety-"); + try { + Path source = dir.resolve("payload"); + Files.write(source, compressed); + expectIOException(() -> GZipUtils.decompress(source.toFile(), false)); + assertArrayEquals(compressed, Files.readAllBytes(source)); + expectIOException(() -> GZipUtils.decompress(source.toFile(), dir.toFile())); + assertArrayEquals(compressed, Files.readAllBytes(source)); + + Path named = dir.resolve("named.gz"); + Files.write(named, compressed); + Path alias = dir.resolve("named"); + Files.createSymbolicLink(alias, named.getFileName()); + expectIOException(() -> GZipUtils.decompress(named.toFile(), dir.toFile())); + assertArrayEquals(compressed, Files.readAllBytes(named)); + } finally { + Files.deleteIfExists(dir.resolve("named")); + Files.deleteIfExists(dir.resolve("named.gz")); + Files.deleteIfExists(dir.resolve("payload")); + Files.deleteIfExists(dir); + } + } + + @Test + public void bzip2RejectsExtensionlessInputAndSameFileAlias() throws Exception { + byte[] compressed = BZip2Utils.compress("payload".getBytes(StandardCharsets.UTF_8)); + Path dir = Files.createTempDirectory("hitool-bzip2-safety-"); + try { + Path source = dir.resolve("payload"); + Files.write(source, compressed); + expectIOException(() -> BZip2Utils.decompress(source.toFile(), false)); + assertArrayEquals(compressed, Files.readAllBytes(source)); + expectIOException(() -> BZip2Utils.decompress(source.toFile(), dir.toFile())); + assertArrayEquals(compressed, Files.readAllBytes(source)); + + Path named = dir.resolve("named.bz2"); + Files.write(named, compressed); + Path alias = dir.resolve("named"); + Files.createSymbolicLink(alias, named.getFileName()); + expectIOException(() -> BZip2Utils.decompress(named.toFile(), dir.toFile())); + assertArrayEquals(compressed, Files.readAllBytes(named)); + } finally { + Files.deleteIfExists(dir.resolve("named")); + Files.deleteIfExists(dir.resolve("named.bz2")); + Files.deleteIfExists(dir.resolve("payload")); + Files.deleteIfExists(dir); + } + } + + private void expectIOException(ThrowingAction action) throws Exception { + try { + action.run(); + fail("Expected IOException before opening the output file"); + } catch (IOException expected) { + // The compressed source must remain intact. + } + } + + private interface ThrowingAction { + void run() throws Exception; + } +} From dc636d6d5d0e8f8bc0820150537564bca423d1e6 Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:33:44 +0800 Subject: [PATCH 5/6] test: cover same-file decompression and array conversion --- .../BeanPropertyIndexedConversionTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java diff --git a/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java b/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java new file mode 100644 index 0000000..cdc13a0 --- /dev/null +++ b/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java @@ -0,0 +1,65 @@ +package hitool.core.beanutils; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +public class BeanPropertyIndexedConversionTest { + @Test + public void convertsFormValuesForExistingAndExpandedNumericArrays() { + Root root = new Root(); + root.setChild(new Child()); + root.getChild().setNumbers(new int[] {1, 4}); + + BeanPropertyUtils.setProperty(root, "child.numbers[0]", "2", true); + assertArrayEquals(new int[] {2, 4}, root.getChild().getNumbers()); + + BeanPropertyUtils.setProperty(root, "child.numbers[3]", new String[] {"7"}, true); + assertArrayEquals(new int[] {2, 4, 0, 7}, root.getChild().getNumbers()); + } + + @Test + public void initializesNullArraysAndPreservesStringNulls() { + Root root = new Root(); + root.setChild(new Child()); + BeanPropertyUtils.setProperty(root, "child.numbers[0]", "3", true); + assertArrayEquals(new int[] {3}, root.getChild().getNumbers()); + + BeanPropertyUtils.setProperty(root, "child.labels[1]", new String[] {"label"}, true); + assertEquals("label", root.getChild().getLabels()[1]); + assertNull(root.getChild().getLabels()[0]); + BeanPropertyUtils.setProperty(root, "child.labels[0]", null, true); + assertNull(root.getChild().getLabels()[0]); + } + + @Test + public void invalidIndexDoesNotChangeExistingElements() { + Root root = new Root(); + root.setChild(new Child()); + root.getChild().setNumbers(new int[] {5}); + try { + BeanPropertyUtils.setProperty(root, "child.numbers[invalid]", "2", true); + fail("Expected invalid indexed property to fail"); + } catch (RuntimeException expected) { + assertArrayEquals(new int[] {5}, root.getChild().getNumbers()); + } + } + + public static class Root { + private Child child; + public Child getChild() { return child; } + public void setChild(Child child) { this.child = child; } + } + + public static class Child { + private int[] numbers; + private String[] labels; + public int[] getNumbers() { return numbers; } + public void setNumbers(int[] numbers) { this.numbers = numbers; } + public String[] getLabels() { return labels; } + public void setLabels(String[] labels) { this.labels = labels; } + } +} From 9c5343b0422fa4ebd3025de2791e62a9bd1cdf00 Mon Sep 17 00:00:00 2001 From: Loong Wan Date: Sat, 31 Oct 2026 22:35:04 +0800 Subject: [PATCH 6/6] test: preserve BeanUtils invalid and null numeric conversion --- .../beanutils/BeanPropertyIndexedConversionTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java b/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java index cdc13a0..25b9b54 100644 --- a/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java +++ b/hitool-core/src/test/java/hitool/core/beanutils/BeanPropertyIndexedConversionTest.java @@ -35,6 +35,18 @@ public void initializesNullArraysAndPreservesStringNulls() { assertNull(root.getChild().getLabels()[0]); } + @Test + public void retainsBeanUtilsConversionForMalformedAndNullNumericValues() { + Root root = new Root(); + root.setChild(new Child()); + root.getChild().setNumbers(new int[] {5, 9}); + // BeanUtils 1.11.0 converts malformed or null primitive values to zero. + BeanPropertyUtils.setProperty(root, "child.numbers[0]", "not-a-number", true); + assertArrayEquals(new int[] {0, 9}, root.getChild().getNumbers()); + BeanPropertyUtils.setProperty(root, "child.numbers[1]", null, true); + assertArrayEquals(new int[] {0, 0}, root.getChild().getNumbers()); + } + @Test public void invalidIndexDoesNotChangeExistingElements() { Root root = new Root();