/*
* Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
* Original author: Edmund Wagner
* Creation date: 22.05.2007
*
* Source: $HeadURL$
* Last changed: $LastChangedDate$
*
* the unrar licence applies to all junrar source and binary distributions
* you are not allowed to use this source to re-create the RAR compression
* algorithm
*
* Here some html entities which can be used for escaping javadoc tags:
* "&": "&" or "&"
* "<": "<" or "<"
* ">": ">" or ">"
* "@": "@"
*/
package com.github.junrar;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.github.junrar.exception.RarException;
import com.github.junrar.exception.RarException.RarExceptionType;
import com.github.junrar.impl.FileVolumeManager;
import com.github.junrar.io.IReadOnlyAccess;
import com.github.junrar.rarfile.AVHeader;
import com.github.junrar.rarfile.BaseBlock;
import com.github.junrar.rarfile.BlockHeader;
import com.github.junrar.rarfile.CommentHeader;
import com.github.junrar.rarfile.EAHeader;
import com.github.junrar.rarfile.EndArcHeader;
import com.github.junrar.rarfile.FileHeader;
import com.github.junrar.rarfile.MacInfoHeader;
import com.github.junrar.rarfile.MainHeader;
import com.github.junrar.rarfile.MarkHeader;
import com.github.junrar.rarfile.ProtectHeader;
import com.github.junrar.rarfile.SignHeader;
import com.github.junrar.rarfile.SubBlockHeader;
import com.github.junrar.rarfile.UnixOwnersHeader;
import com.github.junrar.rarfile.UnrarHeadertype;
import com.github.junrar.unpack.ComprDataIO;
import com.github.junrar.unpack.Unpack;
/**
* The Main Rar Class; represents a rar Archive
*
* @author $LastChangedBy$
* @version $LastChangedRevision$
*/
public class Archive implements Closeable {
private static Logger logger = Logger.getLogger(Archive.class.getName());
private IReadOnlyAccess rof;
private final UnrarCallback unrarCallback;
private final ComprDataIO dataIO;
private final List<BaseBlock> headers = new ArrayList<BaseBlock>();
private MarkHeader markHead = null;
private MainHeader newMhd = null;
private Unpack unpack;
private int currentHeaderIndex;
/** Size of packed data in current file. */
private long totalPackedSize = 0L;
/** Number of bytes of compressed data read from current file. */
private long totalPackedRead = 0L;
private VolumeManager volumeManager;
private Volume volume;
public Archive(VolumeManager volumeManager) throws RarException,
IOException {
this(volumeManager, null);
}
/**
* create a new archive object using the given {@link VolumeManager}
*
* @param volumeManager
* the the {@link VolumeManager} that will provide volume stream
* data
* @throws com.github.junrar.exception.RarException
*/
public Archive(VolumeManager volumeManager, UnrarCallback unrarCallback)
throws RarException, IOException {
this.volumeManager = volumeManager;
this.unrarCallback = unrarCallback;
setVolume(this.volumeManager.nextArchive(this, null));
dataIO = new ComprDataIO(this);
}
public Archive(File firstVolume) throws RarException, IOException {
this(new FileVolumeManager(firstVolume), null);
}
public Archive(File firstVolume, UnrarCallback unrarCallback)
throws RarException, IOException {
this(new FileVolumeManager(firstVolume), unrarCallback);
}
// public File getFile() {
// return file;
// }
//
// void setFile(File file) throws IOException {
// this.file = file;
// setFile(new ReadOnlyAccessFile(file), file.length());
// }
private void setFile(IReadOnlyAccess file, long length) throws IOException {
totalPackedSize = 0L;
totalPackedRead = 0L;
close();
rof = file;
try {
readHeaders(length);
} catch (Exception e) {
logger.log(Level.WARNING,
"exception in archive constructor maybe file is encrypted "
+ "or currupt", e);
// ignore exceptions to allow exraction of working files in
// corrupt archive
}
// Calculate size of packed data
for (BaseBlock block : headers) {
if (block.getHeaderType() == UnrarHeadertype.FileHeader) {
totalPackedSize += ((FileHeader) block).getFullPackSize();
}
}
if (unrarCallback != null) {
unrarCallback.volumeProgressChanged(totalPackedRead,
totalPackedSize);
}
}
public void bytesReadRead(int count) {
if (count > 0) {
totalPackedRead += count;
if (unrarCallback != null) {
unrarCallback.volumeProgressChanged(totalPackedRead,
totalPackedSize);
}
}
}
public IReadOnlyAccess getRof() {
return rof;
}
/**
* @return returns all file headers of the archive
*/
public List<FileHeader> getFileHeaders() {
List<FileHeader> list = new ArrayList<FileHeader>();
for (BaseBlock block : headers) {
if (block.getHeaderType().equals(UnrarHeadertype.FileHeader)) {
list.add((FileHeader) block);
}
}
return list;
}
public FileHeader nextFileHeader() {
int n = headers.size();
while (currentHeaderIndex < n) {
BaseBlock block = headers.get(currentHeaderIndex++);
if (block.getHeaderType() == UnrarHeadertype.FileHeader) {
return (FileHeader) block;
}
}
return null;
}
public UnrarCallback getUnrarCallback() {
return unrarCallback;
}
/**
*
* @return whether the archive is encrypted
*/
public boolean isEncrypted() {
if (newMhd != null) {
return newMhd.isEncrypted();
} else {
throw new NullPointerException("mainheader is null");
}
}
/**
* Read the headers of the archive
*
* @param fileLength
* Length of file.
* @throws com.github.junrar.exception.RarException
*/
private void readHeaders(long fileLength) throws IOException, RarException {
markHead = null;
newMhd = null;
headers.clear();
currentHeaderIndex = 0;
int toRead = 0;
while (true) {
int size = 0;
long newpos = 0;
byte[] baseBlockBuffer = new byte[BaseBlock.BaseBlockSize];
long position = rof.getPosition();
// Weird, but is trying to read beyond the end of the file
if (position >= fileLength) {
break;
}
// logger.info("\n--------reading header--------");
size = rof.readFully(baseBlockBuffer, BaseBlock.BaseBlockSize);
if (size == 0) {
break;
}
BaseBlock block = new BaseBlock(baseBlockBuffer);
block.setPositionInFile(position);
switch (block.getHeaderType()) {
case MarkHeader:
markHead = new MarkHeader(block);
if (!markHead.isSignature()) {
throw new RarException(
RarExceptionType.badRarArchive);
}
headers.add(markHead);
// markHead.print();
break;
case MainHeader:
toRead = block.hasEncryptVersion() ? MainHeader.mainHeaderSizeWithEnc
: MainHeader.mainHeaderSize;
byte[] mainbuff = new byte[toRead];
rof.readFully(mainbuff, toRead);
MainHeader mainhead = new MainHeader(block, mainbuff);
headers.add(mainhead);
this.newMhd = mainhead;
if (newMhd.isEncrypted()) {
throw new RarException(
RarExceptionType.rarEncryptedException);
}
// mainhead.print();
break;
case SignHeader:
toRead = SignHeader.signHeaderSize;
byte[] signBuff = new byte[toRead];
rof.readFully(signBuff, toRead);
SignHeader signHead = new SignHeader(block, signBuff);
headers.add(signHead);
// logger.info("HeaderType: SignHeader");
break;
case AvHeader:
toRead = AVHeader.avHeaderSize;
byte[] avBuff = new byte[toRead];
rof.readFully(avBuff, toRead);
AVHeader avHead = new AVHeader(block, avBuff);
headers.add(avHead);
// logger.info("headertype: AVHeader");
break;
case CommHeader:
toRead = CommentHeader.commentHeaderSize;
byte[] commBuff = new byte[toRead];
rof.readFully(commBuff, toRead);
CommentHeader commHead = new CommentHeader(block, commBuff);
headers.add(commHead);
// logger.info("method: "+commHead.getUnpMethod()+"; 0x"+
// Integer.toHexString(commHead.getUnpMethod()));
newpos = commHead.getPositionInFile()
+ commHead.getHeaderSize();
rof.setPosition(newpos);
break;
case EndArcHeader:
toRead = 0;
if (block.hasArchiveDataCRC()) {
toRead += EndArcHeader.endArcArchiveDataCrcSize;
}
if (block.hasVolumeNumber()) {
toRead += EndArcHeader.endArcVolumeNumberSize;
}
EndArcHeader endArcHead;
if (toRead > 0) {
byte[] endArchBuff = new byte[toRead];
rof.readFully(endArchBuff, toRead);
endArcHead = new EndArcHeader(block, endArchBuff);
// logger.info("HeaderType: endarch\ndatacrc:"+
// endArcHead.getArchiveDataCRC());
} else {
// logger.info("HeaderType: endarch - no Data");
endArcHead = new EndArcHeader(block, null);
}
headers.add(endArcHead);
// logger.info("\n--------end header--------");
return;
default:
byte[] blockHeaderBuffer = new byte[BlockHeader.blockHeaderSize];
rof.readFully(blockHeaderBuffer, BlockHeader.blockHeaderSize);
BlockHeader blockHead = new BlockHeader(block,
blockHeaderBuffer);
switch (blockHead.getHeaderType()) {
case NewSubHeader:
case FileHeader:
toRead = blockHead.getHeaderSize()
- BlockHeader.BaseBlockSize
- BlockHeader.blockHeaderSize;
byte[] fileHeaderBuffer = new byte[toRead];
rof.readFully(fileHeaderBuffer, toRead);
FileHeader fh = new FileHeader(blockHead, fileHeaderBuffer);
headers.add(fh);
newpos = fh.getPositionInFile() + fh.getHeaderSize()
+ fh.getFullPackSize();
rof.setPosition(newpos);
break;
case ProtectHeader:
toRead = blockHead.getHeaderSize()
- BlockHeader.BaseBlockSize
- BlockHeader.blockHeaderSize;
byte[] protectHeaderBuffer = new byte[toRead];
rof.readFully(protectHeaderBuffer, toRead);
ProtectHeader ph = new ProtectHeader(blockHead,
protectHeaderBuffer);
newpos = ph.getPositionInFile() + ph.getHeaderSize()
+ ph.getDataSize();
rof.setPosition(newpos);
break;
case SubHeader: {
byte[] subHeadbuffer = new byte[SubBlockHeader.SubBlockHeaderSize];
rof.readFully(subHeadbuffer,
SubBlockHeader.SubBlockHeaderSize);
SubBlockHeader subHead = new SubBlockHeader(blockHead,
subHeadbuffer);
subHead.print();
switch (subHead.getSubType()) {
case MAC_HEAD: {
byte[] macHeaderbuffer = new byte[MacInfoHeader.MacInfoHeaderSize];
rof.readFully(macHeaderbuffer,
MacInfoHeader.MacInfoHeaderSize);
MacInfoHeader macHeader = new MacInfoHeader(subHead,
macHeaderbuffer);
macHeader.print();
headers.add(macHeader);
break;
}
// TODO implement other subheaders
case BEEA_HEAD:
break;
case EA_HEAD: {
byte[] eaHeaderBuffer = new byte[EAHeader.EAHeaderSize];
rof.readFully(eaHeaderBuffer, EAHeader.EAHeaderSize);
EAHeader eaHeader = new EAHeader(subHead,
eaHeaderBuffer);
eaHeader.print();
headers.add(eaHeader);
break;
}
case NTACL_HEAD:
break;
case STREAM_HEAD:
break;
case UO_HEAD:
toRead = subHead.getHeaderSize();
toRead -= BaseBlock.BaseBlockSize;
toRead -= BlockHeader.blockHeaderSize;
toRead -= SubBlockHeader.SubBlockHeaderSize;
byte[] uoHeaderBuffer = new byte[toRead];
rof.readFully(uoHeaderBuffer, toRead);
UnixOwnersHeader uoHeader = new UnixOwnersHeader(
subHead, uoHeaderBuffer);
uoHeader.print();
headers.add(uoHeader);
break;
default:
break;
}
break;
}
default:
logger.warning("Unknown Header");
throw new RarException(RarExceptionType.notRarArchive);
}
}
// logger.info("\n--------end header--------");
}
}
/**
* Extract the file specified by the given header and write it to the
* supplied output stream
*
* @param header
* the header to be extracted
* @param os
* the outputstream
* @throws com.github.junrar.exception.RarException
*/
public void extractFile(FileHeader hd, OutputStream os) throws RarException {
if (!headers.contains(hd)) {
throw new RarException(RarExceptionType.headerNotInArchive);
}
try {
doExtractFile(hd, os);
} catch (Exception e) {
if (e instanceof RarException) {
throw (RarException) e;
} else {
throw new RarException(e);
}
}
}
/**
* Returns an {@link java.io.InputStream} that will allow to read the file and
* stream it. Please note that this method will create a new Thread and an a
* pair of Pipe streams.
*
* @param header
* the header to be extracted
* @throws com.github.junrar.exception.RarException
* @throws java.io.IOException
* if any IO error occur
*/
public InputStream getInputStream(final FileHeader hd) throws RarException,
IOException {
final PipedInputStream in = new PipedInputStream(32 * 1024);
final PipedOutputStream out = new PipedOutputStream(in);
// creates a new thread that will write data to the pipe. Data will be
// available in another InputStream, connected to the OutputStream.
new Thread(new Runnable() {
public void run() {
try {
extractFile(hd, out);
} catch (RarException e) {
} finally {
try {
out.close();
} catch (IOException e) {
}
}
}
}).start();
return in;
}
private void doExtractFile(FileHeader hd, OutputStream os)
throws RarException, IOException {
dataIO.init(os);
dataIO.init(hd);
dataIO.setUnpFileCRC(this.isOldFormat() ? 0 : 0xffFFffFF);
if (unpack == null) {
unpack = new Unpack(dataIO);
}
if (!hd.isSolid()) {
unpack.init(null);
}
unpack.setDestSize(hd.getFullUnpackSize());
try {
unpack.doUnpack(hd.getUnpVersion(), hd.isSolid());
// Verify file CRC
hd = dataIO.getSubHeader();
long actualCRC = hd.isSplitAfter() ? ~dataIO.getPackedCRC()
: ~dataIO.getUnpFileCRC();
int expectedCRC = hd.getFileCRC();
if (actualCRC != expectedCRC) {
throw new RarException(RarExceptionType.crcError);
}
// if (!hd.isSplitAfter()) {
// // Verify file CRC
// if(~dataIO.getUnpFileCRC() != hd.getFileCRC()){
// throw new RarException(RarExceptionType.crcError);
// }
// }
} catch (Exception e) {
unpack.cleanUp();
if (e instanceof RarException) {
// throw new RarException((RarException)e);
throw (RarException) e;
} else {
throw new RarException(e);
}
}
}
/**
* @return returns the main header of this archive
*/
public MainHeader getMainHeader() {
return newMhd;
}
/**
* @return whether the archive is old format
*/
public boolean isOldFormat() {
return markHead.isOldFormat();
}
/** Close the underlying compressed file. */
public void close() throws IOException {
if (rof != null) {
rof.close();
rof = null;
}
if (unpack != null) {
unpack.cleanUp();
}
}
/**
* @return the volumeManager
*/
public VolumeManager getVolumeManager() {
return volumeManager;
}
/**
* @param volumeManager
* the volumeManager to set
*/
public void setVolumeManager(VolumeManager volumeManager) {
this.volumeManager = volumeManager;
}
/**
* @return the volume
*/
public Volume getVolume() {
return volume;
}
/**
* @param volume
* the volume to set
* @throws java.io.IOException
*/
public void setVolume(Volume volume) throws IOException {
this.volume = volume;
setFile(volume.getReadOnlyAccess(), volume.getLength());
}
}