diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index 208e2f54..3ce51e28 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -86,279 +86,296 @@ } @code { - private string _id; - private List _folders; - private List _files = new List(); - private string _fileinputid = string.Empty; - private string _progressinfoid = string.Empty; - private string _progressbarid = string.Empty; - private string _filter = "*"; - private bool _haseditpermission = false; - private string _image = string.Empty; - private File _file = null; - private string _guid; - private string _message = string.Empty; - private MessageType _messagetype; + private string _id; + private List _folders; + private List _files = new List(); + private string _fileinputid = string.Empty; + private string _progressinfoid = string.Empty; + private string _progressbarid = string.Empty; + private string _filter = "*"; + private bool _haseditpermission = false; + private string _image = string.Empty; + private File _file = null; + private string _guid; + private string _message = string.Empty; + private MessageType _messagetype; - [Parameter] - public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility + [Parameter] + public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility - [Parameter] - public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid + [Parameter] + public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid - [Parameter] - public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path + [Parameter] + public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path - [Parameter] - public int FileId { get; set; } = -1; // optional - for selecting a specific file by default + [Parameter] + public int FileId { get; set; } = -1; // optional - for selecting a specific file by default - [Parameter] - public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif" + [Parameter] + public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif" - [Parameter] - public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true + [Parameter] + public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true - [Parameter] - public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true + [Parameter] + public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true - [Parameter] - public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true + [Parameter] + public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true - [Parameter] - public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true + [Parameter] + public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true - [Parameter] - public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false + [Parameter] + public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false - [Parameter] - public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false + [Parameter] + public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false - [Parameter] - public EventCallback OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded + [Parameter] + public EventCallback OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded - [Parameter] - public EventCallback OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected + [Parameter] + public EventCallback OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected - [Parameter] - public EventCallback OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted + [Parameter] + public EventCallback OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted - protected override async Task OnInitializedAsync() - { - if (!string.IsNullOrEmpty(Id)) - { - _id = Id; - } + protected override async Task OnInitializedAsync() + { + if (!string.IsNullOrEmpty(Id)) + { + _id = Id; + } - // packages folder is a framework folder for uploading installable nuget packages - if (Folder == Constants.PackagesFolder) - { - ShowFiles = false; - ShowFolders = false; - Filter = "nupkg"; - ShowSuccess = true; - } + // packages folder is a framework folder for uploading installable nuget packages + if (Folder == Constants.PackagesFolder) + { + ShowFiles = false; + ShowFolders = false; + Filter = "nupkg"; + ShowSuccess = true; + } - if (!ShowFiles) - { - ShowImage = false; - } + if (!ShowFiles) + { + ShowImage = false; + } - _folders = await FolderService.GetFoldersAsync(ModuleState.SiteId); + _folders = await FolderService.GetFoldersAsync(ModuleState.SiteId); - if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder) - { - Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder); - if (folder != null) - { - FolderId = folder.FolderId; - } - else - { - FolderId = -1; - _message = "Folder Path " + Folder + "Does Not Exist"; - _messagetype = MessageType.Error; - } - } + if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder) + { + Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder); + if (folder != null) + { + FolderId = folder.FolderId; + } + else + { + FolderId = -1; + _message = "Folder Path " + Folder + "Does Not Exist"; + _messagetype = MessageType.Error; + } + } - if (FileId != -1) - { - File file = await FileService.GetFileAsync(FileId); - if (file != null) - { - FolderId = file.FolderId; - await OnSelect.InvokeAsync(FileId); - } - else - { - FileId = -1; // file does not exist - _message = "FileId " + FileId.ToString() + "Does Not Exist"; - _messagetype = MessageType.Error; - } - } + if (FileId != -1) + { + File file = await FileService.GetFileAsync(FileId); + if (file != null) + { + FolderId = file.FolderId; + await OnSelect.InvokeAsync(FileId); + } + else + { + FileId = -1; // file does not exist + _message = "FileId " + FileId.ToString() + "Does Not Exist"; + _messagetype = MessageType.Error; + } + } - await SetImage(); + await SetImage(); - if (!string.IsNullOrEmpty(Filter)) - { - _filter = "." + Filter.Replace(",", ",."); - } + if (!string.IsNullOrEmpty(Filter)) + { + _filter = "." + Filter.Replace(",", ",."); + } - await GetFiles(); + await GetFiles(); - // create unique id for component - _guid = Guid.NewGuid().ToString("N"); - _fileinputid = _guid + "FileInput"; - _progressinfoid = _guid + "ProgressInfo"; - _progressbarid = _guid + "ProgressBar"; - } + // create unique id for component + _guid = Guid.NewGuid().ToString("N"); + _fileinputid = _guid + "FileInput"; + _progressinfoid = _guid + "ProgressInfo"; + _progressbarid = _guid + "ProgressBar"; + } - private async Task GetFiles() - { - _haseditpermission = false; - if (Folder == Constants.PackagesFolder) - { - _haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); - _files = new List(); - } - else - { - Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId); - if (folder != null) - { - _haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions); - _files = await FileService.GetFilesAsync(FolderId); - } - else - { - _haseditpermission = false; - _files = new List(); - } - } - if (_filter != "*") - { - List filtered = new List(); - foreach (File file in _files) - { - if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1) - { - filtered.Add(file); - } - } - _files = filtered; - } - } + private async Task GetFiles() + { + _haseditpermission = false; + if (Folder == Constants.PackagesFolder) + { + _haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); + _files = new List(); + } + else + { + Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId); + if (folder != null) + { + _haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions); + _files = await FileService.GetFilesAsync(FolderId); + } + else + { + _haseditpermission = false; + _files = new List(); + } + } + if (_filter != "*") + { + List filtered = new List(); + foreach (File file in _files) + { + if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1) + { + filtered.Add(file); + } + } + _files = filtered; + } + } - private async Task FolderChanged(ChangeEventArgs e) - { - _message = string.Empty; - try - { - FolderId = int.Parse((string)e.Value); - await GetFiles(); - FileId = -1; - _file = null; - _image = string.Empty; - StateHasChanged(); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); - _message = Localizer["Error.File.Load"]; - _messagetype = MessageType.Error; - } - } + private async Task FolderChanged(ChangeEventArgs e) + { + _message = string.Empty; + try + { + FolderId = int.Parse((string)e.Value); + await GetFiles(); + FileId = -1; + _file = null; + _image = string.Empty; + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); + _message = Localizer["Error.File.Load"]; + _messagetype = MessageType.Error; + } + } - private async Task FileChanged(ChangeEventArgs e) - { - _message = string.Empty; - FileId = int.Parse((string)e.Value); - if (FileId != -1) - { - await OnSelect.InvokeAsync(FileId); - } + private async Task FileChanged(ChangeEventArgs e) + { + _message = string.Empty; + FileId = int.Parse((string)e.Value); + if (FileId != -1) + { + await OnSelect.InvokeAsync(FileId); + } - await SetImage(); - StateHasChanged(); - } + await SetImage(); + StateHasChanged(); + } - private async Task SetImage() - { - _image = string.Empty; - _file = null; - if (FileId != -1) - { - _file = await FileService.GetFileAsync(FileId); - if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0) - { - var maxwidth = 200; - var maxheight = 200; + private async Task SetImage() + { + _image = string.Empty; + _file = null; + if (FileId != -1) + { + _file = await FileService.GetFileAsync(FileId); + if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0) + { + var maxwidth = 200; + var maxheight = 200; - var ratioX = (double)maxwidth / (double)_file.ImageWidth; - var ratioY = (double)maxheight / (double)_file.ImageHeight; - var ratio = ratioX < ratioY ? ratioX : ratioY; + var ratioX = (double)maxwidth / (double)_file.ImageWidth; + var ratioY = (double)maxheight / (double)_file.ImageHeight; + var ratio = ratioX < ratioY ? ratioX : ratioY; - _image = "\"""; - } - } - } + _image = "\"""; + } + } + } - private async Task UploadFile() - { - _message = string.Empty; - var interop = new Interop(JSRuntime); - var upload = await interop.GetFiles(_fileinputid); - if (upload.Length > 0) - { - try - { - string result; - if (Folder == Constants.PackagesFolder) - { - result = await FileService.UploadFilesAsync(Folder, upload, _guid); - } - else - { - result = await FileService.UploadFilesAsync(FolderId, upload, _guid); - } + private async Task UploadFile() + { + _message = string.Empty; + var interop = new Interop(JSRuntime); + var upload = await interop.GetFiles(_fileinputid); + if (upload.Length > 0) + { + string restricted = ""; + foreach (var file in upload) + { + var extension = (file.LastIndexOf(".") != -1) ? file.Substring(file.LastIndexOf(".") + 1) : ""; + if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) + { + restricted += (restricted == "" ? "" : ",") + extension; + } + } + if (restricted == "") + { + try + { + string result; + if (Folder == Constants.PackagesFolder) + { + result = await FileService.UploadFilesAsync(Folder, upload, _guid); + } + else + { + result = await FileService.UploadFilesAsync(FolderId, upload, _guid); + } - if (result == string.Empty) - { - await logger.LogInformation("File Upload Succeeded {Files}", upload); - if (ShowSuccess) - { - _message = Localizer["Success.File.Upload"]; - _messagetype = MessageType.Success; - } + if (result == string.Empty) + { + await logger.LogInformation("File Upload Succeeded {Files}", upload); + if (ShowSuccess) + { + _message = Localizer["Success.File.Upload"]; + _messagetype = MessageType.Success; + } - // set FileId to first file in upload collection - await GetFiles(); - var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault(); - if (file != null) - { - FileId = file.FileId; - await SetImage(); - await OnUpload.InvokeAsync(FileId); - } - StateHasChanged(); - } - else - { - await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", ")); + // set FileId to first file in upload collection + await GetFiles(); + var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault(); + if (file != null) + { + FileId = file.FileId; + await SetImage(); + await OnUpload.InvokeAsync(FileId); + } + StateHasChanged(); + } + else + { + await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", ")); - _message = Localizer["Error.File.Upload"]; - _messagetype = MessageType.Error; - } - } - catch (Exception ex) - { - await logger.LogError(ex, "File Upload Failed {Error}", ex.Message); + _message = Localizer["Error.File.Upload"]; + _messagetype = MessageType.Error; + } + } + catch (Exception ex) + { + await logger.LogError(ex, "File Upload Failed {Error}", ex.Message); - _message = Localizer["Error.File.Upload"]; - _messagetype = MessageType.Error; - } - } + _message = Localizer["Error.File.Upload"]; + _messagetype = MessageType.Error; + } + } + else + { + _message = string.Format(Localizer["Message.File.Restricted"], restricted); + _messagetype = MessageType.Warning; + } + } else { _message = Localizer["Message.File.NotSelected"]; diff --git a/Oqtane.Client/Resources/Modules/Controls/FileManager.resx b/Oqtane.Client/Resources/Modules/Controls/FileManager.resx index 4fcd3641..c2ccac05 100644 --- a/Oqtane.Client/Resources/Modules/Controls/FileManager.resx +++ b/Oqtane.Client/Resources/Modules/Controls/FileManager.resx @@ -141,4 +141,7 @@ File Upload Succeeded + + Files With Extension Of {0} Are Restricted From Upload. Please Contact Your Administrator For More Information. + \ No newline at end of file diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index e13f770c..eca5125f 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -276,9 +276,17 @@ namespace Oqtane.Controllers return; } - if (!formfile.FileName.IsPathOrFileValid()) + // ensure filename is valid + string token = ".part_"; + if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token)) + { + return; + } + + // check for allowable file extensions (ignore token) + var extension = Path.GetExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))).Replace(".", ""); + if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) { - HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict; return; } @@ -331,9 +339,9 @@ namespace Oqtane.Controllers { string merged = ""; - // parse the filename which is in the format of filename.ext.part_x_y + // parse the filename which is in the format of filename.ext.part_001_999 string token = ".part_"; - string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "x_y" + string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999" int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1)); filename = Path.GetFileNameWithoutExtension(filename); // base filename @@ -370,23 +378,15 @@ namespace Oqtane.Controllers System.IO.File.Delete(filepart); } - // check for allowable file extensions - if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename)?.ToLower().Replace(".", ""))) + // remove file if it already exists + if (System.IO.File.Exists(Path.Combine(folder, filename))) { - System.IO.File.Delete(Path.Combine(folder, filename + ".tmp")); + System.IO.File.Delete(Path.Combine(folder, filename)); } - else - { - // remove file if it already exists - if (System.IO.File.Exists(Path.Combine(folder, filename))) - { - System.IO.File.Delete(Path.Combine(folder, filename)); - } - // rename file now that the entire process is completed - System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename)); - _logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folder, filename)); - } + // rename file now that the entire process is completed + System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename)); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folder, filename)); merged = filename; } @@ -394,8 +394,7 @@ namespace Oqtane.Controllers // clean up file parts which are more than 2 hours old ( which can happen if a prior file upload failed ) var cleanupFiles = Directory.EnumerateFiles(folder, "*" + token + "*") - .Where(f => Path.GetExtension(f).StartsWith(token)); - + .Where(f => Path.GetExtension(f).StartsWith(token) && !Path.GetFileName(f).StartsWith(filename)); foreach (var file in cleanupFiles) { var createdDate = System.IO.File.GetCreationTime(file).ToUniversalTime(); diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index d65abfa5..b62c006c 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -344,10 +344,17 @@ Oqtane.Interop = { progressinfo.innerHTML = file.name + ' 100%'; progressbar.value = 1; }; + request.upload.onerror = function () { + progressinfo.innerHTML = file.name + ' Error: ' + xhr.status; + progressbar.value = 0; + }; request.send(data); } + + if (i === files.length - 1) { + fileinput.value = ''; + } } - fileinput.value = ''; }, refreshBrowser: function (reload, wait) { setInterval(function () {