| File: PackageExtraction\ZipArchiveExtensions.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Threading; using NuGet.Common; namespace NuGet.Packaging { /// <summary> /// Nupkg reading helper methods /// </summary> public static class ZipArchiveExtensions { public static ZipArchiveEntry LookupEntry(this ZipArchive zipArchive, string path) { var entry = zipArchive.Entries.FirstOrDefault(zipEntry => UnescapePath(zipEntry.FullName) == path); if (entry == null) { throw new FileNotFoundException(path); } return entry; } public static IEnumerable<string> GetFiles(this ZipArchive zipArchive) { return zipArchive.Entries.Select(e => UnescapePath(e.FullName)); } private static string UnescapePath(string path) { if (path != null #if NETCOREAPP && path.IndexOf('%', StringComparison.Ordinal) > -1) #else && path.IndexOf('%') > -1) #endif { return Uri.UnescapeDataString(path); } return path!; } public static Stream OpenFile(this ZipArchive zipArchive, string path) { var entry = LookupEntry(zipArchive, path); return entry.Open(); } public static string SaveAsFile(this ZipArchiveEntry entry, string fileFullPath, ILogger logger) { using (var inputStream = entry.Open()) { inputStream.CopyToFile(fileFullPath); } entry.UpdateFileTimeFromEntry(fileFullPath, logger); return fileFullPath; } public static void UpdateFileTimeFromEntry(this ZipArchiveEntry entry, string fileFullPath, ILogger logger) { Testable.Default.UpdateFileTimeFromEntry(entry, fileFullPath, logger); } internal static void UpdateFileTime(string fileFullPath, DateTime dateTime) { Testable.Default.UpdateFileTimeEntry(fileFullPath, dateTime); } internal class Testable { public static Testable Default { get; } = new Testable(EnvironmentVariableWrapper.Instance); internal Testable(IEnvironmentVariableReader environmentVariableReader) { _updateFileTimeFromEntryMaxRetries = 9; string? value = environmentVariableReader.GetEnvironmentVariable("NUGET_UPDATEFILETIME_MAXRETRIES"); if (int.TryParse(value, out int maxRetries) && maxRetries > 0) { _updateFileTimeFromEntryMaxRetries = maxRetries; } } private readonly int _updateFileTimeFromEntryMaxRetries; internal void UpdateFileTimeFromEntry(ZipArchiveEntry entry, string fileFullPath, ILogger logger) { if (entry == null) throw new ArgumentNullException(nameof(entry)); if (fileFullPath == null) throw new ArgumentNullException(nameof(fileFullPath)); logger ??= NullLogger.Instance; FileAttributes attr = File.GetAttributes(fileFullPath); if (!attr.HasFlag(FileAttributes.Directory) && entry.LastWriteTime.DateTime != DateTime.MinValue && // Ignore invalid times entry.LastWriteTime.UtcDateTime <= DateTime.UtcNow) // Ignore future times { try { DateTime dateTime = entry.LastWriteTime.Add(entry.LastWriteTime.Offset).UtcDateTime; UpdateFileTimeEntry(fileFullPath, dateTime); } catch (ArgumentOutOfRangeException ex) { string message = string.Format( CultureInfo.CurrentCulture, Strings.FailedFileTime, fileFullPath, // {0} ex.Message); // {1} logger.LogVerbose(message); } } } internal void UpdateFileTimeEntry(string fileFullPath, DateTime dateTime) { if (string.IsNullOrEmpty(fileFullPath)) throw new ArgumentNullException(nameof(fileFullPath)); int retry = 0; bool successful = false; while (!successful) { try { File.SetLastWriteTimeUtc(fileFullPath, dateTime); successful = true; } catch (IOException) when (retry < _updateFileTimeFromEntryMaxRetries) { // Use exponentional backoff, to reduce CPU usage, allowing other threads to work, even if this // isn't an async method and therefore requires the ThreadPool to spin up new threads. Thread.Sleep(1 << retry); retry++; } } } } } }