From bf31fb10ebde3bc004d846308d23a76bb55d6557 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 14 May 2025 10:39:42 -0500 Subject: [PATCH 1/9] fix: use correct join for preset select --- src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs index 92eff397..c008e969 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs @@ -227,7 +227,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); - for (int i = 0; i < joinMap.NumberOfPresets.JoinSpan; i++) + for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++) { int tempNum = i; From 1a366790e74cf461a0e95ec7614beddafc98b7fb Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Mon, 11 Aug 2025 16:23:31 -0400 Subject: [PATCH 2/9] feat: add LoadAssets method to manage asset loading and configuration file cleanup --- src/PepperDash.Essentials/ControlSystem.cs | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 3da56d8b..2fbcb091 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -1,5 +1,6 @@  using System; +using System.IO.Compression; using System.Linq; using System.Reflection; using Crestron.SimplSharp; @@ -267,6 +268,8 @@ namespace PepperDash.Essentials // _ = new ProcessorExtensionDeviceFactory(); // _ = new MobileControlFactory(); + LoadAssets(); + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); var filesReady = SetupFilesystem(); @@ -568,5 +571,62 @@ namespace PepperDash.Essentials return false; } } + + private static void LoadAssets() + { + var applicationDirectory = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); + Debug.LogMessage(LogEventLevel.Information, "Searching: {applicationDirectory:l} for embedded assets - {Destination}", applicationDirectory.FullName, Global.FilePathPrefix); + + var zipFiles = applicationDirectory.GetFiles("assets*.zip"); + + if (zipFiles.Length > 0) + { + var zipFile = zipFiles[0]; + Debug.LogMessage(LogEventLevel.Information, "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName); + + // deleting the directory here as the net472 version of unzip doesn't have an overwrite capability + // this is the most deterministic mvp + if (Directory.Exists(Global.FilePathPrefix)) + { + Debug.LogMessage(LogEventLevel.Information, "Removing existing config directory: {Destination}", Global.FilePathPrefix); + Directory.Delete(Global.FilePathPrefix, true); + } + + // recreating the directory + Directory.CreateDirectory(Global.FilePathPrefix); + ZipFile.ExtractToDirectory(zipFile.FullName, Global.FilePathPrefix); + Debug.LogMessage(LogEventLevel.Information, "Unzipped assets to: {Destination}", Global.FilePathPrefix); + } + + // cleaning up zip files + foreach (var file in zipFiles) + { + File.Delete(file.FullName); + } + + var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json"); + + if (jsonFiles.Length > 0) + { + var jsonFile = jsonFiles[0]; + var finalPath = Path.Combine(Global.FilePathPrefix, jsonFile.Name); + Debug.LogMessage(LogEventLevel.Information, "Found configuration file: {jsonFile:l}... Moving to: {Destination}", jsonFile, finalPath); + + if (File.Exists(finalPath)) + { + Debug.LogMessage(LogEventLevel.Information, "Removing existing configuration file: {Destination}", finalPath); + File.Delete(finalPath); + } + + jsonFile.MoveTo(finalPath); + } + + // Remove any old configuration files that weren't moved + foreach (var file in Directory.GetFiles(Global.ApplicationDirectoryPathPrefix, "configurationFile*.json")) + { + Debug.LogMessage(LogEventLevel.Information, "Removing old configuration file: {file:l}", file); + File.Delete(file); + } + } } } From 01074225077f7766e68065a9f100cdce1b7ec4b4 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Mon, 11 Aug 2025 16:24:44 -0400 Subject: [PATCH 3/9] refactor: remove unused assembly resolution logic from ControlSystem --- src/PepperDash.Essentials/ControlSystem.cs | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 3da56d8b..398c8f1b 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -41,29 +41,6 @@ namespace PepperDash.Essentials SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); - - // AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - } - - private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) - { - var assemblyName = new AssemblyName(args.Name).Name; - if (assemblyName == "PepperDash_Core") - { - return Assembly.LoadFrom("PepperDashCore.dll"); - } - - if (assemblyName == "PepperDash_Essentials_Core") - { - return Assembly.LoadFrom("PepperDash.Essentials.Core.dll"); - } - - if (assemblyName == "Essentials Devices Common") - { - return Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll"); - } - - return null; } /// From 033aa1f3dddafa97925277664c3786cd0b9a2979 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Tue, 12 Aug 2025 12:17:58 -0400 Subject: [PATCH 4/9] feat: enhance asset extraction and configuration file handling in ControlSystem --- src/PepperDash.Essentials/ControlSystem.cs | 60 ++++++++++++------- .../PepperDash.Essentials.csproj | 7 ++- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 2fbcb091..9e97767c 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.IO.Compression; using System.Linq; using System.Reflection; @@ -579,23 +578,40 @@ namespace PepperDash.Essentials var zipFiles = applicationDirectory.GetFiles("assets*.zip"); - if (zipFiles.Length > 0) + if (zipFiles.Length > 1) + { + throw new Exception("Multiple assets zip files found. Cannot continue."); + } + + if (zipFiles.Length == 1) { var zipFile = zipFiles[0]; Debug.LogMessage(LogEventLevel.Information, "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName); - - // deleting the directory here as the net472 version of unzip doesn't have an overwrite capability - // this is the most deterministic mvp - if (Directory.Exists(Global.FilePathPrefix)) + using (var archive = ZipFile.OpenRead(zipFile.FullName)) { - Debug.LogMessage(LogEventLevel.Information, "Removing existing config directory: {Destination}", Global.FilePathPrefix); - Directory.Delete(Global.FilePathPrefix, true); - } + foreach (var entry in archive.Entries) + { + var destinationPath = Path.Combine(Global.FilePathPrefix, entry.FullName); - // recreating the directory - Directory.CreateDirectory(Global.FilePathPrefix); - ZipFile.ExtractToDirectory(zipFile.FullName, Global.FilePathPrefix); - Debug.LogMessage(LogEventLevel.Information, "Unzipped assets to: {Destination}", Global.FilePathPrefix); + // If the entry is a directory, ensure it exists and skip extraction + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + // If a directory exists where a file should go, delete it + if (Directory.Exists(destinationPath)) + Directory.Delete(destinationPath, true); + + // Ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + + entry.ExtractToFile(destinationPath, true); + + Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + } + } } // cleaning up zip files @@ -606,11 +622,16 @@ namespace PepperDash.Essentials var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json"); - if (jsonFiles.Length > 0) + if (jsonFiles.Length > 1) + { + throw new Exception("Multiple configuration files found. Cannot continue."); + } + + if (jsonFiles.Length == 1) { var jsonFile = jsonFiles[0]; var finalPath = Path.Combine(Global.FilePathPrefix, jsonFile.Name); - Debug.LogMessage(LogEventLevel.Information, "Found configuration file: {jsonFile:l}... Moving to: {Destination}", jsonFile, finalPath); + Debug.LogMessage(LogEventLevel.Information, "Found configuration file: {jsonFile:l}... Moving to: {Destination}", jsonFile.FullName, finalPath); if (File.Exists(finalPath)) { @@ -620,13 +641,6 @@ namespace PepperDash.Essentials jsonFile.MoveTo(finalPath); } - - // Remove any old configuration files that weren't moved - foreach (var file in Directory.GetFiles(Global.ApplicationDirectoryPathPrefix, "configurationFile*.json")) - { - Debug.LogMessage(LogEventLevel.Information, "Removing old configuration file: {file:l}", file); - File.Delete(file); - } } } } diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 1ddd8e54..20a42ffd 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -20,9 +20,9 @@ full - - pdbonly - bin\$(Configuration)\PepperDashEssentials.xml + + pdbonly + bin\$(Configuration)\PepperDashEssentials.xml @@ -49,6 +49,7 @@ + From 19e848916647a305711eaa2919656f3b4a6e0634 Mon Sep 17 00:00:00 2001 From: Erik Meyer Date: Tue, 19 Aug 2025 15:48:43 -0400 Subject: [PATCH 5/9] feat: add html assets extraction from zip files in ControlSystem --- src/PepperDash.Essentials/ControlSystem.cs | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 4ca61e66..59137b98 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -597,6 +597,59 @@ namespace PepperDash.Essentials File.Delete(file.FullName); } + var htmlZipFiles = applicationDirectory.GetFiles("htmlassets*.zip"); + + if (htmlZipFiles.Length > 1) + { + throw new Exception("Multiple htmlassets zip files found. Cannot continue."); + } + + + if (htmlZipFiles.Length == 1) + { + var htmlZipFile = htmlZipFiles[0]; + var programDir = new DirectoryInfo(Global.FilePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); + var userOrNvramDir = programDir.Parent; + var rootDir = userOrNvramDir?.Parent; + if (rootDir == null) + { + throw new Exception("Unable to determine root directory for html extraction."); + } + var htmlDir = Path.Combine(rootDir.FullName, "html"); + Debug.LogMessage(LogEventLevel.Information, "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName); + using (var archive = ZipFile.OpenRead(htmlZipFile.FullName)) + { + foreach (var entry in archive.Entries) + { + var destinationPath = Path.Combine(htmlDir, entry.FullName); + + // If the entry is a directory, ensure it exists and skip extraction + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + // Only delete the file if it exists and is a file, not a directory + if (File.Exists(destinationPath)) + File.Delete(destinationPath); + + // Ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + + entry.ExtractToFile(destinationPath, true); + + Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + } + } + } + + // cleaning up html zip files + foreach (var file in htmlZipFiles) + { + File.Delete(file.FullName); + } + var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json"); if (jsonFiles.Length > 1) From 52916d29f4667dadc569eb1ee24d566ab44526ec Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 19 Aug 2025 14:53:06 -0600 Subject: [PATCH 6/9] Update src/PepperDash.Essentials/ControlSystem.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Essentials/ControlSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 59137b98..0d508b33 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -601,7 +601,7 @@ namespace PepperDash.Essentials if (htmlZipFiles.Length > 1) { - throw new Exception("Multiple htmlassets zip files found. Cannot continue."); + throw new Exception("Multiple htmlassets zip files found in application directory. Please ensure only one htmlassets*.zip file is present and retry."); } From d013068a0c61cc6788c346cc50a2a0d6fd3a9dac Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 19 Aug 2025 14:54:11 -0600 Subject: [PATCH 7/9] Update src/PepperDash.Essentials/ControlSystem.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Essentials/ControlSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 0d508b33..554c7bc9 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -613,7 +613,7 @@ namespace PepperDash.Essentials var rootDir = userOrNvramDir?.Parent; if (rootDir == null) { - throw new Exception("Unable to determine root directory for html extraction."); + throw new Exception($"Unable to determine root directory for html extraction. Current path: {Global.FilePathPrefix}"); } var htmlDir = Path.Combine(rootDir.FullName, "html"); Debug.LogMessage(LogEventLevel.Information, "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName); From 06dc0e947eb1e797fc7d27cdb544402a602a5f18 Mon Sep 17 00:00:00 2001 From: erikdred <88980320+erikdred@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:12:20 -0400 Subject: [PATCH 8/9] fix: check for null when getting directory Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Essentials/ControlSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 554c7bc9..c04080f3 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -635,7 +635,9 @@ namespace PepperDash.Essentials File.Delete(destinationPath); // Ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + var parentDir = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(parentDir)) + Directory.CreateDirectory(parentDir); entry.ExtractToFile(destinationPath, true); From c3b39a87da8d922acd8a9d39298cb0d9510afd79 Mon Sep 17 00:00:00 2001 From: Erik Meyer Date: Fri, 22 Aug 2025 15:15:02 -0400 Subject: [PATCH 9/9] fix: enhance zip extraction to prevent directory traversal attacks --- src/PepperDash.Essentials/ControlSystem.cs | 24 +++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index c04080f3..b2fe1e19 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -563,14 +563,21 @@ namespace PepperDash.Essentials if (zipFiles.Length == 1) { var zipFile = zipFiles[0]; + var assetsRoot = System.IO.Path.GetFullPath(Global.FilePathPrefix); + if (!assetsRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && !assetsRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + { + assetsRoot += Path.DirectorySeparatorChar; + } Debug.LogMessage(LogEventLevel.Information, "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName); using (var archive = ZipFile.OpenRead(zipFile.FullName)) { foreach (var entry in archive.Entries) { var destinationPath = Path.Combine(Global.FilePathPrefix, entry.FullName); + var fullDest = System.IO.Path.GetFullPath(destinationPath); + if (!fullDest.StartsWith(assetsRoot, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - // If the entry is a directory, ensure it exists and skip extraction if (string.IsNullOrEmpty(entry.Name)) { Directory.CreateDirectory(destinationPath); @@ -581,11 +588,8 @@ namespace PepperDash.Essentials if (Directory.Exists(destinationPath)) Directory.Delete(destinationPath, true); - // Ensure the parent directory exists Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - entry.ExtractToFile(destinationPath, true); - Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); } } @@ -616,14 +620,22 @@ namespace PepperDash.Essentials throw new Exception($"Unable to determine root directory for html extraction. Current path: {Global.FilePathPrefix}"); } var htmlDir = Path.Combine(rootDir.FullName, "html"); + var htmlRoot = System.IO.Path.GetFullPath(htmlDir); + if (!htmlRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && + !htmlRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + { + htmlRoot += Path.DirectorySeparatorChar; + } Debug.LogMessage(LogEventLevel.Information, "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName); using (var archive = ZipFile.OpenRead(htmlZipFile.FullName)) { foreach (var entry in archive.Entries) { var destinationPath = Path.Combine(htmlDir, entry.FullName); + var fullDest = System.IO.Path.GetFullPath(destinationPath); + if (!fullDest.StartsWith(htmlRoot, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - // If the entry is a directory, ensure it exists and skip extraction if (string.IsNullOrEmpty(entry.Name)) { Directory.CreateDirectory(destinationPath); @@ -634,13 +646,11 @@ namespace PepperDash.Essentials if (File.Exists(destinationPath)) File.Delete(destinationPath); - // Ensure the parent directory exists var parentDir = Path.GetDirectoryName(destinationPath); if (!string.IsNullOrEmpty(parentDir)) Directory.CreateDirectory(parentDir); entry.ExtractToFile(destinationPath, true); - Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); } }