Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: CI

on:
push:
branches: [main, 'cursor/**']
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: node --test tests/*.mjs
21 changes: 15 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ <h1>免费在线<span>工具箱</span></h1>
navigator.clipboard.writeText(text).then(() => toast('已复制到剪贴板'));
}

function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}

// ============ 生成工具面板 ============
function renderPanels() {
const container = document.getElementById('toolPanels');
Expand Down Expand Up @@ -626,10 +635,10 @@ <h2>🔍 正则测试 <span class="back" onclick="showHome()">← 返回</span><
let html = '';
for (let i = 0; i < maxLen; i++) {
const la = a[i] ?? '', lb = b[i] ?? '';
if (la === lb) { html += `<div style="color:var(--text2)">${la}</div>`; }
if (la === lb) { html += `<div style="color:var(--text2)">${escapeHtml(la)}</div>`; }
else {
if (la) html += `<div style="color:var(--danger);background:rgba(255,107,107,0.1);padding:2px 4px;border-radius:3px">- ${la}</div>`;
if (lb) html += `<div style="color:var(--success);background:rgba(0,184,148,0.1);padding:2px 4px;border-radius:3px">+ ${lb}</div>`;
if (la) html += `<div style="color:var(--danger);background:rgba(255,107,107,0.1);padding:2px 4px;border-radius:3px">- ${escapeHtml(la)}</div>`;
if (lb) html += `<div style="color:var(--success);background:rgba(0,184,148,0.1);padding:2px 4px;border-radius:3px">+ ${escapeHtml(lb)}</div>`;
}
}
document.getElementById('diff-result').innerHTML = html;
Expand Down Expand Up @@ -668,7 +677,7 @@ <h2>🔍 正则测试 <span class="back" onclick="showHome()">← 返回</span><
// Markdown (简单实现)
function renderMD() {
const md = document.getElementById('md-input').value;
let html = md
let html = escapeHtml(md)
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
Expand Down Expand Up @@ -720,12 +729,12 @@ <h2>🔍 正则测试 <span class="back" onclick="showHome()">← 返回</span><
const re = new RegExp(pattern, flags);
const matches = text.match(re);
if (matches) {
document.getElementById('regex-result').innerHTML = `<strong>匹配 ${matches.length} 个:</strong><br>` + matches.map(m => `<span style="background:rgba(108,92,231,0.3);padding:2px 4px;border-radius:3px">${m}</span>`).join(' ');
document.getElementById('regex-result').innerHTML = `<strong>匹配 ${matches.length} 个:</strong><br>` + matches.map(m => `<span style="background:rgba(108,92,231,0.3);padding:2px 4px;border-radius:3px">${escapeHtml(m)}</span>`).join(' ');
} else {
document.getElementById('regex-result').innerHTML = '<span style="color:var(--text2)">无匹配</span>';
}
} catch(e) {
document.getElementById('regex-result').innerHTML = `<span style="color:var(--danger)">❌ ${e.message}</span>`;
document.getElementById('regex-result').innerHTML = `<span style="color:var(--danger)">❌ ${escapeHtml(e.message)}</span>`;
}
}

Expand Down
26 changes: 26 additions & 0 deletions tests/escape-html.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from 'node:assert/strict';
import test from 'node:test';

/**
* 与 index.html 中 escapeHtml 实现保持一致(用于锁定 XSS 缓解逻辑)。
*/
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}

test('escapeHtml 转义尖括号与引号', () => {
assert.equal(
escapeHtml('<img src=x onerror=alert(1)>'),
'&lt;img src=x onerror=alert(1)&gt;'
);
assert.equal(escapeHtml('a"b\'c'), 'a&quot;b&#39;c');
});

test('escapeHtml 先处理 & 避免双重实体被误解析', () => {
assert.equal(escapeHtml('Tom & Jerry'), 'Tom &amp; Jerry');
});
Loading