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.
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
===================================



+ 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 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 currRecIdx;
private int blockSize;
private int recordSize;
private int recsPerBlock;
private boolean debug;

/**
@@ -83,10 +84,7 @@ public class TarBuffer {
* @param recordSize the record size to use
*/
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
*/
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.blockSize = blockSize;
this.recordSize = recordSize;
@@ -194,10 +191,8 @@ public class TarBuffer {
throw new IOException("reading (via skip) from an output buffer");
}

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

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

if (inStream == null) {
if (outStream == null) {
throw new IOException("input buffer is closed");
}
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];
@@ -337,6 +333,9 @@ public class TarBuffer {
}

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

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

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

@@ -454,9 +456,8 @@ public class TarBuffer {
} else if (inStream != null) {
if (inStream != System.in) {
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.
*
* 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)
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.
*/
@@ -50,6 +62,12 @@ public interface TarConstants {
*/
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.
*/
@@ -57,19 +75,36 @@ public interface TarConstants {

/**
* The length of the size field in a header buffer.
* Includes the trailing space or NUL.
*/
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;

/** 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;

/**
* 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.
*/
@@ -86,10 +121,76 @@ public interface TarConstants {
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;

/**
* 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,
* 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';

/**
* 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";

/**
* 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.
*/
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";

/**
* 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;

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

import org.apache.tools.zip.ZipEncoding;

/**
* This class represents an entry in a Tar archive. It consists
* 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 devminor[8];
* } 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>
*
*/

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

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

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

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

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

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

/** The entry's major device number. */
private int devMajor;
@@ -116,6 +153,12 @@ public class TarEntry implements TarConstants {
/** The entry's minor device number. */
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 */
private File file;

@@ -134,10 +177,11 @@ public class TarEntry implements TarConstants {
/**
* 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", "");

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

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

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

this.devMajor = 0;
this.devMinor = 0;
this.name = new StringBuffer(name);
this.name = name;
this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
this.userId = 0;
this.groupId = 0;
this.size = 0;
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.linkFlag = linkFlag;
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
* 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.
*/
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.file = file;

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

if (file.isDirectory()) {
this.mode = DEFAULT_DIR_MODE;
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;
} else {
this.mode = DEFAULT_FILE_MODE;
this.linkFlag = LF_NORMAL;
this.size = file.length();
this.name = fileName;
}

this.modTime = file.lastModified() / MILLIS_PER_SECOND;
@@ -247,12 +302,27 @@ public class TarEntry implements TarConstants {
* to null.
*
* @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) {
this();
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
* by the header names being equal.
@@ -271,6 +341,7 @@ public class TarEntry implements TarConstants {
* @param it Entry to be checked for equality.
* @return True if the entries are equal.
*/
@Override
public boolean equals(Object it) {
if (it == null || getClass() != it.getClass()) {
return false;
@@ -283,6 +354,7 @@ public class TarEntry implements TarConstants {
*
* @return the entry hashcode
*/
@Override
public int hashCode() {
return getName().hashCode();
}
@@ -314,7 +386,7 @@ public class TarEntry implements TarConstants {
* @param name This entry's new 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();
}

/**
* 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.
*
@@ -386,7 +467,7 @@ public class TarEntry implements TarConstants {
* @param userName This entry's new user name.
*/
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.
*/
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.
*
* @param size This entry's new file size.
* @throws IllegalArgumentException if the size is &lt; 0.
*/
public void setSize(long size) {
if (size < 0){
throw new IllegalArgumentException("Size is out of range: "+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
@@ -501,7 +659,26 @@ public class TarEntry implements TarConstants {
*/
public boolean isGNULongNameEntry() {
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;
}

/**
* 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
* 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.
*
* <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
*
* @param outbuf The tar entry header buffer to fill in.
*/
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;

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;

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

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) {
outbuf[offset++] = 0;
@@ -581,42 +841,122 @@ public class TarEntry implements TarConstants {

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.
*
* @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) {
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;

name = TarUtils.parseName(header, offset, NAMELEN);
name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
: TarUtils.parseName(header, offset, NAMELEN, encoding);
offset += NAMELEN;
mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
offset += MODELEN;
userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
offset += UIDLEN;
groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
offset += GIDLEN;
size = TarUtils.parseOctal(header, offset, SIZELEN);
size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
offset += SIZELEN;
modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
offset += MODTIMELEN;
offset += CHKSUMLEN;
linkFlag = header[offset++];
linkName = TarUtils.parseName(header, offset, NAMELEN);
linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
: TarUtils.parseName(header, offset, NAMELEN, encoding);
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;
groupName = TarUtils.parseName(header, offset, GNAMELEN);
groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
: TarUtils.parseName(header, offset, GNAMELEN, encoding);
offset += GNAMELEN;
devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
devMajor = (int) TarUtils.parseOctalOrBinary(header, 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;
}

/**
* 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;

import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
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.
@@ -59,6 +66,8 @@ public class TarInputStream extends FilterInputStream {

// CheckStyle:VisibilityModifier ON

private final ZipEncoding encoding;

/**
* Constructor for TarInputStream.
* @param is the input stream to use
@@ -67,6 +76,15 @@ public class TarInputStream extends FilterInputStream {
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.
* @param is the input stream to use
@@ -76,6 +94,16 @@ public class TarInputStream extends FilterInputStream {
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.
* @param is the input stream to use
@@ -83,13 +111,25 @@ public class TarInputStream extends FilterInputStream {
* @param recordSize the record size to use
*/
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.readBuf = null;
this.oneBuf = new byte[1];
this.debug = 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.
* @throws IOException on error
*/
@Override
public void close() throws IOException {
buffer.close();
}
@@ -131,6 +172,7 @@ public class TarInputStream extends FilterInputStream {
* @return The number of available bytes for the current entry.
* @throws IOException for signature
*/
@Override
public int available() throws IOException {
if (entrySize - entryOffset > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
@@ -148,6 +190,7 @@ public class TarInputStream extends FilterInputStream {
* @return the number actually skipped
* @throws IOException on error
*/
@Override
public long skip(long numToSkip) throws IOException {
// REVIEW
// This is horribly inefficient, but it ensures that we
@@ -171,6 +214,7 @@ public class TarInputStream extends FilterInputStream {
*
* @return False.
*/
@Override
public boolean markSupported() {
return false;
}
@@ -180,12 +224,14 @@ public class TarInputStream extends FilterInputStream {
*
* @param markLimit The limit to mark.
*/
@Override
public void mark(int markLimit) {
}

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

@@ -230,44 +276,37 @@ public class TarInputStream extends FilterInputStream {
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) {
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
StringBuffer longName = new StringBuffer();
byte[] buf = new byte[SMALL_BUFFER_SIZE];
int length = 0;
while ((length = read(buf)) >= 0) {
longName.append(new String(buf, 0, length));
longName.append(new String(buf, 0, length)); // TODO default charset?
}
getNextEntry();
if (currEntry == null) {
@@ -283,9 +322,176 @@ public class TarInputStream extends FilterInputStream {
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;
}

/**
* 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.
*
@@ -294,6 +500,7 @@ public class TarInputStream extends FilterInputStream {
* @return The byte read, or -1 at EOF.
* @throws IOException on error
*/
@Override
public int read() throws IOException {
int num = read(oneBuf, 0, 1);
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.
* @throws IOException on error
*/
@Override
public int read(byte[] buf, int offset, int numToRead) throws IOException {
int totalRead = 0;

@@ -399,4 +607,14 @@ public class TarInputStream extends FilterInputStream {
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;

import java.io.File;
import java.io.FilterOutputStream;
import java.io.OutputStream;
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.
@@ -43,6 +49,18 @@ public class TarOutputStream extends FilterOutputStream {
/** GNU tar extensions are used to store long file names in the archive. */
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
protected boolean debug;
protected long currSize;
@@ -56,8 +74,22 @@ public class TarOutputStream extends FilterOutputStream {
protected int longFileMode = LONGFILE_ERROR;
// CheckStyle:VisibilityModifier ON

private int bigNumberMode = BIGNUMBER_ERROR;

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.
* @param os the output stream to use
@@ -66,6 +98,15 @@ public class TarOutputStream extends FilterOutputStream {
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.
* @param os the output stream to use
@@ -75,6 +116,16 @@ public class TarOutputStream extends FilterOutputStream {
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.
* @param os the output stream to use
@@ -82,7 +133,20 @@ public class TarOutputStream extends FilterOutputStream {
* @param recordSize the record size to use
*/
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);
this.encoding = ZipEncodingHelper.getZipEncoding(encoding);

this.buffer = new TarBuffer(os, blockSize, recordSize);
this.debug = false;
@@ -103,6 +167,23 @@ public class TarOutputStream extends FilterOutputStream {
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.
@@ -124,15 +205,25 @@ public class TarOutputStream extends FilterOutputStream {

/**
* 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
*/
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();
buffer.flushBlock();
finished = true;
}

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

if (!closed) {
buffer.close();
out.close();
closed = true;
@@ -172,27 +267,59 @@ public class TarOutputStream extends FilterOutputStream {
* @throws IOException on error
*/
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
// of which are the entry's name
TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
TarConstants.LF_GNUTYPE_LONGNAME);

longLinkEntry.setSize(entry.getName().length() + 1);
longLinkEntry.setSize(nameBytes.length + 1); // +1 for NUL
putNextEntry(longLinkEntry);
write(entry.getName().getBytes());
write(0);
write(nameBytes);
write(0); // NUL terminator
closeEntry();
} 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);

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

/**
@@ -216,6 +344,12 @@ public class TarOutputStream extends FilterOutputStream {
* @throws IOException on error
*/
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) {
for (int i = assemLen; i < assemBuf.length; ++i) {
assemBuf[i] = 0;
@@ -233,6 +367,7 @@ public class TarOutputStream extends FilterOutputStream {
+ "' before the '" + currSize
+ "' bytes specified in the header were written");
}
haveUnclosedEntry = false;
}

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

@@ -275,6 +411,7 @@ public class TarOutputStream extends FilterOutputStream {
* @param numToWrite The number of bytes to write.
* @throws IOException on error
*/
@Override
public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
if ((currBytes + numToWrite) > currSize) {
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.
* An EOF record consists of a record of all zeros.
@@ -351,6 +540,53 @@ public class TarOutputStream extends FilterOutputStream {

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;

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.
*
* <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 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;
}
}

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

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) {
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;
}

}

+ 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
* reentrant way.</p>
*/
interface ZipEncoding {
public interface ZipEncoding {
/**
* Check, whether the given string may be losslessly encoded using this
* 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.
*/
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)) {


Loading…
Cancel
Save