mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-08-27 03:52:02 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e853365fe4 |
@@ -262,9 +262,27 @@ jobs:
|
|||||||
name: ${{ matrix.artifact-name }}
|
name: ${{ matrix.artifact-name }}
|
||||||
path: ${{ matrix.artifact-name }}.zip
|
path: ${{ matrix.artifact-name }}.zip
|
||||||
|
|
||||||
- name: 上传到 Release
|
# ================================================================
|
||||||
|
# 阶段三:创建 GitHub Release(只跑一次,避免矩阵任务重复拼接更新日志)
|
||||||
|
# ================================================================
|
||||||
|
publish-release:
|
||||||
|
name: 发布 GitHub Release
|
||||||
|
needs: native-package
|
||||||
|
if: github.event_name != 'pull_request' && github.ref_type == 'tag'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 下载原生安装包
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: netdisk-fast-download-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: 创建 Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ${{ matrix.artifact-name }}.zip
|
files: |
|
||||||
|
netdisk-fast-download-linux-amd64.zip
|
||||||
|
netdisk-fast-download-windows-amd64.zip
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ github.ref_name }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.WebClientVertxInit;
|
||||||
import cn.qaiu.entity.FileInfo;
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
@@ -8,20 +9,30 @@ import io.netty.handler.codec.http.cookie.DefaultCookie;
|
|||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
import io.vertx.core.net.ProxyType;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientOptions;
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 蓝奏云解析工具
|
* 蓝奏云解析工具
|
||||||
@@ -30,12 +41,26 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class LzTool extends PanBase {
|
public class LzTool extends PanBase {
|
||||||
|
|
||||||
WebClientSession webClientSession = WebClientSession.create(clientNoRedirects);
|
/** ESA 对 gzip 响应常见不带可识别的 Content-Encoding,需客户端自动解压。 */
|
||||||
|
private final WebClient lzClient;
|
||||||
|
private WebClientSession webClientSession;
|
||||||
|
|
||||||
public static final String SHARE_URL_PREFIX = "https://w1.lanzn.com/";
|
public static final String SHARE_URL_PREFIX = "https://w1.lanzn.com/";
|
||||||
|
|
||||||
// 静态编译的正则表达式,避免每次调用都重新编译
|
// 静态编译的正则表达式,避免每次调用都重新编译
|
||||||
private static final Pattern FILE_NAME_PATTERN = Pattern.compile("padding: 56px 0px 20px 0px;\">(.*?)<|filenajax\">(.*?)<");
|
private static final Pattern FILE_NAME_PATTERN = Pattern.compile("padding: 56px 0px 20px 0px;\">(.*?)<|filenajax\">(.*?)<");
|
||||||
|
private static final Pattern P_WP_SIGN = Pattern.compile("wp_sign\\s*=\\s*'([^']+)'");
|
||||||
|
private static final Pattern P_AJAXDATA = Pattern.compile("ajaxdata\\s*=\\s*'([^']+)'");
|
||||||
|
private static final Pattern P_WEBSIGN = Pattern.compile("'websign'\\s*:\\s*'([^']*)'");
|
||||||
|
private static final Pattern P_AJAX_PATH = Pattern.compile("(?:['\"/]|^)(ajax(?:m|file)\\.php\\?file=\\d+)");
|
||||||
|
private static final Pattern P_SIGN = Pattern.compile("'sign'\\s*:\\s*'([^']+)'");
|
||||||
|
private static final Pattern P_ISNGIS = Pattern.compile("var\\s+isngis\\s*=\\s*'([^']+)'");
|
||||||
|
private static final Pattern P_KDNS = Pattern.compile("var\\s+kdns\\s*=\\s*(\\d+)");
|
||||||
|
private static final Pattern P_FILEMORE = Pattern.compile(
|
||||||
|
"url\\s*:\\s*'(/filemoreajax\\.php\\?file=\\d+)'[\\s\\S]*?data\\s*:\\s*\\{([^}]+)\\}");
|
||||||
|
private static final Pattern P_DATA_KV = Pattern.compile("'(\\w+)'\\s*:\\s*('(?:\\\\'|[^'])*'|\\d+|\\w+)");
|
||||||
|
private static final Pattern P_INLINE_SCRIPT =
|
||||||
|
Pattern.compile("(?is)<script(?![^>]*\\bsrc\\s*=)[^>]*>(.*?)</script>");
|
||||||
private static final Pattern FILE_SIZE_PATTERN = Pattern.compile(">文件大小:</span>(.*?)<br>|\"n_filesize\">大小:(.*?)</div>");
|
private static final Pattern FILE_SIZE_PATTERN = Pattern.compile(">文件大小:</span>(.*?)<br>|\"n_filesize\">大小:(.*?)</div>");
|
||||||
private static final Pattern SHARE_USER_PATTERN = Pattern.compile(">分享用户:</span><font>(.*?)</font>|获取<span>(.*?)</span>的文件|\"user-name\">(.*?)</");
|
private static final Pattern SHARE_USER_PATTERN = Pattern.compile(">分享用户:</span><font>(.*?)</font>|获取<span>(.*?)</span>的文件|\"user-name\">(.*?)</");
|
||||||
private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?s)文件描述:</span><br>(.*?)</td>|class=\"n_box_des\">(.*?)</div>");
|
private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?s)文件描述:</span><br>(.*?)</td>|class=\"n_box_des\">(.*?)</div>");
|
||||||
@@ -43,13 +68,14 @@ public class LzTool extends PanBase {
|
|||||||
private static final Pattern CREATE_TIME_PATTERN = Pattern.compile(">上传时间:</span>(.*?)<");
|
private static final Pattern CREATE_TIME_PATTERN = Pattern.compile(">上传时间:</span>(.*?)<");
|
||||||
private static final Pattern URL_DATE_PATTERN = Pattern.compile("(\\d{4}/\\d{1,2}/\\d{1,2})");
|
private static final Pattern URL_DATE_PATTERN = Pattern.compile("(\\d{4}/\\d{1,2}/\\d{1,2})");
|
||||||
private static final Pattern ARG1_PATTERN = Pattern.compile("var arg1='([^']+)'");
|
private static final Pattern ARG1_PATTERN = Pattern.compile("var arg1='([^']+)'");
|
||||||
private static final Pattern IFRAME_SRC_PATTERN = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
|
private static final Pattern IFRAME_SRC_PATTERN = Pattern.compile(
|
||||||
|
"src\\s*=\\s*[\"'](/fn\\?[^\"'\\s>]+)[\"']", Pattern.CASE_INSENSITIVE);
|
||||||
private static final Pattern RELATIVE_TIME_PATTERN = Pattern.compile("^(\\d+|几)\\s*(分钟|小时)前$");
|
private static final Pattern RELATIVE_TIME_PATTERN = Pattern.compile("^(\\d+|几)\\s*(分钟|小时)前$");
|
||||||
private static final Pattern DATE_PATTERN = Pattern.compile("^(\\d{4})\\s*[-/年]\\s*(\\d{1,2})\\s*[-/月]\\s*(\\d{1,2})\\s*日?$");
|
private static final Pattern DATE_PATTERN = Pattern.compile("^(\\d{4})\\s*[-/年]\\s*(\\d{1,2})\\s*[-/月]\\s*(\\d{1,2})\\s*日?$");
|
||||||
private static final Pattern MONTH_DAY_PATTERN = Pattern.compile("^(\\d{1,2})\\s*月\\s*(\\d{1,2})\\s*日?$");
|
private static final Pattern MONTH_DAY_PATTERN = Pattern.compile("^(\\d{1,2})\\s*月\\s*(\\d{1,2})\\s*日?$");
|
||||||
MultiMap headers0 = HeaderUtils.parseHeaders("""
|
MultiMap headers0 = HeaderUtils.parseHeaders("""
|
||||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
||||||
Accept-Encoding: gzip, deflate
|
Accept-Encoding: identity
|
||||||
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||||
Cache-Control: max-age=0
|
Cache-Control: max-age=0
|
||||||
DNT: 1
|
DNT: 1
|
||||||
@@ -68,20 +94,115 @@ public class LzTool extends PanBase {
|
|||||||
|
|
||||||
public LzTool(ShareLinkInfo shareLinkInfo) {
|
public LzTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
|
this.lzClient = createLzClient(shareLinkInfo);
|
||||||
|
this.webClientSession = WebClientSession.create(lzClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ESA 对 gzip 响应常见不带可识别的 Content-Encoding,需客户端自动解压;
|
||||||
|
* 代理模式必须带上 shareLinkInfo 里的 proxy,不能绕过全局代理。
|
||||||
|
*/
|
||||||
|
private static WebClient createLzClient(ShareLinkInfo shareLinkInfo) {
|
||||||
|
WebClientOptions opts = new WebClientOptions()
|
||||||
|
.setFollowRedirects(false)
|
||||||
|
.setDecompressionSupported(true)
|
||||||
|
.setUserAgentEnabled(false)
|
||||||
|
.setConnectTimeout(10000)
|
||||||
|
.setIdleTimeout(12);
|
||||||
|
if (shareLinkInfo != null && shareLinkInfo.getOtherParam().containsKey("proxy")) {
|
||||||
|
JsonObject proxy = (JsonObject) shareLinkInfo.getOtherParam().get("proxy");
|
||||||
|
if (proxy != null && proxy.getString("host") != null) {
|
||||||
|
ProxyOptions proxyOptions = new ProxyOptions()
|
||||||
|
.setType(ProxyType.valueOf(proxy.getString("type", "http").toUpperCase()))
|
||||||
|
.setHost(proxy.getString("host"))
|
||||||
|
.setPort(proxy.getInteger("port", 0));
|
||||||
|
if (StringUtils.isNotEmpty(proxy.getString("username"))) {
|
||||||
|
proxyOptions.setUsername(proxy.getString("username"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(proxy.getString("password"))) {
|
||||||
|
proxyOptions.setPassword(proxy.getString("password"));
|
||||||
|
}
|
||||||
|
opts.setProxyOptions(proxyOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WebClient.create(WebClientVertxInit.get(), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vert.x decompressionSupported 可能已经解压,但响应头仍带 gzip;
|
||||||
|
* 再走 PanBase.asText 会二次解压得到乱码,iframe / down_p 都匹配不到。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String asText(HttpResponse<?> res) {
|
||||||
|
try {
|
||||||
|
Object raw = res.body();
|
||||||
|
if (raw instanceof Buffer body && body.length() > 0) {
|
||||||
|
int i = 0;
|
||||||
|
int len = body.length();
|
||||||
|
while (i < len) {
|
||||||
|
byte c = body.getByte(i);
|
||||||
|
if (c != ' ' && c != '\n' && c != '\r' && c != '\t') {
|
||||||
|
if (c == '<' || c == '{' || c == '[') {
|
||||||
|
return body.toString();
|
||||||
|
}
|
||||||
|
if ((c & 0xff) == 0x1f && i + 1 < len && (body.getByte(i + 1) & 0xff) == 0x8b) {
|
||||||
|
return gunzipUtf8(body);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return super.asText(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String gunzipUtf8(Buffer body) {
|
||||||
|
try (GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(body.getBytes()))) {
|
||||||
|
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return body.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObject asJson(HttpResponse<?> res) {
|
||||||
|
JsonObject parsed = parseLzJson(asText(res));
|
||||||
|
if (parsed != null) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
return super.asJson(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ajax 常被标成 gzip/text/json,body 可能已解压或为空,不能直接 bodyAsJsonObject。 */
|
||||||
|
private static JsonObject parseLzJson(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String t = text.trim();
|
||||||
|
int start = t.indexOf('{');
|
||||||
|
if (start < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new JsonObject(t.substring(start));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String sUrl = shareLinkInfo.getStandardUrl();
|
String sUrl = shareLinkInfo.getStandardUrl();
|
||||||
String pwd = shareLinkInfo.getSharePassword();
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
WebClient client = clientNoRedirects;
|
webClientSession.getAbs(sUrl)
|
||||||
client.getAbs(sUrl)
|
|
||||||
.putHeaders(headers0)
|
.putHeaders(headers0)
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
try {
|
try {
|
||||||
String html = asText(res);
|
String html = asText(res);
|
||||||
if (hasAcwArg1(html)) {
|
if (hasAcwArg1(html)) {
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
webClientSession = WebClientSession.create(lzClient);
|
||||||
if (!setCookie(html, sUrl)) {
|
if (!setCookie(html, sUrl)) {
|
||||||
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
||||||
return;
|
return;
|
||||||
@@ -117,14 +238,14 @@ public class LzTool extends PanBase {
|
|||||||
fail("分享已失效或文件已取消分享");
|
fail("分享已失效或文件已取消分享");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检测是否为目录分享链接 (含 /s/、/b/ 路径段或 b 开头的路径段)
|
// 检测是否为目录分享链接 (含 /s/、/b/ 路径段或 b0 开头的路径段)
|
||||||
if (sUrl.matches(".*/(s|b)/[^/]+.*") || sUrl.matches(".*/b[^/]+.*")) {
|
if (sUrl.matches(".*/(s|b)/[^/]+.*") || sUrl.matches(".*/b0[^/]+.*")) {
|
||||||
fail("该链接为蓝奏云目录分享,请使用目录解析接口");
|
fail("该链接为蓝奏云目录分享,请使用目录解析接口");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 若仍是校验页 (parse()中cookie域名与实际URL不匹配时会出现), 重试一次
|
// 若仍是校验页 (parse()中cookie域名与实际URL不匹配时会出现), 重试一次
|
||||||
if (hasAcwArg1(html)) {
|
if (hasAcwArg1(html)) {
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
webClientSession = WebClientSession.create(lzClient);
|
||||||
if (!setCookie(html, sUrl)) {
|
if (!setCookie(html, sUrl)) {
|
||||||
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
||||||
return;
|
return;
|
||||||
@@ -168,69 +289,76 @@ public class LzTool extends PanBase {
|
|||||||
Matcher matcher = IFRAME_SRC_PATTERN.matcher(html);
|
Matcher matcher = IFRAME_SRC_PATTERN.matcher(html);
|
||||||
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
||||||
if (!matcher.find()) {
|
if (!matcher.find()) {
|
||||||
|
boolean pwdPage = html.contains("down_p") || html.contains("id=\"pwd\"") || html.contains("id='pwd'");
|
||||||
|
if (pwdPage && (pwd == null || pwd.isBlank())) {
|
||||||
|
fail("需要访问密码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
String jsText = getJsByPwd(pwd, html, "document.getElementById('rpt')");
|
if (!postAjaxFromHtml(sUrl, html, pwd)) {
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
|
fail("未找到下载参数,可能密码错误或分享已失效 htmlLen=" + html.length()
|
||||||
getDownURL(sUrl, scriptObjectMirror);
|
+ " hasFn=" + html.contains("/fn?")
|
||||||
|
+ " hasIframe=" + html.contains("iframe"));
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fail(e, "js引擎执行失败");
|
fail(e, "js引擎执行失败 htmlLen=" + html.length()
|
||||||
|
+ " hasFn=" + html.contains("/fn?")
|
||||||
|
+ " hasIframe=" + html.contains("iframe"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有密码
|
// 没有密码
|
||||||
String iframePath = matcher.group(1);
|
String iframePath = matcher.group(1);
|
||||||
String absoluteURI = SHARE_URL_PREFIX + iframePath;
|
String absoluteURI = joinUrl(SHARE_URL_PREFIX, iframePath);
|
||||||
// 创建局部副本,避免修改实例字段导致累积
|
// 创建局部副本,避免修改实例字段导致累积
|
||||||
MultiMap headersCopy = MultiMap.caseInsensitiveMultiMap().addAll(headers0);
|
MultiMap headersCopy = MultiMap.caseInsensitiveMultiMap().addAll(headers0);
|
||||||
headersCopy.add("Referer", absoluteURI);
|
headersCopy.add("Referer", absoluteURI);
|
||||||
webClientSession.getAbs(absoluteURI).putHeaders(headersCopy).send().onSuccess(res2 -> {
|
webClientSession.getAbs(absoluteURI).putHeaders(headersCopy).send().onSuccess(res2 -> {
|
||||||
try {
|
try {
|
||||||
String html2 = asText(res2);
|
String html2 = asText(res2);
|
||||||
|
handleIframeHtml(html2, sUrl, absoluteURI, iframePath, headersCopy);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("蓝奏云 iframe 响应处理异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(SHARE_URL_PREFIX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIframeHtml(String html2, String sUrl, String absoluteURI, String iframePath, MultiMap headersCopy) {
|
||||||
if (isShareCancelledPage(html2)) {
|
if (isShareCancelledPage(html2)) {
|
||||||
fail("分享已失效或文件已取消分享");
|
fail("分享已失效或文件已取消分享");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String jsText = getJsText(html2);
|
if (hasAcwArg1(html2)) {
|
||||||
if (jsText == null) {
|
|
||||||
if (!setCookie(html2, absoluteURI)) {
|
if (!setCookie(html2, absoluteURI)) {
|
||||||
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
fail("蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
webClientSession.getAbs(absoluteURI).send().onSuccess(res3 -> {
|
webClientSession.getAbs(absoluteURI).putHeaders(headersCopy).send().onSuccess(res3 -> {
|
||||||
try {
|
try {
|
||||||
String html3 = asText(res3);
|
String html3 = asText(res3);
|
||||||
if (isShareCancelledPage(html3)) {
|
if (isShareCancelledPage(html3)) {
|
||||||
fail("分享已失效或文件已取消分享");
|
fail("分享已失效或文件已取消分享");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String jsText3 = getJsText(html3);
|
submitIframeAjax(html3, sUrl, absoluteURI, iframePath);
|
||||||
if (jsText3 != null) {
|
|
||||||
try {
|
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText3, null);
|
|
||||||
getDownURL(sUrl, scriptObjectMirror);
|
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
|
||||||
fail(e, "引擎执行失败");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": 获取失败0, 可能分享已失效");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fail("蓝奏云 iframe 响应处理异常: {}", e.getMessage());
|
fail("蓝奏云 iframe 响应处理异常: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(absoluteURI));
|
}).onFailure(handleFail(absoluteURI));
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
submitIframeAjax(html2, sUrl, absoluteURI, iframePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitIframeAjax(String iframeHtml, String sUrl, String absoluteURI, String iframePath) {
|
||||||
try {
|
try {
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
if (!postAjaxFromHtml(absoluteURI, iframeHtml, null)) {
|
||||||
getDownURL(sUrl, scriptObjectMirror);
|
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": 获取失败0, 可能分享已失效");
|
||||||
|
}
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
fail(e, "js引擎执行失败");
|
fail(e, "js引擎执行失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
fail("蓝奏云 iframe 响应处理异常: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}).onFailure(handleFail(SHARE_URL_PREFIX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setCookie(String html, String url) {
|
private boolean setCookie(String html, String url) {
|
||||||
String arg1 = extractAcwArg1(html);
|
String arg1 = extractAcwArg1(html);
|
||||||
@@ -262,9 +390,17 @@ public class LzTool extends PanBase {
|
|||||||
String jsText = getJsText(html);
|
String jsText = getJsText(html);
|
||||||
|
|
||||||
if (jsText == null) {
|
if (jsText == null) {
|
||||||
throw new RuntimeException("获取失败1, 可能分享已失效");
|
throw new RuntimeException("获取失败1, 可能分享已失效 htmlLen=" + (html == null ? 0 : html.length())
|
||||||
|
+ " hasFn=" + (html != null && html.contains("/fn?"))
|
||||||
|
+ " hasScript=" + (html != null && html.contains("<script")));
|
||||||
|
}
|
||||||
|
if (pwd != null) {
|
||||||
|
String quoted = "\"" + pwd.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||||
|
jsText = jsText.replace("document.getElementById('pwd').value", quoted);
|
||||||
|
jsText = jsText.replace("document.getElementById(\"pwd\").value", quoted);
|
||||||
|
jsText = jsText.replace("document.querySelector('#pwd').value", quoted);
|
||||||
|
jsText = jsText.replace("document.querySelector(\"#pwd\").value", quoted);
|
||||||
}
|
}
|
||||||
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
|
||||||
int i = jsText.indexOf(subText);
|
int i = jsText.indexOf(subText);
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
jsText = jsText.substring(0, i);
|
jsText = jsText.substring(0, i);
|
||||||
@@ -273,21 +409,43 @@ public class LzTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getJsText(String html) {
|
private String getJsText(String html) {
|
||||||
if (html == null) {
|
if (html == null || html.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Matcher m = P_INLINE_SCRIPT.matcher(html);
|
||||||
|
String lastAjax = null;
|
||||||
|
while (m.find()) {
|
||||||
|
String body = m.group(1).replaceAll("<!--.*?-->", "").trim();
|
||||||
|
if (body.isEmpty() || body.contains("var arg1=") || body.contains("arg1='")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (body.contains("$.ajax") || body.contains("down_p") || body.contains("wp_sign")
|
||||||
|
|| body.contains("ajaxdata") || body.contains("filemoreajax")) {
|
||||||
|
lastAjax = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastAjax != null) {
|
||||||
|
return lastAjax;
|
||||||
|
}
|
||||||
String jsTagStart = "<script type=\"text/javascript\">";
|
String jsTagStart = "<script type=\"text/javascript\">";
|
||||||
String jsTagEnd = "</script>";
|
|
||||||
int index = html.lastIndexOf(jsTagStart);
|
int index = html.lastIndexOf(jsTagStart);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int startPos = index + jsTagStart.length();
|
int startPos = index + jsTagStart.length();
|
||||||
int endPos = html.indexOf(jsTagEnd, startPos);
|
int endPos = html.indexOf("</script>", startPos);
|
||||||
if (endPos <= startPos) {
|
if (endPos < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return html.substring(startPos, endPos).replaceAll("<!--.*-->", "");
|
String fallback = html.substring(startPos, endPos).replaceAll("<!--.*-->", "");
|
||||||
|
if (fallback.contains("var arg1=") || fallback.contains("arg1='")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (fallback.contains("$.ajax") || fallback.contains("wp_sign") || fallback.contains("down_p")
|
||||||
|
|| fallback.contains("ajaxdata") || fallback.contains("filemoreajax")) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String extractAcwArg1(String html) {
|
static String extractAcwArg1(String html) {
|
||||||
@@ -316,20 +474,210 @@ public class LzTool extends PanBase {
|
|||||||
return html != null && html.contains("var arg1='");
|
return html != null && html.contains("var arg1='");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean postAjaxFromHtml(String referer, String html, String pwd)
|
||||||
|
throws ScriptException, NoSuchMethodException {
|
||||||
|
AjaxCall call = extractAjaxFromHtml(html, pwd);
|
||||||
|
if (call != null) {
|
||||||
|
getDownURL(referer, call);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String jsText;
|
||||||
|
try {
|
||||||
|
jsText = (pwd != null && !pwd.isBlank())
|
||||||
|
? getJsByPwd(pwd, html, "document.getElementById('rpt')")
|
||||||
|
: getJsText(html);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (jsText == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String fun = (pwd != null && !pwd.isBlank() && jsText.contains("down_p")) ? "down_p" : null;
|
||||||
|
ScriptObjectMirror mirror = JsExecUtils.executeDynamicJs(jsText, fun, pwd);
|
||||||
|
if (mirror == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
getDownURL(referer, mirror);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 页面里提取出的 ajax 调用:相对路径 + 表单参数。 */
|
||||||
|
record AjaxCall(String path, Map<String, String> form) {
|
||||||
|
MultiMap toForm() {
|
||||||
|
MultiMap m = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
form.forEach(m::set);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getDownURL(String referer, AjaxCall call) {
|
||||||
|
Map<String, Object> obj = new LinkedHashMap<>();
|
||||||
|
obj.put("url", call.path());
|
||||||
|
obj.put("data", call.form());
|
||||||
|
getDownURL(referer, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AjaxCall extractAjaxFromHtml(String html, String pwd) {
|
||||||
|
if (html == null || html.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher ajax = P_AJAX_PATH.matcher(html);
|
||||||
|
if (!ajax.find()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String ajaxPath = ajax.group(1);
|
||||||
|
Map<String, String> data = new LinkedHashMap<>();
|
||||||
|
data.put("action", "downprocess");
|
||||||
|
Matcher wp = P_WP_SIGN.matcher(html);
|
||||||
|
Matcher ad = P_AJAXDATA.matcher(html);
|
||||||
|
Matcher isngis = P_ISNGIS.matcher(html);
|
||||||
|
String lastIsngis = null;
|
||||||
|
while (isngis.find()) {
|
||||||
|
if (!isngis.group(1).isEmpty()) {
|
||||||
|
lastIsngis = isngis.group(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String kd = "1";
|
||||||
|
Matcher kdns = P_KDNS.matcher(html);
|
||||||
|
if (kdns.find()) {
|
||||||
|
kd = kdns.group(1);
|
||||||
|
}
|
||||||
|
if (wp.find()) {
|
||||||
|
data.put("sign", wp.group(1));
|
||||||
|
if (ad.find()) {
|
||||||
|
data.put("websignkey", ad.group(1));
|
||||||
|
data.put("signs", ad.group(1));
|
||||||
|
}
|
||||||
|
data.put("websign", "");
|
||||||
|
Matcher ws = P_WEBSIGN.matcher(html);
|
||||||
|
if (ws.find()) {
|
||||||
|
data.put("websign", ws.group(1));
|
||||||
|
}
|
||||||
|
data.put("kd", kd);
|
||||||
|
data.put("ves", "1");
|
||||||
|
if (pwd != null && !pwd.isEmpty()) {
|
||||||
|
data.put("p", pwd);
|
||||||
|
}
|
||||||
|
} else if (lastIsngis != null) {
|
||||||
|
data.put("sign", lastIsngis);
|
||||||
|
data.put("kd", kd);
|
||||||
|
if (pwd != null && !pwd.isEmpty()) {
|
||||||
|
data.put("p", pwd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<String> signs = new ArrayList<>();
|
||||||
|
Matcher sm = P_SIGN.matcher(html);
|
||||||
|
while (sm.find()) {
|
||||||
|
signs.add(sm.group(1));
|
||||||
|
}
|
||||||
|
if (signs.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
data.put("sign", signs.size() > 1 ? signs.get(1) : signs.get(0));
|
||||||
|
if (pwd != null && !pwd.isEmpty()) {
|
||||||
|
data.put("p", pwd);
|
||||||
|
}
|
||||||
|
data.put("kd", kd);
|
||||||
|
Matcher ad2 = P_AJAXDATA.matcher(html);
|
||||||
|
if (ad2.find()) {
|
||||||
|
data.put("websignkey", ad2.group(1));
|
||||||
|
data.put("signs", ad2.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AjaxCall("/" + ajaxPath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AjaxCall extractFolderAjax(String html, String pwd) {
|
||||||
|
if (html == null || html.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher block = P_FILEMORE.matcher(html);
|
||||||
|
if (!block.find()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, String> data = new LinkedHashMap<>();
|
||||||
|
Matcher kv = P_DATA_KV.matcher(block.group(2));
|
||||||
|
while (kv.find()) {
|
||||||
|
String key = kv.group(1);
|
||||||
|
String raw = kv.group(2);
|
||||||
|
if ("pwd".equals(key)) {
|
||||||
|
data.put(key, pwd == null ? "" : pwd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ("pg".equals(key) || "pgs".equals(key)) {
|
||||||
|
data.put("pg", "1");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data.put(key, resolveJsValue(html, raw));
|
||||||
|
}
|
||||||
|
if (!data.containsKey("fid") || !data.containsKey("t") || !data.containsKey("k")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (pwd != null && !pwd.isEmpty()) {
|
||||||
|
data.put("pwd", pwd);
|
||||||
|
}
|
||||||
|
return new AjaxCall(block.group(1), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveJsValue(String html, String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (raw.length() >= 2 && raw.charAt(0) == '\'' && raw.charAt(raw.length() - 1) == '\'') {
|
||||||
|
return raw.substring(1, raw.length() - 1);
|
||||||
|
}
|
||||||
|
if (raw.matches("\\d+")) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
Matcher m = Pattern.compile("var\\s+" + Pattern.quote(raw) + "\\s*=\\s*'([^']*)'").matcher(html);
|
||||||
|
if (m.find()) {
|
||||||
|
return m.group(1);
|
||||||
|
}
|
||||||
|
if ("pgs".equals(raw)) {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseFormString(MultiMap map, String form) {
|
||||||
|
if (form == null || form.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String part : form.split("&")) {
|
||||||
|
int eq = part.indexOf('=');
|
||||||
|
if (eq > 0) {
|
||||||
|
map.add(part.substring(0, eq), part.substring(eq + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void getDownURL(String key, Map<String, ?> obj) {
|
private void getDownURL(String key, Map<String, ?> obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
fail("需要访问密码");
|
fail("需要访问密码");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<?, ?> signMap = (Map<?, ?>)obj.get("data");
|
Object dataObj = obj.get("data");
|
||||||
|
if (dataObj == null) {
|
||||||
|
fail("需要访问密码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
String url0 = String.valueOf(obj.get("url"));
|
String url0 = String.valueOf(obj.get("url"));
|
||||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
if (dataObj instanceof CharSequence) {
|
||||||
|
parseFormString(map, dataObj.toString());
|
||||||
|
} else if (dataObj instanceof Map<?, ?> signMap) {
|
||||||
signMap.forEach((k, v) -> {
|
signMap.forEach((k, v) -> {
|
||||||
map.add((String) k, v.toString());
|
if (k != null) {
|
||||||
|
map.add(k.toString(), v == null ? "" : v.toString());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
fail("需要访问密码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
MultiMap headers = HeaderUtils.parseHeaders("""
|
MultiMap headers = HeaderUtils.parseHeaders("""
|
||||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
||||||
Accept-Encoding: gzip, deflate, br
|
Accept-Encoding: identity
|
||||||
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||||
Cache-Control: no-cache
|
Cache-Control: no-cache
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
@@ -347,57 +695,119 @@ public class LzTool extends PanBase {
|
|||||||
|
|
||||||
headers.set("referer", key);
|
headers.set("referer", key);
|
||||||
// action=downprocess&signs=%3Fctdf&websignkey=I5gl&sign=BWMGOF1sBTRWXwI9BjZdYVA7BDhfNAIyUG9UawJtUGMIPlAhACkCa1UyUTAAYFxvUj5XY1E7UGFXaFVq&websign=&kd=1&ves=1
|
// action=downprocess&signs=%3Fctdf&websignkey=I5gl&sign=BWMGOF1sBTRWXwI9BjZdYVA7BDhfNAIyUG9UawJtUGMIPlAhACkCa1UyUTAAYFxvUj5XY1E7UGFXaFVq&websign=&kd=1&ves=1
|
||||||
String url = SHARE_URL_PREFIX + url0;
|
String url = joinUrl(SHARE_URL_PREFIX, url0);
|
||||||
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
try {
|
try {
|
||||||
JsonObject urlJson = asJson(res2);
|
JsonObject urlJson = asJson(res2);
|
||||||
String name = urlJson.getString("inf");
|
Object infVal = urlJson.getValue("inf");
|
||||||
if (urlJson.getInteger("zt") != 1) {
|
String name = infVal instanceof CharSequence ? infVal.toString() : null;
|
||||||
fail(name);
|
Integer zt = urlJson.getInteger("zt");
|
||||||
|
if (zt == null || zt != 1) {
|
||||||
|
fail(name != null ? name : String.valueOf(infVal));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 文件名
|
// 文件名
|
||||||
if (urlJson.containsKey("inf") && urlJson.getMap().get("inf") instanceof CharSequence) {
|
if (name != null) {
|
||||||
((FileInfo)shareLinkInfo.getOtherParam().get("fileInfo")).setFileName(name);
|
Object fi = shareLinkInfo.getOtherParam().get("fileInfo");
|
||||||
|
if (fi instanceof FileInfo fileInfo) {
|
||||||
|
fileInfo.setFileName(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
||||||
headers.remove("Referer");
|
followFileUrl(downUrl, headers);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e, "解析异常");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载域已不再走 arg1/acw_sc__v2 挑战页;带 down_ip=1 时通常直接 302。
|
||||||
|
* 未跳转则走页面 down_r → POST /ajax.php 二次验证。
|
||||||
|
*/
|
||||||
|
private void followFileUrl(String downUrl, MultiMap headers) {
|
||||||
|
String origin = originOf(downUrl, "https://developer2.lanrar.com");
|
||||||
|
putSessionCookie("down_ip", "1", downUrl, ".lanrar.com");
|
||||||
|
headers.set("referer", origin);
|
||||||
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
||||||
.onSuccess(res3 -> {
|
.onSuccess(res3 -> {
|
||||||
try {
|
try {
|
||||||
String location = res3.headers().get("Location");
|
String location = res3.headers().get("Location");
|
||||||
if (location == null) {
|
if (location != null) {
|
||||||
|
setDateAndComplete(location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
String text = asText(res3);
|
String text = asText(res3);
|
||||||
if (isShareCancelledPage(text)) {
|
if (isShareCancelledPage(text)) {
|
||||||
fail(downUrl + " -> 分享已失效或文件已取消分享");
|
fail(downUrl + " -> 分享已失效或文件已取消分享");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 使用cookie 再请求一次
|
if (text.contains("down_r") && text.contains("ajax.php")) {
|
||||||
headers.add("Referer", downUrl);
|
verifyDownloadPage(origin, downUrl, text, headers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasAcwArg1(text)) {
|
||||||
|
retryFileUrlWithAcw(downUrl, text, headers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("蓝奏云直链响应处理异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(handleFail(downUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDownloadPage(String origin, String downUrl, String html, MultiMap headers) {
|
||||||
|
Matcher fileM = Pattern.compile("'file'\\s*:\\s*'([^']+)'").matcher(html);
|
||||||
|
Matcher signM = Pattern.compile("'sign'\\s*:\\s*'([^']+)'").matcher(html);
|
||||||
|
if (!fileM.find() || !signM.find()) {
|
||||||
|
fail(downUrl + " -> 二次验证参数缺失");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String file = fileM.group(1);
|
||||||
|
String sign = signM.group(1);
|
||||||
|
WebClientVertxInit.get().setTimer(2000, id -> postVerifyAjax(origin, downUrl, file, sign, headers, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postVerifyAjax(String origin, String downUrl, String file, String sign, MultiMap headers, boolean filePath) {
|
||||||
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
map.add("file", file);
|
||||||
|
map.add("el", "2");
|
||||||
|
map.add("sign", sign);
|
||||||
|
String ajaxUrl = origin + (filePath ? "/file/ajax.php" : "/ajax.php");
|
||||||
|
headers.set("referer", origin);
|
||||||
|
webClientSession.postAbs(ajaxUrl).putHeaders(headers).sendForm(map).onSuccess(res -> {
|
||||||
|
try {
|
||||||
|
JsonObject json = asJson(res);
|
||||||
|
Integer zt = json.getInteger("zt");
|
||||||
|
String u = json.getString("url");
|
||||||
|
if (zt != null && zt == 1 && u != null && u.startsWith("http") && !u.contains("SignError")) {
|
||||||
|
setDateAndComplete(u);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!filePath) {
|
||||||
|
postVerifyAjax(origin, downUrl, file, sign, headers, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail(downUrl + " -> 二次验证失败: " + (u != null ? u : String.valueOf(json.getValue("inf"))));
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("二次验证解析异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(ajaxUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retryFileUrlWithAcw(String downUrl, String text, MultiMap headers) {
|
||||||
String arg1 = extractAcwArg1(text);
|
String arg1 = extractAcwArg1(text);
|
||||||
if (arg1 == null) {
|
if (arg1 == null) {
|
||||||
fail(downUrl + " -> 蓝奏云反爬 arg1 Cookie 解析失败,可能分享已失效");
|
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String acw_sc__v2 = AcwScV2Generator.acwScV2Simple(arg1);
|
String acw_sc__v2 = AcwScV2Generator.acwScV2Simple(arg1);
|
||||||
// 从 downUrl 中动态提取域名
|
putSessionCookie("acw_sc__v2", acw_sc__v2, downUrl, ".lanrar.com");
|
||||||
String downDomain = ".lanrar.com";
|
headers.set("referer", originOf(downUrl, "https://developer2.lanrar.com"));
|
||||||
try {
|
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
||||||
java.net.URL du = new java.net.URL(downUrl);
|
|
||||||
String h = du.getHost();
|
|
||||||
int dot = h.indexOf('.');
|
|
||||||
if (dot >= 0) downDomain = h.substring(dot);
|
|
||||||
} catch (MalformedURLException ignored) {}
|
|
||||||
// 创建一个 Cookie 并放入 CookieStore
|
|
||||||
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
|
||||||
nettyCookie.setDomain(downDomain);
|
|
||||||
nettyCookie.setPath("/");
|
|
||||||
nettyCookie.setSecure(false);
|
|
||||||
nettyCookie.setHttpOnly(false);
|
|
||||||
WebClientSession webClientSession2 = WebClientSession.create(clientNoRedirects);
|
|
||||||
webClientSession2.cookieStore().put(nettyCookie);
|
|
||||||
webClientSession2.getAbs(downUrl).putHeaders(headers).send()
|
|
||||||
.onSuccess(res4 -> {
|
.onSuccess(res4 -> {
|
||||||
try {
|
try {
|
||||||
String location0 = res4.headers().get("Location");
|
String location0 = res4.headers().get("Location");
|
||||||
@@ -410,18 +820,49 @@ public class LzTool extends PanBase {
|
|||||||
fail("蓝奏云直链二次响应处理异常: {}", e.getMessage());
|
fail("蓝奏云直链二次响应处理异常: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(downUrl));
|
}).onFailure(handleFail(downUrl));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setDateAndComplete(location);
|
|
||||||
} catch (Exception e) {
|
private void putSessionCookie(String name, String value, String url, String fallbackDomain) {
|
||||||
fail("蓝奏云直链响应处理异常: {}", e.getMessage());
|
String domain = fallbackDomain;
|
||||||
|
try {
|
||||||
|
java.net.URL urlObj = new java.net.URL(url);
|
||||||
|
String host = urlObj.getHost();
|
||||||
|
int firstDot = host.indexOf('.');
|
||||||
|
if (firstDot >= 0) {
|
||||||
|
domain = host.substring(firstDot);
|
||||||
}
|
}
|
||||||
})
|
} catch (MalformedURLException ignored) {}
|
||||||
.onFailure(handleFail(downUrl));
|
DefaultCookie nettyCookie = new DefaultCookie(name, value);
|
||||||
} catch (Exception e) {
|
nettyCookie.setDomain(domain);
|
||||||
fail("解析异常");
|
nettyCookie.setPath("/");
|
||||||
|
nettyCookie.setSecure(false);
|
||||||
|
nettyCookie.setHttpOnly(false);
|
||||||
|
webClientSession.cookieStore().put(nettyCookie);
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(url));
|
|
||||||
|
private static String originOf(String url, String fallback) {
|
||||||
|
try {
|
||||||
|
java.net.URL u = new java.net.URL(url);
|
||||||
|
return u.getProtocol() + "://" + u.getHost();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinUrl(String base, String path) {
|
||||||
|
if (path == null || path.isBlank()) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
if (base.endsWith("/") && path.startsWith("/")) {
|
||||||
|
return base.substring(0, base.length() - 1) + path;
|
||||||
|
}
|
||||||
|
if (!base.endsWith("/") && !path.startsWith("/")) {
|
||||||
|
return base + "/" + path;
|
||||||
|
}
|
||||||
|
return base + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDateAndComplete(String location0) {
|
private void setDateAndComplete(String location0) {
|
||||||
@@ -460,7 +901,7 @@ public class LzTool extends PanBase {
|
|||||||
String html = asText(res);
|
String html = asText(res);
|
||||||
// 检查是否需要 cookie 验证
|
// 检查是否需要 cookie 验证
|
||||||
if (hasAcwArg1(html)) {
|
if (hasAcwArg1(html)) {
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
webClientSession = WebClientSession.create(lzClient);
|
||||||
if (!setCookie(html, sUrl)) {
|
if (!setCookie(html, sUrl)) {
|
||||||
promise.tryFail(baseMsg() + "蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
promise.tryFail(baseMsg() + "蓝奏云反爬 arg1 Cookie 解析失败,页面内容异常");
|
||||||
return;
|
return;
|
||||||
@@ -492,21 +933,26 @@ public class LzTool extends PanBase {
|
|||||||
promise.tryFail(baseMsg() + "分享已失效或文件已取消分享");
|
promise.tryFail(baseMsg() + "分享已失效或文件已取消分享");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检测是否为文件分享链接 (不含 /s/、/b/ 路径段且不含 b 开头的路径段)
|
// 检测是否为文件分享链接 (不含 /s/、/b/ 路径段且不含 b0 开头的路径段)
|
||||||
if (!sUrl.matches(".*/(s|b)/[^/]+.*") && !sUrl.matches(".*/b[^/]+.*")) {
|
if (!sUrl.matches(".*/(s|b)/[^/]+.*") && !sUrl.matches(".*/b0[^/]+.*")) {
|
||||||
promise.tryFail(baseMsg() + "该链接为蓝奏云文件分享,请使用文件解析接口");
|
promise.tryFail(baseMsg() + "该链接为蓝奏云文件分享,请使用文件解析接口");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
AjaxCall call = extractFolderAjax(html, pwd);
|
||||||
|
if (call == null) {
|
||||||
String jsText = getJsByPwd(pwd, html, "var urls =window.location.href");
|
String jsText = getJsByPwd(pwd, html, "var urls =window.location.href");
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "file");
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "file", pwd);
|
||||||
Map<String, Object> data = CastUtil.cast(scriptObjectMirror.get("data"));
|
Map<String, Object> data = CastUtil.cast(scriptObjectMirror.get("data"));
|
||||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
Map<String, String> form = new LinkedHashMap<>();
|
||||||
data.forEach((k, v) -> map.set(k, v.toString()));
|
data.forEach((k, v) -> form.put(k, String.valueOf(v)));
|
||||||
log.debug("解析参数: {}", map);
|
call = new AjaxCall("/filemoreajax.php?file=" + form.get("fid"), form);
|
||||||
|
}
|
||||||
|
log.debug("解析参数: {}", call.form());
|
||||||
MultiMap headers = getHeaders(sUrl);
|
MultiMap headers = getHeaders(sUrl);
|
||||||
|
MultiMap map = call.toForm();
|
||||||
|
|
||||||
String url = SHARE_URL_PREFIX + "filemoreajax.php?file=" + data.get("fid");
|
String url = joinUrl(SHARE_URL_PREFIX, call.path());
|
||||||
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
try {
|
try {
|
||||||
String resBody = asText(res2);
|
String resBody = asText(res2);
|
||||||
@@ -542,7 +988,10 @@ public class LzTool extends PanBase {
|
|||||||
promise.tryFail(baseMsg() + "蓝奏云文件列表响应为空");
|
promise.tryFail(baseMsg() + "蓝奏云文件列表响应为空");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonObject fileListJson = new JsonObject(responseBody);
|
JsonObject fileListJson = parseLzJson(responseBody);
|
||||||
|
if (fileListJson == null) {
|
||||||
|
fileListJson = new JsonObject(responseBody);
|
||||||
|
}
|
||||||
if (fileListJson.getInteger("zt") != 1) {
|
if (fileListJson.getInteger("zt") != 1) {
|
||||||
promise.tryFail(baseMsg() + fileListJson.getString("info"));
|
promise.tryFail(baseMsg() + fileListJson.getString("info"));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -101,59 +101,384 @@ public interface JsContent {
|
|||||||
""";
|
""";
|
||||||
String lz = """
|
String lz = """
|
||||||
/**
|
/**
|
||||||
* 蓝奏云解析器js签名获取工具
|
* 蓝奏云解析器 JS 沙箱:伪装 jQuery / document / window。
|
||||||
|
* 新版页面会用 document.cookie、location.reload、querySelector、
|
||||||
|
* $('#pwd').val()、.html()、.css() 等,这里做成可链式的最小实现。
|
||||||
|
* kdns.js 在浏览器里是 `var killdns = true`,需一并注入,否则 kd 会被改成 0。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var signObj;
|
var signObj;
|
||||||
|
var __lzPwd = '';
|
||||||
|
var killdns = true;
|
||||||
|
|
||||||
|
function __lzSetPwd(p) {
|
||||||
|
__lzPwd = p == null ? '' : String(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __lzEl(id) {
|
||||||
|
var nid = String(id == null ? '' : id).replace(/^[#.]/, '');
|
||||||
|
var el = {
|
||||||
|
id: nid,
|
||||||
|
_value: nid === 'pwd' ? __lzPwd : '',
|
||||||
|
checked: false,
|
||||||
|
disabled: false,
|
||||||
|
innerHTML: '',
|
||||||
|
innerText: '',
|
||||||
|
textContent: '',
|
||||||
|
className: '',
|
||||||
|
style: { display: '', visibility: '', width: '', height: '' },
|
||||||
|
classList: {
|
||||||
|
add: function () {},
|
||||||
|
remove: function () {},
|
||||||
|
contains: function () { return false; },
|
||||||
|
toggle: function () {}
|
||||||
|
},
|
||||||
|
setAttribute: function () {},
|
||||||
|
getAttribute: function () { return null; },
|
||||||
|
removeAttribute: function () {},
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEventListener: function () {},
|
||||||
|
appendChild: function (n) { return n; },
|
||||||
|
removeChild: function (n) { return n; },
|
||||||
|
insertBefore: function (n) { return n; },
|
||||||
|
click: function () {},
|
||||||
|
focus: function () {},
|
||||||
|
blur: function () {},
|
||||||
|
submit: function () {},
|
||||||
|
select: function () {},
|
||||||
|
reset: function () {}
|
||||||
|
};
|
||||||
|
el.parentNode = el;
|
||||||
|
el.parentElement = el;
|
||||||
|
el.children = [];
|
||||||
|
el.childNodes = [];
|
||||||
|
el.firstChild = null;
|
||||||
|
el.lastChild = null;
|
||||||
|
try {
|
||||||
|
Object.defineProperty(el, 'value', {
|
||||||
|
get: function () { return nid === 'pwd' ? __lzPwd : el._value; },
|
||||||
|
set: function (v) {
|
||||||
|
el._value = v;
|
||||||
|
if (nid === 'pwd') {
|
||||||
|
__lzPwd = v == null ? '' : String(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
el.value = el._value;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __lzJq(sel) {
|
||||||
|
var id = '';
|
||||||
|
if (typeof sel === 'string') {
|
||||||
|
id = sel.replace(/^[#.]/, '');
|
||||||
|
} else if (sel && sel.id) {
|
||||||
|
id = String(sel.id);
|
||||||
|
}
|
||||||
|
var el = (sel && sel.style && sel.addEventListener) ? sel : __lzEl(id);
|
||||||
|
var api = {
|
||||||
|
0: el,
|
||||||
|
length: 1,
|
||||||
|
selector: sel,
|
||||||
|
ready: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(jQuery); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
on: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
off: function () { return api; },
|
||||||
|
bind: function (t, fn) { return api.on(t, fn); },
|
||||||
|
unbind: function () { return api; },
|
||||||
|
click: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
focus: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
blur: function () { return api; },
|
||||||
|
keyup: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
keydown: function (fn) { return api.keyup(fn); },
|
||||||
|
keypress: function (fn) { return api.keyup(fn); },
|
||||||
|
submit: function (fn) { return api.click(fn); },
|
||||||
|
change: function (fn) { return api.click(fn); },
|
||||||
|
hover: function () { return api; },
|
||||||
|
val: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
if (typeof el.value !== 'undefined') {
|
||||||
|
return el.value;
|
||||||
|
}
|
||||||
|
return (id === 'pwd') ? __lzPwd : '';
|
||||||
|
}
|
||||||
|
el.value = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
html: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return el.innerHTML;
|
||||||
|
}
|
||||||
|
el.innerHTML = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
text: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return el.innerText;
|
||||||
|
}
|
||||||
|
el.innerText = v;
|
||||||
|
el.textContent = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
attr: function (k, v) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
prop: function (k, v) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
css: function () { return api; },
|
||||||
|
addClass: function () { return api; },
|
||||||
|
removeClass: function () { return api; },
|
||||||
|
toggleClass: function () { return api; },
|
||||||
|
hasClass: function () { return false; },
|
||||||
|
show: function () {
|
||||||
|
el.style.display = '';
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
hide: function () {
|
||||||
|
el.style.display = 'none';
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
fadeIn: function () { return api; },
|
||||||
|
fadeOut: function () { return api; },
|
||||||
|
animate: function () { return api; },
|
||||||
|
find: function () { return api; },
|
||||||
|
parent: function () { return api; },
|
||||||
|
children: function () { return api; },
|
||||||
|
eq: function () { return api; },
|
||||||
|
first: function () { return api; },
|
||||||
|
last: function () { return api; },
|
||||||
|
each: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn.call(el, 0, el); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
append: function () { return api; },
|
||||||
|
prepend: function () { return api; },
|
||||||
|
remove: function () { return api; },
|
||||||
|
empty: function () { return api; },
|
||||||
|
ajax: function (obj) {
|
||||||
|
signObj = obj;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
get: function () { return el; }
|
||||||
|
};
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
var $, jQuery;
|
var $, jQuery;
|
||||||
|
$ = jQuery = function (sel) {
|
||||||
$ = jQuery = function () {
|
if (typeof sel === 'function') {
|
||||||
return new jQuery.fn.init();
|
try { sel(jQuery); } catch (e) {}
|
||||||
|
return __lzJq(document);
|
||||||
}
|
}
|
||||||
|
return __lzJq(sel);
|
||||||
|
};
|
||||||
|
|
||||||
jQuery.fn = jQuery.prototype = {
|
jQuery.fn = jQuery.prototype = {
|
||||||
init: function () {
|
init: function (sel) {
|
||||||
return {
|
return __lzJq(sel);
|
||||||
focus: function (a) {
|
|
||||||
|
|
||||||
},
|
|
||||||
keyup: function(a) {
|
|
||||||
|
|
||||||
},
|
|
||||||
ajax: function (obj) {
|
|
||||||
signObj = obj
|
|
||||||
},
|
|
||||||
val: function(a) {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery.fn.init.prototype = jQuery.fn;
|
jQuery.fn.init.prototype = jQuery.fn;
|
||||||
|
$.fn = jQuery.fn;
|
||||||
|
|
||||||
$.ajax = function (obj) {
|
$.ajax = function (obj) {
|
||||||
signObj = obj
|
signObj = obj;
|
||||||
|
return {
|
||||||
|
done: function () { return this; },
|
||||||
|
fail: function () { return this; },
|
||||||
|
always: function () { return this; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$.get = function () {};
|
||||||
|
$.post = function () {};
|
||||||
|
$.extend = function () {
|
||||||
|
var t = arguments[0] || {};
|
||||||
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
|
var s = arguments[i];
|
||||||
|
if (s) {
|
||||||
|
for (var k in s) {
|
||||||
|
if (s.hasOwnProperty(k)) {
|
||||||
|
t[k] = s[k];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
$.each = function (obj, fn) {
|
||||||
|
if (!obj || typeof fn !== 'function') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (typeof obj.length === 'number') {
|
||||||
|
for (var i = 0; i < obj.length; i++) {
|
||||||
|
fn.call(obj[i], i, obj[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var k in obj) {
|
||||||
|
if (obj.hasOwnProperty(k)) {
|
||||||
|
fn.call(obj[k], k, obj[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
$.isFunction = function (f) { return typeof f === 'function'; };
|
||||||
|
$.isArray = function (a) {
|
||||||
|
return Object.prototype.toString.call(a) === '[object Array]';
|
||||||
|
};
|
||||||
|
$.trim = function (s) {
|
||||||
|
return s == null ? '' : String(s).replace(/^\\s+|\\s+$/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
var __lzLocation = {
|
||||||
|
href: '',
|
||||||
|
search: '',
|
||||||
|
pathname: '/',
|
||||||
|
hash: '',
|
||||||
|
host: '',
|
||||||
|
hostname: '',
|
||||||
|
protocol: 'https:',
|
||||||
|
port: '',
|
||||||
|
origin: '',
|
||||||
|
assign: function () {},
|
||||||
|
replace: function () {},
|
||||||
|
reload: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
var document = {
|
var document = {
|
||||||
getElementById: function (v) {
|
cookie: '',
|
||||||
return {
|
title: '',
|
||||||
value: 'v',
|
domain: '',
|
||||||
style: {
|
referrer: '',
|
||||||
display: ''
|
readyState: 'complete',
|
||||||
|
hidden: false,
|
||||||
|
visibilityState: 'visible',
|
||||||
|
documentElement: null,
|
||||||
|
body: null,
|
||||||
|
head: null,
|
||||||
|
location: __lzLocation,
|
||||||
|
getElementById: function (id) { return __lzEl(id); },
|
||||||
|
getElementsByClassName: function () { return []; },
|
||||||
|
getElementsByTagName: function (t) {
|
||||||
|
return t === 'script' ? [] : [__lzEl(t)];
|
||||||
},
|
},
|
||||||
addEventListener: function() {}
|
getElementsByName: function () { return []; },
|
||||||
|
querySelector: function (s) { return __lzEl(s); },
|
||||||
|
querySelectorAll: function (s) { return [__lzEl(s)]; },
|
||||||
|
createElement: function (t) { return __lzEl(t); },
|
||||||
|
createTextNode: function (t) { return { nodeValue: t, data: t }; },
|
||||||
|
createDocumentFragment: function () { return __lzEl('fragment'); },
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
removeEventListener: function () {},
|
||||||
|
write: function () {},
|
||||||
|
writeln: function () {},
|
||||||
|
open: function () {},
|
||||||
|
close: function () {}
|
||||||
|
};
|
||||||
|
document.documentElement = __lzEl('html');
|
||||||
|
document.body = __lzEl('body');
|
||||||
|
document.head = __lzEl('head');
|
||||||
|
|
||||||
var window = {location: {}}
|
var navigator = {
|
||||||
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
|
||||||
|
platform: 'Win32',
|
||||||
|
language: 'zh-CN',
|
||||||
|
cookieEnabled: true,
|
||||||
|
onLine: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var location = __lzLocation;
|
||||||
|
var console = {
|
||||||
|
log: function () {},
|
||||||
|
warn: function () {},
|
||||||
|
error: function () {},
|
||||||
|
info: function () {},
|
||||||
|
debug: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setTimeout(fn, delay) {
|
||||||
|
if (typeof fn === 'function' && (!delay || delay <= 0)) {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
function setInterval() { return 0; }
|
||||||
|
function clearTimeout() {}
|
||||||
|
function clearInterval() {}
|
||||||
|
|
||||||
|
var window = {
|
||||||
|
location: __lzLocation,
|
||||||
|
document: document,
|
||||||
|
navigator: navigator,
|
||||||
|
console: console,
|
||||||
|
innerWidth: 1920,
|
||||||
|
innerHeight: 1080,
|
||||||
|
setTimeout: setTimeout,
|
||||||
|
setInterval: setInterval,
|
||||||
|
clearTimeout: clearTimeout,
|
||||||
|
clearInterval: clearInterval,
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEventListener: function () {},
|
||||||
|
atob: function (s) { return s; },
|
||||||
|
btoa: function (s) { return s; }
|
||||||
|
};
|
||||||
|
window.window = window;
|
||||||
|
window.top = window;
|
||||||
|
window.self = window;
|
||||||
|
window.parent = window;
|
||||||
|
window.jQuery = jQuery;
|
||||||
|
window.$ = $;
|
||||||
|
|
||||||
|
var top = window;
|
||||||
|
var self = window;
|
||||||
|
var parent = window;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
String kwSignString = """
|
String kwSignString = """
|
||||||
|
|||||||
@@ -50,21 +50,47 @@ public class JsExecUtils {
|
|||||||
*/
|
*/
|
||||||
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
||||||
NoSuchMethodException {
|
NoSuchMethodException {
|
||||||
ScriptEngine engine = ENGINE_MANAGER.getEngineByName("JavaScript"); // 得到脚本引擎
|
return executeDynamicJs(jsText, funName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pwd 分享密码,写入伪 DOM(#pwd / getElementById('pwd')),供新版页面取值
|
||||||
|
*/
|
||||||
|
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName, String pwd) throws ScriptException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
ScriptEngine engine = ENGINE_MANAGER.getEngineByName("JavaScript");
|
||||||
try {
|
try {
|
||||||
engine.eval(JsContent.lz + "\n" + jsText);
|
engine.eval(JsContent.lz);
|
||||||
Invocable inv = (Invocable) engine;
|
Invocable inv = (Invocable) engine;
|
||||||
//调用js中的函数
|
if (pwd != null) {
|
||||||
|
inv.invokeFunction("__lzSetPwd", pwd);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
engine.eval(jsText);
|
||||||
if (StringUtils.isNotEmpty(funName)) {
|
if (StringUtils.isNotEmpty(funName)) {
|
||||||
inv.invokeFunction(funName);
|
inv.invokeFunction(funName);
|
||||||
}
|
}
|
||||||
return (ScriptObjectMirror) engine.get("signObj");
|
} catch (ScriptException | NoSuchMethodException | RuntimeException e) {
|
||||||
|
ScriptObjectMirror captured = asSignObj(engine.get("signObj"));
|
||||||
|
if (captured != null) {
|
||||||
|
return captured;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return asSignObj(engine.get("signObj"));
|
||||||
} finally {
|
} finally {
|
||||||
// 清理引擎持有的引用,帮助 GC 回收
|
|
||||||
clearEngineBindings(engine);
|
clearEngineBindings(engine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ScriptObjectMirror asSignObj(Object sign) {
|
||||||
|
if (sign instanceof ScriptObjectMirror mirror
|
||||||
|
&& (mirror.get("url") != null || mirror.get("data") != null)) {
|
||||||
|
return mirror;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用执行js文件(使用缓存的 ScriptEngineManager 创建新引擎实例)
|
* 调用执行js文件(使用缓存的 ScriptEngineManager 创建新引擎实例)
|
||||||
|
|||||||
@@ -1,46 +1,379 @@
|
|||||||
/**
|
/**
|
||||||
* 蓝奏云解析器js签名获取工具
|
* 蓝奏云解析器 JS 沙箱:伪装 jQuery / document / window。
|
||||||
|
* 新版页面会用 document.cookie、location.reload、querySelector、
|
||||||
|
* $('#pwd').val()、.html()、.css() 等,这里做成可链式的最小实现。
|
||||||
|
* kdns.js 在浏览器里是 `var killdns = true`,需一并注入,否则 kd 会被改成 0。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var signObj;
|
var signObj;
|
||||||
|
var __lzPwd = '';
|
||||||
|
var killdns = true;
|
||||||
|
|
||||||
|
function __lzSetPwd(p) {
|
||||||
|
__lzPwd = p == null ? '' : String(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __lzEl(id) {
|
||||||
|
var nid = String(id == null ? '' : id).replace(/^[#.]/, '');
|
||||||
|
var el = {
|
||||||
|
id: nid,
|
||||||
|
_value: nid === 'pwd' ? __lzPwd : '',
|
||||||
|
checked: false,
|
||||||
|
disabled: false,
|
||||||
|
innerHTML: '',
|
||||||
|
innerText: '',
|
||||||
|
textContent: '',
|
||||||
|
className: '',
|
||||||
|
style: { display: '', visibility: '', width: '', height: '' },
|
||||||
|
classList: {
|
||||||
|
add: function () {},
|
||||||
|
remove: function () {},
|
||||||
|
contains: function () { return false; },
|
||||||
|
toggle: function () {}
|
||||||
|
},
|
||||||
|
setAttribute: function () {},
|
||||||
|
getAttribute: function () { return null; },
|
||||||
|
removeAttribute: function () {},
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEventListener: function () {},
|
||||||
|
appendChild: function (n) { return n; },
|
||||||
|
removeChild: function (n) { return n; },
|
||||||
|
insertBefore: function (n) { return n; },
|
||||||
|
click: function () {},
|
||||||
|
focus: function () {},
|
||||||
|
blur: function () {},
|
||||||
|
submit: function () {},
|
||||||
|
select: function () {},
|
||||||
|
reset: function () {}
|
||||||
|
};
|
||||||
|
el.parentNode = el;
|
||||||
|
el.parentElement = el;
|
||||||
|
el.children = [];
|
||||||
|
el.childNodes = [];
|
||||||
|
el.firstChild = null;
|
||||||
|
el.lastChild = null;
|
||||||
|
try {
|
||||||
|
Object.defineProperty(el, 'value', {
|
||||||
|
get: function () { return nid === 'pwd' ? __lzPwd : el._value; },
|
||||||
|
set: function (v) {
|
||||||
|
el._value = v;
|
||||||
|
if (nid === 'pwd') {
|
||||||
|
__lzPwd = v == null ? '' : String(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
el.value = el._value;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __lzJq(sel) {
|
||||||
|
var id = '';
|
||||||
|
if (typeof sel === 'string') {
|
||||||
|
id = sel.replace(/^[#.]/, '');
|
||||||
|
} else if (sel && sel.id) {
|
||||||
|
id = String(sel.id);
|
||||||
|
}
|
||||||
|
var el = (sel && sel.style && sel.addEventListener) ? sel : __lzEl(id);
|
||||||
|
var api = {
|
||||||
|
0: el,
|
||||||
|
length: 1,
|
||||||
|
selector: sel,
|
||||||
|
ready: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(jQuery); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
on: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
off: function () { return api; },
|
||||||
|
bind: function (t, fn) { return api.on(t, fn); },
|
||||||
|
unbind: function () { return api; },
|
||||||
|
click: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
focus: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
blur: function () { return api; },
|
||||||
|
keyup: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
keydown: function (fn) { return api.keyup(fn); },
|
||||||
|
keypress: function (fn) { return api.keyup(fn); },
|
||||||
|
submit: function (fn) { return api.click(fn); },
|
||||||
|
change: function (fn) { return api.click(fn); },
|
||||||
|
hover: function () { return api; },
|
||||||
|
val: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
if (typeof el.value !== 'undefined') {
|
||||||
|
return el.value;
|
||||||
|
}
|
||||||
|
return (id === 'pwd') ? __lzPwd : '';
|
||||||
|
}
|
||||||
|
el.value = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
html: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return el.innerHTML;
|
||||||
|
}
|
||||||
|
el.innerHTML = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
text: function (v) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return el.innerText;
|
||||||
|
}
|
||||||
|
el.innerText = v;
|
||||||
|
el.textContent = v;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
attr: function (k, v) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
prop: function (k, v) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
css: function () { return api; },
|
||||||
|
addClass: function () { return api; },
|
||||||
|
removeClass: function () { return api; },
|
||||||
|
toggleClass: function () { return api; },
|
||||||
|
hasClass: function () { return false; },
|
||||||
|
show: function () {
|
||||||
|
el.style.display = '';
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
hide: function () {
|
||||||
|
el.style.display = 'none';
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
fadeIn: function () { return api; },
|
||||||
|
fadeOut: function () { return api; },
|
||||||
|
animate: function () { return api; },
|
||||||
|
find: function () { return api; },
|
||||||
|
parent: function () { return api; },
|
||||||
|
children: function () { return api; },
|
||||||
|
eq: function () { return api; },
|
||||||
|
first: function () { return api; },
|
||||||
|
last: function () { return api; },
|
||||||
|
each: function (fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn.call(el, 0, el); } catch (e) {}
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
append: function () { return api; },
|
||||||
|
prepend: function () { return api; },
|
||||||
|
remove: function () { return api; },
|
||||||
|
empty: function () { return api; },
|
||||||
|
ajax: function (obj) {
|
||||||
|
signObj = obj;
|
||||||
|
return api;
|
||||||
|
},
|
||||||
|
get: function () { return el; }
|
||||||
|
};
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
var $, jQuery;
|
var $, jQuery;
|
||||||
|
$ = jQuery = function (sel) {
|
||||||
$ = jQuery = function () {
|
if (typeof sel === 'function') {
|
||||||
return new jQuery.fn.init();
|
try { sel(jQuery); } catch (e) {}
|
||||||
|
return __lzJq(document);
|
||||||
}
|
}
|
||||||
|
return __lzJq(sel);
|
||||||
|
};
|
||||||
|
|
||||||
jQuery.fn = jQuery.prototype = {
|
jQuery.fn = jQuery.prototype = {
|
||||||
init: function () {
|
init: function (sel) {
|
||||||
return {
|
return __lzJq(sel);
|
||||||
focus: function (a) {
|
|
||||||
|
|
||||||
},
|
|
||||||
keyup: function(a) {
|
|
||||||
|
|
||||||
},
|
|
||||||
ajax: function (obj) {
|
|
||||||
signObj = obj
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery.fn.init.prototype = jQuery.fn;
|
jQuery.fn.init.prototype = jQuery.fn;
|
||||||
|
$.fn = jQuery.fn;
|
||||||
|
|
||||||
|
|
||||||
// 伪装jquery.ajax函数获取关键数据
|
|
||||||
$.ajax = function (obj) {
|
$.ajax = function (obj) {
|
||||||
signObj = obj
|
signObj = obj;
|
||||||
|
return {
|
||||||
|
done: function () { return this; },
|
||||||
|
fail: function () { return this; },
|
||||||
|
always: function () { return this; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$.get = function () {};
|
||||||
|
$.post = function () {};
|
||||||
|
$.extend = function () {
|
||||||
|
var t = arguments[0] || {};
|
||||||
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
|
var s = arguments[i];
|
||||||
|
if (s) {
|
||||||
|
for (var k in s) {
|
||||||
|
if (s.hasOwnProperty(k)) {
|
||||||
|
t[k] = s[k];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
$.each = function (obj, fn) {
|
||||||
|
if (!obj || typeof fn !== 'function') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (typeof obj.length === 'number') {
|
||||||
|
for (var i = 0; i < obj.length; i++) {
|
||||||
|
fn.call(obj[i], i, obj[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var k in obj) {
|
||||||
|
if (obj.hasOwnProperty(k)) {
|
||||||
|
fn.call(obj[k], k, obj[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
$.isFunction = function (f) { return typeof f === 'function'; };
|
||||||
|
$.isArray = function (a) {
|
||||||
|
return Object.prototype.toString.call(a) === '[object Array]';
|
||||||
|
};
|
||||||
|
$.trim = function (s) {
|
||||||
|
return s == null ? '' : String(s).replace(/^\s+|\s+$/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
var __lzLocation = {
|
||||||
|
href: '',
|
||||||
|
search: '',
|
||||||
|
pathname: '/',
|
||||||
|
hash: '',
|
||||||
|
host: '',
|
||||||
|
hostname: '',
|
||||||
|
protocol: 'https:',
|
||||||
|
port: '',
|
||||||
|
origin: '',
|
||||||
|
assign: function () {},
|
||||||
|
replace: function () {},
|
||||||
|
reload: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
var document = {
|
var document = {
|
||||||
getElementById: function (v) {
|
cookie: '',
|
||||||
return {
|
title: '',
|
||||||
value: 'v'
|
domain: '',
|
||||||
|
referrer: '',
|
||||||
|
readyState: 'complete',
|
||||||
|
hidden: false,
|
||||||
|
visibilityState: 'visible',
|
||||||
|
documentElement: null,
|
||||||
|
body: null,
|
||||||
|
head: null,
|
||||||
|
location: __lzLocation,
|
||||||
|
getElementById: function (id) { return __lzEl(id); },
|
||||||
|
getElementsByClassName: function () { return []; },
|
||||||
|
getElementsByTagName: function (t) {
|
||||||
|
return t === 'script' ? [] : [__lzEl(t)];
|
||||||
|
},
|
||||||
|
getElementsByName: function () { return []; },
|
||||||
|
querySelector: function (s) { return __lzEl(s); },
|
||||||
|
querySelectorAll: function (s) { return [__lzEl(s)]; },
|
||||||
|
createElement: function (t) { return __lzEl(t); },
|
||||||
|
createTextNode: function (t) { return { nodeValue: t, data: t }; },
|
||||||
|
createDocumentFragment: function () { return __lzEl('fragment'); },
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
removeEventListener: function () {},
|
||||||
|
write: function () {},
|
||||||
|
writeln: function () {},
|
||||||
|
open: function () {},
|
||||||
|
close: function () {}
|
||||||
|
};
|
||||||
|
document.documentElement = __lzEl('html');
|
||||||
|
document.body = __lzEl('body');
|
||||||
|
document.head = __lzEl('head');
|
||||||
|
|
||||||
|
var navigator = {
|
||||||
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
|
||||||
|
platform: 'Win32',
|
||||||
|
language: 'zh-CN',
|
||||||
|
cookieEnabled: true,
|
||||||
|
onLine: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var location = __lzLocation;
|
||||||
|
var console = {
|
||||||
|
log: function () {},
|
||||||
|
warn: function () {},
|
||||||
|
error: function () {},
|
||||||
|
info: function () {},
|
||||||
|
debug: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setTimeout(fn, delay) {
|
||||||
|
if (typeof fn === 'function' && (!delay || delay <= 0)) {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
function setInterval() { return 0; }
|
||||||
|
function clearTimeout() {}
|
||||||
|
function clearInterval() {}
|
||||||
|
|
||||||
|
var window = {
|
||||||
|
location: __lzLocation,
|
||||||
|
document: document,
|
||||||
|
navigator: navigator,
|
||||||
|
console: console,
|
||||||
|
innerWidth: 1920,
|
||||||
|
innerHeight: 1080,
|
||||||
|
setTimeout: setTimeout,
|
||||||
|
setInterval: setInterval,
|
||||||
|
clearTimeout: clearTimeout,
|
||||||
|
clearInterval: clearInterval,
|
||||||
|
addEventListener: function (t, fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
try { fn(); } catch (e) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEventListener: function () {},
|
||||||
|
atob: function (s) { return s; },
|
||||||
|
btoa: function (s) { return s; }
|
||||||
|
};
|
||||||
|
window.window = window;
|
||||||
|
window.top = window;
|
||||||
|
window.self = window;
|
||||||
|
window.parent = window;
|
||||||
|
window.jQuery = jQuery;
|
||||||
|
window.$ = $;
|
||||||
|
|
||||||
|
var top = window;
|
||||||
|
var self = window;
|
||||||
|
var parent = window;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class LzToolAjaxExtractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractWpSignAjaxfile() {
|
||||||
|
String html = """
|
||||||
|
<script>
|
||||||
|
var wp_sign = 'BWMGOF5v';
|
||||||
|
var ajaxdata = 'MrBR';
|
||||||
|
var kdns =1;
|
||||||
|
$.ajax({
|
||||||
|
type : 'post',
|
||||||
|
url : '/ajaxfile.php?file=150233466',
|
||||||
|
data : { 'action':'downprocess','websignkey':ajaxdata,'signs':ajaxdata,'sign':wp_sign,'websign':'','kd':kdns,'ves':1 }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
""";
|
||||||
|
LzTool.AjaxCall call = LzTool.extractAjaxFromHtml(html, null);
|
||||||
|
assertNotNull(call);
|
||||||
|
assertEquals("/ajaxfile.php?file=150233466", call.path());
|
||||||
|
assertEquals("BWMGOF5v", call.form().get("sign"));
|
||||||
|
assertEquals("MrBR", call.form().get("websignkey"));
|
||||||
|
assertEquals("downprocess", call.form().get("action"));
|
||||||
|
assertEquals("1", call.form().get("kd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractPasswordSign() {
|
||||||
|
String html = """
|
||||||
|
<input id="pwd">
|
||||||
|
<script>
|
||||||
|
function down_p(){
|
||||||
|
$.ajax({
|
||||||
|
type : 'post',
|
||||||
|
url : '/ajaxm.php?file=1',
|
||||||
|
data : { 'action':'downprocess','sign':'ABC123','p':'x' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
""";
|
||||||
|
LzTool.AjaxCall call = LzTool.extractAjaxFromHtml(html, "e4k4");
|
||||||
|
assertNotNull(call);
|
||||||
|
assertEquals("/ajaxm.php?file=1", call.path());
|
||||||
|
assertEquals("ABC123", call.form().get("sign"));
|
||||||
|
assertEquals("e4k4", call.form().get("p"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新版蓝奏页面会调用更多 document / jQuery API,沙箱必须能跑完并抓住 $.ajax。
|
||||||
|
*/
|
||||||
|
public class JsExecUtilsLzTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNewIframeAjaxWithDomApis() throws Exception {
|
||||||
|
String js = """
|
||||||
|
var lanosso = '';
|
||||||
|
var down_1 = '';
|
||||||
|
var wsk_sign = 'c20230908';
|
||||||
|
var wp_sign = 'SIGN_ABC';
|
||||||
|
var ajaxdata = 'MrBR';
|
||||||
|
var kdns = 1;
|
||||||
|
if (typeof(killdns)=='undefined'){
|
||||||
|
var kdns = 0;
|
||||||
|
}
|
||||||
|
document.cookie = 'x=1';
|
||||||
|
document.location.reload();
|
||||||
|
document.querySelector('#tourl');
|
||||||
|
document.createElement('div');
|
||||||
|
$.ajax({
|
||||||
|
type : 'post',
|
||||||
|
url : '/ajaxfile.php?file=150233466',
|
||||||
|
data : { 'action':'downprocess','websignkey':ajaxdata,'signs':ajaxdata,'sign':wp_sign,'websign':'','kd':kdns,'ves':1 },
|
||||||
|
dataType : 'json',
|
||||||
|
success:function(msg){
|
||||||
|
$("#tourl").html("ok");
|
||||||
|
$("#outime").css("display","block");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
""";
|
||||||
|
ScriptObjectMirror sign = JsExecUtils.executeDynamicJs(js, null);
|
||||||
|
assertNotNull(sign);
|
||||||
|
assertEquals("/ajaxfile.php?file=150233466", String.valueOf(sign.get("url")));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> data = (Map<String, Object>) sign.get("data");
|
||||||
|
assertEquals("downprocess", String.valueOf(data.get("action")));
|
||||||
|
assertEquals("SIGN_ABC", String.valueOf(data.get("sign")));
|
||||||
|
assertEquals("MrBR", String.valueOf(data.get("websignkey")));
|
||||||
|
assertEquals("1", String.valueOf(data.get("kd")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPwdViaJqueryValAndDocument() throws Exception {
|
||||||
|
String js = """
|
||||||
|
function down_p(){
|
||||||
|
var pwd = $('#pwd').val();
|
||||||
|
var pwd2 = document.getElementById('pwd').value;
|
||||||
|
var pwd3 = document.querySelector('#pwd').value;
|
||||||
|
$(".passwdinput").focus();
|
||||||
|
$.ajax({
|
||||||
|
type : 'post',
|
||||||
|
url : '/ajaxm.php',
|
||||||
|
data : { 'action':'downprocess','sign':'S1','p':pwd,'p2':pwd2,'p3':pwd3 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
ScriptObjectMirror sign = JsExecUtils.executeDynamicJs(js, "down_p", "e4k4");
|
||||||
|
assertNotNull(sign);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> data = (Map<String, Object>) sign.get("data");
|
||||||
|
assertEquals("e4k4", String.valueOf(data.get("p")));
|
||||||
|
assertEquals("e4k4", String.valueOf(data.get("p2")));
|
||||||
|
assertEquals("e4k4", String.valueOf(data.get("p3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadyAndSuccessCallbackDoNotDropAjax() throws Exception {
|
||||||
|
String js = """
|
||||||
|
$(function(){
|
||||||
|
document.getElementById('rpt').style.display = 'none';
|
||||||
|
$.ajax({ url: '/ajaxm.php?file=1', data: { a: 1 } });
|
||||||
|
$("#tourl").html("x");
|
||||||
|
});
|
||||||
|
""";
|
||||||
|
ScriptObjectMirror sign = JsExecUtils.executeDynamicJs(js, null);
|
||||||
|
assertNotNull(sign);
|
||||||
|
assertTrue(String.valueOf(sign.get("url")).contains("ajaxm.php"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.4.2</revision>
|
<revision>0.4.3</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user