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