Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java?rev=1350857&r1=1350856&r2=1350857&view=diff ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java (original) +++ ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java Sat Jun 16 04:12:37 2012 @@ -23,6 +23,12 @@ package org.apache.tools.tar; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import org.apache.tools.zip.ZipEncoding; +import org.apache.tools.zip.ZipEncodingHelper; + /** * This class provides static utility methods to work with byte streams. * @@ -32,158 +38,505 @@ public class TarUtils { private static final int BYTE_MASK = 255; + static final ZipEncoding DEFAULT_ENCODING = + ZipEncodingHelper.getZipEncoding(null); + + /** + * Encapsulates the algorithms used up to Ant 1.8 as ZipEncoding. + */ + static final ZipEncoding FALLBACK_ENCODING = new ZipEncoding() { + public boolean canEncode(String name) { return true; } + + public ByteBuffer encode(String name) { + final int length = name.length(); + byte[] buf = new byte[length]; + + // copy until end of input or output is reached. + for (int i = 0; i < length; ++i) { + buf[i] = (byte) name.charAt(i); + } + return ByteBuffer.wrap(buf); + } + + public String decode(byte[] buffer) { + final int length = buffer.length; + StringBuffer result = new StringBuffer(length); + + for (int i = 0; i < length; ++i) { + byte b = buffer[i]; + if (b == 0) { // Trailing null + break; + } + result.append((char) (b & 0xFF)); // Allow for sign-extension + } + + return result.toString(); + } + }; + + /** Private constructor to prevent instantiation of this utility class. */ + private TarUtils(){ + } + /** - * Parse an octal string from a header buffer. This is used for the - * file permission mode value. + * Parse an octal string from a buffer. * - * @param header The header buffer from which to parse. + * <p>Leading spaces are ignored. + * The buffer must contain a trailing space or NUL, + * and may contain an additional trailing space or NUL.</p> + * + * <p>The input buffer is allowed to contain all NULs, + * in which case the method returns 0L + * (this allows for missing fields).</p> + * + * <p>To work-around some tar implementations that insert a + * leading NUL this method returns 0 if it detects a leading NUL + * since Ant 1.9.</p> + * + * @param buffer The buffer from which to parse. * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. + * @param length The maximum number of bytes to parse - must be at least 2 bytes. * @return The long value of the octal string. + * @throws IllegalArgumentException if the trailing space/NUL is missing or if a invalid byte is detected. */ - public static long parseOctal(byte[] header, int offset, int length) { + public static long parseOctal(final byte[] buffer, final int offset, final int length) { long result = 0; - boolean stillPadding = true; int end = offset + length; + int start = offset; - for (int i = offset; i < end; ++i) { - if (header[i] == 0) { - break; - } + if (length < 2){ + throw new IllegalArgumentException("Length "+length+" must be at least 2"); + } - if (header[i] == (byte) ' ' || header[i] == '0') { - if (stillPadding) { - continue; - } + if (buffer[start] == 0) { + return 0L; + } - if (header[i] == (byte) ' ') { - break; - } + // Skip leading spaces + while (start < end){ + if (buffer[start] == ' '){ + start++; + } else { + break; } + } + + // Must have trailing NUL or space + byte trailer; + trailer = buffer[end-1]; + if (trailer == 0 || trailer == ' '){ + end--; + } else { + throw new IllegalArgumentException( + exceptionMessage(buffer, offset, length, end-1, trailer)); + } + // May have additional NUL or space + trailer = buffer[end-1]; + if (trailer == 0 || trailer == ' '){ + end--; + } - stillPadding = false; + for ( ;start < end; start++) { + final byte currentByte = buffer[start]; // CheckStyle:MagicNumber OFF - result = (result << 3) + (header[i] - '0'); + if (currentByte < '0' || currentByte > '7'){ + throw new IllegalArgumentException( + exceptionMessage(buffer, offset, length, start, currentByte)); + } + result = (result << 3) + (currentByte - '0'); // convert from ASCII // CheckStyle:MagicNumber ON } return result; } - /** - * Parse an entry name from a header buffer. + /** + * Compute the value contained in a byte buffer. If the most + * significant bit of the first byte in the buffer is set, this + * bit is ignored and the rest of the buffer is interpreted as a + * binary number. Otherwise, the buffer is interpreted as an + * octal number as per the parseOctal function above. * - * @param header The header buffer from which to parse. + * @param buffer The buffer from which to parse. * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. - * @return The header's entry name. + * @param length The maximum number of bytes to parse. + * @return The long value of the octal or binary string. + * @throws IllegalArgumentException if the trailing space/NUL is + * missing or an invalid byte is detected in an octal number, or + * if a binary number would exceed the size of a signed long + * 64-bit integer. */ - public static StringBuffer parseName(byte[] header, int offset, int length) { - StringBuffer result = new StringBuffer(length); - int end = offset + length; + public static long parseOctalOrBinary(final byte[] buffer, final int offset, + final int length) { - for (int i = offset; i < end; ++i) { - if (header[i] == 0) { - break; - } + 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); + } - result.append((char) header[i]); + 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++) { + val = (val << 8) + (buffer[offset + i] & 0xff); + } + if (negative) { + // 2's complement + val--; + val ^= ((long) Math.pow(2, (length - 1) * 8) - 1); } + return negative ? -val : val; + } - return result; + 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(); } /** - * Determine the number of bytes in an entry name. + * Parse a boolean byte from a buffer. + * Leading spaces and NUL are ignored. + * The buffer may contain trailing spaces or NULs. * - * @param name The header name from which to parse. - * @param buf The buffer from which to parse. + * @param buffer The buffer from which to parse. * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. - * @return The number of bytes in a header's entry name. + * @return The boolean value of the bytes. + * @throws IllegalArgumentException if an invalid byte is detected. */ - public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) { - int i; + public static boolean parseBoolean(final byte[] buffer, final int offset) { + return buffer[offset] == 1; + } + + // Helper method to generate the exception message + private static String exceptionMessage(byte[] buffer, final int offset, + final int length, int current, final byte currentByte) { + String string = new String(buffer, offset, length); // TODO default charset? + string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed + final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length; + return s; + } - for (i = 0; i < length && i < name.length(); ++i) { - buf[offset + i] = (byte) name.charAt(i); + /** + * Parse an entry name from a buffer. + * Parsing stops when a NUL is found + * or the buffer length is reached. + * + * @param buffer The buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The maximum number of bytes to parse. + * @return The entry name. + */ + public static String parseName(byte[] buffer, final int offset, final int length) { + try { + return parseName(buffer, offset, length, DEFAULT_ENCODING); + } catch (IOException ex) { + try { + return parseName(buffer, offset, length, FALLBACK_ENCODING); + } catch (IOException ex2) { + // impossible + throw new RuntimeException(ex2); + } } + } - for (; i < length; ++i) { - buf[offset + i] = 0; + /** + * Parse an entry name from a buffer. + * Parsing stops when a NUL is found + * or the buffer length is reached. + * + * @param buffer The buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The maximum number of bytes to parse. + * @param encoding name of the encoding to use for file names + * @return The entry name. + */ + public static String parseName(byte[] buffer, final int offset, + final int length, + final ZipEncoding encoding) + throws IOException { + + int len = length; + for (; len > 0; len--) { + if (buffer[offset + len - 1] != 0) { + break; + } } + if (len > 0) { + byte[] b = new byte[len]; + System.arraycopy(buffer, offset, b, 0, len); + return encoding.decode(b); + } + return ""; + } - return offset + length; + /** + * Copy a name into a buffer. + * Copies characters from the name into the buffer + * starting at the specified offset. + * If the buffer is longer than the name, the buffer + * is filled with trailing NULs. + * If the name is longer than the buffer, + * the output is truncated. + * + * @param name The header name from which to copy the characters. + * @param buf The buffer where the name is to be stored. + * @param offset The starting offset into the buffer + * @param length The maximum number of header bytes to copy. + * @return The updated offset, i.e. offset + length + */ + public static int formatNameBytes(String name, byte[] buf, final int offset, final int length) { + try { + return formatNameBytes(name, buf, offset, length, DEFAULT_ENCODING); + } catch (IOException ex) { + try { + return formatNameBytes(name, buf, offset, length, + FALLBACK_ENCODING); + } catch (IOException ex2) { + // impossible + throw new RuntimeException(ex2); + } + } } /** - * Parse an octal integer from a header buffer. + * Copy a name into a buffer. + * Copies characters from the name into the buffer + * starting at the specified offset. + * If the buffer is longer than the name, the buffer + * is filled with trailing NULs. + * If the name is longer than the buffer, + * the output is truncated. * - * @param value The header value - * @param buf The buffer from which to parse. - * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. - * @return The integer value of the octal bytes. + * @param name The header name from which to copy the characters. + * @param buf The buffer where the name is to be stored. + * @param offset The starting offset into the buffer + * @param length The maximum number of header bytes to copy. + * @param encoding name of the encoding to use for file names + * @return The updated offset, i.e. offset + length */ - public static int getOctalBytes(long value, byte[] buf, int offset, int length) { - int idx = length - 1; + public static int formatNameBytes(String name, byte[] buf, final int offset, + final int length, + final ZipEncoding encoding) + throws IOException { + int len = name.length(); + ByteBuffer b = encoding.encode(name); + while (b.limit() > length && len > 0) { + b = encoding.encode(name.substring(0, --len)); + } + final int limit = b.limit(); + System.arraycopy(b.array(), b.arrayOffset(), buf, offset, limit); - buf[offset + idx] = 0; - --idx; - buf[offset + idx] = (byte) ' '; - --idx; + // Pad any remaining output bytes with NUL + for (int i = limit; i < length; ++i) { + buf[offset + i] = 0; + } + return offset + length; + } + + /** + * Fill buffer with unsigned octal number, padded with leading zeroes. + * + * @param value number to convert to octal - treated as unsigned + * @param buffer destination buffer + * @param offset starting offset in buffer + * @param length length of buffer to fill + * @throws IllegalArgumentException if the value will not fit in the buffer + */ + public static void formatUnsignedOctalString(final long value, byte[] buffer, + final int offset, final int length) { + int remaining = length; + remaining--; if (value == 0) { - buf[offset + idx] = (byte) '0'; - --idx; + buffer[offset + remaining--] = (byte) '0'; } else { - for (long val = value; idx >= 0 && val > 0; --idx) { + long val = value; + for (; remaining >= 0 && val != 0; --remaining) { // CheckStyle:MagicNumber OFF - buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7)); - val = val >> 3; + buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7)); + val = val >>> 3; // CheckStyle:MagicNumber ON } + if (val != 0){ + throw new IllegalArgumentException + (value+"="+Long.toOctalString(value)+ " will not fit in octal number buffer of length "+length); + } } - for (; idx >= 0; --idx) { - buf[offset + idx] = (byte) ' '; + for (; remaining >= 0; --remaining) { // leading zeros + buffer[offset + remaining] = (byte) '0'; } + } + + /** + * Write an octal integer into a buffer. + * + * Uses {@link #formatUnsignedOctalString} to format + * the value as an octal string with leading zeros. + * The converted number is followed by space and NUL + * + * @param value The value to write + * @param buf The buffer to receive the output + * @param offset The starting offset into the buffer + * @param length The size of the output buffer + * @return The updated offset, i.e offset+length + * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer + */ + public static int formatOctalBytes(final long value, 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] = 0; // Trailing null return offset + length; } /** - * Parse an octal long integer from a header buffer. - * - * @param value The header value - * @param buf The buffer from which to parse. - * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. - * @return The long value of the octal bytes. + * Write an octal long integer into a buffer. + * + * Uses {@link #formatUnsignedOctalString} to format + * the value as an octal string with leading zeros. + * The converted number is followed by a space. + * + * @param value The value to write as octal + * @param buf The destinationbuffer. + * @param offset The starting offset into the buffer. + * @param length The length of the buffer + * @return The updated offset + * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer */ - public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) { - byte[] temp = new byte[length + 1]; + public static int formatLongOctalBytes(final long value, byte[] buf, final int offset, final int length) { - getOctalBytes(value, temp, 0, length + 1); - System.arraycopy(temp, 0, buf, offset, length); + int idx=length-1; // For space + + formatUnsignedOctalString(value, buf, offset, idx); + buf[offset + idx] = (byte) ' '; // Trailing space return offset + length; } /** - * Parse the checksum octal integer from a header buffer. + * Write an long integer into a buffer as an octal string if this + * will fit, or as a binary number otherwise. + * + * Uses {@link #formatUnsignedOctalString} to format + * the value as an octal string with leading zeros. + * The converted number is followed by a space. + * + * @param value The value to write into the buffer. + * @param buf The destination buffer. + * @param offset The starting offset into the buffer. + * @param length The length of the buffer. + * @return The updated offset. + * @throws IllegalArgumentException if the value (and trailer) + * will not fit in the buffer. + */ + public static int formatLongOctalOrBinaryBytes( + final long value, byte[] buf, final int offset, final int length) { + + // Check whether we are dealing with UID/GID or SIZE field + final long maxAsOctalChar = length == TarConstants.UIDLEN ? TarConstants.MAXID : TarConstants.MAXSIZE; + + final boolean negative = value < 0; + if (!negative && value <= maxAsOctalChar) { // OK to store as octal chars + return formatLongOctalBytes(value, buf, offset, length); + } + + 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 = 1l << 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; + } + } + + 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; + } + } + + /** + * Writes an octal value into a buffer. + * + * Uses {@link #formatUnsignedOctalString} to format + * the value as an octal string with leading zeros. + * The converted number is followed by NUL and then space. * - * @param value The header value - * @param buf The buffer from which to parse. - * @param offset The offset into the buffer from which to parse. - * @param length The number of header bytes to parse. - * @return The integer value of the entry's checksum. + * @param value The value to convert + * @param buf The destination buffer + * @param offset The starting offset into the buffer. + * @param length The size of the buffer. + * @return The updated value of offset, i.e. offset+length + * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer */ - public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) { - getOctalBytes(value, buf, offset, length); + public static int formatCheckSumOctalBytes(final long value, byte[] buf, final int offset, final int length) { + + int idx=length-2; // for NUL and space + formatUnsignedOctalString(value, buf, offset, idx); - buf[offset + length - 1] = (byte) ' '; - buf[offset + length - 2] = 0; + buf[offset + idx++] = 0; // Trailing null + buf[offset + idx] = (byte) ' '; // Trailing space return offset + length; } @@ -194,7 +547,7 @@ public class TarUtils { * @param buf The tar entry's header buffer. * @return The computed checksum. */ - public static long computeCheckSum(byte[] buf) { + public static long computeCheckSum(final byte[] buf) { long sum = 0; for (int i = 0; i < buf.length; ++i) { @@ -203,4 +556,5 @@ public class TarUtils { return sum; } + }
Modified: ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java?rev=1350857&r1=1350856&r2=1350857&view=diff ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java (original) +++ ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java Sat Jun 16 04:12:37 2012 @@ -41,7 +41,7 @@ import java.nio.ByteBuffer; * <p>All implementations should implement this interface in a * reentrant way.</p> */ -interface ZipEncoding { +public interface ZipEncoding { /** * Check, whether the given string may be losslessly encoded using this * encoding. Modified: ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java?rev=1350857&r1=1350856&r2=1350857&view=diff ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java (original) +++ ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java Sat Jun 16 04:12:37 2012 @@ -27,7 +27,7 @@ import java.util.Map; /** * Static helper functions for robustly encoding filenames in zip files. */ -abstract class ZipEncodingHelper { +public abstract class ZipEncodingHelper { /** * A class, which holds the high characters of a simple encoding @@ -207,7 +207,7 @@ abstract class ZipEncodingHelper { * the platform's default encoding. * @return A zip encoding for the given encoding name. */ - static ZipEncoding getZipEncoding(String name) { + public static ZipEncoding getZipEncoding(String name) { // fallback encoding is good enough for utf-8. if (isUTF8(name)) {