using System.Text; using Serilog; namespace EdgeInstaller; sealed class DebFile : IDisposable { const int HeaderBytes = 60; const string FileMagic = "!\n"; private MemoryStream _fileStream; public List fileEntries { get; private set; } public DebFile(Stream fs) { Log.Information("Creating new debFile from stream"); fileEntries = new(); if (fs is null) { throw new NullReferenceException(); } fs.Position = 0; // read the first 8 bytes and check for the signature byte[] magicBuffer = new byte[8]; fs.Read(magicBuffer); Log.Debug("Magic = {magicBuffer}", Encoding.ASCII.GetString(magicBuffer)); if (Encoding.ASCII.GetString(magicBuffer) != FileMagic) { throw new ArgumentException(message: "Magic Fail"); } fs.Position = 0; _fileStream = new(); fs.CopyTo(_fileStream); fs.Dispose(); // if we got here then we must have a proper archive. // We shall now get the files that are inside. // from OUR copy of the stream we seek to after the magic byte _fileStream.Position = 8; while (true) // beware making an infinite loop { // Read a header byte[] header = new byte[60]; var read = _fileStream.Read(header); // If nothing was read we have hit the end if (read < 60) // also handle broken headers { break; } // Otherwise process as a header var entry = new DebEntry(header, _fileStream.Position); fileEntries.Add(entry); // Using the obtained size seek forwards to the next potential header _fileStream.Seek(entry.sizeBytes, SeekOrigin.Current); } } public bool getFile(DebEntry file, out Stream output) { // If we are given a file that isn't in this archive we exit nicely if (!fileEntries.Contains(file)) { Log.Error("The given file entry is not valid for this DebFile"); output = Stream.Null; return false; } // Since we have a file that is in the archive // grab the metadata we need long offset = file.offset; long size = file.sizeBytes; // stream the file to the output stream _fileStream.Seek(offset, SeekOrigin.Begin); byte[] fileBuffer = new byte[size]; var count = _fileStream.Read(fileBuffer); if (count != size) { Log.Error("Didn't read the correct amount of data from stream (expected:{size} got:{count})", size, count); output = Stream.Null; return false; } output = new MemoryStream(fileBuffer); // convert the read buffer to a stream return true; } public void Dispose() { // make sure we can clean up our own mess _fileStream.Dispose(); } } sealed class DebEntry { // Escape sequnces are used in this string read as "`\n" const string HeaderMagic = "\x60\n"; public string name { get; private set; } public long mtime { get; private set; } public int mode { get; private set; } public int gid { get; private set; } public int uid { get; private set; } public long sizeBytes { get; private set; } public long offset { get; private set; } // where does the file start public DebEntry(byte[] buffer, long Position) { Log.Debug("Reading header at position {p}", Position - 60); // since we are being passed a header we take 60 to get the header position if (buffer is null) { throw new NullReferenceException(); } if (buffer.Length < 60) { throw new ArgumentException("buffer too short"); } name = Encoding.ASCII.GetString(buffer[0..16]).Trim(); // Trim any trailing whitespace mtime = long.Parse(Encoding.ASCII.GetString(buffer[16..28])); mode = int.Parse(Encoding.ASCII.GetString(buffer[28..34])); gid = int.Parse(Encoding.ASCII.GetString(buffer[34..40])); uid = int.Parse(Encoding.ASCII.GetString(buffer[40..48])); sizeBytes = long.Parse(Encoding.ASCII.GetString(buffer[48..58])); string magic = Encoding.ASCII.GetString(buffer[58..^0]); offset = Position; Log.Debug("Header Magic = {magic}", magic); Log.Debug("{@this}", this); if (magic != HeaderMagic) { throw new ArgumentException("magic failed"); } } }