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