fix(csp): remove all inline styles, scripts, and event handlers for strict CSP compliance
Replace ~50 inline style="" attributes with CSS classes, move 3 inline
<script> blocks to external JS files, replace all inline event handlers
(onclick, onerror) with addEventListener, and remove createElement('style')
from icons.js. All changes support the strict CSP policy (style-src 'self';
script-src 'self') without weakening it.
This commit is contained in:
@@ -16,9 +16,9 @@ const ACTIONS_BAR_TEMPLATES = {
|
||||
<div class="action-buttons">
|
||||
<div class="upload-dropdown" id="upload-dropdown">
|
||||
<button class="btn btn-primary" id="upload-btn">
|
||||
<i class="fas fa-cloud-upload-alt" style="margin-right: 5px;"></i>
|
||||
<i class="fas fa-cloud-upload-alt icon-mr"></i>
|
||||
<span data-i18n="actions.upload">Upload</span>
|
||||
<i class="fas fa-caret-down" style="margin-left: 4px; font-size: 12px;"></i>
|
||||
<i class="fas fa-caret-down icon-ml"></i>
|
||||
</button>
|
||||
<div class="upload-dropdown-menu" id="upload-dropdown-menu">
|
||||
<button class="upload-dropdown-item" id="upload-files-btn">
|
||||
@@ -32,7 +32,7 @@ const ACTIONS_BAR_TEMPLATES = {
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" id="new-folder-btn">
|
||||
<i class="fas fa-folder-plus" style="margin-right: 5px;"></i>
|
||||
<i class="fas fa-folder-plus icon-mr"></i>
|
||||
<span data-i18n="actions.new_folder">New folder</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@ const ACTIONS_BAR_TEMPLATES = {
|
||||
recent: `
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-secondary" id="clear-recent-btn">
|
||||
<i class="fas fa-broom" style="margin-right: 5px;"></i>
|
||||
<i class="fas fa-broom icon-mr"></i>
|
||||
<span data-i18n="actions.clear_recent">Clear recent</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -184,7 +184,7 @@ function switchToFavoritesView() {
|
||||
if (filesGridError) {
|
||||
filesGridError.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-exclamation-circle" style="font-size: 48px; color: #f44336; margin-bottom: 16px;"></i>
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the favorites module</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -215,7 +215,7 @@ function switchToRecentFilesView() {
|
||||
if (filesGridError) {
|
||||
filesGridError.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-exclamation-circle" style="font-size: 48px; color: #f44336; margin-bottom: 16px;"></i>
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the recent files module</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -15,7 +15,7 @@ async function performSearch(query, sortBy) {
|
||||
if (filesGrid) {
|
||||
filesGrid.innerHTML = `
|
||||
<div class="search-results-header">
|
||||
<h3><i class="fas fa-spinner fa-spin" style="margin-right:8px;"></i> Searching for "${query}"...</h3>
|
||||
<h3><i class="fas fa-spinner fa-spin search-spinner"></i> Searching for "${query}"...</h3>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ async function loadTrashItems() {
|
||||
const emptyState = document.createElement('div');
|
||||
emptyState.className = 'empty-state';
|
||||
emptyState.innerHTML = `
|
||||
<i class="fas fa-trash" style="font-size: 48px; color: #ddd; margin-bottom: 16px;"></i>
|
||||
<i class="fas fa-trash empty-state-icon"></i>
|
||||
<p>${window.i18n ? window.i18n.t('trash.empty_state') : 'The trash is empty'}</p>
|
||||
`;
|
||||
elements.filesGrid.appendChild(emptyState);
|
||||
|
||||
+16
-12
@@ -48,10 +48,10 @@ const ui = {
|
||||
<div class="context-menu-item" id="view-file-option">
|
||||
<i class="fas fa-eye"></i> <span data-i18n="actions.view">View</span>
|
||||
</div>
|
||||
<div class="context-menu-item" id="wopi-edit-file-option" style="display:none">
|
||||
<div class="context-menu-item hidden" id="wopi-edit-file-option">
|
||||
<i class="fas fa-file-word"></i> <span>Edit in Office</span>
|
||||
</div>
|
||||
<div class="context-menu-item" id="wopi-edit-file-tab-option" style="display:none">
|
||||
<div class="context-menu-item hidden" id="wopi-edit-file-tab-option">
|
||||
<i class="fas fa-external-link-alt"></i> <span>Edit in Office (new tab)</span>
|
||||
</div>
|
||||
<div class="context-menu-item" id="download-file-option">
|
||||
@@ -87,7 +87,7 @@ const ui = {
|
||||
renameDialog.innerHTML = `
|
||||
<div class="rename-dialog-content">
|
||||
<div class="rename-dialog-header">
|
||||
<i class="fas fa-pen" style="color:#ff5e3a"></i>
|
||||
<i class="fas fa-pen dialog-header-icon"></i>
|
||||
<span data-i18n="dialogs.rename_folder">Rename</span>
|
||||
</div>
|
||||
<div class="rename-dialog-body">
|
||||
@@ -110,13 +110,13 @@ const ui = {
|
||||
moveDialog.innerHTML = `
|
||||
<div class="rename-dialog-content">
|
||||
<div class="rename-dialog-header">
|
||||
<i class="fas fa-arrows-alt" style="color:#ff5e3a"></i>
|
||||
<i class="fas fa-arrows-alt dialog-header-icon"></i>
|
||||
<span data-i18n="dialogs.move_file">Move</span>
|
||||
</div>
|
||||
<div class="rename-dialog-body">
|
||||
<p style="margin:0 0 12px;color:#718096;font-size:14px" data-i18n="dialogs.select_destination">Select destination folder:</p>
|
||||
<p class="move-dialog-hint" data-i18n="dialogs.select_destination">Select destination folder:</p>
|
||||
<div id="move-dialog-breadcrumb" class="move-dialog-breadcrumb"></div>
|
||||
<div id="folder-select-container" style="max-height:220px;overflow-y:auto;">
|
||||
<div id="folder-select-container" class="folder-select-container">
|
||||
</div>
|
||||
</div>
|
||||
<div class="rename-dialog-buttons">
|
||||
@@ -137,14 +137,14 @@ const ui = {
|
||||
shareDialog.innerHTML = `
|
||||
<div class="share-dialog-content">
|
||||
<div class="share-dialog-header">
|
||||
<i class="fas fa-share-alt" style="color:#ff5e3a"></i>
|
||||
<i class="fas fa-share-alt dialog-header-icon"></i>
|
||||
<span data-i18n="dialogs.share_file">Share file</span>
|
||||
</div>
|
||||
<div class="shared-item-info">
|
||||
<strong>Item:</strong> <span id="shared-item-name"></span>
|
||||
</div>
|
||||
|
||||
<div id="existing-shares-section" style="display:none; margin: 15px 0;">
|
||||
<div id="existing-shares-section" class="share-section hidden">
|
||||
<h3 data-i18n="dialogs.existing_shares">Existing shared links</h3>
|
||||
<div id="existing-shares-container"></div>
|
||||
</div>
|
||||
@@ -181,7 +181,7 @@ const ui = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="new-share-section" style="display:none; margin: 15px 0;">
|
||||
<div id="new-share-section" class="share-section hidden">
|
||||
<h3 data-i18n="dialogs.generated_link">Generated link</h3>
|
||||
<div class="form-group">
|
||||
<input type="text" id="generated-share-url" readonly>
|
||||
@@ -232,7 +232,7 @@ const ui = {
|
||||
notificationDialog.innerHTML = `
|
||||
<div class="share-dialog-content">
|
||||
<div class="share-dialog-header">
|
||||
<i class="fas fa-envelope" style="color:#ff5e3a"></i>
|
||||
<i class="fas fa-envelope dialog-header-icon"></i>
|
||||
<span data-i18n="dialogs.notify">Notify shared link</span>
|
||||
</div>
|
||||
|
||||
@@ -1143,12 +1143,14 @@ const ui = {
|
||||
<i class="${isFileFav ? 'fas' : 'far'} fa-star"></i>
|
||||
</button>
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
${iconSpecialClass === 'image-icon' ? `<img class="file-thumb" src="/api/files/${file.id}/thumbnail/icon" loading="lazy" alt="" onerror="this.style.display='none'">` : ''}
|
||||
${iconSpecialClass === 'image-icon' ? `<img class="file-thumb" src="/api/files/${file.id}/thumbnail/icon" loading="lazy" alt="">` : ''}
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${escapeHtml(file.name)}</div>
|
||||
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
|
||||
`;
|
||||
var thumb = el.querySelector('.file-thumb');
|
||||
if (thumb) thumb.addEventListener('error', function() { this.style.display = 'none'; });
|
||||
this._bindStarClick(el);
|
||||
return el;
|
||||
},
|
||||
@@ -1181,7 +1183,7 @@ const ui = {
|
||||
<div class="list-item-checkbox"><input type="checkbox" class="item-checkbox"></div>
|
||||
<div class="name-cell">
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
${iconSpecialClass === 'image-icon' ? `<img class="file-thumb" src="/api/files/${file.id}/thumbnail/icon" loading="lazy" alt="" onerror="this.style.display='none'">` : ''}
|
||||
${iconSpecialClass === 'image-icon' ? `<img class="file-thumb" src="/api/files/${file.id}/thumbnail/icon" loading="lazy" alt="">` : ''}
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${escapeHtml(file.name)}</span>
|
||||
@@ -1191,6 +1193,8 @@ const ui = {
|
||||
<div class="size-cell">${fileSize}</div>
|
||||
<div class="date-cell">${formattedDate}</div>
|
||||
`;
|
||||
var thumb = el.querySelector('.file-thumb');
|
||||
if (thumb) thumb.addEventListener('error', function() { this.style.display = 'none'; });
|
||||
return el;
|
||||
},
|
||||
|
||||
|
||||
+21
-18
@@ -182,32 +182,35 @@ function showUserProfileModal() {
|
||||
overlay.id = 'profile-modal-overlay';
|
||||
overlay.className = 'about-modal-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="about-modal" style="max-width:380px">
|
||||
<div style="text-align:center;padding:20px 20px 0">
|
||||
<div style="width:64px;height:64px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#6366f1);color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;margin-bottom:12px">${initials}</div>
|
||||
<h3 style="margin:0;font-size:18px;color:#1a1a2e">${username}</h3>
|
||||
<p style="margin:4px 0 0;font-size:13px;color:#64748b">${email}</p>
|
||||
<span style="display:inline-block;margin-top:8px;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:600;${
|
||||
role === 'admin'
|
||||
? 'background:#dbeafe;color:#1d4ed8'
|
||||
: 'background:#f1f5f9;color:#64748b'
|
||||
}">${role === 'admin' ? '🛡️ Admin' : '👤 ' + t('user_menu.role_user', 'User')}</span>
|
||||
<div class="about-modal about-modal-body">
|
||||
<div class="about-modal-header">
|
||||
<div class="about-modal-avatar">${initials}</div>
|
||||
<h3 class="about-modal-username">${username}</h3>
|
||||
<p class="about-modal-email">${email}</p>
|
||||
<span class="about-modal-role ${role === 'admin' ? 'about-modal-role-admin' : 'about-modal-role-user'}">${role === 'admin' ? '🛡️ Admin' : '👤 ' + t('user_menu.role_user', 'User')}</span>
|
||||
</div>
|
||||
<div style="padding:16px 20px">
|
||||
<div style="font-size:12px;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">
|
||||
<i class="fas fa-database" style="margin-right:4px"></i>${t('storage.title', 'Storage')}
|
||||
<div class="about-modal-storage">
|
||||
<div class="about-modal-storage-label">
|
||||
<i class="fas fa-database"></i>${t('storage.title', 'Storage')}
|
||||
</div>
|
||||
<div style="background:#f1f5f9;border-radius:6px;height:8px;overflow:hidden;margin-bottom:4px">
|
||||
<div style="height:100%;width:${percentage}%;background:${barColor};border-radius:6px;transition:width .3s"></div>
|
||||
<div class="about-modal-bar-bg">
|
||||
<div class="about-modal-bar-fill" id="about-bar-fill"></div>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#64748b;text-align:right">${percentage}% · ${window.formatFileSize(usedBytes)} / ${window.formatQuotaSize(quotaBytes)}</div>
|
||||
<div class="about-modal-bar-text">${percentage}% · ${window.formatFileSize(usedBytes)} / ${window.formatQuotaSize(quotaBytes)}</div>
|
||||
</div>
|
||||
<div style="padding:0 20px 16px;display:flex;justify-content:center">
|
||||
<button id="profile-modal-close" style="padding:8px 24px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;color:#334155;font-size:13px;font-weight:600;cursor:pointer;transition:background .15s">${t('actions.close', 'Close')}</button>
|
||||
<div class="about-modal-footer">
|
||||
<button id="profile-modal-close" class="about-modal-close-btn">${t('actions.close', 'Close')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Set dynamic bar width and color via JS property (CSP-safe)
|
||||
const barFill = overlay.querySelector('#about-bar-fill');
|
||||
if (barFill) {
|
||||
barFill.style.width = percentage + '%';
|
||||
barFill.style.background = barColor;
|
||||
}
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
requestAnimationFrame(() => overlay.classList.add('show'));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user