diff --git a/src/Spe/App_Config/Include/Spe/Spe.config b/src/Spe/App_Config/Include/Spe/Spe.config
index 61f74505..7e751b7c 100644
--- a/src/Spe/App_Config/Include/Spe/Spe.config
+++ b/src/Spe/App_Config/Include/Spe/Spe.config
@@ -240,6 +240,15 @@
+
+
+
+ image/*
+
+
+
+
+
diff --git a/src/Spe/Client/Applications/UploadFile/PowerShellUploadFilePage2.cs b/src/Spe/Client/Applications/UploadFile/PowerShellUploadFilePage2.cs
index 8621f09a..283443b0 100644
--- a/src/Spe/Client/Applications/UploadFile/PowerShellUploadFilePage2.cs
+++ b/src/Spe/Client/Applications/UploadFile/PowerShellUploadFilePage2.cs
@@ -9,6 +9,7 @@
using Sitecore.Shell.Web.UI;
using Sitecore.Web;
using Sitecore.Web.UI.XmlControls;
+using Spe.Client.Applications.UploadFile.Validation;
using Spe.Core.Diagnostics;
namespace Spe.Client.Applications.UploadFile
@@ -39,6 +40,16 @@ protected override void OnLoad(EventArgs e)
return;
try
{
+ string[] patterns = Factory.GetStringSet("powershell/uploadFile/allowedFileTypes/pattern")?.ToArray() ?? new string[] { "image/*" };
+ var contentTypeValidator = new ContentTypeValidator(patterns);
+ var result = contentTypeValidator.Validate(Request.Files);
+ if (!result.Valid)
+ {
+ CancelResult();
+ Sitecore.Diagnostics.Log.Warn($"[SPE] {result.Message}", this);
+ return;
+ }
+
var pathOrId = Sitecore.Context.ClientPage.ClientRequest.Form["ItemUri"];
var langStr = Sitecore.Context.ClientPage.ClientRequest.Form["LanguageName"];
var language = langStr.Length > 0
@@ -57,6 +68,15 @@ protected override void OnLoad(EventArgs e)
{
uploadArgs.Destination = UploadDestination.File;
uploadArgs.FileOnly = true;
+ string[] allowedLocations = Factory.GetStringSet("powershell/uploadFile/allowedLocations/path").ToArray();
+ var validator = new UploadLocationValidator(allowedLocations);
+ pathOrId = validator.GetFullPath(pathOrId);
+ if (!validator.Validate(pathOrId))
+ {
+ CancelResult();
+ Sitecore.Diagnostics.Log.Warn($"[SPE] Location: '{pathOrId}' is protected. Please configure 'powershell/uploadFile/allowedLocations' if you wish to change it.", this);
+ return;
+ }
}
uploadArgs.Files = Request.Files;
uploadArgs.Folder = pathOrId;
@@ -125,5 +145,10 @@ protected override void OnLoad(EventArgs e)
}
}
}
+
+ private static void CancelResult()
+ {
+ HttpContext.Current.Response.Write("Done");
+ }
}
}
\ No newline at end of file
diff --git a/src/Spe/Client/Applications/UploadFile/Validation/ContentTypeValidator.cs b/src/Spe/Client/Applications/UploadFile/Validation/ContentTypeValidator.cs
new file mode 100644
index 00000000..804681cf
--- /dev/null
+++ b/src/Spe/Client/Applications/UploadFile/Validation/ContentTypeValidator.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Web;
+using Sitecore;
+using Sitecore.Diagnostics;
+using Sitecore.Globalization;
+using Sitecore.Pipelines.Upload;
+using Sitecore.StringExtensions;
+
+namespace Spe.Client.Applications.UploadFile.Validation
+{
+ internal class ContentTypeValidator
+ {
+ internal IReadOnlyCollection validators { get; }
+
+ public ContentTypeValidator(string[] patterns)
+ {
+ validators = CreateValidators(patterns);
+ }
+
+ public ValidationResult Validate(HttpFileCollection Files)
+ {
+ if (!validators.Any())
+ {
+ return new ValidationResult { Message = string.Empty, Valid = true };
+ }
+
+ foreach (string key in Files)
+ {
+ var file = Files[key];
+
+ if (file == null)
+ {
+ continue;
+ }
+
+ if (!IsFileAccepted(file, validators))
+ {
+ var reason = Translate.Text("File type isn`t allowed.");
+ reason = StringUtil.EscapeJavascriptString(reason);
+ var convertedFileName = StringUtil.EscapeJavascriptString(file.FileName);
+
+ var errorText = Translate.Text(string.Format("The '{0}' file cannot be uploaded. File type isn`t allowed.", file.FileName));
+ Log.Warn(errorText, this);
+ return new ValidationResult { Message = errorText, Valid = false };
+ }
+ }
+ return new ValidationResult { Message = string.Empty, Valid = true };
+ }
+
+ protected static bool IsUnpack(HttpPostedFileBase file)
+ {
+ return string.Compare(Path.GetExtension(file.FileName), ".zip", StringComparison.InvariantCultureIgnoreCase) == 0;
+ }
+
+ private static bool IsFileAccepted(HttpPostedFile file, IReadOnlyCollection validators)
+ {
+ if (string.IsNullOrEmpty(file.FileName))
+ {
+ return true;
+ }
+
+ var isArchive = IsUnpack(new HttpPostedFileWrapper(file));
+ if (!isArchive)
+ {
+ return validators.Any(x => x.IsValid(file.FileName));
+ }
+
+
+ if (file.InputStream.Position != 0)
+ {
+ file.InputStream.Position = 0;
+ }
+
+ var archive = new ZipArchive(file.InputStream, ZipArchiveMode.Read, true);
+ try
+ {
+ return archive.Entries
+ .Where(entry => !entry.FullName.EndsWith("/"))
+ .All(entry => validators.Any(x => x.IsValid(entry.FullName)));
+ }
+ finally
+ {
+ archive.Dispose();
+ if (file.InputStream.Position != 0)
+ {
+ file.InputStream.Position = 0;
+ }
+ }
+ }
+
+ private static IReadOnlyCollection CreateValidators(string[] allowedFileTypes)
+ {
+ if (!allowedFileTypes.Any())
+ {
+ return new List();
+ }
+
+ return allowedFileTypes
+ .Select(p => p.Trim())
+ .Select(p => new FileTypeValidator(p))
+ .ToList();
+ }
+ }
+}
diff --git a/src/Spe/Client/Applications/UploadFile/Validation/FileTypeValidator.cs b/src/Spe/Client/Applications/UploadFile/Validation/FileTypeValidator.cs
new file mode 100644
index 00000000..fa738c7d
--- /dev/null
+++ b/src/Spe/Client/Applications/UploadFile/Validation/FileTypeValidator.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq;
+using System.Web;
+
+namespace Spe.Client.Applications.UploadFile.Validation
+{
+ internal class FileTypeValidator
+ {
+ private readonly Func _validate;
+
+ public FileTypeValidator(string pattern)
+ {
+ if (pattern.Contains('.'))
+ {
+ _validate = fileName => fileName.EndsWith(pattern);
+ }
+ else if (pattern.Contains("/*"))
+ {
+ _validate = fileName => MimeMapping.GetMimeMapping(fileName).Split('/').FirstOrDefault() == pattern.Split('/').First();
+ }
+ else if (pattern.Contains("/"))
+ {
+ _validate = fileName => MimeMapping.GetMimeMapping(fileName) == pattern;
+ }
+ else
+ {
+ throw new NotSupportedException("Pattern isn't supported");
+ }
+ }
+
+ public bool IsValid(string fileName)
+ {
+ return _validate(fileName);
+ }
+ }
+}
diff --git a/src/Spe/Client/Applications/UploadFile/Validation/UploadLocationValidator.cs b/src/Spe/Client/Applications/UploadFile/Validation/UploadLocationValidator.cs
new file mode 100644
index 00000000..2e4c2360
--- /dev/null
+++ b/src/Spe/Client/Applications/UploadFile/Validation/UploadLocationValidator.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Web;
+using Sitecore;
+using Sitecore.Diagnostics;
+using Sitecore.Globalization;
+using Sitecore.Pipelines.Upload;
+using Sitecore.StringExtensions;
+
+namespace Spe.Client.Applications.UploadFile.Validation
+{
+ internal class UploadLocationValidator
+ {
+ private readonly List _allowedLocations;
+ private readonly string _webRootPath;
+
+ public UploadLocationValidator(IEnumerable allowedLocations)
+ {
+ _webRootPath = HttpContext.Current.Server.MapPath("\\");
+
+ // Convert relative paths to absolute paths
+ _allowedLocations = allowedLocations
+ .Select(path => Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(_webRootPath, path)))
+ .ToList();
+ }
+
+ public bool Validate(string userDefinedPath)
+ {
+ if (string.IsNullOrWhiteSpace(userDefinedPath)) return false;
+
+ string fullPath;
+ try
+ {
+ fullPath = GetFullPath(userDefinedPath);
+ }
+ catch (Exception)
+ {
+ return false; // Invalid path format
+ }
+
+ return _allowedLocations.Any(allowedPath => fullPath.StartsWith(allowedPath, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public string GetFullPath(string path)
+ {
+ return Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(_webRootPath, path));
+ }
+ }
+}
diff --git a/src/Spe/Client/Applications/UploadFile/Validation/ValidationResult.cs b/src/Spe/Client/Applications/UploadFile/Validation/ValidationResult.cs
new file mode 100644
index 00000000..d4ce0cd5
--- /dev/null
+++ b/src/Spe/Client/Applications/UploadFile/Validation/ValidationResult.cs
@@ -0,0 +1,8 @@
+namespace Spe.Client.Applications.UploadFile.Validation
+{
+ internal class ValidationResult
+ {
+ public string Message { get; set; }
+ public bool Valid { get; set; }
+ }
+}
diff --git a/src/Spe/Spe.csproj b/src/Spe/Spe.csproj
index dbbc688f..1f1bf721 100644
--- a/src/Spe/Spe.csproj
+++ b/src/Spe/Spe.csproj
@@ -178,6 +178,10 @@
+
+
+
+