fix: HTML escaping covers attribute context
escapeHtml now escapes & < > " ' (shared web/js/html-escape.js, loaded by admin, gallery and metadata pages) instead of the textContent/innerHTML round-trip that left quotes intact, so a tag, category, checkpoint or file name containing a quote can no longer break out of a data-*, value or title attribute. Inline onclick handlers that interpolated server data now carry data-action and data-* attributes and are dispatched through one delegated listener per page (web/js/data-actions.js); the img onerror fallback uses data-fallback-src with a capturing error listener. Every remaining template value written into an attribute or text node (prompts, checkpoints, file paths, log lines, directory names, categories, suggestions) is escaped. metadata.html no longer builds an "explorer /select" shell command from the file name (Electron-only branch); the path is copied to the clipboard.
This commit is contained in:
+21
-37
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Gallery - Generation Data</title>
|
||||
<script src="/prompt_manager/js/theme-bridge.js"></script>
|
||||
<script src="/prompt_manager/js/html-escape.js"></script>
|
||||
<script src="/prompt_manager/js/comfy-metadata.js"></script>
|
||||
<link rel="stylesheet" href="lib/tailwind/comfyui-theme.css">
|
||||
<link rel="stylesheet" href="lib/tailwind/styles.css">
|
||||
@@ -32,27 +33,13 @@
|
||||
let currentWorkflowData = null;
|
||||
|
||||
function openInFileExplorer(path) {
|
||||
// For Windows file explorer
|
||||
if (navigator.platform.indexOf('Win') > -1) {
|
||||
// This will work in Electron apps or desktop environments
|
||||
try {
|
||||
window.require('child_process').exec(`explorer /select,"${path}"`);
|
||||
} catch (e) {
|
||||
// Fallback: copy path to clipboard and show alert
|
||||
navigator.clipboard.writeText(path).then(() => {
|
||||
alert('File path copied to clipboard: ' + path);
|
||||
}).catch(() => {
|
||||
alert('File path: ' + path);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// For other platforms, just copy to clipboard
|
||||
navigator.clipboard.writeText(path).then(() => {
|
||||
alert('File path copied to clipboard: ' + path);
|
||||
}).catch(() => {
|
||||
alert('File path: ' + path);
|
||||
});
|
||||
}
|
||||
// A browser page cannot open a file manager, so the path is copied instead.
|
||||
// (The former Electron-only branch built a shell command from the file name.)
|
||||
navigator.clipboard.writeText(path).then(() => {
|
||||
alert('File path copied to clipboard: ' + path);
|
||||
}).catch(() => {
|
||||
alert('File path: ' + path);
|
||||
});
|
||||
}
|
||||
|
||||
async function copyToClipboard(text, button) {
|
||||
@@ -213,8 +200,8 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<!-- File Path -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">File Path</h2>
|
||||
<div class="text-sm text-pm-accent hover:bg-pm-hover cursor-pointer bg-pm-surface p-2 rounded-pm-sm break-all" onclick="openInFileExplorer('${filename}')">
|
||||
${filename}
|
||||
<div class="text-sm text-pm-accent hover:bg-pm-hover cursor-pointer bg-pm-surface p-2 rounded-pm-sm break-all" data-action="open-file" data-path="${escapeHtml(filename)}">
|
||||
${escapeHtml(filename)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -223,7 +210,7 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">Resources used</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-pm-accent cursor-pointer">${checkpoint}</div>
|
||||
<div class="text-pm-accent cursor-pointer">${escapeHtml(checkpoint)}</div>
|
||||
<div class="text-xs text-pm-muted">ComfyUI Generated</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">CHECKPOINT</span>
|
||||
@@ -242,7 +229,7 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded-pm-sm max-h-32 overflow-y-auto">
|
||||
${positivePrompt.substring(0, 200)}${positivePrompt.length > 200 ? '...' : ''}
|
||||
${escapeHtml(positivePrompt.substring(0, 200))}${positivePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${positivePrompt.length > 200 ? '<button class="text-pm-accent hover:bg-pm-hover text-sm mt-1" onclick="showFullPrompt(\'positive\')">Show more</button>' : ''}
|
||||
</div>
|
||||
@@ -258,7 +245,7 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded-pm-sm max-h-32 overflow-y-auto">
|
||||
${negativePrompt.substring(0, 200)}${negativePrompt.length > 200 ? '...' : ''}
|
||||
${escapeHtml(negativePrompt.substring(0, 200))}${negativePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${negativePrompt.length > 200 ? '<button class="text-pm-accent hover:bg-pm-hover text-sm mt-1" onclick="showFullPrompt(\'negative\')">Show more</button>' : ''}
|
||||
</div>
|
||||
@@ -267,12 +254,12 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-3">Other metadata</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">CFG SCALE: ${cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">STEPS: ${steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SAMPLER: ${sampler}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">CFG SCALE: ${escapeHtml(cfgScale)}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">STEPS: ${escapeHtml(steps)}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SAMPLER: ${escapeHtml(sampler)}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SEED: ${seed}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SEED: ${escapeHtml(seed)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -289,6 +276,9 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
dataPanel.querySelector('[data-action="open-file"]').addEventListener('click', (e) => {
|
||||
openInFileExplorer(e.currentTarget.dataset.path);
|
||||
});
|
||||
|
||||
// Store the full data for later use
|
||||
currentWorkflowData = {
|
||||
@@ -304,12 +294,6 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function showFullPrompt(type) {
|
||||
if (currentWorkflowData) {
|
||||
const prompt = type === 'positive' ? currentWorkflowData.positivePrompt : currentWorkflowData.negativePrompt;
|
||||
@@ -343,7 +327,7 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<head><title>ComfyUI Workflow Data</title></head>
|
||||
<body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
|
||||
<h2>ComfyUI Workflow JSON</h2>
|
||||
<pre style="background: #222; padding: 15px; border-radius: 5px; overflow: auto;">${JSON.stringify(currentWorkflowData.workflow, null, 2)}</pre>
|
||||
<pre style="background: #222; padding: 15px; border-radius: 5px; overflow: auto;">${escapeHtml(JSON.stringify(currentWorkflowData.workflow, null, 2))}</pre>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
Reference in New Issue
Block a user