refactor(dnfs): JS代码节点改为在当前页面执行以支持页面特效
移除 iframe 沙箱方案,改为 new Function 在当前页面上下文执行, 代码可直接操作 document/window 对页面施加特效;console 输出捕获到下方区域。
This commit is contained in:
1 parent
2918f2c83c
commit
fed2d42472
4 files changed
+33
-48
No files matched your search
+1
-1
@@ -59,7 +59,7 @@ Authorization: Bearer <base64_payload>_<rsa_sign>
|
||||
| `_sp` | 信封字段 | 前端表现 |
|
||||
|-------|---------|---------|
|
||||
| `link` | `url`(http/https,≤2048)、`note?` | favicon 图标(经 static `/api/favicon` 代理拉取)+ 可点击链接 |
|
||||
| `js` | `code`(≤512KB)、`note?` | "审计源码"(纯文本展示)+ "运行"(插入页面 iframe 沙箱执行,捕获 console 输出)按钮 |
|
||||
| `js` | `code`(≤512KB)、`note?` | "审计源码"(纯文本展示)+ "运行"(在当前页面上下文执行,可施加特效;console 输出捕获到下方)按钮 |
|
||||
| `image` | `name`、`data`(`data:image/` DataURL,≤3.5M 字符)、`note?` | 图片直接显示 |
|
||||
| `file` | `name`、`mime`、`data`(Base64,≤3.5M 字符)、`note?` | 下载按钮(Base64→Blob→下载) |
|
||||
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ static :9200 (Flask + CORS)
|
||||
- 流媒体:视频(MP4/WebM/OGG/AVI/MOV/MKV)与音频(MP3/WAV/OGG/FLAC/AAC/M4A)在线播放,支持进度拖动
|
||||
- 深色模式:`css/theme.css` 统一主题,自动适配系统偏好
|
||||
- 表单组件:注册页图形/邮箱验证码统一 `.code-box` 行内布局(文字标签 + 输入框 + 控件同排,窄屏自动换行),验证码图片样式复用 `theme.css` 的 `.captcha-img` 类
|
||||
- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ JS 代码(可审计/可运行,插入页面 iframe 沙箱执行)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制
|
||||
- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ JS 代码(可审计/可运行,在当前页面执行可加特效)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制
|
||||
- aibox Web 客户端:`aibox.html` 移动端适配聊天页,经 gateway `[WsProxy]` 反向代理(如 `/ws/aibox → ws://127.0.0.1:8765`)与 aibox 智能体通信,握手由 gateway 强制 `is_admin` 验签;协议与 BotOps/client.py 兼容(chunk 流式 / reasoning 思考区 / task 任务面板 / 指数退避重连)
|
||||
- 路径遍历防护:`safe_path_join` 限制访问范围在 `directory.root` 内
|
||||
- Nuitka 编译:可打包为独立二进制 `fsys.bin`(`sys.frozen` 检测 + BASE_DIR 锚定)
|
||||
|
||||
@@ -526,13 +526,18 @@ input[type="button"]:disabled {
|
||||
word-break: break-all;
|
||||
}
|
||||
.sp-image { display: block; max-width: 100%; border-radius: var(--radius-sm); }
|
||||
.sp-run { margin-top: 0.5rem; }
|
||||
.sp-run-frame {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
.sp-run {
|
||||
margin-top: 0.5rem;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
padding: 0.75rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.sp-note { font-size: 0.82rem; color: var(--fg-secondary); margin-top: 0.4rem; }
|
||||
#sp-panel { margin-top: 0.4rem; }
|
||||
|
||||
+21
-41
@@ -492,7 +492,9 @@ function renderSpLink(container, sp, appendNote) {
|
||||
appendNote();
|
||||
}
|
||||
|
||||
/* JS 代码节点:审计(展开源码,textContent 防注入)+ 运行(插入到页面中 iframe 沙箱执行,捕获 console 输出) */
|
||||
/* JS 代码节点:审计(展开源码,textContent 防注入)+ 运行(在当前页面上下文执行,可直接操作页面加特效)。
|
||||
* 代码通过 new Function 注入包装 console 执行,可访问全局 document/window 对当前页面施加特效;
|
||||
* console.log/error/warn 输出捕获到下方输出区。仅管理员可发布(后端强制)。 */
|
||||
function renderSpJs(container, sp, appendNote) {
|
||||
var bar = document.createElement('div');
|
||||
bar.className = 'sp-actions';
|
||||
@@ -506,9 +508,9 @@ function renderSpJs(container, sp, appendNote) {
|
||||
pre.className = 'sp-code';
|
||||
pre.style.display = 'none';
|
||||
pre.textContent = sp.code;
|
||||
var run_box = document.createElement('div');
|
||||
run_box.className = 'sp-run';
|
||||
run_box.style.display = 'none';
|
||||
var out = document.createElement('div');
|
||||
out.className = 'sp-run';
|
||||
out.style.display = 'none';
|
||||
audit_btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var hidden = (pre.style.display === 'none');
|
||||
@@ -517,52 +519,30 @@ function renderSpJs(container, sp, appendNote) {
|
||||
});
|
||||
run_btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (run_box.firstChild) { // 已注入过,仅切换显示,不重复执行
|
||||
run_box.style.display = run_box.style.display === 'none' ? 'block' : 'none';
|
||||
return;
|
||||
out.style.display = out.style.display === 'none' ? 'block' : 'none';
|
||||
if (out.textContent) return; // 已执行过,仅切换显示
|
||||
var lines = [];
|
||||
var hook = {
|
||||
log: function() { lines.push(Array.prototype.slice.call(arguments).join(' ')); },
|
||||
error: function() { lines.push('Error: ' + Array.prototype.slice.call(arguments).join(' ')); },
|
||||
warn: function() { lines.push('Warn: ' + Array.prototype.slice.call(arguments).join(' ')); }
|
||||
};
|
||||
try {
|
||||
/* 在当前页面上下文执行,函数体内可访问全局 document/window 施加特效 */
|
||||
new Function('console', sp.code)(hook);
|
||||
} catch (err) {
|
||||
lines.push('Error: ' + (err && err.message ? err.message : err));
|
||||
}
|
||||
run_box.style.display = 'block';
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.className = 'sp-run-frame'; // 高度由 iframe 内 postMessage 自适应
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
/* 代码中若含 </script> 会提前闭合注入的 script 标签,先转义防逃逸 */
|
||||
var code = sp.code.replace(/<\/script/gi, '<\\/script');
|
||||
var html = '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>'
|
||||
+ '<script>'
|
||||
+ 'var _out=function(s){var d=document.createElement("div");d.textContent="> "+s;document.body.appendChild(d);};'
|
||||
+ 'window.console.log=function(){_out(Array.prototype.slice.call(arguments).join(" "));};'
|
||||
+ 'window.console.error=function(){_out("Error: "+Array.prototype.slice.call(arguments).join(" "));};'
|
||||
+ 'window.console.warn=function(){_out("Warn: "+Array.prototype.slice.call(arguments).join(" "));};'
|
||||
+ 'window.onerror=function(m){_out("Error: "+m);};'
|
||||
+ 'window.addEventListener("load",function(){window.parent.postMessage({__spRunH:document.body.scrollHeight},"*");});'
|
||||
+ '<\/script>'
|
||||
+ '<script>' + code + '<\/script>'
|
||||
+ '</body></html>';
|
||||
iframe.srcdoc = html;
|
||||
run_box.appendChild(iframe);
|
||||
out.textContent = lines.join('\n') || '执行完成';
|
||||
});
|
||||
bar.appendChild(audit_btn);
|
||||
bar.appendChild(run_btn);
|
||||
container.appendChild(bar);
|
||||
container.appendChild(pre);
|
||||
container.appendChild(run_box);
|
||||
container.appendChild(out);
|
||||
appendNote();
|
||||
}
|
||||
|
||||
/* 监听 iframe 内 postMessage,自适应 .sp-run-frame 高度 */
|
||||
var _sp_run_hack = false;
|
||||
function _ensureSpRunHeight() {
|
||||
if (_sp_run_hack) return;
|
||||
_sp_run_hack = true;
|
||||
window.addEventListener('message', function(e) {
|
||||
if (e.data && typeof e.data.__spRunH === 'number') {
|
||||
var f = document.querySelector('.sp-run-frame');
|
||||
if (f) f.style.height = (e.data.__spRunH + 24) + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
_ensureSpRunHeight();
|
||||
|
||||
/* 图片节点:DataURL 直接显示(CSP img-src 允许 data:) */
|
||||
function renderSpImage(container, sp, appendNote) {
|
||||
var img = document.createElement('img');
|
||||
|
||||
Reference in new issue
Block a user