Skip to content

Commit

Permalink
- lib_compiled: Various minor cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
MatejKafka committed Apr 1, 2024
1 parent 377d18b commit 69116c8
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Pog.InnerCommands;
using Pog.InnerCommands.Common;
using Pog.Utils;
using PPaths = Pog.PathConfig.PackagePaths;

namespace Pog.Commands.ContainerCommands;

Expand Down Expand Up @@ -35,11 +36,11 @@ public class InstallFromUrlCommand : PogCmdlet {
protected override void BeginProcessing() {
base.BeginProcessing();
_packageDirPath = SessionState.Path.CurrentLocation.ProviderPath;
_appDirPath = $@"{_packageDirPath}\{PathConfig.PackagePaths.AppDirName}";
_newAppDirPath = $@"{_packageDirPath}\{PathConfig.PackagePaths.NewAppDirName}";
_oldAppDirPath = $@"{_packageDirPath}\{PathConfig.PackagePaths.AppBackupDirName}";
_extractionDirPath = $@"{_packageDirPath}\{PathConfig.PackagePaths.TmpExtractionDirName}";
_tmpDeletePath = $@"{_packageDirPath}\{PathConfig.PackagePaths.TmpDeleteDirName}";
_appDirPath = $@"{_packageDirPath}\{PPaths.AppDirName}";
_newAppDirPath = $@"{_packageDirPath}\{PPaths.NewAppDirName}";
_oldAppDirPath = $@"{_packageDirPath}\{PPaths.AppBackupDirName}";
_extractionDirPath = $@"{_packageDirPath}\{PPaths.TmpExtractionDirName}";
_tmpDeletePath = $@"{_packageDirPath}\{PPaths.TmpDeleteDirName}";

// read download parameters from the global container info variable
var internalInfo = Container.ContainerInternalInfo.GetCurrent(this);
Expand Down Expand Up @@ -99,7 +100,7 @@ protected override void ProcessRecord() {
PackageInstallParametersArchive pa => pa.Target,
_ => throw new UnreachableException(),
};
var targetPath = target == null ? _newAppDirPath : JoinValidateSubdirectory(_newAppDirPath, target);
var targetPath = target == null ? _newAppDirPath : FsUtils.JoinValidateSubdirectory(_newAppDirPath, target);

if (targetPath == null) {
// the target path escapes from the app directory
Expand Down Expand Up @@ -152,21 +153,6 @@ public override void Dispose() {
// (and if it isn't, it probably also won't work here)
}

/// <remarks>Assumes that both paths are resolved and cleaned.</remarks>
private static bool EscapesDirectory(string basePath, string validatedPath) {
return !validatedPath.StartsWith(basePath + '\\') && validatedPath != basePath;
}

/// If `subdirectoryPath` stays inside `basePath`, return the combined path, otherwise return null.
/// Use this function when resolving an untrusted relative path.
private static string? JoinValidateSubdirectory(string basePath, string subdirectoryPath) {
var combined = Path.GetFullPath(basePath + '\\' + subdirectoryPath.TrimEnd('/', '\\'));
if (EscapesDirectory(basePath, combined)) {
return null;
}
return combined;
}

private void InstallNoArchive(SharedFileCache.IFileLock downloadedFile, string targetPath) {
// ensure that the parent directory exists
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
Expand All @@ -182,7 +168,7 @@ private void InstallArchive(PackageInstallParametersArchive param,
InvokePogCommand(new ExpandArchive7Zip(this) {
ArchivePath = downloadedFile.Path,
TargetPath = _extractionDirPath,
Filter = param.Subdirectory == null ? null : new[] {param.Subdirectory},
Filter = param.Subdirectory == null ? null : [param.Subdirectory],
});
}

Expand Down Expand Up @@ -242,16 +228,14 @@ private DirectoryInfo GetExtractedSubdirectory(string extractedRootPath, string?

var subPath = resolvedPaths[0]!;

if (EscapesDirectory(extractedRootPath, subPath)) {
if (FsUtils.EscapesDirectory(extractedRootPath, subPath)) {
ThrowTerminatingArgumentError(subdirectory, "SubdirectoryEscapesRoot",
$"Argument passed to the -Subdirectory parameter must be a relative path that does not escape " +
$"the archive directory, got '{subdirectory}'.");
}

// test if the path exists in the extracted directory
var sub = new DirectoryInfo(subPath);
if ((int) sub.Attributes == -1) {}

if ((sub.Attributes & FileAttributes.Directory) == 0) {
// it's actually a file
// use the parent directory, 7zip should have only extracted the file we're interested in
Expand Down
30 changes: 30 additions & 0 deletions app/Pog/lib_compiled/Pog/src/FsUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -76,6 +78,16 @@ public static bool FileContentEqual(byte[] f1, FileInfo f2) {
return f1.Length == f2.Length && f1.SequenceEqual(File.ReadAllBytes(f2.FullName));
}

public static bool FileContentEqual(ZipArchiveEntry f1, FileInfo f2) {
if (f1.Length != f2.Length) return false;

var f1Content = new byte[f1.Length];
using var f1Stream = f1.Open();
var bytesRead = f1Stream.Read(f1Content, 0, (int) f1.Length);
Debug.Assert(bytesRead == f1.Length);
return f1Content.SequenceEqual(File.ReadAllBytes(f2.FullName));
}

public static bool FileContentEqual(FileInfo f1, FileInfo f2) {
// significantly faster than trying to do a streaming implementation
return f1.Length == f2.Length && File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
Expand Down Expand Up @@ -169,6 +181,8 @@ public static bool EnsureDeleteFile(string filePath) {
return true;
} catch (FileNotFoundException) {
return false;
} catch (DirectoryNotFoundException) {
return false; // thrown when parent dir does not exist
}
}

Expand Down Expand Up @@ -302,4 +316,20 @@ public static bool IsDirectoryLocked(string directoryPath) {
// move directory to itself; this returns false when the directory contains anything locked, and is a no-op otherwise
return !MoveDirectoryUnlocked(directoryPath, directoryPath);
}

/// <remarks>Assumes that both paths are resolved and cleaned.</remarks>
public static bool EscapesDirectory(string basePath, string validatedPath) {
return !validatedPath.StartsWith(basePath + '\\') && validatedPath != basePath;
}

/// If `subdirectoryPath` stays inside `basePath`, return the combined path, otherwise return null.
/// Assumes that <paramref name="basePath"/> is resolved in canonical form.
/// Use this function when resolving an untrusted relative path.
public static string? JoinValidateSubdirectory(string basePath, string subdirectoryPath) {
var combined = Path.GetFullPath(basePath + '\\' + subdirectoryPath.TrimEnd('/', '\\'));
if (EscapesDirectory(basePath, combined)) {
return null;
}
return combined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,14 @@ public enum UserAgentType {
}
}

public class InvokeCachedFileDownload : ScalarCommand<SharedFileCache.IFileLock> {
public class InvokeCachedFileDownload(PogCmdlet cmdlet) : ScalarCommand<SharedFileCache.IFileLock>(cmdlet) {
[Parameter(Mandatory = true)] public string SourceUrl = null!;
[Parameter(Mandatory = true)] public string? ExpectedHash;
[Parameter(Mandatory = true)] public DownloadParameters DownloadParameters = null!;
[Parameter(Mandatory = true)] public Package Package = null!;
[Parameter] public bool StoreInCache = false;
[Parameter] public CmdletProgressBar.ProgressActivity ProgressActivity = new();

public InvokeCachedFileDownload(PogCmdlet cmdlet) : base(cmdlet) {}

// TODO: handle `InvalidCacheEntryException` everywhere
public override SharedFileCache.IFileLock Invoke() {
Debug.Assert(ExpectedHash == null || !StoreInCache);
Expand All @@ -46,7 +44,7 @@ public override SharedFileCache.IFileLock Invoke() {

if (ExpectedHash != null) {
WriteDebug($"Checking if we have a cached copy for '{ExpectedHash}'...");
var entryLock = GetEntryLockedWithCleanup(ExpectedHash, Package);
var entryLock = GetEntryLockedWithCleanup(ExpectedHash);
if (entryLock != null) {
WriteInformation($"File retrieved from the local cache: '{SourceUrl}'");
// do not validate the hash; it was already validated once when the entry was first downloaded;
Expand Down Expand Up @@ -108,7 +106,7 @@ private SharedFileCache.CacheEntryLock AddEntryToCache(string hash, SharedFileCa
return InternalState.DownloadCache.AddEntryLocked(hash, entry);
} catch (CacheEntryAlreadyExistsException) {
WriteVerbose("File is already cached.");
var entryLock = GetEntryLockedWithCleanup(hash, Package);
var entryLock = GetEntryLockedWithCleanup(hash);
if (entryLock == null) {
continue; // retry
}
Expand All @@ -126,9 +124,9 @@ private SharedFileCache.CacheEntryLock AddEntryToCache(string hash, SharedFileCa
}
}

private SharedFileCache.CacheEntryLock? GetEntryLockedWithCleanup(string hash, Package package) {
private SharedFileCache.CacheEntryLock? GetEntryLockedWithCleanup(string hash) {
try {
return InternalState.DownloadCache.GetEntryLocked(hash, package);
return InternalState.DownloadCache.GetEntryLocked(hash, Package);
} catch (InvalidCacheEntryException) {
WriteWarning($"Found an invalid download cache entry '{hash}', replacing...");
// TODO: figure out how to handle the failure more gracefully (maybe skip the cache all-together
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@

namespace Pog.InnerCommands;

public class InvokeContainer : EnumerableCommand<PSObject> {
public class InvokeContainer(PogCmdlet cmdlet) : EnumerableCommand<PSObject>(cmdlet) {
[Parameter(Mandatory = true)] public Container.ContainerType ContainerType;
[Parameter(Mandatory = true)] public Package Package = null!;
[Parameter] public Hashtable? InternalArguments;
[Parameter] public Hashtable? PackageArguments;

private Container? _container;

public InvokeContainer(PogCmdlet cmdlet) : base(cmdlet) {}

public override IEnumerable<PSObject> Invoke() {
_container = new Container(ContainerType, Package, InternalArguments, PackageArguments, Host,
ReadStreamPreferenceVariables());
Expand Down
7 changes: 4 additions & 3 deletions app/Pog/lib_compiled/Pog/src/Pog.Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public sealed class Container : IDisposable {
/// To read the output streams, use the <see cref="Streams"/> property after <see cref="BeginInvoke"/> was called.
/// </param>
/// <param name="streamConfig">Configuration for output stream preference variables.</param>
public Container(ContainerType containerType, Package package, Hashtable? internalArguments, Hashtable? packageArguments,
public Container(ContainerType containerType, Package package,
Hashtable? internalArguments, Hashtable? packageArguments,
PSHost? host, OutputStreamConfig streamConfig) {
_containerType = containerType;
_package = package;
Expand Down Expand Up @@ -141,7 +142,7 @@ private InitialSessionState CreateInitialSessionState() {
});

var containerDir = InternalState.PathConfig.ContainerDir;
iss.ImportPSModule(new[] {
iss.ImportPSModule([
// these two imports contain basic stuff needed for printing output, errors, FS traversal,...
"Microsoft.PowerShell.Management",
"Microsoft.PowerShell.Utility",
Expand All @@ -154,7 +155,7 @@ private InitialSessionState CreateInitialSessionState() {
ContainerType.GetInstallHash => $@"{containerDir}\Install\Env_GetInstallHash.psm1",
_ => throw new ArgumentOutOfRangeException(nameof(_containerType), _containerType, null),
},
});
]);

// TODO: figure out if we can define this without having to write inline PowerShell function
// override Import-Module to hide the default verbose prints when -Verbose is set for the container environment
Expand Down
11 changes: 3 additions & 8 deletions app/Pog/lib_compiled/Pog/src/Pog.SharedFileCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,12 @@ public class CacheEntryInUseException(string entryKey)
// - second one has any other name, and is the actual cache entry; during insertion, if the entry file is also called
// `referencingPackages.json-list`, it is prefixed with `_`
[PublicAPI]
public class SharedFileCache {
public class SharedFileCache(string cacheDirPath, TmpDirectory tmpDir) {
private const string MetadataFileName = "referencingPackages.json-list";

public readonly string Path;
public readonly string Path = cacheDirPath;
/// Directory for temporary files on the same volume as `.Path`, used for adding and removing entries.
private readonly TmpDirectory _tmpDirectory;

public SharedFileCache(string cacheDirPath, TmpDirectory tmpDir) {
Path = cacheDirPath;
_tmpDirectory = tmpDir;
}
private readonly TmpDirectory _tmpDirectory = tmpDir;

public delegate void InvalidCacheEntryCb(InvalidCacheEntryException exception);

Expand Down
11 changes: 3 additions & 8 deletions app/Pog/lib_compiled/Pog/src/Pog.TmpDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Diagnostics;
using System.IO;
using JetBrains.Annotations;
using IOPath = System.IO.Path;

namespace Pog;

Expand All @@ -11,15 +10,11 @@ namespace Pog;
/// because the directory must be at the same partition as the download cache.
/// </summary>
[PublicAPI]
public class TmpDirectory {
public readonly string Path;

public TmpDirectory(string tmpDirPath) {
Path = tmpDirPath;
}
public class TmpDirectory(string tmpDirPath) {
public readonly string Path = tmpDirPath;

public string GetTemporaryPath() {
var path = IOPath.Combine(Path, Guid.NewGuid().ToString());
var path = $"{Path}\\{Guid.NewGuid()}";
// the chance is very small...
Debug.Assert(!Directory.Exists(path) && !File.Exists(path));
return path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Pog.Utils;

public static class FileSystemInfoExtensions {
internal static class FileSystemInfoExtensions {
public static string GetBaseName(this FileSystemInfo info) {
return Path.GetFileNameWithoutExtension(info.Name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Pog.Utils;

public static class EnumerableExtensions {
internal static class EnumerableExtensions {
public static IEnumerable<TOut> SelectOptional<TIn, TOut>(this IEnumerable<TIn> enumerable, Func<TIn, TOut?> selector)
where TOut : class {
return enumerable.Select(selector).WhereNotNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Pog.Utils;

public static class KeyValuePairExtensions {
internal static class KeyValuePairExtensions {
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> keyValuePair,
out TKey key, out TValue value) {
key = keyValuePair.Key;
Expand Down
2 changes: 1 addition & 1 deletion app/Pog/lib_compiled/Pog/src/Utils/LazyDisposable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Pog.Utils;

/// <summary>A <see cref="Lazy{T}"/> object that implements <see cref="IDisposable"/>.</summary>
/// <typeparam name="T">The object being lazily created.</typeparam>
public sealed class LazyDisposable<T> : Lazy<T>, IDisposable where T : IDisposable {
internal sealed class LazyDisposable<T> : Lazy<T>, IDisposable where T : IDisposable {
/// <summary>
/// Initializes a new instance of the <see cref="LazyDisposable{T}"/> class.
/// When lazy initialization occurs, the default constructor is used.
Expand Down

0 comments on commit 69116c8

Please sign in to comment.