forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head f005cd64cce03cf3a301359f94380bc20eaa7c61)
to branch 'i2p.i2p.zzz.dhtsnark' (head 0562e4f429dcebf3f623d0975bd3a63d7645c0b7)
This commit is contained in:
@ -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(); }
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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();
|
||||
|
@ -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(",", ",");
|
||||
String url = toks[i+1].trim().replace(",", ",");
|
||||
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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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("=", "=");
|
||||
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";
|
||||
|
Reference in New Issue
Block a user