mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
Implement EWF archive support.
This commit is contained in:
84
Aaru.Archives/EWF/Consts.cs
Normal file
84
Aaru.Archives/EWF/Consts.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Consts.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains constants for Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
/// <summary>EWF v1 logical evidence signature: "LVF\x09\x0D\x0A\xFF\x00"</summary>
|
||||
static readonly byte[] LVF_SIGNATURE = [0x4C, 0x56, 0x46, 0x09, 0x0D, 0x0A, 0xFF, 0x00];
|
||||
|
||||
/// <summary>EWF v2 logical evidence signature: "LEF2\x0D\x0A\x81\x00"</summary>
|
||||
static readonly byte[] LEF2_SIGNATURE = [0x4C, 0x45, 0x46, 0x32, 0x0D, 0x0A, 0x81, 0x00];
|
||||
|
||||
/// <summary>EWF v1 disk image signature (needed for segment traversal)</summary>
|
||||
static readonly byte[] EVF_SIGNATURE = [0x45, 0x56, 0x46, 0x09, 0x0D, 0x0A, 0xFF, 0x00];
|
||||
|
||||
/// <summary>EWF v2 disk image signature (needed for segment traversal)</summary>
|
||||
static readonly byte[] EVF2_SIGNATURE = [0x45, 0x56, 0x46, 0x32, 0x0D, 0x0A, 0x81, 0x00];
|
||||
|
||||
const int SIGNATURE_LENGTH = 8;
|
||||
|
||||
const string SECTION_TYPE_HEADER = "header";
|
||||
const string SECTION_TYPE_HEADER2 = "header2";
|
||||
const string SECTION_TYPE_VOLUME = "volume";
|
||||
const string SECTION_TYPE_DATA = "data";
|
||||
const string SECTION_TYPE_TABLE = "table";
|
||||
const string SECTION_TYPE_TABLE2 = "table2";
|
||||
const string SECTION_TYPE_SECTORS = "sectors";
|
||||
const string SECTION_TYPE_LTREE = "ltree";
|
||||
const string SECTION_TYPE_LTYPES = "ltypes";
|
||||
const string SECTION_TYPE_DONE = "done";
|
||||
const string SECTION_TYPE_NEXT = "next";
|
||||
|
||||
const int SECTION_DESCRIPTOR_V1_SIZE = 76;
|
||||
const int FILE_HEADER_V1_SIZE = 13;
|
||||
const int FILE_HEADER_V2_SIZE = 32;
|
||||
|
||||
const uint DEFAULT_SECTORS_PER_CHUNK = 64;
|
||||
const uint DEFAULT_BYTES_PER_SECTOR = 512;
|
||||
const uint DEFAULT_CHUNK_SIZE = DEFAULT_SECTORS_PER_CHUNK * DEFAULT_BYTES_PER_SECTOR;
|
||||
|
||||
const uint TABLE_ENTRY_V1_COMPRESSED_FLAG = 0x80000000;
|
||||
const uint TABLE_ENTRY_V1_OFFSET_MASK = 0x7FFFFFFF;
|
||||
|
||||
const int VOLUME_SECTION_SIZE_ENCASE = 1052;
|
||||
const int VOLUME_SECTION_SIZE_SMART = 94;
|
||||
|
||||
const int MAX_CACHE_SIZE = 16777216;
|
||||
|
||||
/// <summary>Ltree header size in bytes</summary>
|
||||
const int LTREE_HEADER_SIZE = 48;
|
||||
}
|
||||
89
Aaru.Archives/EWF/Enums.cs
Normal file
89
Aaru.Archives/EWF/Enums.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Enums.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains enumerations for Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
/// <summary>EWF v2 compression method</summary>
|
||||
enum EwfCompressionMethod : ushort
|
||||
{
|
||||
None = 0,
|
||||
Deflate = 1,
|
||||
Bzip2 = 2
|
||||
}
|
||||
|
||||
/// <summary>EWF v2 section type codes</summary>
|
||||
enum EwfSectionTypeV2 : uint
|
||||
{
|
||||
DeviceInformation = 0x00000001,
|
||||
CaseData = 0x00000002,
|
||||
SectorData = 0x00000003,
|
||||
SectorTable = 0x00000004,
|
||||
ErrorTable = 0x00000005,
|
||||
SessionTable = 0x00000006,
|
||||
IncrementData = 0x00000007,
|
||||
Md5Hash = 0x00000008,
|
||||
Sha1Hash = 0x00000009,
|
||||
RestartData = 0x0000000A,
|
||||
EncryptionKeys = 0x0000000B,
|
||||
MemoryExtentsTable = 0x0000000C,
|
||||
Next = 0x0000000D,
|
||||
FinalInformation = 0x0000000E,
|
||||
Done = 0x0000000F,
|
||||
AnalyticalData = 0x00000010,
|
||||
SingleFilesData = 0x00000020,
|
||||
SingleFilesTree = 0x00000021,
|
||||
SingleFilesMetadata = 0x00000022,
|
||||
SingleFilesSource = 0x00000023
|
||||
}
|
||||
|
||||
/// <summary>LEF file entry flags from opr field</summary>
|
||||
[Flags]
|
||||
enum EwfLefEntryFlags : uint
|
||||
{
|
||||
ReadOnly = 0x00000001,
|
||||
Hidden = 0x00000002,
|
||||
System = 0x00000004,
|
||||
Archive = 0x00000008,
|
||||
Symlink = 0x00000010,
|
||||
Deleted = 0x00000080,
|
||||
HardLink = 0x00001000,
|
||||
AltStream = 0x00002000,
|
||||
Folder = 0x02000000,
|
||||
Sparse = 0x04000000
|
||||
}
|
||||
}
|
||||
115
Aaru.Archives/EWF/EwfArchive.cs
Normal file
115
Aaru.Archives/EWF/EwfArchive.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : EwfArchive.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Manages Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Implements reading Expert Witness Format logical evidence (L01/Lx01) files</summary>
|
||||
public sealed partial class EwfArchive : IArchive
|
||||
{
|
||||
const string MODULE_NAME = "EWF Logical Evidence plugin";
|
||||
uint _bytesPerSector;
|
||||
|
||||
/// <summary>Cache of decompressed chunks</summary>
|
||||
Dictionary<ulong, byte[]> _chunkCache;
|
||||
|
||||
internal uint _chunkSize;
|
||||
|
||||
/// <summary>Map from chunk index to location in segment file</summary>
|
||||
Dictionary<ulong, (int segmentIndex, long offset, uint size, bool compressed)> _chunkTable;
|
||||
|
||||
/// <summary>Compression method for EWF v2</summary>
|
||||
EwfCompressionMethod _compressionMethod;
|
||||
|
||||
/// <summary>Parsed file entries from ltree</summary>
|
||||
List<EwfFileEntry> _entries;
|
||||
|
||||
/// <summary>Whether the image is EWF v2 format</summary>
|
||||
bool _isV2;
|
||||
|
||||
int _maxChunkCache;
|
||||
uint _sectorsPerChunk;
|
||||
|
||||
/// <summary>Ordered list of open segment file streams</summary>
|
||||
List<Stream> _segmentStreams;
|
||||
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Localization.EwfArchive_Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("A7B9F4C1-8E63-4D2B-9A1C-5F7E3D6B8C02");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Author => Authors.NataliaPortillo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Opened { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ArchiveSupportedFeature ArchiveFeatures => ArchiveSupportedFeature.SupportsFilenames |
|
||||
ArchiveSupportedFeature.SupportsCompression |
|
||||
ArchiveSupportedFeature.SupportsSubdirectories |
|
||||
ArchiveSupportedFeature.HasExplicitDirectories |
|
||||
ArchiveSupportedFeature.HasEntryTimestamp |
|
||||
ArchiveSupportedFeature.SupportsXAttrs;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int NumberOfEntries => Opened ? _entries.Count : -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Close()
|
||||
{
|
||||
if(!Opened) return;
|
||||
|
||||
_chunkCache?.Clear();
|
||||
_chunkTable?.Clear();
|
||||
_entries?.Clear();
|
||||
|
||||
if(_segmentStreams != null)
|
||||
{
|
||||
foreach(Stream stream in _segmentStreams) stream.Close();
|
||||
|
||||
_segmentStreams.Clear();
|
||||
}
|
||||
|
||||
Opened = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
122
Aaru.Archives/EWF/EwfFileStream.cs
Normal file
122
Aaru.Archives/EWF/EwfFileStream.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : EwfFileStream.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Stream wrapper for reading file data from EWF logical evidence chunks.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
/// <summary>Read-only stream that reads file data from EWF logical evidence chunks.</summary>
|
||||
sealed class EwfFileStream : Stream
|
||||
{
|
||||
readonly EwfArchive _archive;
|
||||
readonly long _dataOffset;
|
||||
long _position;
|
||||
|
||||
public EwfFileStream(EwfArchive archive, long dataOffset, long dataSize)
|
||||
{
|
||||
_archive = archive;
|
||||
_dataOffset = dataOffset;
|
||||
Length = dataSize;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length { get; }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set => _position = Math.Max(0, Math.Min(value, Length));
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if(_position >= Length) return 0;
|
||||
|
||||
var toRead = (int)Math.Min(count, Length - _position);
|
||||
var totalRead = 0;
|
||||
|
||||
while(totalRead < toRead)
|
||||
{
|
||||
// Calculate which byte in the overall media data this maps to
|
||||
long mediaOffset = _dataOffset + _position;
|
||||
|
||||
// Calculate chunk index and offset within chunk
|
||||
var chunkIndex = (ulong)(mediaOffset / _archive._chunkSize);
|
||||
var offsetInChunk = (int)(mediaOffset % _archive._chunkSize);
|
||||
|
||||
byte[] chunkData = _archive.ReadChunk(chunkIndex);
|
||||
|
||||
if(chunkData == null) break;
|
||||
|
||||
int available = chunkData.Length - offsetInChunk;
|
||||
int toCopy = Math.Min(toRead - totalRead, available);
|
||||
|
||||
Array.Copy(chunkData, offsetInChunk, buffer, offset + totalRead, toCopy);
|
||||
|
||||
totalRead += toCopy;
|
||||
_position += toCopy;
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch(origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = Length + offset;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void Flush() {}
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
192
Aaru.Archives/EWF/Files.cs
Normal file
192
Aaru.Archives/EWF/Files.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Files.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains file access methods for Expert Witness Format logical evidence.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Filters;
|
||||
using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetFilename(int entryNumber, out string fileName)
|
||||
{
|
||||
fileName = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
fileName = _entries[entryNumber].FullPath;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetCompressedSize(int entryNumber, out long length)
|
||||
{
|
||||
length = -1;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
length = _entries[entryNumber].DataSize;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetUncompressedSize(int entryNumber, out long length)
|
||||
{
|
||||
length = -1;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
length = _entries[entryNumber].Size;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetEntryNumber(string fileName, bool caseInsensitiveMatch, out int entryNumber)
|
||||
{
|
||||
entryNumber = -1;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
StringComparison comparison = caseInsensitiveMatch
|
||||
? StringComparison.CurrentCultureIgnoreCase
|
||||
: StringComparison.CurrentCulture;
|
||||
|
||||
for(var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
if(!_entries[i].FullPath.Equals(fileName, comparison)) continue;
|
||||
|
||||
entryNumber = i;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoSuchFile;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Stat(int entryNumber, out FileEntryInfo stat)
|
||||
{
|
||||
stat = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
EwfFileEntry entry = _entries[entryNumber];
|
||||
|
||||
stat = new FileEntryInfo
|
||||
{
|
||||
Length = entry.Size,
|
||||
Attributes = entry.IsDirectory ? FileAttributes.Directory : FileAttributes.File,
|
||||
Blocks = entry.Size / 512,
|
||||
BlockSize = 512
|
||||
};
|
||||
|
||||
if(entry.Size % 512 != 0) stat.Blocks++;
|
||||
|
||||
if(entry.CreationTime != DateTime.MinValue)
|
||||
{
|
||||
stat.CreationTime = entry.CreationTime;
|
||||
stat.CreationTimeUtc = entry.CreationTime;
|
||||
}
|
||||
|
||||
if(entry.ModifyTime != DateTime.MinValue)
|
||||
{
|
||||
stat.LastWriteTime = entry.ModifyTime;
|
||||
stat.LastWriteTimeUtc = entry.ModifyTime;
|
||||
}
|
||||
|
||||
if(entry.AccessTime != DateTime.MinValue)
|
||||
{
|
||||
stat.AccessTime = entry.AccessTime;
|
||||
stat.AccessTimeUtc = entry.AccessTime;
|
||||
}
|
||||
|
||||
// Map EWF flags to file attributes
|
||||
if((entry.Flags & (uint)EwfLefEntryFlags.ReadOnly) != 0) stat.Attributes |= FileAttributes.ReadOnly;
|
||||
|
||||
if((entry.Flags & (uint)EwfLefEntryFlags.Hidden) != 0) stat.Attributes |= FileAttributes.Hidden;
|
||||
|
||||
if((entry.Flags & (uint)EwfLefEntryFlags.System) != 0) stat.Attributes |= FileAttributes.System;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetEntry(int entryNumber, out IFilter filter)
|
||||
{
|
||||
filter = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
EwfFileEntry entry = _entries[entryNumber];
|
||||
|
||||
if(entry.IsDirectory) return ErrorNumber.IsDirectory;
|
||||
|
||||
Stream stream;
|
||||
|
||||
if(entry.Size == 0)
|
||||
stream = new MemoryStream([]);
|
||||
else
|
||||
stream = new EwfFileStream(this, entry.DataOffset, entry.Size);
|
||||
|
||||
filter = new ZZZNoFilter();
|
||||
ErrorNumber errno = filter.Open(stream);
|
||||
|
||||
if(errno == ErrorNumber.NoError) return ErrorNumber.NoError;
|
||||
|
||||
stream.Close();
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
376
Aaru.Archives/EWF/Helpers.cs
Normal file
376
Aaru.Archives/EWF/Helpers.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Helpers.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains helper methods for Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Aaru.Compression;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
/// <summary>Generates the next segment filename following EWF naming conventions.</summary>
|
||||
static string GetNextSegmentFilename(string currentPath)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(currentPath) ?? "";
|
||||
string name = Path.GetFileNameWithoutExtension(currentPath);
|
||||
string ext = Path.GetExtension(currentPath);
|
||||
|
||||
if(string.IsNullOrEmpty(ext) || ext.Length < 2) return null;
|
||||
|
||||
bool isV2 = ext.Length == 5;
|
||||
bool isLower = char.IsLower(ext[1]);
|
||||
|
||||
if(isV2)
|
||||
{
|
||||
string numPart = ext.Substring(3);
|
||||
char prefix1 = ext[1];
|
||||
char prefix2 = ext[2];
|
||||
|
||||
if(int.TryParse(numPart, out int num) && num < 99)
|
||||
return Path.Combine(dir, name + ext.Substring(0, 3) + (num + 1).ToString("D2"));
|
||||
|
||||
if(int.TryParse(numPart, out _))
|
||||
{
|
||||
char a = isLower ? 'a' : 'A';
|
||||
|
||||
return Path.Combine(dir, name + $".{prefix1}{prefix2}{a}{a}");
|
||||
}
|
||||
|
||||
char c1 = ext[3];
|
||||
char c2 = ext[4];
|
||||
char baseChar = isLower ? 'a' : 'A';
|
||||
char maxChar = isLower ? 'z' : 'Z';
|
||||
|
||||
if(c2 < maxChar) return Path.Combine(dir, name + $".{prefix1}{prefix2}{c1}{(char)(c2 + 1)}");
|
||||
|
||||
if(c1 < maxChar) return Path.Combine(dir, name + $".{prefix1}{prefix2}{(char)(c1 + 1)}{baseChar}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
char firstChar = ext[1];
|
||||
string suffix = ext.Substring(2);
|
||||
|
||||
if(int.TryParse(suffix, out int segNum) && segNum < 99)
|
||||
return Path.Combine(dir, name + $".{firstChar}{segNum + 1:D2}");
|
||||
|
||||
if(int.TryParse(suffix, out _))
|
||||
{
|
||||
char a = isLower ? 'a' : 'A';
|
||||
|
||||
return Path.Combine(dir, name + $".{firstChar}{a}{a}");
|
||||
}
|
||||
|
||||
char ch1 = ext[1];
|
||||
char ch2 = ext[2];
|
||||
char ch3 = ext[3];
|
||||
char lBase = isLower ? 'a' : 'A';
|
||||
char lMax = isLower ? 'z' : 'Z';
|
||||
|
||||
if(ch3 < lMax) return Path.Combine(dir, name + $".{ch1}{ch2}{(char)(ch3 + 1)}");
|
||||
|
||||
if(ch2 < lMax) return Path.Combine(dir, name + $".{ch1}{(char)(ch2 + 1)}{lBase}");
|
||||
|
||||
if(ch1 < lMax) return Path.Combine(dir, name + $".{(char)(ch1 + 1)}{lBase}{lBase}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Decompresses zlib (RFC 1950) compressed data.</summary>
|
||||
static byte[] DecompressZlib(byte[] compressedData, int uncompressedSize)
|
||||
{
|
||||
if(compressedData == null || compressedData.Length < 3 || uncompressedSize <= 0)
|
||||
return new byte[Math.Max(uncompressedSize, 0)];
|
||||
|
||||
var decompressed = new byte[uncompressedSize];
|
||||
|
||||
using var ms = new MemoryStream(compressedData, 2, compressedData.Length - 2);
|
||||
using var deflate = new DeflateStream(ms, CompressionMode.Decompress);
|
||||
var totalRead = 0;
|
||||
|
||||
while(totalRead < uncompressedSize)
|
||||
{
|
||||
int read = deflate.Read(decompressed, totalRead, uncompressedSize - totalRead);
|
||||
|
||||
if(read == 0) break;
|
||||
|
||||
totalRead += read;
|
||||
}
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
/// <summary>Parses the ltree text data into a flat list of file entries.</summary>
|
||||
List<EwfFileEntry> ParseLtreeData(byte[] decompressedData)
|
||||
{
|
||||
string text;
|
||||
|
||||
// ltree data is UTF-16 LE, may or may not have BOM
|
||||
if(decompressedData.Length >= 2 && decompressedData[0] == 0xFF && decompressedData[1] == 0xFE)
|
||||
text = Encoding.Unicode.GetString(decompressedData, 2, decompressedData.Length - 2);
|
||||
else
|
||||
text = Encoding.Unicode.GetString(decompressedData);
|
||||
|
||||
var entries = new List<EwfFileEntry>();
|
||||
|
||||
string[] lines = text.Split('\n');
|
||||
var lineIndex = 0;
|
||||
|
||||
// Find the "entry" category
|
||||
while(lineIndex < lines.Length)
|
||||
{
|
||||
string line = lines[lineIndex].Trim('\r', ' ');
|
||||
|
||||
if(line == "entry")
|
||||
{
|
||||
lineIndex++;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
if(lineIndex >= lines.Length)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "No entry category found in ltree data");
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Skip empty lines after category name
|
||||
while(lineIndex < lines.Length && string.IsNullOrWhiteSpace(lines[lineIndex].Trim('\r', ' '))) lineIndex++;
|
||||
|
||||
if(lineIndex >= lines.Length) return entries;
|
||||
|
||||
// Read type indicators line (tab-separated field names)
|
||||
string typeIndicatorLine = lines[lineIndex].Trim('\r', ' ');
|
||||
string[] fieldNames = typeIndicatorLine.Split('\t');
|
||||
lineIndex++;
|
||||
|
||||
// Skip empty lines
|
||||
while(lineIndex < lines.Length && string.IsNullOrWhiteSpace(lines[lineIndex].Trim('\r', ' '))) lineIndex++;
|
||||
|
||||
// Parse root entry and its children recursively
|
||||
ParseLtreeEntries(lines, ref lineIndex, fieldNames, "", entries);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <summary>Recursively parses ltree entries.</summary>
|
||||
void ParseLtreeEntries(string[] lines, ref int lineIndex, string[] fieldNames, string parentPath,
|
||||
List<EwfFileEntry> entries)
|
||||
{
|
||||
if(lineIndex >= lines.Length) return;
|
||||
|
||||
// First line of an entry: "<is_parent> <num_children>"
|
||||
string headerLine = lines[lineIndex].Trim('\r', ' ');
|
||||
lineIndex++;
|
||||
|
||||
string[] headerParts = headerLine.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(headerParts.Length < 2) return;
|
||||
|
||||
// Parse the values line
|
||||
if(lineIndex >= lines.Length) return;
|
||||
|
||||
string valuesLine = lines[lineIndex].Trim('\r', ' ');
|
||||
lineIndex++;
|
||||
|
||||
string[] values = valuesLine.Split('\t');
|
||||
|
||||
// Build field→value map
|
||||
var fieldMap = new Dictionary<string, string>();
|
||||
|
||||
for(var i = 0; i < fieldNames.Length && i < values.Length; i++)
|
||||
{
|
||||
string key = fieldNames[i].Trim();
|
||||
|
||||
if(!string.IsNullOrEmpty(key)) fieldMap[key] = values[i].Trim();
|
||||
}
|
||||
|
||||
// Extract entry properties
|
||||
fieldMap.TryGetValue("n", out string entryName);
|
||||
fieldMap.TryGetValue("id", out string entryId);
|
||||
fieldMap.TryGetValue("ls", out string logicalSize);
|
||||
fieldMap.TryGetValue("cr", out string creationTime);
|
||||
fieldMap.TryGetValue("wr", out string writeTime);
|
||||
fieldMap.TryGetValue("ac", out string accessTime);
|
||||
fieldMap.TryGetValue("ha", out string md5Hash);
|
||||
fieldMap.TryGetValue("sha", out string sha1Hash);
|
||||
fieldMap.TryGetValue("opr", out string flagsStr);
|
||||
fieldMap.TryGetValue("p", out string isParent);
|
||||
fieldMap.TryGetValue("du", out string duplicateOffset);
|
||||
fieldMap.TryGetValue("be", out string binaryExtents);
|
||||
|
||||
bool isDir = isParent == "1" ||
|
||||
!string.IsNullOrEmpty(flagsStr) &&
|
||||
uint.TryParse(flagsStr, out uint fv) &&
|
||||
(fv & (uint)EwfLefEntryFlags.Folder) != 0;
|
||||
|
||||
// Build full path
|
||||
string fullPath;
|
||||
|
||||
if(string.IsNullOrEmpty(entryName) || entryName == "NoName")
|
||||
fullPath = parentPath;
|
||||
else
|
||||
fullPath = string.IsNullOrEmpty(parentPath) ? entryName : parentPath + "/" + entryName;
|
||||
|
||||
// Parse numeric fields
|
||||
long.TryParse(entryId, out long id);
|
||||
long.TryParse(logicalSize, out long size);
|
||||
uint.TryParse(flagsStr, out uint flags);
|
||||
|
||||
// Parse data location from binary extents
|
||||
long dataOffset = 0;
|
||||
long dataSize = 0;
|
||||
|
||||
if(!string.IsNullOrEmpty(binaryExtents)) ParseBinaryExtents(binaryExtents, out dataOffset, out dataSize);
|
||||
|
||||
// Parse duplicate offset if no binary extents
|
||||
if(dataSize == 0 && !string.IsNullOrEmpty(duplicateOffset) && duplicateOffset != "-1")
|
||||
long.TryParse(duplicateOffset, out dataOffset);
|
||||
|
||||
// Parse timestamps
|
||||
DateTime creation = ParsePosixTimestamp(creationTime);
|
||||
DateTime modify = ParsePosixTimestamp(writeTime);
|
||||
DateTime access = ParsePosixTimestamp(accessTime);
|
||||
|
||||
// Skip root entry (empty path), but add all others
|
||||
if(!string.IsNullOrEmpty(fullPath))
|
||||
{
|
||||
entries.Add(new EwfFileEntry
|
||||
{
|
||||
Id = id,
|
||||
Name = entryName ?? "",
|
||||
FullPath = fullPath,
|
||||
IsDirectory = isDir,
|
||||
Size = size,
|
||||
DataOffset = dataOffset,
|
||||
DataSize = dataSize > 0 ? dataSize : size,
|
||||
CreationTime = creation,
|
||||
ModifyTime = modify,
|
||||
AccessTime = access,
|
||||
Md5Hash = md5Hash,
|
||||
Sha1Hash = sha1Hash,
|
||||
Flags = flags
|
||||
});
|
||||
}
|
||||
|
||||
// Parse children
|
||||
if(!int.TryParse(headerParts[1], out int numChildren)) return;
|
||||
|
||||
for(var i = 0; i < numChildren; i++)
|
||||
{
|
||||
if(lineIndex >= lines.Length) break;
|
||||
|
||||
ParseLtreeEntries(lines, ref lineIndex, fieldNames, fullPath, entries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses binary extents field: "count offset_hex size_hex"</summary>
|
||||
static void ParseBinaryExtents(string extents, out long offset, out long size)
|
||||
{
|
||||
offset = 0;
|
||||
size = 0;
|
||||
|
||||
string[] parts = extents.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(parts.Length < 3) return;
|
||||
|
||||
// First part is count, then pairs of offset+size in hex
|
||||
long.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offset);
|
||||
long.TryParse(parts[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out size);
|
||||
}
|
||||
|
||||
/// <summary>Parses a POSIX timestamp string to DateTime.</summary>
|
||||
static DateTime ParsePosixTimestamp(string timestamp)
|
||||
{
|
||||
if(string.IsNullOrEmpty(timestamp) || !long.TryParse(timestamp, out long epoch) || epoch <= 0)
|
||||
return DateTime.MinValue;
|
||||
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(epoch).UtcDateTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads and decompresses a chunk.</summary>
|
||||
internal byte[] ReadChunk(ulong chunkIndex)
|
||||
{
|
||||
if(_chunkCache.TryGetValue(chunkIndex, out byte[] cached)) return cached;
|
||||
|
||||
if(!_chunkTable.TryGetValue(chunkIndex, out (int segmentIndex, long offset, uint size, bool compressed) chunk))
|
||||
return null;
|
||||
|
||||
Stream segStream = _segmentStreams[chunk.segmentIndex];
|
||||
segStream.Seek(chunk.offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] chunkData;
|
||||
|
||||
if(chunk.compressed)
|
||||
{
|
||||
var compressedData = new byte[chunk.size];
|
||||
segStream.ReadExactly(compressedData, 0, (int)chunk.size);
|
||||
|
||||
if(_isV2 && _compressionMethod == EwfCompressionMethod.Bzip2)
|
||||
{
|
||||
chunkData = new byte[_chunkSize];
|
||||
BZip2.DecodeBuffer(compressedData, chunkData);
|
||||
}
|
||||
else
|
||||
chunkData = DecompressZlib(compressedData, (int)_chunkSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint dataLen = chunk.size >= 4 ? chunk.size - 4 : chunk.size;
|
||||
chunkData = new byte[dataLen];
|
||||
segStream.ReadExactly(chunkData, 0, (int)dataLen);
|
||||
}
|
||||
|
||||
if(_chunkCache.Count >= _maxChunkCache) _chunkCache.Clear();
|
||||
|
||||
_chunkCache[chunkIndex] = chunkData;
|
||||
|
||||
return chunkData;
|
||||
}
|
||||
}
|
||||
78
Aaru.Archives/EWF/Info.cs
Normal file
78
Aaru.Archives/EWF/Info.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Info.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Identifies Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Identify(IFilter filter)
|
||||
{
|
||||
if(filter.DataForkLength < SIGNATURE_LENGTH) return false;
|
||||
|
||||
Stream stream = filter.GetDataForkStream();
|
||||
stream.Position = 0;
|
||||
|
||||
var signatureBytes = new byte[SIGNATURE_LENGTH];
|
||||
stream.ReadExactly(signatureBytes, 0, SIGNATURE_LENGTH);
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LVF_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LEF2_SIGNATURE)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetInformation(IFilter filter, Encoding encoding, out string information)
|
||||
{
|
||||
information = "";
|
||||
|
||||
if(!Opened) return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("[bold][blue]EWF Logical Evidence File:[/][/]");
|
||||
sb.AppendFormat("[slateblue1]Format:[/] [green]{0}[/]", _isV2 ? "EWF2 (Lx01)" : "EWF (L01)").AppendLine();
|
||||
sb.AppendFormat("[slateblue1]Files:[/] [teal]{0}[/]", _entries?.Count ?? 0).AppendLine();
|
||||
|
||||
information = sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
371
Aaru.Archives/EWF/Open.cs
Normal file
371
Aaru.Archives/EWF/Open.cs
Normal file
@@ -0,0 +1,371 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Open.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Opens Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Logging;
|
||||
using Marshal = Aaru.Helpers.Marshal;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Open(IFilter filter, Encoding encoding)
|
||||
{
|
||||
if(filter.DataForkLength < SIGNATURE_LENGTH) return ErrorNumber.InvalidArgument;
|
||||
|
||||
Stream stream = filter.GetDataForkStream();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var signatureBytes = new byte[SIGNATURE_LENGTH];
|
||||
stream.ReadExactly(signatureBytes, 0, SIGNATURE_LENGTH);
|
||||
|
||||
_isV2 = signatureBytes.AsSpan().SequenceEqual(LEF2_SIGNATURE);
|
||||
|
||||
// Discover segment files
|
||||
_segmentStreams = [];
|
||||
List<IFilter> segmentFilters = DiscoverSegments(filter);
|
||||
|
||||
foreach(IFilter f in segmentFilters) _segmentStreams.Add(f.GetDataForkStream());
|
||||
|
||||
// Initialize data structures
|
||||
_chunkTable = new Dictionary<ulong, (int segmentIndex, long offset, uint size, bool compressed)>();
|
||||
_chunkCache = new Dictionary<ulong, byte[]>();
|
||||
_entries = [];
|
||||
|
||||
_sectorsPerChunk = DEFAULT_SECTORS_PER_CHUNK;
|
||||
_bytesPerSector = DEFAULT_BYTES_PER_SECTOR;
|
||||
_chunkSize = DEFAULT_CHUNK_SIZE;
|
||||
|
||||
ulong currentChunk = 0;
|
||||
var volumeFound = false;
|
||||
|
||||
byte[] ltreeDecompressed = null;
|
||||
|
||||
// Process each segment file
|
||||
for(var segIdx = 0; segIdx < _segmentStreams.Count; segIdx++)
|
||||
{
|
||||
Stream segStream = _segmentStreams[segIdx];
|
||||
|
||||
if(_isV2)
|
||||
ParseSegmentV2(segStream, segIdx, ref currentChunk, ref volumeFound, ref ltreeDecompressed);
|
||||
else
|
||||
ParseSegmentV1(segStream, segIdx, ref currentChunk, ref volumeFound, ref ltreeDecompressed);
|
||||
}
|
||||
|
||||
_chunkSize = _sectorsPerChunk * _bytesPerSector;
|
||||
_maxChunkCache = (int)(MAX_CACHE_SIZE / _chunkSize);
|
||||
|
||||
if(_maxChunkCache < 1) _maxChunkCache = 1;
|
||||
|
||||
// Parse ltree data into file entries
|
||||
if(ltreeDecompressed is { Length: > 0 }) _entries = ParseLtreeData(ltreeDecompressed);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Parsed {0} file entries from ltree", _entries.Count);
|
||||
AaruLogging.Debug(MODULE_NAME, "Chunk table has {0} entries", _chunkTable.Count);
|
||||
|
||||
Opened = true;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
List<IFilter> DiscoverSegments(IFilter primaryFilter)
|
||||
{
|
||||
List<IFilter> segments = [primaryFilter];
|
||||
string currentPath = primaryFilter.BasePath;
|
||||
|
||||
while(true)
|
||||
{
|
||||
string nextPath = GetNextSegmentFilename(currentPath);
|
||||
|
||||
if(nextPath == null) break;
|
||||
|
||||
IFilter nextFilter = PluginRegister.Singleton.GetFilter(nextPath);
|
||||
|
||||
if(nextFilter == null) break;
|
||||
|
||||
segments.Add(nextFilter);
|
||||
currentPath = nextPath;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Found {0} segment files", segments.Count);
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
void ParseSegmentV1(Stream segStream, int segIdx, ref ulong currentChunk, ref bool volumeFound,
|
||||
ref byte[] ltreeDecompressed)
|
||||
{
|
||||
segStream.Seek(FILE_HEADER_V1_SIZE, SeekOrigin.Begin);
|
||||
|
||||
while(segStream.Position < segStream.Length)
|
||||
{
|
||||
long sectionStart = segStream.Position;
|
||||
|
||||
var descBytes = new byte[SECTION_DESCRIPTOR_V1_SIZE];
|
||||
int bytesRead = segStream.Read(descBytes, 0, SECTION_DESCRIPTOR_V1_SIZE);
|
||||
|
||||
if(bytesRead < SECTION_DESCRIPTOR_V1_SIZE) break;
|
||||
|
||||
EwfSectionDescriptorV1 descriptor =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfSectionDescriptorV1>(descBytes);
|
||||
|
||||
string sectionType = Encoding.ASCII.GetString(descriptor.type_string).TrimEnd('\0');
|
||||
long dataSize = (long)descriptor.size - SECTION_DESCRIPTOR_V1_SIZE;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Section type: {0} at offset {1}, next at {2}, size {3}",
|
||||
sectionType,
|
||||
sectionStart,
|
||||
descriptor.next_offset,
|
||||
descriptor.size);
|
||||
|
||||
switch(sectionType)
|
||||
{
|
||||
case SECTION_TYPE_VOLUME:
|
||||
case SECTION_TYPE_DATA:
|
||||
if(!volumeFound) ParseVolumeSection(segStream, dataSize, ref volumeFound);
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_TABLE:
|
||||
case SECTION_TYPE_TABLE2:
|
||||
ParseTableSectionV1(segStream, segIdx, dataSize, ref currentChunk, (long)descriptor.next_offset);
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_LTREE:
|
||||
if(ltreeDecompressed == null && dataSize > LTREE_HEADER_SIZE)
|
||||
ltreeDecompressed = ParseLtreeSection(segStream, dataSize);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(descriptor.next_offset == 0 || sectionType is SECTION_TYPE_DONE or SECTION_TYPE_NEXT) break;
|
||||
|
||||
segStream.Seek((long)descriptor.next_offset, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseVolumeSection(Stream segStream, long dataSize, ref bool volumeFound)
|
||||
{
|
||||
if(dataSize < VOLUME_SECTION_SIZE_SMART) return;
|
||||
|
||||
var volumeData = new byte[dataSize];
|
||||
segStream.ReadExactly(volumeData, 0, (int)dataSize);
|
||||
|
||||
if(dataSize < VOLUME_SECTION_SIZE_ENCASE)
|
||||
{
|
||||
EwfVolumeSmartSection smartVol =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfVolumeSmartSection>(volumeData);
|
||||
|
||||
_sectorsPerChunk = smartVol.sectors_per_chunk;
|
||||
_bytesPerSector = smartVol.bytes_per_sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
EwfVolumeSection vol = Marshal.ByteArrayToStructureLittleEndian<EwfVolumeSection>(volumeData);
|
||||
|
||||
_sectorsPerChunk = vol.sectors_per_chunk;
|
||||
_bytesPerSector = vol.bytes_per_sector;
|
||||
}
|
||||
|
||||
volumeFound = true;
|
||||
}
|
||||
|
||||
void ParseTableSectionV1(Stream segStream, int segIdx, long dataSize, ref ulong currentChunk,
|
||||
long nextSectionOffset)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV1>()) return;
|
||||
|
||||
var tableHeaderBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV1>()];
|
||||
segStream.ReadExactly(tableHeaderBytes, 0, tableHeaderBytes.Length);
|
||||
|
||||
EwfTableHeaderV1 tableHeader = Marshal.ByteArrayToStructureLittleEndian<EwfTableHeaderV1>(tableHeaderBytes);
|
||||
|
||||
var entryCount = (int)tableHeader.number_of_entries;
|
||||
var entryData = new byte[entryCount * 4];
|
||||
segStream.ReadExactly(entryData, 0, entryData.Length);
|
||||
|
||||
// Skip entries checksum
|
||||
segStream.Seek(4, SeekOrigin.Current);
|
||||
|
||||
for(var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var rawEntry = BitConverter.ToUInt32(entryData, i * 4);
|
||||
bool compressed = (rawEntry & TABLE_ENTRY_V1_COMPRESSED_FLAG) != 0;
|
||||
var offset = (long)(tableHeader.base_offset + (rawEntry & TABLE_ENTRY_V1_OFFSET_MASK));
|
||||
|
||||
uint size;
|
||||
|
||||
if(i + 1 < entryCount)
|
||||
{
|
||||
var nextRawEntry = BitConverter.ToUInt32(entryData, (i + 1) * 4);
|
||||
var nextOffset = (long)(tableHeader.base_offset + (nextRawEntry & TABLE_ENTRY_V1_OFFSET_MASK));
|
||||
size = (uint)(nextOffset - offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(nextSectionOffset > offset)
|
||||
size = (uint)(nextSectionOffset - offset);
|
||||
else
|
||||
size = _chunkSize + 4;
|
||||
}
|
||||
|
||||
ulong chunkIndex = currentChunk + (ulong)i;
|
||||
|
||||
if(!_chunkTable.ContainsKey(chunkIndex)) _chunkTable[chunkIndex] = (segIdx, offset, size, compressed);
|
||||
}
|
||||
|
||||
currentChunk += (ulong)entryCount;
|
||||
}
|
||||
|
||||
byte[] ParseLtreeSection(Stream segStream, long dataSize)
|
||||
{
|
||||
// Read ltree header (48 bytes)
|
||||
var headerBytes = new byte[LTREE_HEADER_SIZE];
|
||||
segStream.ReadExactly(headerBytes, 0, LTREE_HEADER_SIZE);
|
||||
|
||||
EwfLtreeHeader ltreeHeader = Marshal.ByteArrayToStructureLittleEndian<EwfLtreeHeader>(headerBytes);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Ltree data size: {0}", ltreeHeader.data_size);
|
||||
|
||||
// Read the compressed ltree data
|
||||
long compressedSize = dataSize - LTREE_HEADER_SIZE;
|
||||
|
||||
if(compressedSize <= 0) return null;
|
||||
|
||||
var compressedData = new byte[compressedSize];
|
||||
segStream.ReadExactly(compressedData, 0, (int)compressedSize);
|
||||
|
||||
// Decompress
|
||||
try
|
||||
{
|
||||
return DecompressZlib(compressedData, (int)ltreeHeader.data_size);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Failed to decompress ltree data: {0}", ex.Message);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseSegmentV2(Stream segStream, int segIdx, ref ulong currentChunk, ref bool volumeFound,
|
||||
ref byte[] ltreeDecompressed)
|
||||
{
|
||||
segStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var headerBytes = new byte[FILE_HEADER_V2_SIZE];
|
||||
segStream.ReadExactly(headerBytes, 0, FILE_HEADER_V2_SIZE);
|
||||
|
||||
EwfFileHeaderV2 fileHeader = Marshal.ByteArrayToStructureLittleEndian<EwfFileHeaderV2>(headerBytes);
|
||||
_compressionMethod = (EwfCompressionMethod)fileHeader.compression_method;
|
||||
|
||||
int descriptorSize = System.Runtime.InteropServices.Marshal.SizeOf<EwfSectionDescriptorV2>();
|
||||
long position = segStream.Length - descriptorSize;
|
||||
|
||||
while(position >= FILE_HEADER_V2_SIZE)
|
||||
{
|
||||
segStream.Seek(position, SeekOrigin.Begin);
|
||||
|
||||
var descBytes = new byte[descriptorSize];
|
||||
segStream.ReadExactly(descBytes, 0, descriptorSize);
|
||||
|
||||
EwfSectionDescriptorV2 descriptor =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfSectionDescriptorV2>(descBytes);
|
||||
|
||||
var sectionType = (EwfSectionTypeV2)descriptor.type;
|
||||
long dataStart = position - (long)descriptor.data_size;
|
||||
|
||||
switch(sectionType)
|
||||
{
|
||||
case EwfSectionTypeV2.SectorTable:
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
ParseTableSectionV2(segStream, segIdx, (long)descriptor.data_size, ref currentChunk);
|
||||
|
||||
break;
|
||||
|
||||
case EwfSectionTypeV2.SingleFilesTree:
|
||||
if(ltreeDecompressed == null && (long)descriptor.data_size > LTREE_HEADER_SIZE)
|
||||
{
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
ltreeDecompressed = ParseLtreeSection(segStream, (long)descriptor.data_size);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(descriptor.previous_offset == 0) break;
|
||||
|
||||
position = (long)descriptor.previous_offset;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseTableSectionV2(Stream segStream, int segIdx, long dataSize, ref ulong currentChunk)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV2>()) return;
|
||||
|
||||
var tableHeaderBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV2>()];
|
||||
segStream.ReadExactly(tableHeaderBytes, 0, tableHeaderBytes.Length);
|
||||
|
||||
EwfTableHeaderV2 tableHeader = Marshal.ByteArrayToStructureLittleEndian<EwfTableHeaderV2>(tableHeaderBytes);
|
||||
|
||||
ulong firstChunk = tableHeader.first_chunk_number;
|
||||
var entryCount = (int)tableHeader.number_of_entries;
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfTableEntryV2>();
|
||||
|
||||
for(var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.ReadExactly(entryBytes, 0, entrySize);
|
||||
|
||||
EwfTableEntryV2 entry = Marshal.ByteArrayToStructureLittleEndian<EwfTableEntryV2>(entryBytes);
|
||||
bool compressed = (entry.chunk_data_flags & 0x00000001) == 0;
|
||||
ulong chunkIndex = firstChunk + (ulong)i;
|
||||
|
||||
if(!_chunkTable.ContainsKey(chunkIndex))
|
||||
_chunkTable[chunkIndex] = (segIdx, (long)entry.chunk_data_offset, entry.chunk_data_size, compressed);
|
||||
}
|
||||
|
||||
if(firstChunk + (ulong)entryCount > currentChunk) currentChunk = firstChunk + (ulong)entryCount;
|
||||
}
|
||||
}
|
||||
223
Aaru.Archives/EWF/Structs.cs
Normal file
223
Aaru.Archives/EWF/Structs.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Structs.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains structures for Expert Witness Format logical evidence files.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
#region Internal types
|
||||
|
||||
/// <summary>Parsed file entry from the ltree</summary>
|
||||
sealed class EwfFileEntry
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
public bool IsDirectory { get; set; }
|
||||
public long Size { get; set; }
|
||||
public long DataOffset { get; set; }
|
||||
public long DataSize { get; set; }
|
||||
public DateTime CreationTime { get; set; }
|
||||
public DateTime ModifyTime { get; set; }
|
||||
public DateTime AccessTime { get; set; }
|
||||
public string Md5Hash { get; set; }
|
||||
public string Sha1Hash { get; set; }
|
||||
public uint Flags { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region On-disk structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfFileHeaderV1
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] signature;
|
||||
public byte fields_start;
|
||||
public ushort segment_number;
|
||||
public ushort fields_end;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfFileHeaderV2
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] signature;
|
||||
public byte major_version;
|
||||
public byte minor_version;
|
||||
public ushort compression_method;
|
||||
public uint segment_number;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] set_identifier;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSectionDescriptorV1
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] type_string;
|
||||
public ulong next_offset;
|
||||
public ulong size;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
|
||||
public byte[] padding;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSectionDescriptorV2
|
||||
{
|
||||
public uint type;
|
||||
public uint data_flags;
|
||||
public ulong previous_offset;
|
||||
public ulong data_size;
|
||||
public uint descriptor_size;
|
||||
public uint padding_size;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] data_integrity_hash;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] reserved;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfVolumeSection
|
||||
{
|
||||
public byte media_type;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown1;
|
||||
public uint number_of_chunks;
|
||||
public uint sectors_per_chunk;
|
||||
public uint bytes_per_sector;
|
||||
public ulong number_of_sectors;
|
||||
public uint chs_cylinders;
|
||||
public uint chs_heads;
|
||||
public uint chs_sectors;
|
||||
public byte media_flags;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown2;
|
||||
public uint palm_volume_start_sector;
|
||||
public uint unknown3;
|
||||
public uint smart_logs_start_sector;
|
||||
public byte compression_level;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown4;
|
||||
public uint error_granularity;
|
||||
public uint unknown5;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] set_identifier;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 963)]
|
||||
public byte[] unknown6;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
public byte[] signature;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfVolumeSmartSection
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public byte[] unknown1;
|
||||
public uint number_of_chunks;
|
||||
public uint sectors_per_chunk;
|
||||
public uint bytes_per_sector;
|
||||
public uint number_of_sectors;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] unknown2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 45)]
|
||||
public byte[] unknown3;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
public byte[] signature;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableHeaderV1
|
||||
{
|
||||
public uint number_of_entries;
|
||||
public uint padding1;
|
||||
public ulong base_offset;
|
||||
public uint padding2;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableEntryV2
|
||||
{
|
||||
public ulong chunk_data_offset;
|
||||
public uint chunk_data_size;
|
||||
public uint chunk_data_flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableHeaderV2
|
||||
{
|
||||
public ulong first_chunk_number;
|
||||
public uint number_of_entries;
|
||||
public uint unknown1;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
/// <summary>Ltree section header, 48 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfLtreeHeader
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] integrity_hash;
|
||||
public ulong data_size;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] unknown1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
93
Aaru.Archives/EWF/Xattrs.cs
Normal file
93
Aaru.Archives/EWF/Xattrs.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Xattrs.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : EWF logical evidence plugin.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains extended attribute methods for Expert Witness Format logical
|
||||
// evidence.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class EwfArchive
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ListXAttr(int entryNumber, out List<string> xattrs)
|
||||
{
|
||||
xattrs = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
EwfFileEntry entry = _entries[entryNumber];
|
||||
|
||||
xattrs = [];
|
||||
|
||||
if(!string.IsNullOrEmpty(entry.Md5Hash) && entry.Md5Hash != "0" && !entry.Md5Hash.StartsWith("00000000"))
|
||||
xattrs.Add("md5");
|
||||
|
||||
if(!string.IsNullOrEmpty(entry.Sha1Hash) && entry.Sha1Hash != "0" && !entry.Sha1Hash.StartsWith("00000000"))
|
||||
xattrs.Add("sha1");
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetXattr(int entryNumber, string xattr, ref byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
EwfFileEntry entry = _entries[entryNumber];
|
||||
|
||||
switch(xattr)
|
||||
{
|
||||
case "md5" when !string.IsNullOrEmpty(entry.Md5Hash):
|
||||
buffer = Encoding.UTF8.GetBytes(entry.Md5Hash);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
case "sha1" when !string.IsNullOrEmpty(entry.Sha1Hash):
|
||||
buffer = Encoding.UTF8.GetBytes(entry.Sha1Hash);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
default:
|
||||
return ErrorNumber.NoSuchExtendedAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -698,5 +698,11 @@ namespace Aaru.Archives {
|
||||
return ResourceManager.GetString("Archive_contains_0_files", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string EwfArchive_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("EwfArchive_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,4 +342,7 @@
|
||||
<data name="Archive_contains_0_files" xml:space="preserve">
|
||||
<value>[slateblue1]El archivo contiene [green]{0}[/] ficheros[/]</value>
|
||||
</data>
|
||||
<data name="EwfArchive_Name" xml:space="preserve">
|
||||
<value>Evidencia Lógica Expert Witness Format (EWF-L)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -350,4 +350,7 @@
|
||||
<data name="Archive_contains_0_files" xml:space="preserve">
|
||||
<value>[slateblue1]Archive contains [green]{0}[/] files[/]</value>
|
||||
</data>
|
||||
<data name="EwfArchive_Name" xml:space="preserve">
|
||||
<value>Expert Witness Format Logical Evidence (EWF-L)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -57,11 +57,10 @@ public sealed partial class Ewf
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(EVF_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LVF_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(EVF2_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LEF2_SIGNATURE)) return true;
|
||||
// LVF and LEF2 signatures are for logical evidence files (L01/Lx01),
|
||||
// handled by the IArchive plugin in Aaru.Archives
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user