| File: ReleaseFile.cs | Web Access |
| Project: src\deployment-tools\src\Microsoft.Deployment.DotNet.Releases\src\Microsoft.Deployment.DotNet.Releases.csproj (Microsoft.Deployment.DotNet.Releases) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; using System.Security.Cryptography; using System.Text.Json; using System.Threading.Tasks; namespace Microsoft.Deployment.DotNet.Releases { /// <summary> /// Represents a single file associated with a release component such as an SDK or runtime. /// </summary> public class ReleaseFile : IEquatable<ReleaseFile> { private static readonly SHA512 s_defaultHashAlgorithm = SHA512.Create(); private Uri _address; private string _addressString; /// <summary> /// The URL from where to download the file. /// </summary> public Uri Address { get => _address ??= _addressString != null ? new Uri(_addressString) : null; private set { _address = value; _addressString = value?.OriginalString; } } /// <summary> /// The filename and extension of this <see cref="ReleaseFile"/>. /// </summary> public string FileName => Path.GetFileName(Address.LocalPath); /// <summary> /// The <see cref="SHA512"/> hash of the file. /// </summary> public string Hash { get; private set; } /// <summary> /// The version agnostic name and extension of the file. /// </summary> public string Name { get; private set; } /// <summary> /// The runtime identifier associated with the file. /// </summary> public string Rid { get; private set; } /// <summary> /// Creates a new <see cref="ReleaseFile"/> instance from a <see cref="JsonElement"/>. /// </summary> /// <param name="fileElement">The <see cref="JsonElement"/> to deserialize.</param> internal ReleaseFile(JsonElement fileElement) { _addressString = fileElement.GetStringOrDefault("url"); Hash = fileElement.GetStringOrDefault("hash"); Name = fileElement.GetStringOrDefault("name"); Rid = fileElement.GetStringOrDefault("rid"); } /// <summary> /// Creates a new <see cref="ReleaseFile"/> instance. /// </summary> /// <param name="address">The URL of the file.</param> /// <param name="hash">A string containing the SHA512 hash of the file.</param> /// <param name="name">The name and extension of the file.</param> /// <param name="rid">The RID associated with the file.</param> internal ReleaseFile(Uri address, string hash, string name, string rid) { Address = address; Hash = hash; Name = name; Rid = rid; } /// <summary> /// Download this file to the specified local file and verify the file hash. If the destination file exists, the new copy is /// downloaded to a temporary file before verifying its hash. If the hash check fails, the temporary file is deleted. Otherwise, /// the temporary file is copied to the destination path. If the destination file does not exist, the file is downloaded and /// the hash is verified. If the hash check fails, the destination file is deleted. /// </summary> /// <param name="destinationPath">The path, including the filename of the local file. The file will be /// overwritten if it already exists if the hash check passed.</param> /// <exception cref="InvalidDataException">Thrown if the downloaded file's hash does to match the /// expected hash.</exception> public async Task DownloadAsync(string destinationPath) { if (destinationPath is null) { throw new ArgumentNullException(nameof(destinationPath)); } if (destinationPath == string.Empty) { throw new ArgumentException(ReleasesResources.ValueCannotBeEmpty, nameof(destinationPath)); } // If the destination file doesn't exist we can skip using an actual temporary file. string tempPath = !File.Exists(destinationPath) ? destinationPath : Path.GetTempFileName(); await Utils.DownloadFileAsync(Address, tempPath).ConfigureAwait(false); // Most of the files are large since they represent full installations of .NET/.NET Core. They can // easily be 100MB+ so we won't verify the hash in memory. string actualHash = Utils.GetFileHash(tempPath, s_defaultHashAlgorithm); if (!string.Equals(Hash, actualHash, StringComparison.OrdinalIgnoreCase)) { File.Delete(tempPath); throw new InvalidDataException(string.Format(ReleasesResources.HashMismatch, Hash, actualHash, destinationPath)); } // Replace the destination file if the hash verified successfully and we used an actual temporary file. if (!string.Equals(destinationPath, tempPath)) { File.Delete(destinationPath); File.Move(tempPath, destinationPath); } } /// <summary> /// Determines whether the specified object is equal to this instance. /// </summary> /// <param name="obj">The object to compare to the current object.</param> /// <returns><see langword="true"/> if the specified object is equal to the current object; <see langword="false"/> otherwise.</returns> public override bool Equals(object obj) { return Equals((ReleaseFile)obj); } /// <summary> /// Determines whether the specified <see cref="ReleaseFile"/> is equal to this instance. /// </summary> /// <param name="other">The <see cref="ReleaseFile"/> to compare to this instance.</param> /// <returns><see langword="true"/> if the specified <see cref="ReleaseFile"/> is equal to this instance; <see langword="false"/> otherwise.</returns> public bool Equals(ReleaseFile other) { return ReferenceEquals(this, other) || Name == other.Name && Rid == other.Rid && Hash == other.Hash && _addressString == other._addressString; } /// <summary> /// Returns the hash code for this release file. /// </summary> /// <returns>A hash code for the current object.</returns> public override int GetHashCode() => Hash.GetHashCode(); internal static ReleaseFile Create(string hash, string name, string rid, string address) => new ReleaseFile(new Uri(address), hash, name, rid); } }