fix: preserve original file type on upload — never convert PNG to JPG
CI/CD / CI · dotnet build (push) Successful in 53s
CI/CD / Deploy · drsousan (push) Successful in 28s

Problem: cropper always called out.toBlob(..., 'image/jpeg') regardless
of the original file type, silently converting PNGs to JPGs.

Fix:
- openCropper() now stores file.type and file.name on the cropper object
- applyCrop() uses the stored mime type for toBlob() and the filename
- Quality param only passed for lossy formats (jpeg/webp), not for PNG/GIF
- uploadImage() accept list expanded: svg, ico allowed
- Server-side: .svg and .ico added to allowed extensions

Result: PNG stays PNG, WebP stays WebP, ICO stays ICO.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-02 18:06:38 +03:30
parent e79ccf7e8c
commit 5d6a4a630d
2 changed files with 11 additions and 4 deletions
+1 -1
View File
@@ -589,7 +589,7 @@ app.MapPost("/api/upload", async (HttpRequest request, IWebHostEnvironment env)
if (!request.HasFormContentType || !request.Form.Files.Any()) if (!request.HasFormContentType || !request.Form.Files.Any())
return Results.BadRequest("No file provided."); return Results.BadRequest("No file provided.");
var file = request.Form.Files[0]; var file = request.Form.Files[0];
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif" }; var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".ico" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowed.Contains(ext)) return Results.BadRequest("File type not allowed."); if (!allowed.Contains(ext)) return Results.BadRequest("File type not allowed.");
var uploadsDir = Path.Combine(env.WebRootPath, "uploads"); var uploadsDir = Path.Combine(env.WebRootPath, "uploads");
+10 -3
View File
@@ -1690,6 +1690,9 @@ const cropper = {
function openCropper(inputId, previewId, file) { function openCropper(inputId, previewId, file) {
cropper.inputId = inputId; cropper.previewId = previewId; cropper.inputId = inputId; cropper.previewId = previewId;
// Preserve original file type so PNG stays PNG, WebP stays WebP, etc.
cropper.mimeType = file.type || 'image/jpeg';
cropper.fileName = file.name || 'upload';
const reader = new FileReader(); const reader = new FileReader();
reader.onload = e => { reader.onload = e => {
const img = new Image(); const img = new Image();
@@ -1831,7 +1834,8 @@ async function applyCrop() {
btn.disabled = true; btn.disabled = true;
try { try {
const fd = new FormData(); const fd = new FormData();
fd.append('file', new File([blob], 'crop.jpg', {type:'image/jpeg'})); const ext = _inputId && cropper.mimeType ? cropper.mimeType.split('/')[1].replace('jpeg','jpg') : 'jpg';
fd.append('file', new File([blob], `crop.${ext}`, {type: cropper.mimeType || 'image/jpeg'}));
const r = await fetch('/api/upload', {method:'POST', headers:{'Authorization':`Bearer ${token}`}, body:fd}); const r = await fetch('/api/upload', {method:'POST', headers:{'Authorization':`Bearer ${token}`}, body:fd});
if (!r.ok) throw new Error(await r.text()); if (!r.ok) throw new Error(await r.text());
const { url } = await r.json(); const { url } = await r.json();
@@ -1840,13 +1844,16 @@ async function applyCrop() {
toast('تصویر با موفقیت آپلود شد ✓'); toast('تصویر با موفقیت آپلود شد ✓');
} catch(e) { toast('خطا در آپلود: '+e.message,'error'); } } catch(e) { toast('خطا در آپلود: '+e.message,'error'); }
finally { btn.innerHTML=orig; btn.disabled=false; } finally { btn.innerHTML=orig; btn.disabled=false; }
}, 'image/jpeg', 0.92); // Use original mime type; only pass quality for lossy formats
const mime = cropper.mimeType || 'image/jpeg';
const quality = (mime === 'image/jpeg' || mime === 'image/webp') ? 0.92 : undefined;
}, mime, quality);
} }
async function uploadImage(inputId, previewId) { async function uploadImage(inputId, previewId) {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'image/jpeg,image/png,image/webp,image/gif'; fileInput.accept = 'image/jpeg,image/png,image/webp,image/gif,image/svg+xml,image/x-icon,image/vnd.microsoft.icon';
fileInput.onchange = () => { fileInput.onchange = () => {
const file = fileInput.files[0]; const file = fileInput.files[0];
if (!file) return; if (!file) return;