Browse Source

merge tar package from Compress, bringing some POSIX tar support

git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@1350857 13f79535-47bb-0310-9956-ffa450edef68
master
Stefan Bodewig 13 years ago
parent
commit
2e5b53fa10
10 changed files with 1648 additions and 222 deletions
  1. +3
    -0
      WHATSNEW
  2. +63
    -0
      src/main/org/apache/tools/tar/TarArchiveSparseEntry.java
  3. +25
    -24
      src/main/org/apache/tools/tar/TarBuffer.java
  4. +139
    -9
      src/main/org/apache/tools/tar/TarConstants.java
  5. +476
    -55
      src/main/org/apache/tools/tar/TarEntry.java
  6. +246
    -28
      src/main/org/apache/tools/tar/TarInputStream.java
  7. +253
    -17
      src/main/org/apache/tools/tar/TarOutputStream.java
  8. +440
    -86
      src/main/org/apache/tools/tar/TarUtils.java
  9. +1
    -1
      src/main/org/apache/tools/zip/ZipEncoding.java
  10. +2
    -2
      src/main/org/apache/tools/zip/ZipEncodingHelper.java

+ 3
- 0
WHATSNEW View File

@@ -49,6 +49,9 @@ Other changes:
Java VMs. Java VMs.
Bugzilla Report 52706. Bugzilla Report 52706.


* merged the TAR package from Commons Compress, it can now read
archives using POSIX extension headers and STAR extensions.

Changes from Ant 1.8.3 TO Ant 1.8.4 Changes from Ant 1.8.3 TO Ant 1.8.4
=================================== ===================================




+ 63
- 0
src/main/org/apache/tools/tar/TarArchiveSparseEntry.java View File

@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tools.tar;

import java.io.IOException;

/**
* This class represents a sparse entry in a Tar archive.
*
* <p>
* The C structure for a sparse entry is:
* <pre>
* struct posix_header {
* struct sparse sp[21]; // TarConstants.SPARSELEN_GNU_SPARSE - offset 0
* char isextended; // TarConstants.ISEXTENDEDLEN_GNU_SPARSE - offset 504
* };
* </pre>
* Whereas, "struct sparse" is:
* <pre>
* struct sparse {
* char offset[12]; // offset 0
* char numbytes[12]; // offset 12
* };
* </pre>
*/

public class TarArchiveSparseEntry implements TarConstants {
/** If an extension sparse header follows. */
private boolean isExtended;

/**
* Construct an entry from an archive's header bytes. File is set
* to null.
*
* @param headerBuf The header bytes from a tar archive entry.
* @throws IOException on unknown format
*/
public TarArchiveSparseEntry(byte[] headerBuf) throws IOException {
int offset = 0;
offset += SPARSELEN_GNU_SPARSE;
isExtended = TarUtils.parseBoolean(headerBuf, offset);
}

public boolean isExtended() {
return isExtended;
}
}

+ 25
- 24
src/main/org/apache/tools/tar/TarBuffer.java View File

@@ -51,12 +51,13 @@ public class TarBuffer {


private InputStream inStream; private InputStream inStream;
private OutputStream outStream; private OutputStream outStream;
private byte[] blockBuffer;
private final int blockSize;
private final int recordSize;
private final int recsPerBlock;
private final byte[] blockBuffer;

private int currBlkIdx; private int currBlkIdx;
private int currRecIdx; private int currRecIdx;
private int blockSize;
private int recordSize;
private int recsPerBlock;
private boolean debug; private boolean debug;


/** /**
@@ -83,10 +84,7 @@ public class TarBuffer {
* @param recordSize the record size to use * @param recordSize the record size to use
*/ */
public TarBuffer(InputStream inStream, int blockSize, int recordSize) { public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
this.inStream = inStream;
this.outStream = null;

this.initialize(blockSize, recordSize);
this(inStream, null, blockSize, recordSize);
} }


/** /**
@@ -113,16 +111,15 @@ public class TarBuffer {
* @param recordSize the record size to use * @param recordSize the record size to use
*/ */
public TarBuffer(OutputStream outStream, int blockSize, int recordSize) { public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
this.inStream = null;
this.outStream = outStream;

this.initialize(blockSize, recordSize);
this(null, outStream, blockSize, recordSize);
} }


/** /**
* Initialization common to all constructors.
* Private constructor to perform common setup.
*/ */
private void initialize(int blockSize, int recordSize) {
private TarBuffer(InputStream inStream, OutputStream outStream, int blockSize, int recordSize) {
this.inStream = inStream;
this.outStream = outStream;
this.debug = false; this.debug = false;
this.blockSize = blockSize; this.blockSize = blockSize;
this.recordSize = recordSize; this.recordSize = recordSize;
@@ -194,10 +191,8 @@ public class TarBuffer {
throw new IOException("reading (via skip) from an output buffer"); throw new IOException("reading (via skip) from an output buffer");
} }


if (currRecIdx >= recsPerBlock) {
if (!readBlock()) {
return; // UNDONE
}
if (currRecIdx >= recsPerBlock && !readBlock()) {
return; // UNDONE
} }


currRecIdx++; currRecIdx++;
@@ -216,13 +211,14 @@ public class TarBuffer {
} }


if (inStream == null) { if (inStream == null) {
if (outStream == null) {
throw new IOException("input buffer is closed");
}
throw new IOException("reading from an output buffer"); throw new IOException("reading from an output buffer");
} }


if (currRecIdx >= recsPerBlock) {
if (!readBlock()) {
return null;
}
if (currRecIdx >= recsPerBlock && !readBlock()) {
return null;
} }


byte[] result = new byte[recordSize]; byte[] result = new byte[recordSize];
@@ -337,6 +333,9 @@ public class TarBuffer {
} }


if (outStream == null) { if (outStream == null) {
if (inStream == null){
throw new IOException("Output buffer is closed");
}
throw new IOException("writing to an input buffer"); throw new IOException("writing to an input buffer");
} }


@@ -374,6 +373,9 @@ public class TarBuffer {
} }


if (outStream == null) { if (outStream == null) {
if (inStream == null){
throw new IOException("Output buffer is closed");
}
throw new IOException("writing to an input buffer"); throw new IOException("writing to an input buffer");
} }


@@ -454,9 +456,8 @@ public class TarBuffer {
} else if (inStream != null) { } else if (inStream != null) {
if (inStream != System.in) { if (inStream != System.in) {
inStream.close(); inStream.close();

inStream = null;
} }
inStream = null;
} }
} }
} }

+ 139
- 9
src/main/org/apache/tools/tar/TarConstants.java View File

@@ -26,10 +26,22 @@ package org.apache.tools.tar;
/** /**
* This interface contains all the definitions used in the package. * This interface contains all the definitions used in the package.
* *
* For tar formats (FORMAT_OLDGNU, FORMAT_POSIX, etc.) see GNU tar
* <I>tar.h</I> type <I>enum archive_format</I>
*/ */
// CheckStyle:InterfaceIsTypeCheck OFF (bc) // CheckStyle:InterfaceIsTypeCheck OFF (bc)
public interface TarConstants { public interface TarConstants {


/**
* GNU format as per before tar 1.12.
*/
int FORMAT_OLDGNU = 2;

/**
* Pure Posix format.
*/
int FORMAT_POSIX = 3;

/** /**
* The length of the name field in a header buffer. * The length of the name field in a header buffer.
*/ */
@@ -50,6 +62,12 @@ public interface TarConstants {
*/ */
int GIDLEN = 8; int GIDLEN = 8;


/**
* The maximum value of gid/uid in a tar archive which can
* be expressed in octal char notation (that's 7 sevens, octal).
*/
long MAXID = 07777777L;
/** /**
* The length of the checksum field in a header buffer. * The length of the checksum field in a header buffer.
*/ */
@@ -57,19 +75,36 @@ public interface TarConstants {


/** /**
* The length of the size field in a header buffer. * The length of the size field in a header buffer.
* Includes the trailing space or NUL.
*/ */
int SIZELEN = 12; int SIZELEN = 12;


/** /**
* The maximum size of a file in a tar archive (That's 11 sevens, octal).
* The maximum size of a file in a tar archive
* which can be expressed in octal char notation (that's 11 sevens, octal).
*/ */
long MAXSIZE = 077777777777L; long MAXSIZE = 077777777777L;


/** Offset of start of magic field within header record */
int MAGIC_OFFSET = 257;
/** /**
* The length of the magic field in a header buffer.
* The length of the magic field in a header buffer including the version.
*/ */
int MAGICLEN = 8; int MAGICLEN = 8;


/**
* The length of the magic field in a header buffer.
*/
int PURE_MAGICLEN = 6;

/** Offset of start of magic field within header record */
int VERSION_OFFSET = 263;
/**
* Previously this was regarded as part of "magic" field, but it
* is separate.
*/
int VERSIONLEN = 2;

/** /**
* The length of the modification time field in a header buffer. * The length of the modification time field in a header buffer.
*/ */
@@ -86,10 +121,76 @@ public interface TarConstants {
int GNAMELEN = 32; int GNAMELEN = 32;


/** /**
* The length of the devices field in a header buffer.
* The length of each of the device fields (major and minor) in a header buffer.
*/ */
int DEVLEN = 8; int DEVLEN = 8;


/**
* Length of the prefix field.
*
*/
int PREFIXLEN = 155;

/**
* The length of the access time field in an old GNU header buffer.
*
*/
int ATIMELEN_GNU = 12;

/**
* The length of the created time field in an old GNU header buffer.
*
*/
int CTIMELEN_GNU = 12;

/**
* The length of the multivolume start offset field in an old GNU header buffer.
*
*/
int OFFSETLEN_GNU = 12;

/**
* The length of the long names field in an old GNU header buffer.
*
*/
int LONGNAMESLEN_GNU = 4;

/**
* The length of the padding field in an old GNU header buffer.
*
*/
int PAD2LEN_GNU = 1;

/**
* The sum of the length of all sparse headers in an old GNU header buffer.
*
*/
int SPARSELEN_GNU = 96;

/**
* The length of the is extension field in an old GNU header buffer.
*
*/
int ISEXTENDEDLEN_GNU = 1;

/**
* The length of the real size field in an old GNU header buffer.
*
*/
int REALSIZELEN_GNU = 12;

/**
* The sum of the length of all sparse headers in a sparse header buffer.
*
*/
int SPARSELEN_GNU_SPARSE = 504;

/**
* The length of the is extension field in a sparse header buffer.
*
*/
int ISEXTENDEDLEN_GNU_SPARSE = 1;

/** /**
* LF_ constants represent the "link flag" of an entry, or more commonly, * LF_ constants represent the "link flag" of an entry, or more commonly,
* the "entry type". This is the "old way" of indicating a normal file. * the "entry type". This is the "old way" of indicating a normal file.
@@ -137,22 +238,51 @@ public interface TarConstants {
byte LF_CONTIG = (byte) '7'; byte LF_CONTIG = (byte) '7';


/** /**
* The magic tag representing a POSIX tar archive.
* Identifies the *next* file on the tape as having a long name.
*/ */
byte LF_GNUTYPE_LONGNAME = (byte) 'L';

/**
* Sparse file type.
*/
byte LF_GNUTYPE_SPARSE = (byte) 'S';

// See "http://www.opengroup.org/onlinepubs/009695399/utilities/pax.html#tag_04_100_13_02"

/**
* Identifies the entry as a Pax extended header.
*/
byte LF_PAX_EXTENDED_HEADER_LC = (byte) 'x';

/**
* Identifies the entry as a Pax extended header (SunOS tar -E).
*/
byte LF_PAX_EXTENDED_HEADER_UC = (byte) 'X';

/**
* Identifies the entry as a Pax global extended header.
*/
byte LF_PAX_GLOBAL_EXTENDED_HEADER = (byte) 'g';

String TMAGIC = "ustar"; String TMAGIC = "ustar";


/**
* The magic tag representing a POSIX tar archive.
*/
String MAGIC_POSIX = "ustar\0";
String VERSION_POSIX = "00";

/** /**
* The magic tag representing a GNU tar archive. * The magic tag representing a GNU tar archive.
*/ */
String GNU_TMAGIC = "ustar "; String GNU_TMAGIC = "ustar ";
// Appear to be two possible GNU versions
String VERSION_GNU_SPACE = " \0";
String VERSION_GNU_ZERO = "0\0";


/** /**
* The namr of the GNU tar entry which contains a long name.
* The name of the GNU tar entry which contains a long name.
*/ */
String GNU_LONGLINK = "././@LongLink"; String GNU_LONGLINK = "././@LongLink";


/**
* Identifies the *next* file on the tape as having a long name.
*/
byte LF_GNUTYPE_LONGNAME = (byte) 'L';
} }

+ 476
- 55
src/main/org/apache/tools/tar/TarEntry.java View File

@@ -24,9 +24,13 @@
package org.apache.tools.tar; package org.apache.tools.tar;


import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;


import org.apache.tools.zip.ZipEncoding;

/** /**
* This class represents an entry in a Tar archive. It consists * This class represents an entry in a Tar archive. It consists
* of the entry's header, as well as the entry's File. Entries * of the entry's header, as well as the entry's File. Entries
@@ -72,13 +76,44 @@ import java.util.Locale;
* char devmajor[8]; * char devmajor[8];
* char devminor[8]; * char devminor[8];
* } header; * } header;
* All unused bytes are set to null.
* New-style GNU tar files are slightly different from the above.
* For values of size larger than 077777777777L (11 7s)
* or uid and gid larger than 07777777L (7 7s)
* the sign bit of the first byte is set, and the rest of the
* field is the binary representation of the number.
* See TarUtils.parseOctalOrBinary.
* </pre>
*
* <p>
* The C structure for a old GNU Tar Entry's header is:
* <pre>
* struct oldgnu_header {
* char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0
* char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345
* char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357
* char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369
* char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381
* char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385
* struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386
* char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482
* char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483
* char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495
* };
* </pre>
* Whereas, "struct sparse" is:
* <pre>
* struct sparse {
* char offset[12]; // offset 0
* char numbytes[12]; // offset 12
* };
* </pre> * </pre>
* *
*/ */


public class TarEntry implements TarConstants { public class TarEntry implements TarConstants {
/** The entry's name. */ /** The entry's name. */
private StringBuffer name;
private String name;


/** The entry's permission mode. */ /** The entry's permission mode. */
private int mode; private int mode;
@@ -99,16 +134,18 @@ public class TarEntry implements TarConstants {
private byte linkFlag; private byte linkFlag;


/** The entry's link name. */ /** The entry's link name. */
private StringBuffer linkName;
private String linkName;


/** The entry's magic tag. */ /** The entry's magic tag. */
private StringBuffer magic;
private String magic;
/** The version of the format */
private String version;


/** The entry's user name. */ /** The entry's user name. */
private StringBuffer userName;
private String userName;


/** The entry's group name. */ /** The entry's group name. */
private StringBuffer groupName;
private String groupName;


/** The entry's major device number. */ /** The entry's major device number. */
private int devMajor; private int devMajor;
@@ -116,6 +153,12 @@ public class TarEntry implements TarConstants {
/** The entry's minor device number. */ /** The entry's minor device number. */
private int devMinor; private int devMinor;


/** If an extension sparse header follows. */
private boolean isExtended;

/** The entry's real size in case of a sparse file. */
private long realSize;

/** The entry's file reference */ /** The entry's file reference */
private File file; private File file;


@@ -134,10 +177,11 @@ public class TarEntry implements TarConstants {
/** /**
* Construct an empty entry and prepares the header values. * Construct an empty entry and prepares the header values.
*/ */
private TarEntry () {
this.magic = new StringBuffer(TMAGIC);
this.name = new StringBuffer();
this.linkName = new StringBuffer();
private TarEntry() {
this.magic = MAGIC_POSIX;
this.version = VERSION_POSIX;
this.name = "";
this.linkName = "";


String user = System.getProperty("user.name", ""); String user = System.getProperty("user.name", "");


@@ -147,8 +191,8 @@ public class TarEntry implements TarConstants {


this.userId = 0; this.userId = 0;
this.groupId = 0; this.groupId = 0;
this.userName = new StringBuffer(user);
this.groupName = new StringBuffer("");
this.userName = user;
this.groupName = "";
this.file = null; this.file = null;
} }


@@ -178,19 +222,16 @@ public class TarEntry implements TarConstants {


this.devMajor = 0; this.devMajor = 0;
this.devMinor = 0; this.devMinor = 0;
this.name = new StringBuffer(name);
this.name = name;
this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
this.linkFlag = isDir ? LF_DIR : LF_NORMAL; this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
this.userId = 0; this.userId = 0;
this.groupId = 0; this.groupId = 0;
this.size = 0; this.size = 0;
this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND; this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
this.linkName = new StringBuffer("");
this.userName = new StringBuffer("");
this.groupName = new StringBuffer("");
this.devMajor = 0;
this.devMinor = 0;

this.linkName = "";
this.userName = "";
this.groupName = "";
} }


/** /**
@@ -203,38 +244,52 @@ public class TarEntry implements TarConstants {
this(name); this(name);
this.linkFlag = linkFlag; this.linkFlag = linkFlag;
if (linkFlag == LF_GNUTYPE_LONGNAME) { if (linkFlag == LF_GNUTYPE_LONGNAME) {
magic = new StringBuffer(GNU_TMAGIC);
magic = GNU_TMAGIC;
version = VERSION_GNU_SPACE;
} }
} }


/** /**
* Construct an entry for a file. File is set to file, and the * Construct an entry for a file. File is set to file, and the
* header is constructed from information from the file. * header is constructed from information from the file.
* The name is set from the normalized file path.
* *
* @param file The file that the entry represents. * @param file The file that the entry represents.
*/ */
public TarEntry(File file) { public TarEntry(File file) {
this(file, normalizeFileName(file.getPath(), false));
}

/**
* Construct an entry for a file. File is set to file, and the
* header is constructed from information from the file.
*
* @param file The file that the entry represents.
* @param fileName the name to be used for the entry.
*/
public TarEntry(File file, String fileName) {
this(); this();


this.file = file; this.file = file;


String fileName = normalizeFileName(file.getPath(), false);
this.linkName = new StringBuffer("");
this.name = new StringBuffer(fileName);
this.linkName = "";


if (file.isDirectory()) { if (file.isDirectory()) {
this.mode = DEFAULT_DIR_MODE; this.mode = DEFAULT_DIR_MODE;
this.linkFlag = LF_DIR; this.linkFlag = LF_DIR;


int nameLength = name.length();
if (nameLength == 0 || name.charAt(nameLength - 1) != '/') {
this.name.append("/");
int nameLength = fileName.length();
if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
this.name = fileName + "/";
} else {
this.name = fileName;
} }
this.size = 0; this.size = 0;
} else { } else {
this.mode = DEFAULT_FILE_MODE; this.mode = DEFAULT_FILE_MODE;
this.linkFlag = LF_NORMAL; this.linkFlag = LF_NORMAL;
this.size = file.length(); this.size = file.length();
this.name = fileName;
} }


this.modTime = file.lastModified() / MILLIS_PER_SECOND; this.modTime = file.lastModified() / MILLIS_PER_SECOND;
@@ -247,12 +302,27 @@ public class TarEntry implements TarConstants {
* to null. * to null.
* *
* @param headerBuf The header bytes from a tar archive entry. * @param headerBuf The header bytes from a tar archive entry.
* @throws IllegalArgumentException if any of the numeric fields have an invalid format
*/ */
public TarEntry(byte[] headerBuf) { public TarEntry(byte[] headerBuf) {
this(); this();
parseTarHeader(headerBuf); parseTarHeader(headerBuf);
} }


/**
* Construct an entry from an archive's header bytes. File is set
* to null.
*
* @param headerBuf The header bytes from a tar archive entry.
* @param encoding encoding to use for file names
* @throws IllegalArgumentException if any of the numeric fields have an invalid format
*/
public TarEntry(byte[] headerBuf, ZipEncoding encoding)
throws IOException {
this();
parseTarHeader(headerBuf, encoding);
}

/** /**
* Determine if the two entries are equal. Equality is determined * Determine if the two entries are equal. Equality is determined
* by the header names being equal. * by the header names being equal.
@@ -271,6 +341,7 @@ public class TarEntry implements TarConstants {
* @param it Entry to be checked for equality. * @param it Entry to be checked for equality.
* @return True if the entries are equal. * @return True if the entries are equal.
*/ */
@Override
public boolean equals(Object it) { public boolean equals(Object it) {
if (it == null || getClass() != it.getClass()) { if (it == null || getClass() != it.getClass()) {
return false; return false;
@@ -283,6 +354,7 @@ public class TarEntry implements TarConstants {
* *
* @return the entry hashcode * @return the entry hashcode
*/ */
@Override
public int hashCode() { public int hashCode() {
return getName().hashCode(); return getName().hashCode();
} }
@@ -314,7 +386,7 @@ public class TarEntry implements TarConstants {
* @param name This entry's new name. * @param name This entry's new name.
*/ */
public void setName(String name) { public void setName(String name) {
this.name = new StringBuffer(normalizeFileName(name, false));
this.name = normalizeFileName(name, false);
} }


/** /**
@@ -335,6 +407,15 @@ public class TarEntry implements TarConstants {
return linkName.toString(); return linkName.toString();
} }


/**
* Set this entry's link name.
*
* @param link the link name to use.
*/
public void setLinkName(String link) {
this.linkName = link;
}

/** /**
* Get this entry's user id. * Get this entry's user id.
* *
@@ -386,7 +467,7 @@ public class TarEntry implements TarConstants {
* @param userName This entry's new user name. * @param userName This entry's new user name.
*/ */
public void setUserName(String userName) { public void setUserName(String userName) {
this.userName = new StringBuffer(userName);
this.userName = userName;
} }


/** /**
@@ -404,7 +485,7 @@ public class TarEntry implements TarConstants {
* @param groupName This entry's new group name. * @param groupName This entry's new group name.
*/ */
public void setGroupName(String groupName) { public void setGroupName(String groupName) {
this.groupName = new StringBuffer(groupName);
this.groupName = groupName;
} }


/** /**
@@ -488,11 +569,88 @@ public class TarEntry implements TarConstants {
* Set this entry's file size. * Set this entry's file size.
* *
* @param size This entry's new file size. * @param size This entry's new file size.
* @throws IllegalArgumentException if the size is &lt; 0.
*/ */
public void setSize(long size) { public void setSize(long size) {
if (size < 0){
throw new IllegalArgumentException("Size is out of range: "+size);
}
this.size = size; this.size = size;
} }


/**
* Get this entry's major device number.
*
* @return This entry's major device number.
*/
public int getDevMajor() {
return devMajor;
}

/**
* Set this entry's major device number.
*
* @param devNo This entry's major device number.
* @throws IllegalArgumentException if the devNo is &lt; 0.
*/
public void setDevMajor(int devNo) {
if (devNo < 0){
throw new IllegalArgumentException("Major device number is out of "
+ "range: " + devNo);
}
this.devMajor = devNo;
}

/**
* Get this entry's minor device number.
*
* @return This entry's minor device number.
*/
public int getDevMinor() {
return devMinor;
}

/**
* Set this entry's minor device number.
*
* @param devNo This entry's minor device number.
* @throws IllegalArgumentException if the devNo is &lt; 0.
*/
public void setDevMinor(int devNo) {
if (devNo < 0){
throw new IllegalArgumentException("Minor device number is out of "
+ "range: " + devNo);
}
this.devMinor = devNo;
}

/**
* Indicates in case of a sparse file if an extension sparse header
* follows.
*
* @return true if an extension sparse header follows.
*/
public boolean isExtended() {
return isExtended;
}

/**
* Get this entry's real file size in case of a sparse file.
*
* @return This entry's real file size.
*/
public long getRealSize() {
return realSize;
}

/**
* Indicate if this entry is a GNU sparse block
*
* @return true if this is a sparse extension provided by GNU tar
*/
public boolean isGNUSparse() {
return linkFlag == LF_GNUTYPE_SPARSE;
}


/** /**
* Indicate if this entry is a GNU long name block * Indicate if this entry is a GNU long name block
@@ -501,7 +659,26 @@ public class TarEntry implements TarConstants {
*/ */
public boolean isGNULongNameEntry() { public boolean isGNULongNameEntry() {
return linkFlag == LF_GNUTYPE_LONGNAME return linkFlag == LF_GNUTYPE_LONGNAME
&& name.toString().equals(GNU_LONGLINK);
&& name.equals(GNU_LONGLINK);
}

/**
* Check if this is a Pax header.
*
* @return {@code true} if this is a Pax header.
*/
public boolean isPaxHeader(){
return linkFlag == LF_PAX_EXTENDED_HEADER_LC
|| linkFlag == LF_PAX_EXTENDED_HEADER_UC;
}

/**
* Check if this is a Pax header.
*
* @return {@code true} if this is a Pax header.
*/
public boolean isGlobalPaxHeader(){
return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
} }


/** /**
@@ -525,6 +702,54 @@ public class TarEntry implements TarConstants {
return false; return false;
} }


/**
* Check if this is a "normal file"
*/
public boolean isFile() {
if (file != null) {
return file.isFile();
}
if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
return true;
}
return !getName().endsWith("/");
}

/**
* Check if this is a symbolic link entry.
*/
public boolean isSymbolicLink() {
return linkFlag == LF_SYMLINK;
}

/**
* Check if this is a link entry.
*/
public boolean isLink() {
return linkFlag == LF_LINK;
}

/**
* Check if this is a character device entry.
*/
public boolean isCharacterDevice() {
return linkFlag == LF_CHR;
}

/**
* Check if this is a block device entry.
*/
public boolean isBlockDevice() {
return linkFlag == LF_BLK;
}

/**
* Check if this is a FIFO (pipe) entry.
*/
public boolean isFIFO() {
return linkFlag == LF_FIFO;
}

/** /**
* If this entry represents a file, and the file is a directory, return * If this entry represents a file, and the file is a directory, return
* an array of TarEntries for this entry's children. * an array of TarEntries for this entry's children.
@@ -549,17 +774,46 @@ public class TarEntry implements TarConstants {
/** /**
* Write an entry's header information to a header buffer. * Write an entry's header information to a header buffer.
* *
* <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
*
* @param outbuf The tar entry header buffer to fill in. * @param outbuf The tar entry header buffer to fill in.
*/ */
public void writeEntryHeader(byte[] outbuf) { public void writeEntryHeader(byte[] outbuf) {
try {
writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
} catch (IOException ex) {
try {
writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
} catch (IOException ex2) {
// impossible
throw new RuntimeException(ex2);
}
}
}

/**
* Write an entry's header information to a header buffer.
*
* @param outbuf The tar entry header buffer to fill in.
* @param encoding encoding to use when writing the file name.
* @param starMode whether to use the star/GNU tar/BSD tar
* extension for numeric fields if their value doesn't fit in the
* maximum size of standard tar archives
*/
public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding,
boolean starMode) throws IOException {
int offset = 0; int offset = 0;


offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);
offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);
offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);
offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);
offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);
offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
encoding);
offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
starMode);
offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
starMode);
offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
starMode);


int csOffset = offset; int csOffset = offset;


@@ -568,12 +822,18 @@ public class TarEntry implements TarConstants {
} }


outbuf[offset++] = linkFlag; outbuf[offset++] = linkFlag;
offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);
offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);
offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);
offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);
offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);
offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);
offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
encoding);
offset = TarUtils.formatNameBytes(magic, outbuf, offset, PURE_MAGICLEN);
offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
encoding);
offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
encoding);
offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
starMode);
offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
starMode);


while (offset < outbuf.length) { while (offset < outbuf.length) {
outbuf[offset++] = 0; outbuf[offset++] = 0;
@@ -581,42 +841,122 @@ public class TarEntry implements TarConstants {


long chk = TarUtils.computeCheckSum(outbuf); long chk = TarUtils.computeCheckSum(outbuf);


TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
}

private int writeEntryHeaderField(long value, byte[] outbuf, int offset,
int length, boolean starMode) {
if (!starMode && (value < 0
|| value >= (1l << (3 * (length - 1))))) {
// value doesn't fit into field when written as octal
// number, will be written to PAX header or causes an
// error
return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
}
return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
length);
} }


/** /**
* Parse an entry's header information from a header buffer. * Parse an entry's header information from a header buffer.
* *
* @param header The tar entry header buffer to get information from. * @param header The tar entry header buffer to get information from.
* @throws IllegalArgumentException if any of the numeric fields have an invalid format
*/ */
public void parseTarHeader(byte[] header) { public void parseTarHeader(byte[] header) {
try {
parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
} catch (IOException ex) {
try {
parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
} catch (IOException ex2) {
// not really possible
throw new RuntimeException(ex2);
}
}
}

/**
* Parse an entry's header information from a header buffer.
*
* @param header The tar entry header buffer to get information from.
* @param encoding encoding to use for file names
* @throws IllegalArgumentException if any of the numeric fields
* have an invalid format
*/
public void parseTarHeader(byte[] header, ZipEncoding encoding)
throws IOException {
parseTarHeader(header, encoding, false);
}

private void parseTarHeader(byte[] header, ZipEncoding encoding,
final boolean oldStyle)
throws IOException {
int offset = 0; int offset = 0;


name = TarUtils.parseName(header, offset, NAMELEN);
name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
: TarUtils.parseName(header, offset, NAMELEN, encoding);
offset += NAMELEN; offset += NAMELEN;
mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
offset += MODELEN; offset += MODELEN;
userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
offset += UIDLEN; offset += UIDLEN;
groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
offset += GIDLEN; offset += GIDLEN;
size = TarUtils.parseOctal(header, offset, SIZELEN);
size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
offset += SIZELEN; offset += SIZELEN;
modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
offset += MODTIMELEN; offset += MODTIMELEN;
offset += CHKSUMLEN; offset += CHKSUMLEN;
linkFlag = header[offset++]; linkFlag = header[offset++];
linkName = TarUtils.parseName(header, offset, NAMELEN);
linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
: TarUtils.parseName(header, offset, NAMELEN, encoding);
offset += NAMELEN; offset += NAMELEN;
magic = TarUtils.parseName(header, offset, MAGICLEN);
offset += MAGICLEN;
userName = TarUtils.parseName(header, offset, UNAMELEN);
magic = TarUtils.parseName(header, offset, PURE_MAGICLEN);
offset += PURE_MAGICLEN;
version = TarUtils.parseName(header, offset, VERSIONLEN);
offset += VERSIONLEN;
userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
: TarUtils.parseName(header, offset, UNAMELEN, encoding);
offset += UNAMELEN; offset += UNAMELEN;
groupName = TarUtils.parseName(header, offset, GNAMELEN);
groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
: TarUtils.parseName(header, offset, GNAMELEN, encoding);
offset += GNAMELEN; offset += GNAMELEN;
devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
offset += DEVLEN; offset += DEVLEN;
devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
offset += DEVLEN;

int type = evaluateType(header);
switch (type) {
case FORMAT_OLDGNU: {
offset += ATIMELEN_GNU;
offset += CTIMELEN_GNU;
offset += OFFSETLEN_GNU;
offset += LONGNAMESLEN_GNU;
offset += PAD2LEN_GNU;
offset += SPARSELEN_GNU;
isExtended = TarUtils.parseBoolean(header, offset);
offset += ISEXTENDEDLEN_GNU;
realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
offset += REALSIZELEN_GNU;
break;
}
case FORMAT_POSIX:
default: {
String prefix = oldStyle
? TarUtils.parseName(header, offset, PREFIXLEN)
: TarUtils.parseName(header, offset, PREFIXLEN, encoding);
// SunOS tar -E does not add / to directory names, so fix
// up to be consistent
if (isDirectory() && !name.endsWith("/")){
name = name + "/";
}
if (prefix.length() > 0){
name = prefix + "/" + name;
}
}
}
} }


/** /**
@@ -661,4 +1001,85 @@ public class TarEntry implements TarConstants {
} }
return fileName; return fileName;
} }

/**
* Evaluate an entry's header format from a header buffer.
*
* @param header The tar entry header buffer to evaluate the format for.
* @return format type
*/
private int evaluateType(byte[] header) {
if (matchAsciiBuffer(GNU_TMAGIC, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
return FORMAT_OLDGNU;
}
if (matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
return FORMAT_POSIX;
}
return 0;
}

/**
* Check if buffer contents matches Ascii String.
*
* @param expected
* @param buffer
* @param offset
* @param length
* @return {@code true} if buffer is the same as the expected string
*/
private static boolean matchAsciiBuffer(String expected, byte[] buffer,
int offset, int length){
byte[] buffer1;
try {
buffer1 = expected.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Should not happen
}
return isEqual(buffer1, 0, buffer1.length, buffer, offset, length,
false);
}

/**
* Compare byte buffers, optionally ignoring trailing nulls
*
* @param buffer1
* @param offset1
* @param length1
* @param buffer2
* @param offset2
* @param length2
* @param ignoreTrailingNulls
* @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
*/
private static boolean isEqual(
final byte[] buffer1, final int offset1, final int length1,
final byte[] buffer2, final int offset2, final int length2,
boolean ignoreTrailingNulls){
int minLen=length1 < length2 ? length1 : length2;
for (int i=0; i < minLen; i++){
if (buffer1[offset1+i] != buffer2[offset2+i]){
return false;
}
}
if (length1 == length2){
return true;
}
if (ignoreTrailingNulls){
if (length1 > length2){
for(int i = length2; i < length1; i++){
if (buffer1[offset1+i] != 0){
return false;
}
}
} else {
for(int i = length1; i < length2; i++){
if (buffer2[offset2+i] != 0){
return false;
}
}
}
return true;
}
return false;
}
} }

+ 246
- 28
src/main/org/apache/tools/tar/TarInputStream.java View File

@@ -23,10 +23,17 @@


package org.apache.tools.tar; package org.apache.tools.tar;


import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.tools.zip.ZipEncoding;
import org.apache.tools.zip.ZipEncodingHelper;


/** /**
* The TarInputStream reads a UNIX tar archive as an InputStream. * The TarInputStream reads a UNIX tar archive as an InputStream.
@@ -59,6 +66,8 @@ public class TarInputStream extends FilterInputStream {


// CheckStyle:VisibilityModifier ON // CheckStyle:VisibilityModifier ON


private final ZipEncoding encoding;

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param is the input stream to use * @param is the input stream to use
@@ -67,6 +76,15 @@ public class TarInputStream extends FilterInputStream {
this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
} }


/**
* Constructor for TarInputStream.
* @param is the input stream to use
* @param encoding name of the encoding to use for file names
*/
public TarInputStream(InputStream is, String encoding) {
this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
}

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param is the input stream to use * @param is the input stream to use
@@ -76,6 +94,16 @@ public class TarInputStream extends FilterInputStream {
this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE); this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
} }


/**
* Constructor for TarInputStream.
* @param is the input stream to use
* @param blockSize the block size to use
* @param encoding name of the encoding to use for file names
*/
public TarInputStream(InputStream is, int blockSize, String encoding) {
this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
}

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param is the input stream to use * @param is the input stream to use
@@ -83,13 +111,25 @@ public class TarInputStream extends FilterInputStream {
* @param recordSize the record size to use * @param recordSize the record size to use
*/ */
public TarInputStream(InputStream is, int blockSize, int recordSize) { public TarInputStream(InputStream is, int blockSize, int recordSize) {
super(is);
this(is, blockSize, recordSize, null);
}


/**
* Constructor for TarInputStream.
* @param is the input stream to use
* @param blockSize the block size to use
* @param recordSize the record size to use
* @param encoding name of the encoding to use for file names
*/
public TarInputStream(InputStream is, int blockSize, int recordSize,
String encoding) {
super(is);
this.buffer = new TarBuffer(is, blockSize, recordSize); this.buffer = new TarBuffer(is, blockSize, recordSize);
this.readBuf = null; this.readBuf = null;
this.oneBuf = new byte[1]; this.oneBuf = new byte[1];
this.debug = false; this.debug = false;
this.hasHitEOF = false; this.hasHitEOF = false;
this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
} }


/** /**
@@ -106,6 +146,7 @@ public class TarInputStream extends FilterInputStream {
* Closes this stream. Calls the TarBuffer's close() method. * Closes this stream. Calls the TarBuffer's close() method.
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public void close() throws IOException { public void close() throws IOException {
buffer.close(); buffer.close();
} }
@@ -131,6 +172,7 @@ public class TarInputStream extends FilterInputStream {
* @return The number of available bytes for the current entry. * @return The number of available bytes for the current entry.
* @throws IOException for signature * @throws IOException for signature
*/ */
@Override
public int available() throws IOException { public int available() throws IOException {
if (entrySize - entryOffset > Integer.MAX_VALUE) { if (entrySize - entryOffset > Integer.MAX_VALUE) {
return Integer.MAX_VALUE; return Integer.MAX_VALUE;
@@ -148,6 +190,7 @@ public class TarInputStream extends FilterInputStream {
* @return the number actually skipped * @return the number actually skipped
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public long skip(long numToSkip) throws IOException { public long skip(long numToSkip) throws IOException {
// REVIEW // REVIEW
// This is horribly inefficient, but it ensures that we // This is horribly inefficient, but it ensures that we
@@ -171,6 +214,7 @@ public class TarInputStream extends FilterInputStream {
* *
* @return False. * @return False.
*/ */
@Override
public boolean markSupported() { public boolean markSupported() {
return false; return false;
} }
@@ -180,12 +224,14 @@ public class TarInputStream extends FilterInputStream {
* *
* @param markLimit The limit to mark. * @param markLimit The limit to mark.
*/ */
@Override
public void mark(int markLimit) { public void mark(int markLimit) {
} }


/** /**
* Since we do not support marking just yet, we do nothing. * Since we do not support marking just yet, we do nothing.
*/ */
@Override
public void reset() { public void reset() {
} }


@@ -230,44 +276,37 @@ public class TarInputStream extends FilterInputStream {
readBuf = null; readBuf = null;
} }


byte[] headerBuf = buffer.readRecord();

if (headerBuf == null) {
if (debug) {
System.err.println("READ NULL RECORD");
}
hasHitEOF = true;
} else if (buffer.isEOFRecord(headerBuf)) {
if (debug) {
System.err.println("READ EOF RECORD");
}
hasHitEOF = true;
}
byte[] headerBuf = getRecord();


if (hasHitEOF) { if (hasHitEOF) {
currEntry = null; currEntry = null;
} else {
currEntry = new TarEntry(headerBuf);

if (debug) {
System.err.println("TarInputStream: SET CURRENTRY '"
+ currEntry.getName()
+ "' size = "
+ currEntry.getSize());
}

entryOffset = 0;
return null;
}


entrySize = currEntry.getSize();
try {
currEntry = new TarEntry(headerBuf, encoding);
} catch (IllegalArgumentException e) {
IOException ioe = new IOException("Error detected parsing the header");
ioe.initCause(e);
throw ioe;
} }
if (debug) {
System.err.println("TarInputStream: SET CURRENTRY '"
+ currEntry.getName()
+ "' size = "
+ currEntry.getSize());
}

entryOffset = 0;
entrySize = currEntry.getSize();


if (currEntry != null && currEntry.isGNULongNameEntry()) {
if (currEntry.isGNULongNameEntry()) {
// read in the name // read in the name
StringBuffer longName = new StringBuffer(); StringBuffer longName = new StringBuffer();
byte[] buf = new byte[SMALL_BUFFER_SIZE]; byte[] buf = new byte[SMALL_BUFFER_SIZE];
int length = 0; int length = 0;
while ((length = read(buf)) >= 0) { while ((length = read(buf)) >= 0) {
longName.append(new String(buf, 0, length));
longName.append(new String(buf, 0, length)); // TODO default charset?
} }
getNextEntry(); getNextEntry();
if (currEntry == null) { if (currEntry == null) {
@@ -283,9 +322,176 @@ public class TarInputStream extends FilterInputStream {
currEntry.setName(longName.toString()); currEntry.setName(longName.toString());
} }


if (currEntry.isPaxHeader()){ // Process Pax headers
paxHeaders();
}

if (currEntry.isGNUSparse()){ // Process sparse files
readGNUSparse();
}

// If the size of the next element in the archive has changed
// due to a new size being reported in the posix header
// information, we update entrySize here so that it contains
// the correct value.
entrySize = currEntry.getSize();
return currEntry; return currEntry;
} }


/**
* Get the next record in this tar archive. This will skip
* over any remaining data in the current entry, if there
* is one, and place the input stream at the header of the
* next entry.
* If there are no more entries in the archive, null will
* be returned to indicate that the end of the archive has
* been reached.
*
* @return The next header in the archive, or null.
* @throws IOException on error
*/
private byte[] getRecord() throws IOException {
if (hasHitEOF) {
return null;
}

byte[] headerBuf = buffer.readRecord();

if (headerBuf == null) {
if (debug) {
System.err.println("READ NULL RECORD");
}
hasHitEOF = true;
} else if (buffer.isEOFRecord(headerBuf)) {
if (debug) {
System.err.println("READ EOF RECORD");
}
hasHitEOF = true;
}

return hasHitEOF ? null : headerBuf;
}

private void paxHeaders() throws IOException{
Map<String, String> headers = parsePaxHeaders(this);
getNextEntry(); // Get the actual file entry
applyPaxHeadersToCurrentEntry(headers);
}

Map<String, String> parsePaxHeaders(InputStream i) throws IOException {
Map<String, String> headers = new HashMap<String, String>();
// Format is "length keyword=value\n";
while(true){ // get length
int ch;
int len = 0;
int read = 0;
while((ch = i.read()) != -1) {
read++;
if (ch == ' '){ // End of length string
// Get keyword
ByteArrayOutputStream coll = new ByteArrayOutputStream();
while((ch = i.read()) != -1) {
read++;
if (ch == '='){ // end of keyword
String keyword = coll.toString("UTF-8");
// Get rest of entry
byte[] rest = new byte[len - read];
int got = i.read(rest);
if (got != len - read){
throw new IOException("Failed to read "
+ "Paxheader. Expected "
+ (len - read)
+ " bytes, read "
+ got);
}
// Drop trailing NL
String value = new String(rest, 0,
len - read - 1, "UTF-8");
headers.put(keyword, value);
break;
}
coll.write((byte) ch);
}
break; // Processed single header
}
len *= 10;
len += ch - '0';
}
if (ch == -1){ // EOF
break;
}
}
return headers;
}

private void applyPaxHeadersToCurrentEntry(Map<String, String> headers) {
/*
* The following headers are defined for Pax.
* atime, ctime, charset: cannot use these without changing TarEntry fields
* mtime
* comment
* gid, gname
* linkpath
* size
* uid,uname
* SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
*/
for (Entry<String, String> ent : headers.entrySet()){
String key = ent.getKey();
String val = ent.getValue();
if ("path".equals(key)){
currEntry.setName(val);
} else if ("linkpath".equals(key)){
currEntry.setLinkName(val);
} else if ("gid".equals(key)){
currEntry.setGroupId(Integer.parseInt(val));
} else if ("gname".equals(key)){
currEntry.setGroupName(val);
} else if ("uid".equals(key)){
currEntry.setUserId(Integer.parseInt(val));
} else if ("uname".equals(key)){
currEntry.setUserName(val);
} else if ("size".equals(key)){
currEntry.setSize(Long.parseLong(val));
} else if ("mtime".equals(key)){
currEntry.setModTime((long) (Double.parseDouble(val) * 1000));
} else if ("SCHILY.devminor".equals(key)){
currEntry.setDevMinor(Integer.parseInt(val));
} else if ("SCHILY.devmajor".equals(key)){
currEntry.setDevMajor(Integer.parseInt(val));
}
}
}

/**
* Adds the sparse chunks from the current entry to the sparse chunks,
* including any additional sparse entries following the current entry.
*
* @throws IOException on error
*
* @todo Sparse files get not yet really processed.
*/
private void readGNUSparse() throws IOException {
/* we do not really process sparse files yet
sparses = new ArrayList();
sparses.addAll(currEntry.getSparses());
*/
if (currEntry.isExtended()) {
TarArchiveSparseEntry entry;
do {
byte[] headerBuf = getRecord();
if (hasHitEOF) {
currEntry = null;
break;
}
entry = new TarArchiveSparseEntry(headerBuf);
/* we do not really process sparse files yet
sparses.addAll(entry.getSparses());
*/
} while (entry.isExtended());
}
}

/** /**
* Reads a byte from the current tar archive entry. * Reads a byte from the current tar archive entry.
* *
@@ -294,6 +500,7 @@ public class TarInputStream extends FilterInputStream {
* @return The byte read, or -1 at EOF. * @return The byte read, or -1 at EOF.
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public int read() throws IOException { public int read() throws IOException {
int num = read(oneBuf, 0, 1); int num = read(oneBuf, 0, 1);
return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK; return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
@@ -312,6 +519,7 @@ public class TarInputStream extends FilterInputStream {
* @return The number of bytes read, or -1 at EOF. * @return The number of bytes read, or -1 at EOF.
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public int read(byte[] buf, int offset, int numToRead) throws IOException { public int read(byte[] buf, int offset, int numToRead) throws IOException {
int totalRead = 0; int totalRead = 0;


@@ -399,4 +607,14 @@ public class TarInputStream extends FilterInputStream {
out.write(buf, 0, numRead); out.write(buf, 0, numRead);
} }
} }

/**
* Whether this class is able to read the given entry.
*
* <p>May return false if the current entry is a sparse file.</p>
*/
public boolean canReadEntryData(TarEntry te) {
return !te.isGNUSparse();
}
} }


+ 253
- 17
src/main/org/apache/tools/tar/TarOutputStream.java View File

@@ -23,9 +23,15 @@


package org.apache.tools.tar; package org.apache.tools.tar;


import java.io.File;
import java.io.FilterOutputStream; import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.tools.zip.ZipEncoding;
import org.apache.tools.zip.ZipEncodingHelper;


/** /**
* The TarOutputStream writes a UNIX tar archive as an OutputStream. * The TarOutputStream writes a UNIX tar archive as an OutputStream.
@@ -43,6 +49,18 @@ public class TarOutputStream extends FilterOutputStream {
/** GNU tar extensions are used to store long file names in the archive. */ /** GNU tar extensions are used to store long file names in the archive. */
public static final int LONGFILE_GNU = 2; public static final int LONGFILE_GNU = 2;


/** POSIX/PAX extensions are used to store long file names in the archive. */
public static final int LONGFILE_POSIX = 3;

/** Fail if a big number (e.g. size &gt; 8GiB) is required in the archive. */
public static final int BIGNUMBER_ERROR = 0;

/** star/GNU tar/BSD tar extensions are used to store big number in the archive. */
public static final int BIGNUMBER_STAR = 1;

/** POSIX/PAX extensions are used to store big numbers in the archive. */
public static final int BIGNUMBER_POSIX = 2;

// CheckStyle:VisibilityModifier OFF - bc // CheckStyle:VisibilityModifier OFF - bc
protected boolean debug; protected boolean debug;
protected long currSize; protected long currSize;
@@ -56,8 +74,22 @@ public class TarOutputStream extends FilterOutputStream {
protected int longFileMode = LONGFILE_ERROR; protected int longFileMode = LONGFILE_ERROR;
// CheckStyle:VisibilityModifier ON // CheckStyle:VisibilityModifier ON


private int bigNumberMode = BIGNUMBER_ERROR;

private boolean closed = false; private boolean closed = false;


/** Indicates if putNextEntry has been called without closeEntry */
private boolean haveUnclosedEntry = false;

/** indicates if this archive is finished */
private boolean finished = false;

private final ZipEncoding encoding;

private boolean addPaxHeadersForNonAsciiNames = false;
private static final ZipEncoding ASCII =
ZipEncodingHelper.getZipEncoding("ASCII");

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param os the output stream to use * @param os the output stream to use
@@ -66,6 +98,15 @@ public class TarOutputStream extends FilterOutputStream {
this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
} }


/**
* Constructor for TarInputStream.
* @param os the output stream to use
* @param encoding name of the encoding to use for file names
*/
public TarOutputStream(OutputStream os, String encoding) {
this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
}

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param os the output stream to use * @param os the output stream to use
@@ -75,6 +116,16 @@ public class TarOutputStream extends FilterOutputStream {
this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE); this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
} }


/**
* Constructor for TarInputStream.
* @param os the output stream to use
* @param blockSize the block size to use
* @param encoding name of the encoding to use for file names
*/
public TarOutputStream(OutputStream os, int blockSize, String encoding) {
this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
}

/** /**
* Constructor for TarInputStream. * Constructor for TarInputStream.
* @param os the output stream to use * @param os the output stream to use
@@ -82,7 +133,20 @@ public class TarOutputStream extends FilterOutputStream {
* @param recordSize the record size to use * @param recordSize the record size to use
*/ */
public TarOutputStream(OutputStream os, int blockSize, int recordSize) { public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
this(os, blockSize, recordSize, null);
}

/**
* Constructor for TarInputStream.
* @param os the output stream to use
* @param blockSize the block size to use
* @param recordSize the record size to use
* @param encoding name of the encoding to use for file names
*/
public TarOutputStream(OutputStream os, int blockSize, int recordSize,
String encoding) {
super(os); super(os);
this.encoding = ZipEncodingHelper.getZipEncoding(encoding);


this.buffer = new TarBuffer(os, blockSize, recordSize); this.buffer = new TarBuffer(os, blockSize, recordSize);
this.debug = false; this.debug = false;
@@ -103,6 +167,23 @@ public class TarOutputStream extends FilterOutputStream {
this.longFileMode = longFileMode; this.longFileMode = longFileMode;
} }


/**
* Set the big number mode.
* This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or BIGNUMBER_STAR(2).
* This specifies the treatment of big files (sizes &gt; TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header.
* Default is BIGNUMBER_ERROR.
* @param bigNumberMode the mode to use
*/
public void setBigNumberMode(int bigNumberMode) {
this.bigNumberMode = bigNumberMode;
}

/**
* Whether to add a PAX extension header for non-ASCII file names.
*/
public void setAddPaxHeadersForNonAsciiNames(boolean b) {
addPaxHeadersForNonAsciiNames = b;
}


/** /**
* Sets the debugging flag. * Sets the debugging flag.
@@ -124,15 +205,25 @@ public class TarOutputStream extends FilterOutputStream {


/** /**
* Ends the TAR archive without closing the underlying OutputStream. * Ends the TAR archive without closing the underlying OutputStream.
* The result is that the two EOF records of nulls are written.
*
* An archive consists of a series of file entries terminated by an
* end-of-archive entry, which consists of two 512 blocks of zero bytes.
* POSIX.1 requires two EOF records, like some other implementations.
*
* @throws IOException on error * @throws IOException on error
*/ */
public void finish() throws IOException { public void finish() throws IOException {
// See Bugzilla 28776 for a discussion on this
// http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
if (finished) {
throw new IOException("This archive has already been finished");
}

if (haveUnclosedEntry) {
throw new IOException("This archives contains unclosed entries.");
}
writeEOFRecord(); writeEOFRecord();
writeEOFRecord(); writeEOFRecord();
buffer.flushBlock(); buffer.flushBlock();
finished = true;
} }


/** /**
@@ -141,9 +232,13 @@ public class TarOutputStream extends FilterOutputStream {
* TarBuffer's close(). * TarBuffer's close().
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public void close() throws IOException { public void close() throws IOException {
if (!closed) {
if(!finished) {
finish(); finish();
}

if (!closed) {
buffer.close(); buffer.close();
out.close(); out.close();
closed = true; closed = true;
@@ -172,27 +267,59 @@ public class TarOutputStream extends FilterOutputStream {
* @throws IOException on error * @throws IOException on error
*/ */
public void putNextEntry(TarEntry entry) throws IOException { public void putNextEntry(TarEntry entry) throws IOException {
if (entry.getName().length() >= TarConstants.NAMELEN) {

if (longFileMode == LONGFILE_GNU) {
if(finished) {
throw new IOException("Stream has already been finished");
}
Map<String, String> paxHeaders = new HashMap<String, String>();
final String entryName = entry.getName();
final byte[] nameBytes = encoding.encode(entryName).array();
boolean paxHeaderContainsPath = false;
if (nameBytes.length >= TarConstants.NAMELEN) {

if (longFileMode == LONGFILE_POSIX) {
paxHeaders.put("path", entryName);
paxHeaderContainsPath = true;
} else if (longFileMode == LONGFILE_GNU) {
// create a TarEntry for the LongLink, the contents // create a TarEntry for the LongLink, the contents
// of which are the entry's name // of which are the entry's name
TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK, TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
TarConstants.LF_GNUTYPE_LONGNAME); TarConstants.LF_GNUTYPE_LONGNAME);


longLinkEntry.setSize(entry.getName().length() + 1);
longLinkEntry.setSize(nameBytes.length + 1); // +1 for NUL
putNextEntry(longLinkEntry); putNextEntry(longLinkEntry);
write(entry.getName().getBytes());
write(0);
write(nameBytes);
write(0); // NUL terminator
closeEntry(); closeEntry();
} else if (longFileMode != LONGFILE_TRUNCATE) { } else if (longFileMode != LONGFILE_TRUNCATE) {
throw new RuntimeException("file name '" + entry.getName()
+ "' is too long ( > "
+ TarConstants.NAMELEN + " bytes)");
throw new RuntimeException("file name '" + entryName
+ "' is too long ( > "
+ TarConstants.NAMELEN + " bytes)");
} }
} }


entry.writeEntryHeader(recordBuf);
if (bigNumberMode == BIGNUMBER_POSIX) {
addPaxHeadersForBigNumbers(paxHeaders, entry);
} else if (bigNumberMode != BIGNUMBER_STAR) {
failForBigNumbers(entry);
}

if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
&& !ASCII.canEncode(entryName)) {
paxHeaders.put("path", entryName);
}

if (addPaxHeadersForNonAsciiNames
&& (entry.isLink() || entry.isSymbolicLink())
&& !ASCII.canEncode(entry.getLinkName())) {
paxHeaders.put("linkpath", entry.getLinkName());
}

if (paxHeaders.size() > 0) {
writePaxHeaders(entryName, paxHeaders);
}

entry.writeEntryHeader(recordBuf, encoding,
bigNumberMode == BIGNUMBER_STAR);
buffer.writeRecord(recordBuf); buffer.writeRecord(recordBuf);


currBytes = 0; currBytes = 0;
@@ -202,7 +329,8 @@ public class TarOutputStream extends FilterOutputStream {
} else { } else {
currSize = entry.getSize(); currSize = entry.getSize();
} }
currName = entry.getName();
currName = entryName;
haveUnclosedEntry = true;
} }


/** /**
@@ -216,6 +344,12 @@ public class TarOutputStream extends FilterOutputStream {
* @throws IOException on error * @throws IOException on error
*/ */
public void closeEntry() throws IOException { public void closeEntry() throws IOException {
if (finished) {
throw new IOException("Stream has already been finished");
}
if (!haveUnclosedEntry){
throw new IOException("No current entry to close");
}
if (assemLen > 0) { if (assemLen > 0) {
for (int i = assemLen; i < assemBuf.length; ++i) { for (int i = assemLen; i < assemBuf.length; ++i) {
assemBuf[i] = 0; assemBuf[i] = 0;
@@ -233,6 +367,7 @@ public class TarOutputStream extends FilterOutputStream {
+ "' before the '" + currSize + "' before the '" + currSize
+ "' bytes specified in the header were written"); + "' bytes specified in the header were written");
} }
haveUnclosedEntry = false;
} }


/** /**
@@ -243,6 +378,7 @@ public class TarOutputStream extends FilterOutputStream {
* @param b The byte written. * @param b The byte written.
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
oneBuf[0] = (byte) b; oneBuf[0] = (byte) b;


@@ -275,6 +411,7 @@ public class TarOutputStream extends FilterOutputStream {
* @param numToWrite The number of bytes to write. * @param numToWrite The number of bytes to write.
* @throws IOException on error * @throws IOException on error
*/ */
@Override
public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException { public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
if ((currBytes + numToWrite) > currSize) { if ((currBytes + numToWrite) > currSize) {
throw new IOException("request to write '" + numToWrite throw new IOException("request to write '" + numToWrite
@@ -340,6 +477,58 @@ public class TarOutputStream extends FilterOutputStream {
} }
} }


/**
* Writes a PAX extended header with the given map as contents.
*/
void writePaxHeaders(String entryName,
Map<String, String> headers) throws IOException {
String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
if (name.length() >= TarConstants.NAMELEN) {
name = name.substring(0, TarConstants.NAMELEN - 1);
}
TarEntry pex = new TarEntry(name,
TarConstants.LF_PAX_EXTENDED_HEADER_LC);

StringWriter w = new StringWriter();
for (Map.Entry<String, String> h : headers.entrySet()) {
String key = h.getKey();
String value = h.getValue();
int len = key.length() + value.length()
+ 3 /* blank, equals and newline */
+ 2 /* guess 9 < actual length < 100 */;
String line = len + " " + key + "=" + value + "\n";
int actualLength = line.getBytes("UTF-8").length;
while (len != actualLength) {
// Adjust for cases where length < 10 or > 100
// or where UTF-8 encoding isn't a single octet
// per character.
// Must be in loop as size may go from 99 to 100 in
// first pass so we'd need a second.
len = actualLength;
line = len + " " + key + "=" + value + "\n";
actualLength = line.getBytes("UTF-8").length;
}
w.write(line);
}
byte[] data = w.toString().getBytes("UTF-8");
pex.setSize(data.length);
putNextEntry(pex);
write(data);
closeEntry();
}

private String stripTo7Bits(String name) {
final int length = name.length();
StringBuffer result = new StringBuffer(length);
for (int i = 0; i < length; i++) {
char stripped = (char) (name.charAt(i) & 0x7F);
if (stripped != 0) { // would be read as Trailing null
result.append(stripped);
}
}
return result.toString();
}

/** /**
* Write an EOF (end of archive) record to the tar archive. * Write an EOF (end of archive) record to the tar archive.
* An EOF record consists of a record of all zeros. * An EOF record consists of a record of all zeros.
@@ -351,6 +540,53 @@ public class TarOutputStream extends FilterOutputStream {


buffer.writeRecord(recordBuf); buffer.writeRecord(recordBuf);
} }
}


private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
TarEntry entry) {
addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
TarConstants.MAXSIZE);
addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getGroupId(),
TarConstants.MAXID);
addPaxHeaderForBigNumber(paxHeaders, "mtime", entry.getModTime().getTime() / 1000,
TarConstants.MAXSIZE);
addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getUserId(),
TarConstants.MAXID);
// star extensions by J\u00f6rg Schilling
addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
entry.getDevMajor(), TarConstants.MAXID);
addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
entry.getDevMinor(), TarConstants.MAXID);
// there is no PAX header for file mode
failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
}


private void addPaxHeaderForBigNumber(Map<String, String> paxHeaders,
String header, long value,
long maxValue) {
if (value < 0 || value > maxValue) {
paxHeaders.put(header, String.valueOf(value));
}
}

private void failForBigNumbers(TarEntry entry) {
failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
failForBigNumber("group id", entry.getGroupId(), TarConstants.MAXID);
failForBigNumber("last modification time",
entry.getModTime().getTime() / 1000,
TarConstants.MAXSIZE);
failForBigNumber("user id", entry.getUserId(), TarConstants.MAXID);
failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
failForBigNumber("major device number", entry.getDevMajor(),
TarConstants.MAXID);
failForBigNumber("minor device number", entry.getDevMinor(),
TarConstants.MAXID);
}

private void failForBigNumber(String field, long value, long maxValue) {
if (value < 0 || value > maxValue) {
throw new RuntimeException(field + " '" + value
+ "' is too big ( > "
+ maxValue + " )");
}
}
}

+ 440
- 86
src/main/org/apache/tools/tar/TarUtils.java View File

@@ -23,6 +23,12 @@


package org.apache.tools.tar; 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. * 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; 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.
*
* <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>
* *
* @param header The header buffer from which to parse.
* <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 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. * @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; long result = 0;
boolean stillPadding = true;
int end = offset + length; 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;
} }
}


stillPadding = false;
// 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--;
}

for ( ;start < end; start++) {
final byte currentByte = buffer[start];
// CheckStyle:MagicNumber OFF // 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 // CheckStyle:MagicNumber ON
} }


return result; 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 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 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;
}


for (i = 0; i < length && i < name.length(); ++i) {
buf[offset + i] = (byte) name.charAt(i);
// 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;
}

/**
* 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) { if (value == 0) {
buf[offset + idx] = (byte) '0';
--idx;
buffer[offset + remaining--] = (byte) '0';
} else { } else {
for (long val = value; idx >= 0 && val > 0; --idx) {
long val = value;
for (; remaining >= 0 && val != 0; --remaining) {
// CheckStyle:MagicNumber OFF // 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 // 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; 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; 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; return offset + length;
} }
@@ -194,7 +547,7 @@ public class TarUtils {
* @param buf The tar entry's header buffer. * @param buf The tar entry's header buffer.
* @return The computed checksum. * @return The computed checksum.
*/ */
public static long computeCheckSum(byte[] buf) {
public static long computeCheckSum(final byte[] buf) {
long sum = 0; long sum = 0;


for (int i = 0; i < buf.length; ++i) { for (int i = 0; i < buf.length; ++i) {
@@ -203,4 +556,5 @@ public class TarUtils {


return sum; return sum;
} }

} }

+ 1
- 1
src/main/org/apache/tools/zip/ZipEncoding.java View File

@@ -41,7 +41,7 @@ import java.nio.ByteBuffer;
* <p>All implementations should implement this interface in a * <p>All implementations should implement this interface in a
* reentrant way.</p> * reentrant way.</p>
*/ */
interface ZipEncoding {
public interface ZipEncoding {
/** /**
* Check, whether the given string may be losslessly encoded using this * Check, whether the given string may be losslessly encoded using this
* encoding. * encoding.


+ 2
- 2
src/main/org/apache/tools/zip/ZipEncodingHelper.java View File

@@ -27,7 +27,7 @@ import java.util.Map;
/** /**
* Static helper functions for robustly encoding filenames in zip files. * 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 * A class, which holds the high characters of a simple encoding
@@ -207,7 +207,7 @@ abstract class ZipEncodingHelper {
* the platform's default encoding. * the platform's default encoding.
* @return A zip encoding for the given encoding name. * @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. // fallback encoding is good enough for utf-8.
if (isUTF8(name)) { if (isUTF8(name)) {


Loading…
Cancel
Save