fix: sandbox HTML preview execution

This commit is contained in:
kl
2026-08-14 11:12:50 +08:00
parent 9c3fd82076
commit 583208898f
4 changed files with 87 additions and 48 deletions

View File

@@ -469,8 +469,8 @@ kk.xlsxshowtoolbar = false
# 首页是否显示key密钥 默认为false禁用 # 首页是否显示key密钥 默认为false禁用
kk.isshowkey = false kk.isshowkey = false
# 预览html文件 是否启用JavaScript 默认为true # 预览html文件 是否在隔离沙箱中启用JavaScript默认为false
kk.scriptjs = true kk.scriptjs = false
############################################################################### ###############################################################################

View File

@@ -466,8 +466,8 @@ kk.xlsxshowtoolbar = true
# 首页是否显示key密钥 默认为false禁用 # 首页是否显示key密钥 默认为false禁用
kk.isshowkey = true kk.isshowkey = true
# 预览html文件 是否启用JavaScript 默认为true # 预览html文件 是否在隔离沙箱中启用JavaScript默认为false
kk.scriptjs = true kk.scriptjs = false
############################################################################### ###############################################################################
@@ -475,4 +475,4 @@ kk.scriptjs = true
############################################################################### ###############################################################################
# 纯文本文件类型直接显示 # 纯文本文件类型直接显示
simText = ${KK_SIMTEXT:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd} simText = ${KK_SIMTEXT:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd}

View File

@@ -16,6 +16,22 @@
<script src="js/jsformat.js" type="text/javascript"></script> <script src="js/jsformat.js" type="text/javascript"></script>
</#if> </#if>
<script src="js/base64.min.js" type="text/javascript"></script> <script src="js/base64.min.js" type="text/javascript"></script>
<style>
#htmlPreviewFrame {
width: 100%;
min-height: 65vh;
border: 0;
background: #fff;
}
#htmlSource {
min-height: 65vh;
overflow: auto;
border: 0;
background: #fff;
white-space: pre-wrap;
word-break: break-word;
}
</style>
</head> </head>
<body> <body>
<input hidden id="textData" value="${textData}"/> <input hidden id="textData" value="${textData}"/>
@@ -25,7 +41,7 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<strong><font color="red"><input class="GLOkBtn" type="button" value="运行html" onclick="loadXmlData();" /></font></strong> <strong><font color="red"><input class="GLOkBtn" type="button" value="在沙箱中运行html" onclick="loadXmlData();" /></font></strong>
<a data-toggle="collapse" data-parent="#accordion" onclick="loadText();"> <a data-toggle="collapse" data-parent="#accordion" onclick="loadText();">
${file.name} ${file.name}
</a> </a>
@@ -39,58 +55,43 @@
<script> <script>
// 将Freemarker的布尔值传递给JavaScript // 将Freemarker的布尔值传递给JavaScript
var scriptjs = ${scriptjs?c}; // ?c 将布尔值转换为字符串true/false var scriptjs = ${scriptjs?c}; // ?c 将布尔值转换为字符串true/false
function decodePreviewText() {
var escapedText = Base64.decode($("#textData").val());
var decoder = document.createElement("textarea");
decoder.innerHTML = escapedText;
return decoder.value;
}
function replacePreviewContent(element) {
var container = document.getElementById("text");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
container.appendChild(element);
}
/** /**
*加载普通文本 *加载普通文本
*/ */
function loadText() { function loadText() {
var base64data = $("#textData").val() var source = document.createElement("pre");
var div = document.getElementById("text"); source.id = "htmlSource";
div.innerHTML = ""; // source.textContent = decodePreviewText();
var textData = Base64.decode(base64data); replacePreviewContent(source);
textData = htmlttt(textData,1);
var textPreData = "<xmp style='background-color: #FFFFFF;overflow-y: scroll;border:none'>" + textData + "</xmp>";
$("#text").append(textPreData);
} }
function htmlttt (str,txt){
var s = "";
if(str.length == 0) return "";
s = str.replace(/&amp;/gi,"&");
s = s.replace(/&lt;/gi,"<");
s = s.replace(/&gt;/gi,">");
s = s.replace(/&nbsp;/gi," ");
s = s.replace(/&#39;/gi,"\'");
s = s.replace(/&quot;/gi,"\"");
s = s.replace(/javascript/g,"javascript ");
if (txt === 2){
s = s.replace(/<script/gi, "&lt;script ");
s = s.replace(/javascript/g,"javascript ");
s = s.replace(/<\/script/gi, "&lt;/script ");
s = s.replace(/<iframe/gi, "&lt;iframe ");
s = s.replace(/<\/iframe/gi, "&lt;/iframe ");
s = s.replace(/confirm/gi, "c&onfirm");
s = s.replace(/alert/gi, "a&lert");
s = s.replace(/eval/gi, "e&val");
}
return s;
}
/** /**
*加载运行 *加载运行
*/ */
function loadXmlData() { function loadXmlData() {
var base64data = $("#textData").val(); var frame = document.createElement("iframe");
var textData = Base64.decode(base64data); frame.id = "htmlPreviewFrame";
frame.title = "HTML sandbox preview";
// 直接使用JavaScript变量进行判断 frame.setAttribute("sandbox", scriptjs ? "allow-scripts" : "");
if (scriptjs) { frame.setAttribute("referrerpolicy", "no-referrer");
textData = htmlttt(textData, 1); frame.srcdoc = decodePreviewText();
} else { replacePreviewContent(frame);
textData = htmlttt(textData, 2);
}
$('#text').html(textData);
} }
/** /**
@@ -141,4 +142,4 @@
</script> </script>
</#if> </#if>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,38 @@
package cn.keking.web;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HtmlPreviewSandboxTests {
@Test
void shouldRenderHtmlOnlyInsideAnOpaqueOriginSandbox() throws IOException {
String template = readResource("web/code.ftl");
assertTrue(template.contains("frame.setAttribute(\"sandbox\", scriptjs ? \"allow-scripts\" : \"\")"));
assertTrue(template.contains("frame.srcdoc = decodePreviewText()"));
assertFalse(template.contains("allow-same-origin"));
assertFalse(template.contains("$('#text').html(textData)"));
assertFalse(template.contains("function htmlttt"));
}
@Test
void shouldDisplaySourceAsTextAndDisableScriptsByDefault() throws IOException {
String template = readResource("web/code.ftl");
String properties = readResource("application.properties");
assertTrue(template.contains("source.textContent = decodePreviewText()"));
assertTrue(properties.contains("kk.scriptjs = false"));
}
private String readResource(String path) throws IOException {
ClassPathResource resource = new ClassPathResource(path);
return new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
}
}