/*
* Created on Oct 8, 2012
* Created by Paul Gardner
*
* Copyright 2012 Vuze, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gudy.azureus2.core3.util;
import java.io.FileInputStream;
import java.io.IOException;
public class
RARTOCDecoder
{
private DataProvider provider;
public
RARTOCDecoder(
DataProvider _provider )
{
provider = _provider;
}
public void
analyse(
TOCResultHandler result_handler )
throws IOException
{
try{
analyseSupport( result_handler );
result_handler.complete();
}catch( Throwable e ){
IOException ioe;
if ( e instanceof IOException ){
ioe = (IOException)e ;
}else{
ioe = new IOException( "Analysis failed: " + Debug.getNestedExceptionMessage( e ));
}
result_handler.failed( ioe );
throw( ioe );
}
}
private void
analyseSupport(
TOCResultHandler result_handler )
throws IOException
{
// http://acritum.com/winrar/rar-format
byte[] header_buffer = new byte[7]; // marker block always 7 bytes
readFully( header_buffer );
if ( !new String( header_buffer ).startsWith( "Rar!" )){
throw( new IOException( "Not a rar file" ));
}
// read archive header
readFully( header_buffer );
int archive_header_size = getShort( header_buffer, 5 );
if ( archive_header_size > 1024 ){
throw( new IOException( "Invalid archive header" ));
}
provider.skip( archive_header_size - 7 ); // skip over archive header
while( true ){
// read next 7 bytes of header record
int read = provider.read( header_buffer );
if ( read < 7 ){
// seen some short archive, just bail
break;
}
int block_type = header_buffer[2]&0xff;
int entry_flags = getShort( header_buffer, 3 );
int header_size = getShort( header_buffer, 5 );
//System.out.println( "type=" + Integer.toString( block_type, 16 ) + ", flags: " + Integer.toString( entry_flags, 16 ) + ", hs=" + header_size);
if ( block_type < 0x70 || block_type > 0x90 ){
throw( new IOException( "invalid header, archive corrupted"));
}
// ignore crc in first 2 bytes
if ( block_type == 0x74 ){
boolean password = ( entry_flags & 0x004 ) != 0;
/* in theory if this is set then there should be an optional 4 byte ADD_SIZE here but it doesn't work out
if ( ( entry_flags & 0x8000 ) != 0 ){
provider.skip( 4 );
}
*/
byte[] buffer = new byte[25]; // read up until potential optional HIGH_PACK entries
readFully( buffer );
long comp_size = getInteger( buffer, 0 ); // pack size
long act_size = getInteger( buffer, 4 ); // uncompressed size
// 1 byte host_os
// 4 bytes crc
// 4 bytes file time
// 1 byte unrar version
// 1 byte method
// total = 11
int extended_length = 0;
if ( ( entry_flags & 0x0100 ) != 0 ){
// extended size info available
extended_length = 8;
byte[] extended_size_info = new byte[8];
readFully( extended_size_info );
comp_size |= getInteger( extended_size_info, 0 ) << 32;
act_size |= getInteger( extended_size_info, 4 ) << 32;
}
// 19 - 4-comp+4-act+11
int name_length = getShort( buffer, 19 );
if ( name_length > 32*1024 ){
throw( new IOException( "name length too large: " + name_length ));
}
// 4 byte attr
byte[] name = new byte[name_length];
readFully( name );
String decoded_name;
if ( ( entry_flags & 0x0200 ) != 0 ){
int zero_pos = -1;
for (int i=0;i<name.length;i++){
if ( name[i] == 0 ){
zero_pos = i;
break;
}
}
if ( zero_pos == -1 ){
decoded_name = new String( name, "UTF-8" );
}else{
decoded_name = decodeName( name, zero_pos + 1 );
if ( decoded_name == null ){
decoded_name = new String( name, 0, zero_pos , "UTF-8" );
}
}
}else{
decoded_name = new String( name, "UTF-8" );
}
if ( ( entry_flags & 0xe0 ) == 0xe0 ){
// directory
}else{
result_handler.entryRead( decoded_name, act_size, password );
}
provider.skip( header_size - ( 7 + 25 + extended_length + name_length ) + comp_size );
}else if ( block_type == 0x7b ){
// end of archive
break;
}else{
provider.skip( archive_header_size - 7 );
if ( ( entry_flags & 0x8000 ) != 0 ){
provider.skip( 4 );
}
}
}
}
private String
decodeName(
byte[] b_data,
int pos )
{
try{
int Flags = 0;
int FlagBits = 0;
byte[] Name = b_data;
byte[] EncName = b_data;
int EncSize = b_data.length;
int MaxDecSize = 4096;
int[] NameW = new int[MaxDecSize];
int EncPos = pos;
int DecPos = 0;
byte HighByte = EncName[EncPos++];
while ( EncPos<EncSize && DecPos<MaxDecSize ){
if ( FlagBits ==0 ){
Flags = EncName[EncPos++];
FlagBits = 8;
}
switch((Flags>>6)&0x03){
case 0:{
NameW[DecPos++]=EncName[EncPos++]&0xff;
break;
}
case 1:{
NameW[DecPos++]=(EncName[EncPos++]&0xff)+((HighByte<<8)&0xff00);
break;
}
case 2:{
NameW[DecPos++]=(EncName[EncPos++]&0xff)+((EncName[EncPos++]<<8)&0xff00);
break;
}
case 3:{
int Length = EncName[EncPos++]&0xff;
if ((Length & 0x80) != 0){
byte Correction = EncName[EncPos++];
for (Length=(Length&0x7f)+2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++){
NameW[DecPos]=((Name[DecPos]+Correction)&0xff)+((HighByte<<8)&0xff00);
}
}else{
for (Length+=2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++){
NameW[DecPos]=Name[DecPos]&0xff;
}
}
break;
}
}
Flags <<=2;
FlagBits -=2;
}
byte[] temp = new byte[DecPos*2];
for (int i=0;i<DecPos;i++){
temp[i*2] = (byte)(( NameW[i]>>8 ) & 0xff);
temp[i*2+1] = (byte)(( NameW[i] ) & 0xff);
}
return( new String( temp, "UTF-16BE" ));
}catch( Throwable e ){
Debug.outNoStack( "Failed to decode name: " + ByteFormatter.encodeString( b_data ) + " - " + Debug.getNestedExceptionMessage( e ));
return( null );
}
}
private void
readFully(
byte[] buffer )
throws IOException
{
if ( provider.read( buffer ) != buffer.length ){
throw( new IOException( "unexpected end-of-file" ));
}
}
public interface
TOCResultHandler
{
public void
entryRead(
String name,
long size,
boolean password )
throws IOException;
public void
complete();
public void
failed(
IOException error );
}
public interface
DataProvider
{
public int
read(
byte[] buffer )
throws IOException;
public void
skip(
long bytes )
throws IOException;
}
public static void
main(
String[] args )
{
try{
final FileInputStream fis = new FileInputStream( "C:\\temp\\mp.part6.rar" );
RARTOCDecoder decoder =
new RARTOCDecoder(
new DataProvider()
{
public int
read(
byte[] buffer )
throws IOException
{
return( fis.read( buffer ));
}
public void
skip(
long bytes )
throws IOException
{
fis.skip( bytes );
}
});
decoder.analyse(
new TOCResultHandler()
{
public void
entryRead(
String name,
long size,
boolean password )
{
System.out.println( name + ": " + size + (password?" protected":""));
}
public void
complete()
{
System.out.println( "complete" );
}
public void
failed(
IOException error )
{
System.out.println( "failed: " + Debug.getNestedExceptionMessage( error ));
}
});
}catch( Throwable e ){
e.printStackTrace();
}
}
private static int
getShort(
byte[] buffer,
int pos )
{
return((( buffer[pos+1] << 8 ) & 0xff00 ) | ( buffer[pos] & 0xff ));
}
private static long
getInteger(
byte[] buffer,
int pos )
{
return(((( buffer[pos+3] << 24 ) & 0xff000000 ) |
(( buffer[pos+2] << 16 ) & 0xff0000 ) |
(( buffer[pos+1] << 8 ) & 0xff00 ) |
( buffer[pos] & 0xff )) & 0xffffffffL );
}
}