1 /* 2 * Created on Oct 8, 2012 3 * Created by Paul Gardner 4 * 5 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 */ 19 20 package org.gudy.azureus2.core3.util; 21 22 23 import java.io.FileInputStream; 24 import java.io.IOException; 25 26 public class 27 RARTOCDecoder 28 { 29 private DataProvider provider; 30 31 public RARTOCDecoder( DataProvider _provider )32 RARTOCDecoder( 33 DataProvider _provider ) 34 { 35 provider = _provider; 36 } 37 38 public void analyse( TOCResultHandler result_handler )39 analyse( 40 TOCResultHandler result_handler ) 41 42 throws IOException 43 { 44 try{ 45 analyseSupport( result_handler ); 46 47 result_handler.complete(); 48 49 }catch( Throwable e ){ 50 51 IOException ioe; 52 53 if ( e instanceof IOException ){ 54 55 ioe = (IOException)e ; 56 57 }else{ 58 59 ioe = new IOException( "Analysis failed: " + Debug.getNestedExceptionMessage( e )); 60 } 61 62 result_handler.failed( ioe ); 63 64 throw( ioe ); 65 } 66 } 67 68 private void analyseSupport( TOCResultHandler result_handler )69 analyseSupport( 70 TOCResultHandler result_handler ) 71 72 throws IOException 73 { 74 // http://acritum.com/winrar/rar-format 75 76 byte[] header_buffer = new byte[7]; // marker block always 7 bytes 77 78 readFully( header_buffer ); 79 80 if ( !new String( header_buffer ).startsWith( "Rar!" )){ 81 82 throw( new IOException( "Not a rar file" )); 83 } 84 85 // read archive header 86 87 readFully( header_buffer ); 88 89 int archive_header_size = getShort( header_buffer, 5 ); 90 91 if ( archive_header_size > 1024 ){ 92 93 throw( new IOException( "Invalid archive header" )); 94 } 95 96 provider.skip( archive_header_size - 7 ); // skip over archive header 97 98 while( true ){ 99 100 // read next 7 bytes of header record 101 102 int read = provider.read( header_buffer ); 103 104 if ( read < 7 ){ 105 106 // seen some short archive, just bail 107 108 break; 109 } 110 111 int block_type = header_buffer[2]&0xff; 112 113 int entry_flags = getShort( header_buffer, 3 ); 114 int header_size = getShort( header_buffer, 5 ); 115 116 //System.out.println( "type=" + Integer.toString( block_type, 16 ) + ", flags: " + Integer.toString( entry_flags, 16 ) + ", hs=" + header_size); 117 118 if ( block_type < 0x70 || block_type > 0x90 ){ 119 120 throw( new IOException( "invalid header, archive corrupted")); 121 } 122 123 // ignore crc in first 2 bytes 124 125 if ( block_type == 0x74 ){ 126 127 boolean password = ( entry_flags & 0x004 ) != 0; 128 129 /* in theory if this is set then there should be an optional 4 byte ADD_SIZE here but it doesn't work out 130 if ( ( entry_flags & 0x8000 ) != 0 ){ 131 132 provider.skip( 4 ); 133 } 134 */ 135 136 byte[] buffer = new byte[25]; // read up until potential optional HIGH_PACK entries 137 138 readFully( buffer ); 139 140 long comp_size = getInteger( buffer, 0 ); // pack size 141 long act_size = getInteger( buffer, 4 ); // uncompressed size 142 143 // 1 byte host_os 144 // 4 bytes crc 145 // 4 bytes file time 146 // 1 byte unrar version 147 // 1 byte method 148 // total = 11 149 150 int extended_length = 0; 151 152 if ( ( entry_flags & 0x0100 ) != 0 ){ 153 154 // extended size info available 155 156 extended_length = 8; 157 158 byte[] extended_size_info = new byte[8]; 159 160 readFully( extended_size_info ); 161 162 comp_size |= getInteger( extended_size_info, 0 ) << 32; 163 act_size |= getInteger( extended_size_info, 4 ) << 32; 164 } 165 166 // 19 - 4-comp+4-act+11 167 168 int name_length = getShort( buffer, 19 ); 169 170 if ( name_length > 32*1024 ){ 171 172 173 throw( new IOException( "name length too large: " + name_length )); 174 } 175 176 // 4 byte attr 177 178 byte[] name = new byte[name_length]; 179 180 readFully( name ); 181 182 String decoded_name; 183 184 if ( ( entry_flags & 0x0200 ) != 0 ){ 185 186 int zero_pos = -1; 187 188 for (int i=0;i<name.length;i++){ 189 190 if ( name[i] == 0 ){ 191 192 zero_pos = i; 193 194 break; 195 } 196 } 197 198 if ( zero_pos == -1 ){ 199 200 decoded_name = new String( name, "UTF-8" ); 201 202 }else{ 203 204 decoded_name = decodeName( name, zero_pos + 1 ); 205 206 if ( decoded_name == null ){ 207 208 decoded_name = new String( name, 0, zero_pos , "UTF-8" ); 209 } 210 } 211 }else{ 212 213 decoded_name = new String( name, "UTF-8" ); 214 } 215 216 if ( ( entry_flags & 0xe0 ) == 0xe0 ){ 217 218 // directory 219 220 }else{ 221 222 result_handler.entryRead( decoded_name, act_size, password ); 223 } 224 225 provider.skip( header_size - ( 7 + 25 + extended_length + name_length ) + comp_size ); 226 227 }else if ( block_type == 0x7b ){ 228 229 // end of archive 230 231 break; 232 233 }else{ 234 235 provider.skip( archive_header_size - 7 ); 236 237 if ( ( entry_flags & 0x8000 ) != 0 ){ 238 239 provider.skip( 4 ); 240 } 241 } 242 } 243 } 244 245 private String decodeName( byte[] b_data, int pos )246 decodeName( 247 byte[] b_data, 248 int pos ) 249 { 250 try{ 251 int Flags = 0; 252 int FlagBits = 0; 253 254 byte[] Name = b_data; 255 byte[] EncName = b_data; 256 int EncSize = b_data.length; 257 int MaxDecSize = 4096; 258 259 int[] NameW = new int[MaxDecSize]; 260 261 int EncPos = pos; 262 int DecPos = 0; 263 264 byte HighByte = EncName[EncPos++]; 265 266 while ( EncPos<EncSize && DecPos<MaxDecSize ){ 267 268 if ( FlagBits ==0 ){ 269 270 Flags = EncName[EncPos++]; 271 FlagBits = 8; 272 } 273 274 switch((Flags>>6)&0x03){ 275 276 case 0:{ 277 NameW[DecPos++]=EncName[EncPos++]&0xff; 278 279 break; 280 } 281 case 1:{ 282 NameW[DecPos++]=(EncName[EncPos++]&0xff)+((HighByte<<8)&0xff00); 283 284 break; 285 } 286 case 2:{ 287 NameW[DecPos++]=(EncName[EncPos++]&0xff)+((EncName[EncPos++]<<8)&0xff00); 288 289 break; 290 } 291 case 3:{ 292 int Length = EncName[EncPos++]&0xff; 293 294 if ((Length & 0x80) != 0){ 295 296 byte Correction = EncName[EncPos++]; 297 298 for (Length=(Length&0x7f)+2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++){ 299 300 NameW[DecPos]=((Name[DecPos]+Correction)&0xff)+((HighByte<<8)&0xff00); 301 } 302 }else{ 303 for (Length+=2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++){ 304 305 NameW[DecPos]=Name[DecPos]&0xff; 306 } 307 } 308 309 break; 310 } 311 } 312 313 Flags <<=2; 314 FlagBits -=2; 315 } 316 317 byte[] temp = new byte[DecPos*2]; 318 319 for (int i=0;i<DecPos;i++){ 320 321 temp[i*2] = (byte)(( NameW[i]>>8 ) & 0xff); 322 temp[i*2+1] = (byte)(( NameW[i] ) & 0xff); 323 } 324 325 return( new String( temp, "UTF-16BE" )); 326 327 }catch( Throwable e ){ 328 329 Debug.outNoStack( "Failed to decode name: " + ByteFormatter.encodeString( b_data ) + " - " + Debug.getNestedExceptionMessage( e )); 330 331 return( null ); 332 } 333 } 334 private void readFully( byte[] buffer )335 readFully( 336 byte[] buffer ) 337 338 throws IOException 339 { 340 if ( provider.read( buffer ) != buffer.length ){ 341 342 throw( new IOException( "unexpected end-of-file" )); 343 } 344 } 345 346 public interface 347 TOCResultHandler 348 { 349 public void entryRead( String name, long size, boolean password )350 entryRead( 351 String name, 352 long size, 353 boolean password ) 354 355 throws IOException; 356 357 public void complete()358 complete(); 359 360 public void failed( IOException error )361 failed( 362 IOException error ); 363 } 364 365 public interface 366 DataProvider 367 { 368 public int read( byte[] buffer )369 read( 370 byte[] buffer ) 371 372 throws IOException; 373 374 public void skip( long bytes )375 skip( 376 long bytes ) 377 378 throws IOException; 379 } 380 381 public static void main( String[] args )382 main( 383 String[] args ) 384 { 385 try{ 386 final FileInputStream fis = new FileInputStream( "C:\\temp\\mp.part6.rar" ); 387 388 RARTOCDecoder decoder = 389 new RARTOCDecoder( 390 new DataProvider() 391 { 392 public int 393 read( 394 byte[] buffer ) 395 396 throws IOException 397 { 398 return( fis.read( buffer )); 399 } 400 401 public void 402 skip( 403 long bytes ) 404 405 throws IOException 406 { 407 fis.skip( bytes ); 408 } 409 }); 410 411 decoder.analyse( 412 new TOCResultHandler() 413 { 414 public void 415 entryRead( 416 String name, 417 long size, 418 boolean password ) 419 { 420 System.out.println( name + ": " + size + (password?" protected":"")); 421 } 422 423 public void 424 complete() 425 { 426 System.out.println( "complete" ); 427 } 428 429 public void 430 failed( 431 IOException error ) 432 { 433 System.out.println( "failed: " + Debug.getNestedExceptionMessage( error )); 434 } 435 }); 436 437 }catch( Throwable e ){ 438 439 e.printStackTrace(); 440 } 441 } 442 443 private static int getShort( byte[] buffer, int pos )444 getShort( 445 byte[] buffer, 446 int pos ) 447 { 448 return((( buffer[pos+1] << 8 ) & 0xff00 ) | ( buffer[pos] & 0xff )); 449 } 450 451 private static long getInteger( byte[] buffer, int pos )452 getInteger( 453 byte[] buffer, 454 int pos ) 455 { 456 return(((( buffer[pos+3] << 24 ) & 0xff000000 ) | 457 (( buffer[pos+2] << 16 ) & 0xff0000 ) | 458 (( buffer[pos+1] << 8 ) & 0xff00 ) | 459 ( buffer[pos] & 0xff )) & 0xffffffffL ); 460 } 461 } 462