Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/SharpSite.Plugins/PluginAssemblyValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace SharpSite.Plugins;
/// </summary>
public class PluginAssemblyValidator(ILogger<PluginAssemblyValidator> logger)
{
private static readonly string HashRegistryPath = Path.Combine("plugins", "_assembly-hashes.json");
private static readonly object _hashFileLock = new();

/// <summary>
Expand Down Expand Up @@ -132,4 +131,6 @@ private void SaveHashRegistry(Dictionary<string, string> registry)
File.WriteAllText(HashRegistryPath, json);
}
}

private static string HashRegistryPath => SharpSitePathProvider.AssemblyHashRegistryPath;
}
32 changes: 32 additions & 0 deletions src/SharpSite.Plugins/SharpSitePathProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace SharpSite.Plugins;

public static class SharpSitePathProvider
{
private static string _ContentRootPath = Directory.GetCurrentDirectory();

public static void Initialize(string? contentRootPath)
{
if (!string.IsNullOrWhiteSpace(contentRootPath))
{
_ContentRootPath = contentRootPath;
}
}

public static string ContentRootPath => _ContentRootPath;

public static string PluginsRootPath => Path.Combine(_ContentRootPath, "plugins");

public static string UploadedPluginsRootPath => Path.Combine(PluginsRootPath, "_uploaded");

public static string PluginWebRootPath => Path.Combine(PluginsRootPath, "_wwwroot");

public static string DefaultPluginsRootPath => Path.Combine(_ContentRootPath, "defaultplugins");

public static string ApplicationStatePath => Path.Combine(PluginsRootPath, "applicationState.json");

public static string AssemblyHashRegistryPath => Path.Combine(PluginsRootPath, "_assembly-hashes.json");

public static string GetPluginInstallationPath(string pluginFolderName) => Path.Combine(PluginsRootPath, pluginFolderName);

public static string GetPluginPrivateDirectoryPath(string directoryName) => Path.Combine(PluginsRootPath, "_" + directoryName);
}
4 changes: 2 additions & 2 deletions src/SharpSite.Web/ApplicationState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void SetTheme(PluginManifest manifest)
private string GetApplicationStateFileContents()
{
// read the applicationState.json file in the root of the plugins folder
var appStateFile = Path.Combine("plugins", "applicationState.json");
var appStateFile = SharpSitePathProvider.ApplicationStatePath;
if (File.Exists(appStateFile))
{
return File.ReadAllText(appStateFile);
Expand Down Expand Up @@ -205,7 +205,7 @@ private Task PostLoadApplicationState(IServiceProvider services)
public async Task Save()
{
// save application state to applicationState.json in the root of the plugins folder
var appStateFile = Path.Combine("plugins", "applicationState.json");
var appStateFile = SharpSitePathProvider.ApplicationStatePath;

var json = JsonSerializer.Serialize(this, SerializerOptions);
await File.WriteAllTextAsync(appStateFile, json);
Expand Down
35 changes: 18 additions & 17 deletions src/SharpSite.Web/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ public class PluginManager(
private const long MaxTotalExtractedSize = 100L * 1024 * 1024; // 100MB
private const long MaxSingleFileSize = 50L * 1024 * 1024; // 50MB
private const double MaxCompressionRatio = 100.0; // 100:1

public static void Initialize()
public static void Initialize(string? contentRootPath = null)
{
Directory.CreateDirectory("plugins");
Directory.CreateDirectory(Path.Combine("plugins", "_uploaded"));
Directory.CreateDirectory(Path.Combine("plugins", "_wwwroot"));
SharpSitePathProvider.Initialize(contentRootPath);
Directory.CreateDirectory(SharpSitePathProvider.PluginsRootPath);
Directory.CreateDirectory(SharpSitePathProvider.UploadedPluginsRootPath);
Directory.CreateDirectory(SharpSitePathProvider.PluginWebRootPath);

}

Expand Down Expand Up @@ -185,7 +186,7 @@ public async Task LoadPluginsAtStartup()
_ServiceDescriptors.AddMemoryCache();
}

foreach (var pluginFolder in Directory.GetDirectories("plugins"))
foreach (var pluginFolder in Directory.GetDirectories(SharpSitePathProvider.PluginsRootPath))
{
var pluginName = Path.GetFileName(pluginFolder);
if (pluginName.StartsWith("_")) continue;
Expand Down Expand Up @@ -378,15 +379,15 @@ private void ValidateArchiveSecurity(ZipArchive archive)
DirectoryInfo pluginLibFolder;
ZipArchive archive;

var pluginFolder = Directory.CreateDirectory(Path.Combine("plugins", "_uploaded"));
var pluginFolder = Directory.CreateDirectory(SharpSitePathProvider.UploadedPluginsRootPath);
var filePath = Path.Combine(pluginFolder.FullName, $"{pluginManifest.IdVersionToString()}.sspkg");

using var pluginAssemblyFileStream = File.OpenWrite(filePath);
await pluginAssemblyFileStream.WriteAsync(plugin.Bytes);
logger.LogInformation("Plugin saved to {FilePath}", filePath);

// Create a folder named after the plugin name under /plugins
pluginLibFolder = Directory.CreateDirectory(Path.Combine("plugins", pluginManifest.IdVersionToString()));
pluginLibFolder = Directory.CreateDirectory(SharpSitePathProvider.GetPluginInstallationPath(pluginManifest.IdVersionToString()));

using var pluginMemoryStream = new MemoryStream(plugin.Bytes);
archive = new ZipArchive(pluginMemoryStream, ZipArchiveMode.Read, true);
Expand All @@ -397,7 +398,7 @@ private void ValidateArchiveSecurity(ZipArchive archive)

if (hasWebContent)
{
pluginWwwRootFolder = Directory.CreateDirectory(Path.Combine("plugins", "_wwwroot", pluginManifest.IdVersionToString()));
pluginWwwRootFolder = Directory.CreateDirectory(Path.Combine(SharpSitePathProvider.PluginWebRootPath, pluginManifest.IdVersionToString()));
}

foreach (var entry in archive.Entries)
Expand Down Expand Up @@ -490,7 +491,7 @@ public Task<DirectoryInfo> CreateDirectoryInPluginsFolder(string name)
{
throw new InvalidFolderException($"Invalid path for folder: {name}");
}
return Task.FromResult(Directory.CreateDirectory(Path.Combine("plugins", "_" + name)));
return Task.FromResult(Directory.CreateDirectory(SharpSitePathProvider.GetPluginPrivateDirectoryPath(name)));
}

public T? GetPluginProvidedService<T>() where T : class
Expand All @@ -514,7 +515,7 @@ public Task<DirectoryInfo> MoveDirectoryInPluginsFolder(string oldName, string n
{

// check if the oldName directory exists
if (!Directory.Exists(Path.Combine("plugins", "_" + oldName)))
if (!Directory.Exists(SharpSitePathProvider.GetPluginPrivateDirectoryPath(oldName)))
{
throw new DirectoryNotFoundException($"Directory {oldName} not found in plugins folder.");
}
Expand All @@ -525,17 +526,17 @@ public Task<DirectoryInfo> MoveDirectoryInPluginsFolder(string oldName, string n

// move the directory specified, which is prefixed with an underscore, to a new name
Directory.Move(
Path.Combine("plugins", "_" + oldName),
Path.Combine("plugins", "_" + newName)
SharpSitePathProvider.GetPluginPrivateDirectoryPath(oldName),
SharpSitePathProvider.GetPluginPrivateDirectoryPath(newName)
);

return Task.FromResult(new DirectoryInfo(Path.Combine("plugins", "_" + newName)));
return Task.FromResult(new DirectoryInfo(SharpSitePathProvider.GetPluginPrivateDirectoryPath(newName)));

}

public DirectoryInfo GetDirectoryInPluginsFolder(string name)
{
return new DirectoryInfo(Path.Combine("plugins", "_" + name));
return new DirectoryInfo(SharpSitePathProvider.GetPluginPrivateDirectoryPath(name));
}

private static readonly char[] _InvalidChars = Path.GetInvalidPathChars();
Expand Down Expand Up @@ -588,7 +589,7 @@ private static bool IsValidDirectory(string name)
private static void EnsurePluginNotInstalled(PluginManifest? manifest, ILogger logger)
{

if (manifest is not null && Directory.Exists(Path.Combine("plugins", manifest.IdVersionToString())))
if (manifest is not null && Directory.Exists(SharpSitePathProvider.GetPluginInstallationPath(manifest.IdVersionToString())))
{
var errMsg = string.Format(Locales.SharedResource.sharpsite_plugin_exists, manifest.IdVersionToString());
PluginException ex = new(errMsg);
Expand All @@ -601,7 +602,7 @@ private static void EnsurePluginNotInstalled(PluginManifest? manifest, ILogger l
public async Task InstallDefaultPlugins()
{

var defaultPluginFolder = new DirectoryInfo("defaultplugins");
var defaultPluginFolder = new DirectoryInfo(SharpSitePathProvider.DefaultPluginsRootPath);
if (!defaultPluginFolder.Exists) return;

foreach (var file in defaultPluginFolder.GetFiles("*.sspkg"))
Expand Down
4 changes: 2 additions & 2 deletions src/SharpSite.Web/PluginManagerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static class PluginManagerExtensions
public static ApplicationState AddPluginManagerAndAppState(this WebApplicationBuilder builder)
{

PluginManager.Initialize();
PluginManager.Initialize(builder.Environment.ContentRootPath);

var appState = new ApplicationState();
builder.Services.AddSingleton(appState);
Expand All @@ -27,7 +27,7 @@ public static WebApplication ConfigurePluginFileSystem(this WebApplication app)
{

var pluginRoot = new PhysicalFileProvider(
Path.Combine(app.Environment.ContentRootPath, @"plugins/_wwwroot"));
SharpSitePathProvider.PluginWebRootPath);
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
Expand Down
43 changes: 43 additions & 0 deletions tests/SharpSite.Tests.Web/Startup/Step2DependencyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Reflection;
using Microsoft.AspNetCore.Components;
using SharpSite.Abstractions.FileStorage;
using Xunit;

namespace SharpSite.Tests.Web.Startup;

public class Step2DependencyTests
{
[Fact]
public void Step2_ShouldNotInjectFileStorageDirectly()
{
// Arrange
var componentType = typeof(SharpSite.Web.Components.Startup.Step2);

// Act
var injectedFileStorageProperties = componentType
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(property =>
property.PropertyType == typeof(IHandleFileStorage) &&
property.GetCustomAttribute<InjectAttribute>() is not null);

// Assert
Assert.Empty(injectedFileStorageProperties);
}

[Fact]
public void Step2_ShouldInjectPluginManagerForOptionalFileStorageLookup()
{
// Arrange
var componentType = typeof(SharpSite.Web.Components.Startup.Step2);

// Act
var pluginManagerProperty = componentType
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.SingleOrDefault(property =>
property.PropertyType == typeof(SharpSite.Web.PluginManager) &&
property.GetCustomAttribute<InjectAttribute>() is not null);

// Assert
Assert.NotNull(pluginManagerProperty);
}
}
Loading