const express = require("express"); const router = express.Router(); const {jsdomFromText, browser} = require("sdenv"); const {Script} = require("node:vm"); const fs = require("node:fs"); const crypto = require("node:crypto") const AreaNameEnum = require('../enums/AreaNameEnum'); const Store = require("../utils/Store"); let store = new Store(); router.post('/rsCookie', async (req, res) => { let uuid = crypto.randomUUID().replace(/-/g, ""); let start = new Date(); try { let url = req.body['url']; let areaName = req.body['areaName']; let htmlStr = req.body['htmlBase64']; let jsStr = req.body['jsBase64']; let cookie = req.body['cookieBase64']; let userAgent = req.body['userAgentBase64']; console.log(`${uuid};接收到 ${areaName} 请求:${url}`) fs.writeFileSync(`./back/${uuid}.url`, `${url}\n ${userAgent}\n ${cookie} \n`); if (url == null || url === '') { return res.status(500).send('error url') } if (htmlStr == null || htmlStr === '') { return res.status(500).send('error html') } let jsText; let loadHtmlJs; if (jsStr == null || jsStr === "") { let jsPath = AreaNameEnum.getByAreaName(areaName).JS_FILE if (jsPath == null) { console.error('未找到js文件') return res.send('未找到js文件') } jsText = fs.readFileSync(jsPath).toString('utf8'); loadHtmlJs = true; } else { jsText = Buffer.from(jsStr, 'base64').toString('utf-8') loadHtmlJs = false } let cookies = await handle(url, Buffer.from(htmlStr, 'base64').toString('utf-8'), jsText, cookie != null && cookie !== "" ? Buffer.from(cookie, 'base64').toString('utf-8') : null, userAgent != null && userAgent !== "" ? Buffer.from(userAgent, 'base64').toString('utf-8') : null, uuid, loadHtmlJs) console.log(`${uuid};返回cookie ---->`, cookies.split('; ')) res.status(200).send(cookies); } catch (e) { console.error(e.message) return res.status(500).send(e.message) } finally { console.log(`${uuid};rsCookie ${new Date() - start} ms`) } }) function loadJs(window, jsText) { // 加载js let js = ''; // 加载 页面上的js const allScript = window.document.querySelectorAll('script[r="m"]'); for (let i = 0; i < allScript.length; i++) { const script = allScript[i]; let attr = script.textContent; if (attr) { js += attr } else { js += jsText } js += ";\n" } return js; } function CookieStr2List(cookies) { let list = [] for (let cookie of cookies.trim().split("; ")) { list.push(cookie); } return list } async function handle(url, htmlStr, jsText, cookie, userAgent, uuid, loadHtmlJs) { // 获取 origin let baseUrl = new URL(url).origin; // 初始化 jsDom 和 cookieJar const [jsDom, cookieJar] = await jsdomFromText({ url: url, referrer: url, userAgent: userAgent, contentType: "text/html", runScripts: "outside-only", // runScripts: 'dangerously'/'outside-only' }) // 设置 cookie if (cookie != null) { let cookieList = CookieStr2List(cookie); console.log(`${uuid};cookie 加载长度--->`, cookieList, baseUrl) fs.writeFileSync(`./back/${uuid}.cookie`, cookie) for (let i = 0; i < cookieList.length; i++) { cookieJar.setCookieSync(cookieList[i], baseUrl); } } // 加载dom let dom = await jsDom(htmlStr); // console.log('html 加载长度--->', dom.serialize()) console.log(`${uuid};html 加载长度--->`, dom.serialize().length) fs.writeFileSync(`./back/${uuid}.html`, dom.serialize()) window = dom.window // js执行成功后的瑞树会跳转页面 会触发onbeforeunload钩子 window.onbeforeunload = async (url) => { const cookies = cookieJar.getCookieStringSync(baseUrl); // console.debug(`${url} 生成cookie:`, cookies); store.set(uuid, cookies) // window.close(); } // 初始化浏览器 browser(window, 'chrome'); // 加载js let js; if (loadHtmlJs) { js = loadJs(window, jsText); } else { js = jsText; } console.log(`${uuid};js 加载长度--->`, js.length) fs.writeFileSync(`./back/${uuid}.js`, js) // 执行 js let script = new Script(js); let internalVMContext = dom.getInternalVMContext(); script.runInContext(internalVMContext); // 等待 onbeforeunload 钩子触发后的回掉 let timeout = -1; let reTry = -1; if (process.env.NODE_ENV === 'prod') { timeout = 100 reTry = 10 } else { timeout = 100 reTry = 50 } let val = await store.waitGetAndDelete(uuid, timeout, reTry) // bug 关闭后 部分参数是需要永久保存在内存中的 比如 cookie 实例 下次调用会报错 // internalVMContext.close() // window.close() // dom = null if (val != null) { return val; } throw new Error('执行超时') } module.exports = router