mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-02 05:43:40 +01:00
Extract plugin to temp directory before copying to final location, some minor cleanup
This commit is contained in:
parent
f2c89bfc00
commit
34679d085b
8 changed files with 263 additions and 153 deletions
|
|
@ -48,6 +48,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// </summary>
|
||||
public const int PluginWaitBeforeFreeDefault = 1000; // upped from 500ms, seems more stable
|
||||
|
||||
private const string BrokenMarkerFileName = ".broken";
|
||||
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<PluginManager>();
|
||||
|
||||
private readonly object pluginListLock = new();
|
||||
|
|
@ -874,7 +876,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup disabled plugins. Does not target devPlugins.
|
||||
/// Cleanup disabled and broken plugins. Does not target devPlugins.
|
||||
/// </summary>
|
||||
public void CleanupPlugins()
|
||||
{
|
||||
|
|
@ -882,6 +884,13 @@ internal class PluginManager : IInternalDisposableService
|
|||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path.Combine(pluginDir.FullName, BrokenMarkerFileName)))
|
||||
{
|
||||
Log.Warning("Cleaning up broken plugin {Name}", pluginDir.Name);
|
||||
pluginDir.Delete(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
var versionDirs = pluginDir.GetDirectories();
|
||||
|
||||
versionDirs = versionDirs
|
||||
|
|
@ -1423,7 +1432,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
else
|
||||
{
|
||||
// If we are doing anything other than a fresh install, not having a workingPluginId is an error that must be fixed
|
||||
Debug.Assert(inheritedWorkingPluginId != null, "inheritedWorkingPluginId != null");
|
||||
if (inheritedWorkingPluginId != null)
|
||||
throw new InvalidOperationException("Inherited WorkingPluginId must not be null");
|
||||
}
|
||||
|
||||
// Ensure that we have a testing opt-in for this plugin if we are installing a testing version
|
||||
|
|
@ -1434,31 +1444,28 @@ internal class PluginManager : IInternalDisposableService
|
|||
this.configuration.QueueSave();
|
||||
}
|
||||
|
||||
var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version?.ToString() ?? string.Empty));
|
||||
var pluginVersionsDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName));
|
||||
var tempOutputDir = new DirectoryInfo(FilesystemUtil.GetTempFileName());
|
||||
var outputDir = new DirectoryInfo(Path.Combine(pluginVersionsDir.FullName, version?.ToString() ?? string.Empty));
|
||||
|
||||
FilesystemUtil.DeleteAndRecreateDirectory(tempOutputDir);
|
||||
FilesystemUtil.DeleteAndRecreateDirectory(outputDir);
|
||||
|
||||
Log.Debug("Extracting plugin to {TempOutputDir}", tempOutputDir);
|
||||
|
||||
try
|
||||
{
|
||||
if (outputDir.Exists)
|
||||
outputDir.Delete(true);
|
||||
using var archive = new ZipArchive(zipStream);
|
||||
|
||||
outputDir.Create();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored, since the plugin may be loaded already
|
||||
}
|
||||
|
||||
Log.Debug("Extracting to {OutputDir}", outputDir);
|
||||
|
||||
using (var archive = new ZipArchive(zipStream))
|
||||
{
|
||||
foreach (var zipFile in archive.Entries)
|
||||
{
|
||||
var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName)));
|
||||
var outputFile = new FileInfo(
|
||||
Path.GetFullPath(Path.Combine(tempOutputDir.FullName, zipFile.FullName)));
|
||||
|
||||
if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
if (!outputFile.FullName.StartsWith(tempOutputDir.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability");
|
||||
throw new IOException(
|
||||
"Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability");
|
||||
}
|
||||
|
||||
if (outputFile.Directory == null)
|
||||
|
|
@ -1469,70 +1476,88 @@ internal class PluginManager : IInternalDisposableService
|
|||
if (zipFile.Name.IsNullOrEmpty())
|
||||
{
|
||||
// Assuming Empty for Directory
|
||||
Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}");
|
||||
Log.Verbose(
|
||||
"ZipFile name is null or empty, treating as a directory: {Path}", outputFile.Directory.FullName);
|
||||
Directory.CreateDirectory(outputFile.Directory.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure directory is created
|
||||
Directory.CreateDirectory(outputFile.Directory.FullName);
|
||||
|
||||
try
|
||||
{
|
||||
zipFile.ExtractToFile(outputFile.FullName, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (outputFile.Extension.EndsWith("dll"))
|
||||
{
|
||||
throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}");
|
||||
}
|
||||
zipFile.ExtractToFile(outputFile.FullName, true);
|
||||
}
|
||||
|
||||
var tempDllFile = LocalPluginManifest.GetPluginFile(tempOutputDir, repoManifest);
|
||||
var tempManifestFile = LocalPluginManifest.GetManifestFile(tempDllFile);
|
||||
|
||||
// We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use.
|
||||
FilesystemUtil.WriteAllTextSafe(
|
||||
tempManifestFile.FullName,
|
||||
JsonConvert.SerializeObject(repoManifest, Formatting.Indented));
|
||||
|
||||
// Reload as a local manifest, add some attributes, and save again.
|
||||
var tempManifest = LocalPluginManifest.Load(tempManifestFile);
|
||||
|
||||
if (tempManifest == null)
|
||||
throw new Exception("Plugin had no valid manifest");
|
||||
|
||||
if (tempManifest.InternalName != repoManifest.InternalName)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Distributed internal name does not match repo internal name: {tempManifest.InternalName} - {repoManifest.InternalName}");
|
||||
}
|
||||
|
||||
if (tempManifest.WorkingPluginId != Guid.Empty)
|
||||
throw new Exception("Plugin shall not specify a WorkingPluginId");
|
||||
|
||||
tempManifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid();
|
||||
|
||||
if (useTesting)
|
||||
{
|
||||
tempManifest.Testing = true;
|
||||
}
|
||||
|
||||
// Document the url the plugin was installed from
|
||||
tempManifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty
|
||||
? repoManifest.SourceRepo.PluginMasterUrl
|
||||
: SpecialPluginSource.MainRepo;
|
||||
|
||||
tempManifest.Save(tempManifestFile, "installation");
|
||||
|
||||
// Copy the directory to the final location
|
||||
Log.Debug("Copying plugin from {TempOutputDir} to {OutputDir}", tempOutputDir, outputDir);
|
||||
FilesystemUtil.CopyFilesRecursively(tempOutputDir, outputDir);
|
||||
|
||||
var finalDllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest);
|
||||
var finalManifestFile = LocalPluginManifest.GetManifestFile(finalDllFile);
|
||||
var finalManifest = LocalPluginManifest.Load(finalManifestFile) ??
|
||||
throw new Exception("Plugin had no valid manifest after copy");
|
||||
|
||||
Log.Information("Installed plugin {InternalName} (testing={UseTesting})", tempManifest.Name, useTesting);
|
||||
var plugin = await this.LoadPluginAsync(finalDllFile, finalManifest, reason);
|
||||
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
return plugin;
|
||||
}
|
||||
|
||||
var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest);
|
||||
var manifestFile = LocalPluginManifest.GetManifestFile(dllFile);
|
||||
|
||||
// We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use.
|
||||
Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented));
|
||||
|
||||
// Reload as a local manifest, add some attributes, and save again.
|
||||
var manifest = LocalPluginManifest.Load(manifestFile);
|
||||
|
||||
if (manifest == null)
|
||||
throw new Exception("Plugin had no valid manifest");
|
||||
|
||||
if (manifest.InternalName != repoManifest.InternalName)
|
||||
catch
|
||||
{
|
||||
Directory.Delete(outputDir.FullName, true);
|
||||
throw new Exception(
|
||||
$"Distributed internal name does not match repo internal name: {manifest.InternalName} - {repoManifest.InternalName}");
|
||||
// Attempt to clean up if we can
|
||||
try
|
||||
{
|
||||
outputDir.Delete(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Write marker file if we can't, we'll try to do it at the next start
|
||||
File.WriteAllText(Path.Combine(pluginVersionsDir.FullName, BrokenMarkerFileName), string.Empty);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (manifest.WorkingPluginId != Guid.Empty)
|
||||
throw new Exception("Plugin shall not specify a WorkingPluginId");
|
||||
|
||||
manifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid();
|
||||
|
||||
if (useTesting)
|
||||
finally
|
||||
{
|
||||
manifest.Testing = true;
|
||||
tempOutputDir.Delete(true);
|
||||
}
|
||||
|
||||
// Document the url the plugin was installed from
|
||||
manifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty ? repoManifest.SourceRepo.PluginMasterUrl : SpecialPluginSource.MainRepo;
|
||||
|
||||
manifest.Save(manifestFile, "installation");
|
||||
|
||||
Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})");
|
||||
|
||||
var plugin = await this.LoadPluginAsync(dllFile, manifest, reason);
|
||||
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1547,7 +1572,6 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// <returns>The loaded plugin.</returns>
|
||||
private async Task<LocalPlugin> LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false)
|
||||
{
|
||||
var name = manifest?.Name ?? dllFile.Name;
|
||||
var loadPlugin = !doNotLoad;
|
||||
|
||||
LocalPlugin? plugin;
|
||||
|
|
@ -1560,7 +1584,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
|
||||
if (isDev)
|
||||
{
|
||||
Log.Information($"Loading dev plugin {name}");
|
||||
Log.Information("Loading dev plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalDevPlugin(dllFile, manifest);
|
||||
|
||||
// This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet
|
||||
|
|
@ -1569,7 +1593,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Information($"Loading plugin {name}");
|
||||
Log.Information("Loading plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalPlugin(dllFile, manifest);
|
||||
}
|
||||
|
||||
|
|
@ -1663,7 +1687,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Verbose($"{name} not loaded, wantToLoad:{wantedByAnyProfile} orphaned:{plugin.IsOrphaned}");
|
||||
Log.Verbose("{Name} not loaded, wantToLoad:{WantedByAnyProfile} orphaned:{IsOrphaned}", manifest.InternalName, wantedByAnyProfile, plugin.IsOrphaned);
|
||||
}
|
||||
}
|
||||
catch (InvalidPluginException)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal class ProfileManager : IServiceType
|
|||
/// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins.
|
||||
/// </summary>
|
||||
public bool IsBusy => this.isBusy;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a disposable that will lock the profile list while it is not disposed.
|
||||
/// You must NEVER use this in async code.
|
||||
|
|
@ -77,7 +77,7 @@ internal class ProfileManager : IServiceType
|
|||
{
|
||||
var want = false;
|
||||
var wasInAnyProfile = false;
|
||||
|
||||
|
||||
lock (this.profiles)
|
||||
{
|
||||
foreach (var profile in this.profiles)
|
||||
|
|
@ -93,7 +93,7 @@ internal class ProfileManager : IServiceType
|
|||
|
||||
if (!wasInAnyProfile && addIfNotDeclared)
|
||||
{
|
||||
Log.Warning("'{Guid}'('{InternalName}') was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState);
|
||||
Log.Warning("{Guid}({InternalName}) was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState);
|
||||
await this.DefaultProfile.AddOrUpdateAsync(workingPluginId, internalName, defaultState, false);
|
||||
|
||||
return defaultState;
|
||||
|
|
@ -175,7 +175,7 @@ internal class ProfileManager : IServiceType
|
|||
{
|
||||
// Disable it
|
||||
modelV1.IsEnabled = false;
|
||||
|
||||
|
||||
// Try to find matching plugins for all plugins in the profile
|
||||
var pm = Service<PluginManager>.Get();
|
||||
foreach (var plugin in modelV1.Plugins)
|
||||
|
|
@ -313,7 +313,7 @@ internal class ProfileManager : IServiceType
|
|||
profile.MigrateProfilesToGuidsForPlugin(internalName, newGuid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validate profiles for errors.
|
||||
/// </summary>
|
||||
|
|
@ -328,7 +328,7 @@ internal class ProfileManager : IServiceType
|
|||
{
|
||||
if (seenIds.Contains(pluginEntry.WorkingPluginId))
|
||||
throw new Exception($"Plugin '{pluginEntry.WorkingPluginId}'('{pluginEntry.InternalName}') is twice in profile '{profile.Guid}'('{profile.Name}')");
|
||||
|
||||
|
||||
seenIds.Add(pluginEntry.WorkingPluginId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -605,6 +605,10 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
if (this.loader != null)
|
||||
return;
|
||||
|
||||
this.DllFile.Refresh();
|
||||
if (!this.DllFile.Exists)
|
||||
throw new Exception($"Plugin DLL file at '{this.DllFile.FullName}' did not exist, cannot load.");
|
||||
|
||||
try
|
||||
{
|
||||
this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
||||
|
|
|
|||
|
|
@ -57,15 +57,15 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest
|
|||
/// <param name="reason">The reason the manifest was saved.</param>
|
||||
public void Save(FileInfo manifestFile, string reason)
|
||||
{
|
||||
Log.Verbose("Saving manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason);
|
||||
Log.Verbose("Saving manifest for {PluginName} because {Reason}", this.InternalName, reason);
|
||||
|
||||
try
|
||||
{
|
||||
Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
FilesystemUtil.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Error("Could not write out manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason);
|
||||
Log.Error("Could not write out manifest for {PluginName} because {Reason}", this.InternalName, reason);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest
|
|||
public static LocalPluginManifest? Load(FileInfo manifestFile) => JsonConvert.DeserializeObject<LocalPluginManifest>(File.ReadAllText(manifestFile.FullName));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file.
|
||||
/// </summary>
|
||||
/// <param name="dir">Manifest directory.</param>
|
||||
/// <param name="manifest">The manifest.</param>
|
||||
|
|
@ -86,7 +86,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest
|
|||
public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll"));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist.
|
||||
/// A standardized way to get the manifest file that should accompany a plugin DLL.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> file.</returns>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue