修改Tracker服务器相关bug

Change-Id: Id3ec61034575bd794724be98f832f8c065448b92
diff --git a/src/main/java/com/pt/utils/TorrentPasskeyModifier.java b/src/main/java/com/pt/utils/TorrentPasskeyModifier.java
new file mode 100644
index 0000000..dc5a82c
--- /dev/null
+++ b/src/main/java/com/pt/utils/TorrentPasskeyModifier.java
@@ -0,0 +1,218 @@
+package com.pt.utils;
+
+import java.io.*;
+import java.util.*;
+
+public class TorrentPasskeyModifier {
+
+    private byte[] infoValueBytes; // 保存 info 的 value 原始 bencode
+
+    public byte[] analyzeTorrentFile(byte[] fileBytes, String username) throws IOException {
+        ByteArrayInputStream in = new ByteArrayInputStream(fileBytes);
+        Map<String, Object> torrentMap = decodeWithInfoPreservation(in);
+
+        // 修改 announce
+        if (torrentMap.containsKey("announce")) {
+            String announce = (String) torrentMap.get("announce");
+            torrentMap.put("announce", replacePasskeyInUrl(announce, username));
+        }
+
+        // 修改 announce-list
+        if (torrentMap.containsKey("announce-list")) {
+            Object list = torrentMap.get("announce-list");
+            if (list instanceof List) {
+                replacePasskeyInAnnounceList((List<?>) list, username);
+            }
+        }
+
+        // 编码为新 torrent 文件
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write('d');
+
+        List<String> keys = new ArrayList<>(torrentMap.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            encodeString(key, out);
+            if ("info".equals(key)) {
+                out.write(infoValueBytes); // 写入原始 info 的 value bencode
+            } else {
+                encode(torrentMap.get(key), out);
+            }
+        }
+
+        out.write('e');
+        return out.toByteArray();
+    }
+
+    private String replacePasskeyInUrl(String url, String username) {
+        if (url == null) return null;
+        if (url.contains("passkey=")) {
+            return url.replaceAll("(?<=passkey=)[^&]*", username);
+        } else {
+            return url.contains("?")
+                    ? url + "&passkey=" + username
+                    : url + "?passkey=" + username;
+        }
+    }
+
+    private void replacePasskeyInAnnounceList(List<?> list, String username) {
+        for (Object tierObj : list) {
+            if (tierObj instanceof List) {
+                List<Object> tier = (List<Object>) tierObj;
+                for (int i = 0; i < tier.size(); i++) {
+                    Object url = tier.get(i);
+                    if (url instanceof String) {
+                        tier.set(i, replacePasskeyInUrl((String) url, username));
+                    }
+                }
+            }
+        }
+    }
+
+    // --- Bencode 解码并提取原始 info 字典字节 ---
+    private Map<String, Object> decodeWithInfoPreservation(InputStream in) throws IOException {
+        if (in.read() != 'd') throw new IOException("Not a bencode dict");
+        Map<String, Object> map = new LinkedHashMap<>();
+        while (true) {
+            int c = in.read();
+            if (c == 'e') break;
+            if (c == -1) throw new EOFException();
+
+            String key = decodeString(in, (char) c);
+            if ("info".equals(key)) {
+                ByteArrayOutputStream infoOut = new ByteArrayOutputStream();
+                in.mark(1);
+                int b = in.read();
+                if (b != 'd') throw new IOException("Invalid info dict");
+                infoOut.write(b);
+                int depth = 1;
+
+                while (depth > 0) {
+                    b = in.read();
+                    if (b == -1) throw new IOException("Unexpected EOF in info");
+                    infoOut.write(b);
+
+                    if (b == 'd' || b == 'l') depth++;
+                    else if (b == 'e') depth--;
+                    else if (b >= '0' && b <= '9') {
+                        int len = b - '0';
+                        while (true) {
+                            int nc = in.read();
+                            infoOut.write(nc);
+                            if (nc == ':') break;
+                            len = len * 10 + (nc - '0');
+                        }
+                        for (int i = 0; i < len; i++) {
+                            infoOut.write(in.read());
+                        }
+                    } else if (b == 'i') {
+                        while (true) {
+                            int nc = in.read();
+                            infoOut.write(nc);
+                            if (nc == 'e') break;
+                        }
+                    }
+                }
+
+                this.infoValueBytes = infoOut.toByteArray();
+                map.put("info", null); // 占位
+            } else {
+                map.put(key, decode(in));
+            }
+        }
+        return map;
+    }
+
+    private Object decode(InputStream in) throws IOException {
+        int c = in.read();
+        if (c == -1) throw new EOFException();
+        if (c == 'd') return decodeDict(in);
+        if (c == 'l') return decodeList(in);
+        if (c == 'i') return decodeInt(in);
+        if (c >= '0' && c <= '9') return decodeString(in, (char) c);
+        throw new IOException("Invalid bencode start: " + (char) c);
+    }
+
+    private Map<String, Object> decodeDict(InputStream in) throws IOException {
+        Map<String, Object> map = new LinkedHashMap<>();
+        while (true) {
+            int c = in.read();
+            if (c == 'e') break;
+            if (c == -1) throw new EOFException();
+            String key = decodeString(in, (char) c);
+            map.put(key, decode(in));
+        }
+        return map;
+    }
+
+    private List<Object> decodeList(InputStream in) throws IOException {
+        List<Object> list = new ArrayList<>();
+        while (true) {
+            int c = in.read();
+            if (c == 'e') break;
+            if (c == -1) throw new EOFException();
+            in.reset();
+            list.add(decode(in));
+        }
+        return list;
+    }
+
+    private String decodeString(InputStream in, char firstDigit) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(firstDigit);
+        while (true) {
+            int c = in.read();
+            if (c == ':') break;
+            sb.append((char) c);
+        }
+        int len = Integer.parseInt(sb.toString());
+        byte[] buf = new byte[len];
+        if (in.read(buf) != len) throw new EOFException();
+        return new String(buf, "UTF-8");
+    }
+
+    private Long decodeInt(InputStream in) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        while (true) {
+            int c = in.read();
+            if (c == 'e') break;
+            sb.append((char) c);
+        }
+        return Long.parseLong(sb.toString());
+    }
+
+    // --- 编码 ---
+    private void encode(Object obj, OutputStream out) throws IOException {
+        if (obj instanceof String) encodeString((String) obj, out);
+        else if (obj instanceof Integer || obj instanceof Long) {
+            out.write('i');
+            out.write(obj.toString().getBytes());
+            out.write('e');
+        } else if (obj instanceof List) {
+            out.write('l');
+            for (Object item : (List<?>) obj) encode(item, out);
+            out.write('e');
+        } else if (obj instanceof Map) {
+            out.write('d');
+            Map<String, Object> map = (Map<String, Object>) obj;
+            List<String> keys = new ArrayList<>(map.keySet());
+            Collections.sort(keys);
+            for (String key : keys) {
+                encodeString(key, out);
+                encode(map.get(key), out);
+            }
+            out.write('e');
+        } else if (obj == null) {
+            // 跳过
+        } else {
+            throw new IOException("Unsupported type: " + obj.getClass());
+        }
+    }
+
+    private void encodeString(String str, OutputStream out) throws IOException {
+        byte[] bytes = str.getBytes("UTF-8");
+        out.write(Integer.toString(bytes.length).getBytes());
+        out.write(':');
+        out.write(bytes);
+    }
+}