improve file upload validation and error handling on server

This commit is contained in:
sbwalker 2025-02-06 08:19:57 -05:00
parent dec0c0649c
commit 8c83a18f93

View File

@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Cors;
using System.IO.Compression; using System.IO.Compression;
using Oqtane.Services; using Oqtane.Services;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Http.HttpResults;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -430,80 +431,96 @@ namespace Oqtane.Controllers
[HttpPost("upload")] [HttpPost("upload")]
public async Task<IActionResult> UploadFile([FromForm] string folder, IFormFile formfile) public async Task<IActionResult> UploadFile([FromForm] string folder, IFormFile formfile)
{ {
if (string.IsNullOrEmpty(folder))
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A Folder");
return StatusCode((int)HttpStatusCode.Forbidden);
}
if (formfile == null || formfile.Length <= 0) if (formfile == null || formfile.Length <= 0)
{ {
return NoContent(); _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A File");
return StatusCode((int)HttpStatusCode.Forbidden);
} }
// ensure filename is valid // ensure filename is valid
if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName)) if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName))
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName); _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
return NoContent(); return StatusCode((int)HttpStatusCode.Forbidden);
} }
// ensure headers exist // ensure headers exist
if (!Request.Headers.TryGetValue("PartCount", out StringValues partCount) || !Request.Headers.TryGetValue("TotalParts", out StringValues totalParts)) if (!Request.Headers.TryGetValue("PartCount", out StringValues partcount) || !int.TryParse(partcount, out int partCount) || partCount <= 0 ||
!Request.Headers.TryGetValue("TotalParts", out StringValues totalparts) || !int.TryParse(totalparts, out int totalParts) || totalParts <= 0)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Request Is Missing Required Headers"); _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Is Missing Required Headers");
return NoContent(); return StatusCode((int)HttpStatusCode.Forbidden);
} }
string fileName = formfile.FileName + ".part_" + int.Parse(partCount).ToString("000") + "_" + int.Parse(totalParts).ToString("000"); // create file name using header values
string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
string folderPath = ""; string folderPath = "";
int FolderId; try
if (int.TryParse(folder, out FolderId))
{ {
Folder Folder = _folders.GetFolder(FolderId); int FolderId;
if (Folder != null && Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, Folder.PermissionList)) if (int.TryParse(folder, out FolderId))
{ {
folderPath = _folders.GetFolderPath(Folder); Folder Folder = _folders.GetFolder(FolderId);
} if (Folder != null && Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, Folder.PermissionList))
}
else
{
FolderId = -1;
if (User.IsInRole(RoleNames.Host))
{
folderPath = GetFolderPath(folder);
}
}
if (!string.IsNullOrEmpty(folderPath))
{
CreateDirectory(folderPath);
using (var stream = new FileStream(Path.Combine(folderPath, fileName), FileMode.Create))
{
await formfile.CopyToAsync(stream);
}
string upload = await MergeFile(folderPath, fileName);
if (upload != "" && FolderId != -1)
{
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
if (file != null)
{ {
if (file.FileId == 0) folderPath = _folders.GetFolderPath(Folder);
{ }
file = _files.AddFile(file); }
} else
else {
{ FolderId = -1;
file = _files.UpdateFile(file); if (User.IsInRole(RoleNames.Host))
} {
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload)); folderPath = GetFolderPath(folder);
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
} }
} }
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return NoContent(); if (!string.IsNullOrEmpty(folderPath))
{
CreateDirectory(folderPath);
using (var stream = new FileStream(Path.Combine(folderPath, fileName), FileMode.Create))
{
await formfile.CopyToAsync(stream);
}
string upload = await MergeFile(folderPath, fileName);
if (upload != "" && FolderId != -1)
{
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
if (file != null)
{
if (file.FileId == 0)
{
file = _files.AddFile(file);
}
else
{
file = _files.UpdateFile(file);
}
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload));
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
}
}
return NoContent();
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
return StatusCode((int)HttpStatusCode.Forbidden);
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Upload Attempt Failed {Folder} {File}", folder, formfile.FileName);
return StatusCode((int)HttpStatusCode.InternalServerError);
}
} }
private async Task<string> MergeFile(string folder, string filename) private async Task<string> MergeFile(string folder, string filename)