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");
