Compare commits

..

2 Commits

Author SHA1 Message Date
q 422d5b845b fix: recognize 123pan uid.share links and trim paste input
Clipboard auto-detect now matches {userId}.share.123pan.cn/123pan/{key}, and the share URL field is trimmed on paste and blur. Split web-service compiler args so IDE rebuilds no longer wipe classes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 01:22:47 +08:00
q 054e9cc1ec fix: harden gzip decode and release 0.4.5
Avoid ZipException when Vert.x already decompressed gzip bodies, keep jackson-databind on the IDE classpath, and show a real build version instead of unknown.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 01:00:32 +08:00
12 changed files with 73 additions and 19 deletions
+1 -1
View File
@@ -457,7 +457,7 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径 > 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell ```shell
cd ~ cd ~
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v0.4.2/netdisk-fast-download-linux-amd64.zip wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v0.4.5/netdisk-fast-download-linux-amd64.zip
unzip netdisk-fast-download.zip unzip netdisk-fast-download.zip
cd netdisk-fast-download cd netdisk-fast-download
bash service-install.sh bash service-install.sh
@@ -162,13 +162,18 @@ public class CommonUtil {
try (var is = CommonUtil.class.getClassLoader().getResourceAsStream("app.properties")) { try (var is = CommonUtil.class.getClassLoader().getResourceAsStream("app.properties")) {
if (is != null) { if (is != null) {
properties.load(is); properties.load(is);
if (!properties.isEmpty()) { String version = properties.getProperty("app.version");
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build"); String build = properties.getProperty("build");
if (version != null && !version.contains("${")) {
appVersion = version + "build" + (build == null || build.contains("${") ? "" : build);
} }
} }
} catch (IOException e) { } catch (Exception e) {
LOGGER.error("读取app.properties失败", e); LOGGER.error("读取app.properties失败", e);
} }
if (appVersion == null) {
appVersion = "unknown";
}
} }
return appVersion; return appVersion;
} }
+1 -1
View File
@@ -63,7 +63,7 @@
<lombok.version>1.18.38</lombok.version> <lombok.version>1.18.38</lombok.version>
<slf4j.version>2.0.16</slf4j.version> <slf4j.version>2.0.16</slf4j.version>
<commons-lang3.version>3.18.0</commons-lang3.version> <commons-lang3.version>3.18.0</commons-lang3.version>
<jackson.version>2.18.6</jackson.version> <jackson.version>2.18.9</jackson.version>
<logback.version>1.5.32</logback.version> <logback.version>1.5.32</logback.version>
<junit.version>4.13.2</junit.version> <junit.version>4.13.2</junit.version>
</properties> </properties>
@@ -368,9 +368,12 @@ public abstract class PanBase implements IPanTool, Closeable {
// 检查响应头中的Content-Encoding是否为gzip // 检查响应头中的Content-Encoding是否为gzip
String contentEncoding = res.getHeader("Content-Encoding"); String contentEncoding = res.getHeader("Content-Encoding");
try { try {
if ("gzip".equalsIgnoreCase(contentEncoding)) { if ("gzip".equalsIgnoreCase(contentEncoding) && res.body() instanceof Buffer gzipBody
&& gzipBody.length() >= 2
&& (gzipBody.getByte(0) & 0xff) == 0x1f
&& (gzipBody.getByte(1) & 0xff) == 0x8b) {
// 如果是gzip压缩的响应体,解压(只解压一次,缓存结果) // 如果是gzip压缩的响应体,解压(只解压一次,缓存结果)
String decompressed = decompressGzip((Buffer) res.body()); String decompressed = decompressGzip(gzipBody);
return new JsonObject(decompressed); return new JsonObject(decompressed);
} else { } else {
return res.bodyAsJsonObject(); return res.bodyAsJsonObject();
@@ -119,8 +119,18 @@ public class HttpResponseHelper {
}; };
} }
private static boolean looksLikeGzip(Buffer compressed) {
return compressed != null && compressed.length() >= 2
&& (compressed.getByte(0) & 0xff) == 0x1f
&& (compressed.getByte(1) & 0xff) == 0x8b;
}
// -------------------- gzip -------------------- // -------------------- gzip --------------------
private static String decompressGzip(Buffer compressed) throws IOException { private static String decompressGzip(Buffer compressed) throws IOException {
// Vert.x 可能已解压但仍带 Content-Encoding: gzip,再走 GZIPInputStream 会变成 ZipException
if (!looksLikeGzip(compressed)) {
return compressed.toString(StandardCharsets.UTF_8);
}
try (ByteArrayInputStream bais = new ByteArrayInputStream(compressed.getBytes()); try (ByteArrayInputStream bais = new ByteArrayInputStream(compressed.getBytes());
GZIPInputStream gzis = new GZIPInputStream(bais); GZIPInputStream gzis = new GZIPInputStream(bais);
InputStreamReader isr = new InputStreamReader(gzis, StandardCharsets.UTF_8); InputStreamReader isr = new InputStreamReader(gzis, StandardCharsets.UTF_8);
@@ -170,6 +170,10 @@ public class PanDomainTemplateTest {
assertTrue("YE should match redirected mshare URL", redirectedUrl.find()); assertTrue("YE should match redirected mshare URL", redirectedUrl.find());
assertEquals("lN7UVv-pbYJ", redirectedUrl.group("KEY")); assertEquals("lN7UVv-pbYJ", redirectedUrl.group("KEY"));
Matcher shareUrl = yePattern.matcher("https://1815268665.share.123pan.cn/123pan/iaKtVv-r4aCd");
assertTrue("YE should match uid.share.123pan.cn URL", shareUrl.find());
assertEquals("iaKtVv-r4aCd", shareUrl.group("KEY"));
Matcher htmlUrl = yePattern.matcher("https://www.123278.com/s/lN7UVv-pbYJ.html?pwd=abcd"); Matcher htmlUrl = yePattern.matcher("https://www.123278.com/s/lN7UVv-pbYJ.html?pwd=abcd");
assertTrue("YE should match html URL with query", htmlUrl.find()); assertTrue("YE should match html URL with query", htmlUrl.find());
assertEquals("lN7UVv-pbYJ", htmlUrl.group("KEY")); assertEquals("lN7UVv-pbYJ", htmlUrl.group("KEY"));
+1 -1
View File
@@ -17,7 +17,7 @@
</modules> </modules>
<properties> <properties>
<revision>0.4.4</revision> <revision>0.4.5</revision>
<java.version>17</java.version> <java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "nfd-web", "name": "nfd-web",
"version": "0.4.2", "version": "0.4.5",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
+6 -2
View File
@@ -246,8 +246,8 @@
storage: 'hash' storage: 'hash'
}, },
'123pan': { '123pan': {
reg: /((?:https?:\/\/)?www\.(123pan|123865|123684)\.com\/s\/[\w-]{6,})/, reg: /((?:https?:\/\/)?(?:[a-zA-Z\d-]+\.(?:m?share)\.123pan\.cn\/123pan\/[\w-]+|(?:www\.)?(?:123panpay|123pan|123\d{3})\.(?:com|cn)\/s\/[\w-]{6,}(?:\.html)?)(?:\?[^#\s]*)?)/i,
host: /www\.123pan\.com/, host: /(?:[a-zA-Z\d-]+\.(?:m?share)\.123pan\.cn|(?:www\.)?(?:123panpay|123pan|123\d{3})\.(?:com|cn))/i,
input: ['.ca-fot input', ".appinput .appinput"], input: ['.ca-fot input', ".appinput .appinput"],
button: ['.ca-fot button', ".appinput button"], button: ['.ca-fot button', ".appinput button"],
name: '123云盘', name: '123云盘',
@@ -377,6 +377,7 @@
parseLink(text = '') { parseLink(text = '') {
let obj = {name: '', link: '', storage: '', storagePwdName: ''}; let obj = {name: '', link: '', storage: '', storagePwdName: ''};
if (text) { if (text) {
text = String(text).trim();
try { try {
text = decodeURIComponent(text); text = decodeURIComponent(text);
} catch { } catch {
@@ -390,6 +391,9 @@
let matches = text.match(val.reg); let matches = text.match(val.reg);
obj.name = val.name; obj.name = val.name;
obj.link = matches[0]; obj.link = matches[0];
if (obj.link && !/^https?:\/\//i.test(obj.link)) {
obj.link = 'https://' + obj.link.replace(/^\/\//, '');
}
obj.storage = val.storage; obj.storage = val.storage;
obj.storagePwdName = val.storagePwdName || null; obj.storagePwdName = val.storagePwdName || null;
if (val.replaceHost) { if (val.replaceHost) {
+19 -5
View File
@@ -90,7 +90,7 @@
<!-- 开关按钮控制是否自动读取剪切板 --> <!-- 开关按钮控制是否自动读取剪切板 -->
<el-switch v-model="autoReadClipboard" active-text="自动识别剪切板"></el-switch> <el-switch v-model="autoReadClipboard" active-text="自动识别剪切板"></el-switch>
<el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url" @paste="onPaste"> <el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url" @paste="onPaste" @blur="normalizeShortcutInput">
<template #prepend>分享链接</template> <template #prepend>分享链接</template>
<template #append v-if="!autoReadClipboard"> <template #append v-if="!autoReadClipboard">
<el-button @click="getPaste(true)">读取剪切板</el-button> <el-button @click="getPaste(true)">读取剪切板</el-button>
@@ -1143,10 +1143,24 @@ export default {
normalizeShortcutInput() { normalizeShortcutInput() {
if (!this.link) return if (!this.link) return
const trimmed = this.link.trim() const trimmed = this.link.trim()
if (!trimmed) return if (!trimmed) {
this.link = ''
return
}
this.link = trimmed
// 已经是直接链接,跳过 // 已经是直接链接:仍尝试从整段文本中抽出标准分享地址
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
const linkInfo = parserUrl.parseLink(trimmed)
if (linkInfo.link) {
this.link = linkInfo.link
const pwd = parserUrl.parsePwd(trimmed)
if (!this.password && pwd) {
this.password = pwd
}
}
return
}
// 尝试短格式 // 尝试短格式
const shortInfo = this.expandShortFormat(trimmed) const shortInfo = this.expandShortFormat(trimmed)
@@ -1420,7 +1434,7 @@ export default {
// 获取剪切板内容 // 获取剪切板内容
async getPaste(isManual = false) { async getPaste(isManual = false) {
try { try {
const text = await navigator.clipboard.readText() const text = (await navigator.clipboard.readText() || '').trim()
const shortInfo = this.expandShortFormat(text) const shortInfo = this.expandShortFormat(text)
if (shortInfo) { if (shortInfo) {
+12 -1
View File
@@ -30,6 +30,16 @@
<groupId>cn.qaiu</groupId> <groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId> <artifactId>parser</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@@ -81,7 +91,8 @@
${project.basedir}/src/main/generated ${project.basedir}/src/main/generated
</generatedSourcesDirectory> </generatedSourcesDirectory>
<compilerArgs> <compilerArgs>
<arg>-AoutputDirectory=${project.basedir}/src/main -Xlint:unchecked</arg> <arg>-AoutputDirectory=${project.basedir}/src/main</arg>
<arg>-Xlint:unchecked</arg>
</compilerArgs> </compilerArgs>
</configuration> </configuration>
</plugin> </plugin>
@@ -463,11 +463,14 @@ public class ParserApi {
// 获取版本号 // 获取版本号
@RouteMapping("/build-version") @RouteMapping("/build-version")
public String getVersion() { public String getVersion() {
return CommonUtil.getAppVersion() String version = CommonUtil.getAppVersion();
if (version == null || version.isBlank()) {
return "unknown";
}
return version
.replace("-", "") .replace("-", "")
.replace("Z", "") .replace("Z", "")
.replace("T", "_") .replace("T", "_")
.replace("-", "")
.replace(":", ""); .replace(":", "");
} }