Our parser for STAR-encoded binary values was wrong, STAR only uses 7 or 11 bytes and can encode negative values as twos complement signaled by the first byte being 0xff rather than 0x80. COMPRESS-182
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/compress/trunk@1296576 13f79535-47bb-0310-9956-ffa450edef68
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 d989b8a..b45b1f2 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
@@ -18,6 +18,8 @@
*/
package org.apache.commons.compress.archivers.tar;
+import java.math.BigInteger;
+
/**
* This class provides static utility methods to work with byte streams.
*
@@ -127,17 +129,52 @@
if ((buffer[offset] & 0x80) == 0) {
return parseOctal(buffer, offset, length);
}
+ final boolean negative = buffer[offset] == (byte) 0xff;
+ if (length < 9) {
+ return parseBinaryLong(buffer, offset, length, negative);
+ }
+ return parseBinaryBigInteger(buffer, offset, length, negative);
+ }
- long val = buffer[offset] & 0x7f;
+ private static long parseBinaryLong(final byte[] buffer, final int offset,
+ final int length,
+ final boolean negative) {
+ if (length >= 9) {
+ throw new IllegalArgumentException("At offset " + offset + ", "
+ + length + " byte binary number"
+ + " exceeds maximum signed long"
+ + " value");
+ }
+ long val = 0;
for (int i = 1; i < length; i++) {
- if (val >= (1L << (63 - 8))) {
- throw new IllegalArgumentException(
- "At offset " + offset + ", " + length + " byte " +
- "binary number exceeds maximum signed long value");
- }
val = (val << 8) + (buffer[offset + i] & 0xff);
}
- return val;
+ if (negative) {
+ // 2's complement
+ val--;
+ val ^= ((long) Math.pow(2, (length - 1) * 8) - 1);
+ }
+ return negative ? -val : val;
+ }
+
+ private static long parseBinaryBigInteger(final byte[] buffer,
+ final int offset,
+ final int length,
+ final boolean negative) {
+ byte[] remainder = new byte[length - 1];
+ System.arraycopy(buffer, offset + 1, remainder, 0, length - 1);
+ BigInteger val = new BigInteger(remainder);
+ if (negative) {
+ // 2's complement
+ val = val.add(BigInteger.valueOf(-1)).not();
+ }
+ if (val.bitLength() > 63) {
+ throw new IllegalArgumentException("At offset " + offset + ", "
+ + length + " byte binary number"
+ + " exceeds maximum signed long"
+ + " value");
+ }
+ return negative ? -val.longValue() : val.longValue();
}
/**
@@ -325,23 +362,54 @@
// Check whether we are dealing with UID/GID or SIZE field
final long maxAsOctalChar = length == TarConstants.UIDLEN ? TarConstants.MAXID : TarConstants.MAXSIZE;
- if (value <= maxAsOctalChar) { // OK to store as octal chars
+ final boolean negative = value < 0;
+ if (!negative && value <= maxAsOctalChar) { // OK to store as octal chars
return formatLongOctalBytes(value, buf, offset, length);
}
- long val = value;
+ if (length < 9) {
+ formatLongBinary(value, buf, offset, length, negative);
+ }
+ formatBigIntegerBinary(value, buf, offset, length, negative);
+
+ buf[offset] = (byte) (negative ? 0xff : 0x80);
+ return offset + length;
+ }
+
+ private static void formatLongBinary(final long value, byte[] buf,
+ final int offset, final int length,
+ final boolean negative) {
+ final int bits = (length - 1) * 8;
+ final long max = (long) Math.pow(2, bits);
+ long val = Math.abs(value);
+ if (val >= max) {
+ throw new IllegalArgumentException("Value " + value +
+ " is too large for " + length + " byte field.");
+ }
+ if (negative) {
+ val ^= max - 1;
+ val |= 0xff << bits;
+ val++;
+ }
for (int i = offset + length - 1; i >= offset; i--) {
buf[i] = (byte) val;
val >>= 8;
}
+ }
- if (val != 0 || (buf[offset] & 0x80) != 0) {
- throw new IllegalArgumentException("Value " + value +
- " is too large for " + length + " byte field.");
+ private static void formatBigIntegerBinary(final long value, byte[] buf,
+ final int offset,
+ final int length,
+ final boolean negative) {
+ BigInteger val = BigInteger.valueOf(value);
+ final byte[] b = val.toByteArray();
+ final int len = b.length;
+ final int off = offset + length - len;
+ System.arraycopy(b, 0, buf, off, len);
+ final byte fill = (byte) (negative ? 0xff : 0);
+ for (int i = offset + 1; i < off; i++) {
+ buf[i] = fill;
}
-
- buf[offset] |= 0x80;
- return offset + length;
}
/**
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
index 28f11e0..e7ca1b7 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
@@ -136,11 +136,18 @@
assertEquals(value,parseValue);
}
- public void testRoundTripOctalOrBinary() {
- checkRoundTripOctalOrBinary(0, 8);
- checkRoundTripOctalOrBinary(1, 8);
- checkRoundTripOctalOrBinary(Long.MAX_VALUE, 8); // [0x7f ff ff ff ff ff ff ff
- checkRoundTripOctalOrBinary(TarConstants.MAXSIZE, 8); // will need binary format
+ public void testRoundTripOctalOrBinary8() {
+ testRoundTripOctalOrBinary(8);
+ }
+ public void testRoundTripOctalOrBinary12() {
+ testRoundTripOctalOrBinary(12);
+ }
+ private void testRoundTripOctalOrBinary(int length) {
+ checkRoundTripOctalOrBinary(0, length);
+ checkRoundTripOctalOrBinary(1, length);
+ checkRoundTripOctalOrBinary(Long.MAX_VALUE >> 7, length); // [0x00 ff ff ff ff ff ff ff
+ checkRoundTripOctalOrBinary(TarConstants.MAXSIZE, length); // will need binary format
+ checkRoundTripOctalOrBinary(-1, length); // will need binary format
}
// Check correct trailing bytes are generated
@@ -190,4 +197,31 @@
int len = TarUtils.formatNameBytes(string, buff, 0, buff.length);
assertEquals(string, TarUtils.parseName(buff, 0, len));
}
+
+ public void testReadNegativeBinary8Byte() {
+ byte[] b = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xf1, (byte) 0xef,
+ };
+ assertEquals(-3601l, TarUtils.parseOctalOrBinary(b, 0, 8));
+ }
+
+ public void testReadNegativeBinary12Byte() {
+ byte[] b = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xf1, (byte) 0xef,
+ };
+ assertEquals(-3601l, TarUtils.parseOctalOrBinary(b, 0, 12));
+ }
+
+
+ public void testWriteNegativeBinary8Byte() {
+ byte[] b = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xf1, (byte) 0xef,
+ };
+ assertEquals(-3601l, TarUtils.parseOctalOrBinary(b, 0, 8));
+ }
+
}