|
8 | 8 | using System.Collections.Generic;
|
9 | 9 | using System.Globalization;
|
10 | 10 | using System.IO;
|
| 11 | +using System.IO.Compression; |
11 | 12 | using System.Linq;
|
12 | 13 | using System.Management.Automation;
|
13 | 14 | using System.Net;
|
@@ -936,7 +937,11 @@ private bool TryInstallToTempPath(
|
936 | 937 | var pkgVersion = pkgToInstall.Version.ToString();
|
937 | 938 | var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName.ToLower(), pkgVersion);
|
938 | 939 | Directory.CreateDirectory(tempDirNameVersion);
|
939 |
| - System.IO.Compression.ZipFile.ExtractToDirectory(pathToFile, tempDirNameVersion); |
| 940 | + |
| 941 | + if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out error)) |
| 942 | + { |
| 943 | + return false; |
| 944 | + } |
940 | 945 |
|
941 | 946 | File.Delete(pathToFile);
|
942 | 947 |
|
@@ -1146,6 +1151,71 @@ private bool TrySaveNupkgToTempPath(
|
1146 | 1151 | }
|
1147 | 1152 | }
|
1148 | 1153 |
|
| 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 | + |
1149 | 1219 | /// <summary>
|
1150 | 1220 | /// Moves package files/directories from the temp install path into the final install path location.
|
1151 | 1221 | /// </summary>
|
|
0 commit comments