Java解析bt torrent资料中的信息

Java解析bt torrent文件中的信息

这篇代码是从http://sourceforge.net/projects/bitext/这里提取出来的。功能很简单,就是解析bt种子。

废话不多说,直接贴代码。有兴趣的同学可以试一试。

 

/*
 * BeDecoder.java
 *
 * Created on May 30, 2003, 2:44 PM
 * Copyright (C) 2003, 2004, 2005, 2006 Aelitis, 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; either version 2
 * of the License, or (at your option) any later version.
 * 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.
 *
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */
package com.weyoung.bt;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A set of utility methods to decode a bencoded array of byte into a Map.
 * integer are represented as Long, String as byte[], dictionnaries as Map, and
 * list as List.
 * 
 * @author TdC_VgA
 * 
 */
@SuppressWarnings(value={"rawtypes", "unchecked", "unused"})
public class BDecoder {

	public static Charset BYTE_CHARSET = Charset.forName("UTF-8");;
	
    public static Charset DEFAULT_CHARSET = Charset.forName("UTF-8");;
	
	private boolean recovery_mode;

	
	public static Map decode(byte[] data) throws IOException {
		return (new BDecoder().decodeByteArray(data));
	}

	public static Map decode(BufferedInputStream is) throws IOException {
		return (new BDecoder().decodeStream(is));
	}

	public BDecoder() { }

	public Map decodeByteArray(byte[] data) throws IOException {
		return (decode(new ByteArrayInputStream(data)));
	}

	public Map decodeStream(BufferedInputStream data) throws IOException {
		Object res = decodeInputStream(data, 0);

		if (res == null) {
			throw (new IOException("BDecoder: zero length file"));
		} else if (!(res instanceof Map)) {
			throw (new IOException("BDecoder: top level isn't a Map"));
		}

		return ((Map) res);
	}

	private Map decode(ByteArrayInputStream data) throws IOException {
		Object res = decodeInputStream(data, 0);

		if (res == null) {
			throw (new IOException("BDecoder: zero length file"));
		} else if (!(res instanceof Map)) {
			throw (new IOException("BDecoder: top level isn't a Map"));
		}

		return ((Map) res);
	}

	private Object decodeInputStream(InputStream bais, int nesting)

	throws IOException {
		if (nesting == 0 && !bais.markSupported()) {
			throw new IOException("InputStream must support the mark() method");
		}
		// set a mark
		bais.mark(Integer.MAX_VALUE);
		// read a byte
		int tempByte = bais.read();
		// decide what to do
		switch (tempByte) {
		case 'd':
			// create a new dictionary object
			Map tempMap = new HashMap();
			try {
				// get the key
				byte[] tempByteArray = null;
				while ((tempByteArray = 
						(byte[]) decodeInputStream(bais,nesting + 1)) != null) {
					// decode some more
					Object value = decodeInputStream(bais, nesting + 1);
					// add the value to the map
					CharBuffer cb = 
							BYTE_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
					String key = new String(cb.array(), 0, cb.limit());
					tempMap.put(key, value);
				}
				bais.mark(Integer.MAX_VALUE);
				tempByte = bais.read();
				bais.reset();
				if (nesting > 0 && tempByte == -1) {
					throw (new IOException(
							"BDecoder: invalid input data, 'e' missing from end of dictionary"));
				}
			} catch (Throwable e) {
				if (!recovery_mode) {
					if (e instanceof IOException) {
						throw ((IOException) e);
					}
					throw (new IOException(e.toString()));
				}
			}
			// return the map
			return tempMap;
		case 'l':
			// create the list
			List tempList = new ArrayList();
			try {
				// create the key
				Object tempElement = null;
				while ((tempElement = decodeInputStream(bais, nesting + 1)) != null) {
					// add the element
					tempList.add(tempElement);
				}
				bais.mark(Integer.MAX_VALUE);
				tempByte = bais.read();
				bais.reset();
				if (nesting > 0 && tempByte == -1) {
					throw (new IOException(
							"BDecoder: invalid input data, 'e' missing from end of list"));
				}
			} catch (Throwable e) {
				if (!recovery_mode) {
					if (e instanceof IOException) {
						throw ((IOException) e);
					}
					throw (new IOException(e.toString()));
				}
			}
			// return the list
			return tempList;

		case 'e':
		case -1:
			return null;
		case 'i':
			return new Long(getNumberFromStream(bais, 'e'));
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			// move back one
			bais.reset();
			// get the string
			return getByteArrayFromStream(bais);
		default: {
			int rem_len = bais.available();
			if (rem_len > 256) {
				rem_len = 256;
			}
			byte[] rem_data = new byte[rem_len];
			bais.read(rem_data);
			throw (new IOException("BDecoder: unknown command '" + tempByte
					+ ", remainder = " + new String(rem_data, DEFAULT_CHARSET)));
		}
		}
	}

	private long getNumberFromStream(
			InputStream bais, char parseChar)throws IOException {
		StringBuffer sb = new StringBuffer(3);

		int tempByte = bais.read();
		while ((tempByte != parseChar) && (tempByte >= 0)) {
			sb.append((char) tempByte);
			tempByte = bais.read();
		}

		// are we at the end of the stream?
		if (tempByte < 0) {
			return -1;
		}

		return Long.parseLong(sb.toString());
	}

	// This one causes lots of "Query Information" calls to the filesystem
	private long getNumberFromStreamOld(
			InputStream bais, char parseChar) throws IOException {
		int length = 0;
		// place a mark
		bais.mark(Integer.MAX_VALUE);
		int tempByte = bais.read();
		while ((tempByte != parseChar) && (tempByte >= 0)) {
			tempByte = bais.read();
			length++;
		}

		// are we at the end of the stream?
		if (tempByte < 0) {
			return -1;
		}

		// reset the mark
		bais.reset();
		// get the length
		byte[] tempArray = new byte[length];
		int count = 0;
		int len = 0;

		// get the string
		while (count != length
				&& (len = bais.read(tempArray, count, length - count)) > 0) {
			count += len;
		}

		// jump ahead in the stream to compensate for the :
		bais.skip(1);
		// return the value
		CharBuffer cb = 
				DEFAULT_CHARSET.decode(ByteBuffer.wrap(tempArray));
		String str_value = new String(cb.array(), 0, cb.limit());

		return Long.parseLong(str_value);
	}

	private byte[] getByteArrayFromStream(
			InputStream bais) throws IOException {
		int length = (int) getNumberFromStream(bais, ':');
		if (length < 0) {
			return null;
		}
		// note that torrent hashes can be big (consider a 55GB file with 2MB
		// pieces
		// this generates a pieces hash of 1/2 meg

		if (length > 8 * 1024 * 1024) {
			throw new IOException("Byte array length too large (" + length + ")");
		}

		byte[] tempArray = new byte[length];
		int count = 0;
		int len = 0;
		// get the string
		while (count != length
				&& (len = bais.read(tempArray, count, length - count)) > 0) {
			count += len;
		}
		if (count != tempArray.length) {
			throw (new IOException(
					"BDecoder::getByteArrayFromStream: truncated"));
		}
		return tempArray;
	}

	public void setRecoveryMode(boolean r) {
		recovery_mode = r;
	}

	private void print(PrintWriter writer, Object obj) {
		print(writer, obj, "", false);
	}

	private void print(
			PrintWriter writer, Object obj, String indent, boolean skip_indent) {
		String use_indent = skip_indent ? "" : indent;
		if (obj instanceof Long) {
			writer.println(use_indent + obj);
		} else if (obj instanceof byte[]) {
			byte[] b = (byte[]) obj;
			if (b.length == 20) {
				writer.println(use_indent + " { " + b + " }");
			} else if (b.length < 64) {
				writer.println(new String(b, DEFAULT_CHARSET));
			} else {
				writer.println("[byte array length " + b.length);
			}

		} else if (obj instanceof String) {
			writer.println(use_indent + obj);
		} else if (obj instanceof List) {
			List l = (List) obj;
			writer.println(use_indent + "[");
			
			for (int i = 0; i < l.size(); i++) {
				writer.print(indent + "  (" + i + ") ");
				print(writer, l.get(i), indent + "    ", true);
			}
			writer.println(indent + "]");

		} else {
			Map m = (Map) obj;
			Iterator it = m.keySet().iterator();

			while (it.hasNext()) {
				String key = (String) it.next();
				if (key.length() > 256) {
					writer.print(indent + key.substring(0, 256) + "... = ");
				} else {
					writer.print(indent + key + " = ");
				}
				print(writer, m.get(key), indent + "  ", true);
			}
		}
	}

	private static void print(File f, File output) {
		try {
			BDecoder decoder = new BDecoder();
			decoder.setRecoveryMode(false);
			PrintWriter pw = new PrintWriter(new FileWriter(output));
			decoder.print(pw, 
					decoder.decodeStream(
							new BufferedInputStream(new FileInputStream(f))));
			pw.flush();
		} catch (Throwable e) {
		}
	}

	public static void main(String[] args) {
//		print(new File(
//				"C:\\Temp\\8565658FA6C187A602A5360A69F11933624DD9B5.dat.bak"),
//				new File("C:\\Temp\\bdecoder.log"));
		print(new File("d:/2.torrent"), new File("d:/2.properties"));
	}
}