propagate from branch 'i2p.i2p' (head f005cd64cce03cf3a301359f94380bc20eaa7c61)

to branch 'i2p.i2p.zzz.dhtsnark' (head 0562e4f429dcebf3f623d0975bd3a63d7645c0b7)
This commit is contained in:
zzz
2012-06-02 15:16:14 +00:00
64 changed files with 10331 additions and 8632 deletions

View File

@ -29,8 +29,12 @@ import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Hash;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Accepts connections on a TCP port and routes them to sub-acceptors.
@ -41,11 +45,15 @@ public class ConnectionAcceptor implements Runnable
private I2PServerSocket serverSocket;
private PeerAcceptor peeracceptor;
private Thread thread;
private I2PSnarkUtil _util;
private final I2PSnarkUtil _util;
private final ObjectCounter<Hash> _badCounter = new ObjectCounter();
private boolean stop;
private boolean socketChanged;
private static final int MAX_BAD = 2;
private static final long BAD_CLEAN_INTERVAL = 30*60*1000;
public ConnectionAcceptor(I2PSnarkUtil util) { _util = util; }
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
@ -59,6 +67,7 @@ public class ConnectionAcceptor implements Runnable
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
}
}
}
@ -70,11 +79,10 @@ public class ConnectionAcceptor implements Runnable
this.peeracceptor = peeracceptor;
_util = util;
socketChanged = false;
stop = false;
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
}
public void halt()
@ -142,6 +150,12 @@ public class ConnectionAcceptor implements Runnable
try { socket.close(); } catch (IOException ioe) {}
continue;
}
if (_badCounter.count(socket.getPeerDestination().calculateHash()) >= MAX_BAD) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting connection from " + socket.getPeerDestination().calculateHash() + " after " + MAX_BAD + " failures");
try { socket.close(); } catch (IOException ioe) {}
continue;
}
Thread t = new I2PAppThread(new Handler(socket), "I2PSnark incoming connection");
t.start();
}
@ -171,10 +185,12 @@ public class ConnectionAcceptor implements Runnable
}
private class Handler implements Runnable {
private I2PSocket _socket;
private final I2PSocket _socket;
public Handler(I2PSocket socket) {
_socket = socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
@ -182,13 +198,23 @@ public class ConnectionAcceptor implements Runnable
// this is for the readahead in PeerAcceptor.connection()
in = new BufferedInputStream(in);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash());
peeracceptor.connection(_socket, in, out);
} catch (PeerAcceptor.ProtocolException ihe) {
_badCounter.increment(_socket.getPeerDestination().calculateHash());
if (_log.shouldLog(Log.INFO))
_log.info("Protocol error from " + _socket.getPeerDestination().calculateHash(), ihe);
try { _socket.close(); } catch (IOException ignored) { }
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash(), ioe);
try { _socket.close(); } catch (IOException ignored) { }
}
}
}
/** @since 0.9.1 */
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() { _badCounter.clear(); }
}
}

View File

@ -20,6 +20,7 @@ import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketEepGet;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@ -210,6 +211,8 @@ public class I2PSnarkUtil {
// we don't need fast handshake for peer connections.
//if (opts.getProperty("i2p.streaming.connectDelay") == null)
// opts.setProperty("i2p.streaming.connectDelay", "500");
if (opts.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT, "75000");
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
@ -225,7 +228,9 @@ public class I2PSnarkUtil {
if (opts.getProperty("i2p.streaming.maxConnsPerMinute") == null)
opts.setProperty("i2p.streaming.maxConnsPerMinute", "2");
if (opts.getProperty("i2p.streaming.maxTotalConnsPerMinute") == null)
opts.setProperty("i2p.streaming.maxTotalConnsPerMinute", "6");
opts.setProperty("i2p.streaming.maxTotalConnsPerMinute", "8");
if (opts.getProperty("i2p.streaming.maxConnsPerHour") == null)
opts.setProperty("i2p.streaming.maxConnsPerHour", "20");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
}
// FIXME this only instantiates krpc once, left stuck with old manager
@ -452,14 +457,18 @@ public class I2PSnarkUtil {
_openTrackerString = ot;
}
/** Comma delimited list of open trackers to use as backups
* non-null but possibly empty
*/
public String getOpenTrackerString() {
if (_openTrackerString == null)
return DEFAULT_OPENTRACKERS;
return _openTrackerString;
}
/** comma delimited list open trackers to use as backups */
/** sorted map of name to announceURL=baseURL */
/** List of open trackers to use as backups
* Null if disabled
*/
public List<String> getOpenTrackers() {
if (!shouldUseOpenTrackers())
return null;

View File

@ -46,6 +46,10 @@ public class PeerAcceptor
private final PeerCoordinator coordinator;
final PeerCoordinatorSet coordinators;
/** shorten timeout while reading handshake */
private static final long HASH_READ_TIMEOUT = 45*1000;
public PeerAcceptor(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
@ -69,11 +73,20 @@ public class PeerAcceptor
// talk about, and we can just look for that in our list of active torrents.
byte peerInfoHash[] = null;
if (in instanceof BufferedInputStream) {
// multitorrent
in.mark(LOOKAHEAD_SIZE);
peerInfoHash = readHash(in);
long timeout = socket.getReadTimeout();
socket.setReadTimeout(HASH_READ_TIMEOUT);
try {
peerInfoHash = readHash(in);
} catch (IOException ioe) {
// unique exception so ConnectionAcceptor can blame the peer
throw new ProtocolException(ioe.toString());
}
socket.setReadTimeout(timeout);
in.reset();
} else {
// is this working right?
// Single torrent - is this working right?
try {
peerInfoHash = readHash(in);
if (_log.shouldLog(Log.INFO))
@ -130,22 +143,50 @@ public class PeerAcceptor
}
}
private static final String PROTO_STR = "BitTorrent protocol";
private static final int PROTO_STR_LEN = PROTO_STR.length();
private static final int PROTO_LEN = PROTO_STR_LEN + 1;
private static final int[] PROTO = new int[PROTO_LEN];
static {
PROTO[0] = PROTO_STR_LEN;
for (int i = 0; i < PROTO_STR_LEN; i++) {
PROTO[i+1] = PROTO_STR.charAt(i);
}
}
/** 48 */
private static final int LOOKAHEAD_SIZE = 1 + // chr(19)
"BitTorrent protocol".length() +
private static final int LOOKAHEAD_SIZE = PROTO_LEN +
8 + // blank, reserved
20; // infohash
/**
* Read ahead to the infohash, throwing an exception if there isn't enough data
* Read ahead to the infohash, throwing an exception if there isn't enough data.
* Also check the first 20 bytes for the correct protocol here and throw IOE if bad,
* so we don't hang waiting for 48 bytes if it's not a bittorrent client.
* The 20 bytes are checked again in Peer.handshake().
*/
private byte[] readHash(InputStream in) throws IOException {
byte buf[] = new byte[LOOKAHEAD_SIZE];
private static byte[] readHash(InputStream in) throws IOException {
for (int i = 0; i < PROTO_LEN; i++) {
int b = in.read();
if (b != PROTO[i])
throw new IOException("Bad protocol 0x" + Integer.toHexString(b) + " at byte " + i);
}
if (in.skip(8) != 8)
throw new IOException("EOF before hash");
byte buf[] = new byte[20];
int read = DataHelper.read(in, buf);
if (read != buf.length)
throw new IOException("Unable to read the hash (read " + read + ")");
byte rv[] = new byte[20];
System.arraycopy(buf, buf.length-rv.length, rv, 0, rv.length);
return rv;
return buf;
}
/**
* A unique exception so we can tell the ConnectionAcceptor about non-BT connections
* @since 0.9.1
*/
public static class ProtocolException extends IOException {
public ProtocolException(String s) {
super(s);
}
}
}

View File

@ -371,7 +371,9 @@ class PeerCoordinator implements PeerListener
* @since 0.9.1
*/
public boolean needOutboundPeers() {
return wantedBytes != 0 && needPeers();
//return wantedBytes != 0 && needPeers();
// minus one to make it a little easier for new peers to get in on large swarms
return wantedBytes != 0 && !halted && peers.size() < getMaxConnections() - 1;
}
/**
@ -1146,10 +1148,18 @@ class PeerCoordinator implements PeerListener
PartialPiece pp = iter.next();
int savedPiece = pp.getPiece();
if (havePieces.get(savedPiece)) {
iter.remove();
// this is just a double-check, it should be in there
boolean skipped = false;
for(Piece piece : wantedPieces) {
if (piece.getId() == savedPiece) {
if (peer.isCompleted() && piece.getPeerCount() > 1) {
// Try to preserve rarest-first when we have only one seeder
// by not preferring a partial piece that others have too
// from a seeder
skipped = true;
break;
}
iter.remove();
piece.setRequested(peer, true);
if (_log.shouldLog(Log.INFO)) {
_log.info("Restoring orphaned partial piece " + pp +
@ -1158,8 +1168,12 @@ class PeerCoordinator implements PeerListener
return pp;
}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
if (_log.shouldLog(Log.WARN)) {
if (skipped)
_log.warn("Partial piece " + pp + " with multiple peers skipped for seeder");
else
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
}
}
}
if (_log.shouldLog(Log.WARN) && !partialPieces.isEmpty())

View File

@ -57,6 +57,15 @@ class Piece implements Comparable {
/** caller must synchronize */
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
/**
* How many peers have this piece?
* Caller must synchronize
* @since 0.9.1
*/
public int getPeerCount() {
return this.peers.size();
}
/** caller must synchronize */
public boolean isRequested() {
return this.requests != null && !this.requests.isEmpty();

View File

@ -365,7 +365,7 @@ public class SnarkManager implements Snark.CompleteListener {
public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
String startDelay, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers, String theme) {
String upLimit, String upBW, boolean useOpenTrackers, String theme) {
boolean changed = false;
//if (eepHost != null) {
// // unused, we use socket eepget
@ -549,14 +549,6 @@ public class SnarkManager implements Snark.CompleteListener {
_util.setUseOpenTrackers(useOpenTrackers);
changed = true;
}
if (openTrackers != null) {
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) {
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
_util.setOpenTrackerString(openTrackers);
addMessage(_("Open Tracker list changed - torrent restart required to take effect."));
changed = true;
}
}
if (theme != null) {
if(!theme.equals(_config.getProperty(PROP_THEME))) {
_config.setProperty(PROP_THEME, theme);
@ -571,6 +563,20 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
/**
* @param ot null to restore default
* @since 0.9.1
*/
public void saveOpenTrackers(String ot) {
_util.setOpenTrackerString(ot);
if (ot != null)
_config.setProperty(PROP_OPENTRACKERS, ot);
else
_config.remove(PROP_OPENTRACKERS);
addMessage(_("Open Tracker list changed - torrent restart required to take effect."));
saveConfig();
}
public void saveConfig() {
try {
synchronized (_configFile) {
@ -1442,7 +1448,7 @@ public class SnarkManager implements Snark.CompleteListener {
String name = toks[i].trim().replace("&#44;", ",");
String url = toks[i+1].trim().replace("&#44;", ",");
if ( (name.length() > 0) && (url.length() > 0) ) {
String urls[] = DEFAULT_TRACKERS[i+1].split("=", 2);
String urls[] = url.split("=", 2);
String url2 = urls.length > 1 ? urls[1] : null;
_trackerMap.put(name, new Tracker(name, urls[0], url2));
}

View File

@ -260,7 +260,7 @@ public class TrackerClient extends I2PAppThread
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
Tracker tr = (Tracker)iter.next();
if ((!stop) && (!tr.stop) &&
(completed || coordinator.needOutboundPeers()) &&
(completed || coordinator.needOutboundPeers() || !tr.started) &&
(event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
{
try
@ -298,7 +298,7 @@ public class TrackerClient extends I2PAppThread
List<Peer> ordered = new ArrayList(peers);
Collections.shuffle(ordered, r);
Iterator<Peer> it = ordered.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
// FIXME if id == us || dest == us continue;
// only delay if we actually make an attempt to add peer
@ -351,7 +351,7 @@ public class TrackerClient extends I2PAppThread
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = (DELAY_MUL * r.nextInt(10)) + DELAY_MIN;
@ -387,7 +387,7 @@ public class TrackerClient extends I2PAppThread
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = (DELAY_MUL * r.nextInt(10)) + DELAY_MIN;

View File

@ -4,6 +4,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.Collator;
import java.text.DecimalFormat;
import java.util.ArrayList;
@ -690,11 +693,11 @@ public class I2PSnarkServlet extends DefaultServlet {
String refreshDel = req.getParameter("refreshDelay");
String startupDel = req.getParameter("startupDelay");
boolean useOpenTrackers = req.getParameter("useOpenTrackers") != null;
String openTrackers = req.getParameter("openTrackers");
//String openTrackers = req.getParameter("openTrackers");
String theme = req.getParameter("theme");
_manager.updateConfig(dataDir, filesPublic, autoStart, refreshDel, startupDel,
seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts,
upLimit, upBW, useOpenTrackers, openTrackers, theme);
upLimit, upBW, useOpenTrackers, theme);
} else if ("Save2".equals(action)) {
String taction = req.getParameter("taction");
if (taction != null)
@ -768,26 +771,35 @@ public class I2PSnarkServlet extends DefaultServlet {
/** @since 0.9 */
private void processTrackerForm(String action, HttpServletRequest req) {
if (action.equals(_("Delete selected"))) {
if (action.equals(_("Delete selected")) || action.equals(_("Change open trackers"))) {
boolean changed = false;
Map<String, Tracker> trackers = _manager.getTrackerMap();
StringBuilder openBuf = new StringBuilder(128);
Enumeration e = req.getParameterNames();
while (e.hasMoreElements()) {
Object o = e.nextElement();
if (!(o instanceof String))
continue;
String k = (String) o;
if (!k.startsWith("delete_"))
continue;
k = k.substring(7);
if (trackers.remove(k) != null) {
_manager.addMessage(_("Removed") + ": " + k);
changed = true;
if (k.startsWith("delete_")) {
k = k.substring(7);
if (trackers.remove(k) != null) {
_manager.addMessage(_("Removed") + ": " + k);
changed = true;
}
} else if (k.startsWith("open_")) {
if (openBuf.length() > 0)
openBuf.append(',');
openBuf.append(k.substring(5));
}
}
if (changed) {
_manager.saveTrackerMap();
}
String newOpen = openBuf.toString();
if (!newOpen.equals(_manager.util().getOpenTrackerString())) {
_manager.saveOpenTrackers(newOpen);
}
} else if (action.equals(_("Add tracker"))) {
String name = req.getParameter("tname");
String hurl = req.getParameter("thurl");
@ -800,6 +812,11 @@ public class I2PSnarkServlet extends DefaultServlet {
Map<String, Tracker> trackers = _manager.getTrackerMap();
trackers.put(name, new Tracker(name, aurl, hurl));
_manager.saveTrackerMap();
if (req.getParameter("_add_open_") != null) {
String oldOpen = _manager.util().getOpenTrackerString();
String newOpen = oldOpen.length() <= 0 ? aurl : oldOpen + ',' + aurl;
_manager.saveOpenTrackers(newOpen);
}
} else {
_manager.addMessage(_("Enter valid tracker name and URLs"));
}
@ -808,6 +825,7 @@ public class I2PSnarkServlet extends DefaultServlet {
}
} else if (action.equals(_("Restore defaults"))) {
_manager.setDefaultTrackerMap();
_manager.saveOpenTrackers(null);
_manager.addMessage(_("Restored default trackers"));
} else {
_manager.addMessage("Unknown POST action: \"" + action + '\"');
@ -1419,7 +1437,7 @@ public class I2PSnarkServlet extends DefaultServlet {
boolean filesPublic = _manager.areFilesPublic();
boolean autoStart = _manager.shouldAutoStart();
boolean useOpenTrackers = _manager.util().shouldUseOpenTrackers();
String openTrackers = _manager.util().getOpenTrackerString();
//String openTrackers = _manager.util().getOpenTrackerString();
//int seedPct = 0;
out.write("<form action=\"/i2psnark/configure\" method=\"POST\">\n" +
@ -1521,7 +1539,7 @@ public class I2PSnarkServlet extends DefaultServlet {
"<tr><td>");
out.write(_("Up bandwidth limit"));
out.write(": <td><input type=\"text\" name=\"upBW\" class=\"r\" value=\""
+ _manager.util().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" > KBps <i>");
+ _manager.util().getMaxUpBW() + "\" size=\"4\" maxlength=\"4\" > KBps <i>");
out.write(_("Half available bandwidth recommended."));
out.write("<br><a href=\"/config.jsp\" target=\"blank\">");
out.write(_("View or change router bandwidth"));
@ -1533,12 +1551,12 @@ public class I2PSnarkServlet extends DefaultServlet {
+ (useOpenTrackers ? "checked " : "")
+ "title=\"");
out.write(_("If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"));
out.write("\" > " +
out.write("\" ></td></tr>\n");
"<tr><td>");
out.write(_("Open tracker announce URLs"));
out.write(": <td><input type=\"text\" name=\"openTrackers\" value=\""
+ openTrackers + "\" size=\"50\" ><br>\n");
// "<tr><td>");
//out.write(_("Open tracker announce URLs"));
//out.write(": <td><input type=\"text\" name=\"openTrackers\" value=\""
// + openTrackers + "\" size=\"50\" ><br>\n");
//out.write("\n");
//out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
@ -1610,27 +1628,37 @@ public class I2PSnarkServlet extends DefaultServlet {
.append("</th><th>")
.append(_("Website URL"))
.append("</th><th>")
.append(_("Open Tracker?"))
.append("</th><th>")
.append(_("Announce URL"))
.append("</th></tr>\n");
List<String> openTrackers = _manager.util().getOpenTrackers();
for (Tracker t : _manager.getSortedTrackers()) {
String name = t.name;
String homeURL = t.baseURL;
String announceURL = t.announceURL.replace("&#61;", "=");
buf.append("<tr><td align=\"center\"><input type=\"checkbox\" class=\"optbox\" name=\"delete_")
.append(name).append("\">" +
.append(name).append("\" title=\"").append(_("Delete")).append("\">" +
"</td><td align=\"left\">").append(name)
.append("</td><td align=\"left\">").append(urlify(homeURL, 35))
.append("</td><td align=\"left\">").append(urlify(announceURL, 35))
.append("</td><td align=\"center\"><input type=\"checkbox\" class=\"optbox\" name=\"open_")
.append(announceURL).append("\"");
if (openTrackers != null && openTrackers.contains(t.announceURL))
buf.append(" checked=\"checked\"");
buf.append(">" +
"</td><td align=\"left\">").append(urlify(announceURL, 35))
.append("</td></tr>\n");
}
buf.append("<tr><td align=\"center\"><b>")
.append(_("Add")).append(":</b></td>" +
"<td align=\"left\"><input type=\"text\" size=\"16\" name=\"tname\"></td>" +
"<td align=\"left\"><input type=\"text\" size=\"40\" name=\"thurl\"></td>" +
"<td align=\"center\"><input type=\"checkbox\" class=\"optbox\" name=\"_add_open_\"></td>" +
"<td align=\"left\"><input type=\"text\" size=\"40\" name=\"taurl\"></td></tr>\n" +
"<tr><td colspan=\"2\"></td><td colspan=\"2\" align=\"left\">\n" +
"<tr><td colspan=\"2\"></td><td colspan=\"3\" align=\"left\">\n" +
"<input type=\"submit\" name=\"taction\" class=\"default\" value=\"").append(_("Add tracker")).append("\">\n" +
"<input type=\"submit\" name=\"taction\" class=\"delete\" value=\"").append(_("Delete selected")).append("\">\n" +
"<input type=\"submit\" name=\"taction\" class=\"accept\" value=\"").append(_("Change open trackers")).append("\">\n" +
// "<input type=\"reset\" class=\"cancel\" value=\"").append(_("Cancel")).append("\">\n" +
"<input type=\"submit\" name=\"taction\" class=\"reload\" value=\"").append(_("Restore defaults")).append("\">\n" +
"<input type=\"submit\" name=\"taction\" class=\"add\" value=\"").append(_("Add tracker")).append("\">\n" +
@ -1647,7 +1675,7 @@ public class I2PSnarkServlet extends DefaultServlet {
}
/**
* @param url in base32 or hex, xt must be first magnet param
* @param url in base32 or hex
* @since 0.8.4
*/
private void addMagnet(String url) {
@ -1662,7 +1690,7 @@ public class I2PSnarkServlet extends DefaultServlet {
return;
}
ihash = xt.substring("urn:btih:".length());
trackerURL = getParam("tr", url);
trackerURL = getTrackerParam(url);
name = "Magnet " + ihash;
String dn = getParam("dn", url);
if (dn != null)
@ -1698,6 +1726,9 @@ public class I2PSnarkServlet extends DefaultServlet {
_manager.addMagnet(name, ih, trackerURL, true);
}
/**
* @return first decoded parameter or null
*/
private static String getParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
@ -1715,9 +1746,100 @@ public class I2PSnarkServlet extends DefaultServlet {
rv = rv.substring(0, idx);
else
rv = rv.trim();
return decode(rv);
}
/**
* @return all decoded parameters or null
* @since 0.9.1
*/
private static List<String> getMultiParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
idx += key.length() + 2;
} else {
idx = uri.indexOf('&' + key + '=');
if (idx >= 0)
idx += key.length() + 2;
}
if (idx < 0 || idx > uri.length())
return null;
List<String> rv = new ArrayList();
while (true) {
String p = uri.substring(idx);
uri = p;
idx = p.indexOf('&');
if (idx >= 0)
p = p.substring(0, idx);
else
p = p.trim();
rv.add(decode(p));
idx = uri.indexOf('&' + key + '=');
if (idx < 0)
break;
idx += key.length() + 2;
}
return rv;
}
/**
* @return first valid I2P tracker or null
* @since 0.9.1
*/
private static String getTrackerParam(String uri) {
List<String> trackers = getMultiParam("tr", uri);
if (trackers == null)
return null;
for (String t : trackers) {
try {
URI u = new URI(t);
String protocol = u.getScheme();
String host = u.getHost();
if (protocol == null || host == null ||
!protocol.toLowerCase(Locale.US).equals("http") ||
!host.toLowerCase(Locale.US).endsWith(".i2p"))
continue;
return t;
} catch(URISyntaxException use) {}
}
return null;
}
/**
* Decode %xx encoding, convert to UTF-8 if necessary
* Copied from i2ptunnel LocalHTTPServer
* @since 0.9.1
*/
private static String decode(String s) {
if (!s.contains("%"))
return s;
StringBuilder buf = new StringBuilder(s.length());
boolean utf8 = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '%') {
buf.append(c);
} else {
try {
int val = Integer.parseInt(s.substring(++i, (++i) + 1), 16);
if ((val & 0x80) != 0)
utf8 = true;
buf.append((char) val);
} catch (IndexOutOfBoundsException ioobe) {
break;
} catch (NumberFormatException nfe) {
break;
}
}
}
if (utf8) {
try {
return new String(buf.toString().getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException uee) {}
}
return buf.toString();
}
/** copied from ConfigTunnelsHelper */
private static final String HOP = "hop";
private static final String TUNNEL = "tunnel";