Harden assembly timestamp handling
This commit is contained in:
@@ -1,62 +1,84 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Utils
|
||||
{
|
||||
//=======================================================================
|
||||
public static class AssemblyUtil
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
#region Gets the build date and time (by reading the COFF header)
|
||||
// http://msdn.microsoft.com/en-us/library/ms680313
|
||||
// --- Definitions ---
|
||||
|
||||
struct _IMAGE_FILE_HEADER
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public ushort Machine;
|
||||
public ushort NumberOfSections;
|
||||
public uint TimeDateStamp;
|
||||
public uint PointerToSymbolTable;
|
||||
public uint NumberOfSymbols;
|
||||
public ushort SizeOfOptionalHeader;
|
||||
public ushort Characteristics;
|
||||
#pragma warning restore CS0649
|
||||
};
|
||||
private const Int32 DosHeaderPeOffsetPosition = 0x3c;
|
||||
private const UInt32 PeSignature = 0x00004550;
|
||||
private const Int32 CoffTimeDateStampPosition = 8;
|
||||
|
||||
// --- Build Information ---
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC date and time represented by the assembly's PE/COFF timestamp.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deterministic builds may not store the physical build time in this field.
|
||||
/// Use <see cref="TryGetBuildDateTime(Assembly, out DateTime)"/> when the caller
|
||||
/// needs to distinguish an unavailable timestamp from <see cref="DateTime.MinValue"/>.
|
||||
/// </remarks>
|
||||
|
||||
public static DateTime GetBuildDateTime(this Assembly assembly)
|
||||
{
|
||||
var path = assembly.Location;
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
|
||||
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
fileStream.Position = 0x3C;
|
||||
fileStream.Read(buffer, 0, 4);
|
||||
fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
|
||||
fileStream.Read(buffer, 0, 4); // "PE\0\0"
|
||||
fileStream.Read(buffer, 0, buffer.Length);
|
||||
}
|
||||
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));
|
||||
DateTime buildDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
return buildDate.AddSeconds(coffHeader.TimeDateStamp);
|
||||
//return TimeZoneInfo.Local.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
|
||||
}
|
||||
finally
|
||||
{
|
||||
pinnedBuffer.Free();
|
||||
}
|
||||
}
|
||||
return new DateTime();
|
||||
return TryGetBuildDateTime(assembly, out DateTime buildDateTime)
|
||||
? buildDateTime
|
||||
: DateTime.MinValue;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Tries to get the UTC date and time represented by the assembly's PE/COFF timestamp.
|
||||
/// </summary>
|
||||
public static Boolean TryGetBuildDateTime(this Assembly assembly, out DateTime buildDateTime)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assembly);
|
||||
|
||||
if (String.IsNullOrEmpty(assembly.Location))
|
||||
{
|
||||
buildDateTime = DateTime.MinValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetBuildDateTime(assembly.Location, out buildDateTime);
|
||||
}
|
||||
|
||||
// --- PE Header Parsing ---
|
||||
|
||||
internal static Boolean TryGetBuildDateTime(String assemblyPath, out DateTime buildDateTime)
|
||||
{
|
||||
buildDateTime = DateTime.MinValue;
|
||||
|
||||
try
|
||||
{
|
||||
using FileStream fileStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using BinaryReader reader = new BinaryReader(fileStream);
|
||||
|
||||
fileStream.Position = DosHeaderPeOffsetPosition;
|
||||
UInt32 coffHeaderOffset = reader.ReadUInt32();
|
||||
fileStream.Position = coffHeaderOffset;
|
||||
if (reader.ReadUInt32() != PeSignature)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fileStream.Position = coffHeaderOffset + CoffTimeDateStampPosition;
|
||||
UInt32 timeDateStamp = reader.ReadUInt32();
|
||||
buildDateTime = DateTime.UnixEpoch.AddSeconds(timeDateStamp);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception) when (exception is IOException
|
||||
or UnauthorizedAccessException
|
||||
or ArgumentException
|
||||
or NotSupportedException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- The End ---
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user