Allow any file to be uploaded (#84)

## Summary

- Allow any file to be uploaded
- Removes .epub restriction

## Additional Context

- Fixes #74
This commit is contained in:
Dave Allie
2025-12-21 15:43:17 +11:00
committed by GitHub
parent 926c786705
commit 9b4dfbd180
2 changed files with 39 additions and 64 deletions

View File

@@ -513,13 +513,6 @@ void CrossPointWebServer::handleUpload() {
Serial.printf("[%lu] [WEB] [UPLOAD] START: %s to path: %s\n", millis(), uploadFileName.c_str(), uploadPath.c_str()); Serial.printf("[%lu] [WEB] [UPLOAD] START: %s to path: %s\n", millis(), uploadFileName.c_str(), uploadPath.c_str());
Serial.printf("[%lu] [WEB] [UPLOAD] Free heap: %d bytes\n", millis(), ESP.getFreeHeap()); Serial.printf("[%lu] [WEB] [UPLOAD] Free heap: %d bytes\n", millis(), ESP.getFreeHeap());
// Validate file extension
if (!isEpubFile(uploadFileName)) {
uploadError = "Only .epub files are allowed";
Serial.printf("[%lu] [WEB] [UPLOAD] REJECTED - not an epub file\n", millis());
return;
}
// Create file path // Create file path
String filePath = uploadPath; String filePath = uploadPath;
if (!filePath.endsWith("/")) filePath += "/"; if (!filePath.endsWith("/")) filePath += "/";

View File

@@ -3,15 +3,15 @@
CrossPoint E-Reader • Open Source CrossPoint E-Reader • Open Source
</p> </p>
</div> </div>
<!-- Upload Modal --> <!-- Upload Modal -->
<div class="modal-overlay" id="uploadModal"> <div class="modal-overlay" id="uploadModal">
<div class="modal"> <div class="modal">
<button class="modal-close" onclick="closeUploadModal()">&times;</button> <button class="modal-close" onclick="closeUploadModal()">&times;</button>
<h3>📤 Upload eBook</h3> <h3>📤 Upload file</h3>
<div class="upload-form"> <div class="upload-form">
<p class="file-info">Select an .epub file to upload to <strong id="uploadPathDisplay"></strong></p> <p class="file-info">Select a file to upload to <strong id="uploadPathDisplay"></strong></p>
<input type="file" id="fileInput" accept=".epub" onchange="validateFile()"> <input type="file" id="fileInput" onchange="validateFile()">
<button id="uploadBtn" class="upload-btn" onclick="uploadFile()" disabled>Upload</button> <button id="uploadBtn" class="upload-btn" onclick="uploadFile()" disabled>Upload</button>
<div id="progress-container"> <div id="progress-container">
<div id="progress-bar"><div id="progress-fill"></div></div> <div id="progress-bar"><div id="progress-fill"></div></div>
@@ -20,7 +20,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- New Folder Modal --> <!-- New Folder Modal -->
<div class="modal-overlay" id="folderModal"> <div class="modal-overlay" id="folderModal">
<div class="modal"> <div class="modal">
@@ -33,7 +33,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Delete Confirmation Modal --> <!-- Delete Confirmation Modal -->
<div class="modal-overlay" id="deleteModal"> <div class="modal-overlay" id="deleteModal">
<div class="modal"> <div class="modal">
@@ -50,7 +50,7 @@
</div> </div>
</div> </div>
</div> </div>
<script> <script>
// Modal functions // Modal functions
function openUploadModal() { function openUploadModal() {
@@ -58,7 +58,7 @@
document.getElementById('uploadPathDisplay').textContent = currentPath === '/' ? '/ 🏠' : currentPath; document.getElementById('uploadPathDisplay').textContent = currentPath === '/' ? '/ 🏠' : currentPath;
document.getElementById('uploadModal').classList.add('open'); document.getElementById('uploadModal').classList.add('open');
} }
function closeUploadModal() { function closeUploadModal() {
document.getElementById('uploadModal').classList.remove('open'); document.getElementById('uploadModal').classList.remove('open');
document.getElementById('fileInput').value = ''; document.getElementById('fileInput').value = '';
@@ -67,18 +67,18 @@
document.getElementById('progress-fill').style.width = '0%'; document.getElementById('progress-fill').style.width = '0%';
document.getElementById('progress-fill').style.backgroundColor = '#27ae60'; document.getElementById('progress-fill').style.backgroundColor = '#27ae60';
} }
function openFolderModal() { function openFolderModal() {
const currentPath = document.getElementById('currentPath').value; const currentPath = document.getElementById('currentPath').value;
document.getElementById('folderPathDisplay').textContent = currentPath === '/' ? '/ 🏠' : currentPath; document.getElementById('folderPathDisplay').textContent = currentPath === '/' ? '/ 🏠' : currentPath;
document.getElementById('folderModal').classList.add('open'); document.getElementById('folderModal').classList.add('open');
document.getElementById('folderName').value = ''; document.getElementById('folderName').value = '';
} }
function closeFolderModal() { function closeFolderModal() {
document.getElementById('folderModal').classList.remove('open'); document.getElementById('folderModal').classList.remove('open');
} }
// Close modals when clicking overlay // Close modals when clicking overlay
document.querySelectorAll('.modal-overlay').forEach(function(overlay) { document.querySelectorAll('.modal-overlay').forEach(function(overlay) {
overlay.addEventListener('click', function(e) { overlay.addEventListener('click', function(e) {
@@ -87,58 +87,40 @@
} }
}); });
}); });
function validateFile() { function validateFile() {
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn'); const uploadBtn = document.getElementById('uploadBtn');
const file = fileInput.files[0]; const file = fileInput.files[0];
uploadBtn.disabled = !file;
if (file) {
const fileName = file.name.toLowerCase();
if (!fileName.endsWith('.epub')) {
alert('Only .epub files are allowed!');
fileInput.value = '';
uploadBtn.disabled = true;
return;
}
uploadBtn.disabled = false;
} else {
uploadBtn.disabled = true;
}
} }
function uploadFile() { function uploadFile() {
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0]; const file = fileInput.files[0];
const currentPath = document.getElementById('currentPath').value; const currentPath = document.getElementById('currentPath').value;
if (!file) { if (!file) {
alert('Please select a file first!'); alert('Please select a file first!');
return; return;
} }
const fileName = file.name.toLowerCase();
if (!fileName.endsWith('.epub')) {
alert('Only .epub files are allowed!');
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
const progressContainer = document.getElementById('progress-container'); const progressContainer = document.getElementById('progress-container');
const progressFill = document.getElementById('progress-fill'); const progressFill = document.getElementById('progress-fill');
const progressText = document.getElementById('progress-text'); const progressText = document.getElementById('progress-text');
const uploadBtn = document.getElementById('uploadBtn'); const uploadBtn = document.getElementById('uploadBtn');
progressContainer.style.display = 'block'; progressContainer.style.display = 'block';
uploadBtn.disabled = true; uploadBtn.disabled = true;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
// Include path as query parameter since multipart form data doesn't make // Include path as query parameter since multipart form data doesn't make
// form fields available until after file upload completes // form fields available until after file upload completes
xhr.open('POST', '/upload?path=' + encodeURIComponent(currentPath), true); xhr.open('POST', '/upload?path=' + encodeURIComponent(currentPath), true);
xhr.upload.onprogress = function(e) { xhr.upload.onprogress = function(e) {
if (e.lengthComputable) { if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100); const percent = Math.round((e.loaded / e.total) * 100);
@@ -146,7 +128,7 @@
progressText.textContent = 'Uploading: ' + percent + '%'; progressText.textContent = 'Uploading: ' + percent + '%';
} }
}; };
xhr.onload = function() { xhr.onload = function() {
if (xhr.status === 200) { if (xhr.status === 200) {
progressText.textContent = 'Upload complete!'; progressText.textContent = 'Upload complete!';
@@ -159,39 +141,39 @@
uploadBtn.disabled = false; uploadBtn.disabled = false;
} }
}; };
xhr.onerror = function() { xhr.onerror = function() {
progressText.textContent = 'Upload failed - network error'; progressText.textContent = 'Upload failed - network error';
progressFill.style.backgroundColor = '#e74c3c'; progressFill.style.backgroundColor = '#e74c3c';
uploadBtn.disabled = false; uploadBtn.disabled = false;
}; };
xhr.send(formData); xhr.send(formData);
} }
function createFolder() { function createFolder() {
const folderName = document.getElementById('folderName').value.trim(); const folderName = document.getElementById('folderName').value.trim();
const currentPath = document.getElementById('currentPath').value; const currentPath = document.getElementById('currentPath').value;
if (!folderName) { if (!folderName) {
alert('Please enter a folder name!'); alert('Please enter a folder name!');
return; return;
} }
// Validate folder name (no special characters except underscore and hyphen) // Validate folder name (no special characters except underscore and hyphen)
const validName = /^[a-zA-Z0-9_\-]+$/.test(folderName); const validName = /^[a-zA-Z0-9_\-]+$/.test(folderName);
if (!validName) { if (!validName) {
alert('Folder name can only contain letters, numbers, underscores, and hyphens.'); alert('Folder name can only contain letters, numbers, underscores, and hyphens.');
return; return;
} }
const formData = new FormData(); const formData = new FormData();
formData.append('name', folderName); formData.append('name', folderName);
formData.append('path', currentPath); formData.append('path', currentPath);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/mkdir', true); xhr.open('POST', '/mkdir', true);
xhr.onload = function() { xhr.onload = function() {
if (xhr.status === 200) { if (xhr.status === 200) {
window.location.reload(); window.location.reload();
@@ -199,14 +181,14 @@
alert('Failed to create folder: ' + xhr.responseText); alert('Failed to create folder: ' + xhr.responseText);
} }
}; };
xhr.onerror = function() { xhr.onerror = function() {
alert('Failed to create folder - network error'); alert('Failed to create folder - network error');
}; };
xhr.send(formData); xhr.send(formData);
} }
// Delete functions // Delete functions
function openDeleteModal(name, path, isFolder) { function openDeleteModal(name, path, isFolder) {
document.getElementById('deleteItemName').textContent = (isFolder ? '📁 ' : '📄 ') + name; document.getElementById('deleteItemName').textContent = (isFolder ? '📁 ' : '📄 ') + name;
@@ -214,22 +196,22 @@
document.getElementById('deleteItemType').value = isFolder ? 'folder' : 'file'; document.getElementById('deleteItemType').value = isFolder ? 'folder' : 'file';
document.getElementById('deleteModal').classList.add('open'); document.getElementById('deleteModal').classList.add('open');
} }
function closeDeleteModal() { function closeDeleteModal() {
document.getElementById('deleteModal').classList.remove('open'); document.getElementById('deleteModal').classList.remove('open');
} }
function confirmDelete() { function confirmDelete() {
const path = document.getElementById('deleteItemPath').value; const path = document.getElementById('deleteItemPath').value;
const itemType = document.getElementById('deleteItemType').value; const itemType = document.getElementById('deleteItemType').value;
const formData = new FormData(); const formData = new FormData();
formData.append('path', path); formData.append('path', path);
formData.append('type', itemType); formData.append('type', itemType);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/delete', true); xhr.open('POST', '/delete', true);
xhr.onload = function() { xhr.onload = function() {
if (xhr.status === 200) { if (xhr.status === 200) {
window.location.reload(); window.location.reload();
@@ -238,12 +220,12 @@
closeDeleteModal(); closeDeleteModal();
} }
}; };
xhr.onerror = function() { xhr.onerror = function() {
alert('Failed to delete - network error'); alert('Failed to delete - network error');
closeDeleteModal(); closeDeleteModal();
}; };
xhr.send(formData); xhr.send(formData);
} }
</script> </script>