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

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

commit 78f8a60d36aadf9489f0f714b90b4682002db5bf
Author: Gary Gregory <[email protected]>
AuthorDate: Mon Aug 10 16:12:51 2026 -0400

    TarArchiveEntry.addPaxHeader(String, String) now throws ArchiveException
    instead of IllegalArgumentException.
---
 src/changes/changes.xml                            |   1 +
 .../compress/archivers/tar/TarArchiveEntry.java    | 148 ++++++++++-----------
 .../commons/compress/archivers/tar/TarUtils.java   |  32 +++--
 .../archivers/tar/TarArchiveEntryTest.java         |   7 +-
 4 files changed, 97 insertions(+), 91 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d67fea52b..9c32acb97 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -170,6 +170,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="fix" dev="ggregory" due-to="Gary 
Gregory">TarArchiveEntry.setSize(long) now throw ArchiveException instead of 
IllegalArgumentException.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">ParsingUtils now 
throws the IOException subclass CompressException.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Most 
ArArchiveEntry constructors now throws the IOException subclass 
ArchiveException.</action>
+      <action type="fix" dev="ggregory" due-to="Gary 
Gregory">TarArchiveEntry.addPaxHeader(String, String) now throws 
ArchiveException instead of IllegalArgumentException.</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
MemoryLimitException.MemoryLimitException(long, long).</action>
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
CompressException.CompressException(String, Object...).</action>
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index 25eb5658a..ecbb79ee0 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -694,14 +694,11 @@ public TarArchiveEntry(final String name, final byte 
linkFlag, final boolean pre
      *
      * @param name  The full name of the header to set.
      * @param value value of header.
+     * @throws ArchiveException Thrown when parsing numbers and validating 
input.
      * @since 1.15
      */
-    public void addPaxHeader(final String name, final String value) {
-        try {
-            processPaxHeader(name, value);
-        } catch (final IOException ex) {
-            throw new IllegalArgumentException("Invalid input", ex);
-        }
+    public void addPaxHeader(final String name, final String value) throws 
ArchiveException {
+        processPaxHeader(name, value);
     }
 
     /**
@@ -765,7 +762,7 @@ private int fill(final int value, final int offset, final 
byte[] outbuf, final i
         return fill((byte) value, offset, outbuf, length);
     }
 
-    void fillGNUSparse0xData(final Map<String, String> headers) throws 
CompressException {
+    private void fillGNUSparse0xData(final Map<String, String> headers) throws 
CompressException {
         paxGNUSparse = true;
         realSize = 
ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.SIZE));
         if (headers.containsKey(TarGnuSparseKeys.NAME)) {
@@ -774,7 +771,7 @@ void fillGNUSparse0xData(final Map<String, String> headers) 
throws CompressExcep
         }
     }
 
-    void fillGNUSparse1xData(final Map<String, String> headers) throws 
CompressException {
+    private void fillGNUSparse1xData(final Map<String, String> headers) throws 
CompressException {
         paxGNUSparse = true;
         paxGNU1XSparse = true;
         if (headers.containsKey(TarGnuSparseKeys.NAME)) {
@@ -1618,9 +1615,10 @@ private void parseUstarHeaderBlock(final Map<String, 
String> globalPaxHeaders, f
      *
      * @param key     The header name.
      * @param val     The header value.
+     * @throws ArchiveException Thrown when parsing numbers and validating 
input.
      * @since 1.15
      */
-    private void processPaxHeader(final String key, final String val) throws 
IOException {
+    private void processPaxHeader(final String key, final String val) throws 
ArchiveException {
         processPaxHeader(key, val, extraPaxHeaders);
     }
 
@@ -1630,73 +1628,74 @@ private void processPaxHeader(final String key, final 
String val) throws IOExcep
      * @param key     The header name.
      * @param val     The header value.
      * @param headers map of headers used for dealing with sparse file.
-     * @throws IOException if encountered errors when parsing the numbers.
+     * @throws ArchiveException Thrown when parsing numbers and validating 
input.
      * @since 1.15
      */
-    private void processPaxHeader(final String key, final String val, final 
Map<String, String> headers) throws CompressException {
-        /*
-         * The following headers are defined for PAX. charset: cannot use 
these without changing TarArchiveEntry fields mtime atime ctime
-         * LIBARCHIVE.creationtime comment gid, gname linkpath size uid,uname 
SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
-         *
-         * GNU sparse files use additional members, we use GNU.sparse.size to 
detect the 0.0 and 0.1 versions and GNU.sparse.realsize for 1.0.
-         *
-         * star files use additional members of which we use SCHILY.filetype 
in order to detect star sparse files.
-         *
-         * If called from addExtraPaxHeader, these additional headers must be 
already present.
-         */
-        switch (key) {
-        case PAX_NAME_KEY:
-            setName(val);
-            break;
-        case PAX_LINK_NAME_KEY:
-            setLinkName(val);
-            break;
-        case "gid":
-            setGroupId(ParsingUtils.parseLongValue(val));
-            break;
-        case "gname":
-            setGroupName(val);
-            break;
-        case "uid":
-            setUserId(ParsingUtils.parseLongValue(val));
-            break;
-        case "uname":
-            setUserName(val);
-            break;
-        case "size":
-            setSize(ParsingUtils.parseLongValue(val));
-            break;
-        case "mtime":
-            
setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
-            break;
-        case "atime":
-            
setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
-            break;
-        case "ctime":
-            
setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
-            break;
-        case "LIBARCHIVE.creationtime":
-            
setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
-            break;
-        case "SCHILY.devminor":
-            setDevMinor(ParsingUtils.parseIntValue(val));
-            break;
-        case "SCHILY.devmajor":
-            setDevMajor(ParsingUtils.parseIntValue(val));
-            break;
-        case TarGnuSparseKeys.SIZE:
-            fillGNUSparse0xData(headers);
-            break;
-        case TarGnuSparseKeys.REALSIZE:
-            fillGNUSparse1xData(headers);
-            break;
-        case "SCHILY.filetype":
-            if ("sparse".equals(val)) {
-                fillStarSparseData(headers);
+    private void processPaxHeader(final String key, final String val, final 
Map<String, String> headers) throws ArchiveException {
+        //
+        // The following headers are defined for PAX. charset: cannot use 
these without changing TarArchiveEntry fields mtime atime ctime
+        // LIBARCHIVE.creationtime comment gid, gname linkpath size uid,uname 
SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those GNU 
sparse
+        // files use additional members, we use GNU.sparse.size to detect the 
0.0 and 0.1 versions and GNU.sparse.realsize for 1.0. star files use additional
+        // members of which we use SCHILY.filetype in order to detect star 
sparse files. If called from addExtraPaxHeader, these additional headers must be
+        // already present.
+        //
+        try {
+            switch (key) {
+            case PAX_NAME_KEY:
+                setName(val);
+                break;
+            case PAX_LINK_NAME_KEY:
+                setLinkName(val);
+                break;
+            case "gid":
+                setGroupId(ParsingUtils.parseLongValue(val));
+                break;
+            case "gname":
+                setGroupName(val);
+                break;
+            case "uid":
+                setUserId(ParsingUtils.parseLongValue(val));
+                break;
+            case "uname":
+                setUserName(val);
+                break;
+            case "size":
+                setSize(ParsingUtils.parseLongValue(val));
+                break;
+            case "mtime":
+                
setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
+                break;
+            case "atime":
+                
setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
+                break;
+            case "ctime":
+                
setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
+                break;
+            case "LIBARCHIVE.creationtime":
+                
setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
+                break;
+            case "SCHILY.devminor":
+                setDevMinor(ParsingUtils.parseIntValue(val));
+                break;
+            case "SCHILY.devmajor":
+                setDevMajor(ParsingUtils.parseIntValue(val));
+                break;
+            case TarGnuSparseKeys.SIZE:
+                fillGNUSparse0xData(headers);
+                break;
+            case TarGnuSparseKeys.REALSIZE:
+                fillGNUSparse1xData(headers);
+                break;
+            case "SCHILY.filetype":
+                if ("sparse".equals(val)) {
+                    fillStarSparseData(headers);
+                }
+                break;
+            default:
+                extraPaxHeaders.put(key, val);
             }
-            break;
-        default:
-            extraPaxHeaders.put(key, val);
+        } catch (final CompressException e) {
+            throw new ArchiveException(String.format("%s for key '%s'", 
e.getClass().getSimpleName(), key), (Throwable) e);
         }
     }
 
@@ -1996,8 +1995,9 @@ public String toString() {
      * Update the entry using a map of PAX headers.
      *
      * @param headers PAX headers.
+     * @throws ArchiveException Thrown when parsing numbers and validating 
input.
      */
-    void updateEntryFromPaxHeaders(final Map<String, String> headers) throws 
IOException {
+    void updateEntryFromPaxHeaders(final Map<String, String> headers) throws 
ArchiveException {
         for (final Map.Entry<String, String> ent : headers.entrySet()) {
             processPaxHeader(ent.getKey(), ent.getValue(), headers);
         }
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index d7aa17d4e..b98cefb5d 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -34,6 +34,7 @@
 import java.util.Map;
 import java.util.regex.Pattern;
 
+import org.apache.commons.compress.CompressException;
 import org.apache.commons.compress.MemoryLimitException;
 import org.apache.commons.compress.archivers.ArchiveException;
 import org.apache.commons.compress.archivers.zip.ZipEncoding;
@@ -50,6 +51,8 @@
  */
 public final class TarUtils {
 
+    private static final char SP = ' ';
+
     /**
      * Encapsulates the algorithms used up to Commons Compress 1.3 as 
ZipEncoding.
      */
@@ -100,10 +103,10 @@ public ByteBuffer encode(final String name) {
      * @param sparseHeaders       per file sparse headers.
      * @param globalPaxHeaders    global PAX headers.
      * @param globalSparseHeaders global sparse headers.
-     * @throws IOException if an I/O error occurs while reading the entry.
+     * @throws ArchiveException   Thrown if an I/O error occurs while reading 
the entry.
      */
     static void applyPaxHeadersToEntry(final TarArchiveEntry entry, final 
Map<String, String> paxHeaders, final List<TarArchiveStructSparse> 
sparseHeaders,
-            final Map<String, String> globalPaxHeaders, final 
List<TarArchiveStructSparse> globalSparseHeaders) throws IOException {
+            final Map<String, String> globalPaxHeaders, final 
List<TarArchiveStructSparse> globalSparseHeaders) throws ArchiveException {
         // Apply PAX headers to the entry
         entry.updateEntryFromPaxHeaders(globalPaxHeaders);
         entry.updateEntryFromPaxHeaders(paxHeaders);
@@ -176,7 +179,7 @@ public static int formatCheckSumOctalBytes(final long 
value, final byte[] buf, f
         int idx = length - 2; // for NUL and space
         formatUnsignedOctalString(value, buf, offset, idx);
         buf[offset + idx++] = 0; // Trailing null
-        buf[offset + idx] = (byte) ' '; // Trailing space
+        buf[offset + idx] = (byte) SP; // Trailing space
         return offset + length;
     }
 
@@ -213,7 +216,7 @@ private static void formatLongBinary(final long value, 
final byte[] buf, final i
     public static int formatLongOctalBytes(final long value, final byte[] buf, 
final int offset, final int length) {
         final int idx = length - 1; // For space
         formatUnsignedOctalString(value, buf, offset, idx);
-        buf[offset + idx] = (byte) ' '; // Trailing space
+        buf[offset + idx] = (byte) SP; // Trailing space
         return offset + length;
     }
 
@@ -310,7 +313,7 @@ public static int formatNameBytes(final String name, final 
byte[] buf, final int
     public static int formatOctalBytes(final long value, final byte[] buf, 
final int offset, final int length) {
         int idx = length - 2; // For space and trailing null
         formatUnsignedOctalString(value, buf, offset, idx);
-        buf[offset + idx++] = (byte) ' '; // Trailing space
+        buf[offset + idx++] = (byte) SP; // Trailing space
         buf[offset + idx] = 0; // Trailing null
         return offset + length;
     }
@@ -463,10 +466,10 @@ public static boolean parseBoolean(final byte[] buffer, 
final int offset) {
      *
      * @param sparseMap The sparse map string consisting of comma-separated 
values "offset,size[,offset-1,size-1...]".
      * @return unmodifiable list of sparse headers parsed from sparse map.
-     * @throws IOException Corrupted TAR archive.
+     * @throws CompressException Thrown for a corrupted TAR archive.
      * @since 1.21
      */
-    static List<TarArchiveStructSparse> parseFromPAX01SparseHeaders(final 
String sparseMap) throws IOException {
+    static List<TarArchiveStructSparse> parseFromPAX01SparseHeaders(final 
String sparseMap) throws CompressException {
         final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
         final String[] sparseHeaderStrings = 
HEADER_STRINGS_PATTERN.split(sparseMap);
         if (sparseHeaderStrings.length % 2 == 1) {
@@ -561,7 +564,7 @@ static long parseOctal(final byte[] buffer, final int 
offset, final int length,
         }
         // Skip leading spaces
         while (start < end) {
-            if (buffer[start] != ' ') {
+            if (buffer[start] != SP) {
                 break;
             }
             start++;
@@ -571,7 +574,7 @@ static long parseOctal(final byte[] buffer, final int 
offset, final int length,
         // space but some implementations use the extra digit for big
         // sizes/uids/gids ...
         byte trailer = buffer[end - 1];
-        while (start < end && (trailer == 0 || trailer == ' ')) {
+        while (start < end && (trailer == 0 || trailer == SP)) {
             end--;
             trailer = buffer[end - 1];
         }
@@ -690,9 +693,10 @@ static Map<String, String> parsePaxHeaders(final 
InputStream inputStream, final
                 if (ch == '\n') { // blank line in header
                     break;
                 }
-                if (ch == ' ') { // End of length string
+                if (ch == SP) { // End of length string
                     // Get keyword
-                    final ByteArrayOutputStream coll = new 
ByteArrayOutputStream();
+                    @SuppressWarnings("resource")
+                    final ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
                     while ((ch = inputStream.read()) != -1) {
                         read++;
                         totalRead++;
@@ -700,7 +704,7 @@ static Map<String, String> parsePaxHeaders(final 
InputStream inputStream, final
                             break;
                         }
                         if (ch == '=') { // end of keyword
-                            final String keyword = 
coll.toString(StandardCharsets.UTF_8);
+                            final String keyword = 
baos.toString(StandardCharsets.UTF_8);
                             // Get rest of entry
                             final int restLen = len - read;
                             // Validate entry length
@@ -761,7 +765,7 @@ static Map<String, String> parsePaxHeaders(final 
InputStream inputStream, final
                             }
                             break;
                         }
-                        coll.write((byte) ch);
+                        baos.write((byte) ch);
                     }
                     break; // Processed single header
                 }
@@ -914,7 +918,7 @@ static boolean verifyCheckSum(final byte[] header, final 
boolean lenient) {
         for (int i = 0; i < header.length; i++) {
             byte b = header[i];
             if (TarConstants.CHKSUM_OFFSET <= i && i < 
TarConstants.CHKSUM_OFFSET + TarConstants.CHKSUMLEN) {
-                b = ' ';
+                b = SP;
             }
             unsignedSum += 0xff & b;
             signedSum += b;
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveEntryTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveEntryTest.java
index efe476608..0543c7c8a 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveEntryTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveEntryTest.java
@@ -43,6 +43,7 @@
 import java.util.Random;
 
 import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.compress.CompressException;
 import org.apache.commons.compress.archivers.ArchiveException;
 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
 import org.apache.commons.lang3.StringUtils;
@@ -63,7 +64,7 @@ private void assertPosixMagic(final TarArchiveEntry t) {
         assertEquals(MAGIC_POSIX + VERSION_POSIX, readMagic(t));
     }
 
-    private TarArchiveEntry createEntryForTimeTests() {
+    private TarArchiveEntry createEntryForTimeTests() throws CompressException 
{
         final TarArchiveEntry entry = new TarArchiveEntry("./times.txt");
         entry.addPaxHeader("size", "1");
         entry.addPaxHeader("mtime", "1647221103.5998539");
@@ -277,7 +278,7 @@ void testPaxTimeFieldsForInvalidValues() {
         final TarArchiveEntry entry = new TarArchiveEntry("test.txt");
         for (final String name : headerNames) {
             for (final String value : testValues) {
-                final Exception exp = 
assertThrows(IllegalArgumentException.class, () -> entry.addPaxHeader(name, 
value));
+                final ArchiveException exp = 
assertThrows(ArchiveException.class, () -> entry.addPaxHeader(name, value));
                 assertTrue(exp.getCause().getMessage().startsWith("Corrupted 
PAX header. Time field value is invalid"));
             }
         }
@@ -325,7 +326,7 @@ void testShouldNotWriteTimePaxHeadersByDefault() throws 
IOException {
     }
 
     @Test
-    void testShouldParseTimePaxHeadersAndNotCountAsExtraPaxHeaders() {
+    void testShouldParseTimePaxHeadersAndNotCountAsExtraPaxHeaders() throws 
CompressException {
         final TarArchiveEntry entry = createEntryForTimeTests();
         assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header 
count");
         assertNull(entry.getExtraPaxHeader("size"), "size");

Reply via email to