const fs = require('fs');
const PATH = '/opt/code-workspace/ly_ueditor_plus/ueditor-plus/ueditor.all.js';
global.window = global;
global.navigator = { userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" };
global.document = {
createElement: () => ({ style: {}, setAttribute(){}, removeAttribute(){}, getAttribute(){ return null; }, appendChild(){}, childNodes: [] }),
documentMode: 12, compatMode: "CSS1Compat", readyState: "complete",
attachEvent: () => true, detachEvent: () => {}, addEventListener: () => {},
getElementsByTagName: () => [], createTextNode: (t) => ({ nodeValue: t }),
querySelectorAll: () => [], body: {}
};
global.attachEvent = () => true; global.detachEvent = () => {};
global.XMLHttpRequest = function(){};
global.FormData = function(){}; global.FileReader = function(){};
global.addEventListener = () => {}; global.removeEventListener = () => {};
global.localStorage = { getItem: () => null, setItem(){}, removeItem(){} };
global.location = { host: "x", protocol: "http:", href: "http://x/" };
require(PATH);
const UE = global.window.UE;
const src = fs.readFileSync(PATH, 'utf8');
const a1 = 'i=e.innerHTML,';
const i1 = src.indexOf(a1);
const i2 = src.indexOf(',i=UE.filterWord(i),', i1);
if (i1 < 0 || i2 < 0) { console.error('恢复表达式锚点未找到!'); process.exit(1); }
const restoreExpr = src.slice(i1 + a1.length, i2);
console.log('恢复表达式长度:', restoreExpr.length, '| 前60:', restoreExpr.slice(0, 60));
const restoreFn = new Function('i', 'p', 'return (' + restoreExpr + ');');
const anchor2 = 'i=UE.filterWord(i),';
const x1 = src.indexOf(anchor2);
const x2 = src.indexOf(';var C=UE.htmlparser(i);', x1);
const pasteTransform = new Function('i', 'return (' + src.slice(x1 + anchor2.length, x2) + ');');
const ed = {
options: {}, commands: {}, inputRules: [], outputRules: [],
setOpt(o, v){ if (typeof o === 'string') this.options[o] = v; else Object.assign(this.options, o); },
getOpt(k){ return this.options[k]; },
addInputRule(f){ this.inputRules.push(f); }, addOutputRule(f){ this.outputRules.push(f); },
addListener(){}, removeListener(){}, fireEvent(){},
getPreferences(){ return {}; }, ready(){},
queryCommandState(){ return 0; },
selection: { getRange(){ return { collapsed: true, createBookmark: () => ({}), select(){} }; } }
};
UE.plugins.pasteplain.call(ed);
UE.plugins.insertcode.call(ed);
UE.plugins.autotypeset.call(ed);
function simulatePaste(rawHtml, plainText) {
let i = restoreFn(rawHtml, plainText || '');
i = UE.filterWord(i);
i = pasteTransform(i);
let C = UE.htmlparser(i);
if (ed.options.filterRules) UE.filterNode(C, ed.options.filterRules);
UE.Editor.prototype.filterInputRule.call(ed, C);
return C.toHtml();
}
let pass = 0, fail = 0;
function check(name, cond, detail) {
if (cond) { pass++; console.log('PASS ' + name); }
else { fail++; console.log('FAIL ' + name + ' :: ' + detail); }
}
const plain19 = [
'# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson',
'# (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)',
'',
'# 1. 环境准备(确保 source 仓库已启用)',
'dnf repolist --enabled | grep -i source',
'',
'# 2. 获取源码包并安装构建依赖',
'cd /root/rpmbuild/SRPMS',
'dnf download --source rsyslog',
'dnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm',
'',
'# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)',
"rpmbuild --rebuild --nocheck \\",
" --define 'dist .oe2509' \\",
" --define 'build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm' \\",
' rsyslog-8.2312.0-8.oe2509.src.rpm',
'',
'# 4. 安装重建后的基础 RPM(同版本替换)',
'cd /root/rpmbuild/RPMS/x86_64',
'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm',
'',
'# 5. 验证',
'rsyslogd -N1 # 应 exit=0',
'systemctl start rsyslog && systemctl is-active rsyslog # 应 active',
'logger "verification" && tail -3 /var/log/messages # 应恢复写入'
].join('\n');
const brokenPre = '<pre class="codetheme-tomorrownight language-bash" tabindex="0" style="box-sizing:border-box">'
+ '# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson'
+ '# (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)'
+ '# 1. 环境准备(确保 source 仓库已启用)'
+ 'dnf repolist --enabled | grep -i source'
+ '# 2. 获取源码包并安装构建依赖'
+ 'cd /root/rpmbuild/SRPMS'
+ '\ndnf download --source rsyslog'
+ '\ndnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm'
+ '# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)'
+ 'rpmbuild --rebuild --nocheck \\'
+ '\n --define 'dist .oe2509' \\'
+ '\n --define 'build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm' \\'
+ '\n rsyslog-8.2312.0-8.oe2509.src.rpm'
+ '# 4. 安装重建后的基础 RPM(同版本替换)'
+ 'cd /root/rpmbuild/RPMS/x86_64'
+ 'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm'
+ '# 5. 验证'
+ 'rsyslogd -N1 # 应 exit=0'
+ 'systemctl start rsyslog && systemctl is-active rsyslog # 应 active'
+ 'logger "verification" && tail -3 /var/log/messages # 应恢复写入'
+ '</pre>';
const outR1 = simulatePaste(brokenPre, plain19);
check('R1 损坏pre+纯文本→换行恢复',
/libfastjson<br\s*\/?># (关键/.test(outR1) &&
/直接绑定)<br\s*\/?>(<br\s*\/?>)?# 1\./.test(outR1) &&
/x86_64<br\s*\/?>rpm -Uvh/.test(outR1) &&
/应恢复写入/.test(outR1),
JSON.stringify(outR1).slice(0, 300));
const brCount1 = (outR1.match(/<br\s*\/?>/gi) || []).length;
check('R1 每行一个 <br/> (≥19)', brCount1 >= 19, 'br 数=' + brCount1);
const outR2 = simulatePaste('<pre>x\ny\nz</pre>', 'x\ny\nz');
const brCount2 = (outR2.match(/<br\s*\/?>/gi) || []).length;
check('R2 正常pre恢复跳过', /x<br\s*\/?>y<br\s*\/?>z/.test(outR2) && brCount2 === 2, JSON.stringify(outR2) + ' br=' + brCount2);
const outR3 = simulatePaste('<pre>abc</pre>', '完全不同的内容\n第二行');
check('R3 不匹配不注入', /abc/.test(outR3) && outR3.indexOf('完全不同的内容') < 0, JSON.stringify(outR3));
const outR4 = simulatePaste('<p>hi</p>', 'hi');
check('R4 无pre不动', /<p>hi<\/p>/.test(outR4), JSON.stringify(outR4));
const outR5 = simulatePaste('<pre>single</pre>', 'single');
check('R5 单行恢复跳过', /<pre>single<br\s*\/?><\/pre>/.test(outR5), JSON.stringify(outR5));
const outR6 = simulatePaste('<pre>echo $1 $2 && $&</pre>', 'echo $1 $2 && $&');
check('R6 替换符不被吃掉', /echo \$1 \$2 && \$&/.test(outR6) || /echo \$1 \$2 && \$&/.test(outR6), JSON.stringify(outR6));
const spanLines = [
'# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson',
'# (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)',
'',
'# 1. 环境准备(确保 source 仓库已启用)',
'dnf repolist --enabled | grep -i source',
'',
'# 2. 获取源码包并安装构建依赖',
'cd /root/rpmbuild/SRPMS',
'dnf download --source rsyslog',
'dnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm',
'',
'# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)',
"rpmbuild --rebuild --nocheck \\",
" --define 'dist .oe2509' \\",
" --define 'build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm' \\",
' rsyslog-8.2312.0-8.oe2509.src.rpm',
'',
'# 4. 安装重建后的基础 RPM(同版本替换)',
'cd /root/rpmbuild/RPMS/x86_64',
'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm',
'',
'# 5. 验证',
'rsyslogd -N1 # 应 exit=0',
'systemctl start rsyslog && systemctl is-active rsyslog # 应 active',
'logger "verification" && tail -3 /var/log/messages # 应恢复写入'
];
const spanHtml = '<pre class=" codetheme-tomorrownight language-bash" tabindex="0" style="color:rgb(204,204,204)"><code class="language-bash">'
+ spanLines.map(function (l) {
return l ? '<span class="token comment" style="color:rgb(153,153,153)">' + l + '</span>\n' : '\n';
}).join('')
+ '</code></pre>';
const outS1 = simulatePaste(spanHtml, spanLines.join('\n'));
const brCountS1 = (outS1.match(/<br\s*\/?>/gi) || []).length;
check('S1 span+换行代码块→逐行<br/> (≥19)',
/前置:先按 micoder[\s\S]{0,100}?<br\s*\/?>[\s\S]{0,120}?#[\s\S]{0,20}?(关键/.test(outS1) &&
/x86_64[\s\S]{0,100}?<br\s*\/?>[\s\S]{0,100}?rpm -Uvh/.test(outS1) &&
brCountS1 >= 19,
'br=' + brCountS1 + ' :: ' + JSON.stringify(outS1).slice(0, 260));
check('S1 pre 内无残留 \\n', outS1.indexOf('\n') < 0, JSON.stringify(outS1).slice(0, 120));
check('S1 span 结构保留', /<span class="token/.test(outS1), JSON.stringify(outS1).slice(0, 120));
const outA = simulatePaste('# a\n# b\n\n# c\nd', '# a\n# b\n\n# c\nd');
check('纯文本多行→<br/>', /# a<br\s*\/?># b/.test(outA) && /# b<br\s*\/?><br\s*\/?># c/.test(outA), JSON.stringify(outA));
const outB = simulatePaste('<p>intro</p><pre class="brush:js">var a=1;\nvar b=2;</pre><p>outro</p>', '');
check('pre 内换行保留(回归)', /var a=1;(\n|<br\s*\/?>)var b=2/.test(outB), JSON.stringify(outB));
const outC = simulatePaste('<p>a</p>\n<p>b</p>', 'a\nb');
check('段落间换行不产生<br>(回归)', outC.indexOf('<br') < 0, JSON.stringify(outC));
console.log('\n结果: ' + pass + ' 通过, ' + fail + ' 失败');
process.exit(fail ? 1 : 0);