/* * 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 com.frostwire.torrent; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Vector; final class TOTorrentDeserialiseImpl extends TOTorrentImpl { public TOTorrentDeserialiseImpl(InputStream is) throws TOTorrentException { construct(is); } public TOTorrentDeserialiseImpl(byte[] bytes) throws TOTorrentException { construct(bytes); } public TOTorrentDeserialiseImpl(Map<String, Object> 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); } final String char_data = new String(metaInfo.toByteArray(), 0, 100); // try to guess HTML only from the first 100 bytes if (char_data.toLowerCase(Locale.US).indexOf("html") != -1) { 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, e); } construct(metaInfo.toByteArray()); } protected void construct(byte[] bytes) throws TOTorrentException { try { BDecoder decoder = new BDecoder(); decoder.setVerifyMapOrder(true); Map<String, Object> meta_data = decoder.decodeByteArray(bytes); // print( "", "", meta_data ); construct(meta_data); } catch (IOException e) { throw (new TOTorrentException("Error reading torrent: " + Debug.getNestedExceptionMessage(e), TOTorrentException.RT_DECODE_FAILS, e)); } } @SuppressWarnings("unchecked") protected void construct(Map<String, Object> 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<String> 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(" ", ""); setAnnounceURL(new URI(announce_url)); } } else if (key.equalsIgnoreCase(TK_ANNOUNCE_LIST)) { got_announce_list = true; List<Object> 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<Object>) 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); // sometimes we just get a byte[]! turn into a list if (temp instanceof byte[]) { List<byte[]> l = new ArrayList<byte[]>(); l.add((byte[]) temp); temp = l; } if (temp instanceof List) { List<Object> set = (List<Object>) temp; Vector<URI> urls = new Vector<URI>(); for (int j = 0; j < set.size(); j++) { String urlStr = readStringFromMetaData((byte[]) set.get(j)); urlStr = cleanUrlStr(urlStr); //check to see if the announce url is somewhere in the announce-list //urls.add(new URL(StringInterner.intern(url_str))); urls.add(new URI(urlStr)); if (urlStr.equalsIgnoreCase(announce_url)) { announce_url_found = true; } } if (urls.size() > 0) { URI[] url_array = new URI[urls.size()]; urls.copyInto(url_array); addTorrentAnnounceURLSet(url_array); } } else { Debug.out("Torrent has invalid url-list entry (" + temp + ") - ignoring: meta=" + meta_data); } } // 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<URI> urls = new Vector<URI>(); //urls.add(new URL(StringInterner.intern(announce_url))); urls.add(new URI(announce_url)); URI[] url_array = new URI[urls.size()]; urls.copyInto(url_array); addTorrentAnnounceURLSet(url_array); } catch (Exception e) { Debug.out("Invalid URL '" + announce_url + "' - meta=" + meta_data, 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<Object>) prop); } else { setAdditionalMapProperty(key, (Map<String, Object>) 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<String, Object> info = (Map<String, Object>) 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<Object> meta_files = (List<Object>) info.get(TK_FILES); TOTorrentFileImpl[] files = new TOTorrentFileImpl[meta_files.size()]; if (hasUTF8Keys) { for (int i = 0; i < files.length; i++) { Map<String, Object> file_map = (Map<String, Object>) 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<String, Object> file_map = (Map<String, Object>) meta_files.get(i); long len = ((Long) file_map.get(TK_LENGTH)).longValue(); List<byte[]> paths = (List<byte[]>) file_map.get(TK_PATH); List<byte[]> paths8 = (List<byte[]>) file_map.get(TK_PATH_UTF8); byte[][] path_comps = null; if (paths != null) { 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<String> 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<String> 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); } else { if (info instanceof HashMapEx) { HashMapEx info_ex = (HashMapEx) info; if (info_ex.getFlag(HashMapEx.FL_MAP_ORDER_INCORRECT)) { String name = getUTF8Name(); if (name == null) { name = new String(getName()); } String message = "torrent.decode.info.order.bad:" + name; System.out.println(message); } } } } 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); } } @SuppressWarnings("unchecked") protected void print(String indent, String name, Map<String, Object> map) { System.out.println(indent + name + "{map}"); Iterator<String> 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<String, Object>) value); } else if (value instanceof List) { print(indent + " ", key, (List<Object>) value); } else if (value instanceof Long) { print(indent + " ", key, (Long) value); } else { print(indent + " ", key, (byte[]) value); } } } @SuppressWarnings("unchecked") protected void print(String indent, String name, List<Object> list) { System.out.println(indent + name + "{list}"); Iterator<Object> it = list.iterator(); int index = 0; while (it.hasNext()) { Object value = it.next(); if (value instanceof Map) { print(indent + " ", "[" + index + "]", (Map<String, Object>) value); } else if (value instanceof List) { print(indent + " ", "[" + index + "]", (List<Object>) 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 + "}"); } } // this method fix common mistakes made by users while creating torrents in other clients private String cleanUrlStr(String urlStr) { return urlStr.replace(" ", "").replace("\t", "").replace("\n", ""); } }