Merge branch 'main' of github.com:qaiu/netdisk-fast-download

This commit is contained in:
q
2026-08-18 00:21:51 +08:00
8 changed files with 286 additions and 37 deletions
@@ -382,9 +382,10 @@ public abstract class PanBase implements IPanTool, Closeable {
log.error("响应gzip解压或JSON解析失败: {}", e.getMessage());
fail("响应gzip解压或JSON解析失败: {}", e.getMessage());
} else {
// 上游响应体可能来自内网探测目标,仅写日志,避免经 HTTP 500 回传给调用方
String bodyPreview = responseBodyPreview(res);
log.error("解析失败: json格式异常: {}", bodyPreview);
fail("解析失败: json格式异常: {}", bodyPreview);
fail("解析失败: json格式异常");
}
return JsonObject.of();
}
@@ -121,9 +121,9 @@ public enum PanDomainTemplate {
"https://lecloud.lenovo.com/share/{shareKey}",
LeTool.class),
// https://v2.fangcloud.com/s/
// https://v2.fangcloud.com/s/ https://v2.fangcloud.cn/h5/share/ (移动端H5落地页)
FC("亿方云",
compile("https://v2\\.fangcloud\\.(com|cn)/(s|share|sharing)/(?<KEY>.+)"),
compile("https://v2\\.fangcloud\\.(com|cn)/(?:h5/)?(s|share|sharing)/(?<KEY>.+)"),
"https://v2.fangcloud.com/s/{shareKey}",
"https://www.fangcloud.com/",
FcTool.class),
@@ -111,7 +111,8 @@ public class Ce4Tool extends PanBase {
private void requestShareDetail(String baseUrl, String key, String pwd, String path) {
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
// 禁止跟随重定向:防止公网 host 302 到内网/元数据绕过 assertPublicHost
HttpRequest<Buffer> httpRequest = clientNoRedirects.getAbs(shareApiUrl);
if (pwd != null && !pwd.isEmpty()) {
httpRequest.addQueryParam("password", pwd);
}
@@ -232,7 +233,7 @@ public class Ce4Tool extends PanBase {
.put("uris", new JsonArray().add(filePath))
.put("download", true);
clientSession.postAbs(fileUrlApi)
clientNoRedirects.postAbs(fileUrlApi)
.putHeader("Content-Type", "application/json")
.sendJsonObject(requestBody)
.onSuccess(res -> {
@@ -78,7 +78,8 @@ public class CeTool extends PanBase {
private void tryV4Ping(String baseUrl, String key, String pwd) {
String pingUrlV4 = baseUrl + PING_API_V4_PATH;
clientSession.getAbs(pingUrlV4).send().onSuccess(res -> {
// 禁止跟随重定向:assertPublicHost 只校验初始 host,自动 30x 会绕过 SSRF 防护
clientNoRedirects.getAbs(pingUrlV4).send().onSuccess(res -> {
if (res.statusCode() == 200) {
try {
JsonObject json = asJson(res);
@@ -108,7 +109,7 @@ public class CeTool extends PanBase {
private void tryV3Ping(String baseUrl, String key, String pwd) {
String pingUrlV3 = baseUrl + PING_API_V3_PATH;
clientSession.getAbs(pingUrlV3).send().onSuccess(res -> {
clientNoRedirects.getAbs(pingUrlV3).send().onSuccess(res -> {
if (res.statusCode() == 200) {
try {
JsonObject json = asJson(res);
@@ -139,7 +140,7 @@ public class CeTool extends PanBase {
*/
private void verifyV3AndParse(String baseUrl, String key, String pwd) {
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
HttpRequest<Buffer> httpRequest = clientNoRedirects.getAbs(shareApiUrl);
if (pwd != null && !pwd.isEmpty()) {
httpRequest.addQueryParam("password", pwd);
}
@@ -175,7 +176,7 @@ public class CeTool extends PanBase {
*/
private void tryV4ShareApi(String baseUrl, String key, String pwd) {
String shareApiUrl = baseUrl + "/api/v4/share/info/" + key;
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
HttpRequest<Buffer> httpRequest = clientNoRedirects.getAbs(shareApiUrl);
if (pwd != null && !pwd.isEmpty()) {
httpRequest.addQueryParam("password", pwd);
}
@@ -291,7 +292,8 @@ public class CeTool extends PanBase {
}
private void getDownURL(String shareApiUrl) {
clientSession.putAbs(shareApiUrl)
// PUT 默认不跟随重定向,但仍统一使用 no-redirect 客户端避免配置漂移
clientNoRedirects.putAbs(shareApiUrl)
.putHeader("Referer", shareLinkInfo.getShareUrl())
.send().onSuccess(res -> {
JsonObject jsonObject = asJson(res);
@@ -22,6 +22,7 @@ public class FcTool extends PanBase {
public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/";
public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/";
private static final String SHARE_INFO_URL = "https://v2.fangcloud.cn/apps/share_links/info/";
private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" +
"&scenario=share&unique_name={uname}";
@@ -38,6 +39,25 @@ public class FcTool extends PanBase {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
WebClientSession sClient = WebClientSession.create(client);
// 先查询分享有效性, 避免分享已失效/已过期时仍去解析HTML, 报出令人困惑的技术错误
sClient.getAbs(SHARE_INFO_URL + dataKey).send().onSuccess(infoRes -> {
JsonObject infoJson = asJson(infoRes);
if (promise.future().isComplete()) {
return;
}
JsonObject process = infoJson.getJsonObject("process");
boolean isClosed = process != null && Boolean.TRUE.equals(process.getBoolean("is_closed"));
boolean isExpired = process != null && Boolean.TRUE.equals(process.getBoolean("is_expired"));
if (process == null || isClosed || isExpired) {
fail("分享已失效或不存在");
return;
}
doParse(dataKey, pwd, sClient);
}).onFailure(handleFail(SHARE_INFO_URL + dataKey));
return promise.future();
}
private void doParse(String dataKey, String pwd, WebClientSession sClient) {
// 第一次请求 自动重定向
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
@@ -67,7 +87,6 @@ public class FcTool extends PanBase {
}
getDownURL(dataKey, promise, res, sClient);
}).onFailure(handleFail(SHARE_URL_PREFIX + dataKey));
return promise.future();
}
private void getDownURL(String dataKey, Promise<String> promise, HttpResponse<Buffer> res,
@@ -0,0 +1,44 @@
package cn.qaiu.parser;
import org.junit.Test;
import java.io.IOException;
import java.net.URL;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* GHSA-997r-7xx2-p9x6 regression: Cloudreve generic parser must reject
* hosts that resolve to loopback / private / link-local / metadata ranges
* before any outbound request.
*/
public class AssertPublicHostTest {
@Test
public void rejectsLoopbackAndPrivateHosts() throws Exception {
String[] blocked = {
"http://127.0.0.1.nip.io/s/poc",
"http://localhost/s/poc",
"http://10.0.0.1/s/poc",
"http://192.168.1.1/s/poc",
"http://172.16.0.1/s/poc",
"http://169.254.169.254/s/poc",
"http://[::1]/s/poc"
};
for (String raw : blocked) {
try {
PanBase.assertPublicHost(new URL(raw));
fail("expected block for " + raw);
} catch (IOException expected) {
assertTrue(expected.getMessage().contains("不允许访问")
|| expected.getMessage().contains("无法解析"));
}
}
}
@Test
public void allowsPublicHost() throws Exception {
PanBase.assertPublicHost(new URL("https://example.com/s/demo"));
}
}