From 0b86413e6045be10f5f0c4986672547b6d5d001f Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Thu, 18 Apr 2024 21:37:48 +0200 Subject: [PATCH] 6748: Stricter file and folder name validation (#6792) * Media Library: More strict file and folder name validation, fixes #6748 * Resetting MediaLibraryService changes to 1.10.x * Code styling in FileSystemStorageProvider * Adding string file and folder name validation to FileSystemStorageProvider, so that MediaLibrary components don't need to do it separately * Applying the same file and folder name validation to AzureFileSystem too * Code styling and fixes in AzureFileSystem, MediaLibrary and IStorageProvider * Simplifying invalid character detection * Code styling * Adding InvalidNameCharacterException to be able to handle invalid characters precisely at various user-facing components * Updating MediaLibrary not to log an error when a file can't be uploaded due to invalid characters --------- Co-authored-by: Lombiq --- .../Services/FileSystems/AzureFileSystem.cs | 43 ++++++++++---- .../Controllers/ClientStorageController.cs | 28 +++++---- .../Controllers/FolderController.cs | 44 +++++++------- .../MediaFileName/MediaFileNameDriver.cs | 16 +++-- .../Services/MediaLibraryService.cs | 21 +++---- .../Exceptions/DefaultExceptionPolicy.cs | 4 +- src/Orchard/Exceptions/ExceptionExtensions.cs | 9 +-- .../Media/FileSystemStorageProvider.cs | 59 +++++++++++++++---- .../FileSystems/Media/IStorageProvider.cs | 2 +- .../Media/InvalidNameCharacterException.cs | 7 +++ .../Media/StorageProviderExtensions.cs | 4 -- src/Orchard/Orchard.Framework.csproj | 2 +- 12 files changed, 148 insertions(+), 91 deletions(-) create mode 100644 src/Orchard/FileSystems/Media/InvalidNameCharacterException.cs diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/AzureFileSystem.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/AzureFileSystem.cs index a0470c2b83a..314b984d4bc 100644 --- a/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/AzureFileSystem.cs +++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/AzureFileSystem.cs @@ -89,6 +89,8 @@ private static string ConvertToRelativeUriPath(string path) { return newPath; } + private static string GetFolderName(string path) => path.Substring(path.LastIndexOf('/') + 1); + public string Combine(string path1, string path2) { if (path1 == null) { throw new ArgumentNullException("path1"); @@ -141,10 +143,10 @@ public IEnumerable ListFiles(string path) { } return BlobClient.ListBlobs(prefix) - .OfType() - .Where(blobItem => !blobItem.Uri.AbsoluteUri.EndsWith(FolderEntry)) - .Select(blobItem => new AzureBlobFileStorage(blobItem, _absoluteRoot)) - .ToArray(); + .OfType() + .Where(blobItem => !blobItem.Uri.AbsoluteUri.EndsWith(FolderEntry)) + .Select(blobItem => new AzureBlobFileStorage(blobItem, _absoluteRoot)) + .ToArray(); } public IEnumerable ListFolders(string path) { @@ -194,6 +196,11 @@ public bool TryCreateFolder(string path) { public void CreateFolder(string path) { path = ConvertToRelativeUriPath(path); + + if (FileSystemStorageProvider.FolderNameContainsInvalidCharacters(GetFolderName(path))) { + throw new InvalidNameCharacterException("The directory name contains invalid character(s)"); + } + Container.EnsureDirectoryDoesNotExist(String.Concat(_root, path)); // Creating a virtually hidden file to make the directory an existing concept @@ -225,6 +232,10 @@ public void RenameFolder(string path, string newPath) { path = ConvertToRelativeUriPath(path); newPath = ConvertToRelativeUriPath(newPath); + if (FileSystemStorageProvider.FolderNameContainsInvalidCharacters(GetFolderName(newPath))) { + throw new InvalidNameCharacterException("The new directory name contains invalid character(s)"); + } + if (!path.EndsWith("/")) path += "/"; @@ -260,6 +271,10 @@ public void RenameFile(string path, string newPath) { path = ConvertToRelativeUriPath(path); newPath = ConvertToRelativeUriPath(newPath); + if (FileSystemStorageProvider.FileNameContainsInvalidCharacters(Path.GetFileName(newPath))) { + throw new InvalidNameCharacterException("The new file name contains invalid character(s)"); + } + Container.EnsureBlobExists(String.Concat(_root, path)); Container.EnsureBlobDoesNotExist(String.Concat(_root, newPath)); @@ -284,6 +299,10 @@ public void CopyFile(string path, string newPath) { public IStorageFile CreateFile(string path) { path = ConvertToRelativeUriPath(path); + if (FileSystemStorageProvider.FileNameContainsInvalidCharacters(Path.GetFileName(path))) { + throw new InvalidNameCharacterException("The file name contains invalid character(s)"); + } + if (Container.BlobExists(String.Concat(_root, path))) { throw new ArgumentException("File " + path + " already exists"); } @@ -371,10 +390,7 @@ public AzureBlobFolderStorage(CloudBlobDirectory blob, string rootPath) { _rootPath = rootPath; } - public string GetName() { - var path = GetPath(); - return path.Substring(path.LastIndexOf('/') + 1); - } + public string GetName() => GetFolderName(GetPath()); public string GetPath() { return _blob.Uri.ToString().Substring(_rootPath.Length).Trim('/'); @@ -399,11 +415,12 @@ private static long GetDirectorySize(CloudBlobDirectory directoryBlob) { long size = 0; foreach (var blobItem in directoryBlob.ListBlobs()) { - if (blobItem is CloudBlockBlob) - size += ((CloudBlockBlob)blobItem).Properties.Length; - - if (blobItem is CloudBlobDirectory) - size += GetDirectorySize((CloudBlobDirectory)blobItem); + if (blobItem is CloudBlockBlob blob) { + size += blob.Properties.Length; + } + else if (blobItem is CloudBlobDirectory directory) { + size += GetDirectorySize(directory); + } } return size; diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/ClientStorageController.cs b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/ClientStorageController.cs index 8130d5e5183..49c35432332 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/ClientStorageController.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/ClientStorageController.cs @@ -3,15 +3,14 @@ using System.IO; using System.Web.Mvc; using Orchard.ContentManagement; +using Orchard.FileSystems.Media; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.MediaLibrary.Models; using Orchard.MediaLibrary.Services; using Orchard.MediaLibrary.ViewModels; using Orchard.Themes; using Orchard.UI.Admin; -using Orchard.MediaLibrary.Models; -using Orchard.Localization; -using System.Linq; -using Orchard.FileSystems.Media; -using Orchard.Logging; namespace Orchard.MediaLibrary.Controllers { [Admin, Themed(false)] @@ -107,10 +106,16 @@ public ActionResult Upload(string folderPath, string type) { url = mediaPart.FileName, }); } + catch (InvalidNameCharacterException) { + statuses.Add(new { + error = T("The file name contains invalid character(s)").Text, + progress = 1.0, + }); + } catch (Exception ex) { - Logger.Error(ex, "Unexpected exception when uploading a media."); + Logger.Error(ex, T("Unexpected exception when uploading a media.").Text); statuses.Add(new { - error = T(ex.Message).Text, + error = ex.Message, progress = 1.0, }); } @@ -130,7 +135,7 @@ public ActionResult Replace(int replaceId, string type) { return HttpNotFound(); // Check permission - if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.EditMediaContent, replaceMedia.FolderPath) && _mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, replaceMedia.FolderPath)) + if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.EditMediaContent, replaceMedia.FolderPath) && _mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, replaceMedia.FolderPath)) && !_mediaLibraryService.CanManageMediaFolder(replaceMedia.FolderPath)) { return new HttpUnauthorizedResult(); } @@ -138,7 +143,7 @@ public ActionResult Replace(int replaceId, string type) { var statuses = new List(); var settings = Services.WorkContext.CurrentSite.As(); - + // Loop through each file in the request for (int i = 0; i < HttpContext.Request.Files.Count; i++) { // Pointer to file @@ -146,7 +151,8 @@ public ActionResult Replace(int replaceId, string type) { var filename = Path.GetFileName(file.FileName); // if the file has been pasted, provide a default name - if (file.ContentType.Equals("image/png", StringComparison.InvariantCultureIgnoreCase) && !filename.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) { + if (file.ContentType.Equals("image/png", StringComparison.InvariantCultureIgnoreCase) + && !filename.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) { filename = "clipboard.png"; } @@ -184,7 +190,7 @@ public ActionResult Replace(int replaceId, string type) { }); } catch (Exception ex) { - Logger.Error(ex, "Unexpected exception when uploading a media."); + Logger.Error(ex, T("Unexpected exception when uploading a media.").Text); statuses.Add(new { error = T(ex.Message).Text, diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/FolderController.cs b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/FolderController.cs index b79ede9e561..9b567b1c503 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/FolderController.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Controllers/FolderController.cs @@ -1,9 +1,9 @@ using System; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using System.Web.Mvc; using Orchard.ContentManagement; +using Orchard.FileSystems.Media; using Orchard.Localization; using Orchard.Logging; using Orchard.MediaLibrary.Models; @@ -36,7 +36,7 @@ IMediaLibraryService mediaManagerService public ActionResult Create(string folderPath) { if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, folderPath) || _mediaLibraryService.CheckMediaFolderPermission(Permissions.EditMediaContent, folderPath))) { Services.Notifier.Error(T("Couldn't create media folder")); - return RedirectToAction("Index", "Admin", new { area = "Orchard.MediaLibrary", folderPath = folderPath }); + return RedirectToAction("Index", "Admin", new { area = "Orchard.MediaLibrary", folderPath }); } // If the user is trying to access a folder above his boundaries, redirect him to his home folder @@ -68,28 +68,32 @@ public ActionResult Create() { return new HttpUnauthorizedResult(); } + var failed = false; try { - bool valid = String.IsNullOrWhiteSpace(viewModel.Name) || Regex.IsMatch(viewModel.Name, @"^[^:?#\[\]@!$&'()*+,.;=\s\""\<\>\\\|%]+$"); - if (!valid) { - throw new ArgumentException(T("Folder contains invalid characters").ToString()); - } - else { - _mediaLibraryService.CreateFolder(viewModel.FolderPath, viewModel.Name); - Services.Notifier.Information(T("Media folder created")); - } + _mediaLibraryService.CreateFolder(viewModel.FolderPath, viewModel.Name); + Services.Notifier.Information(T("Media folder created")); + } + catch (InvalidNameCharacterException) { + Services.Notifier.Error(T("The folder name contains invalid character(s).")); + failed = true; } catch (ArgumentException argumentException) { Services.Notifier.Error(T("Creating Folder failed: {0}", argumentException.Message)); + failed = true; + } + + if (failed) { Services.TransactionManager.Cancel(); return View(viewModel); } + return RedirectToAction("Index", "Admin", new { area = "Orchard.MediaLibrary" }); } public ActionResult Edit(string folderPath) { if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, folderPath) || _mediaLibraryService.CheckMediaFolderPermission(Permissions.EditMediaContent, folderPath))) { Services.Notifier.Error(T("Couldn't edit media folder")); - return RedirectToAction("Index", "Admin", new { area = "Orchard.MediaLibrary", folderPath = folderPath }); + return RedirectToAction("Index", "Admin", new { area = "Orchard.MediaLibrary", folderPath }); } if (!_mediaLibraryService.CanManageMediaFolder(folderPath)) { @@ -125,7 +129,7 @@ public ActionResult Edit() { var viewModel = new MediaManagerFolderEditViewModel(); UpdateModel(viewModel); - if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, viewModel.FolderPath) + if (!(_mediaLibraryService.CheckMediaFolderPermission(Permissions.ImportMediaContent, viewModel.FolderPath) || _mediaLibraryService.CheckMediaFolderPermission(Permissions.EditMediaContent, viewModel.FolderPath))) { return new HttpUnauthorizedResult(); } @@ -136,14 +140,12 @@ public ActionResult Edit() { } try { - bool valid = String.IsNullOrWhiteSpace(viewModel.Name) || Regex.IsMatch(viewModel.Name, @"^[^:?#\[\]@!$&'()*+,.;=\s\""\<\>\\\|%]+$"); - if (!valid) { - throw new ArgumentException(T("Folder contains invalid characters").ToString()); - } - else { - _mediaLibraryService.RenameFolder(viewModel.FolderPath, viewModel.Name); - Services.Notifier.Information(T("Media folder renamed")); - } + _mediaLibraryService.RenameFolder(viewModel.FolderPath, viewModel.Name); + Services.Notifier.Information(T("Media folder renamed")); + } + catch (InvalidNameCharacterException) { + Services.Notifier.Error(T("The folder name contains invalid character(s).")); + return View(viewModel); } catch (Exception exception) { Services.Notifier.Error(T("Editing Folder failed: {0}", exception.Message)); @@ -198,7 +200,7 @@ public ActionResult Move(string folderPath, int[] mediaItemIds) { // don't try to rename the file if there is no associated media file if (!string.IsNullOrEmpty(media.FileName)) { // check permission on source folder - if(!_mediaLibraryService.CheckMediaFolderPermission(Permissions.DeleteMediaContent, media.FolderPath)) { + if (!_mediaLibraryService.CheckMediaFolderPermission(Permissions.DeleteMediaContent, media.FolderPath)) { return new HttpUnauthorizedResult(); } diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/MediaFileName/MediaFileNameDriver.cs b/src/Orchard.Web/Modules/Orchard.MediaLibrary/MediaFileName/MediaFileNameDriver.cs index f9e58b547ee..f165d5955b2 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/MediaFileName/MediaFileNameDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/MediaFileName/MediaFileNameDriver.cs @@ -1,14 +1,14 @@ using System; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; +using Orchard.FileSystems.Media; using Orchard.Localization; using Orchard.MediaLibrary.Models; using Orchard.MediaLibrary.Services; using Orchard.Security; using Orchard.UI.Notify; -namespace Orchard.MediaLibrary.MediaFileName -{ +namespace Orchard.MediaLibrary.MediaFileName { public class MediaFileNameDriver : ContentPartDriver { private readonly IAuthenticationService _authenticationService; private readonly IAuthorizationService _authorizationService; @@ -58,6 +58,8 @@ protected override DriverResult Editor(MediaPart part, IUpdateModel updater, dyn var priorFileName = model.FileName; if (updater.TryUpdateModel(model, Prefix, null, null)) { if (model.FileName != null && !model.FileName.Equals(priorFileName, StringComparison.OrdinalIgnoreCase)) { + var fieldName = "MediaFileNameEditorSettings.FileName"; + try { _mediaLibraryService.RenameFile(part.FolderPath, priorFileName, model.FileName); part.FileName = model.FileName; @@ -65,14 +67,18 @@ protected override DriverResult Editor(MediaPart part, IUpdateModel updater, dyn _notifier.Add(NotifyType.Information, T("File '{0}' was renamed to '{1}'", priorFileName, model.FileName)); } catch (OrchardException) { - updater.AddModelError("MediaFileNameEditorSettings.FileName", T("Unable to rename file. Invalid Windows file path.")); + updater.AddModelError(fieldName, T("Unable to rename file. Invalid Windows file path.")); + } + catch (InvalidNameCharacterException) { + updater.AddModelError(fieldName, T("The file name contains invalid character(s).")); } - catch (Exception) { - updater.AddModelError("MediaFileNameEditorSettings.FileName", T("Unable to rename file")); + catch (Exception exception) { + updater.AddModelError(fieldName, T("Unable to rename file: {0}", exception.Message)); } } } } + return model; }); } diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Services/MediaLibraryService.cs b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Services/MediaLibraryService.cs index 349469205d5..abadaff3ac8 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Services/MediaLibraryService.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Services/MediaLibraryService.cs @@ -6,13 +6,13 @@ using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData.Models; using Orchard.Core.Common.Models; +using Orchard.Core.Title.Models; using Orchard.FileSystems.Media; using Orchard.Localization; using Orchard.MediaLibrary.Factories; using Orchard.MediaLibrary.Models; -using Orchard.Core.Title.Models; -using Orchard.Validation; using Orchard.MediaLibrary.Providers; +using Orchard.Validation; namespace Orchard.MediaLibrary.Services { public class MediaLibraryService : IMediaLibraryService { @@ -21,7 +21,6 @@ public class MediaLibraryService : IMediaLibraryService { private readonly IStorageProvider _storageProvider; private readonly IEnumerable _mediaFactorySelectors; private readonly IMediaFolderProvider _mediaFolderProvider; - private static char[] HttpUnallowed = new char[] { '<', '>', '*', '%', '&', ':', '\\', '?', '#' }; public MediaLibraryService( IOrchardServices orchardServices, @@ -145,12 +144,6 @@ public MediaPart ImportMedia(Stream stream, string relativePath, string filename } public string GetUniqueFilename(string folderPath, string filename) { - - // remove any char which is unallowed in an HTTP request - foreach (var unallowedChar in HttpUnallowed) { - filename = filename.Replace(unallowedChar.ToString(), ""); - } - // compute a unique filename var uniqueFilename = filename; var index = 1; @@ -177,9 +170,9 @@ public MediaPart ImportMedia(string relativePath, string filename, string conten var mediaFile = BuildMediaFile(relativePath, storageFile); using (var stream = storageFile.OpenRead()) { - var mediaFactory = GetMediaFactory(stream, mimeType, contentType); - if (mediaFactory == null) - throw new Exception(T("No media factory available to handle this resource.").Text); + var mediaFactory = GetMediaFactory(stream, mimeType, contentType) + ?? throw new Exception(T("No media factory available to handle this resource.").Text); + var mediaPart = mediaFactory.CreateMedia(stream, mediaFile.Name, mimeType, contentType); if (mediaPart != null) { mediaPart.FolderPath = relativePath; @@ -256,7 +249,7 @@ public bool CheckMediaFolderPermission(Orchard.Security.Permissions.Permission p if (_orchardServices.Authorizer.Authorize(Permissions.ManageMediaContent)) { return true; } - if (_orchardServices.WorkContext.CurrentUser==null) + if (_orchardServices.WorkContext.CurrentUser == null) return _orchardServices.Authorizer.Authorize(permission); // determines the folder type: public, user own folder (my), folder of another user (private) var rootedFolderPath = this.GetRootedFolderPath(folderPath) ?? ""; @@ -268,7 +261,7 @@ public bool CheckMediaFolderPermission(Orchard.Security.Permissions.Permission p isMyfolder = true; } - if(isMyfolder) { + if (isMyfolder) { return _orchardServices.Authorizer.Authorize(Permissions.ManageOwnMedia); } else { // other diff --git a/src/Orchard/Exceptions/DefaultExceptionPolicy.cs b/src/Orchard/Exceptions/DefaultExceptionPolicy.cs index db6de355985..08e7466e749 100644 --- a/src/Orchard/Exceptions/DefaultExceptionPolicy.cs +++ b/src/Orchard/Exceptions/DefaultExceptionPolicy.cs @@ -34,7 +34,7 @@ public bool HandleException(object sender, Exception exception) { return false; } - if (sender is IEventBus && exception is OrchardFatalException) { + if (sender is IEventBus && exception is OrchardFatalException) { return false; } @@ -49,7 +49,7 @@ public bool HandleException(object sender, Exception exception) { } private static bool IsFatal(Exception exception) { - return + return exception is OrchardSecurityException || exception is StackOverflowException || exception is AccessViolationException || diff --git a/src/Orchard/Exceptions/ExceptionExtensions.cs b/src/Orchard/Exceptions/ExceptionExtensions.cs index a66ba1a8b9d..534c774065e 100644 --- a/src/Orchard/Exceptions/ExceptionExtensions.cs +++ b/src/Orchard/Exceptions/ExceptionExtensions.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Orchard.Security; -using System.Threading; -using System.Security; using System.Runtime.InteropServices; +using System.Security; +using System.Threading; +using Orchard.Security; namespace Orchard.Exceptions { public static class ExceptionExtensions { diff --git a/src/Orchard/FileSystems/Media/FileSystemStorageProvider.cs b/src/Orchard/FileSystems/Media/FileSystemStorageProvider.cs index ab0abfd2652..34a7b505518 100644 --- a/src/Orchard/FileSystems/Media/FileSystemStorageProvider.cs +++ b/src/Orchard/FileSystems/Media/FileSystemStorageProvider.cs @@ -4,15 +4,22 @@ using System.Linq; using System.Web.Hosting; using Orchard.Environment.Configuration; +using Orchard.Exceptions; using Orchard.Localization; +using Orchard.Utility.Extensions; using Orchard.Validation; -using Orchard.Exceptions; namespace Orchard.FileSystems.Media { public class FileSystemStorageProvider : IStorageProvider { private readonly string _storagePath; // c:\orchard\media\default private readonly string _virtualPath; // ~/Media/Default/ private readonly string _publicPath; // /Orchard/Media/Default/ + public static readonly char[] HttpUnallowedCharacters = + new char[] { '<', '>', '*', '%', '&', ':', '\\', '/', '?', '#', '"', '{', '}', '|', '^', '[', ']', '`' }; + public static readonly char[] InvalidFolderNameCharacters = + Path.GetInvalidPathChars().Union(HttpUnallowedCharacters).ToArray(); + public static readonly char[] InvalidFileNameCharacters = + Path.GetInvalidFileNameChars().Union(HttpUnallowedCharacters).ToArray(); public FileSystemStorageProvider(ShellSettings settings) { var mediaPath = HostingEnvironment.IsHosted @@ -27,7 +34,7 @@ public FileSystemStorageProvider(ShellSettings settings) { appPath = HostingEnvironment.ApplicationVirtualPath; } if (!appPath.EndsWith("/")) - appPath = appPath + '/'; + appPath += '/'; if (!appPath.StartsWith("/")) appPath = '/' + appPath; @@ -39,21 +46,21 @@ public FileSystemStorageProvider(ShellSettings settings) { public Localizer T { get; set; } - public int MaxPathLength { - get; set; - // The public setter allows injecting this from Sites.MyTenant.Config or Sites.config, by using - // an AutoFac component: - /* - + /// + /// The public setter allows injecting this from Sites.MyTenant.Config or Sites.config, by using an AutoFac + /// component. See the example below. + /// + /* + - - */ - } + */ + public int MaxPathLength { get; set; } /// /// Maps a relative path into the storage path. @@ -215,6 +222,12 @@ public bool TryCreateFolder(string path) { /// The relative path to the folder to be created. /// If the folder already exists. public void CreateFolder(string path) { + // We are dealing with a folder here, but GetFileName returns the last path segment, which in this case is + // the folder name. + if (FolderNameContainsInvalidCharacters(Path.GetFileName(path))) { + throw new InvalidNameCharacterException(T("The directory name contains invalid character(s)").ToString()); + } + DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path)); if (directoryInfo.Exists) { throw new ArgumentException(T("Directory {0} already exists", path).ToString()); @@ -248,6 +261,12 @@ public void RenameFolder(string oldPath, string newPath) { throw new ArgumentException(T("Directory {0} does not exist", oldPath).ToString()); } + // We are dealing with a folder here, but GetFileName returns the last path segment, which in this case is + // the folder name. + if (FolderNameContainsInvalidCharacters(Path.GetFileName(newPath))) { + throw new InvalidNameCharacterException(T("The new directory name contains invalid character(s)").ToString()); + } + DirectoryInfo targetDirectory = new DirectoryInfo(MapStorage(newPath)); if (targetDirectory.Exists) { throw new ArgumentException(T("Directory {0} already exists", newPath).ToString()); @@ -313,6 +332,10 @@ public void RenameFile(string oldPath, string newPath) { throw new ArgumentException(T("File {0} does not exist", oldPath).ToString()); } + if (FileNameContainsInvalidCharacters(Path.GetFileName(newPath))) { + throw new InvalidNameCharacterException(T("The new file name contains invalid character(s)").ToString()); + } + FileInfo targetFileInfo = new FileInfo(MapStorage(newPath)); if (targetFileInfo.Exists) { throw new ArgumentException(T("File {0} already exists", newPath).ToString()); @@ -342,6 +365,10 @@ public void CopyFile(string originalPath, string duplicatePath) { /// If the file already exists. /// The created file. public IStorageFile CreateFile(string path) { + if (FileNameContainsInvalidCharacters(Path.GetFileName(path))) { + throw new InvalidNameCharacterException(T("The file name contains invalid character(s)").ToString()); + } + FileInfo fileInfo = new FileInfo(MapStorage(path)); if (fileInfo.Exists) { throw new ArgumentException(T("File {0} already exists", fileInfo.Name).ToString()); @@ -427,6 +454,12 @@ private static bool IsHidden(FileSystemInfo di) { return (di.Attributes & FileAttributes.Hidden) != 0; } + public static bool FolderNameContainsInvalidCharacters(string folderName) => + folderName.IndexOfAny(InvalidFolderNameCharacters) > -1; + + public static bool FileNameContainsInvalidCharacters(string fileName) => + fileName.IndexOfAny(InvalidFileNameCharacters) > -1; + #endregion private class FileSystemStorageFile : IStorageFile { diff --git a/src/Orchard/FileSystems/Media/IStorageProvider.cs b/src/Orchard/FileSystems/Media/IStorageProvider.cs index 39501cdaa7d..b7d771e6b3f 100644 --- a/src/Orchard/FileSystems/Media/IStorageProvider.cs +++ b/src/Orchard/FileSystems/Media/IStorageProvider.cs @@ -128,7 +128,7 @@ public interface IStorageProvider : IDependency { void SaveStream(string path, Stream inputStream); /// - /// Combines to paths. + /// Combines two paths. /// /// The parent path. /// The child path. diff --git a/src/Orchard/FileSystems/Media/InvalidNameCharacterException.cs b/src/Orchard/FileSystems/Media/InvalidNameCharacterException.cs new file mode 100644 index 00000000000..53fcff0c550 --- /dev/null +++ b/src/Orchard/FileSystems/Media/InvalidNameCharacterException.cs @@ -0,0 +1,7 @@ +using System; + +namespace Orchard.FileSystems.Media { + public class InvalidNameCharacterException : ArgumentException { + public InvalidNameCharacterException(string message) : base(message) { } + } +} diff --git a/src/Orchard/FileSystems/Media/StorageProviderExtensions.cs b/src/Orchard/FileSystems/Media/StorageProviderExtensions.cs index 8380b7645fb..1a5e4bf96ea 100644 --- a/src/Orchard/FileSystems/Media/StorageProviderExtensions.cs +++ b/src/Orchard/FileSystems/Media/StorageProviderExtensions.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Orchard.FileSystems.Media { public static class StorageProviderExtensions { diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index a24d2a5fa73..57312d77f90 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -45,7 +45,6 @@ ..\OrchardBasicCorrectness.ruleset false false - pdbonly @@ -159,6 +158,7 @@ +