1 /*
2  * BeDecoder.java
3  *
4  * Created on May 30, 2003, 2:44 PM
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 import java.io.*;
23 import java.nio.ByteBuffer;
24 import java.nio.CharBuffer;
25 import java.nio.charset.CharsetDecoder;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 
33 import com.aelitis.azureus.util.JSONUtils;
34 
35 /**
36  * A set of utility methods to decode a bencoded array of byte into a Map.
37  * integer are represented as Long, String as byte[], dictionnaries as Map, and list as List.
38  *
39  * @author TdC_VgA
40  *
41  */
42 public class BDecoder
43 {
44 	public static final int MAX_BYTE_ARRAY_SIZE		= 100*1024*1024;
45 	private static final int MAX_MAP_KEY_SIZE		= 64*1024;
46 
47 	private static final boolean TRACE	= false;
48 
49 	private boolean recovery_mode;
50 	private boolean	verify_map_order;
51 
52 	private final static byte[]	PORTABLE_ROOT;
53 
54 	static{
55 		byte[]	portable = null;
56 
57 		try{
58 			String root = System.getProperty( "azureus.portable.root", "" );
59 
60 			if ( root.length() > 0 ){
61 
62 				portable = root.getBytes( "UTF-8" );
63 			}
64 		}catch( Throwable e ){
65 
66 			e.printStackTrace();
67 		}
68 
69 		PORTABLE_ROOT = portable;
70 	}
71 
72 	public static Map<String,Object>
decode( byte[] data )73 	decode(
74 		byte[]	data )
75 
76 		throws IOException
77 	{
78 		return( new BDecoder().decodeByteArray( data ));
79 	}
80 
81 	public static Map<String,Object>
decode( byte[] data, int offset, int length )82 	decode(
83 		byte[]	data,
84 		int		offset,
85 		int		length )
86 
87 		throws IOException
88 	{
89 		return( new BDecoder().decodeByteArray( data, offset, length ));
90 	}
91 
92 	public static Map<String,Object>
decode( BufferedInputStream is )93 	decode(
94 		BufferedInputStream	is  )
95 
96 		throws IOException
97 	{
98 		return( new BDecoder().decodeStream( is ));
99 	}
100 
101 
102 	public
BDecoder()103 	BDecoder()
104 	{
105 	}
106 
107 	public Map<String,Object>
decodeByteArray( byte[] data)108 	decodeByteArray(
109 		byte[] data)
110 
111 		throws IOException
112 	{
113 		return( decode(new BDecoderInputStreamArray(data),true));
114 	}
115 
116 	public Map<String, Object>
decodeByteArray( byte[] data, int offset, int length )117 	decodeByteArray(
118 		byte[] 	data,
119 		int		offset,
120 		int		length )
121 
122 		throws IOException
123 	{
124 		return( decode(new BDecoderInputStreamArray(data, offset, length ),true));
125 	}
126 
127 	public Map<String, Object>
decodeByteArray( byte[] data, int offset, int length, boolean internKeys)128 	decodeByteArray(
129 		byte[] 	data,
130 		int		offset,
131 		int		length,
132 		boolean internKeys)
133 
134 		throws IOException
135 	{
136 		return( decode(new BDecoderInputStreamArray(data, offset, length ),internKeys));
137 	}
138 
139 	// used externally
decodeByteBuffer(ByteBuffer buffer, boolean internKeys)140 	public Map<String, Object> decodeByteBuffer(ByteBuffer buffer, boolean internKeys) throws IOException {
141 		InputStream is = new BDecoderInputStreamArray(buffer);
142 		Map<String,Object> result = decode(is,internKeys);
143 		buffer.position(buffer.limit()-is.available());
144 		return result;
145 	}
146 
147 	public Map<String, Object>
decodeStream( BufferedInputStream data )148 	decodeStream(
149 		BufferedInputStream data )
150 
151 		throws IOException
152 	{
153 		return decodeStream(data, true);
154 	}
155 
156 	public Map<String, Object>
decodeStream( BufferedInputStream data, boolean internKeys)157 	decodeStream(
158 		BufferedInputStream data,
159 		boolean internKeys)
160 
161 		throws IOException
162 	{
163 		Object	res = decodeInputStream(data, "", 0, internKeys);
164 
165 		if ( res == null ){
166 
167 			throw( new BEncodingException( "BDecoder: zero length file" ));
168 
169 		}else if ( !(res instanceof Map )){
170 
171 			throw( new BEncodingException( "BDecoder: top level isn't a Map" ));
172 		}
173 
174 		return((Map<String,Object>)res );
175 	}
176 
177 	private Map<String, Object>
decode( InputStream data, boolean internKeys )178 	decode(
179 		InputStream data, boolean internKeys )
180 
181 		throws IOException
182 	{
183 		Object res = decodeInputStream(data, "", 0, internKeys);
184 
185 		if ( res == null ){
186 
187 			throw( new BEncodingException( "BDecoder: zero length file" ));
188 
189 		}else if ( !(res instanceof Map )){
190 
191 			throw( new BEncodingException( "BDecoder: top level isn't a Map" ));
192 		}
193 
194 		return((Map<String, Object>)res );
195 	}
196 
197 	// reuseable objects for key decoding
198 	private ByteBuffer keyBytesBuffer = ByteBuffer.allocate(32);
199 	private CharBuffer keyCharsBuffer = CharBuffer.allocate(32);
200 	private CharsetDecoder keyDecoder = Constants.BYTE_CHARSET.newDecoder();
201 
202 	private Object
decodeInputStream( InputStream dbis, String context, int nesting, boolean internKeys)203 	decodeInputStream(
204 		InputStream dbis,
205 		String		context,
206 		int			nesting,
207 		boolean internKeys)
208 
209 		throws IOException
210 	{
211 		if (nesting == 0 && !dbis.markSupported()) {
212 
213 			throw new IOException("InputStream must support the mark() method");
214 		}
215 
216 			//set a mark
217 
218 		dbis.mark(1);
219 
220 			//read a byte
221 
222 		int tempByte = dbis.read();
223 
224 			//decide what to do
225 
226 		switch (tempByte) {
227 		case 'd' :
228 				//create a new dictionary object
229 
230 			LightHashMap tempMap = new LightHashMap();
231 
232 			try{
233 				byte[]	prev_key = null;
234 
235 					//get the key
236 
237 				while (true) {
238 
239 					dbis.mark(1);
240 
241 					tempByte = dbis.read();
242 					if(tempByte == 'e' || tempByte == -1)
243 						break; // end of map
244 
245 					dbis.reset();
246 
247 					// decode key strings manually so we can reuse the bytebuffer
248 
249 					int keyLength = (int)getPositiveNumberFromStream(dbis, ':');
250 
251 					int skipBytes = 0;
252 
253 					if ( keyLength > MAX_MAP_KEY_SIZE ){
254 						skipBytes = keyLength - MAX_MAP_KEY_SIZE;
255 						keyLength = MAX_MAP_KEY_SIZE;
256 						//new Exception().printStackTrace();
257 						//throw( new IOException( msg ));
258 					}
259 
260 					if(keyLength < keyBytesBuffer.capacity())
261 					{
262 						keyBytesBuffer.position(0).limit(keyLength);
263 						keyCharsBuffer.position(0).limit(keyLength);
264 					} else {
265 						keyBytesBuffer = ByteBuffer.allocate(keyLength);
266 						keyCharsBuffer = CharBuffer.allocate(keyLength);
267 					}
268 
269 					getByteArrayFromStream(dbis, keyLength, keyBytesBuffer.array());
270 
271 					if (skipBytes > 0) {
272 						dbis.skip(skipBytes);
273 					}
274 
275 					if ( verify_map_order ){
276 
277 						byte[] current_key = new byte[keyLength];
278 
279 						System.arraycopy( keyBytesBuffer.array(), 0, current_key, 0, keyLength );
280 
281 						if ( prev_key != null ){
282 
283 							int	len = Math.min( prev_key.length, keyLength );
284 
285 							int	state = 0;
286 
287 							for ( int i=0;i<len;i++){
288 
289 								int	cb = current_key[i]&0x00ff;
290 								int	pb = prev_key[i]&0x00ff;
291 
292 								if ( cb > pb ){
293 									state = 1;
294 									break;
295 								}else if ( cb < pb ){
296 									state = 2;
297 									break;
298 								}
299 							}
300 
301 							if ( state == 0){
302 								if ( prev_key.length > keyLength ){
303 
304 									state = 2;
305 								}
306 							}
307 
308 							if ( state == 2 ){
309 
310 								// Debug.out( "Dictionary order incorrect: prev=" + new String( prev_key ) + ", current=" + new String( current_key ));
311 
312 								if (!( tempMap instanceof LightHashMapEx )){
313 
314 									LightHashMapEx x = new LightHashMapEx( tempMap );
315 
316 									x.setFlag( LightHashMapEx.FL_MAP_ORDER_INCORRECT, true );
317 
318 									tempMap = x;
319 								}
320 							}
321 						}
322 
323 						prev_key = current_key;
324 					}
325 
326 					keyDecoder.reset();
327 					keyDecoder.decode(keyBytesBuffer,keyCharsBuffer,true);
328 					keyDecoder.flush(keyCharsBuffer);
329 					String key = new String(keyCharsBuffer.array(),0,keyCharsBuffer.limit());
330 
331 					// keys often repeat a lot - intern to save space
332 					if (internKeys)
333 						key = StringInterner.intern( key );
334 
335 
336 					//decode value
337 
338 					Object value = decodeInputStream(dbis,key,nesting+1,internKeys);
339 
340 					// value interning is too CPU-intensive, let's skip that for now
341 					/*if(value instanceof byte[] && ((byte[])value).length < 17)
342 					value = StringInterner.internBytes((byte[])value);*/
343 
344 					if ( TRACE ){
345 						System.out.println( key + "->" + value + ";" );
346 					}
347 
348 						// recover from some borked encodings that I have seen whereby the value has
349 						// not been encoded. This results in, for example,
350 						// 18:azureus_propertiesd0:e
351 						// we only get null back here if decoding has hit an 'e' or end-of-file
352 						// that is, there is no valid way for us to get a null 'value' here
353 
354 					if ( value == null ){
355 
356 						System.err.println( "Invalid encoding - value not serialsied for '" + key + "' - ignoring: map so far=" + tempMap + ",loc=" + Debug.getCompressedStackTrace());
357 
358 						break;
359 					}
360 
361 					if (skipBytes > 0) {
362 
363 						String msg = "dictionary key is too large - "
364 								+ (keyLength + skipBytes) + ":, max=" + MAX_MAP_KEY_SIZE
365 								+ ": skipping key starting with " + new String(key.substring(0, 128));
366 						System.err.println( msg );
367 
368 					} else {
369 
370   					if ( tempMap.put( key, value) != null ){
371 
372   						Debug.out( "BDecoder: key '" + key + "' already exists!" );
373   					}
374 					}
375 				}
376 
377 				/*
378 	        if ( tempMap.size() < 8 ){
379 
380 	        	tempMap = new CompactMap( tempMap );
381 	        }*/
382 
383 				dbis.mark(1);
384 				tempByte = dbis.read();
385 				dbis.reset();
386 				if ( nesting > 0 && tempByte == -1 ){
387 
388 					throw( new BEncodingException( "BDecoder: invalid input data, 'e' missing from end of dictionary"));
389 				}
390 			}catch( Throwable e ){
391 
392 				if ( !recovery_mode ){
393 
394 					if ( e instanceof IOException ){
395 
396 						throw((IOException)e);
397 					}
398 
399 					throw( new IOException( Debug.getNestedExceptionMessage(e)));
400 				}
401 			}
402 
403 			tempMap.compactify(-0.9f);
404 
405 				//return the map
406 
407 			return tempMap;
408 
409 		case 'l' :
410 				//create the list
411 
412 			ArrayList tempList = new ArrayList();
413 
414 			try{
415 					//create the key
416 
417 				String context2 = PORTABLE_ROOT==null?context:(context+"[]");
418 
419 				Object tempElement = null;
420 				while ((tempElement = decodeInputStream(dbis, context2, nesting+1, internKeys)) != null) {
421 						//add the element
422 					tempList.add(tempElement);
423 				}
424 
425 				tempList.trimToSize();
426 				dbis.mark(1);
427 				tempByte = dbis.read();
428 				dbis.reset();
429 				if ( nesting > 0 && tempByte == -1 ){
430 
431 					throw( new BEncodingException( "BDecoder: invalid input data, 'e' missing from end of list"));
432 				}
433 			}catch( Throwable e ){
434 
435 				if ( !recovery_mode ){
436 
437 					if ( e instanceof IOException ){
438 
439 						throw((IOException)e);
440 					}
441 
442 					throw( new IOException( Debug.getNestedExceptionMessage(e)));
443 				}
444 			}
445 				//return the list
446 			return tempList;
447 
448 		case 'e' :
449 		case -1 :
450 			return null;
451 
452 		case 'i' :
453 			return Long.valueOf(getNumberFromStream(dbis, 'e'));
454 
455 		case '0' :
456 		case '1' :
457 		case '2' :
458 		case '3' :
459 		case '4' :
460 		case '5' :
461 		case '6' :
462 		case '7' :
463 		case '8' :
464 		case '9' :
465 				//move back one
466 			dbis.reset();
467 				//get the string
468 			return getByteArrayFromStream(dbis, context );
469 
470 		default :{
471 
472 			int	rem_len = dbis.available();
473 
474 			if ( rem_len > 256 ){
475 
476 				rem_len	= 256;
477 			}
478 
479 			byte[] rem_data = new byte[rem_len];
480 
481 			dbis.read( rem_data );
482 
483 			throw( new BEncodingException(
484 					"BDecoder: unknown command '" + tempByte + ", remainder = " + new String( rem_data )));
485 		}
486 		}
487 	}
488 
489 	/*
490   private long getNumberFromStream(InputStream dbis, char parseChar) throws IOException {
491     StringBuffer sb = new StringBuffer(3);
492 
493     int tempByte = dbis.read();
494     while ((tempByte != parseChar) && (tempByte >= 0)) {
495     	sb.append((char)tempByte);
496       tempByte = dbis.read();
497     }
498 
499     //are we at the end of the stream?
500     if (tempByte < 0) {
501       return -1;
502     }
503 
504     String str = sb.toString();
505 
506     	// support some borked impls that sometimes don't bother encoding anything
507 
508     if ( str.length() == 0 ){
509 
510     	return( 0 );
511     }
512 
513     return Long.parseLong(str);
514   }
515 	 */
516 
517 	/** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method)
518 	 */
519 	private final char[] numberChars = new char[32];
520 
521 	/**
522 	 * @note will break (likely return a negative) if number >
523 	 * {@link Integer#MAX_VALUE}.  This check is intentionally skipped to
524 	 * increase performance
525 	 */
526 	private int
getPositiveNumberFromStream( InputStream dbis, char parseChar)527 	getPositiveNumberFromStream(
528 			InputStream	dbis,
529 			char	parseChar)
530 
531 	throws IOException
532 	{
533 		int tempByte = dbis.read();
534 		if (tempByte < 0) {
535 			return -1;
536 		}
537 		if (tempByte != parseChar) {
538 
539 			int value = tempByte - '0';
540 
541 			tempByte = dbis.read();
542 			// optimized for single digit cases
543 			if (tempByte == parseChar) {
544 				return value;
545 			}
546 			if (tempByte < 0) {
547 				return -1;
548 			}
549 
550 			while (true) {
551 				// Base10 shift left --> v*8 + v*2 = v*10
552 				value = (value << 3) + (value << 1) + (tempByte - '0');
553 				// For bounds check:
554 				// if (value < 0) return something;
555 				tempByte = dbis.read();
556 				if (tempByte == parseChar) {
557 					return value;
558 				}
559 				if (tempByte < 0) {
560 					return -1;
561 				}
562 			}
563 		} else {
564 			return 0;
565 		}
566 	}
567 
568 	private long
getNumberFromStream( InputStream dbis, char parseChar)569 	getNumberFromStream(
570 		InputStream 	dbis,
571 		char 					parseChar)
572 
573 		throws IOException
574 	{
575 
576 
577 		int tempByte = dbis.read();
578 
579 		int pos = 0;
580 
581 		while ((tempByte != parseChar) && (tempByte >= 0)) {
582 			numberChars[pos++] = (char)tempByte;
583 			if ( pos == numberChars.length ){
584 				throw( new NumberFormatException( "Number too large: " + new String(numberChars,0,pos) + "..." ));
585 			}
586 			tempByte = dbis.read();
587 		}
588 
589 		//are we at the end of the stream?
590 
591 		if (tempByte < 0) {
592 
593 			return -1;
594 
595 		}else if ( pos == 0 ){
596 			// support some borked impls that sometimes don't bother encoding anything
597 
598 			return(0);
599 		}
600 
601 		try{
602 			return( parseLong( numberChars, 0, pos ));
603 
604 		}catch( NumberFormatException e ){
605 
606 			String temp = new String( numberChars, 0, pos );
607 
608 			try{
609 				double d = Double.parseDouble( temp );
610 
611 				long l = (long)d;
612 
613 				Debug.out( "Invalid number '" + temp + "' - decoding as " + l + " and attempting recovery" );
614 
615 				return( l );
616 
617 			}catch( Throwable f ){
618 			}
619 
620 			throw( e );
621 		}
622 	}
623 
624 	// This is similar to Long.parseLong(String) source
625 	// It is also used in projects external to azureus2/azureus3 hence it is public
626 	public static long
parseLong( char[] chars, int start, int length )627 	parseLong(
628 		char[]	chars,
629 		int		start,
630 		int		length )
631 	{
632 		if ( length > 0 ){
633 			// Short Circuit: We don't support octal parsing, so if it
634 			// starts with 0, it's 0
635 			if (chars[start] == '0') {
636 
637 				return 0;
638 			}
639 
640 			long result = 0;
641 
642 			boolean negative = false;
643 
644 			int 	i 	= start;
645 
646 			long limit;
647 
648 			if ( chars[i] == '-' ){
649 
650 				negative = true;
651 
652 				limit = Long.MIN_VALUE;
653 
654 				i++;
655 
656 			}else{
657 				// Short Circuit: If we are only processing one char,
658 				// and it wasn't a '-', just return that digit instead
659 				// of doing the negative junk
660 				if (length == 1) {
661 					int digit = chars[i] - '0';
662 
663 					if ( digit < 0 || digit > 9 ){
664 
665 						throw new NumberFormatException(new String(chars,start,length));
666 
667 					}else{
668 
669 						return digit;
670 					}
671 				}
672 
673 				limit = -Long.MAX_VALUE;
674 			}
675 
676 			int	max = start + length;
677 
678 			if ( i < max ){
679 
680 				int digit = chars[i++] - '0';
681 
682 				if ( digit < 0 || digit > 9 ){
683 
684 					throw new NumberFormatException(new String(chars,start,length));
685 
686 				}else{
687 
688 					result = -digit;
689 				}
690 			}
691 
692 			long multmin = limit / 10;
693 
694 			while ( i < max ){
695 
696 				// Accumulating negatively avoids surprises near MAX_VALUE
697 
698 				int digit = chars[i++] - '0';
699 
700 				if ( digit < 0 || digit > 9 ){
701 
702 					throw new NumberFormatException(new String(chars,start,length));
703 				}
704 
705 				if ( result < multmin ){
706 
707 					throw new NumberFormatException(new String(chars,start,length));
708 				}
709 
710 				result *= 10;
711 
712 				if ( result < limit + digit ){
713 
714 					throw new NumberFormatException(new String(chars,start,length));
715 				}
716 
717 				result -= digit;
718 			}
719 
720 			if ( negative ){
721 
722 				if ( i > start+1 ){
723 
724 					return result;
725 
726 				}else{	/* Only got "-" */
727 
728 					throw new NumberFormatException(new String(chars,start,length));
729 				}
730 			}else{
731 
732 				return -result;
733 			}
734 		}else{
735 
736 			throw new NumberFormatException(new String(chars,start,length));
737 		}
738 
739 	}
740 
741 
742 
743 	// This one causes lots of "Query Information" calls to the filesystem
744 	/*
745   private long getNumberFromStreamOld(InputStream dbis, char parseChar) throws IOException {
746     int length = 0;
747 
748     //place a mark
749     dbis.mark(???); 1 wouldn't work here ;)
750 
751     int tempByte = dbis.read();
752     while ((tempByte != parseChar) && (tempByte >= 0)) {
753       tempByte = dbis.read();
754       length++;
755     }
756 
757     //are we at the end of the stream?
758     if (tempByte < 0) {
759       return -1;
760     }
761 
762     //reset the mark
763     dbis.reset();
764 
765     //get the length
766     byte[] tempArray = new byte[length];
767     int count = 0;
768     int len = 0;
769 
770     //get the string
771     while (count != length && (len = dbis.read(tempArray, count, length - count)) > 0) {
772       count += len;
773     }
774 
775     //jump ahead in the stream to compensate for the :
776     dbis.skip(1);
777 
778     //return the value
779 
780     CharBuffer	cb = Constants.DEFAULT_CHARSET.decode(ByteBuffer.wrap(tempArray));
781 
782     String	str_value = new String(cb.array(),0,cb.limit());
783 
784     return Long.parseLong(str_value);
785   }
786 	 */
787 
788 	private byte[]
getByteArrayFromStream( InputStream dbis, String context )789 	getByteArrayFromStream(
790 		InputStream dbis,
791 		String		context )
792 
793 		throws IOException
794 	{
795 		int length = (int) getPositiveNumberFromStream(dbis, ':');
796 
797 		if (length < 0) {
798 			return null;
799 		}
800 
801 		// note that torrent hashes can be big (consider a 55GB file with 2MB pieces
802 		// this generates a pieces hash of 1/2 meg
803 
804 		if ( length > MAX_BYTE_ARRAY_SIZE ){
805 
806 			throw( new IOException( "Byte array length too large (" + length + ")"));
807 		}
808 
809 		byte[] tempArray = new byte[length];
810 
811 		getByteArrayFromStream(dbis, length, tempArray);
812 
813 		if ( PORTABLE_ROOT != null && length >= PORTABLE_ROOT.length && tempArray[1] == ':' && tempArray[2] == '\\' && context != null ){
814 
815 			boolean	mismatch = false;
816 
817 			for ( int i=2;i<PORTABLE_ROOT.length;i++){
818 
819 				if ( tempArray[i] != PORTABLE_ROOT[i] ){
820 
821 					mismatch = true;
822 
823 					break;
824 				}
825 			}
826 
827 			if ( !mismatch ){
828 
829 				context = context.toLowerCase( Locale.US );
830 
831 					// always a chance a hash will match the root so we just pick on relevant looking
832 					// entries...
833 
834 				if ( 	context.contains( "file" ) ||
835 						context.contains( "link" ) ||
836 						context.contains( "dir" ) ||
837 						context.contains( "folder" ) ||
838 						context.contains( "path" ) ||
839 						context.contains( "save" ) ||
840 						context.contains( "torrent" )){
841 
842 					tempArray[0] = PORTABLE_ROOT[0];
843 
844 					/*
845 					String	test = new String( tempArray, 0, tempArray.length > 80?80:tempArray.length );
846 
847 					System.out.println( "mapped " + context + "->" + tempArray.length + ": " + test );
848 					*/
849 
850 				}else{
851 
852 					String	test = new String( tempArray, 0, tempArray.length > 80?80:tempArray.length );
853 
854 					System.out.println( "Portable: not mapping " + context + "->" + tempArray.length + ": " + test );
855 				}
856 			}
857 		}
858 
859 		return tempArray;
860 	}
861 
getByteArrayFromStream(InputStream dbis, int length, byte[] targetArray)862 	private void getByteArrayFromStream(InputStream dbis, int length, byte[] targetArray) throws IOException {
863 
864 		int count = 0;
865 		int len = 0;
866 		//get the string
867 		while (count != length && (len = dbis.read(targetArray, count, length - count)) > 0)
868 			count += len;
869 
870 		if (count != length)
871 			throw (new IOException("BDecoder::getByteArrayFromStream: truncated"));
872 	}
873 
874 	public void
setVerifyMapOrder( boolean b )875 	setVerifyMapOrder(
876 		boolean	b )
877 	{
878 		verify_map_order = b;
879 	}
880 
881 	public void
setRecoveryMode( boolean r )882 	setRecoveryMode(
883 		boolean	r )
884 	{
885 		recovery_mode	= r;
886 	}
887 
888 	public static void
print( Object obj )889 	print(
890 		Object		obj )
891 	{
892 		StringWriter 	sw = new StringWriter();
893 
894 		PrintWriter		pw = new PrintWriter( sw );
895 
896 		print( pw, obj );
897 
898 		pw.flush();
899 
900 		System.out.println( sw.toString());
901 	}
902 
903 	public static void
print( PrintWriter writer, Object obj )904 	print(
905 		PrintWriter	writer,
906 		Object		obj )
907 	{
908 		print( writer, obj, "", false );
909 	}
910 
911 	private static void
print( PrintWriter writer, Object obj, String indent, boolean skip_indent )912 	print(
913 		PrintWriter	writer,
914 		Object		obj,
915 		String		indent,
916 		boolean		skip_indent )
917 	{
918 		String	use_indent = skip_indent?"":indent;
919 
920 		if ( obj instanceof Long ){
921 
922 			writer.println( use_indent + obj );
923 
924 		}else if ( obj instanceof byte[]){
925 
926 			byte[]	b = (byte[])obj;
927 
928 			if ( b.length==20 ){
929 				writer.println( use_indent + " { "+ ByteFormatter.nicePrint( b )+ " }" );
930 			}else if ( b.length < 64 ){
931 				writer.println( new String(b) + " [" + ByteFormatter.encodeString( b ) + "]" );
932 			}else{
933 				writer.println( "[byte array length " + b.length );
934 			}
935 
936 		}else if ( obj instanceof String ){
937 
938 			writer.println( use_indent + obj );
939 
940 		}else if ( obj instanceof List ){
941 
942 			List	l = (List)obj;
943 
944 			writer.println( use_indent + "[" );
945 
946 			for (int i=0;i<l.size();i++){
947 
948 				writer.print( indent + "  (" + i + ") " );
949 
950 				print( writer, l.get(i), indent + "    ", true );
951 			}
952 
953 			writer.println( indent + "]" );
954 
955 		}else{
956 
957 			Map	m = (Map)obj;
958 
959 			Iterator	it = m.keySet().iterator();
960 
961 			while( it.hasNext()){
962 
963 				String	key = (String)it.next();
964 
965 				if ( key.length() > 256 ){
966 					writer.print( indent + key.substring(0,256) + "... = " );
967 				}else{
968 					writer.print( indent + key + " = " );
969 				}
970 
971 				print( writer, m.get(key), indent + "  ", true );
972 			}
973 		}
974 	}
975 
976 	/**
977 	 * Converts any byte[] entries into UTF-8 strings.
978 	 * REPLACES EXISTING MAP VALUES
979 	 *
980 	 * @param map
981 	 * @return
982 	 */
983 
984 	public static Map
decodeStrings( Map map )985 	decodeStrings(
986 		Map	map )
987 	{
988 		if (map == null ){
989 
990 			return( null );
991 		}
992 
993 		Iterator it = map.entrySet().iterator();
994 
995 		while( it.hasNext()){
996 
997 			Map.Entry	entry = (Map.Entry)it.next();
998 
999 			Object	value = entry.getValue();
1000 
1001 			if ( value instanceof byte[]){
1002 
1003 				try{
1004 					entry.setValue( new String((byte[])value,"UTF-8" ));
1005 
1006 				}catch( Throwable e ){
1007 
1008 					System.err.println(e);
1009 				}
1010 			}else if ( value instanceof Map ){
1011 
1012 				decodeStrings((Map)value );
1013 			}else if ( value instanceof List ){
1014 
1015 				decodeStrings((List)value );
1016 			}
1017 		}
1018 
1019 		return( map );
1020 	}
1021 
1022 	/**
1023 	 * Decodes byte arrays into strings.
1024 	 * REPLACES EXISTING LIST VALUES
1025 	 *
1026 	 * @param list
1027 	 * @return the same list passed in
1028 	 */
1029 	public static List
decodeStrings( List list )1030 	decodeStrings(
1031 		List	list )
1032 	{
1033 		if ( list == null ){
1034 
1035 			return( null );
1036 		}
1037 
1038 		for (int i=0;i<list.size();i++){
1039 
1040 			Object value = list.get(i);
1041 
1042 			if ( value instanceof byte[]){
1043 
1044 				try{
1045 					String str = new String((byte[])value, "UTF-8" );
1046 
1047 					list.set( i, str );
1048 
1049 				}catch( Throwable e ){
1050 
1051 					System.err.println(e);
1052 				}
1053 			}else if ( value instanceof Map ){
1054 
1055 				decodeStrings((Map)value );
1056 
1057 			}else if ( value instanceof List ){
1058 
1059 				decodeStrings((List)value );
1060 			}
1061 		}
1062 
1063 		return( list );
1064 	}
1065 
1066 	private static void
print( File f, File output )1067 	print(
1068 		File		f,
1069 		File		output )
1070 	{
1071 		try{
1072 			BDecoder	decoder = new BDecoder();
1073 
1074 			decoder.setRecoveryMode( false );
1075 
1076 			PrintWriter	pw = new PrintWriter( new FileWriter( output ));
1077 
1078 			print( pw, decoder.decodeStream( new BufferedInputStream( new FileInputStream( f ))));
1079 
1080 			pw.flush();
1081 
1082 		}catch( Throwable e ){
1083 
1084 			e.printStackTrace();
1085 		}
1086 	}
1087 
1088    	// JSON
1089 
1090     private static Object
decodeFromJSONGeneric( Object obj )1091     decodeFromJSONGeneric(
1092     	Object		obj )
1093     {
1094     	if ( obj == null ){
1095 
1096     		return( null );
1097 
1098     	}else if ( obj instanceof Map ){
1099 
1100     		return( decodeFromJSONObject((Map)obj));
1101 
1102     	}else if ( obj instanceof List ){
1103 
1104     		return( decodeFromJSONArray((List)obj));
1105 
1106      	}else if ( obj instanceof String ){
1107 
1108  			String s = (String)obj;
1109 
1110      		try{
1111 
1112      			int	len = s.length();
1113 
1114      			if ( len >= 6 && s.startsWith( "\\x" ) && s.endsWith( "\\x" )){
1115 
1116      				byte[]	result = new byte[(len-4)/2];
1117 
1118      				int	pos = 2;
1119 
1120      				for ( int i=0;i<result.length;i++){
1121 
1122      					result[i] = (byte)Integer.parseInt( s.substring( pos, pos+2 ), 16 );
1123 
1124      					pos += 2;
1125      				}
1126 
1127      				return( result );
1128      			}
1129 
1130      			return(s.getBytes( "UTF-8" ));
1131 
1132      		}catch( Throwable e ){
1133 
1134      			return(s.getBytes());
1135      		}
1136 
1137      	}else if ( obj instanceof Long ){
1138 
1139       		return( obj );
1140 
1141       	}else if ( obj instanceof Boolean ){
1142 
1143     		return( new Long(((Boolean)obj)?1:0 ));
1144 
1145     	}else if ( obj instanceof Double ){
1146 
1147     		return( String.valueOf((Double)obj));
1148 
1149     	}else{
1150 
1151     		System.err.println( "Unexpected JSON value type: " + obj.getClass());
1152 
1153     		return( obj );
1154     	}
1155     }
1156 
1157     public static List
decodeFromJSONArray( List j_list )1158     decodeFromJSONArray(
1159     	List		j_list )
1160     {
1161     	List	b_list = new ArrayList();
1162 
1163     	for ( Object o: j_list ){
1164 
1165     		b_list.add( decodeFromJSONGeneric( o ));
1166     	}
1167 
1168     	return( b_list );
1169     }
1170 
1171 
1172     public static Map
decodeFromJSONObject( Map<Object,Object> j_map )1173     decodeFromJSONObject(
1174     	Map<Object,Object>		j_map )
1175     {
1176     	Map	b_map = new HashMap();
1177 
1178     	for ( Map.Entry<Object,Object> entry: j_map.entrySet()){
1179 
1180     		Object	key = entry.getKey();
1181     		Object	val	= entry.getValue();
1182 
1183     		b_map.put((String)key, decodeFromJSONGeneric( val ));
1184     	}
1185 
1186     	return( b_map );
1187     }
1188 
1189     public static Map
decodeFromJSON( String json )1190     decodeFromJSON(
1191     	String	json )
1192     {
1193     	Map j_map = JSONUtils.decodeJSON(json);
1194 
1195     	return( decodeFromJSONObject( j_map ));
1196     }
1197 
1198 
1199 /*
1200 	private interface
1201 	BDecoderInputStream
1202 	{
1203 		public int
1204 		read()
1205 
1206 			throws IOException;
1207 
1208 		public int
1209 		read(
1210 			byte[] buffer )
1211 
1212 			throws IOException;
1213 
1214 		public int
1215 		read(
1216 			byte[] 	buffer,
1217 			int		offset,
1218 			int		length )
1219 
1220 			throws IOException;
1221 
1222 		public int
1223 		available()
1224 
1225 			throws IOException;
1226 
1227 		public boolean
1228 		markSupported();
1229 
1230 		public void
1231 		mark(
1232 				int	limit );
1233 
1234 		public void
1235 		reset()
1236 
1237 			throws IOException;
1238 	}
1239 
1240 	private class
1241 	BDecoderInputStreamStream
1242 
1243 		implements BDecoderInputStream
1244 	{
1245 		final private BufferedInputStream		is;
1246 
1247 		private
1248 		BDecoderInputStreamStream(
1249 			BufferedInputStream	_is )
1250 		{
1251 			is	= _is;
1252 		}
1253 
1254 		public int
1255 		read()
1256 
1257 		throws IOException
1258 		{
1259 			return( is.read());
1260 		}
1261 
1262 		public int
1263 		read(
1264 			byte[] buffer )
1265 
1266 		throws IOException
1267 		{
1268 			return( is.read( buffer ));
1269 		}
1270 
1271 		public int
1272 		read(
1273 			byte[] 	buffer,
1274 			int		offset,
1275 			int		length )
1276 
1277 			throws IOException
1278 		{
1279 			return( is.read( buffer, offset, length ));
1280 		}
1281 
1282 		public int
1283 		available()
1284 
1285 			throws IOException
1286 		{
1287 			return( is.available());
1288 		}
1289 
1290 		public boolean
1291 		markSupported()
1292 		{
1293 			return( is.markSupported());
1294 		}
1295 
1296 		public void
1297 		mark(
1298 			int	limit )
1299 		{
1300 			is.mark( limit );
1301 		}
1302 
1303 		public void
1304 		reset()
1305 
1306 			throws IOException
1307 		{
1308 			is.reset();
1309 		}
1310 	}
1311 */
1312 	private static class
1313 	BDecoderInputStreamArray
1314 
1315 		extends InputStream
1316 	{
1317 		final private byte[] bytes;
1318 		private int pos = 0;
1319 		private int markPos;
1320 		private int overPos;
1321 
1322 
BDecoderInputStreamArray(ByteBuffer buffer)1323 		public BDecoderInputStreamArray(ByteBuffer buffer) {
1324 			bytes = buffer.array();
1325 			pos = buffer.arrayOffset() + buffer.position();
1326 			overPos = pos + buffer.remaining();
1327 		}
1328 
1329 
1330 		private
BDecoderInputStreamArray( byte[] _buffer )1331 		BDecoderInputStreamArray(
1332 			byte[]		_buffer )
1333 		{
1334 			bytes = _buffer;
1335 			overPos = bytes.length;
1336 		}
1337 
1338 		private
BDecoderInputStreamArray( byte[] _buffer, int _offset, int _length )1339 		BDecoderInputStreamArray(
1340 			byte[]		_buffer,
1341 			int			_offset,
1342 			int			_length )
1343 		{
1344 			if (_offset == 0) {
1345 				bytes = _buffer;
1346 				overPos = _length;
1347 			} else {
1348 				bytes = _buffer;
1349 				pos = _offset;
1350 				overPos = Math.min(_offset + _length, bytes.length);
1351 			}
1352 		}
1353 
1354 		public int
read()1355 		read()
1356 
1357 			throws IOException
1358 		{
1359 			if (pos < overPos) {
1360 				return bytes[pos++] & 0xFF;
1361 			}
1362 			return -1;
1363 		}
1364 
1365 		public int
read( byte[] buffer )1366 		read(
1367 			byte[] buffer )
1368 
1369 			throws IOException
1370 		{
1371 			return( read( buffer, 0, buffer.length ));
1372 		}
1373 
1374 		public int
read( byte[] b, int offset, int length )1375 		read(
1376 			byte[] 	b,
1377 			int		offset,
1378 			int		length )
1379 
1380 			throws IOException
1381 		{
1382 
1383 			if (pos < overPos) {
1384 				int toRead = Math.min(length, overPos - pos);
1385 				System.arraycopy(bytes, pos, b, offset, toRead);
1386 				pos += toRead;
1387 				return toRead;
1388 			}
1389 			return -1;
1390 
1391 		}
1392 
1393 		public int
available()1394 		available()
1395 
1396 			throws IOException
1397 		{
1398 			return overPos - pos;
1399 		}
1400 
1401 		public boolean
markSupported()1402 		markSupported()
1403 		{
1404 			return( true );
1405 		}
1406 
1407 		public void
mark( int limit )1408 		mark(
1409 			int	limit )
1410 		{
1411 			markPos = pos;
1412 		}
1413 
1414 		public void
reset()1415 		reset()
1416 
1417 			throws IOException
1418 		{
1419 			pos = markPos;
1420 		}
1421 	}
1422 
1423 
1424 	public static void
main( String[] args )1425 	main(
1426 			String[]	args )
1427 	{
1428 		print( 	new File( "C:\\Temp\\tables.config" ),
1429 				new File( "C:\\Temp\\tables.txt" ));
1430 	}
1431 }
1432