Files
i2p.plugins.zzzot/src/jsp/announce.jsp
zzz 1a90be6701 Disable returning non-compact peer list
We can no longer do it in a standard way, as we don't store the full destination.
Since we can't bencode Peer correctly any more, Peer no longer extends HashMap,
and we don't need the String destcache either.
This saves lots more space.
2025-04-28 07:44:37 -04:00

271 lines
7.6 KiB
Plaintext

<%@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" %><%
/*
* 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;
final boolean ALLOW_IP_MISMATCH = false;
final boolean ALLOW_COMPACT_RESPONSE = true;
final boolean ALLOW_NONCOMPACT_RESPONSE = false;
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
// 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");
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");
boolean compact = ALLOW_COMPACT_RESPONSE && request.getParameter("compact") != null;
// use to enforce destination
String him = request.getHeader("X-I2P-DestB64");
String xff = request.getHeader("X-Forwarded-For");
String xfs = request.getHeader("X-Forwarded-Server");
boolean fail = false;
String msg = "bad announce";
if (xff != null || xfs != null) {
fail = true;
msg = "Non-I2P access denied";
//response.setStatus(403, msg);
response.setStatus(403);
}
if (!compact && !ALLOW_NONCOMPACT_RESPONSE && !fail) {
fail = true;
msg = "non-compact responses unsupported";
}
if (info_hash == null && !fail) {
fail = true;
msg = "no info hash";
}
if (!fail && info_hash.length() != 20) {
fail = true;
msg = "bad info hash length " + info_hash.length();
}
if (ip == null && !fail) {
fail = true;
msg = "no ip (dest)";
}
if (peer_id == null && !fail) {
fail = true;
msg = "no peer id";
}
if (!fail && peer_id.length() != 20) {
fail = true;
msg = "bad peer id length " + peer_id.length();
}
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null && !fail) {
fail = true;
msg = "tracker is down";
}
InfoHash ih = null;
if (!fail) {
try {
ih = torrents.createInfoHash(info_hash);
} catch (IllegalArgumentException e) {
fail = true;
msg = "bad infohash " + e;
}
}
Destination d = null;
if (!fail) {
try {
if (ip.endsWith(".i2p"))
ip = ip.substring(0, ip.length() - 4);
byte[] b = Base64.decode(ip);
if (b == null)
throw new Exception();
d = Destination.create(new ByteArrayInputStream(b)); // cache
} catch (Exception e) {
fail = true;
msg = "bad dest " + e;
}
}
PID pid = null;
if (!fail) {
try {
pid = torrents.createPID(peer_id);
} catch (IllegalArgumentException e) {
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) {};
// spoof check
// if him == null, we are not using the I2P HTTP server tunnel, or something is wrong
boolean matchIP = ALLOW_IP_MISMATCH || him == null || ip == null || ip.equals(him);
if (want <= 0 && (!matchIP) && !fail) {
fail = true;
msg = "ip mismatch";
}
long left = 0;
if (!"completed".equals(event)) {
try {
left = Long.parseLong(sleft);
if (left < 0)
left = 0;
} catch (NumberFormatException nfe) {};
}
Map<String, Object> m = new HashMap<String, Object>(8);
if (fail) {
m.put("failure reason", msg);
} else if ("stopped".equals(event)) {
Peers peers = torrents.get(ih);
if (matchIP && peers != null)
peers.remove(pid);
m.put("interval", torrents.getInterval());
} 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) {
p = new Peer(pid.getData(), d);
// don't add if spoofed
if (matchIP) {
Peer p2 = peers.putIfAbsent(pid, p);
if (p2 != null)
p = p2;
}
}
// don't update if spoofed
if (matchIP)
p.setLeft(left);
m.put("interval", torrents.getInterval());
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 {
List<Peer> peerlist = new ArrayList<Peer>(peers.values());
peerlist.remove(p); // them
if (want < size - 1) {
if (size > 150) {
// If size is huge, use random iterator for efficiency
List<Peer> rv = new ArrayList<Peer>(size);
for (RandomIterator<Peer> iter = new RandomIterator<Peer>(peerlist); iter.hasNext(); ) {
rv.add(iter.next());
}
peerlist = rv;
} else {
Collections.shuffle(peerlist);
peerlist = peerlist.subList(0, want);
}
}
if (compact) {
// one big string
byte[] peerhashes = new byte[32 * peerlist.size()];
for (int i = 0; i < peerlist.size(); i++)
System.arraycopy(peerlist.get(i).getHashBytes(), 0, peerhashes, i * 32, 32);
m.put("peers", peerhashes);
} else if (ALLOW_NONCOMPACT_RESPONSE) {
// 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);
} else {
// won't get here
}
}
}
BEncoder.bencode(m, cout);
/*
* Remove the newline on the last line or
* it will generate an IllegalStateException
*
*/
%>