blob: dc5a82c241070b84f06f96a7e8e6b5461e53db88 [file] [log] [blame]
Edwardsamaxlcba512d2025-06-09 21:17:29 +08001package com.pt.utils;
2
3import java.io.*;
4import java.util.*;
5
6public class TorrentPasskeyModifier {
7
8 private byte[] infoValueBytes; // 保存 info 的 value 原始 bencode
9
10 public byte[] analyzeTorrentFile(byte[] fileBytes, String username) throws IOException {
11 ByteArrayInputStream in = new ByteArrayInputStream(fileBytes);
12 Map<String, Object> torrentMap = decodeWithInfoPreservation(in);
13
14 // 修改 announce
15 if (torrentMap.containsKey("announce")) {
16 String announce = (String) torrentMap.get("announce");
17 torrentMap.put("announce", replacePasskeyInUrl(announce, username));
18 }
19
20 // 修改 announce-list
21 if (torrentMap.containsKey("announce-list")) {
22 Object list = torrentMap.get("announce-list");
23 if (list instanceof List) {
24 replacePasskeyInAnnounceList((List<?>) list, username);
25 }
26 }
27
28 // 编码为新 torrent 文件
29 ByteArrayOutputStream out = new ByteArrayOutputStream();
30 out.write('d');
31
32 List<String> keys = new ArrayList<>(torrentMap.keySet());
33 Collections.sort(keys);
34 for (String key : keys) {
35 encodeString(key, out);
36 if ("info".equals(key)) {
37 out.write(infoValueBytes); // 写入原始 info 的 value bencode
38 } else {
39 encode(torrentMap.get(key), out);
40 }
41 }
42
43 out.write('e');
44 return out.toByteArray();
45 }
46
47 private String replacePasskeyInUrl(String url, String username) {
48 if (url == null) return null;
49 if (url.contains("passkey=")) {
50 return url.replaceAll("(?<=passkey=)[^&]*", username);
51 } else {
52 return url.contains("?")
53 ? url + "&passkey=" + username
54 : url + "?passkey=" + username;
55 }
56 }
57
58 private void replacePasskeyInAnnounceList(List<?> list, String username) {
59 for (Object tierObj : list) {
60 if (tierObj instanceof List) {
61 List<Object> tier = (List<Object>) tierObj;
62 for (int i = 0; i < tier.size(); i++) {
63 Object url = tier.get(i);
64 if (url instanceof String) {
65 tier.set(i, replacePasskeyInUrl((String) url, username));
66 }
67 }
68 }
69 }
70 }
71
72 // --- Bencode 解码并提取原始 info 字典字节 ---
73 private Map<String, Object> decodeWithInfoPreservation(InputStream in) throws IOException {
74 if (in.read() != 'd') throw new IOException("Not a bencode dict");
75 Map<String, Object> map = new LinkedHashMap<>();
76 while (true) {
77 int c = in.read();
78 if (c == 'e') break;
79 if (c == -1) throw new EOFException();
80
81 String key = decodeString(in, (char) c);
82 if ("info".equals(key)) {
83 ByteArrayOutputStream infoOut = new ByteArrayOutputStream();
84 in.mark(1);
85 int b = in.read();
86 if (b != 'd') throw new IOException("Invalid info dict");
87 infoOut.write(b);
88 int depth = 1;
89
90 while (depth > 0) {
91 b = in.read();
92 if (b == -1) throw new IOException("Unexpected EOF in info");
93 infoOut.write(b);
94
95 if (b == 'd' || b == 'l') depth++;
96 else if (b == 'e') depth--;
97 else if (b >= '0' && b <= '9') {
98 int len = b - '0';
99 while (true) {
100 int nc = in.read();
101 infoOut.write(nc);
102 if (nc == ':') break;
103 len = len * 10 + (nc - '0');
104 }
105 for (int i = 0; i < len; i++) {
106 infoOut.write(in.read());
107 }
108 } else if (b == 'i') {
109 while (true) {
110 int nc = in.read();
111 infoOut.write(nc);
112 if (nc == 'e') break;
113 }
114 }
115 }
116
117 this.infoValueBytes = infoOut.toByteArray();
118 map.put("info", null); // 占位
119 } else {
120 map.put(key, decode(in));
121 }
122 }
123 return map;
124 }
125
126 private Object decode(InputStream in) throws IOException {
127 int c = in.read();
128 if (c == -1) throw new EOFException();
129 if (c == 'd') return decodeDict(in);
130 if (c == 'l') return decodeList(in);
131 if (c == 'i') return decodeInt(in);
132 if (c >= '0' && c <= '9') return decodeString(in, (char) c);
133 throw new IOException("Invalid bencode start: " + (char) c);
134 }
135
136 private Map<String, Object> decodeDict(InputStream in) throws IOException {
137 Map<String, Object> map = new LinkedHashMap<>();
138 while (true) {
139 int c = in.read();
140 if (c == 'e') break;
141 if (c == -1) throw new EOFException();
142 String key = decodeString(in, (char) c);
143 map.put(key, decode(in));
144 }
145 return map;
146 }
147
148 private List<Object> decodeList(InputStream in) throws IOException {
149 List<Object> list = new ArrayList<>();
150 while (true) {
151 int c = in.read();
152 if (c == 'e') break;
153 if (c == -1) throw new EOFException();
154 in.reset();
155 list.add(decode(in));
156 }
157 return list;
158 }
159
160 private String decodeString(InputStream in, char firstDigit) throws IOException {
161 StringBuilder sb = new StringBuilder();
162 sb.append(firstDigit);
163 while (true) {
164 int c = in.read();
165 if (c == ':') break;
166 sb.append((char) c);
167 }
168 int len = Integer.parseInt(sb.toString());
169 byte[] buf = new byte[len];
170 if (in.read(buf) != len) throw new EOFException();
171 return new String(buf, "UTF-8");
172 }
173
174 private Long decodeInt(InputStream in) throws IOException {
175 StringBuilder sb = new StringBuilder();
176 while (true) {
177 int c = in.read();
178 if (c == 'e') break;
179 sb.append((char) c);
180 }
181 return Long.parseLong(sb.toString());
182 }
183
184 // --- 编码 ---
185 private void encode(Object obj, OutputStream out) throws IOException {
186 if (obj instanceof String) encodeString((String) obj, out);
187 else if (obj instanceof Integer || obj instanceof Long) {
188 out.write('i');
189 out.write(obj.toString().getBytes());
190 out.write('e');
191 } else if (obj instanceof List) {
192 out.write('l');
193 for (Object item : (List<?>) obj) encode(item, out);
194 out.write('e');
195 } else if (obj instanceof Map) {
196 out.write('d');
197 Map<String, Object> map = (Map<String, Object>) obj;
198 List<String> keys = new ArrayList<>(map.keySet());
199 Collections.sort(keys);
200 for (String key : keys) {
201 encodeString(key, out);
202 encode(map.get(key), out);
203 }
204 out.write('e');
205 } else if (obj == null) {
206 // 跳过
207 } else {
208 throw new IOException("Unsupported type: " + obj.getClass());
209 }
210 }
211
212 private void encodeString(String str, OutputStream out) throws IOException {
213 byte[] bytes = str.getBytes("UTF-8");
214 out.write(Integer.toString(bytes.length).getBytes());
215 out.write(':');
216 out.write(bytes);
217 }
218}