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:
Jared Wolff
2026-03-05 16:18:30 -05:00
parent f2d35ca792
commit c08926b817
28 changed files with 397 additions and 212 deletions
+4 -4
View File
@@ -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>
+2 -2
View File
@@ -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>
`;
+1 -1
View File
@@ -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>
`;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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'));