2015-04-16 22:33:50 +00:00
|
|
|
<%@page import="java.io.ByteArrayInputStream,java.util.ArrayList,java.util.Collections,java.util.List,java.util.Map,java.util.HashMap,java.util.concurrent.ConcurrentMap,net.i2p.data.Base64,net.i2p.data.Destination,net.i2p.zzzot.*,org.klomp.snark.bencode.BEncoder" %><%
|
2010-03-21 15:08:07 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Above one-liner is so there is no whitespace -> IllegalStateException
|
|
|
|
*
|
|
|
|
* Copyright 2010 zzz (zzz@mail.i2p)
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* USE CAUTION WHEN EDITING
|
|
|
|
* Trailing whitespace OR NEWLINE on the last line will cause
|
|
|
|
* IllegalStateExceptions !!!
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
// would be nice to make these configurable
|
|
|
|
final int MAX_RESPONSES = 25;
|
2010-04-13 17:41:24 +00:00
|
|
|
final boolean ALLOW_IP_MISMATCH = false;
|
2010-07-09 16:31:24 +00:00
|
|
|
final boolean ALLOW_COMPACT_RESPONSE = true;
|
2025-04-27 17:04:46 -04:00
|
|
|
final boolean ALLOW_NONCOMPACT_RESPONSE = false;
|
2010-03-21 15:08:07 +00:00
|
|
|
|
|
|
|
// so the chars will turn into bytes correctly
|
|
|
|
request.setCharacterEncoding("ISO-8859-1");
|
2014-11-11 14:08:43 +00:00
|
|
|
// above doesn't work for the query string
|
|
|
|
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
|
|
|
|
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
|
|
|
|
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
|
2010-03-21 15:08:07 +00:00
|
|
|
java.io.OutputStream cout = response.getOutputStream();
|
|
|
|
response.setCharacterEncoding("ISO-8859-1");
|
|
|
|
response.setContentType("text/plain");
|
|
|
|
response.setHeader("Pragma", "no-cache");
|
|
|
|
String info_hash = request.getParameter("info_hash");
|
|
|
|
String peer_id = request.getParameter("peer_id");
|
|
|
|
// ignored
|
|
|
|
String port = request.getParameter("port");
|
|
|
|
// ignored
|
|
|
|
String uploaded = request.getParameter("uploaded");
|
|
|
|
// ignored
|
|
|
|
String downloaded = request.getParameter("downloaded");
|
|
|
|
String sleft = request.getParameter("left");
|
|
|
|
String event = request.getParameter("event");
|
|
|
|
String ip = request.getParameter("ip");
|
|
|
|
String numwant = request.getParameter("numwant");
|
2010-07-09 16:31:24 +00:00
|
|
|
boolean compact = ALLOW_COMPACT_RESPONSE && request.getParameter("compact") != null;
|
2010-04-13 17:41:24 +00:00
|
|
|
// use to enforce destination
|
|
|
|
String him = request.getHeader("X-I2P-DestB64");
|
2010-03-21 15:08:07 +00:00
|
|
|
String xff = request.getHeader("X-Forwarded-For");
|
2010-04-12 22:01:50 +00:00
|
|
|
String xfs = request.getHeader("X-Forwarded-Server");
|
2010-03-21 15:08:07 +00:00
|
|
|
|
|
|
|
boolean fail = false;
|
|
|
|
String msg = "bad announce";
|
|
|
|
|
2010-04-12 22:01:50 +00:00
|
|
|
if (xff != null || xfs != null) {
|
2010-03-21 15:08:07 +00:00
|
|
|
fail = true;
|
|
|
|
msg = "Non-I2P access denied";
|
2017-03-07 15:33:33 +00:00
|
|
|
//response.setStatus(403, msg);
|
|
|
|
response.setStatus(403);
|
2010-03-21 15:08:07 +00:00
|
|
|
}
|
|
|
|
|
2025-04-27 17:04:46 -04:00
|
|
|
if (!compact && !ALLOW_NONCOMPACT_RESPONSE && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "non-compact responses unsupported";
|
|
|
|
}
|
|
|
|
|
2010-03-21 15:08:07 +00:00
|
|
|
if (info_hash == null && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "no info hash";
|
|
|
|
}
|
|
|
|
|
2014-11-26 20:52:58 +00:00
|
|
|
if (!fail && info_hash.length() != 20) {
|
2014-11-12 14:48:51 +00:00
|
|
|
fail = true;
|
|
|
|
msg = "bad info hash length " + info_hash.length();
|
|
|
|
}
|
|
|
|
|
2010-03-21 15:08:07 +00:00
|
|
|
if (ip == null && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "no ip (dest)";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (peer_id == null && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "no peer id";
|
|
|
|
}
|
|
|
|
|
2014-11-26 20:52:58 +00:00
|
|
|
if (!fail && peer_id.length() != 20) {
|
2014-11-12 14:48:51 +00:00
|
|
|
fail = true;
|
|
|
|
msg = "bad peer id length " + peer_id.length();
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrents torrents = ZzzOTController.getTorrents();
|
|
|
|
if (torrents == null && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "tracker is down";
|
|
|
|
}
|
|
|
|
|
2010-03-21 15:08:07 +00:00
|
|
|
InfoHash ih = null;
|
|
|
|
if (!fail) {
|
|
|
|
try {
|
2014-11-12 14:48:51 +00:00
|
|
|
ih = torrents.createInfoHash(info_hash);
|
|
|
|
} catch (IllegalArgumentException e) {
|
2010-03-21 15:08:07 +00:00
|
|
|
fail = true;
|
|
|
|
msg = "bad infohash " + e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Destination d = null;
|
|
|
|
if (!fail) {
|
|
|
|
try {
|
|
|
|
if (ip.endsWith(".i2p"))
|
|
|
|
ip = ip.substring(0, ip.length() - 4);
|
2014-08-09 16:21:02 +00:00
|
|
|
byte[] b = Base64.decode(ip);
|
|
|
|
if (b == null)
|
|
|
|
throw new Exception();
|
|
|
|
d = Destination.create(new ByteArrayInputStream(b)); // cache
|
2010-03-21 15:08:07 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
fail = true;
|
|
|
|
msg = "bad dest " + e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PID pid = null;
|
|
|
|
if (!fail) {
|
|
|
|
try {
|
2014-11-12 14:48:51 +00:00
|
|
|
pid = torrents.createPID(peer_id);
|
|
|
|
} catch (IllegalArgumentException e) {
|
2010-03-21 15:08:07 +00:00
|
|
|
fail = true;
|
|
|
|
msg = "bad peer id " + e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// int params
|
|
|
|
|
|
|
|
// ignored
|
|
|
|
long up = 0;
|
|
|
|
try {
|
|
|
|
up = Long.parseLong(uploaded);
|
|
|
|
if (up < 0)
|
|
|
|
up = 0;
|
|
|
|
} catch (NumberFormatException nfe) {};
|
|
|
|
|
|
|
|
// ignored
|
|
|
|
long down = 0;
|
|
|
|
try {
|
|
|
|
down = Long.parseLong(downloaded);
|
|
|
|
if (down < 0)
|
|
|
|
down = 0;
|
|
|
|
} catch (NumberFormatException nfe) {};
|
|
|
|
|
|
|
|
int want = MAX_RESPONSES;
|
|
|
|
try {
|
|
|
|
want = Integer.parseInt(numwant);
|
|
|
|
if (want > MAX_RESPONSES)
|
|
|
|
want = MAX_RESPONSES;
|
|
|
|
else if (want < 0)
|
|
|
|
want = 0;
|
|
|
|
} catch (NumberFormatException nfe) {};
|
|
|
|
|
2010-04-13 17:41:24 +00:00
|
|
|
// spoof check
|
|
|
|
// if him == null, we are not using the I2P HTTP server tunnel, or something is wrong
|
2010-07-09 16:31:24 +00:00
|
|
|
boolean matchIP = ALLOW_IP_MISMATCH || him == null || ip == null || ip.equals(him);
|
2010-04-13 17:41:24 +00:00
|
|
|
if (want <= 0 && (!matchIP) && !fail) {
|
|
|
|
fail = true;
|
|
|
|
msg = "ip mismatch";
|
|
|
|
}
|
|
|
|
|
2010-03-21 15:08:07 +00:00
|
|
|
long left = 0;
|
|
|
|
if (!"completed".equals(event)) {
|
|
|
|
try {
|
|
|
|
left = Long.parseLong(sleft);
|
|
|
|
if (left < 0)
|
|
|
|
left = 0;
|
|
|
|
} catch (NumberFormatException nfe) {};
|
|
|
|
}
|
|
|
|
|
2017-03-07 15:33:33 +00:00
|
|
|
Map<String, Object> m = new HashMap<String, Object>(8);
|
2010-03-21 15:08:07 +00:00
|
|
|
if (fail) {
|
|
|
|
m.put("failure reason", msg);
|
|
|
|
} else if ("stopped".equals(event)) {
|
|
|
|
Peers peers = torrents.get(ih);
|
2010-04-13 17:41:24 +00:00
|
|
|
if (matchIP && peers != null)
|
2010-03-21 15:08:07 +00:00
|
|
|
peers.remove(pid);
|
2014-11-12 15:19:13 +00:00
|
|
|
m.put("interval", torrents.getInterval());
|
2010-03-21 15:08:07 +00:00
|
|
|
} else {
|
|
|
|
Peers peers = torrents.get(ih);
|
|
|
|
if (peers == null) {
|
|
|
|
peers = new Peers();
|
|
|
|
Peers p2 = torrents.putIfAbsent(ih, peers);
|
|
|
|
if (p2 != null)
|
|
|
|
peers = p2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fixme same peer id, different dest
|
|
|
|
Peer p = peers.get(pid);
|
|
|
|
if (p == null) {
|
2025-04-28 07:44:37 -04:00
|
|
|
p = new Peer(pid.getData(), d);
|
2010-04-13 17:41:24 +00:00
|
|
|
// don't add if spoofed
|
|
|
|
if (matchIP) {
|
|
|
|
Peer p2 = peers.putIfAbsent(pid, p);
|
|
|
|
if (p2 != null)
|
|
|
|
p = p2;
|
|
|
|
}
|
2010-03-21 15:08:07 +00:00
|
|
|
}
|
2010-04-13 17:41:24 +00:00
|
|
|
// don't update if spoofed
|
|
|
|
if (matchIP)
|
|
|
|
p.setLeft(left);
|
2010-03-21 15:08:07 +00:00
|
|
|
|
2014-11-12 15:19:13 +00:00
|
|
|
m.put("interval", torrents.getInterval());
|
2010-03-21 15:08:07 +00:00
|
|
|
int size = peers.size();
|
|
|
|
int seeds = peers.countSeeds();
|
|
|
|
m.put("complete", Integer.valueOf(seeds));
|
|
|
|
m.put("incomplete", Integer.valueOf(size - seeds));
|
|
|
|
if (want <= 0) {
|
|
|
|
// snark < 0.7.13 always wants a list
|
|
|
|
m.put("peers", java.util.Collections.EMPTY_LIST);
|
|
|
|
} else {
|
2017-03-07 15:33:33 +00:00
|
|
|
List<Peer> peerlist = new ArrayList<Peer>(peers.values());
|
2010-03-21 15:08:07 +00:00
|
|
|
peerlist.remove(p); // them
|
|
|
|
if (want < size - 1) {
|
2015-04-16 22:33:50 +00:00
|
|
|
if (size > 150) {
|
|
|
|
// If size is huge, use random iterator for efficiency
|
|
|
|
List<Peer> rv = new ArrayList<Peer>(size);
|
2017-03-07 15:33:33 +00:00
|
|
|
for (RandomIterator<Peer> iter = new RandomIterator<Peer>(peerlist); iter.hasNext(); ) {
|
2015-04-16 22:33:50 +00:00
|
|
|
rv.add(iter.next());
|
|
|
|
}
|
|
|
|
peerlist = rv;
|
|
|
|
} else {
|
|
|
|
Collections.shuffle(peerlist);
|
|
|
|
peerlist = peerlist.subList(0, want);
|
|
|
|
}
|
2010-07-09 16:31:24 +00:00
|
|
|
}
|
|
|
|
if (compact) {
|
2025-04-27 17:04:46 -04:00
|
|
|
// one big string
|
2010-07-11 14:42:42 +00:00
|
|
|
byte[] peerhashes = new byte[32 * peerlist.size()];
|
|
|
|
for (int i = 0; i < peerlist.size(); i++)
|
2025-04-27 17:04:46 -04:00
|
|
|
System.arraycopy(peerlist.get(i).getHashBytes(), 0, peerhashes, i * 32, 32);
|
2010-07-09 16:31:24 +00:00
|
|
|
m.put("peers", peerhashes);
|
2025-04-27 17:04:46 -04:00
|
|
|
} else if (ALLOW_NONCOMPACT_RESPONSE) {
|
2025-04-28 07:44:37 -04:00
|
|
|
// This requires the Peer entries to be Maps
|
|
|
|
// so they can be bencoded, but we don't save
|
|
|
|
// the full Destination any more, and Peer does not.
|
|
|
|
// extend HashMap, to greatly reduce memory usage.
|
|
|
|
// We could create a Map here with the b32 as the IP,
|
|
|
|
// but that's nonstandard. So if non-compact is enabled,
|
|
|
|
// don't return any peers.
|
|
|
|
//m.put("peers", peerlist);
|
|
|
|
m.put("peers", java.util.Collections.EMPTY_LIST);
|
2025-04-27 17:04:46 -04:00
|
|
|
} else {
|
|
|
|
// won't get here
|
2010-03-21 15:08:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
BEncoder.bencode(m, cout);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove the newline on the last line or
|
|
|
|
* it will generate an IllegalStateException
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
%>
|