Return-Path:
*
* You should never have a need to access this class directly. TarBuffers are
* created by Tar IO Streams.
*
* @author Timothy Gerard Endres time@ice.com
*/
public class TarBuffer
{
public final static int DEFAULT_RCDSIZE = ( 512 );
public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
private byte[] blockBuffer;
private int blockSize;
private int currBlkIdx;
private int currRecIdx;
private boolean debug;
private InputStream inStream;
private OutputStream outStream;
private int recordSize;
private int recsPerBlock;
public TarBuffer( InputStream inStream )
{
this( inStream, TarBuffer.DEFAULT_BLKSIZE );
}
public TarBuffer( InputStream inStream, int blockSize )
{
this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
}
public TarBuffer( InputStream inStream, int blockSize, int recordSize )
{
this.inStream = inStream;
this.outStream = null;
this.initialize( blockSize, recordSize );
}
public TarBuffer( OutputStream outStream )
{
this( outStream, TarBuffer.DEFAULT_BLKSIZE );
}
public TarBuffer( OutputStream outStream, int blockSize )
{
this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
}
public TarBuffer( OutputStream outStream, int blockSize, int recordSize )
{
this.inStream = null;
this.outStream = outStream;
this.initialize( blockSize, recordSize );
}
/**
* Set the debugging flag for the buffer.
*
* @param debug If true, print debugging output.
*/
public void setDebug( boolean debug )
{
this.debug = debug;
}
/**
* Get the TAR Buffer's block size. Blocks consist of multiple records.
*
* @return The BlockSize value
*/
public int getBlockSize()
{
return this.blockSize;
}
/**
* Get the current block number, zero based.
*
* @return The current zero based block number.
*/
public int getCurrentBlockNum()
{
return this.currBlkIdx;
}
/**
* Get the current record number, within the current block, zero based.
* Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
*
* @return The current zero based record number.
*/
public int getCurrentRecordNum()
{
return this.currRecIdx - 1;
}
/**
* Get the TAR Buffer's record size.
*
* @return The RecordSize value
*/
public int getRecordSize()
{
return this.recordSize;
}
/**
* Determine if an archive record indicate End of Archive. End of archive is
* indicated by a record that consists entirely of null bytes.
*
* @param record The record data to check.
* @return The EOFRecord value
*/
public boolean isEOFRecord( byte[] record )
{
for( int i = 0, sz = this.getRecordSize(); i < sz; ++i )
{
if( record[ i ] != 0 )
{
return false;
}
}
return true;
}
/**
* Close the TarBuffer. If this is an output buffer, also flush the current
* block before closing.
*
* @exception IOException Description of Exception
*/
public void close()
throws IOException
{
if( this.debug )
{
System.err.println( "TarBuffer.closeBuffer()." );
}
if( this.outStream != null )
{
this.flushBlock();
if( this.outStream != System.out
&& this.outStream != System.err )
{
this.outStream.close();
this.outStream = null;
}
}
else if( this.inStream != null )
{
if( this.inStream != System.in )
{
this.inStream.close();
this.inStream = null;
}
}
}
/**
* Read a record from the input stream and return the data.
*
* @return The record data.
* @exception IOException Description of Exception
*/
public byte[] readRecord()
throws IOException
{
if( this.debug )
{
System.err.println( "ReadRecord: recIdx = " + this.currRecIdx
+ " blkIdx = " + this.currBlkIdx );
}
if( this.inStream == null )
{
throw new IOException( "reading from an output buffer" );
}
if( this.currRecIdx >= this.recsPerBlock )
{
if( !this.readBlock() )
{
return null;
}
}
byte[] result = new byte[ this.recordSize ];
System.arraycopy( this.blockBuffer,
( this.currRecIdx * this.recordSize ), result, 0,
this.recordSize );
this.currRecIdx++;
return result;
}
/**
* Skip over a record on the input stream.
*
* @exception IOException Description of Exception
*/
public void skipRecord()
throws IOException
{
if( this.debug )
{
System.err.println( "SkipRecord: recIdx = " + this.currRecIdx
+ " blkIdx = " + this.currBlkIdx );
}
if( this.inStream == null )
{
throw new IOException( "reading (via skip) from an output buffer" );
}
if( this.currRecIdx >= this.recsPerBlock )
{
if( !this.readBlock() )
{
return;// UNDONE
}
}
this.currRecIdx++;
}
/**
* Write an archive record to the archive.
*
* @param record The record data to write to the archive.
* @exception IOException Description of Exception
*/
public void writeRecord( byte[] record )
throws IOException
{
if( this.debug )
{
System.err.println( "WriteRecord: recIdx = " + this.currRecIdx
+ " blkIdx = " + this.currBlkIdx );
}
if( this.outStream == null )
{
throw new IOException( "writing to an input buffer" );
}
if( record.length != this.recordSize )
{
throw new IOException( "record to write has length '"
+ record.length
+ "' which is not the record size of '"
+ this.recordSize + "'" );
}
if( this.currRecIdx >= this.recsPerBlock )
{
this.writeBlock();
}
System.arraycopy( record, 0, this.blockBuffer,
( this.currRecIdx * this.recordSize ),
this.recordSize );
this.currRecIdx++;
}
/**
* Write an archive record to the archive, where the record may be inside of
* a larger array buffer. The buffer must be "offset plus record size" long.
*
* @param buf The buffer containing the record data to write.
* @param offset The offset of the record data within buf.
* @exception IOException Description of Exception
*/
public void writeRecord( byte[] buf, int offset )
throws IOException
{
if( this.debug )
{
System.err.println( "WriteRecord: recIdx = " + this.currRecIdx
+ " blkIdx = " + this.currBlkIdx );
}
if( this.outStream == null )
{
throw new IOException( "writing to an input buffer" );
}
if( ( offset + this.recordSize ) > buf.length )
{
throw new IOException( "record has length '" + buf.length
+ "' with offset '" + offset
+ "' which is less than the record size of '"
+ this.recordSize + "'" );
}
if( this.currRecIdx >= this.recsPerBlock )
{
this.writeBlock();
}
System.arraycopy( buf, offset, this.blockBuffer,
( this.currRecIdx * this.recordSize ),
this.recordSize );
this.currRecIdx++;
}
/**
* Flush the current data block if it has any data in it.
*
* @exception IOException Description of Exception
*/
private void flushBlock()
throws IOException
{
if( this.debug )
{
System.err.println( "TarBuffer.flushBlock() called." );
}
if( this.outStream == null )
{
throw new IOException( "writing to an input buffer" );
}
if( this.currRecIdx > 0 )
{
this.writeBlock();
}
}
/**
* Initialization common to all constructors.
*
* @param blockSize Description of Parameter
* @param recordSize Description of Parameter
*/
private void initialize( int blockSize, int recordSize )
{
this.debug = false;
this.blockSize = blockSize;
this.recordSize = recordSize;
this.recsPerBlock = ( this.blockSize / this.recordSize );
this.blockBuffer = new byte[ this.blockSize ];
if( this.inStream != null )
{
this.currBlkIdx = -1;
this.currRecIdx = this.recsPerBlock;
}
else
{
this.currBlkIdx = 0;
this.currRecIdx = 0;
}
}
/**
* @return false if End-Of-File, else true
* @exception IOException Description of Exception
*/
private boolean readBlock()
throws IOException
{
if( this.debug )
{
System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx );
}
if( this.inStream == null )
{
throw new IOException( "reading from an output buffer" );
}
this.currRecIdx = 0;
int offset = 0;
int bytesNeeded = this.blockSize;
while( bytesNeeded > 0 )
{
long numBytes = this.inStream.read( this.blockBuffer, offset,
bytesNeeded );
//
// NOTE
// We have fit EOF, and the block is not full!
//
// This is a broken archive. It does not follow the standard
// blocking algorithm. However, because we are generous, and
// it requires little effort, we will simply ignore the error
// and continue as if the entire block were read. This does
// not appear to break anything upstream. We used to return
// false in this case.
//
// Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
//
if( numBytes == -1 )
{
break;
}
offset += numBytes;
bytesNeeded -= numBytes;
if( numBytes != this.blockSize )
{
if( this.debug )
{
System.err.println( "ReadBlock: INCOMPLETE READ "
+ numBytes + " of " + this.blockSize
+ " bytes read." );
}
}
}
this.currBlkIdx++;
return true;
}
/**
* Write a TarBuffer block to the archive.
*
* @exception IOException Description of Exception
*/
private void writeBlock()
throws IOException
{
if( this.debug )
{
System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx );
}
if( this.outStream == null )
{
throw new IOException( "writing to an input buffer" );
}
this.outStream.write( this.blockBuffer, 0, this.blockSize );
this.outStream.flush();
this.currRecIdx = 0;
this.currBlkIdx++;
}
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarConstants.java
Index: TarConstants.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.tar;
/**
* This interface contains all the definitions used in the package.
*
* @author Timothy Gerard Endres time@ice.com
* @author Stefano Mazzocchi
* stefano@apache.org
*/
public interface TarConstants
{
/**
* The length of the name field in a header buffer.
*/
int NAMELEN = 100;
/**
* The length of the mode field in a header buffer.
*/
int MODELEN = 8;
/**
* The length of the user id field in a header buffer.
*/
int UIDLEN = 8;
/**
* The length of the group id field in a header buffer.
*/
int GIDLEN = 8;
/**
* The length of the checksum field in a header buffer.
*/
int CHKSUMLEN = 8;
/**
* The length of the size field in a header buffer.
*/
int SIZELEN = 12;
/**
* The length of the magic field in a header buffer.
*/
int MAGICLEN = 8;
/**
* The length of the modification time field in a header buffer.
*/
int MODTIMELEN = 12;
/**
* The length of the user name field in a header buffer.
*/
int UNAMELEN = 32;
/**
* The length of the group name field in a header buffer.
*/
int GNAMELEN = 32;
/**
* The length of the devices field in a header buffer.
*/
int DEVLEN = 8;
/**
* 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.
*/
byte LF_OLDNORM = 0;
/**
* Normal file type.
*/
byte LF_NORMAL = (byte)'0';
/**
* Link file type.
*/
byte LF_LINK = (byte)'1';
/**
* Symbolic link file type.
*/
byte LF_SYMLINK = (byte)'2';
/**
* Character device file type.
*/
byte LF_CHR = (byte)'3';
/**
* Block device file type.
*/
byte LF_BLK = (byte)'4';
/**
* Directory file type.
*/
byte LF_DIR = (byte)'5';
/**
* FIFO (pipe) file type.
*/
byte LF_FIFO = (byte)'6';
/**
* Contiguous file type.
*/
byte LF_CONTIG = (byte)'7';
/**
* The magic tag representing a POSIX tar archive.
*/
String TMAGIC = "ustar";
/**
* The magic tag representing a GNU tar archive.
*/
String GNU_TMAGIC = "ustar ";
/**
* The namr 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';
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarEntry.java
Index: TarEntry.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.tar;
import java.io.File;
import java.util.Date;
/**
* This class represents an entry in a Tar archive. It consists of the entry's
* header, as well as the entry's File. Entries can be instantiated in one of
* three ways, depending on how they are to be used.
*
* TarEntries that are created from the header bytes read from an archive are
* instantiated with the TarEntry( byte[] ) constructor. These entries will be
* used when extracting from or listing the contents of an archive. These
* entries have their header filled in using the header bytes. They also set the
* File to null, since they reference an archive entry not a file.
*
* TarEntries that are created from Files that are to be written into an archive
* are instantiated with the TarEntry( File ) constructor. These entries have
* their header filled in using the File's information. They also keep a
* reference to the File for convenience when writing entries.
*
* Finally, TarEntries can be constructed from nothing but a name. This allows
* the programmer to construct the entry by hand, for instance when only an
* InputStream is available for writing to the archive, and the header
* information is constructed from other information. In this case the header
* fields are set to defaults and the File is set to null.
*
* The C structure for a Tar Entry's header is:
*
* This class uses the ASi extra field in the format:
* struct header {
* char name[NAMSIZ];
* char mode[8];
* char uid[8];
* char gid[8];
* char size[12];
* char mtime[12];
* char chksum[8];
* char linkflag;
* char linkname[NAMSIZ];
* char magic[8];
* char uname[TUNMLEN];
* char gname[TGNMLEN];
* char devmajor[8];
* char devminor[8];
* } header;
*
*
* @author Timothy Gerard Endres time@ice.com
* @author Stefano Mazzocchi
* stefano@apache.org
*/
public class TarEntry implements TarConstants
{
/**
* The entry's modification time.
*/
private int checkSum;
/**
* The entry's group name.
*/
private int devMajor;
/**
* The entry's major device number.
*/
private int devMinor;
/**
* The entry's minor device number.
*/
private File file;
/**
* The entry's user id.
*/
private int groupId;
/**
* The entry's user name.
*/
private StringBuffer groupName;
/**
* The entry's checksum.
*/
private byte linkFlag;
/**
* The entry's link flag.
*/
private StringBuffer linkName;
/**
* The entry's link name.
*/
private StringBuffer magic;
/**
* The entry's size.
*/
private long modTime;
/**
* The entry's name.
*/
private int mode;
private StringBuffer name;
/**
* The entry's group id.
*/
private long size;
/**
* The entry's permission mode.
*/
private int userId;
/**
* The entry's magic tag.
*/
private StringBuffer userName;
/**
* Construct an entry with only a name. This allows the programmer to
* construct the entry's header "by hand". File is set to null.
*
* @param name Description of Parameter
*/
public TarEntry( String name )
{
this();
boolean isDir = name.endsWith( "/" );
this.checkSum = 0;
this.devMajor = 0;
this.devMinor = 0;
this.name = new StringBuffer( name );
this.mode = isDir ? 040755 : 0100644;
this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
this.userId = 0;
this.groupId = 0;
this.size = 0;
this.checkSum = 0;
this.modTime = ( new Date() ).getTime() / 1000;
this.linkName = new StringBuffer( "" );
this.userName = new StringBuffer( "" );
this.groupName = new StringBuffer( "" );
this.devMajor = 0;
this.devMinor = 0;
}
/**
* Construct an entry with a name an a link flag.
*
* @param name Description of Parameter
* @param linkFlag Description of Parameter
*/
public TarEntry( String name, byte linkFlag )
{
this( name );
this.linkFlag = linkFlag;
}
/**
* 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.
*/
public TarEntry( File file )
{
this();
this.file = file;
String name = file.getPath();
String osname = System.getProperty( "os.name" );
if( osname != null )
{
// Strip off drive letters!
// REVIEW Would a better check be "(File.separator == '\')"?
String win32Prefix = "Windows";
String prefix = osname.substring( 0, win32Prefix.length() );
if( prefix.equalsIgnoreCase( win32Prefix ) )
{
if( name.length() > 2 )
{
char ch1 = name.charAt( 0 );
char ch2 = name.charAt( 1 );
if( ch2 == ':'
&& ( ( ch1 >= 'a' && ch1 <= 'z' )
|| ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
{
name = name.substring( 2 );
}
}
}
else if( osname.toLowerCase().indexOf( "netware" ) > -1 )
{
int colon = name.indexOf( ':' );
if( colon != -1 )
{
name = name.substring( colon + 1 );
}
}
}
name = name.replace( File.separatorChar, '/' );
// No absolute pathnames
// Windows (and Posix?) paths can start with "\\NetworkDrive\",
// so we loop on starting /'s.
while( name.startsWith( "/" ) )
{
name = name.substring( 1 );
}
this.linkName = new StringBuffer( "" );
this.name = new StringBuffer( name );
if( file.isDirectory() )
{
this.mode = 040755;
this.linkFlag = LF_DIR;
if( this.name.charAt( this.name.length() - 1 ) != '/' )
{
this.name.append( "/" );
}
}
else
{
this.mode = 0100644;
this.linkFlag = LF_NORMAL;
}
this.size = file.length();
this.modTime = file.lastModified() / 1000;
this.checkSum = 0;
this.devMajor = 0;
this.devMinor = 0;
}
/**
* Construct an entry from an archive's header bytes. File is set to null.
*
* @param headerBuf The header bytes from a tar archive entry.
*/
public TarEntry( byte[] headerBuf )
{
this();
this.parseTarHeader( headerBuf );
}
/**
* The entry's file reference
*/
/**
* Construct an empty entry and prepares the header values.
*/
private TarEntry()
{
this.magic = new StringBuffer( TMAGIC );
this.name = new StringBuffer();
this.linkName = new StringBuffer();
String user = System.getProperty( "user.name", "" );
if( user.length() > 31 )
{
user = user.substring( 0, 31 );
}
this.userId = 0;
this.groupId = 0;
this.userName = new StringBuffer( user );
this.groupName = new StringBuffer( "" );
this.file = null;
}
/**
* Set this entry's group id.
*
* @param groupId This entry's new group id.
*/
public void setGroupId( int groupId )
{
this.groupId = groupId;
}
/**
* Set this entry's group name.
*
* @param groupName This entry's new group name.
*/
public void setGroupName( String groupName )
{
this.groupName = new StringBuffer( groupName );
}
/**
* Convenience method to set this entry's group and user ids.
*
* @param userId This entry's new user id.
* @param groupId This entry's new group id.
*/
public void setIds( int userId, int groupId )
{
this.setUserId( userId );
this.setGroupId( groupId );
}
/**
* Set this entry's modification time. The parameter passed to this method
* is in "Java time".
*
* @param time This entry's new modification time.
*/
public void setModTime( long time )
{
this.modTime = time / 1000;
}
/**
* Set this entry's modification time.
*
* @param time This entry's new modification time.
*/
public void setModTime( Date time )
{
this.modTime = time.getTime() / 1000;
}
/**
* Set the mode for this entry
*
* @param mode The new Mode value
*/
public void setMode( int mode )
{
this.mode = mode;
}
/**
* Set this entry's name.
*
* @param name This entry's new name.
*/
public void setName( String name )
{
this.name = new StringBuffer( name );
}
/**
* Convenience method to set this entry's group and user names.
*
* @param userName This entry's new user name.
* @param groupName This entry's new group name.
*/
public void setNames( String userName, String groupName )
{
this.setUserName( userName );
this.setGroupName( groupName );
}
/**
* Set this entry's file size.
*
* @param size This entry's new file size.
*/
public void setSize( long size )
{
this.size = size;
}
/**
* Set this entry's user id.
*
* @param userId This entry's new user id.
*/
public void setUserId( int userId )
{
this.userId = userId;
}
/**
* Set this entry's user name.
*
* @param userName This entry's new user name.
*/
public void setUserName( String userName )
{
this.userName = new StringBuffer( userName );
}
/**
* If this entry represents a file, and the file is a directory, return an
* array of TarEntries for this entry's children.
*
* @return An array of TarEntry's for this entry's children.
*/
public TarEntry[] getDirectoryEntries()
{
if( this.file == null || !this.file.isDirectory() )
{
return new TarEntry[ 0 ];
}
String[] list = this.file.list();
TarEntry[] result = new TarEntry[ list.length ];
for( int i = 0; i < list.length; ++i )
{
result[ i ] = new TarEntry( new File( this.file, list[ i ] ) );
}
return result;
}
/**
* Get this entry's file.
*
* @return This entry's file.
*/
public File getFile()
{
return this.file;
}
/**
* Get this entry's group id.
*
* @return This entry's group id.
*/
public int getGroupId()
{
return this.groupId;
}
/**
* Get this entry's group name.
*
* @return This entry's group name.
*/
public String getGroupName()
{
return this.groupName.toString();
}
/**
* Set this entry's modification time.
*
* @return The ModTime value
*/
public Date getModTime()
{
return new Date( this.modTime * 1000 );
}
/**
* Get this entry's mode.
*
* @return This entry's mode.
*/
public int getMode()
{
return this.mode;
}
/**
* Get this entry's name.
*
* @return This entry's name.
*/
public String getName()
{
return this.name.toString();
}
/**
* Get this entry's file size.
*
* @return This entry's file size.
*/
public long getSize()
{
return this.size;
}
/**
* Get this entry's user id.
*
* @return This entry's user id.
*/
public int getUserId()
{
return this.userId;
}
/**
* Get this entry's user name.
*
* @return This entry's user name.
*/
public String getUserName()
{
return this.userName.toString();
}
/**
* Determine if the given entry is a descendant of this entry. Descendancy
* is determined by the name of the descendant starting with this entry's
* name.
*
* @param desc Entry to be checked as a descendent of this.
* @return True if entry is a descendant of this.
*/
public boolean isDescendent( TarEntry desc )
{
return desc.getName().startsWith( this.getName() );
}
/**
* Return whether or not this entry represents a directory.
*
* @return True if this entry is a directory.
*/
public boolean isDirectory()
{
if( this.file != null )
{
return this.file.isDirectory();
}
if( this.linkFlag == LF_DIR )
{
return true;
}
if( this.getName().endsWith( "/" ) )
{
return true;
}
return false;
}
/**
* Indicate if this entry is a GNU long name block
*
* @return true if this is a long name extension provided by GNU tar
*/
public boolean isGNULongNameEntry()
{
return linkFlag == LF_GNUTYPE_LONGNAME &&
name.toString().equals( GNU_LONGLINK );
}
/**
* Determine if the two entries are equal. Equality is determined by the
* header names being equal.
*
* @param it Description of Parameter
* @return it Entry to be checked for equality.
* @return True if the entries are equal.
*/
public boolean equals( TarEntry it )
{
return this.getName().equals( it.getName() );
}
/**
* Parse an entry's header information from a header buffer.
*
* @param header The tar entry header buffer to get information from.
*/
public void parseTarHeader( byte[] header )
{
int offset = 0;
this.name = TarUtils.parseName( header, offset, NAMELEN );
offset += NAMELEN;
this.mode = (int)TarUtils.parseOctal( header, offset, MODELEN );
offset += MODELEN;
this.userId = (int)TarUtils.parseOctal( header, offset, UIDLEN );
offset += UIDLEN;
this.groupId = (int)TarUtils.parseOctal( header, offset, GIDLEN );
offset += GIDLEN;
this.size = TarUtils.parseOctal( header, offset, SIZELEN );
offset += SIZELEN;
this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN );
offset += MODTIMELEN;
this.checkSum = (int)TarUtils.parseOctal( header, offset, CHKSUMLEN );
offset += CHKSUMLEN;
this.linkFlag = header[ offset++ ];
this.linkName = TarUtils.parseName( header, offset, NAMELEN );
offset += NAMELEN;
this.magic = TarUtils.parseName( header, offset, MAGICLEN );
offset += MAGICLEN;
this.userName = TarUtils.parseName( header, offset, UNAMELEN );
offset += UNAMELEN;
this.groupName = TarUtils.parseName( header, offset, GNAMELEN );
offset += GNAMELEN;
this.devMajor = (int)TarUtils.parseOctal( header, offset, DEVLEN );
offset += DEVLEN;
this.devMinor = (int)TarUtils.parseOctal( header, offset, DEVLEN );
}
/**
* Write an entry's header information to a header buffer.
*
* @param outbuf The tar entry header buffer to fill in.
*/
public void writeEntryHeader( byte[] outbuf )
{
int offset = 0;
offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN );
offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN );
offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN );
offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN );
offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN );
offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN );
int csOffset = offset;
for( int c = 0; c < CHKSUMLEN; ++c )
{
outbuf[ offset++ ] = (byte)' ';
}
outbuf[ offset++ ] = this.linkFlag;
offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN );
offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN );
offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN );
offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN );
offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN );
offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN );
while( offset < outbuf.length )
{
outbuf[ offset++ ] = 0;
}
long checkSum = TarUtils.computeCheckSum( outbuf );
TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN );
}
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarInputStream.java
Index: TarInputStream.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.tar;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The TarInputStream reads a UNIX tar archive as an InputStream. methods are
* provided to position at each successive entry in the archive, and the read
* each entry as a normal input stream using read().
*
* @author Timothy Gerard Endres time@ice.com
* @author Stefano Mazzocchi
* stefano@apache.org
*/
public class TarInputStream extends FilterInputStream
{
protected TarBuffer buffer;
protected TarEntry currEntry;
protected boolean debug;
protected int entryOffset;
protected int entrySize;
protected boolean hasHitEOF;
protected byte[] oneBuf;
protected byte[] readBuf;
public TarInputStream( InputStream is )
{
this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
}
public TarInputStream( InputStream is, int blockSize )
{
this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
}
public TarInputStream( InputStream is, int blockSize, int recordSize )
{
super( is );
this.buffer = new TarBuffer( is, blockSize, recordSize );
this.readBuf = null;
this.oneBuf = new byte[ 1 ];
this.debug = false;
this.hasHitEOF = false;
}
/**
* Sets the debugging flag.
*
* @param debug The new Debug value
*/
public void setDebug( boolean debug )
{
this.debug = debug;
this.buffer.setDebug( debug );
}
/**
* Get the next entry 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, and read the header and instantiate a new
* TarEntry from the header bytes and return that 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 TarEntry in the archive, or null.
* @exception IOException Description of Exception
*/
public TarEntry getNextEntry()
throws IOException
{
if( this.hasHitEOF )
{
return null;
}
if( this.currEntry != null )
{
int numToSkip = this.entrySize - this.entryOffset;
if( this.debug )
{
System.err.println( "TarInputStream: SKIP currENTRY '"
+ this.currEntry.getName() + "' SZ "
+ this.entrySize + " OFF "
+ this.entryOffset + " skipping "
+ numToSkip + " bytes" );
}
if( numToSkip > 0 )
{
this.skip( numToSkip );
}
this.readBuf = null;
}
byte[] headerBuf = this.buffer.readRecord();
if( headerBuf == null )
{
if( this.debug )
{
System.err.println( "READ NULL RECORD" );
}
this.hasHitEOF = true;
}
else if( this.buffer.isEOFRecord( headerBuf ) )
{
if( this.debug )
{
System.err.println( "READ EOF RECORD" );
}
this.hasHitEOF = true;
}
if( this.hasHitEOF )
{
this.currEntry = null;
}
else
{
this.currEntry = new TarEntry( headerBuf );
if( !( headerBuf[ 257 ] == 'u' && headerBuf[ 258 ] == 's'
&& headerBuf[ 259 ] == 't' && headerBuf[ 260 ] == 'a'
&& headerBuf[ 261 ] == 'r' ) )
{
this.entrySize = 0;
this.entryOffset = 0;
this.currEntry = null;
throw new IOException( "bad header in block "
+ this.buffer.getCurrentBlockNum()
+ " record "
+ this.buffer.getCurrentRecordNum()
+ ", " +
"header magic is not 'ustar', but '"
+ headerBuf[ 257 ]
+ headerBuf[ 258 ]
+ headerBuf[ 259 ]
+ headerBuf[ 260 ]
+ headerBuf[ 261 ]
+ "', or (dec) "
+ ( (int)headerBuf[ 257 ] )
+ ", "
+ ( (int)headerBuf[ 258 ] )
+ ", "
+ ( (int)headerBuf[ 259 ] )
+ ", "
+ ( (int)headerBuf[ 260 ] )
+ ", "
+ ( (int)headerBuf[ 261 ] ) );
}
if( this.debug )
{
System.err.println( "TarInputStream: SET CURRENTRY '"
+ this.currEntry.getName()
+ "' size = "
+ this.currEntry.getSize() );
}
this.entryOffset = 0;
// REVIEW How do we resolve this discrepancy?!
this.entrySize = (int)this.currEntry.getSize();
}
if( this.currEntry != null && this.currEntry.isGNULongNameEntry() )
{
// read in the name
StringBuffer longName = new StringBuffer();
byte[] buffer = new byte[ 256 ];
int length = 0;
while( ( length = read( buffer ) ) >= 0 )
{
longName.append( new String( buffer, 0, length ) );
}
getNextEntry();
this.currEntry.setName( longName.toString() );
}
return this.currEntry;
}
/**
* Get the record size being used by this stream's TarBuffer.
*
* @return The TarBuffer record size.
*/
public int getRecordSize()
{
return this.buffer.getRecordSize();
}
/**
* Get the available data that can be read from the current entry in the
* archive. This does not indicate how much data is left in the entire
* archive, only in the current entry. This value is determined from the
* entry's size header field and the amount of data already read from the
* current entry.
*
* @return The number of available bytes for the current entry.
* @exception IOException Description of Exception
*/
public int available()
throws IOException
{
return this.entrySize - this.entryOffset;
}
/**
* Closes this stream. Calls the TarBuffer's close() method.
*
* @exception IOException Description of Exception
*/
public void close()
throws IOException
{
this.buffer.close();
}
/**
* Copies the contents of the current tar archive entry directly into an
* output stream.
*
* @param out The OutputStream into which to write the entry's data.
* @exception IOException Description of Exception
*/
public void copyEntryContents( OutputStream out )
throws IOException
{
byte[] buf = new byte[ 32 * 1024 ];
while( true )
{
int numRead = this.read( buf, 0, buf.length );
if( numRead == -1 )
{
break;
}
out.write( buf, 0, numRead );
}
}
/**
* Since we do not support marking just yet, we do nothing.
*
* @param markLimit The limit to mark.
*/
public void mark( int markLimit )
{
}
/**
* Since we do not support marking just yet, we return false.
*
* @return False.
*/
public boolean markSupported()
{
return false;
}
/**
* Reads a byte from the current tar archive entry. This method simply calls
* read( byte[], int, int ).
*
* @return The byte read, or -1 at EOF.
* @exception IOException Description of Exception
*/
public int read()
throws IOException
{
int num = this.read( this.oneBuf, 0, 1 );
if( num == -1 )
{
return num;
}
else
{
return (int)this.oneBuf[ 0 ];
}
}
/**
* Reads bytes from the current tar archive entry. This method simply calls
* read( byte[], int, int ).
*
* @param buf The buffer into which to place bytes read.
* @return The number of bytes read, or -1 at EOF.
* @exception IOException Description of Exception
*/
public int read( byte[] buf )
throws IOException
{
return this.read( buf, 0, buf.length );
}
/**
* Reads bytes from the current tar archive entry. This method is aware of
* the boundaries of the current entry in the archive and will deal with
* them as if they were this stream's start and EOF.
*
* @param buf The buffer into which to place bytes read.
* @param offset The offset at which to place bytes read.
* @param numToRead The number of bytes to read.
* @return The number of bytes read, or -1 at EOF.
* @exception IOException Description of Exception
*/
public int read( byte[] buf, int offset, int numToRead )
throws IOException
{
int totalRead = 0;
if( this.entryOffset >= this.entrySize )
{
return -1;
}
if( ( numToRead + this.entryOffset ) > this.entrySize )
{
numToRead = ( this.entrySize - this.entryOffset );
}
if( this.readBuf != null )
{
int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length
: numToRead;
System.arraycopy( this.readBuf, 0, buf, offset, sz );
if( sz >= this.readBuf.length )
{
this.readBuf = null;
}
else
{
int newLen = this.readBuf.length - sz;
byte[] newBuf = new byte[ newLen ];
System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
this.readBuf = newBuf;
}
totalRead += sz;
numToRead -= sz;
offset += sz;
}
while( numToRead > 0 )
{
byte[] rec = this.buffer.readRecord();
if( rec == null )
{
// Unexpected EOF!
throw new IOException( "unexpected EOF with " + numToRead
+ " bytes unread" );
}
int sz = numToRead;
int recLen = rec.length;
if( recLen > sz )
{
System.arraycopy( rec, 0, buf, offset, sz );
this.readBuf = new byte[ recLen - sz ];
System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
}
else
{
sz = recLen;
System.arraycopy( rec, 0, buf, offset, recLen );
}
totalRead += sz;
numToRead -= sz;
offset += sz;
}
this.entryOffset += totalRead;
return totalRead;
}
/**
* Since we do not support marking just yet, we do nothing.
*/
public void reset()
{
}
/**
* Skip bytes in the input buffer. This skips bytes in the current entry's
* data, not the entire archive, and will stop at the end of the current
* entry's data if the number to skip extends beyond that point.
*
* @param numToSkip The number of bytes to skip.
* @exception IOException Description of Exception
*/
public void skip( int numToSkip )
throws IOException
{
// REVIEW
// This is horribly inefficient, but it ensures that we
// properly skip over bytes via the TarBuffer...
//
byte[] skipBuf = new byte[ 8 * 1024 ];
for( int num = numToSkip; num > 0; )
{
int numRead = this.read( skipBuf, 0,
( num > skipBuf.length ? skipBuf.length
: num ) );
if( numRead == -1 )
{
break;
}
num -= numRead;
}
}
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarOutputStream.java
Index: TarOutputStream.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.tar;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are
* provided to put entries, and then write their contents by writing to this
* stream using write().
*
* @author Timothy Gerard Endres time@ice.com
*/
public class TarOutputStream extends FilterOutputStream
{
public final static int LONGFILE_ERROR = 0;
public final static int LONGFILE_TRUNCATE = 1;
public final static int LONGFILE_GNU = 2;
protected int longFileMode = LONGFILE_ERROR;
protected byte[] assemBuf;
protected int assemLen;
protected TarBuffer buffer;
protected int currBytes;
protected int currSize;
protected boolean debug;
protected byte[] oneBuf;
protected byte[] recordBuf;
public TarOutputStream( OutputStream os )
{
this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
}
public TarOutputStream( OutputStream os, int blockSize )
{
this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
}
public TarOutputStream( OutputStream os, int blockSize, int recordSize )
{
super( os );
this.buffer = new TarBuffer( os, blockSize, recordSize );
this.debug = false;
this.assemLen = 0;
this.assemBuf = new byte[ recordSize ];
this.recordBuf = new byte[ recordSize ];
this.oneBuf = new byte[ 1 ];
}
/**
* Sets the debugging flag in this stream's TarBuffer.
*
* @param debug The new BufferDebug value
*/
public void setBufferDebug( boolean debug )
{
this.buffer.setDebug( debug );
}
/**
* Sets the debugging flag.
*
* @param debugF True to turn on debugging.
*/
public void setDebug( boolean debugF )
{
this.debug = debugF;
}
public void setLongFileMode( int longFileMode )
{
this.longFileMode = longFileMode;
}
/**
* Get the record size being used by this stream's TarBuffer.
*
* @return The TarBuffer record size.
*/
public int getRecordSize()
{
return this.buffer.getRecordSize();
}
/**
* Ends the TAR archive and closes the underlying OutputStream. This means
* that finish() is called followed by calling the TarBuffer's close().
*
* @exception IOException Description of Exception
*/
public void close()
throws IOException
{
this.finish();
this.buffer.close();
}
/**
* Close an entry. This method MUST be called for all file entries that
* contain data. The reason is that we must buffer data written to the
* stream in order to satisfy the buffer's record based writes. Thus, there
* may be data fragments still being assembled that must be written to the
* output stream before this entry is closed and the next entry written.
*
* @exception IOException Description of Exception
*/
public void closeEntry()
throws IOException
{
if( this.assemLen > 0 )
{
for( int i = this.assemLen; i < this.assemBuf.length; ++i )
{
this.assemBuf[ i ] = 0;
}
this.buffer.writeRecord( this.assemBuf );
this.currBytes += this.assemLen;
this.assemLen = 0;
}
if( this.currBytes < this.currSize )
{
throw new IOException( "entry closed at '" + this.currBytes
+ "' before the '" + this.currSize
+ "' bytes specified in the header were written" );
}
}
/**
* Ends the TAR archive without closing the underlying OutputStream. The
* result is that the EOF record of nulls is written.
*
* @exception IOException Description of Exception
*/
public void finish()
throws IOException
{
this.writeEOFRecord();
}
/**
* Put an entry on the output stream. This writes the entry's header record
* and positions the output stream for writing the contents of the entry.
* Once this method is called, the stream is ready for calls to write() to
* write the entry's contents. Once the contents are written, closeEntry()
* MUST be called to ensure that all buffered data is completely
* written to the output stream.
*
* @param entry The TarEntry to be written to the archive.
* @exception IOException Description of Exception
*/
public void putNextEntry( TarEntry entry )
throws IOException
{
if( entry.getName().length() >= TarConstants.NAMELEN )
{
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 );
putNextEntry( longLinkEntry );
write( entry.getName().getBytes() );
write( 0 );
closeEntry();
}
else if( longFileMode != LONGFILE_TRUNCATE )
{
throw new RuntimeException( "file name '" + entry.getName()
+ "' is too long ( > "
+ TarConstants.NAMELEN + " bytes)" );
}
}
entry.writeEntryHeader( this.recordBuf );
this.buffer.writeRecord( this.recordBuf );
this.currBytes = 0;
if( entry.isDirectory() )
{
this.currSize = 0;
}
else
{
this.currSize = (int)entry.getSize();
}
}
/**
* Writes a byte to the current tar archive entry. This method simply calls
* read( byte[], int, int ).
*
* @param b The byte written.
* @exception IOException Description of Exception
*/
public void write( int b )
throws IOException
{
this.oneBuf[ 0 ] = (byte)b;
this.write( this.oneBuf, 0, 1 );
}
/**
* Writes bytes to the current tar archive entry. This method simply calls
* write( byte[], int, int ).
*
* @param wBuf The buffer to write to the archive.
* @exception IOException Description of Exception
*/
public void write( byte[] wBuf )
throws IOException
{
this.write( wBuf, 0, wBuf.length );
}
/**
* Writes bytes to the current tar archive entry. This method is aware of
* the current entry and will throw an exception if you attempt to write
* bytes past the length specified for the current entry. The method is also
* (painfully) aware of the record buffering required by TarBuffer, and
* manages buffers that are not a multiple of recordsize in length,
* including assembling records from small buffers.
*
* @param wBuf The buffer to write to the archive.
* @param wOffset The offset in the buffer from which to get bytes.
* @param numToWrite The number of bytes to write.
* @exception IOException Description of Exception
*/
public void write( byte[] wBuf, int wOffset, int numToWrite )
throws IOException
{
if( ( this.currBytes + numToWrite ) > this.currSize )
{
throw new IOException( "request to write '" + numToWrite
+ "' bytes exceeds size in header of '"
+ this.currSize + "' bytes" );
//
// We have to deal with assembly!!!
// The programmer can be writing little 32 byte chunks for all
// we know, and we must assemble complete records for writing.
// REVIEW Maybe this should be in TarBuffer? Could that help to
// eliminate some of the buffer copying.
//
}
if( this.assemLen > 0 )
{
if( ( this.assemLen + numToWrite ) >= this.recordBuf.length )
{
int aLen = this.recordBuf.length - this.assemLen;
System.arraycopy( this.assemBuf, 0, this.recordBuf, 0,
this.assemLen );
System.arraycopy( wBuf, wOffset, this.recordBuf,
this.assemLen, aLen );
this.buffer.writeRecord( this.recordBuf );
this.currBytes += this.recordBuf.length;
wOffset += aLen;
numToWrite -= aLen;
this.assemLen = 0;
}
else
{
System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen,
numToWrite );
wOffset += numToWrite;
this.assemLen += numToWrite;
numToWrite -= numToWrite;
}
}
//
// When we get here we have EITHER:
// o An empty "assemble" buffer.
// o No bytes to write (numToWrite == 0)
//
while( numToWrite > 0 )
{
if( numToWrite < this.recordBuf.length )
{
System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen,
numToWrite );
this.assemLen += numToWrite;
break;
}
this.buffer.writeRecord( wBuf, wOffset );
int num = this.recordBuf.length;
this.currBytes += num;
numToWrite -= num;
wOffset += num;
}
}
/**
* Write an EOF (end of archive) record to the tar archive. An EOF record
* consists of a record of all zeros.
*
* @exception IOException Description of Exception
*/
private void writeEOFRecord()
throws IOException
{
for( int i = 0; i < this.recordBuf.length; ++i )
{
this.recordBuf[ i ] = 0;
}
this.buffer.writeRecord( this.recordBuf );
}
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarUtils.java
Index: TarUtils.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.tar;
/**
* This class provides static utility methods to work with byte streams.
*
* @author Timothy Gerard Endres time@ice.com
* @author Stefano Mazzocchi
* stefano@apache.org
*/
public class TarUtils
{
/**
* Parse the checksum octal integer from a header buffer.
*
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
* @param value Description of Parameter
* @param buf Description of Parameter
* @return The integer value of the entry's checksum.
*/
public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
{
getOctalBytes( value, buf, offset, length );
buf[ offset + length - 1 ] = (byte)' ';
buf[ offset + length - 2 ] = 0;
return offset + length;
}
/**
* Parse an octal long integer from a header buffer.
*
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
* @param value Description of Parameter
* @param buf Description of Parameter
* @return The long value of the octal bytes.
*/
public static int getLongOctalBytes( long value, byte[] buf, int offset, int length )
{
byte[] temp = new byte[ length + 1 ];
getOctalBytes( value, temp, 0, length + 1 );
System.arraycopy( temp, 0, buf, offset, length );
return offset + length;
}
/**
* Determine the number of bytes in an entry name.
*
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
* @param name Description of Parameter
* @param buf Description of Parameter
* @return The number of bytes in a header's entry name.
*/
public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
{
int i;
for( i = 0; i < length && i < name.length(); ++i )
{
buf[ offset + i ] = (byte)name.charAt( i );
}
for( ; i < length; ++i )
{
buf[ offset + i ] = 0;
}
return offset + length;
}
/**
* Parse an octal integer from a header buffer.
*
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
* @param value Description of Parameter
* @param buf Description of Parameter
* @return The integer value of the octal bytes.
*/
public static int getOctalBytes( long value, byte[] buf, int offset, int length )
{
byte[] result = new byte[ length ];
int idx = length - 1;
buf[ offset + idx ] = 0;
--idx;
buf[ offset + idx ] = (byte)' ';
--idx;
if( value == 0 )
{
buf[ offset + idx ] = (byte)'0';
--idx;
}
else
{
for( long val = value; idx >= 0 && val > 0; --idx )
{
buf[ offset + idx ] = (byte)( (byte)'0' + (byte)( val & 7 ) );
val = val >> 3;
}
}
for( ; idx >= 0; --idx )
{
buf[ offset + idx ] = (byte)' ';
}
return offset + length;
}
/**
* Compute the checksum of a tar entry header.
*
* @param buf The tar entry's header buffer.
* @return The computed checksum.
*/
public static long computeCheckSum( byte[] buf )
{
long sum = 0;
for( int i = 0; i < buf.length; ++i )
{
sum += 255 & buf[ i ];
}
return sum;
}
/**
* Parse an entry name from a header buffer.
*
* @param header The header 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.
*/
public static StringBuffer parseName( byte[] header, int offset, int length )
{
StringBuffer result = new StringBuffer( length );
int end = offset + length;
for( int i = offset; i < end; ++i )
{
if( header[ i ] == 0 )
{
break;
}
result.append( (char)header[ i ] );
}
return result;
}
/**
* Parse an octal string from a header buffer. This is used for the file
* permission mode value.
*
* @param header The header 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 string.
*/
public static long parseOctal( byte[] header, int offset, int length )
{
long result = 0;
boolean stillPadding = true;
int end = offset + length;
for( int i = offset; i < end; ++i )
{
if( header[ i ] == 0 )
{
break;
}
if( header[ i ] == (byte)' ' || header[ i ] == '0' )
{
if( stillPadding )
{
continue;
}
if( header[ i ] == (byte)' ' )
{
break;
}
}
stillPadding = false;
result = ( result << 3 ) + ( header[ i ] - '0' );
}
return result;
}
}
1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/AsiExtraField.java
Index: AsiExtraField.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.aut.zip;
import java.util.zip.CRC32;
import java.util.zip.ZipException;
/**
* Adds Unix file permission and UID/GID fields as well as symbolic link
* handling.
* Value Size Description
* ----- ---- -----------
* (Unix3) 0x756e Short tag for this extra block type
* TSize Short total data size for this block
* CRC Long CRC-32 of the remaining data
* Mode Short file permissions
* SizDev Long symlink'd size OR major/minor dev num
* UID Short user ID
* GID Short group ID
* (var.) variable symbolic link filename
*
taken from appnote.iz (Info-ZIP note, 981119) found at
* ftp://ftp.uu.net/pub/archiving/zip/doc/
* * Short is two bytes and Long is four bytes in big endian byte and word order, * device numbers are currently not supported.
* * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { private final static ZipShort HEADER_ID = new ZipShort( 0x756E ); /** * Standard Unix stat(2) file mode. * * @since 1.1 */ private int mode = 0; /** * User ID. * * @since 1.1 */ private int uid = 0; /** * Group ID. * * @since 1.1 */ private int gid = 0; /** * File this entry points to, if it is a symbolic link.* * empty string - if entry is not a symbolic link.
* * @since 1.1 */ private String link = ""; /** * Is this an entry for a directory? * * @since 1.1 */ private boolean dirFlag = false; /** * Instance used to calculate checksums. * * @since 1.1 */ private CRC32 crc = new CRC32(); public AsiExtraField() { } /** * Indicate whether this entry is a directory. * * @param dirFlag The new Directory value * @since 1.1 */ public void setDirectory( boolean dirFlag ) { this.dirFlag = dirFlag; mode = getMode( mode ); } /** * Set the group id. * * @param gid The new GroupId value * @since 1.1 */ public void setGroupId( int gid ) { this.gid = gid; } /** * Indicate that this entry is a symbolic link to the given filename. * * @param name Name of the file this entry links to, empty String if it is * not a symbolic link. * @since 1.1 */ public void setLinkedFile( String name ) { link = name; mode = getMode( mode ); } /** * File mode of this file. * * @param mode The new Mode value * @since 1.1 */ public void setMode( int mode ) { this.mode = getMode( mode ); } /** * Set the user id. * * @param uid The new UserId value * @since 1.1 */ public void setUserId( int uid ) { this.uid = uid; } /** * Delegate to local file data. * * @return The CentralDirectoryData value * @since 1.1 */ public byte[] getCentralDirectoryData() { return getLocalFileDataData(); } /** * Delegate to local file data. * * @return The CentralDirectoryLength value * @since 1.1 */ public ZipShort getCentralDirectoryLength() { return getLocalFileDataLength(); } /** * Get the group id. * * @return The GroupId value * @since 1.1 */ public int getGroupId() { return gid; } /** * The Header-ID. * * @return The HeaderId value * @since 1.1 */ public ZipShort getHeaderId() { return HEADER_ID; } /** * Name of linked file * * @return name of the file this entry links to if it is a symbolic link, * the empty string otherwise. * @since 1.1 */ public String getLinkedFile() { return link; } /** * The actual data to put into local file data - without Header-ID or length * specifier. * * @return The LocalFileDataData value * @since 1.1 */ public byte[] getLocalFileDataData() { // CRC will be added later byte[] data = new byte[ getLocalFileDataLength().getValue() - 4 ]; System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 ); byte[] linkArray = getLinkedFile().getBytes(); System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(), 0, data, 2, 4 ); System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(), 0, data, 6, 2 ); System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(), 0, data, 8, 2 ); System.arraycopy( linkArray, 0, data, 10, linkArray.length ); crc.reset(); crc.update( data ); long checksum = crc.getValue(); byte[] result = new byte[ data.length + 4 ]; System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 ); System.arraycopy( data, 0, result, 4, data.length ); return result; } /** * Length of the extra field in the local file data - without Header-ID or * length specifier. * * @return The LocalFileDataLength value * @since 1.1 */ public ZipShort getLocalFileDataLength() { return new ZipShort( 4// CRC + 2// Mode + 4// SizDev + 2// UID + 2// GID + getLinkedFile().getBytes().length ); } /** * File mode of this file. * * @return The Mode value * @since 1.1 */ public int getMode() { return mode; } /** * Get the user id. * * @return The UserId value * @since 1.1 */ public int getUserId() { return uid; } /** * Is this entry a directory? * * @return The Directory value * @since 1.1 */ public boolean isDirectory() { return dirFlag && !isLink(); } /** * Is this entry a symbolic link? * * @return The Link value * @since 1.1 */ public boolean isLink() { return getLinkedFile().length() != 0; } /** * Populate data from this array as if it was in local file data. * * @param data Description of Parameter * @param offset Description of Parameter * @param length Description of Parameter * @exception ZipException Description of Exception * @since 1.1 */ public void parseFromLocalFileData( byte[] data, int offset, int length ) throws ZipException { long givenChecksum = ( new ZipLong( data, offset ) ).getValue(); byte[] tmp = new byte[ length - 4 ]; System.arraycopy( data, offset + 4, tmp, 0, length - 4 ); crc.reset(); crc.update( tmp ); long realChecksum = crc.getValue(); if( givenChecksum != realChecksum ) { throw new ZipException( "bad CRC checksum " + Long.toHexString( givenChecksum ) + " instead of " + Long.toHexString( realChecksum ) ); } int newMode = ( new ZipShort( tmp, 0 ) ).getValue(); byte[] linkArray = new byte[ (int)( new ZipLong( tmp, 2 ) ).getValue() ]; uid = ( new ZipShort( tmp, 6 ) ).getValue(); gid = ( new ZipShort( tmp, 8 ) ).getValue(); if( linkArray.length == 0 ) { link = ""; } else { System.arraycopy( tmp, 10, linkArray, 0, linkArray.length ); link = new String( linkArray ); } setDirectory( ( newMode & DIR_FLAG ) != 0 ); setMode( newMode ); } /** * Get the file mode for given permissions with the correct file type. * * @param mode Description of Parameter * @return The Mode value * @since 1.1 */ protected int getMode( int mode ) { int type = FILE_FLAG; if( isLink() ) { type = LINK_FLAG; } else if( isDirectory() ) { type = DIR_FLAG; } return type | ( mode & PERM_MASK ); } } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ExtraFieldUtils.java Index: ExtraFieldUtils.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; import java.util.ArrayList; import java.util.Hashtable; import java.util.zip.ZipException; /** * ZipExtraField related methods * * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class ExtraFieldUtils { /** * Static registry of known extra fields. * * @since 1.1 */ private static Hashtable implementations; static { implementations = new Hashtable(); register( AsiExtraField.class ); } /** * Create an instance of the approriate ExtraField, falls back to {@link * UnrecognizedExtraField UnrecognizedExtraField}. * * @param headerId Description of Parameter * @return Description of the Returned Value * @exception InstantiationException Description of Exception * @exception IllegalAccessException Description of Exception * @since 1.1 */ public static ZipExtraField createExtraField( ZipShort headerId ) throws InstantiationException, IllegalAccessException { Class c = (Class)implementations.get( headerId ); if( c != null ) { return (ZipExtraField)c.newInstance(); } UnrecognizedExtraField u = new UnrecognizedExtraField(); u.setHeaderId( headerId ); return u; } /** * Merges the central directory fields of the given ZipExtraFields. * * @param data Description of Parameter * @return Description of the Returned Value * @since 1.1 */ public static byte[] mergeCentralDirectoryData( ZipExtraField[] data ) { int sum = 4 * data.length; for( int i = 0; i < data.length; i++ ) { sum += data[ i ].getCentralDirectoryLength().getValue(); } byte[] result = new byte[ sum ]; int start = 0; for( int i = 0; i < data.length; i++ ) { System.arraycopy( data[ i ].getHeaderId().getBytes(), 0, result, start, 2 ); System.arraycopy( data[ i ].getCentralDirectoryLength().getBytes(), 0, result, start + 2, 2 ); byte[] local = data[ i ].getCentralDirectoryData(); System.arraycopy( local, 0, result, start + 4, local.length ); start += ( local.length + 4 ); } return result; } /** * Merges the local file data fields of the given ZipExtraFields. * * @param data Description of Parameter * @return Description of the Returned Value * @since 1.1 */ public static byte[] mergeLocalFileDataData( ZipExtraField[] data ) { int sum = 4 * data.length; for( int i = 0; i < data.length; i++ ) { sum += data[ i ].getLocalFileDataLength().getValue(); } byte[] result = new byte[ sum ]; int start = 0; for( int i = 0; i < data.length; i++ ) { System.arraycopy( data[ i ].getHeaderId().getBytes(), 0, result, start, 2 ); System.arraycopy( data[ i ].getLocalFileDataLength().getBytes(), 0, result, start + 2, 2 ); byte[] local = data[ i ].getLocalFileDataData(); System.arraycopy( local, 0, result, start + 4, local.length ); start += ( local.length + 4 ); } return result; } /** * Split the array into ExtraFields and populate them with the give data. * * @param data Description of Parameter * @return Description of the Returned Value * @exception ZipException Description of Exception * @since 1.1 */ public static ZipExtraField[] parse( byte[] data ) throws ZipException { ArrayList v = new ArrayList(); int start = 0; while( start <= data.length - 4 ) { ZipShort headerId = new ZipShort( data, start ); int length = ( new ZipShort( data, start + 2 ) ).getValue(); if( start + 4 + length > data.length ) { throw new ZipException( "data starting at " + start + " is in unknown format" ); } try { ZipExtraField ze = createExtraField( headerId ); ze.parseFromLocalFileData( data, start + 4, length ); v.add( ze ); } catch( InstantiationException ie ) { throw new ZipException( ie.getMessage() ); } catch( IllegalAccessException iae ) { throw new ZipException( iae.getMessage() ); } start += ( length + 4 ); } if( start != data.length ) {// array not exhausted throw new ZipException( "data starting at " + start + " is in unknown format" ); } final ZipExtraField[] result = new ZipExtraField[ v.size() ]; return (ZipExtraField[])v.toArray( result ); } /** * Register a ZipExtraField implementation.* * The given class must have a no-arg constructor and implement the {@link * ZipExtraField ZipExtraField interface}.
* * @param c Description of Parameter * @since 1.1 */ public static void register( Class c ) { try { ZipExtraField ze = (ZipExtraField)c.newInstance(); implementations.put( ze.getHeaderId(), c ); } catch( ClassCastException cc ) { throw new RuntimeException( c + " doesn\'t implement ZipExtraField" ); } catch( InstantiationException ie ) { throw new RuntimeException( c + " is not a concrete class" ); } catch( IllegalAccessException ie ) { throw new RuntimeException( c + "\'s no-arg constructor is not public" ); } } } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/UnixStat.java Index: UnixStat.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; /** * Constants from stat.h on Unix systems. * * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public interface UnixStat { /** * Bits used for permissions (and sticky bit) * * @since 1.1 */ int PERM_MASK = 07777; /** * Indicates symbolic links. * * @since 1.1 */ int LINK_FLAG = 0120000; /** * Indicates plain files. * * @since 1.1 */ int FILE_FLAG = 0100000; /** * Indicates directories. * * @since 1.1 */ int DIR_FLAG = 040000; // ---------------------------------------------------------- // somewhat arbitrary choices that are quite common for shared // installations // ----------------------------------------------------------- /** * Default permissions for symbolic links. * * @since 1.1 */ int DEFAULT_LINK_PERM = 0777; /** * Default permissions for directories. * * @since 1.1 */ int DEFAULT_DIR_PERM = 0755; /** * Default permissions for plain files. * * @since 1.1 */ int DEFAULT_FILE_PERM = 0644; } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/UnrecognizedExtraField.java Index: UnrecognizedExtraField.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; /** * Simple placeholder for all those extra fields we don't want to deal with.* * Assumes local file data and central directory entries are identical - unless * told the opposite.
* * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class UnrecognizedExtraField implements ZipExtraField { /** * Extra field data in central directory - without Header-ID or length * specifier. * * @since 1.1 */ private byte[] centralData; /** * The Header-ID. * * @since 1.1 */ private ZipShort headerId; /** * Extra field data in local file data - without Header-ID or length * specifier. * * @since 1.1 */ private byte[] localData; public void setCentralDirectoryData( byte[] data ) { centralData = data; } public void setHeaderId( ZipShort headerId ) { this.headerId = headerId; } public void setLocalFileDataData( byte[] data ) { localData = data; } public byte[] getCentralDirectoryData() { if( centralData != null ) { return centralData; } return getLocalFileDataData(); } public ZipShort getCentralDirectoryLength() { if( centralData != null ) { return new ZipShort( centralData.length ); } return getLocalFileDataLength(); } public ZipShort getHeaderId() { return headerId; } public byte[] getLocalFileDataData() { return localData; } public ZipShort getLocalFileDataLength() { return new ZipShort( localData.length ); } public void parseFromLocalFileData( byte[] data, int offset, int length ) { byte[] tmp = new byte[ length ]; System.arraycopy( data, offset, tmp, 0, length ); setLocalFileDataData( tmp ); } } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipEntry.java Index: ZipEntry.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.zip.ZipException; /** * Extension that adds better handling of extra fields and provides access to * the internal and external file attributes. * * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class ZipEntry extends java.util.zip.ZipEntry { /** * Helper for JDK 1.1 * * @since 1.2 */ private static Method setCompressedSizeMethod = null; /** * Helper for JDK 1.1 * * @since 1.2 */ private static Object lockReflection = new Object(); /** * Helper for JDK 1.1 * * @since 1.2 */ private static boolean triedToGetMethod = false; private int internalAttributes = 0; private long externalAttributes = 0; private ArrayList extraFields = new ArrayList(); /** * Helper for JDK 1.1 <-> 1.2 incompatibility. * * @since 1.2 */ private Long compressedSize = null; /** * Creates a new zip entry with the specified name. * * @param name Description of Parameter * @since 1.1 */ public ZipEntry( String name ) { super( name ); } /** * Creates a new zip entry with fields taken from the specified zip entry. * * @param entry Description of Parameter * @exception ZipException Description of Exception * @since 1.1 */ public ZipEntry( java.util.zip.ZipEntry entry ) throws ZipException { /* * REVISIT: call super(entry) instead of this stuff in Ant2, * "copy constructor" has not been available in JDK 1.1 */ super( entry.getName() ); setComment( entry.getComment() ); setMethod( entry.getMethod() ); setTime( entry.getTime() ); long size = entry.getSize(); if( size > 0 ) { setSize( size ); } long cSize = entry.getCompressedSize(); if( cSize > 0 ) { setComprSize( cSize ); } long crc = entry.getCrc(); if( crc > 0 ) { setCrc( crc ); } byte[] extra = entry.getExtra(); if( extra != null ) { setExtraFields( ExtraFieldUtils.parse( extra ) ); } else { // initializes extra data to an empty byte array setExtra(); } } /** * Creates a new zip entry with fields taken from the specified zip entry. * * @param entry Description of Parameter * @exception ZipException Description of Exception * @since 1.1 */ public ZipEntry( ZipEntry entry ) throws ZipException { this( (java.util.zip.ZipEntry)entry ); setInternalAttributes( entry.getInternalAttributes() ); setExternalAttributes( entry.getExternalAttributes() ); setExtraFields( entry.getExtraFields() ); } /** * Try to get a handle to the setCompressedSize method. * * @since 1.2 */ private static void checkSCS() { if( !triedToGetMethod ) { synchronized( lockReflection ) { triedToGetMethod = true; try { setCompressedSizeMethod = java.util.zip.ZipEntry.class.getMethod( "setCompressedSize", new Class[]{Long.TYPE} ); } catch( NoSuchMethodException nse ) { } } } } /** * Are we running JDK 1.2 or higher? * * @return Description of the Returned Value * @since 1.2 */ private static boolean haveSetCompressedSize() { checkSCS(); return setCompressedSizeMethod != null; } /** * Invoke setCompressedSize via reflection. * * @param ze Description of Parameter * @param size Description of Parameter * @since 1.2 */ private static void performSetCompressedSize( ZipEntry ze, long size ) { Long[] s = {new Long( size )}; try { setCompressedSizeMethod.invoke( ze, s ); } catch( InvocationTargetException ite ) { Throwable nested = ite.getTargetException(); throw new RuntimeException( "Exception setting the compressed size " + "of " + ze + ": " + nested.getMessage() ); } catch( Throwable other ) { throw new RuntimeException( "Exception setting the compressed size " + "of " + ze + ": " + other.getMessage() ); } } /** * Make this class work in JDK 1.1 like a 1.2 class.* * This either stores the size for later usage or invokes setCompressedSize * via reflection.
* * @param size The new ComprSize value * @since 1.2 */ public void setComprSize( long size ) { if( haveSetCompressedSize() ) { performSetCompressedSize( this, size ); } else { compressedSize = new Long( size ); } } /** * Sets the external file attributes. * * @param value The new ExternalAttributes value * @since 1.1 */ public void setExternalAttributes( long value ) { externalAttributes = value; } /** * Throws an Exception if extra data cannot be parsed into extra fields. * * @param extra The new Extra value * @exception RuntimeException Description of Exception * @since 1.1 */ public void setExtra( byte[] extra ) throws RuntimeException { try { setExtraFields( ExtraFieldUtils.parse( extra ) ); } catch( Exception e ) { throw new RuntimeException( e.getMessage() ); } } /** * Replaces all currently attached extra fields with the new array. * * @param fields The new ExtraFields value * @since 1.1 */ public void setExtraFields( ZipExtraField[] fields ) { extraFields.clear(); for( int i = 0; i < fields.length; i++ ) { extraFields.add( fields[ i ] ); } setExtra(); } /** * Sets the internal file attributes. * * @param value The new InternalAttributes value * @since 1.1 */ public void setInternalAttributes( int value ) { internalAttributes = value; } /** * Retrieves the extra data for the central directory. * * @return The CentralDirectoryExtra value * @since 1.1 */ public byte[] getCentralDirectoryExtra() { return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() ); } /** * Override to make this class work in JDK 1.1 like a 1.2 class. * * @return The CompressedSize value * @since 1.2 */ public long getCompressedSize() { if( compressedSize != null ) { // has been set explicitly and we are running in a 1.1 VM return compressedSize.longValue(); } return super.getCompressedSize(); } /** * Retrieves the external file attributes. * * @return The ExternalAttributes value * @since 1.1 */ public long getExternalAttributes() { return externalAttributes; } /** * Retrieves extra fields. * * @return The ExtraFields value * @since 1.1 */ public ZipExtraField[] getExtraFields() { final ZipExtraField[] result = new ZipExtraField[ extraFields.size() ]; return (ZipExtraField[])extraFields.toArray( result ); } /** * Retrieves the internal file attributes. * * @return The InternalAttributes value * @since 1.1 */ public int getInternalAttributes() { return internalAttributes; } /** * Retrieves the extra data for the local file data. * * @return The LocalFileDataExtra value * @since 1.1 */ public byte[] getLocalFileDataExtra() { byte[] extra = getExtra(); return extra != null ? extra : new byte[ 0 ]; } /** * Adds an extra fields - replacing an already present extra field of the * same type. * * @param ze The feature to be added to the ExtraField attribute * @since 1.1 */ public void addExtraField( ZipExtraField ze ) { ZipShort type = ze.getHeaderId(); boolean done = false; for( int i = 0; !done && i < extraFields.size(); i++ ) { if( ( (ZipExtraField)extraFields.get( i ) ).getHeaderId().equals( type ) ) { extraFields.set( i, ze ); done = true; } } if( !done ) { extraFields.add( ze ); } setExtra(); } /** * Overwrite clone * * @return Description of the Returned Value * @since 1.1 */ public Object clone() { ZipEntry e = null; try { e = new ZipEntry( (java.util.zip.ZipEntry)super.clone() ); } catch( Exception ex ) { // impossible as extra data is in correct format ex.printStackTrace(); } e.setInternalAttributes( getInternalAttributes() ); e.setExternalAttributes( getExternalAttributes() ); e.setExtraFields( getExtraFields() ); return e; } /** * Remove an extra fields. * * @param type Description of Parameter * @since 1.1 */ public void removeExtraField( ZipShort type ) { boolean done = false; for( int i = 0; !done && i < extraFields.size(); i++ ) { if( ( (ZipExtraField)extraFields.get( i ) ).getHeaderId().equals( type ) ) { extraFields.remove( i ); done = true; } } if( !done ) { throw new java.util.NoSuchElementException(); } setExtra(); } /** * Unfortunately {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} seems to access the extra data directly, * so overriding getExtra doesn't help - we need to modify super's data * directly. * * @since 1.1 */ protected void setExtra() { super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) ); } } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipExtraField.java Index: ZipExtraField.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; import java.util.zip.ZipException; /** * General format of extra field data.* * Extra fields usually appear twice per file, once in the local file data and * once in the central directory. Usually they are the same, but they don't have * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} * will only use the local file data in both places.
* * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public interface ZipExtraField { /** * The Header-ID. * * @return The HeaderId value * @since 1.1 */ ZipShort getHeaderId(); /** * Length of the extra field in the local file data - without Header-ID or * length specifier. * * @return The LocalFileDataLength value * @since 1.1 */ ZipShort getLocalFileDataLength(); /** * Length of the extra field in the central directory - without Header-ID or * length specifier. * * @return The CentralDirectoryLength value * @since 1.1 */ ZipShort getCentralDirectoryLength(); /** * The actual data to put into local file data - without Header-ID or length * specifier. * * @return The LocalFileDataData value * @since 1.1 */ byte[] getLocalFileDataData(); /** * The actual data to put central directory - without Header-ID or length * specifier. * * @return The CentralDirectoryData value * @since 1.1 */ byte[] getCentralDirectoryData(); /** * Populate data from this array as if it was in local file data. * * @param data Description of Parameter * @param offset Description of Parameter * @param length Description of Parameter * @exception ZipException Description of Exception * @since 1.1 */ void parseFromLocalFileData( byte[] data, int offset, int length ) throws ZipException; } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipLong.java Index: ZipLong.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; /** * Utility class that represents a four byte integer with conversion rules for * the big endian byte order of ZIP files. * * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class ZipLong implements Cloneable { private long value; /** * Create instance from a number. * * @param value Description of Parameter * @since 1.1 */ public ZipLong( long value ) { this.value = value; } /** * Create instance from bytes. * * @param bytes Description of Parameter * @since 1.1 */ public ZipLong( byte[] bytes ) { this( bytes, 0 ); } /** * Create instance from the four bytes starting at offset. * * @param bytes Description of Parameter * @param offset Description of Parameter * @since 1.1 */ public ZipLong( byte[] bytes, int offset ) { value = ( bytes[ offset + 3 ] << 24 ) & 0xFF000000l; value += ( bytes[ offset + 2 ] << 16 ) & 0xFF0000; value += ( bytes[ offset + 1 ] << 8 ) & 0xFF00; value += ( bytes[ offset ] & 0xFF ); } /** * Get value as two bytes in big endian byte order. * * @return The Bytes value * @since 1.1 */ public byte[] getBytes() { byte[] result = new byte[ 4 ]; result[ 0 ] = (byte)( ( value & 0xFF ) ); result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 ); result[ 2 ] = (byte)( ( value & 0xFF0000 ) >> 16 ); result[ 3 ] = (byte)( ( value & 0xFF000000l ) >> 24 ); return result; } /** * Get value as Java int. * * @return The Value value * @since 1.1 */ public long getValue() { return value; } /** * Override to make two instances with same value equal. * * @param o Description of Parameter * @return Description of the Returned Value * @since 1.1 */ public boolean equals( Object o ) { if( o == null || !( o instanceof ZipLong ) ) { return false; } return value == ( (ZipLong)o ).getValue(); } /** * Override to make two instances with same value equal. * * @return Description of the Returned Value * @since 1.1 */ public int hashCode() { return (int)value; } }// ZipLong 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipOutputStream.java Index: ZipOutputStream.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.ZipException; /** * Reimplementation of {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} that does handle the extended functionality of * this package, especially internal/external file attributes and extra fields * with different layouts for local file data and central directory entries.* * This implementation will use a Data Descriptor to store size and CRC * information for DEFLATED entries, this means, you don't need to calculate * them yourself. Unfortunately this is not possible for the STORED method, here * setting the CRC and uncompressed size information is required before {@link * #putNextEntry putNextEntry} will be called.
* * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class ZipOutputStream extends DeflaterOutputStream { /** * Helper, a 0 as ZipShort. * * @since 1.1 */ private final static byte[] ZERO = {0, 0}; /** * Helper, a 0 as ZipLong. * * @since 1.1 */ private final static byte[] LZERO = {0, 0, 0, 0}; /** * Compression method for deflated entries. * * @since 1.1 */ public final static int DEFLATED = ZipEntry.DEFLATED; /** * Compression method for deflated entries. * * @since 1.1 */ public final static int STORED = ZipEntry.STORED; /* * Various ZIP constants */ /** * local file header signature * * @since 1.1 */ protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); /** * data descriptor signature * * @since 1.1 */ protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L ); /** * central file header signature * * @since 1.1 */ protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); /** * end of central dir signature * * @since 1.1 */ protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); /** * Smallest date/time ZIP can handle. * * @since 1.1 */ private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); /** * The file comment. * * @since 1.1 */ private String comment = ""; /** * Compression level for next entry. * * @since 1.1 */ private int level = Deflater.DEFAULT_COMPRESSION; /** * Default compression method for next entry. * * @since 1.1 */ private int method = DEFLATED; /** * List of ZipEntries written so far. * * @since 1.1 */ private ArrayList entries = new ArrayList(); /** * CRC instance to avoid parsing DEFLATED data twice. * * @since 1.1 */ private CRC32 crc = new CRC32(); /** * Count the bytes written to out. * * @since 1.1 */ private long written = 0; /** * Data for current entry started here. * * @since 1.1 */ private long dataStart = 0; /** * Start of central directory. * * @since 1.1 */ private ZipLong cdOffset = new ZipLong( 0 ); /** * Length of central directory. * * @since 1.1 */ private ZipLong cdLength = new ZipLong( 0 ); /** * Holds the offsets of the LFH starts for each entry * * @since 1.1 */ private Hashtable offsets = new Hashtable(); /** * The encoding to use for filenames and the file comment.* * For a list of possible values see * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html * . Defaults to the platform's default character encoding.
* * @since 1.3 */ private String encoding = null; /** * Current entry. * * @since 1.1 */ private ZipEntry entry; /** * Creates a new ZIP OutputStream filtering the underlying stream. * * @param out Description of Parameter * @since 1.1 */ public ZipOutputStream( OutputStream out ) { super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); } /** * Convert a Date object to a DOS date/time field.
*
* Stolen from InfoZip's fileio.c
* * For a list of possible values see * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html * . Defaults to the platform's default character encoding.
* * @param encoding The new Encoding value * @since 1.3 */ public void setEncoding( String encoding ) { this.encoding = encoding; } /** * Sets the compression level for subsequent entries.* * Default is Deflater.DEFAULT_COMPRESSION.
* * @param level The new Level value * @since 1.1 */ public void setLevel( int level ) { this.level = level; } /** * Sets the default compression method for subsequent entries.* * Default is DEFLATED.
* * @param method The new Method value * @since 1.1 */ public void setMethod( int method ) { this.method = method; } /** * The encoding to use for filenames and the file comment. * * @return null if using the platform's default character encoding. * @since 1.3 */ public String getEncoding() { return encoding; } /** * Writes all necessary data for this entry. * * @exception IOException Description of Exception * @since 1.1 */ public void closeEntry() throws IOException { if( entry == null ) { return; } long realCrc = crc.getValue(); crc.reset(); if( entry.getMethod() == DEFLATED ) { def.finish(); while( !def.finished() ) { deflate(); } entry.setSize( def.getTotalIn() ); entry.setComprSize( def.getTotalOut() ); entry.setCrc( realCrc ); def.reset(); written += entry.getCompressedSize(); } else { if( entry.getCrc() != realCrc ) { throw new ZipException( "bad CRC checksum for entry " + entry.getName() + ": " + Long.toHexString( entry.getCrc() ) + " instead of " + Long.toHexString( realCrc ) ); } if( entry.getSize() != written - dataStart ) { throw new ZipException( "bad size for entry " + entry.getName() + ": " + entry.getSize() + " instead of " + ( written - dataStart ) ); } } writeDataDescriptor( entry ); entry = null; } /* * Found out by experiment, that DeflaterOutputStream.close() * will call finish() - so we don't need to override close * ourselves. */ /** * Finishs writing the contents and closes this as well as the underlying * stream. * * @exception IOException Description of Exception * @since 1.1 */ public void finish() throws IOException { closeEntry(); cdOffset = new ZipLong( written ); for( int i = 0; i < entries.size(); i++ ) { writeCentralFileHeader( (ZipEntry)entries.get( i ) ); } cdLength = new ZipLong( written - cdOffset.getValue() ); writeCentralDirectoryEnd(); offsets.clear(); entries.clear(); } /** * Begin writing next entry. * * @param ze Description of Parameter * @exception IOException Description of Exception * @since 1.1 */ public void putNextEntry( ZipEntry ze ) throws IOException { closeEntry(); entry = ze; entries.add( entry ); if( entry.getMethod() == -1 ) {// not specified entry.setMethod( method ); } if( entry.getTime() == -1 ) {// not specified entry.setTime( System.currentTimeMillis() ); } if( entry.getMethod() == STORED ) { if( entry.getSize() == -1 ) { throw new ZipException( "uncompressed size is required for STORED method" ); } if( entry.getCrc() == -1 ) { throw new ZipException( "crc checksum is required for STORED method" ); } entry.setComprSize( entry.getSize() ); } else { def.setLevel( level ); } writeLocalFileHeader( entry ); } /** * Writes bytes to ZIP entry.* * Override is necessary to support STORED entries, as well as calculationg * CRC automatically for DEFLATED entries.
* * @param b Description of Parameter * @param offset Description of Parameter * @param length Description of Parameter * @exception IOException Description of Exception */ public void write( byte[] b, int offset, int length ) throws IOException { if( entry.getMethod() == DEFLATED ) { super.write( b, offset, length ); } else { out.write( b, offset, length ); written += length; } crc.update( b, offset, length ); } /** * Retrieve the bytes for the given String in the encoding set for this * Stream. * * @param name Description of Parameter * @return The Bytes value * @exception ZipException Description of Exception * @since 1.3 */ protected byte[] getBytes( String name ) throws ZipException { if( encoding == null ) { return name.getBytes(); } else { try { return name.getBytes( encoding ); } catch( UnsupportedEncodingException uee ) { throw new ZipException( uee.getMessage() ); } } } /** * Writes the "End of central dir record" * * @exception IOException Description of Exception * @since 1.1 */ protected void writeCentralDirectoryEnd() throws IOException { out.write( EOCD_SIG.getBytes() ); // disk numbers out.write( ZERO ); out.write( ZERO ); // number of entries byte[] num = ( new ZipShort( entries.size() ) ).getBytes(); out.write( num ); out.write( num ); // length and location of CD out.write( cdLength.getBytes() ); out.write( cdOffset.getBytes() ); // ZIP file comment byte[] data = getBytes( comment ); out.write( ( new ZipShort( data.length ) ).getBytes() ); out.write( data ); } /** * Writes the central file header entry * * @param ze Description of Parameter * @exception IOException Description of Exception * @since 1.1 */ protected void writeCentralFileHeader( ZipEntry ze ) throws IOException { out.write( CFH_SIG.getBytes() ); written += 4; // version made by out.write( ( new ZipShort( 20 ) ).getBytes() ); written += 2; // version needed to extract // general purpose bit flag if( ze.getMethod() == DEFLATED ) { // requires version 2 as we are going to store length info // in the data descriptor out.write( ( new ZipShort( 20 ) ).getBytes() ); // bit3 set to signal, we use a data descriptor out.write( ( new ZipShort( 8 ) ).getBytes() ); } else { out.write( ( new ZipShort( 10 ) ).getBytes() ); out.write( ZERO ); } written += 4; // compression method out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); written += 2; // last mod. time and date out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); written += 4; // CRC // compressed length // uncompressed length out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() ); out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); written += 12; // file name length byte[] name = getBytes( ze.getName() ); out.write( ( new ZipShort( name.length ) ).getBytes() ); written += 2; // extra field length byte[] extra = ze.getCentralDirectoryExtra(); out.write( ( new ZipShort( extra.length ) ).getBytes() ); written += 2; // file comment length String comm = ze.getComment(); if( comm == null ) { comm = ""; } byte[] comment = getBytes( comm ); out.write( ( new ZipShort( comment.length ) ).getBytes() ); written += 2; // disk number start out.write( ZERO ); written += 2; // internal file attributes out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() ); written += 2; // external file attributes out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() ); written += 4; // relative offset of LFH out.write( ( (ZipLong)offsets.get( ze ) ).getBytes() ); written += 4; // file name out.write( name ); written += name.length; // extra field out.write( extra ); written += extra.length; // file comment out.write( comment ); written += comment.length; } /** * Writes the data descriptor entry * * @param ze Description of Parameter * @exception IOException Description of Exception * @since 1.1 */ protected void writeDataDescriptor( ZipEntry ze ) throws IOException { if( ze.getMethod() != DEFLATED ) { return; } out.write( DD_SIG.getBytes() ); out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); written += 16; } /** * Writes the local file header entry * * @param ze Description of Parameter * @exception IOException Description of Exception * @since 1.1 */ protected void writeLocalFileHeader( ZipEntry ze ) throws IOException { offsets.put( ze, new ZipLong( written ) ); out.write( LFH_SIG.getBytes() ); written += 4; // version needed to extract // general purpose bit flag if( ze.getMethod() == DEFLATED ) { // requires version 2 as we are going to store length info // in the data descriptor out.write( ( new ZipShort( 20 ) ).getBytes() ); // bit3 set to signal, we use a data descriptor out.write( ( new ZipShort( 8 ) ).getBytes() ); } else { out.write( ( new ZipShort( 10 ) ).getBytes() ); out.write( ZERO ); } written += 4; // compression method out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); written += 2; // last mod. time and date out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); written += 4; // CRC // compressed length // uncompressed length if( ze.getMethod() == DEFLATED ) { out.write( LZERO ); out.write( LZERO ); out.write( LZERO ); } else { out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); } written += 12; // file name length byte[] name = getBytes( ze.getName() ); out.write( ( new ZipShort( name.length ) ).getBytes() ); written += 2; // extra field length byte[] extra = ze.getLocalFileDataExtra(); out.write( ( new ZipShort( extra.length ) ).getBytes() ); written += 2; // file name out.write( name ); written += name.length; // extra field out.write( extra ); written += extra.length; dataStart = written; } } 1.1 jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipShort.java Index: ZipShort.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software License * version 1.1, a copy of which has been included with this distribution in * the LICENSE.txt file. */ package org.apache.aut.zip; /** * Utility class that represents a two byte integer with conversion rules for * the big endian byte order of ZIP files. * * @author Stefan Bodewig * @version $Revision: 1.1 $ */ public class ZipShort implements Cloneable { private int value; /** * Create instance from a number. * * @param value Description of Parameter * @since 1.1 */ public ZipShort( int value ) { this.value = value; } /** * Create instance from bytes. * * @param bytes Description of Parameter * @since 1.1 */ public ZipShort( byte[] bytes ) { this( bytes, 0 ); } /** * Create instance from the two bytes starting at offset. * * @param bytes Description of Parameter * @param offset Description of Parameter * @since 1.1 */ public ZipShort( byte[] bytes, int offset ) { value = ( bytes[ offset + 1 ] << 8 ) & 0xFF00; value += ( bytes[ offset ] & 0xFF ); } /** * Get value as two bytes in big endian byte order. * * @return The Bytes value * @since 1.1 */ public byte[] getBytes() { byte[] result = new byte[ 2 ]; result[ 0 ] = (byte)( value & 0xFF ); result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 ); return result; } /** * Get value as Java int. * * @return The Value value * @since 1.1 */ public int getValue() { return value; } /** * Override to make two instances with same value equal. * * @param o Description of Parameter * @return Description of the Returned Value * @since 1.1 */ public boolean equals( Object o ) { if( o == null || !( o instanceof ZipShort ) ) { return false; } return value == ( (ZipShort)o ).getValue(); } /** * Override to make two instances with same value equal. * * @return Description of the Returned Value * @since 1.1 */ public int hashCode() { return value; } }// ZipShort -- To unsubscribe, e-mail: