/*
* File : TOTorrentDeserialiseImpl.java
* Created : 5 Oct. 2003
* By : Parg
*
* Azureus - a Java Bittorrent client
*
* 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.
*
* 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 ( see the LICENSE file ).
*
* 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.torrent.impl;
import java.io.*;
import java.net.*;
import java.util.*;
//import org.gudy.azureus2.core3.html.HTMLUtils;
import org.gudy.azureus2.core3.torrent.*;
import org.gudy.azureus2.core3.util.*;
public class
TOTorrentDeserialiseImpl
extends TOTorrentImpl
{
public
TOTorrentDeserialiseImpl(
File file )
throws TOTorrentException
{
if ( !file.exists()){
throw( new TOTorrentException( "Torrent file '" + file.toString() + "' does not exist",
TOTorrentException.RT_FILE_NOT_FOUND ));
}
if(!file.isFile()) {
throw( new TOTorrentException( "Torrent must be a file ('" + file.toString() + "')",
TOTorrentException.RT_FILE_NOT_FOUND ));
}
if ( file.length() == 0 ){
throw( new TOTorrentException( "Torrent is zero length ('" + file.toString() + "')",
TOTorrentException.RT_ZERO_LENGTH ));
}
// parg: there used to be a check made that the torrent file wasn't larger than 1MB.
// However, this as been exceeded! (see bug 826617)
// As there is no technical reason for this limit I have removed it
FileInputStream fis = null;
try{
fis = new FileInputStream(file);
construct( fis );
}catch( Throwable e ){
// throw( new TOTorrentException( "Error reading torrent file '" + file.toString() + " - " + Debug.getNestedExceptionMessage(e),
// TOTorrentException.RT_READ_FAILS ));
}finally{
if ( fis != null ){
try{
fis.close();
}catch( IOException e ){
// Debug.printStackTrace( e );
}
}
}
}
public
TOTorrentDeserialiseImpl(
InputStream is )
throws TOTorrentException
{
construct( is );
}
public
TOTorrentDeserialiseImpl(
byte[] bytes )
throws TOTorrentException
{
construct( bytes );
}
public
TOTorrentDeserialiseImpl(
Map map )
throws TOTorrentException
{
construct( map );
}
protected void
construct(
InputStream is )
throws TOTorrentException
{
ByteArrayOutputStream metaInfo = new ByteArrayOutputStream();
try{
byte[] buf = new byte[32*1024]; // raised this limit as 2k was rather too small
// do a check to see if it's a BEncode file.
int iFirstByte = is.read();
if ( iFirstByte != 'd' &&
iFirstByte != 'e' &&
iFirstByte != 'i' &&
!(iFirstByte >= '0' && iFirstByte <= '9')){
// often people download an HTML file by accident - if it looks like HTML
// then produce a more informative error
try{
metaInfo.write(iFirstByte);
int nbRead;
while ((nbRead = is.read(buf)) > 0 && metaInfo.size() < 32000 ){
metaInfo.write(buf, 0, nbRead);
}
String char_data = new String( metaInfo.toByteArray());
if ( char_data.toLowerCase().indexOf( "html") != -1 ){
// char_data = HTMLUtils.convertHTMLToText2( char_data );
//
// char_data = HTMLUtils.splitWithLineLength( char_data, 80 );
if ( char_data.length() > 400 ){
char_data = char_data.substring(0,400) + "...";
}
throw( new TOTorrentException(
"Contents maybe HTML:\n" + char_data,
TOTorrentException.RT_DECODE_FAILS ));
}
}catch( Throwable e ){
if ( e instanceof TOTorrentException ){
throw((TOTorrentException)e);
}
// ignore this
}
throw( new TOTorrentException( "Contents invalid - bad header",
TOTorrentException.RT_DECODE_FAILS ));
}
metaInfo.write(iFirstByte);
int nbRead;
while ((nbRead = is.read(buf)) > 0){
metaInfo.write(buf, 0, nbRead);
}
}catch( Throwable e ){
// throw( new TOTorrentException( "Error reading torrent: " + Debug.getNestedExceptionMessage(e),
// TOTorrentException.RT_READ_FAILS ));
}
construct( metaInfo.toByteArray());
}
protected void
construct(
byte[] bytes )
throws TOTorrentException
{
try{
Map meta_data = BDecoder.decode(bytes);
// print( "", "", meta_data );
construct( meta_data );
}catch( IOException e ){
// throw( new TOTorrentException( "Error reading torrent: " + Debug.getNestedExceptionMessage(e),
// TOTorrentException.RT_DECODE_FAILS, e ));
}
}
protected void
construct(
Map meta_data )
throws TOTorrentException
{
try{
String announce_url = null;
boolean got_announce = false;
boolean got_announce_list = false;
boolean bad_announce = false;
// decode the stuff
Iterator root_it = meta_data.keySet().iterator();
while( root_it.hasNext()){
String key = (String)root_it.next();
if ( key.equalsIgnoreCase( TK_ANNOUNCE )){
got_announce = true;
announce_url = readStringFromMetaData( meta_data, TK_ANNOUNCE );
if ( announce_url == null ){
bad_announce = true;
}else{
announce_url = announce_url.replaceAll( " ", "" );
try{
setAnnounceURL( new URL(announce_url));
}catch( MalformedURLException e ){
if ( announce_url.indexOf( "://" ) == -1 ){
announce_url = "http:/" + (announce_url.startsWith("/")?"":"/") + announce_url;
}
try{
setAnnounceURL(new URL(announce_url));
}catch( MalformedURLException f ){
bad_announce = true;
}
}
}
}else if ( key.equalsIgnoreCase( TK_ANNOUNCE_LIST )){
got_announce_list = true;
List announce_list = null;
Object ann_list = meta_data.get( TK_ANNOUNCE_LIST );
if ( ann_list instanceof List ){ //some malformed torrents have this key as a zero-sized string instead of a zero-sized list
announce_list = (List)ann_list;
}
if ( announce_list != null && announce_list.size() > 0 ){
announce_url = readStringFromMetaData( meta_data, TK_ANNOUNCE );
if ( announce_url != null ){
announce_url = announce_url.replaceAll( " ", "" );
}
boolean announce_url_found = false;
for (int i=0;i<announce_list.size();i++){
Object temp = announce_list.get(i);
if ( temp instanceof List ){
List set = (List)temp;
Vector urls = new Vector();
for (int j=0;j<set.size();j++){
String url_str = readStringFromMetaData((byte[])set.get(j));
url_str = url_str.replaceAll( " ", "" );
//check to see if the announce url is somewhere in the announce-list
try{
urls.add( new URL( StringInterner.intern(url_str) ));
if ( url_str.equalsIgnoreCase( announce_url )) {
announce_url_found = true;
}
}catch( MalformedURLException e ){
if ( url_str.indexOf( "://" ) == -1 ){
url_str = "http:/" + (url_str.startsWith("/")?"":"/") + url_str;
}
try{
urls.add( new URL( StringInterner.intern(url_str) ));
if ( url_str.equalsIgnoreCase( announce_url )) {
announce_url_found = true;
}
}catch( MalformedURLException f ){
// Debug.printStackTrace( f );
}
}
}
if ( urls.size() > 0 ){
URL[] url_array = new URL[urls.size()];
urls.copyInto( url_array );
addTorrentAnnounceURLSet( url_array );
}
}else{
// Debug.out( "Torrent has invalid url-list entry (" + temp + ") - ignoring" );
}
}
//if the original announce url isn't found, add it to the list
// watch out for those invalid torrents with announce url missing
if ( !announce_url_found && announce_url != null && announce_url.length() > 0) {
try {
Vector urls = new Vector();
urls.add( new URL( StringInterner.intern(announce_url) ));
URL[] url_array = new URL[ urls.size() ];
urls.copyInto( url_array );
addTorrentAnnounceURLSet( url_array );
}
catch (Exception e) {
// Debug.printStackTrace( e );
}
}
}
}else if ( key.equalsIgnoreCase( TK_COMMENT )){
setComment((byte[])meta_data.get( TK_COMMENT ));
}else if ( key.equalsIgnoreCase( TK_CREATED_BY )){
setCreatedBy((byte[])meta_data.get( TK_CREATED_BY ));
}else if ( key.equalsIgnoreCase( TK_CREATION_DATE )){
// non standard, don't fail if format wrong
try{
Long creation_date = (Long)meta_data.get( TK_CREATION_DATE );
if ( creation_date != null ){
setCreationDate( creation_date.longValue());
}
}catch( Exception e ){
System.out.println( "creation_date extraction fails, ignoring");
}
}else if ( key.equalsIgnoreCase( TK_INFO )){
// processed later
}else{
Object prop = meta_data.get( key );
if ( prop instanceof byte[] ){
setAdditionalByteArrayProperty( key, (byte[])prop );
}else if ( prop instanceof Long ){
setAdditionalLongProperty( key, (Long)prop );
}else if ( prop instanceof List ){
setAdditionalListProperty( key, (List)prop );
}else{
setAdditionalMapProperty( key, (Map)prop );
}
}
}
if ( bad_announce ){
if ( got_announce_list ){
TOTorrentAnnounceURLSet[] sets = getAnnounceURLGroup().getAnnounceURLSets();
if ( sets.length > 0 ){
setAnnounceURL( sets[0].getAnnounceURLs()[0]);
}else{
throw( new TOTorrentException( "ANNOUNCE_URL malformed ('" + announce_url + "' and no usable announce list)",
TOTorrentException.RT_DECODE_FAILS ));
}
}else{
throw( new TOTorrentException( "ANNOUNCE_URL malformed ('" + announce_url + "'",
TOTorrentException.RT_DECODE_FAILS ));
}
}
if ( ! ( got_announce_list || got_announce )){
setAnnounceURL( TorrentUtils.getDecentralisedEmptyURL());
}
Map info = (Map)meta_data.get( TK_INFO );
if ( info == null ){
throw( new TOTorrentException( "Decode fails, 'info' element not found'",
TOTorrentException.RT_DECODE_FAILS ));
}
boolean hasUTF8Keys = info.containsKey(TK_NAME_UTF8);
setName((byte[])info.get( TK_NAME ));
long piece_length = ((Long)info.get( TK_PIECE_LENGTH )).longValue();
if ( piece_length <= 0 ){
throw( new TOTorrentException( "Decode fails, piece-length is invalid",
TOTorrentException.RT_DECODE_FAILS ));
}
setPieceLength( piece_length );
setHashFromInfo( info );
Long simple_file_length = (Long)info.get( TK_LENGTH );
long total_length = 0;
String encoding = getAdditionalStringProperty("encoding");
hasUTF8Keys &= encoding == null || encoding.equals(ENCODING_ACTUALLY_UTF8_KEYS);
if ( simple_file_length != null ){
setSimpleTorrent( true );
total_length = simple_file_length.longValue();
if (hasUTF8Keys) {
setNameUTF8((byte[])info.get( TK_NAME_UTF8 ));
setAdditionalStringProperty("encoding", ENCODING_ACTUALLY_UTF8_KEYS);
}
setFiles( new TOTorrentFileImpl[]{ new TOTorrentFileImpl( this, 0, total_length, new byte[][]{getName()})});
}else{
setSimpleTorrent( false );
List meta_files = (List)info.get( TK_FILES );
TOTorrentFileImpl[] files = new TOTorrentFileImpl[ meta_files.size()];
if (hasUTF8Keys) {
for (int i=0;i<files.length;i++){
Map file_map = (Map)meta_files.get(i);
hasUTF8Keys &= file_map.containsKey(TK_PATH_UTF8);
if (!hasUTF8Keys) {
break;
}
}
if (hasUTF8Keys) {
setNameUTF8((byte[])info.get( TK_NAME_UTF8 ));
setAdditionalStringProperty("encoding", ENCODING_ACTUALLY_UTF8_KEYS);
}
}
for (int i=0;i<files.length;i++){
Map file_map = (Map)meta_files.get(i);
long len = ((Long)file_map.get( TK_LENGTH )).longValue();
List paths = (List)file_map.get( TK_PATH );
List paths8 = (List)file_map.get( TK_PATH_UTF8 );
byte[][] path_comps = new byte[paths.size()][];
for (int j=0;j<paths.size();j++){
path_comps[j] = (byte[])paths.get(j);
}
TOTorrentFileImpl file;
if (hasUTF8Keys) {
byte[][] path_comps8 = new byte[paths8.size()][];
for (int j=0;j<paths8.size();j++){
path_comps8[j] = (byte[])paths8.get(j);
}
file = files[i] = new TOTorrentFileImpl( this, total_length, len, path_comps, path_comps8 );
} else {
file = files[i] = new TOTorrentFileImpl( this, total_length, len, path_comps );
}
total_length += len;
// preserve any non-standard attributes
Iterator file_it = file_map.keySet().iterator();
while( file_it.hasNext()){
String key = (String)file_it.next();
if ( key.equals( TK_LENGTH ) ||
key.equals( TK_PATH )){
// standard
// we don't skip TK_PATH_UTF8 because some code might assume getAdditionalProperty can get it
}else{
file.setAdditionalProperty( key, file_map.get( key ));
}
}
}
setFiles( files );
}
byte[] flat_pieces = (byte[])info.get( TK_PIECES );
// work out how many pieces we require for the torrent
int pieces_required = (int)((total_length + (piece_length-1)) / piece_length);
int pieces_supplied = flat_pieces.length/20;
if ( pieces_supplied < pieces_required ){
throw( new TOTorrentException( "Decode fails, insufficient pieces supplied",
TOTorrentException.RT_DECODE_FAILS ));
}
if ( pieces_supplied > pieces_required ){
// Debug.out( "Torrent '" + new String( getName()) + "' has too many pieces (required=" + pieces_required + ",supplied=" + pieces_supplied + ") - ignoring excess" );
}
byte[][]pieces = new byte[pieces_supplied][20];
for (int i=0;i<pieces.length;i++){
System.arraycopy( flat_pieces, i*20, pieces[i], 0, 20 );
}
setPieces( pieces );
// extract and additional info elements
Iterator info_it = info.keySet().iterator();
while( info_it.hasNext()){
String key = (String)info_it.next();
if ( key.equals( TK_NAME ) ||
key.equals( TK_LENGTH ) ||
key.equals( TK_FILES ) ||
key.equals( TK_PIECE_LENGTH ) ||
key.equals( TK_PIECES )){
// standard attributes
}else{
addAdditionalInfoProperty( key, info.get( key ));
}
}
try{
byte[] ho = (byte[])info.get( TK_HASH_OVERRIDE );
if ( ho != null ){
setHashOverride( ho );
}
}catch( Throwable e ){
// Debug.printStackTrace(e);
}
}catch( Throwable e ){
if ( e instanceof TOTorrentException){
throw((TOTorrentException)e);
}
// throw( new TOTorrentException( "Torrent decode fails '" + Debug.getNestedExceptionMessageAndStack(e) + "'",
// TOTorrentException.RT_DECODE_FAILS, e ));
}
}
//
//
// public void
// printMap()
// {
// try{
//
// print( "", "root", serialiseToMap());
//
// }catch( TOTorrentException e ){
//
// Debug.printStackTrace( e );
// }
// }
//
// protected void
// print(
// String indent,
// String name,
// Map map )
// {
// System.out.println( indent + name + "{map}" );
//
// Iterator it = map.keySet().iterator();
//
// while( it.hasNext()){
//
// String key = (String)it.next();
//
// Object value = map.get( key );
//
// if ( value instanceof Map ){
//
// print( indent+" ", key, (Map)value);
//
// }else if ( value instanceof List ){
//
// print( indent+" ", key, (List)value );
//
// }else if ( value instanceof Long ){
//
// print( indent+" ", key, (Long)value );
//
// }else{
//
// print( indent+" ", key, (byte[])value);
// }
// }
// }
//
// protected void
// print(
// String indent,
// String name,
// List list )
// {
// System.out.println( indent + name + "{list}" );
//
// Iterator it = list.iterator();
//
// int index = 0;
//
// while( it.hasNext()){
//
// Object value = it.next();
//
// if ( value instanceof Map ){
//
// print( indent+" ", "[" + index + "]", (Map)value);
//
// }else if ( value instanceof List ){
//
// print( indent+" ", "[" + index + "]", (List)value );
//
// }else if ( value instanceof Long ){
//
// print( indent+" ", "[" + index + "]", (Long)value );
//
// }else{
//
// print( indent+" ", "[" + index + "]", (byte[])value);
// }
//
// index++;
// }
// }
// protected void
// print(
// String indent,
// String name,
// Long value )
// {
// System.out.println( indent + name + "{long} = " + value.longValue());
// }
//
// protected void
// print(
// String indent,
// String name,
// byte[] value )
// {
// String x = new String(value);
//
// boolean print = true;
//
// for (int i=0;i<x.length();i++){
//
// char c = x.charAt(i);
//
// if ( c < 128 ){
//
// }else{
//
// print = false;
//
// break;
// }
// }
//
// if ( print ){
//
// System.out.println( indent + name + "{byte[]} = " + x );
//
// }else{
//
// System.out.println( indent + name + "{byte[], length " + value.length + "}" );
// }
// }
}