Skip to content

Commit 8f32b68

Browse files
alericksonanamnavi
authored andcommitted
Add method to extract nupkg to directory (#1456)
1 parent 9333ddd commit 8f32b68

File tree

1 file changed

+71
-1
lines changed

1 file changed

+71
-1
lines changed

src/code/InstallHelper.cs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Globalization;
1010
using System.IO;
11+
using System.IO.Compression;
1112
using System.Linq;
1213
using System.Management.Automation;
1314
using System.Net;
@@ -936,7 +937,11 @@ private bool TryInstallToTempPath(
936937
var pkgVersion = pkgToInstall.Version.ToString();
937938
var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName.ToLower(), pkgVersion);
938939
Directory.CreateDirectory(tempDirNameVersion);
939-
System.IO.Compression.ZipFile.ExtractToDirectory(pathToFile, tempDirNameVersion);
940+
941+
if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out error))
942+
{
943+
return false;
944+
}
940945

941946
File.Delete(pathToFile);
942947

@@ -1146,6 +1151,71 @@ private bool TrySaveNupkgToTempPath(
11461151
}
11471152
}
11481153

1154+
/// <summary>
1155+
/// Extracts files from .nupkg
1156+
/// Similar functionality as System.IO.Compression.ZipFile.ExtractToDirectory,
1157+
/// but while ExtractToDirectory cannot overwrite files, this method can.
1158+
/// </summary>
1159+
private bool TryExtractToDirectory(string zipPath, string extractPath, out ErrorRecord error)
1160+
{
1161+
error = null;
1162+
// Normalize the path
1163+
extractPath = Path.GetFullPath(extractPath);
1164+
1165+
// Ensures that the last character on the extraction path is the directory separator char.
1166+
// Without this, a malicious zip file could try to traverse outside of the expected extraction path.
1167+
if (!extractPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
1168+
{
1169+
extractPath += Path.DirectorySeparatorChar;
1170+
}
1171+
1172+
try
1173+
{
1174+
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
1175+
{
1176+
foreach (ZipArchiveEntry entry in archive.Entries)
1177+
{
1178+
// If a file has one or more parent directories.
1179+
if (entry.FullName.Contains(Path.DirectorySeparatorChar) || entry.FullName.Contains(Path.AltDirectorySeparatorChar))
1180+
{
1181+
// Create the parent directories if they do not already exist
1182+
var lastPathSeparatorIdx = entry.FullName.Contains(Path.DirectorySeparatorChar) ?
1183+
entry.FullName.LastIndexOf(Path.DirectorySeparatorChar) : entry.FullName.LastIndexOf(Path.AltDirectorySeparatorChar);
1184+
var parentDirs = entry.FullName.Substring(0, lastPathSeparatorIdx);
1185+
var destinationDirectory = Path.Combine(extractPath, parentDirs);
1186+
if (!Directory.Exists(destinationDirectory))
1187+
{
1188+
Directory.CreateDirectory(destinationDirectory);
1189+
}
1190+
}
1191+
1192+
// Gets the full path to ensure that relative segments are removed.
1193+
string destinationPath = Path.GetFullPath(Path.Combine(extractPath, entry.FullName));
1194+
1195+
// Validate that the resolved output path starts with the resolved destination directory.
1196+
// For example, if a zip file contains a file entry ..\sneaky-file, and the zip file is extracted to the directory c:\output,
1197+
// then naively combining the paths would result in an output file path of c:\output\..\sneaky-file, which would cause the file to be written to c:\sneaky-file.
1198+
if (destinationPath.StartsWith(extractPath, StringComparison.Ordinal))
1199+
{
1200+
entry.ExtractToFile(destinationPath, overwrite: true);
1201+
}
1202+
}
1203+
}
1204+
}
1205+
catch (Exception e)
1206+
{
1207+
error = new ErrorRecord(
1208+
new Exception($"Error occured while extracting .nupkg: '{e.Message}'"),
1209+
"ErrorExtractingNupkg",
1210+
ErrorCategory.OperationStopped,
1211+
_cmdletPassedIn);
1212+
1213+
return false;
1214+
}
1215+
1216+
return true;
1217+
}
1218+
11491219
/// <summary>
11501220
/// Moves package files/directories from the temp install path into the final install path location.
11511221
/// </summary>

0 commit comments

Comments
 (0)