From 7e69a56892effaa3976882af869a4e593e698de1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 10 Jun 2022 15:29:58 -0700 Subject: [PATCH] LibGSF (nw) --- BurnOutSharp/BurnOutSharp.csproj | 1 + BurnOutSharp/External/libgsf/GsfBlob.cs | 78 + BurnOutSharp/External/libgsf/GsfClipData.cs | 294 ++ .../External/libgsf/GsfDocMetaData.cs | 555 ++++ BurnOutSharp/External/libgsf/GsfLibXML.cs | 864 ++++++ BurnOutSharp/External/libgsf/GsfMSOleImpl.cs | 80 + BurnOutSharp/External/libgsf/GsfMSOleUtils.cs | 2431 +++++++++++++++++ BurnOutSharp/External/libgsf/GsfMetaNames.cs | 378 +++ .../External/libgsf/GsfOpenDocUtils.cs | 784 ++++++ .../External/libgsf/GsfOpenPkgUtils.cs | 723 +++++ .../External/libgsf/GsfSharedMemory.cs | 90 + BurnOutSharp/External/libgsf/GsfZipImpl.cs | 391 +++ .../External/libgsf/Input/GsfInfile.cs | 121 + .../External/libgsf/Input/GsfInfileMSOle.cs | 963 +++++++ .../External/libgsf/Input/GsfInfileMSVBA.cs | 338 +++ .../External/libgsf/Input/GsfInfileStdio.cs | 130 + .../External/libgsf/Input/GsfInfileTar.cs | 487 ++++ .../External/libgsf/Input/GsfInfileZip.cs | 789 ++++++ .../External/libgsf/Input/GsfInput.cs | 529 ++++ .../External/libgsf/Input/GsfInputBzip.cs | 140 + .../External/libgsf/Input/GsfInputGZip.cs | 443 +++ .../External/libgsf/Input/GsfInputGio.cs | 239 ++ .../External/libgsf/Input/GsfInputHTTP.cs | 186 ++ .../libgsf/Input/GsfInputIoChannel.cs | 43 + .../External/libgsf/Input/GsfInputMemory.cs | 149 + .../External/libgsf/Input/GsfInputProxy.cs | 111 + .../External/libgsf/Input/GsfInputStdio.cs | 226 ++ .../External/libgsf/Input/GsfInputTextline.cs | 212 ++ .../libgsf/Input/GsfStructuredBlob.cs | 213 ++ .../External/libgsf/Output/GsfOutfile.cs | 35 + .../External/libgsf/Output/GsfOutfileMSOle.cs | 930 +++++++ .../External/libgsf/Output/GsfOutfileStdio.cs | 97 + .../External/libgsf/Output/GsfOutfileZip.cs | 881 ++++++ .../External/libgsf/Output/GsfOutput.cs | 286 ++ .../External/libgsf/Output/GsfOutputBzip.cs | 212 ++ .../External/libgsf/Output/GsfOutputCsv.cs | 181 ++ .../External/libgsf/Output/GsfOutputGZip.cs | 298 ++ .../External/libgsf/Output/GsfOutputGio.cs | 144 + .../libgsf/Output/GsfOutputIOChannel.cs | 93 + .../External/libgsf/Output/GsfOutputIconv.cs | 171 ++ .../External/libgsf/Output/GsfOutputMemory.cs | 157 ++ .../External/libgsf/Output/GsfOutputStdio.cs | 260 ++ BurnOutSharp/External/libgsf/README.md | 10 + 43 files changed, 15743 insertions(+) create mode 100644 BurnOutSharp/External/libgsf/GsfBlob.cs create mode 100644 BurnOutSharp/External/libgsf/GsfClipData.cs create mode 100644 BurnOutSharp/External/libgsf/GsfDocMetaData.cs create mode 100644 BurnOutSharp/External/libgsf/GsfLibXML.cs create mode 100644 BurnOutSharp/External/libgsf/GsfMSOleImpl.cs create mode 100644 BurnOutSharp/External/libgsf/GsfMSOleUtils.cs create mode 100644 BurnOutSharp/External/libgsf/GsfMetaNames.cs create mode 100644 BurnOutSharp/External/libgsf/GsfOpenDocUtils.cs create mode 100644 BurnOutSharp/External/libgsf/GsfOpenPkgUtils.cs create mode 100644 BurnOutSharp/External/libgsf/GsfSharedMemory.cs create mode 100644 BurnOutSharp/External/libgsf/GsfZipImpl.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfile.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfileMSVBA.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfileStdio.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfileTar.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInfileZip.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInput.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputBzip.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputGZip.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputGio.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputHTTP.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputIoChannel.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputMemory.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputProxy.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputStdio.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfInputTextline.cs create mode 100644 BurnOutSharp/External/libgsf/Input/GsfStructuredBlob.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutfile.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutfileMSOle.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutfileStdio.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutfileZip.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutput.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputBzip.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputCsv.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputGZip.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputGio.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputIOChannel.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputIconv.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputMemory.cs create mode 100644 BurnOutSharp/External/libgsf/Output/GsfOutputStdio.cs create mode 100644 BurnOutSharp/External/libgsf/README.md diff --git a/BurnOutSharp/BurnOutSharp.csproj b/BurnOutSharp/BurnOutSharp.csproj index 7dbd59c7..8d855c4f 100644 --- a/BurnOutSharp/BurnOutSharp.csproj +++ b/BurnOutSharp/BurnOutSharp.csproj @@ -30,6 +30,7 @@ + diff --git a/BurnOutSharp/External/libgsf/GsfBlob.cs b/BurnOutSharp/External/libgsf/GsfBlob.cs new file mode 100644 index 00000000..966da23f --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfBlob.cs @@ -0,0 +1,78 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-blob.c: a chunk of data + * + * Copyright (C) 2006 Novell Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; + +namespace LibGSF +{ + public class GsfBlob + { + #region Properties + + public long Size { get; private set; } + + public byte[] Data { get; private set; } + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfBlob() { } + + /// + /// Creates a new GsfBlob object to hold the specified data. The blob can then + /// be used as a facility for reference-counting for the data. The data is + /// copied internally, so the blob does not hold references to external chunks + /// of memory. + /// + /// Size of the data in bytes. + /// Data which will be copied into the blob, or null if is zero. + /// Location to store error, or null. + /// A newly-created GsfBlob, or null if the data could not be copied. + public static GsfBlob Create(long size, byte[] data_to_copy, int dataPtr, ref Exception error) + { + if (!((size > 0 && data_to_copy != null) || (size == 0 && data_to_copy == null))) + return null; + + byte[] data; + if (data_to_copy != null) + { + data = new byte[size]; + Array.Copy(data_to_copy, dataPtr, data, 0, size); + } + else + { + data = null; + } + + return new GsfBlob + { + Size = size, + Data = data, + }; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfClipData.cs b/BurnOutSharp/External/libgsf/GsfClipData.cs new file mode 100644 index 00000000..2cca089e --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfClipData.cs @@ -0,0 +1,294 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-clip-data.c: clipboard data + * + * Copyright (C) 2006 Novell Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF +{ + #region Enums + + public enum GsfClipFormat + { + /// + /// Windows clipboard format + /// + GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD = -1, + + /// + /// Macintosh clipboard format + /// + GSF_CLIP_FORMAT_MACINTOSH_CLIPBOARD = -2, + + /// + /// GUID that contains a format identifier + /// + GSF_CLIP_FORMAT_GUID = -3, + + /// + /// No clipboard data + /// + GSF_CLIP_FORMAT_NO_DATA = 0, + + /// + /// Custom clipboard format + /// + /// + /// In the file it's actually any positive integer + /// + GSF_CLIP_FORMAT_CLIPBOARD_FORMAT_NAME = 1, + + /// + /// Unknown clipboard type or invalid data + /// + /// + /// This is our own value for unknown types or invalid data + /// + GSF_CLIP_FORMAT_UNKNOWN + } + + public enum GsfClipFormatWindows + { + /// + /// Our own value + /// + GSF_CLIP_FORMAT_WINDOWS_ERROR = -1, + + /// + /// Our own value + /// + GSF_CLIP_FORMAT_WINDOWS_UNKNOWN = -2, + + /// + /// CF_METAFILEPICT + /// + GSF_CLIP_FORMAT_WINDOWS_METAFILE = 3, + + /// + /// CF_DIB + /// + GSF_CLIP_FORMAT_WINDOWS_DIB = 8, + + /// + /// CF_ENHMETAFILE + /// + GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE = 14 + } + + #endregion + + public class GsfClipData + { + #region Properties + + public GsfClipFormat Format { get; private set; } + + public GsfBlob DataBlob { get; private set; } + + #endregion + + #region Private Classes + + private class FormatOffsetPair + { + public GsfClipFormatWindows Format { get; set; } + + public long Offset { get; set; } + }; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfClipData() { } + + /// + /// Creates a new GsfClipData object. This function acquires a reference to the + /// , so you should unref the blob on your own if you no longer need it + /// directly. + /// + /// Format for the data inside the + /// Object which holds the binary contents for the GsfClipData + /// A newly-created GsfClipData. + public static GsfClipData Create(GsfClipFormat format, GsfBlob data_blob) + { + if (data_blob == null) + return null; + + return new GsfClipData + { + Format = format, + DataBlob = data_blob, + }; + } + + #endregion + + #region Functions + + /// + /// Queries the Windows clipboard data format for a GsfClipData. The must + /// have been created with #GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD. + /// + /// Location to store error, or NULL + /// A GsfClipFormatWindows value. + public GsfClipFormatWindows GetWindowsClipboardFormat(ref Exception error) + { + GsfClipFormatWindows format; + + if (error == null) + return GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ERROR; + + if (Format != GsfClipFormat.GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD) + return GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ERROR; + + long size = DataBlob.Size; + + if (size < 4) + { + error = new InvalidDataException("The clip_data is in Windows clipboard format, but it is smaller than the required 4 bytes."); + return GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ERROR; + } + + byte[] data = DataBlob.Data; + + uint value = BitConverter.ToUInt32(data, 0); + + switch (value) + { + case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_METAFILE: + format = CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_METAFILE, "Windows Metafile format", size, ref error); + break; + + case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_DIB: + case 2: /* CF_BITMAP */ + format = CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_DIB, "Windows DIB or BITMAP format", size, ref error); + break; + + case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE: + format = CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE, "Windows Enhanced Metafile format", size, ref error); + break; + + default: + format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_UNKNOWN; + break; + } + + return format; + } + + /// + /// Queries a pointer directly to the clipboard data of a GsfClipData. The + /// resulting pointer is not necessarily the same data pointer that was passed to + /// gsf_blob_new() prior to creating the . For example, if the data is + /// in GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD format, then it will have extra header + /// bytes in front of the actual metafile data. This function will skip over + /// those header bytes if necessary and return a pointer to the "real" data. + /// + /// Location to return the size of the returned data buffer. + /// Location to store error, or NULL. + /// + /// Pointer to the real clipboard data. The size in bytes of this + /// buffer is returned in the argument. + /// + public byte[] PeekRealData(ref long ret_size, ref Exception error) + { + if (error == null) + return null; + + byte[] data = DataBlob.Data; + + long offset; + if (Format == GsfClipFormat.GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD) + { + GsfClipFormatWindows win_format = GetWindowsClipboardFormat(ref error); + if (win_format == GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ERROR) + return null; + + // gsf_clip_data_get_windows_clipboard_format() already did the size checks for us, + // so we can jump to the offset right away without doing extra checks. + + offset = GetWindowsClipboardDataOffset(win_format); + } + else + { + offset = 0; + } + + ret_size = DataBlob.Size - offset; + return new ReadOnlySpan(data, (int)offset, (int)ret_size).ToArray(); + } + + private static void SetErrorMissingClipboardData(ref Exception error, string format_name, long at_least_size) + { + error = new InvalidDataException($"The clip_data is in {format_name}, but it is smaller than at least {at_least_size} bytes"); + } + + private static long GetWindowsClipboardDataOffset(GsfClipFormatWindows format) + { + FormatOffsetPair[] pairs = + { + new FormatOffsetPair { Format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_UNKNOWN, Offset = 4 }, + new FormatOffsetPair { Format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_METAFILE, Offset = 12 }, + new FormatOffsetPair { Format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_DIB, Offset = 4 }, + + // FIXME: does this have a PACKEDMETA in front as well, similar to GSF_CLIP_FORMAT_WINDOWS_METAFILE? + new FormatOffsetPair { Format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE, Offset = 4 } + }; + int num_pairs = pairs.Length; + + int i; + + for (i = 0; i < num_pairs; i++) + { + if (pairs[i].Format == format) + return pairs[i].Offset; + } + + Console.Error.WriteLine("Should not have reached this point"); + return 0; + } + + /// + /// Checks that the specified blob size matches the expected size for the format. + /// + /// + /// The same format if the size is correct, or + /// GSF_CLIP_FORMAT_WINDOWS_ERROR if the size is too small. + /// + private static GsfClipFormatWindows CheckFormatWindows(GsfClipFormatWindows format, string format_name, long blob_size, ref Exception error) + { + long offset = GetWindowsClipboardDataOffset(format); + if (blob_size <= offset) + { + SetErrorMissingClipboardData(ref error, format_name, offset + 1); + format = GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ERROR; + } + + return format; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfDocMetaData.cs b/BurnOutSharp/External/libgsf/GsfDocMetaData.cs new file mode 100644 index 00000000..1e2ec20b --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfDocMetaData.cs @@ -0,0 +1,555 @@ +/* + * gsf-doc-meta-data.c: + * + * Copyright (C) 2002-2006 Dom Lachowicz (cinamod@hotmail.com) + * Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using LibGSF.Input; +using LibGSF.Output; +using static LibGSF.GsfMSOleUtils; + +namespace LibGSF +{ + public class GsfDocProp + { + #region Properties + + public string Name { get; set; } + + public object Value { get; set; } + + /// + /// Optionally NULL + /// + public string LinkedTo { get; set; } + + public uint RefCount { get; set; } + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfDocProp() { } + + /// The name of the property. + /// A new GsfDocProp. + public static GsfDocProp Create(string name) + { + if (name == null) + return null; + + return new GsfDocProp + { + Name = name, + Value = null, + LinkedTo = null, + }; + } + + #endregion + + #region Functions + + /// + /// Release the given property. + /// + public void Free() + { + RefCount--; + if (RefCount == 0) + { + LinkedTo = null; + if (Value != null) + Value = null; + + Name = null; + } + } + + public GsfDocProp Reference() + { + RefCount++; + return this; + } + + /// The current value of prop, and replaces it with . + public object SwapValue(object val) + { + object old_val = Value; + Value = val; + return old_val; + } + + #endregion + } + + public class GsfDocMetaData + { + #region Properties + + internal Dictionary Table = new Dictionary(); + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfDocMetaData() { } + + /// + /// A new metadata property collection + /// + public static GsfDocMetaData Create() => new GsfDocMetaData(); + + #endregion + + #region Functions + + /// + /// The property with in meta. The caller can + /// modify the property value and link but not the name. + /// + public GsfDocProp Lookup(string name) + { + if (name == null) + return null; + + if (!Table.ContainsKey(name)) + return null; + + return Table[name]; + } + + /// + /// Take ownership of and and insert a property into meta. + /// If a property exists with @name, it is replaced (The link is lost) + /// + /// The id. + public void Insert(string name, object value) + { + if (name == null) + return; + + GsfDocProp docProp = GsfDocProp.Create(name); + docProp.Value = value; + docProp.LinkedTo = null; + docProp.RefCount = 1; + + Table[name] = docProp; + } + + /// + /// Read a stream formated as a set of MS OLE properties from and store the + /// results in . + /// + /// an Exception if there was an error. + /// Since: 1.14.24 + public Exception ReadFromMSOLE(GsfInput input) + { + GsfDocProp prop; + + // http://bugzilla.gnome.org/show_bug.cgi?id=352055 + // psiwin generates files with empty property sections + if (input.Size <= 0) + return null; + + byte[] data = input.Read(28, null); + if (data == null) + return new Exception("Unable to read MS property stream header"); + + /* + * Validate the Property Set Header. + * Format (bytes): + * 00 - 01 Byte order 0xfffe + * 02 - 03 Format 0 + * 04 - 05 OS Version high word is the OS + * 06 - 07 low word is the OS version + * 0 = win16 + * 1 = mac + * 2 = win32 + * 08 - 23 Class Identifier Usually Format ID + * 24 - 27 Section count Should be at least 1 + */ + ushort os = BitConverter.ToUInt16(data, 6); + ushort version = BitConverter.ToUInt16(data, 2); + uint num_sections = BitConverter.ToUInt32(data, 24); + + if (BitConverter.ToUInt16(data, 0) != 0xfffe + || (version != 0 && version != 1) + || os > 2 + || num_sections > input.Size / 20 + || num_sections > 100) // Arbitrary sanity check + { + return new Exception("Invalid MS property stream header"); + } + + // Extract the section info + /* + * The Format ID/Offset list follows. + * Format: + * 00 - 16 Section Name Format ID + * 16 - 19 Section Offset The offset is the number of + * bytes from the start of the + * whole stream to where the + * section begins. + */ + GsfMSOleMetaDataSection[] sections = new GsfMSOleMetaDataSection[num_sections]; + for (uint i = 0; i < num_sections; i++) + { + data = input.Read(20, null); + if (data == null) + return new Exception("Unable to read MS property stream header"); + + byte[] guid = new ReadOnlySpan(data, 0, 16).ToArray(); + if (guid.SequenceEqual(ComponentGUID)) + { + sections[i].Type = GsfMSOLEMetaDataType.COMPONENT_PROP; + } + else if (guid.SequenceEqual(DocumentGUID)) + { + sections[i].Type = GsfMSOLEMetaDataType.DOC_PROP; + } + else if (guid.SequenceEqual(UserGUID)) + { + sections[i].Type = GsfMSOLEMetaDataType.USER_PROP; + } + else + { + sections[i].Type = GsfMSOLEMetaDataType.USER_PROP; + Console.Error.WriteLine("Unknown property section type, treating it as USER"); + } + + sections[i].Offset = BitConverter.ToUInt32(data, 16); + } + + /* + * A section is the third part of the property set stream. + * Format (bytes): + * 00 - 03 Section size A byte count for the section (which is inclusive + * of the byte count itself and should always be a + * multiple of 4); + * 04 - 07 Property count A count of the number of properties + * 08 - xx An array of 32-bit Property ID/Offset pairs + * yy - zz An array of Property Type indicators/Value pairs + */ + for (uint i = 0; i < num_sections; i++) + { + if (input.Seek(sections[i].Offset, SeekOrigin.Begin) || (data = input.Read(8, null)) == null) + return new Exception("Invalid MS property section"); + + sections[i].IConvHandle = null; + sections[i].CharSize = 1; + sections[i].Dict = null; + sections[i].Size = BitConverter.ToUInt32(data, 0); // Includes header + sections[i].NumProps = BitConverter.ToUInt32(data, 4); + + if (sections[i].NumProps <= 0) + continue; + + if (sections[i].NumProps > input.Remaining() / 8) + return new Exception("Invalid MS property stream header or file truncated"); + + if (sections[i].Offset + sections[i].Size > input.Size) + return new Exception("Invalid MS property stream header or file truncated"); + + /* + * Get and save all the Property ID/Offset pairs. + * Format (bytes): + * 00 - 03 id Property ID + * 04 - 07 offset The distance from the start of the section to the + * start of the Property Type/Value pair. + */ + GsfMSOleMetaDataProp[] props = new GsfMSOleMetaDataProp[sections[i].NumProps]; + for (uint j = 0; j < sections[i].NumProps; j++) + { + if ((data = input.Read(8, null)) == null) + return new Exception("Invalid MS property section"); + + props[j].Id = BitConverter.ToUInt32(data, 0); + props[j].Offset = BitConverter.ToUInt32(data, 4); + } + + // FIXME: Should we check that ids are distinct? + + // Order prop info by offset to facilitate bounds checking + List tempProps = new List(props); + tempProps.Sort(PropertyCompare); + props = tempProps.ToArray(); + + // Sanity checks. + for (uint j = 0; j < sections[i].NumProps; j++) + { + uint end = (uint)((j == sections[i].NumProps - 1) ? sections[i].Size : props[j + 1].Offset); + if (props[j].Offset < 0 || props[j].Offset + 4 > end) + return new Exception("Invalid MS property section"); + } + + // Find and process the code page. + // Property ID 1 is reserved as an indicator of the code page. + sections[i].IConvHandle = null; + sections[i].CharSize = 1; + + for (uint j = 0; j < sections[i].NumProps; j++) // First codepage + { + if (props[j].Id == 1) + { + sections[i].PropertyRead(input, props, j, this); + if ((prop = Lookup(GsfMetaNames.GSF_META_NAME_CODEPAGE)) != null) + { + object val = prop.Value; + if (val != null && val is int) + { + int codepage = (int)val; + sections[i].IConvHandle = Encoding.GetEncoding(codepage); + sections[i].CharSize = (uint)CodePageCharSize(codepage); + } + } + } + } + + if (sections[i].IConvHandle == null) + sections[i].IConvHandle = Encoding.GetEncoding(1252); + + // Find and process the Property Set Dictionary + // Property ID 0 is reserved as an indicator of the dictionary. + // For User Defined Sections, Property ID 0 is NOT a dictionary. + + for (uint j = 0; j < sections[i].NumProps; j++) // The dictionary + { + if (props[j].Id == 0) + sections[i].PropertyRead(input, props, j, this); + } + + // Process all the properties + for (uint j = 0; j < sections[i].NumProps; j++) // The rest + { + if (props[j].Id > 1) + sections[i].PropertyRead(input, props, j, this); + } + + sections[i].IConvHandle = null; + if (sections[i].Dict != null) + sections[i].Dict = null; + } + + return null; + } + + /// + /// If does not exist in the collection, do nothing. If @name does exist, + /// remove it and its value from the collection + /// + /// The non-null string name of the property + public void Remove(string name) + { + if (name == null) + return; + + if (!Table.ContainsKey(name)) + return; + + Table.Remove(name); + } + + /// The property with in meta. + public GsfDocProp Steal(string name) + { + if (name == null) + return null; + + if (!Table.ContainsKey(name)) + return null; + + GsfDocProp prop = Table[name]; + if (prop != null) + Table.Remove(name); + + return prop; + } + + public void Store(GsfDocProp prop) + { + if (prop == null) + return; + + if (Table.ContainsKey(prop.Name) && Table[prop.Name] == prop) + return; + + Table[prop.Name] = prop; + } + + /// The number of items in this collection + public int Size() => Table.Count; + + /// + /// A kludge to differentiate DocumentSummary from Summary + /// true on success + /// Since: 1.14.24 + public bool WriteToMSOLE(GsfOutput output, bool doc_not_component) + { + byte[] header = + { + 0xfe, 0xff, // Byte order + 0, 0, // Format + 0x04, 0x0a, // OS : XP == 0xA04 + 0x02, 0x00, // Win32 == 2 + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // Clasid = 0 + }; + + bool success = false; + byte[] buf = new byte[4]; + + const int default_codepage = 1252; + + WritePropState state = new WritePropState + { + CodePage = 0, + IConvHandle = null, + CharSize = 1, + Output = output, + Dict = null, + BuiltIn = new WritePropStatePropList + { + Count = 1, // Codepage + Props = null, + }, + User = new WritePropStatePropList + { + Count = 2, // Codepage and Dictionary + Props = null, + }, + DocNotComponent = doc_not_component, + }; + + foreach (var prop in Table) + { + state.CountProperties(prop.Key, prop.Value); + } + + state.IConvHandle = Encoding.GetEncoding(default_codepage); + if (state.CodePage == 0) + { + state.GuessCodePage(false); + if (state.Dict != null) + state.GuessCodePage(true); + + if (state.CodePage == 0) + state.CodePage = default_codepage; + } + + state.IConvHandle = Encoding.GetEncoding(state.CodePage); + state.CharSize = CodePageCharSize(state.CodePage); + + // Write stream header + buf = BitConverter.GetBytes((uint)(state.Dict != null ? 2 : 1)); + if (!output.Write(header.Length, header) || !output.Write(4, buf)) + { + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return success; + } + + // Write section header(s) + buf = BitConverter.GetBytes((uint)(state.Dict != null ? 0x44 : 0x30)); + if (!output.Write(16, doc_not_component ? DocumentGUID : ComponentGUID) || !output.Write(4, buf)) + { + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return success; + } + + if (state.Dict != null) + { + buf = BitConverter.GetBytes((uint)0); + if (!output.Write(UserGUID.Length, UserGUID) || !output.Write(4, buf)) // Bogus position, fix it later + { + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return success; + } + } + + // Write section(s) + if (!state.WriteSection(false)) + { + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return success; + } + + if (state.Dict != null) + { + long baseOffset = state.Output.CurrentOffset; + buf = BitConverter.GetBytes((uint)baseOffset); + if (!state.Output.Seek(0x40, SeekOrigin.Begin) + || !output.Write(4, buf) + || !state.Output.Seek(0, SeekOrigin.End) + || !state.WriteSection(true)) + { + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return success; + } + } + + state.IConvHandle = null; + state.BuiltIn.Props = null; + state.User.Props = null; + state.Dict = null; + return true; + } + + #endregion + + #region Utilities + + private int CodePageCharSize(int codepage) => (codepage == 1200 || codepage == 1201 ? 2 : 1); + + private static int PropertyCompare(GsfMSOleMetaDataProp prop_a, GsfMSOleMetaDataProp prop_b) + { + if (prop_a.Offset < prop_b.Offset) + return -1; + else if (prop_a.Offset > prop_b.Offset) + return +1; + else + return 0; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfLibXML.cs b/BurnOutSharp/External/libgsf/GsfLibXML.cs new file mode 100644 index 00000000..4390b699 --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfLibXML.cs @@ -0,0 +1,864 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-libxml.c : + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Schema; +using LibGSF.Input; +using LibGSF.Output; + +namespace LibGSF +{ + #region Enums + + /// + /// Controls the handling of character data within a parser node. + /// + public enum GsfXMLContent + { + /// + /// Node has no cstr contents + /// + GSF_XML_NO_CONTENT = 0, + + /// + /// Node has cstr contents + /// + GSF_XML_CONTENT, + + /// + /// Node has contents that is shared with children + /// + GSF_XML_SHARED_CONTENT, + + /// + /// Node is second or later occurrence + /// + GSF_XML_2ND + } + + public enum GsfXMLOutState + { + GSF_XML_OUT_NOCONTENT, + GSF_XML_OUT_CHILD, + GSF_XML_OUT_CHILD_PRETTY, + GSF_XML_OUT_CONTENT + } + + #endregion + + #region Delegates + + public delegate void GsfXMLInExtDtor(GsfXMLIn xin, object old_state); + + public delegate bool GsfXMLInUnknownFunc(GsfXMLIn xin, string elem, string[] attrs); + + public delegate bool GsfXMLProbeFunc(string name, string prefix, string URI, int nb_namespaces, string[] namespaces, int nb_attributes, int nb_defaulted, string[] attributes); + + #endregion + + #region Classes + + // TODO: Figure out what the structure of this is + public class GsfXMLBlob + { + + } + + public class GsfXMLIn : GsfInput + { + #region Properties + + /// + /// User data + /// + public object UserState { get; set; } + + /// + /// The current node content + /// + public string Content { get; set; } + + /// + /// Current document being parsed + /// + public XmlDocument Doc { get; set; } + + /// + /// Current node (not on the stack) + /// + public XmlNode Node { get; set; } + + public Stack NodeStack { get; private set; } + + #endregion + + #region Internal Properties + + public GsfInput Input { get; set; } + + public Stack ContentsStack { get; internal set; } + + public bool Initialized { get; internal set; } + + #endregion + + #region Functions + + /// + /// Take the first node from as the current node and call its start handler. + /// + /// Arbitrary content for the parser + public void PushState(XmlDocument doc, object new_state, GsfXMLInExtDtor dtor, string[] attrs) + { + if (doc == null) + return; + if (doc.DocumentType == null) + return; + + // TODO: Figure out how to define Start here + PushChild(doc.FirstChild, attrs, null); + } + + public void StartElement(string name, string ns) + { + if ((name != null && Node.Name != name) || (ns != null && Node.NamespaceURI != ns)) + return; + + Node = Node.NextSibling; + } + + public void EndElement() + { + if (Node.NodeType != XmlNodeType.EndElement) + throw new XmlException(); + + Node = Node.NextSibling; + } + + /// + /// This function will not be called when parsing an empty document. + /// + public void StartDocument() + { + Initialized = true; + Node = Doc.FirstChild; + NodeStack = new Stack(); + ContentsStack = new Stack(); + } + + public void EndDocument() + { + Content = null; + if (Initialized) + { + NodeStack = null; + Initialized = false; + + if (Node != Doc.DocumentType) + Console.Error.WriteLine("Document likely damaged."); + } + } + + #endregion + + #region Utilities + + private string NodeName(XmlNode node) => (node.Name != null) ? node.Name : "{catch all)}"; + + private void PushChild(XmlNode node, string[] attrs, Action start) + { + if (node.InnerText != null) + { + if (Content.Length != 0) + { + ContentsStack.Push(Content); + Content = new string('\0', 128); + } + else + { + ContentsStack.Push(null); + } + } + + NodeStack.Push(Node); + Node = node; + + start?.Invoke(this, attrs); + } + + #endregion + }; + + public class GsfXMLInParser : XmlReader + { + #region Properties + + /// + /// Parent GsfXMLIn for reading + /// + public GsfXMLIn Parent { get; internal set; } + + /// + /// Current user state + /// + public GsfXMLIn UserState { get; internal set; } + + /// + /// Internal reader instance for unhandled functionality + /// + private readonly XmlReader inst; + + #endregion + + #region Override Properties + + public override int AttributeCount => inst.AttributeCount; + + public override string BaseURI => inst.BaseURI; + + public override int Depth => inst.Depth; + + public override bool EOF => inst.EOF; + + public override bool IsEmptyElement => inst.IsEmptyElement; + + public override string LocalName => inst.LocalName; + + public override string NamespaceURI => inst.NamespaceURI; + + public override XmlNameTable NameTable => inst.NameTable; + + public override XmlNodeType NodeType => inst.NodeType; + + public override string Prefix => inst.Prefix; + + public override ReadState ReadState => inst.ReadState; + + public override string Value => inst.Value; + + #endregion + + #region Constructor and Destructor + + /// + /// Constructor + /// + public GsfXMLInParser(GsfXMLIn parent) + { + if (parent == null) + return; + + Parent = parent; + + XmlReaderSettings settings = new XmlReaderSettings + { + ValidationType = ValidationType.None, + ValidationFlags = XmlSchemaValidationFlags.AllowXmlAttributes, + }; + + inst = Create(inputUri: null, settings); + + Parent.StartDocument(); + } + + /// + /// Destructor + /// + ~GsfXMLInParser() + { + Parent.EndDocument(); + } + + #endregion + + #region XmlReader Custom Implementation + + /// + public override void ReadStartElement() => Parent.StartElement(null, null); + + /// + public override void ReadStartElement(string name) => Parent.StartElement(name, null); + + /// + public override void ReadStartElement(string localname, string ns) => Parent.StartElement(localname, ns); + + /// + public override void ReadEndElement() => Parent.EndElement(); + + /// + public override string ReadElementContentAsString() => Parent.Node.InnerText; + + #endregion + + #region XmlReader Default Implementation + + /// + public override string GetAttribute(int i) => inst.GetAttribute(i); + + /// + public override string GetAttribute(string name) => inst.GetAttribute(name); + + /// + public override string GetAttribute(string name, string namespaceURI) => inst.GetAttribute(name, namespaceURI); + + /// + public override string LookupNamespace(string prefix) => inst.LookupNamespace(prefix); + + /// + public override bool MoveToAttribute(string name) => inst.MoveToAttribute(name); + + /// + public override bool MoveToAttribute(string name, string ns) => inst.MoveToAttribute(name, ns); + + /// + public override bool MoveToElement() => inst.MoveToElement(); + + /// + public override bool MoveToFirstAttribute() => inst.MoveToFirstAttribute(); + + /// + public override bool MoveToNextAttribute() => inst.MoveToNextAttribute(); + + /// + public override bool Read() => inst.Read(); + + /// + public override bool ReadAttributeValue() => inst.ReadAttributeValue(); + + /// + public override void ResolveEntity() => inst.ResolveEntity(); + + #endregion + } + + public class GsfXMLOut + { + #region Properties + + public GsfOutput Output { get; set; } = null; + + #endregion + + #region Internal Properties + + public string DocType { get; private set; } = null; + + public Stack Stack { get; private set; } = new Stack(); + + public GsfXMLOutState State { get; private set; } = GsfXMLOutState.GSF_XML_OUT_CHILD; + + public int Indent { get; private set; } = 0; + + public bool PrettyPrint { get; private set; } = true; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + protected GsfXMLOut() { } + + /// + /// Create an XML output stream. + /// + public static GsfXMLOut Create(GsfOutput output) + { + if (output == null) + return null; + + return new GsfXMLOut() + { + Output = output + }; + } + + #endregion + + #region Functions + + /// + /// Write the document start + /// + public void StartDocument() + { + string header = "\n"; + Output.Write(header.Length, Encoding.UTF8.GetBytes(header)); + if (DocType != null) + Output.PutString(DocType); + } + + /// + /// Output a start element , if necessary preceeded by an XML declaration. + /// + /// Element name + public void StartElement(string id) + { + if (id == null) + return; + + if (State == GsfXMLOutState.GSF_XML_OUT_NOCONTENT) + { + if (PrettyPrint) + Output.Write(2, Encoding.UTF8.GetBytes(">\n")); + else + Output.Write(1, Encoding.UTF8.GetBytes(">")); + } + + OutIndent(); + Output.PrintF($"<{id}"); + + Stack.Push(id); + Indent++; + State = GsfXMLOutState.GSF_XML_OUT_NOCONTENT; + } + + /// + /// Closes/ends an XML element. + /// + /// The element that has been closed. + public string EndElement() + { + if (Stack == null || Stack.Count == 0) + return null; + + string id = Stack.Pop(); + Indent--; + switch (State) + { + case GsfXMLOutState.GSF_XML_OUT_NOCONTENT: + if (PrettyPrint) + Output.Write(3, Encoding.UTF8.GetBytes("/>\n")); + else + Output.Write(2, Encoding.UTF8.GetBytes("/>")); + + break; + + case GsfXMLOutState.GSF_XML_OUT_CHILD_PRETTY: + OutIndent(); + if (PrettyPrint) + Output.PrintF($"\n"); + else + Output.PrintF($""); + + break; + + case GsfXMLOutState.GSF_XML_OUT_CHILD: + case GsfXMLOutState.GSF_XML_OUT_CONTENT: + if (PrettyPrint) + Output.PrintF($"\n"); + else + Output.PrintF($""); + + break; + } + + State = PrettyPrint ? GsfXMLOutState.GSF_XML_OUT_CHILD_PRETTY : GsfXMLOutState.GSF_XML_OUT_CHILD; + return id; + } + + /// New state of pretty-print flag. + /// The previous state of the pretty-print flag. + public bool SetPrettyPrint(bool pp) + { + bool res = PrettyPrint; + if (pp != res) + PrettyPrint = pp; + + return res; + } + + /// + /// Convenience routine to output a simple element with content . + /// + /// Element name + /// Content of the element + public void OutSimpleElement(string id, string content) + { + StartElement(id); + if (content != null) + AddString(null, content); + + EndElement(); + } + + /// + /// Convenience routine to output an element with integer value . + /// + /// Element name + /// Element value + public void OutSimpleSignedElement(string id, long val) + { + StartElement(id); + AddSigned(null, val); + EndElement(); + } + + /// + /// Convenience routine to output an element with float value using + /// significant digits. + /// + /// Element name + /// Element value + /// The number of significant digits to use, -1 meaning "enough". + public void OutSimpleFloatElement(string id, double val, int precision) + { + StartElement(id); + AddFloat(null, val, precision); + EndElement(); + } + + /// + /// Dump to an attribute named @id without checking to see if + /// the content needs escaping. A useful performance enhancement when + /// the application knows that structure of the content well. If + /// is null do nothing (no warning, no output) + /// + /// Tag id, or null for node content + /// A UTF-8 encoded string to export + public void AddStringUnchecked(string id, string valUtf8) + { + if (valUtf8 == null) + return; + + if (id == null) + { + CloseTagIfNecessary(); + Output.Write(valUtf8.Length, Encoding.UTF8.GetBytes(valUtf8)); + } + else + { + Output.PrintF($" {id}=\"{valUtf8}\""); + } + } + + /// + /// Dump to an attribute named or as the nodes content escaping + /// characters as necessary. If @valUtf8 is %null do nothing (no warning, no + /// output) + /// + /// Tag id, or null for node content + /// A UTF-8 encoded string + public void AddString(string id, string valUtf8) + { + if (valUtf8 == null) + return; + + int cur = 0; // valUtf8[0]; + int start = 0; // valUtf8[0]; + + if (id == null) + CloseTagIfNecessary(); + else + Output.PrintF($" {id}=\""); + + while (valUtf8[cur] != '\0') + { + if (valUtf8[cur] == '<') + { + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + Output.Write(4, Encoding.UTF8.GetBytes("<")); + } + else if (valUtf8[cur] == '>') + { + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + Output.Write(4, Encoding.UTF8.GetBytes(">")); + } + else if (valUtf8[cur] == '&') + { + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + Output.Write(5, Encoding.UTF8.GetBytes("&")); + } + else if (valUtf8[cur] == '"') + { + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + Output.Write(6, Encoding.UTF8.GetBytes(""")); + } + else if ((valUtf8[cur] == '\n' || valUtf8[cur] == '\r' || valUtf8[cur] == '\t') && id != null) + { + string tempBuf = $"&#{(int)valUtf8[cur]};"; + byte[] buf = Encoding.UTF8.GetBytes(tempBuf); + + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + Output.Write(buf.Length, buf); + } + else if ((valUtf8[cur] >= 0x20 && valUtf8[cur] != 0x7f) || (valUtf8[cur] == '\n' || valUtf8[cur] == '\r' || valUtf8[cur] == '\t')) + { + cur++; + } + else + { + // This is immensely pathetic, but XML 1.0 does not + // allow certain characters to be encoded. XML 1.1 + // does allow this, but libxml2 does not support it. + Console.Error.WriteLine($"Unknown char {valUtf8[cur]} in string"); + + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + start = ++cur; + } + } + + if (cur != start) + Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8)); + + if (id != null) + Output.Write(1, Encoding.UTF8.GetBytes("\"")); + } + + /// + /// Dump bytes in into the content of node using base64 + /// + /// Tag id, or null for node content + /// Data to be written + /// Length of data + public void AddBase64(string id, byte[] data, int len) => AddStringUnchecked(id, Convert.ToBase64String(data, 0, len)); + + /// + /// Dump boolean value to an attribute named or as the nodes content + /// + /// Tag id, or %null for node content + /// A boolean + /// Use '1' or '0' to simplify import + public void AddBool(string id, bool val) => AddStringUnchecked(id, val ? "1" : "0"); + + /// + /// Dump integer value to an attribute named or as the nodes content + /// + /// Tag id, or null for node content + /// The value + public void AddSigned(string id, long val) => AddStringUnchecked(id, val.ToString()); + + /// + /// Dump unsigned integer value to an attribute named or as the nodes + /// + /// Tag id, or null for node content + /// The value + public void AddUnsigned(string id, ulong val) => AddStringUnchecked(id, val.ToString()); + + /// + /// Dump float value to an attribute named or as the nodes + /// content with precision . The number will be formattted + /// according to the "C" locale. + /// + /// Tag id, or null for node content + /// The value + /// The number of significant digits to use, -1 meaning "enough". + public void AddFloat(string id, double val, int precision) + { + if (precision < 0 || precision > 17) + AddStringUnchecked(id, val.ToString()); + else + AddStringUnchecked(id, val.ToString($"F{precision}")); + } + + /// + /// Dump Color .. to an attribute named or as the nodes content + /// + /// Tag id, or null for node content + /// Red value + /// Green value + /// Blue value + public void AddColor(string id, uint r, uint g, uint b) + { + string buf = $"{r:X}:{g:X}:{b:X}\0"; + AddStringUnchecked(id, buf); + } + + /// + /// Output the value of as a string. Does NOT store any type information + /// with the string, just the value. + /// + /// Tag id, or null for node content + public void AddValue(string id, object val) + { + if (val == null) + return; + + if (val is char) + { + // FIXME: What if we are in 0x80-0xff? + AddString(id, $"{(char)val}\0"); + } + else if (val is byte) + { + // FIXME: What if we are in 0x80-0xff? + AddString(id, $"{(byte)val}\0"); + } + else if (val is bool) + { + AddString(id, (bool)val ? "t" : "f"); + } + else if (val is int) + { + AddSigned(id, (int)val); + } + else if (val is uint) + { + AddUnsigned(id, (uint)val); + } + else if (val is long) + { + AddSigned(id, (long)val); + } + else if (val is ulong) + { + AddUnsigned(id, (ulong)val); + } + else if (val is float) + { + AddFloat(id, (float)val, -1); + } + else if (val is double) + { + AddFloat(id, (double)val, -1); + } + else if (val is string) + { + AddString(id, (string)val); + } + else if (val is DateTime) + { + DateTime ts = (DateTime)val; + string str = ts.ToString("yyyy-MM-dd hh:mm:ss"); + AddString(id, str); + } + + // FIXME FIXME FIXME Add some error checking + } + + public void SetOutput(GsfOutput output) + { + if (output.Wrap(this)) + Output = output; + } + + #endregion + + #region Utilities + + private void OutIndent() + { + if (!PrettyPrint) + return; + + // 2-space indent steps + for (int i = Indent; i > 0; i--) + { + Output.Write(2, Encoding.UTF8.GetBytes(" ")); + } + } + + private void CloseTagIfNecessary() + { + if (State == GsfXMLOutState.GSF_XML_OUT_NOCONTENT) + { + State = GsfXMLOutState.GSF_XML_OUT_CONTENT; + Output.Write(1, new byte[] { (byte)'>' }); + } + } + + #endregion + } + + public class GsfLibXML + { + /// + /// Try to parse as a value of type into . + /// + /// Result value + /// Type of data + /// Value string + /// + /// True when parsing of as a value of type was succesfull; + /// false otherwise. + /// + public static bool ValueFromString(ref object res, Type t, string str) + { + if (str == null) + return false; + + res = null; + + // Handle nullable DateTime + if (t == typeof(DateTime)) + t = typeof(DateTime?); + + // If the passed-in type is derived from G_TYPE_ENUM + // or G_TYPE_FLAGS, we cannot switch on its type + // because we don't know its GType at compile time. + // We just pretend to have a G_TYPE_ENUM/G_TYPE_FLAGS. + if (t.IsEnum) + t = t.GetEnumUnderlyingType(); + + if (t == typeof(char)) + res = str[0]; + else if (t == typeof(byte)) + res = (byte)str[0]; + else if (t == typeof(bool)) + res = (char.ToLower(str[0]) == 't' || char.ToLower(str[0]) == 'y' || str[0] == '0'); + else if (t == typeof(int)) + res = int.Parse(str); + else if (t == typeof(uint)) + res = uint.Parse(str); + else if (t == typeof(long)) + res = long.Parse(str); + else if (t == typeof(ulong)) + res = ulong.Parse(str); + // TODO: Handle enum and flag strings + else if (t == typeof(float)) + res = float.Parse(str); + else if (t == typeof(double)) + res = double.Parse(str); + else if (t == typeof(string)) + res = str; + else if (t == typeof(DateTime?)) + res = DateTime.Parse(str); + else + Console.Error.WriteLine($"ValueFromString(): Don't know how to handle type '{t.Name}'"); + + return res != null; + } + } + + #endregion +} diff --git a/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs b/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs new file mode 100644 index 00000000..b7344410 --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs @@ -0,0 +1,80 @@ +/* THIS IS NOT INSTALLED */ + +/* + * gsf-MSOLE-impl.h: + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +namespace LibGSF +{ + public static class GsfMSOleImpl + { + public const int OLE_HEADER_SIZE = 0x200; /* independent of big block size size */ + public const int OLE_HEADER_SIGNATURE = 0x00; + public const int OLE_HEADER_CLSID = 0x08; /* See ReadClassStg */ + public const int OLE_HEADER_MINOR_VER = 0x18; /* 0x33 and 0x3e have been seen */ + public const int OLE_HEADER_MAJOR_VER = 0x1a; /* 0x3 been seen in wild */ + public const int OLE_HEADER_BYTE_ORDER = 0x1c; /* 0xfe 0xff == Intel Little Endian */ + public const int OLE_HEADER_BB_SHIFT = 0x1e; + public const int OLE_HEADER_SB_SHIFT = 0x20; + /* 0x22..0x27 reserved == 0 */ + public const int OLE_HEADER_CSECTDIR = 0x28; + public const int OLE_HEADER_NUM_BAT = 0x2c; + public const int OLE_HEADER_DIRENT_START = 0x30; + /* 0x34..0x37 transacting signature must be 0 */ + public const int OLE_HEADER_THRESHOLD = 0x38; + public const int OLE_HEADER_SBAT_START = 0x3c; + public const int OLE_HEADER_NUM_SBAT = 0x40; + public const int OLE_HEADER_METABAT_BLOCK = 0x44; + public const int OLE_HEADER_NUM_METABAT = 0x48; + public const int OLE_HEADER_START_BAT = 0x4c; + public const int BAT_INDEX_SIZE = 4; + public const int OLE_HEADER_METABAT_SIZE = ((OLE_HEADER_SIZE - OLE_HEADER_START_BAT) / BAT_INDEX_SIZE); + + public const int DIRENT_MAX_NAME_SIZE = 0x40; + public const int DIRENT_DETAILS_SIZE = 0x40; + public const int DIRENT_SIZE = (DIRENT_MAX_NAME_SIZE + DIRENT_DETAILS_SIZE); + public const int DIRENT_NAME_LEN = 0x40; /* length in bytes incl 0 terminator */ + public const int DIRENT_TYPE = 0x42; + public const int DIRENT_COLOUR = 0x43; + public const int DIRENT_PREV = 0x44; + public const int DIRENT_NEXT = 0x48; + public const int DIRENT_CHILD = 0x4c; + public const int DIRENT_CLSID = 0x50; /* only for dirs */ + public const int DIRENT_USERFLAGS = 0x60; /* only for dirs */ + public const int DIRENT_CREATE_TIME = 0x64; /* for files */ + public const int DIRENT_MODIFY_TIME = 0x6c; /* for files */ + public const int DIRENT_FIRSTBLOCK = 0x74; + public const int DIRENT_FILE_SIZE = 0x78; + /* 0x7c..0x7f reserved == 0 */ + + public const int DIRENT_TYPE_INVALID = 0; + public const int DIRENT_TYPE_DIR = 1; + public const int DIRENT_TYPE_FILE = 2; + public const int DIRENT_TYPE_LOCKBYTES = 3; /* ? */ + public const int DIRENT_TYPE_PROPERTY = 4; /* ? */ + public const int DIRENT_TYPE_ROOTDIR = 5; + public const uint DIRENT_MAGIC_END = 0xffffffff; + + /* flags in the block allocation list to denote special blocks */ + public const uint BAT_MAGIC_UNUSED = 0xffffffff; /* -1 */ + public const uint BAT_MAGIC_END_OF_CHAIN = 0xfffffffe; /* -2 */ + public const uint BAT_MAGIC_BAT = 0xfffffffd; /* a bat block, -3 */ + public const uint BAT_MAGIC_METABAT = 0xfffffffc; /* a metabat block -4 */ + } +} diff --git a/BurnOutSharp/External/libgsf/GsfMSOleUtils.cs b/BurnOutSharp/External/libgsf/GsfMSOleUtils.cs new file mode 100644 index 00000000..67c0ae39 --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfMSOleUtils.cs @@ -0,0 +1,2431 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-MSOLE-utils.c: + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * Copyright (C) 2002-2006 Dom Lachowicz (cinamod@hotmail.com) + * excel_iconv* family of functions (C) 2001 by Vlad Harchev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using LibGSF.Input; +using LibGSF.Output; +using static LibGSF.GsfMetaNames; + +namespace LibGSF +{ + #region Enums + + public enum GsfMSOLEMetaDataType + { + /// + /// In either summary or docsummary + /// + COMMON_PROP, + + /// + /// SummaryInformation properties + /// + COMPONENT_PROP, + + /// + /// DocumentSummaryInformation properties + /// + DOC_PROP, + + USER_PROP + } + + [Flags] + public enum GsfMSOLEVariantType + { + VT_EMPTY = 0, + VT_NULL = 1, + VT_I2 = 2, + VT_I4 = 3, + VT_R4 = 4, + VT_R8 = 5, + VT_CY = 6, + VT_DATE = 7, + VT_BSTR = 8, + VT_DISPATCH = 9, + VT_ERROR = 10, + VT_BOOL = 11, + VT_VARIANT = 12, + VT_UNKNOWN = 13, + VT_DECIMAL = 14, + + VT_I1 = 16, + VT_UI1 = 17, + VT_UI2 = 18, + VT_UI4 = 19, + VT_I8 = 20, + VT_UI8 = 21, + VT_INT = 22, + VT_UINT = 23, + VT_VOID = 24, + VT_HRESULT = 25, + VT_PTR = 26, + VT_SAFEARRAY = 27, + VT_CARRAY = 28, + VT_USERDEFINED = 29, + VT_LPSTR = 30, + VT_LPWSTR = 31, + + VT_FILETIME = 64, + VT_BLOB = 65, + VT_STREAM = 66, + VT_STORAGE = 67, + VT_STREAMED_OBJECT = 68, + VT_STORED_OBJECT = 69, + VT_BLOB_OBJECT = 70, + VT_CF = 71, + VT_CLSID = 72, + VT_VECTOR = 0x1000 + } + + #endregion + + #region Classes + + public static class GsfMSOleUtils + { + #region Constants + + /// + /// The Format Identifier for Summary Information + /// F29F85E0-4FF9-1068-AB91-08002B27B3D9 + /// + public static readonly byte[] ComponentGUID = + { + 0xe0, 0x85, 0x9f, 0xf2, 0xf9, 0x4f, 0x68, 0x10, + 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9 + }; + + /// + /// The Format Identifier for Document Summary Information + /// D5CDD502-2E9C-101B-9397-08002B2CF9AE + /// + public static readonly byte[] DocumentGUID = + { + 0x02, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae + }; + + /// + /// The Format Identifier for User-Defined Properties + /// D5CDD505-2E9C-101B-9397-08002B2CF9AE + /// + public static readonly byte[] UserGUID = + { + 0x05, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae + }; + + public static readonly GsfMSOleMetaDataPropMap[] BuiltInProperties = + { + new GsfMSOleMetaDataPropMap + { + MsName = "Dictionary", + Section = GsfMSOLEMetaDataType.COMMON_PROP, + GsfName = GSF_META_NAME_DICTIONARY, + Id = 0, + PreferredType = 0, // Magic + }, + new GsfMSOleMetaDataPropMap + { + MsName = "CodePage", + Section = GsfMSOLEMetaDataType.COMMON_PROP, + GsfName = GSF_META_NAME_CODEPAGE, + Id = 1, + PreferredType = GsfMSOLEVariantType.VT_I2 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "LOCALE_SYSTEM_DEFAULT", + Section = GsfMSOLEMetaDataType.COMMON_PROP, + GsfName = GSF_META_NAME_LOCALE_SYSTEM_DEFAULT, + Id = 0x80000000, + PreferredType = GsfMSOLEVariantType.VT_UI4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "CASE_SENSITIVE", + Section = GsfMSOLEMetaDataType.COMMON_PROP, + GsfName = GSF_META_NAME_CASE_SENSITIVE, + Id = 0x80000003, + PreferredType = GsfMSOLEVariantType.VT_UI4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Category", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_CATEGORY, + Id = 2, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "PresentationFormat", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_PRESENTATION_FORMAT, + Id = 3, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumBytes", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_BYTE_COUNT, + Id = 4, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumLines", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_LINE_COUNT, + Id = 5, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumParagraphs", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_PARAGRAPH_COUNT, + Id = 6, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumSlides", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_SLIDE_COUNT, + Id = 7, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumNotes", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_NOTE_COUNT, + Id = 8, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumHiddenSlides", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_HIDDEN_SLIDE_COUNT, + Id = 9, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumMMClips", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MM_CLIP_COUNT, + Id = 10, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Scale", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_SCALE, + Id = 11, + PreferredType = GsfMSOLEVariantType.VT_BOOL + }, + new GsfMSOleMetaDataPropMap + { + MsName = "HeadingPairs", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_HEADING_PAIRS, + Id = 12, + PreferredType = GsfMSOLEVariantType.VT_VECTOR | GsfMSOLEVariantType.VT_VARIANT + }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocumentParts", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_DOCUMENT_PARTS, + Id = 13, + PreferredType = GsfMSOLEVariantType.VT_VECTOR | GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Manager", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MANAGER, + Id = 14, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Company", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_COMPANY, + Id = 15, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "LinksDirty", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_LINKS_DIRTY, + Id = 16, + PreferredType = GsfMSOLEVariantType.VT_BOOL + }, + + // Possible match: { 0x0011, 0x0003, "PIDDSI_CCHWITHSPACES", "Number of characters with white-space" }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_17", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_17, + Id = 17, + PreferredType = GsfMSOLEVariantType.VT_UNKNOWN + }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_18", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_18, + Id = 18, + PreferredType = GsfMSOLEVariantType.VT_UNKNOWN + }, + + // Possible match: { 0x0013, 0x000b, "PIDDSI_SHAREDDOC", "Shared document" }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_19", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_19, + Id = 19, + PreferredType = GsfMSOLEVariantType.VT_BOOL + }, + + // Possible match: + PIDDSI_LINKBASE = 0x0014 + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_20", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_20, + Id = 20, + PreferredType = GsfMSOLEVariantType.VT_UNKNOWN + }, + + // Possible match: + PIDDSI_HLINKS= 0x0015, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_21", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_21, + Id = 21, + PreferredType = GsfMSOLEVariantType.VT_UNKNOWN + }, + + // Possible match: { 0x0016, 0x000b, "PIDDSI_HYPERLINKSCHANGED", "Hyper links changed" }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_22", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_22, + Id = 22, + PreferredType = GsfMSOLEVariantType.VT_BOOL + }, + + // Possible match: { 0x0017, 0x0003, "PIDDSI_VERSION", "Creating application version" }, + new GsfMSOleMetaDataPropMap + { + MsName = "DocSumInfo_23", + Section = GsfMSOLEMetaDataType.DOC_PROP, + GsfName = GSF_META_NAME_MSOLE_UNKNOWN_23, + Id = 23, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Title", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_TITLE, + Id = 2, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Subject", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_SUBJECT, + Id = 3, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Author", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_CREATOR, + Id = 4, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Keywords", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_KEYWORDS, + Id = 5, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Comments", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_DESCRIPTION, + Id = 6, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Template", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_TEMPLATE, + Id = 7, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "LastSavedBy", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_LAST_SAVED_BY, + Id = 8, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "RevisionNumber", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_REVISION_COUNT, + Id = 9, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "TotalEditingTime", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_EDITING_DURATION, + Id = 10, + PreferredType = GsfMSOLEVariantType.VT_FILETIME + }, + new GsfMSOleMetaDataPropMap + { + MsName = "LastPrinted", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_LAST_PRINTED, + Id = 11, + PreferredType = GsfMSOLEVariantType.VT_FILETIME + }, + new GsfMSOleMetaDataPropMap + { + MsName = "CreateTime", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_DATE_CREATED, + Id = 12, + PreferredType = GsfMSOLEVariantType.VT_FILETIME + }, + new GsfMSOleMetaDataPropMap + { + MsName = "LastSavedTime", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_DATE_MODIFIED, + Id = 13, + PreferredType = GsfMSOLEVariantType.VT_FILETIME + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumPages", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_PAGE_COUNT, + Id = 14, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumWords", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_WORD_COUNT, + Id = 15, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "NumCharacters", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_CHARACTER_COUNT, + Id = 16, + PreferredType = GsfMSOLEVariantType.VT_I4 + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Thumbnail", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_THUMBNAIL, + Id = 17, + PreferredType = GsfMSOLEVariantType.VT_CF + }, + new GsfMSOleMetaDataPropMap + { + MsName = "AppName", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_GENERATOR, + Id = 18, + PreferredType = GsfMSOLEVariantType.VT_LPSTR + }, + new GsfMSOleMetaDataPropMap + { + MsName = "Security", + Section = GsfMSOLEMetaDataType.COMPONENT_PROP, + GsfName = GSF_META_NAME_SECURITY, + Id = 19, + PreferredType = GsfMSOLEVariantType.VT_I4 + } + }; + + public static readonly LanguageId[] MSOLELanguageIds = + { + new LanguageId { Tag = "-none-", Id = 0x0000 }, /* none (language neutral) */ + new LanguageId { Tag = "-none-", Id = 0x0400 }, /* none */ + new LanguageId { Tag = "af_ZA", Id = 0x0436 }, /* Afrikaans */ + new LanguageId { Tag = "am", Id = 0x045e }, /* Amharic */ + new LanguageId { Tag = "sq_AL", Id = 0x041c }, /* Albanian */ + new LanguageId { Tag = "ar_SA", Id = 0x0401 }, /* Arabic (Saudi) */ + new LanguageId { Tag = "ar_IQ", Id = 0x0801 }, /* Arabic (Iraq) */ + new LanguageId { Tag = "ar_EG", Id = 0x0c01 }, /* Arabic (Egypt) */ + new LanguageId { Tag = "ar_LY", Id = 0x1001 }, /* Arabic (Libya) */ + new LanguageId { Tag = "ar_DZ", Id = 0x1401 }, /* Arabic (Algeria) */ + new LanguageId { Tag = "ar_MA", Id = 0x1801 }, /* Arabic (Morocco) */ + new LanguageId { Tag = "ar_TN", Id = 0x1c01 }, /* Arabic (Tunisia) */ + new LanguageId { Tag = "ar_OM", Id = 0x2001 }, /* Arabic (Oman) */ + new LanguageId { Tag = "ar_YE", Id = 0x2401 }, /* Arabic (Yemen) */ + new LanguageId { Tag = "ar_SY", Id = 0x2801 }, /* Arabic (Syria) */ + new LanguageId { Tag = "ar_JO", Id = 0x2c01 }, /* Arabic (Jordan) */ + new LanguageId { Tag = "ar_LB", Id = 0x3001 }, /* Arabic (Lebanon) */ + new LanguageId { Tag = "ar_KW", Id = 0x3401 }, /* Arabic (Kuwait) */ + new LanguageId { Tag = "ar_AE", Id = 0x3801 }, /* Arabic (United Arab Emirates) */ + new LanguageId { Tag = "ar_BH", Id = 0x3c01 }, /* Arabic (Bahrain) */ + new LanguageId { Tag = "ar_QA", Id = 0x4001 }, /* Arabic (Qatar) */ + new LanguageId { Tag = "as", Id = 0x044d }, /* Assamese */ + new LanguageId { Tag = "az", Id = 0x042c }, /* Azerbaijani */ + new LanguageId { Tag = "hy_AM", Id = 0x042b }, /* Armenian */ + new LanguageId { Tag = "az", Id = 0x044c }, /* Azeri (Latin) az_ */ + new LanguageId { Tag = "az", Id = 0x082c }, /* Azeri (Cyrillic) az_ */ + new LanguageId { Tag = "eu_ES", Id = 0x042d }, /* Basque */ + new LanguageId { Tag = "be_BY", Id = 0x0423 }, /* Belarussian */ + new LanguageId { Tag = "bn", Id = 0x0445 }, /* Bengali bn_ */ + new LanguageId { Tag = "bg_BG", Id = 0x0402 }, /* Bulgarian */ + new LanguageId { Tag = "ca_ES", Id = 0x0403 }, /* Catalan */ + new LanguageId { Tag = "zh_TW", Id = 0x0404 }, /* Chinese (Taiwan) */ + new LanguageId { Tag = "zh_CN", Id = 0x0804 }, /* Chinese (PRC) */ + new LanguageId { Tag = "zh_HK", Id = 0x0c04 }, /* Chinese (Hong Kong) */ + new LanguageId { Tag = "zh_SG", Id = 0x1004 }, /* Chinese (Singapore) */ + new LanguageId { Tag = "ch_MO", Id = 0x1404 }, /* Chinese (Macau SAR) */ + new LanguageId { Tag = "hr_HR", Id = 0x041a }, /* Croatian */ + new LanguageId { Tag = "cs_CZ", Id = 0x0405 }, /* Czech */ + new LanguageId { Tag = "da_DK", Id = 0x0406 }, /* Danish */ + new LanguageId { Tag = "div", Id = 0x465 }, /* Divehi div_*/ + new LanguageId { Tag = "nl_NL", Id = 0x0413 }, /* Dutch (Netherlands) */ + new LanguageId { Tag = "nl_BE", Id = 0x0813 }, /* Dutch (Belgium) */ + new LanguageId { Tag = "en_US", Id = 0x0409 }, /* English (USA) */ + new LanguageId { Tag = "en_GB", Id = 0x0809 }, /* English (UK) */ + new LanguageId { Tag = "en_AU", Id = 0x0c09 }, /* English (Australia) */ + new LanguageId { Tag = "en_CA", Id = 0x1009 }, /* English (Canada) */ + new LanguageId { Tag = "en_NZ", Id = 0x1409 }, /* English (New Zealand) */ + new LanguageId { Tag = "en_IE", Id = 0x1809 }, /* English (Ireland) */ + new LanguageId { Tag = "en_ZA", Id = 0x1c09 }, /* English (South Africa) */ + new LanguageId { Tag = "en_JM", Id = 0x2009 }, /* English (Jamaica) */ + new LanguageId { Tag = "en", Id = 0x2409 }, /* English (Caribbean) */ + new LanguageId { Tag = "en_BZ", Id = 0x2809 }, /* English (Belize) */ + new LanguageId { Tag = "en_TT", Id = 0x2c09 }, /* English (Trinidad) */ + new LanguageId { Tag = "en_ZW", Id = 0x3009 }, /* English (Zimbabwe) */ + new LanguageId { Tag = "en_PH", Id = 0x3409 }, /* English (Phillipines) */ + new LanguageId { Tag = "et_EE", Id = 0x0425 }, /* Estonian */ + new LanguageId { Tag = "fo", Id = 0x0438 }, /* Faeroese fo_ */ + new LanguageId { Tag = "fa_IR", Id = 0x0429 }, /* Farsi */ + new LanguageId { Tag = "fi_FI", Id = 0x040b }, /* Finnish */ + new LanguageId { Tag = "fr_FR", Id = 0x040c }, /* French (France) */ + new LanguageId { Tag = "fr_BE", Id = 0x080c }, /* French (Belgium) */ + new LanguageId { Tag = "fr_CA", Id = 0x0c0c }, /* French (Canada) */ + new LanguageId { Tag = "fr_CH", Id = 0x100c }, /* French (Switzerland) */ + new LanguageId { Tag = "fr_LU", Id = 0x140c }, /* French (Luxembourg) */ + new LanguageId { Tag = "fr_MC", Id = 0x180c }, /* French (Monaco) */ + new LanguageId { Tag = "gl", Id = 0x0456 }, /* Galician gl_ */ + new LanguageId { Tag = "ga_IE", Id = 0x083c }, /* Irish Gaelic */ + new LanguageId { Tag = "gd_GB", Id = 0x100c }, /* Scottish Gaelic */ + new LanguageId { Tag = "ka_GE", Id = 0x0437 }, /* Georgian */ + new LanguageId { Tag = "de_DE", Id = 0x0407 }, /* German (Germany) */ + new LanguageId { Tag = "de_CH", Id = 0x0807 }, /* German (Switzerland) */ + new LanguageId { Tag = "de_AT", Id = 0x0c07 }, /* German (Austria) */ + new LanguageId { Tag = "de_LU", Id = 0x1007 }, /* German (Luxembourg) */ + new LanguageId { Tag = "de_LI", Id = 0x1407 }, /* German (Liechtenstein) */ + new LanguageId { Tag = "el_GR", Id = 0x0408 }, /* Greek */ + new LanguageId { Tag = "gu", Id = 0x0447 }, /* Gujarati gu_ */ + new LanguageId { Tag = "ha", Id = 0x0468 }, /* Hausa */ + new LanguageId { Tag = "he_IL", Id = 0x040d }, /* Hebrew */ + new LanguageId { Tag = "hi_IN", Id = 0x0439 }, /* Hindi */ + new LanguageId { Tag = "hu_HU", Id = 0x040e }, /* Hungarian */ + new LanguageId { Tag = "is_IS", Id = 0x040f }, /* Icelandic */ + new LanguageId { Tag = "id_ID", Id = 0x0421 }, /* Indonesian */ + new LanguageId { Tag = "iu", Id = 0x045d }, /* Inkutitut */ + new LanguageId { Tag = "it_IT", Id = 0x0410 }, /* Italian (Italy) */ + new LanguageId { Tag = "it_CH", Id = 0x0810 }, /* Italian (Switzerland) */ + new LanguageId { Tag = "ja_JP", Id = 0x0411}, /* Japanese */ + new LanguageId { Tag = "kn", Id = 0x044b }, /* Kannada kn_ */ + new LanguageId { Tag = "ks", Id = 0x0860 }, /* Kashmiri (India) ks_ */ + new LanguageId { Tag = "kk", Id = 0x043f }, /* Kazakh kk_ */ + new LanguageId { Tag = "kok", Id = 0x0457 }, /* Konkani kok_ */ + new LanguageId { Tag = "ko_KR", Id = 0x0412 }, /* Korean */ + new LanguageId { Tag = "ko", Id = 0x0812 }, /* Korean (Johab) ko_ */ + new LanguageId { Tag = "kir", Id = 0x0440 }, /* Kyrgyz */ + new LanguageId { Tag = "la", Id = 0x0476 }, /* Latin */ + new LanguageId { Tag = "lo", Id = 0x0454 }, /* Laothian */ + new LanguageId { Tag = "lv_LV", Id = 0x0426 }, /* Latvian */ + new LanguageId { Tag = "lt_LT", Id = 0x0427 }, /* Lithuanian */ + new LanguageId { Tag = "lt_LT", Id = 0x0827 }, /* Lithuanian (Classic) */ + new LanguageId { Tag = "mk", Id = 0x042f }, /* FYRO Macedonian */ + new LanguageId { Tag = "my_MY", Id = 0x043e }, /* Malaysian */ + new LanguageId { Tag = "my_BN", Id = 0x083e }, /* Malay Brunei Darussalam */ + new LanguageId { Tag = "ml", Id = 0x044c }, /* Malayalam ml_ */ + new LanguageId { Tag = "mr", Id = 0x044e }, /* Marathi mr_ */ + new LanguageId { Tag = "mt", Id = 0x043a }, /* Maltese */ + new LanguageId { Tag = "mo", Id = 0x0450 }, /* Mongolian */ + new LanguageId { Tag = "ne_NP", Id = 0x0461 }, /* Napali (Nepal) */ + new LanguageId { Tag = "ne_IN", Id = 0x0861 }, /* Nepali (India) */ + new LanguageId { Tag = "nb_NO", Id = 0x0414 }, /* Norwegian (Bokmaal) */ + new LanguageId { Tag = "nn_NO", Id = 0x0814 }, /* Norwegian (Nynorsk) */ + new LanguageId { Tag = "or", Id = 0x0448 }, /* Oriya or_ */ + new LanguageId { Tag = "om", Id = 0x0472 }, /* Oromo (Afan, Galla) */ + new LanguageId { Tag = "pl_PL", Id = 0x0415 }, /* Polish */ + new LanguageId { Tag = "pt_BR", Id = 0x0416 }, /* Portuguese (Brazil) */ + new LanguageId { Tag = "pt_PT", Id = 0x0816 }, /* Portuguese (Portugal) */ + new LanguageId { Tag = "pa", Id = 0x0446 }, /* Punjabi pa_ */ + new LanguageId { Tag = "ps", Id = 0x0463 }, /* Pashto (Pushto) */ + new LanguageId { Tag = "rm", Id = 0x0417 }, /* Rhaeto_Romanic rm_ */ + new LanguageId { Tag = "ro_RO", Id = 0x0418 }, /* Romanian */ + new LanguageId { Tag = "ro_MD", Id = 0x0818 }, /* Romanian (Moldova) */ + new LanguageId { Tag = "ru_RU", Id = 0x0419 }, /* Russian */ + new LanguageId { Tag = "ru_MD", Id = 0x0819 }, /* Russian (Moldova) */ + new LanguageId { Tag = "se", Id = 0x043b }, /* Sami (Lappish) se_ */ + new LanguageId { Tag = "sa", Id = 0x044f }, /* Sanskrit sa_ */ + new LanguageId { Tag = "sr", Id = 0x0c1a }, /* Serbian (Cyrillic) sr_ */ + new LanguageId { Tag = "sr", Id = 0x081a }, /* Serbian (Latin) sr_ */ + new LanguageId { Tag = "sd", Id = 0x0459 }, /* Sindhi sd_ */ + new LanguageId { Tag = "sk_SK", Id = 0x041b }, /* Slovak */ + new LanguageId { Tag = "sl_SI", Id = 0x0424 }, /* Slovenian */ + new LanguageId { Tag = "wen", Id = 0x042e }, /* Sorbian wen_ */ + new LanguageId { Tag = "so", Id = 0x0477 }, /* Somali */ + new LanguageId { Tag = "es_ES", Id = 0x040a }, /* Spanish (Spain, Traditional) */ + new LanguageId { Tag = "es_MX", Id = 0x080a }, /* Spanish (Mexico) */ + new LanguageId { Tag = "es_ES", Id = 0x0c0a }, /* Spanish (Modern) */ + new LanguageId { Tag = "es_GT", Id = 0x100a }, /* Spanish (Guatemala) */ + new LanguageId { Tag = "es_CR", Id = 0x140a }, /* Spanish (Costa Rica) */ + new LanguageId { Tag = "es_PA", Id = 0x180a }, /* Spanish (Panama) */ + new LanguageId { Tag = "es_DO", Id = 0x1c0a }, /* Spanish (Dominican Republic) */ + new LanguageId { Tag = "es_VE", Id = 0x200a }, /* Spanish (Venezuela) */ + new LanguageId { Tag = "es_CO", Id = 0x240a }, /* Spanish (Colombia) */ + new LanguageId { Tag = "es_PE", Id = 0x280a }, /* Spanish (Peru) */ + new LanguageId { Tag = "es_AR", Id = 0x2c0a }, /* Spanish (Argentina) */ + new LanguageId { Tag = "es_EC", Id = 0x300a }, /* Spanish (Ecuador) */ + new LanguageId { Tag = "es_CL", Id = 0x340a }, /* Spanish (Chile) */ + new LanguageId { Tag = "es_UY", Id = 0x380a }, /* Spanish (Uruguay) */ + new LanguageId { Tag = "es_PY", Id = 0x3c0a }, /* Spanish (Paraguay) */ + new LanguageId { Tag = "es_BO", Id = 0x400a }, /* Spanish (Bolivia) */ + new LanguageId { Tag = "es_SV", Id = 0x440a }, /* Spanish (El Salvador) */ + new LanguageId { Tag = "es_HN", Id = 0x480a }, /* Spanish (Honduras) */ + new LanguageId { Tag = "es_NI", Id = 0x4c0a }, /* Spanish (Nicaragua) */ + new LanguageId { Tag = "es_PR", Id = 0x500a }, /* Spanish (Puerto Rico) */ + new LanguageId { Tag = "sx", Id = 0x0430 }, /* Sutu */ + new LanguageId { Tag = "sw", Id = 0x0441 }, /* Swahili (Kiswahili/Kenya) */ + new LanguageId { Tag = "sv_SE", Id = 0x041d }, /* Swedish */ + new LanguageId { Tag = "sv_FI", Id = 0x081d }, /* Swedish (Finland) */ + new LanguageId { Tag = "ta", Id = 0x0449 }, /* Tamil ta_ */ + new LanguageId { Tag = "tt", Id = 0x0444 }, /* Tatar (Tatarstan) tt_ */ + new LanguageId { Tag = "te", Id = 0x044a }, /* Telugu te_ */ + new LanguageId { Tag = "th_TH", Id = 0x041e }, /* Thai */ + new LanguageId { Tag = "ts", Id = 0x0431 }, /* Tsonga ts_ */ + new LanguageId { Tag = "tn", Id = 0x0432 }, /* Tswana tn_ */ + new LanguageId { Tag = "tr_TR", Id = 0x041f }, /* Turkish */ + new LanguageId { Tag = "tl", Id = 0x0464 }, /* Tagalog */ + new LanguageId { Tag = "tg", Id = 0x0428 }, /* Tajik */ + new LanguageId { Tag = "bo", Id = 0x0451 }, /* Tibetan */ + new LanguageId { Tag = "ti", Id = 0x0473 }, /* Tigrinya */ + new LanguageId { Tag = "uk_UA", Id = 0x0422 }, /* Ukrainian */ + new LanguageId { Tag = "ur_PK", Id = 0x0420 }, /* Urdu (Pakistan) */ + new LanguageId { Tag = "ur_IN", Id = 0x0820 }, /* Urdu (India) */ + new LanguageId { Tag = "uz", Id = 0x0443 }, /* Uzbek (Latin) uz_ */ + new LanguageId { Tag = "uz", Id = 0x0843 }, /* Uzbek (Cyrillic) uz_ */ + new LanguageId { Tag = "ven", Id = 0x0433 }, /* Venda ven_ */ + new LanguageId { Tag = "vi_VN", Id = 0x042a }, /* Vietnamese */ + new LanguageId { Tag = "cy_GB", Id = 0x0452 }, /* Welsh */ + new LanguageId { Tag = "xh", Id = 0x0434 }, /* Xhosa xh */ + new LanguageId { Tag = "yi", Id = 0x043d }, /* Yiddish yi_ */ + new LanguageId { Tag = "yo", Id = 0x046a }, /* Yoruba */ + new LanguageId { Tag = "zu", Id = 0x0435 }, /* Zulu zu_ */ + new LanguageId { Tag = "en_US", Id = 0x0800 } /* Default */ + }; + + public const int VBA_COMPRESSION_WINDOW = 4096; + + #endregion + + #region Utilities + + private static Dictionary NameToPropHash = null; + + public static GsfMSOleMetaDataPropMap GsfNameToProp(string name) + { + if (NameToPropHash == null) + { + NameToPropHash = new Dictionary(); + foreach (GsfMSOleMetaDataPropMap prop in GsfMSOleUtils.BuiltInProperties) + { + NameToPropHash[prop.GsfName] = prop; + } + } + + if (NameToPropHash.ContainsKey(name)) + return NameToPropHash[name]; + + return null; + } + + #endregion + } + + public class GsfMSOleMetaDataPropMap + { + public string MsName { get; set; } + + public GsfMSOLEMetaDataType Section { get; set; } + + public string GsfName { get; set; } + + public uint Id { get; set; } + + public GsfMSOLEVariantType PreferredType { get; set; } + } + + public class GsfMSOleMetaDataProp + { + public uint Id { get; set; } + + public long Offset { get; set; } + } + + public class GsfMSOleMetaDataSection + { + #region Properties + + public GsfMSOLEMetaDataType Type { get; set; } + + public long Offset { get; set; } + + public uint Size { get; set; } + + public uint NumProps { get; set; } + + public Encoding IConvHandle { get; set; } + + public uint CharSize { get; set; } + + public Dictionary Dict { get; set; } + + #endregion + + #region Functions + + public object PropertyParse(GsfMSOLEVariantType type, byte[] data, ref int dataPtr, int data_end) + { + // Not valid in a prop set + if (((int)type & ~0x1fff) != 0) + return null; + + object res = null; + string str = null; + uint len; + Exception error; + int bytes_needed = 0; + + type &= (GsfMSOLEVariantType)0xfff; + + bool is_vector = (type & GsfMSOLEVariantType.VT_VECTOR) != 0; + if (is_vector) + { + // A vector is basically an array. If the type associated with + // it is a variant, then each element can have a different + // variant type. Otherwise, each element has the same variant + // type associated with the vector. + + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + uint n = BitConverter.ToUInt32(data, dataPtr); + dataPtr += bytes_needed; + + int size1 = PropMinSize(type); + if (!NEED_RECS(n, (uint)size1, dataPtr, data_end, ref bytes_needed)) + return null; + + List vector = new List(); + + for (uint i = 0; i < n; i++) + { + int data0 = dataPtr; + object v = PropertyParse(type, data, ref dataPtr, data_end); + if (v != null) + vector.Add(v as GsfDocProp); + + if (dataPtr == data0) + break; + } + + return vector; + } + + res = new object(); + switch (type) + { + // A property with a type indicator of VT_EMPTY has no data + // associated with it; that is, the size of the value is zero. + case GsfMSOLEVariantType.VT_EMPTY: + // value::unset == empty + break; + + // This is like a pointer to null + case GsfMSOLEVariantType.VT_NULL: + // value::unset == null too :-) do we need to distinguish ? + break; + + // 2-byte signed integer + case GsfMSOLEVariantType.VT_I2: + if (!NEED_RECS(2, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToInt16(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 4-byte signed integer + case GsfMSOLEVariantType.VT_I4: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToInt32(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 32-bit IEEE floating-point value + case GsfMSOLEVariantType.VT_R4: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToSingle(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 64-bit IEEE floating-point value + case GsfMSOLEVariantType.VT_R8: + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToDouble(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 8-byte two's complement integer (scaled by 10,000) + case GsfMSOLEVariantType.VT_CY: + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + // CHEAT : just store as an int64 for now + res = BitConverter.ToInt64(data, dataPtr); + break; + + // 64-bit floating-point number representing the number of days + // (not seconds) since December 31, 1899. + case GsfMSOLEVariantType.VT_DATE: + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + dataPtr += bytes_needed; + break; + + // Pointer to null-terminated Unicode string; the string is pre- + // ceeded by a DWORD representing the byte count of the number + // of bytes in the string (including the terminating null). + case GsfMSOLEVariantType.VT_BSTR: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + dataPtr += bytes_needed; + break; + + case GsfMSOLEVariantType.VT_DISPATCH: + break; + + // A boolean (WORD) value containg 0 (false) or -1 (true). + case GsfMSOLEVariantType.VT_BOOL: + if (!NEED_RECS(1, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = (data[dataPtr] != 0x00); + dataPtr += bytes_needed; + break; + + // A type indicator (a DWORD) followed by the corresponding + // value. VT_VARIANT is only used in conjunction with + // VT_VECTOR. + case GsfMSOLEVariantType.VT_VARIANT: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = null; + type = (GsfMSOLEVariantType)BitConverter.ToUInt32(data, dataPtr); + dataPtr += bytes_needed; + return PropertyParse(type, data, ref dataPtr, data_end); + + // 1-byte unsigned integer + case GsfMSOLEVariantType.VT_UI1: + if (!NEED_RECS(1, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = data[dataPtr]; + dataPtr += bytes_needed; + break; + + // 1-byte signed integer + case GsfMSOLEVariantType.VT_I1: + if (!NEED_RECS(1, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = (sbyte)data[dataPtr]; + dataPtr += bytes_needed; + break; + + // 2-byte unsigned integer + case GsfMSOLEVariantType.VT_UI2: + if (!NEED_RECS(2, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToUInt16(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 4-type unsigned integer + case GsfMSOLEVariantType.VT_UI4: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToUInt32(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 8-byte signed integer + case GsfMSOLEVariantType.VT_I8: + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToInt64(data, dataPtr); + dataPtr += bytes_needed; + break; + + // 8-byte unsigned integer + case GsfMSOLEVariantType.VT_UI8: + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + res = BitConverter.ToUInt64(data, dataPtr); + dataPtr += bytes_needed; + break; + + // This is the representation of many strings. It is stored in + // the same representation as VT_BSTR. Note that the serialized + // representation of VP_LPSTR has a preceding byte count, + // whereas the in-memory representation does not. + case GsfMSOLEVariantType.VT_LPSTR: + { + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + len = BitConverter.ToUInt32(data, dataPtr); + dataPtr += bytes_needed; + + if (len >= 0x10000) + return null; + + uint need = len; + if (CharSize > 1 && (need & 3) != 0) + need = (uint)((need & ~3) + 4); + + if (!NEED_RECS(need, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + error = null; + + try + { + byte[] lpstrTemp = Encoding.Convert(IConvHandle, Encoding.UTF8, data, dataPtr, (int)(len > CharSize ? len - CharSize : 0)); + str = Encoding.UTF8.GetString(lpstrTemp); + } + catch (Exception ex) + { + error = ex; + } + + res = string.Empty; + if (str != null) + { + res = str; + } + else if (error != null) + { + Console.Error.WriteLine($"Error: {error.Message}"); + error = null; + } + else + { + Console.Error.WriteLine("unknown error converting string property, using blank"); + } + + dataPtr += bytes_needed; + break; + } + + // A counted and null-terminated Unicode string; a DWORD character + // count (where the count includes the terminating null) followed + // by that many Unicode (16-bit) characters. Note that the count + // is character count, not byte count. + case GsfMSOLEVariantType.VT_LPWSTR: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + len = BitConverter.ToUInt32(data, dataPtr); + dataPtr += bytes_needed; + + if (!NEED_RECS(len, 2, dataPtr, data_end, ref bytes_needed)) + return null; + + if (len >= 0x10000) + return null; + + error = null; + + try + { + byte[] lpwstrTemp = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, data, dataPtr, (int)len * 2); + str = Encoding.UTF8.GetString(lpwstrTemp); + } + catch (Exception ex) + { + error = ex; + } + + res = string.Empty; + if (str != null) + { + res = str; + } + else if (error != null) + { + Console.Error.WriteLine($"Error: {error.Message}"); + error = null; + } + else + { + Console.Error.WriteLine("unknown error converting string property, using blank"); + } + + dataPtr += bytes_needed; + break; + + // 64-bit FILETIME structure, as defined by Win32. + case GsfMSOLEVariantType.VT_FILETIME: + { + if (!NEED_RECS(8, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + // ft * 100ns since Jan 1 1601 + ulong ft = BitConverter.ToUInt64(data, dataPtr); + res = DateTime.FromFileTime((long)ft); + dataPtr += bytes_needed; + break; + } + + // A DWORD count of bytes, followed by that many bytes of data. + // The byte count does not include the four bytes for the length + // of the count itself: An empty blob would have a count of + // zero, followed by zero bytes. Thus the serialized represen- + // tation of a VT_BLOB is similar to that of a VT_BSTR but does + // not guarantee a null byte at the end of the data. + case GsfMSOLEVariantType.VT_BLOB: + if (!NEED_RECS(4, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + dataPtr += bytes_needed; + res = null; + break; + + // Indicates the value is stored in a stream that is sibling + // to the CONTENTS stream. Following this type indicator is + // data in the format of a serialized VT_LPSTR, which names + // the stream containing the data. + case GsfMSOLEVariantType.VT_STREAM: + res = null; + break; + + // Indicates the value is stored in an IStorage that is + // sibling to the CONTENTS stream. Following this type + // indicator is data in the format of a serialized VT_LPSTR, + // which names the IStorage containing the data. + case GsfMSOLEVariantType.VT_STORAGE: + res = null; + break; + + // Same as VT_STREAM, but indicates that the stream contains a + // serialized object, which is a class ID followed by initiali- + // zation data for the class. + case GsfMSOLEVariantType.VT_STREAMED_OBJECT: + res = null; + break; + + // Same as VT_STORAGE, but indicates that the designated + // IStorage contains a loadable object. + case GsfMSOLEVariantType.VT_STORED_OBJECT: + res = null; + break; + + // Contains a serialized object in the same representation as + // would appear in a VT_STREAMED_OBJECT. That is, following + // the VT_BLOB_OBJECT tag is a DWORD byte count of the + // remaining data (where the byte count does not include the + // size of itself) which is in the format of a class ID + // followed by initialization data for that class + case GsfMSOLEVariantType.VT_BLOB_OBJECT: + res = null; + break; + + case GsfMSOLEVariantType.VT_CF: + error = null; + if (!ParseVariantCF(res, data, ref dataPtr, ref data_end, ref error)) + { + // Suck, we can't propagate the error upwards + if (error != null) + { + Console.Error.WriteLine($"Error: {error.Message}"); + error = null; + } + else + { + Console.Error.WriteLine("Unknown error parsing vt_cf"); + } + + res = null; + } + + break; + + // A class ID (or other GUID) + case GsfMSOLEVariantType.VT_CLSID: + if (!NEED_RECS(16, 1, dataPtr, data_end, ref bytes_needed)) + return null; + + dataPtr += bytes_needed; + res = null; + break; + + // A DWORD containing a status code. + case GsfMSOLEVariantType.VT_ERROR: + + case GsfMSOLEVariantType.VT_UNKNOWN: + case GsfMSOLEVariantType.VT_DECIMAL: + case GsfMSOLEVariantType.VT_INT: + case GsfMSOLEVariantType.VT_UINT: + case GsfMSOLEVariantType.VT_VOID: + case GsfMSOLEVariantType.VT_HRESULT: + case GsfMSOLEVariantType.VT_PTR: + case GsfMSOLEVariantType.VT_SAFEARRAY: + case GsfMSOLEVariantType.VT_CARRAY: + case GsfMSOLEVariantType.VT_USERDEFINED: + Console.Error.WriteLine($"Type {VariantName(type)} (0x{type:x}) is not permitted in property sets"); + res = null; + break; + + default: + res = null; + break; + } + + return res; + } + + public bool PropertyRead(GsfInput input, GsfMSOleMetaDataProp[] props, uint i, GsfDocMetaData accum) + { + if (i >= NumProps) + return false; + + long size = ((i + 1) >= NumProps) ? Size : props[i + 1].Offset; + if (size < props[i].Offset + 4) + return false; + + string name; + object val; + + size -= props[i].Offset; // Includes the type id + + // From now on, size is actually a size. + byte[] data; + if (input.Seek(Offset + props[i].Offset, SeekOrigin.Begin) || (data = input.Read((int)size, null)) == null) + { + Console.Error.WriteLine("Failed to read prop #%d", i); + return false; + } + + int dataPtr = 0; // data[0] + GsfMSOLEVariantType type = (GsfMSOLEVariantType)BitConverter.ToUInt32(data, dataPtr); + dataPtr += 4; + + // Dictionary is magic + if (props[i].Id == 0) + { + uint len, id; + int gslen; + int start = dataPtr; // data[dataPtr] + int end = start + ((int)size - 4); + + if (Dict != null) + return false; + + Dict = new Dictionary(); + + uint n = (uint)type; + for (uint j = 0; j < n; j++) + { + if (end - dataPtr < 8) + return false; + + id = BitConverter.ToUInt32(data, dataPtr); + len = BitConverter.ToUInt32(data, dataPtr + 4); + + if (len >= 0x10000) + return false; + if (len > end - dataPtr + 8) + return false; + + gslen = 0; + + try + { + byte[] convTemp = Encoding.Convert(IConvHandle, Encoding.UTF8, data, dataPtr + 8, (int)(len * CharSize)); + name = Encoding.UTF8.GetString(convTemp); + } + catch + { + name = null; + } + + len = (uint)gslen; + dataPtr += (int)(8 + len); + + Dict[id] = name; + + // MS documentation blows goats ! + // The docs claim there are padding bytes in the dictionary. + // Their examples show padding bytes. + // In reality non-unicode strings do not seem to + // have padding. + if (CharSize != 1 && ((dataPtr - start) % 4) != 0) + dataPtr += 4 - ((dataPtr - start) % 4); + } + } + else + { + bool linked = false; + + name = PropIdToGsf(props[i].Id, ref linked); + val = PropertyParse(type, data, ref dataPtr, (int)(dataPtr + size - 4)); + + if (name != null && val != null) + { + if (linked) + { + GsfDocProp prop = accum.Lookup(name); + if (prop == null) + Console.Error.WriteLine($"Linking property '{(name != null ? name : "")}' before it's value is specified"); + else if (!(val is string)) + Console.Error.WriteLine($"Linking property '{(name != null ? name : "")}' before it's value is specified"); + else + prop.LinkedTo = (val as string); + } + else + { + accum.Insert(name, val); + val = null; + name = null; + } + } + } + + return true; + } + + #endregion + + #region Utilities + + private bool NEED_RECS(uint _n, uint _size1, int dataPtr, int data_end, ref int bytes_needed) + { + bytes_needed = (int)_n; + if (_size1 > 0 && (data_end - dataPtr) / _size1 < bytes_needed) + { + Console.Error.WriteLine("Invalid MS property or file truncated"); + return false; + } + + bytes_needed *= (int)_size1; + return true; + } + + /// + /// Can return errors from gsf_blob_new() and GSF_ERROR_INVALID_DATA + /// + private bool ParseVariantCF(object res, byte[] data, ref int dataPtr, ref int data_end, ref Exception error) + { + /* clipboard size uint sizeof (clipboard format tag) + sizeof (clipboard data) + * clipboard format tag int32 see below + * clipboard data byte[] see below + * + * Clipboard format tag: + * -1 - Windows clipboard format + * -2 - Macintosh clipboard format + * -3 - GUID that contains a format identifier (FMTID) + * >0 - custom clipboard format name plus data (see msdn site below) + * 0 - No data + * + * References: + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/stg/stg/propvariant.asp + * http://jakarta.apache.org/poi/hpsf/thumbnails.html + * http://linux.com.hk/docs/poi/org/apache/poi/hpsf/Thumbnail.html + * http://sparks.discreet.com/knowledgebase/public/solutions/ExtractThumbnailImg.htm + */ + + // Clipboard size field + + if (data_end < dataPtr + 4) + { + SetErrorMissingData(ref error, "VT_CF", 4, data_end - dataPtr); + return false; + } + + uint clip_size = BitConverter.ToUInt32(data, dataPtr); + + if (clip_size < 4) + { + // Must emcompass int32 format plus data size + error = new Exception($"Corrupt data in the VT_CF property; clipboard data length must be at least 4 bytes, but the data says it only has {clip_size} bytes available."); + return false; + } + + dataPtr += 4; + + // Check clipboard format plus data size + + if (data_end < dataPtr + clip_size) + { + SetErrorMissingData(ref error, "VT_CF", (int)clip_size, data_end - dataPtr); + return false; + } + + GsfClipFormat clip_format = (GsfClipFormat)BitConverter.ToInt32(data, dataPtr); + dataPtr += 4; + + switch (clip_format) + { + case GsfClipFormat.GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD: + case GsfClipFormat.GSF_CLIP_FORMAT_MACINTOSH_CLIPBOARD: + case GsfClipFormat.GSF_CLIP_FORMAT_GUID: + case GsfClipFormat.GSF_CLIP_FORMAT_NO_DATA: + // Everything is ok + break; + + default: + if (clip_format > 0) + clip_format = GsfClipFormat.GSF_CLIP_FORMAT_CLIPBOARD_FORMAT_NAME; + else + clip_format = GsfClipFormat.GSF_CLIP_FORMAT_UNKNOWN; + + break; + } + + int clip_data_size = (int)clip_size - 4; + + GsfBlob blob = GsfBlob.Create(clip_data_size, data, dataPtr, ref error); + + dataPtr += clip_data_size; + + if (blob == null) + return false; + + GsfClipData clip_data = GsfClipData.Create(clip_format, blob); + res = clip_data; + + return true; + } + + private string PropIdToGsf(uint id, ref bool linked) + { + linked = false; + if (Dict != null) + { + if ((id & 0x1000000) != 0) + { + linked = true; + unchecked { id &= (uint)~0x1000000; } + } + + if (Dict.TryGetValue(id, out string res) && res != null) + return res; + } + + GsfMSOleMetaDataPropMap[] map = GsfMSOleUtils.BuiltInProperties; + int i = GsfMSOleUtils.BuiltInProperties.Length; + while (i-- > 0) + { + if (map[i].Id == id && (map[i].Section == GsfMSOLEMetaDataType.COMMON_PROP || map[i].Section == Type)) + return map[i].GsfName; + } + + return null; + } + + /// + /// Return a number no bigger than the number of bytes used for a property + /// value of a given type. The returned number might be too small, but + /// we try to return as big a value as possible. + /// + private int PropMinSize(GsfMSOLEVariantType type) + { + switch (type) + { + case GsfMSOLEVariantType.VT_EMPTY: + case GsfMSOLEVariantType.VT_NULL: + return 0; + + case GsfMSOLEVariantType.VT_BOOL: + case GsfMSOLEVariantType.VT_I1: + case GsfMSOLEVariantType.VT_UI1: + return 1; + + case GsfMSOLEVariantType.VT_I2: + case GsfMSOLEVariantType.VT_UI2: + return 2; + + case GsfMSOLEVariantType.VT_I4: + case GsfMSOLEVariantType.VT_R4: + case GsfMSOLEVariantType.VT_ERROR: + case GsfMSOLEVariantType.VT_VARIANT: + case GsfMSOLEVariantType.VT_UI4: + case GsfMSOLEVariantType.VT_LPSTR: + case GsfMSOLEVariantType.VT_LPWSTR: + case GsfMSOLEVariantType.VT_BLOB: + case GsfMSOLEVariantType.VT_BLOB_OBJECT: + case GsfMSOLEVariantType.VT_CF: + case GsfMSOLEVariantType.VT_VECTOR: + return 4; + + case GsfMSOLEVariantType.VT_BSTR: + return 5; + + case GsfMSOLEVariantType.VT_R8: + case GsfMSOLEVariantType.VT_CY: + case GsfMSOLEVariantType.VT_DATE: + case GsfMSOLEVariantType.VT_I8: + case GsfMSOLEVariantType.VT_UI8: + case GsfMSOLEVariantType.VT_FILETIME: + return 8; + + case GsfMSOLEVariantType.VT_CLSID: + return 16; + + case GsfMSOLEVariantType.VT_DISPATCH: + case GsfMSOLEVariantType.VT_UNKNOWN: + case GsfMSOLEVariantType.VT_DECIMAL: + case GsfMSOLEVariantType.VT_INT: + case GsfMSOLEVariantType.VT_UINT: + case GsfMSOLEVariantType.VT_VOID: + case GsfMSOLEVariantType.VT_HRESULT: + case GsfMSOLEVariantType.VT_PTR: + case GsfMSOLEVariantType.VT_SAFEARRAY: + case GsfMSOLEVariantType.VT_CARRAY: + case GsfMSOLEVariantType.VT_USERDEFINED: + case GsfMSOLEVariantType.VT_STREAM: + case GsfMSOLEVariantType.VT_STORAGE: + case GsfMSOLEVariantType.VT_STREAMED_OBJECT: + case GsfMSOLEVariantType.VT_STORED_OBJECT: + default: + return 0; + } + } + + private void SetErrorMissingData(ref Exception error, string property_name, int size_needed, int size_gotten) + { + error = new Exception($"Missing data when reading the {property_name} property; got {size_needed} bytes, but {size_gotten} bytes at least are needed."); + } + + private string VariantName(GsfMSOLEVariantType type) + { + string[] names = + { + "VT_EMPTY", "VT_null", "VT_I2", "VT_I4", "VT_R4", + "VT_R8", "VT_CY", "VT_DATE", "VT_BSTR", "VT_DISPATCH", + "VT_ERROR", "VT_BOOL", "VT_VARIANT", "VT_UNKNOWN", "VT_DECIMAL", + null, "VT_I1", "VT_UI1", "VT_UI2", "VT_UI4", + "VT_I8", "VT_UI8", "VT_INT", "VT_UINT", "VT_VOID", + "VT_HRESULT", "VT_PTR", "VT_SAFEARRAY", "VT_CARRAY", "VT_USERDEFINED", + "VT_LPSTR", "VT_LPWSTR", + }; + + string[] names2 = + { + "VT_FILETIME", + "VT_BLOB", "VT_STREAM", "VT_STORAGE", "VT_STREAMED_OBJECT", + "VT_STORED_OBJECT", "VT_BLOB_OBJECT", "VT_CF", "VT_CLSID" + }; + + type &= ~GsfMSOLEVariantType.VT_VECTOR; + if (type <= GsfMSOLEVariantType.VT_LPWSTR) + return names[(int)type]; + + if (type < GsfMSOLEVariantType.VT_FILETIME) + return "_UNKNOWN_"; + if (type > GsfMSOLEVariantType.VT_CLSID) + return "_UNKNOWN_"; + + return names2[type - GsfMSOLEVariantType.VT_FILETIME]; + } + + #endregion + } + + public class WritePropState + { + #region Properties + + public GsfOutput Output { get; set; } + + public bool DocNotComponent { get; set; } + + public Dictionary Dict { get; set; } + + public WritePropStatePropList BuiltIn { get; set; } + + public WritePropStatePropList User { get; set; } + + public int CodePage { get; set; } + + public Encoding IConvHandle { get; set; } + + public int CharSize { get; set; } + + #endregion + + #region Functions + + public void GuessCodePage(bool user) + { + List ptr = user ? User.Props : BuiltIn.Props; + uint count = user ? User.Count : BuiltIn.Count; + uint i = 0; + + if (i < count) + { + // CodePage + i++; + } + + if (user && i < count) + { + // Dictionary + i++; + } + + foreach (GsfDocProp prop in ptr) + { + string name = prop.Name; + GuessCodePageString(name); + GuessCodePageProperty(name, prop.Value); + } + } + + public void CountProperties(string name, GsfDocProp prop) + { + GsfMSOleMetaDataPropMap map = GsfMSOleUtils.GsfNameToProp(name); + + // Allocate predefined ids or add it to the dictionary + if (map != null) + { + // Dictionary is handled elsewhere + if (map.Id == 0) + return; + + if (map.Section == (DocNotComponent ? GsfMSOLEMetaDataType.COMPONENT_PROP : GsfMSOLEMetaDataType.DOC_PROP)) + return; + + // CodePage + if (map.Id == 1) + { + object val = prop.Value; + if (val != null && val is int intVal) + CodePage = intVal; + + return; + } + + BuiltIn.Count += (uint)(prop.LinkedTo != null ? 2 : 1); + BuiltIn.Props.Add(prop); + } + + // Keep user props in the document + else if (DocNotComponent) + { + if (Dict == null) + Dict = new Dictionary(); + + Dict[name] = User.Count; + User.Count += (uint)(prop.LinkedTo != null ? 2 : 1); + User.Props.Add(prop); + } + } + + public bool WriteSection(bool user) + { + if (user && Dict == null) + return true; + + List ptr = user ? User.Props : BuiltIn.Props; + uint count = user ? User.Count : BuiltIn.Count; + long baseOffset = Output.CurrentOffset; + + // Skip past the size+count and id/offset pairs + byte[] buf = BitConverter.GetBytes((uint)0); + for (int j = 0; j < 1 + 1 + 2 * count; j++) + { + Output.Write(4, buf); + } + + object scratch = string.Empty; + + GsfMSOleMetaDataProp[] offsets = new GsfMSOleMetaDataProp[count]; + + // 0) CodePage + uint i = 0; + if (i < count) + { + offsets[0].Id = 1; + offsets[0].Offset = Output.CurrentOffset; + buf = BitConverter.GetBytes((uint)GsfMSOLEVariantType.VT_I2); + byte[] buf2 = BitConverter.GetBytes(CodePage); + Output.Write(4, buf); Output.Write(4, buf2); + i++; + } + + // 1) Dictionary + if (user && i < count) + { + offsets[1].Id = 0; + offsets[1].Offset = Output.CurrentOffset; + buf = BitConverter.GetBytes((uint)Dict.Count); + Output.Write(4, buf); + foreach (var kvp in Dict) + { + WriteDictionaryEntry(kvp.Key, kvp.Value); + } + + i++; + } + + // 2) Props + foreach (GsfDocProp prop in ptr) + { + offsets[i].Offset = Output.CurrentOffset; + string name = prop.Name; + if (user) + { + Dict.TryGetValue(name, out uint tmp); + offsets[i].Id = tmp; + if (offsets[i].Id < 2) + { + Console.Error.WriteLine($"Invalid ID (%d) for custom name '%s'", offsets[i].Id, name); + continue; + } + } + else + { + GsfMSOleMetaDataPropMap map = GsfMSOleUtils.GsfNameToProp(name); + if (map == null) + { + Console.Error.WriteLine("Missing map for built-in property '%s'", name); + continue; + } + + offsets[i].Id = map.Id; + } + + WriteProperty(name, prop.Value, false); + if (prop.LinkedTo != null) + { + i++; + offsets[i].Id = offsets[i - 1].Id | 0x1000000; + offsets[i].Offset = Output.CurrentOffset; + scratch = prop.LinkedTo; + WriteProperty(null, scratch, false); + } + } + + bool warned = false; + while (i < count) + { + if (!warned) + { + warned = true; + Console.Error.WriteLine("Something strange in MetadataWriteSection"); + } + + offsets[i].Id = 0; + offsets[i].Offset = offsets[i - 1].Offset; + i++; + } + + long len = Output.CurrentOffset - baseOffset; + Output.Seek(baseOffset, SeekOrigin.Begin); + buf = BitConverter.GetBytes(len); + Output.Write(4, buf); + buf = BitConverter.GetBytes(count); + Output.Write(4, buf); + + for (i = 0; i < count; i++) + { + buf = BitConverter.GetBytes(offsets[i].Id); + Output.Write(4, buf); + buf = BitConverter.GetBytes(offsets[i].Offset - baseOffset); + Output.Write(4, buf); + } + + return Output.Seek(0, SeekOrigin.End); + } + + #endregion + + #region Utilities + + private void GuessCodePageProperty(string name, object value) + { + GsfMSOleMetaDataPropMap map = (name != null) ? GsfMSOleUtils.GsfNameToProp(name) : null; + GsfMSOLEVariantType type = ValueToMSOLEVariant(value, map); + + if (type.HasFlag(GsfMSOLEVariantType.VT_VECTOR)) + { + GsfDocProp[] vector = value as GsfDocProp[]; + int n = vector.Length; + for (int i = 0; i < n; i++) + { + GuessCodePageProperty(null, vector[i]); + } + + return; + } + + switch (type) + { + case GsfMSOLEVariantType.VT_LPSTR: + GuessCodePageString((string)value); + return; + + default: + // Don't care. + return; + } + } + + private void GuessCodePageString(string str) + { + if (CodePage != 0) + return; + + if (str == null) + return; + + // Don't bother with ASCII strings + bool is_ascii = true; + for (int p = 0; p < str.Length && is_ascii; p++) + { + is_ascii = (str[p] & 0x80) == 0; + } + + if (is_ascii) + return; + + try + { + byte[] cstrTemp = IConvHandle.GetBytes(str); + cstrTemp = Encoding.Convert(IConvHandle, Encoding.UTF8, cstrTemp); + string cstr = Encoding.UTF8.GetString(cstrTemp); + } + catch + { + // Conversion failed. Switch to UTF-8 + CodePage = -535; + } + } + + private GsfMSOLEVariantType ValueToMSOLEVariant(object value, GsfMSOleMetaDataPropMap map) + { + if (value == null) + return GsfMSOLEVariantType.VT_EMPTY; + + if (value is bool) + return GsfMSOLEVariantType.VT_BOOL; + else if (value is char) + return GsfMSOLEVariantType.VT_UI1; + else if (value is float) + return GsfMSOLEVariantType.VT_R4; + else if (value is double) + return GsfMSOLEVariantType.VT_R8; + else if (value is string) + return GsfMSOLEVariantType.VT_LPSTR; + else if (value is int) + return (map?.PreferredType == GsfMSOLEVariantType.VT_I2 ? GsfMSOLEVariantType.VT_I2 : GsfMSOLEVariantType.VT_I4); + else if (value is uint) + return (map?.PreferredType == GsfMSOLEVariantType.VT_UI2 ? GsfMSOLEVariantType.VT_UI2 : GsfMSOLEVariantType.VT_UI4); + else if (value is DateTime) + return GsfMSOLEVariantType.VT_FILETIME; + + if (value is List vector) + { + GsfMSOLEVariantType type, tmp; + + if (vector == null) + return GsfMSOLEVariantType.VT_UNKNOWN; + + if (map != null) + { + type = map.PreferredType & (~GsfMSOLEVariantType.VT_VECTOR); + if (type == GsfMSOLEVariantType.VT_VARIANT) + return GsfMSOLEVariantType.VT_VECTOR | GsfMSOLEVariantType.VT_VARIANT; + } + else + { + type = GsfMSOLEVariantType.VT_UNKNOWN; + } + + int n = vector.Count; + for (int i = 0; i < n; i++) + { + tmp = ValueToMSOLEVariant(vector[i], null); + if (type == GsfMSOLEVariantType.VT_UNKNOWN) + type = tmp; + else if (type != tmp) + return GsfMSOLEVariantType.VT_VECTOR | GsfMSOLEVariantType.VT_VARIANT; + } + + return GsfMSOLEVariantType.VT_VECTOR | type; + } + + return GsfMSOLEVariantType.VT_UNKNOWN; + } + + private void WriteDictionaryEntry(string name, uint id) + { + byte[] buf = BitConverter.GetBytes(id); + Output.Write(4, buf); + WriteString(name); + } + + /// True on success + private bool WriteProperty(string name, object value, bool suppress_type) + { + if (value == null) + return false; + + GsfMSOleMetaDataPropMap map = (name != null) ? GsfMSOleUtils.GsfNameToProp(name) : null; + byte[] buf = new byte[8]; + + GsfMSOLEVariantType type = ValueToMSOLEVariant(value, map); + if (!suppress_type) + { + buf = BitConverter.GetBytes((uint)type); + Output.Write(4, buf); + } + + if (type.HasFlag(GsfMSOLEVariantType.VT_VECTOR)) + { + GsfDocProp[] vector = value as GsfDocProp[]; + int n = vector.Length; + bool res; + + buf = BitConverter.GetBytes(n); + res = Output.Write(4, buf); + for (int i = 0; i < n; i++) + { + bool suppress = type != (GsfMSOLEVariantType.VT_VECTOR | GsfMSOLEVariantType.VT_VARIANT); + res &= WriteProperty(null, vector[i], suppress); + } + + return res; + } + + switch (type) + { + case GsfMSOLEVariantType.VT_BOOL: + if ((bool)value) + buf = BitConverter.GetBytes(0xffffffff); + else + buf = BitConverter.GetBytes(0); + + return Output.Write(4, buf); + + case GsfMSOLEVariantType.VT_UI1: + buf = BitConverter.GetBytes((int)(byte)value); + return Output.Write(4, buf); + + case GsfMSOLEVariantType.VT_I2: + buf = BitConverter.GetBytes((short)value); + byte[] buf2 = BitConverter.GetBytes((short)0); + return Output.Write(2, buf) && Output.Write(2, buf2); + + case GsfMSOLEVariantType.VT_I4: + buf = BitConverter.GetBytes((int)value); + return Output.Write(4, buf); + + case GsfMSOLEVariantType.VT_UI2: + case GsfMSOLEVariantType.VT_UI4: + buf = BitConverter.GetBytes((uint)value); + return Output.Write(4, buf); + + case GsfMSOLEVariantType.VT_R4: + buf = BitConverter.GetBytes((float)value); + return Output.Write(4, buf); + + case GsfMSOLEVariantType.VT_R8: + buf = BitConverter.GetBytes((double)value); + return Output.Write(8, buf); + + case GsfMSOLEVariantType.VT_LPSTR: + return WriteString((string)value); + + case GsfMSOLEVariantType.VT_FILETIME: + { + DateTime ts = (DateTime)value; + ulong ft = (ulong)ts.ToFileTime(); + buf = BitConverter.GetBytes(ft); + return Output.Write(8, buf); + } + + default: + break; + } + + Console.Error.WriteLine($"Ignoring property '{(name != null ? name : "")}', how do we export a property of type '{value.GetType()}'"); + return false; + } + + private bool WriteString(string txt) + { + byte[] buf = new byte[4]; + int bytes_written; + bool res; + + if (txt == null) + txt = ""; + + int len = txt.Length; + + byte[] ctxt; + try + { + ctxt = Encoding.Unicode.GetBytes(txt); + ctxt = Encoding.Convert(IConvHandle, Encoding.UTF8, ctxt); + bytes_written = ctxt.Length; + } + catch + { + ctxt = null; + bytes_written = 0; + } + + if (ctxt == null) + { + // See bug #703952 + Console.Error.WriteLine("Failed to write metadata string"); + bytes_written = 0; + } + + // *Bytes*, not characters, including the termination, but not the padding. + buf = BitConverter.GetBytes(bytes_written + CharSize); + res = Output.Write(4, buf); + + res = res && Output.Write(bytes_written, ctxt); + + buf = BitConverter.GetBytes((uint)0); + res = res && Output.Write((int)CharSize, buf); + + if (CharSize > 1) + { + uint padding = (uint)(4 - (bytes_written + CharSize) % 4); + if (padding < 4) + res = res && Output.Write((int)padding, buf); + } + + return res; + } + + #endregion + } + + public class WritePropStatePropList + { + /// + /// Includes 2nd prop for links + /// + public uint Count { get; set; } + + public List Props { get; set; } + } + + public class LanguageId + { + #region Properties + + public string Tag { get; set; } + + public uint Id { get; set; } + + #endregion + + #region Functions + + /// Language id, i.e., locale name. + /// + /// The LID (Language Identifier) for the input language. + /// If lang is %null, return 0x0400 ("-none-"), and not 0x0000 ("no proofing") + /// + public static uint LanguageIdForLanguage(string lang) + { + if (lang == null) + return 0x0400; // Return -none- + + // Allow lang to match as a prefix (eg fr == fr_FR@euro) + int len = lang.Length; + for (int i = 0; i < GsfMSOleUtils.MSOLELanguageIds.Length; i++) + { + if (lang.Equals(GsfMSOleUtils.MSOLELanguageIds[i].Tag.Substring(0, Math.Min(len, GsfMSOleUtils.MSOLELanguageIds[i].Tag.Length)))) + return GsfMSOleUtils.MSOLELanguageIds[i].Id; + } + + return 0x0400; // Return -none- + } + + /// Numerical language id + /// + /// The xx_YY style string (can be just xx or + /// xxx) for the given LID. If the LID is not found, is set to 0x0400, + /// or is set to 0x0000, will return "-none-" + /// + public static string LanguageForLanguageId(uint lid) + { + for (int i = 0; i < GsfMSOleUtils.MSOLELanguageIds.Length; i++) + { + if (GsfMSOleUtils.MSOLELanguageIds[i].Id == lid) + return GsfMSOleUtils.MSOLELanguageIds[i].Tag; + } + + return "-none-"; // Default + } + + /// Convert the the codepage into an applicable LID + /// Character code page. + public static uint CodePageToLanguageId(int codepage) + { + switch (codepage) + { + case 77: /* MAC_CHARSET */ + return 0xFFF; /* This number is a hack */ + case 128: /* SHIFTJIS_CHARSET */ + return 0x411; /* Japanese */ + case 129: /* HANGEUL_CHARSET */ + return 0x412; /* Korean */ + case 130: /* JOHAB_CHARSET */ + return 0x812; /* Korean (Johab) */ + case 134: /* GB2312_CHARSET - Chinese Simplified */ + return 0x804; /* China PRC - And others!! */ + case 136: /* CHINESEBIG5_CHARSET - Chinese Traditional */ + return 0x404; /* Taiwan - And others!! */ + case 161: /* GREEK_CHARSET */ + return 0x408; /* Greek */ + case 162: /* TURKISH_CHARSET */ + return 0x41f; /* Turkish */ + case 163: /* VIETNAMESE_CHARSET */ + return 0x42a; /* Vietnamese */ + case 177: /* HEBREW_CHARSET */ + return 0x40d; /* Hebrew */ + case 178: /* ARABIC_CHARSET */ + return 0x01; /* Arabic */ + case 186: /* BALTIC_CHARSET */ + return 0x425; /* Estonian - And others!! */ + case 204: /* RUSSIAN_CHARSET */ + return 0x419; /* Russian - And others!! */ + case 222: /* THAI_CHARSET */ + return 0x41e; /* Thai */ + case 238: /* EASTEUROPE_CHARSET */ + return 0x405; /* Czech - And many others!! */ + } + + /* default */ + return 0x0; + } + + /// Numerical language id + /// Our best guess at the codepage for the given language id + public static int LanguageIdToCodePage(uint lid) + { + if (lid == 0x0FFF) /* Macintosh Hack */ + return 0x0FFF; + + switch (lid & 0xff) + { + case 0x01: /* Arabic */ + return 1256; + case 0x02: /* Bulgarian */ + return 1251; + case 0x03: /* Catalan */ + return 1252; + case 0x04: /* Chinese */ + switch (lid) + { + case 0x1004: /* Chinese (Singapore) */ + case 0x0404: /* Chinese (Taiwan) */ + case 0x1404: /* Chinese (Macau SAR) */ + case 0x0c04: /* Chinese (Hong Kong SAR, PRC) */ + return 950; + + case 0x0804: /* Chinese (PRC) */ + return 936; + default: + break; + } + break; + case 0x05: /* Czech */ + return 1250; + case 0x06: /* Danish */ + return 1252; + case 0x07: /* German */ + return 1252; + case 0x08: /* Greek */ + return 1253; + case 0x09: /* English */ + return 1252; + case 0x0a: /* Spanish */ + return 1252; + case 0x0b: /* Finnish */ + return 1252; + case 0x0c: /* French */ + return 1252; + case 0x0d: /* Hebrew */ + return 1255; + case 0x0e: /* Hungarian */ + return 1250; + case 0x0f: /* Icelandic */ + return 1252; + case 0x10: /* Italian */ + return 1252; + case 0x11: /* Japanese */ + return 932; + case 0x12: /* Korean */ + switch (lid) + { + case 0x0812: /* Korean (Johab) */ + return 1361; + case 0x0412: /* Korean */ + return 949; + default: + break; + } + break; + case 0x13: /* Dutch */ + return 1252; + case 0x14: /* Norwegian */ + return 1252; + case 0x15: /* Polish */ + return 1250; + case 0x16: /* Portuguese */ + return 1252; + case 0x17: /* Rhaeto-Romanic */ + return 1252; + case 0x18: /* Romanian */ + return 1250; + case 0x19: /* Russian */ + return 1251; + case 0x1a: /* Serbian, Croatian, (Bosnian?) */ + switch (lid) + { + case 0x041a: /* Croatian */ + return 1252; + case 0x0c1a: /* Serbian (Cyrillic) */ + return 1251; + case 0x081a: /* Serbian (Latin) */ + return 1252; + default: + break; + } + break; + case 0x1b: /* Slovak */ + return 1250; + case 0x1c: /* Albanian */ + return 1251; + case 0x1d: /* Swedish */ + return 1252; + case 0x1e: /* Thai */ + return 874; + case 0x1f: /* Turkish */ + return 1254; + case 0x20: /* Urdu. This is Unicode only. */ + return 0; + case 0x21: /* Bahasa Indonesian */ + return 1252; + case 0x22: /* Ukrainian */ + return 1251; + case 0x23: /* Byelorussian / Belarusian */ + return 1251; + case 0x24: /* Slovenian */ + return 1250; + case 0x25: /* Estonian */ + return 1257; + case 0x26: /* Latvian */ + return 1257; + case 0x27: /* Lithuanian */ + return 1257; + case 0x29: /* Farsi / Persian. This is Unicode only. */ + return 0; + case 0x2a: /* Vietnamese */ + return 1258; + case 0x2b: /* Windows 2000: Armenian. This is Unicode only. */ + return 0; + case 0x2c: /* Azeri */ + switch (lid) + { + case 0x082c: /* Azeri (Cyrillic) */ + return 1251; + default: + break; + } + break; + case 0x2d: /* Basque */ + return 1252; + case 0x2f: /* Macedonian */ + return 1251; + case 0x36: /* Afrikaans */ + return 1252; + case 0x37: /* Windows 2000: Georgian. This is Unicode only. */ + return 0; + case 0x38: /* Faeroese */ + return 1252; + case 0x39: /* Windows 2000: Hindi. This is Unicode only. */ + return 0; + case 0x3E: /* Malaysian / Malay */ + return 1252; + case 0x41: /* Swahili */ + return 1252; + case 0x43: /* Uzbek */ + switch (lid) + { + case 0x0843: /* Uzbek (Cyrillic) */ + return 1251; + default: + break; + } + break; + case 0x45: /* Windows 2000: Bengali. This is Unicode only. */ + case 0x46: /* Windows 2000: Punjabi. This is Unicode only. */ + case 0x47: /* Windows 2000: Gujarati. This is Unicode only. */ + case 0x48: /* Windows 2000: Oriya. This is Unicode only. */ + case 0x49: /* Windows 2000: Tamil. This is Unicode only. */ + case 0x4a: /* Windows 2000: Telugu. This is Unicode only. */ + case 0x4b: /* Windows 2000: Kannada. This is Unicode only. */ + case 0x4c: /* Windows 2000: Malayalam. This is Unicode only. */ + case 0x4d: /* Windows 2000: Assamese. This is Unicode only. */ + case 0x4e: /* Windows 2000: Marathi. This is Unicode only. */ + case 0x4f: /* Windows 2000: Sanskrit. This is Unicode only. */ + case 0x55: /* Myanmar / Burmese. This is Unicode only. */ + case 0x57: /* Windows 2000: Konkani. This is Unicode only. */ + case 0x61: /* Windows 2000: Nepali (India). This is Unicode only. */ + return 0; + +#if TESTING + /****************************************************************** + * Below this line is untested, unproven, and are just guesses. * + * Insert above and use at your own risk * + ******************************************************************/ + + case 0x042c: /* Azeri (Latin) */ + case 0x0443: /* Uzbek (Latin) */ + case 0x30: /* Sutu */ + return 1252; /* UNKNOWN, believed to be CP1252 */ + + case 0x3f: /* Kazakh */ + return 1251; /* JUST UNKNOWN, probably CP1251 */ + + case 0x44: /* Tatar */ + case 0x58: /* Manipuri */ + case 0x59: /* Sindhi */ + case 0x60: /* Kashmiri (India) */ + return 0; /* UNKNOWN, believed to be Unicode only */ +#endif + }; + + // This is just a guess, but it will be a frequent guess + return 1252; + } + + /// Numerical language id + /// The Iconv codepage string for the given LID. + public static string LanguageIdToCodePageString(uint lid) + { + if (lid == 0x0FFF) /* Macintosh Hack */ + return "MACINTOSH"; + + int cp = LanguageIdToCodePage(lid); + return $"CP{cp}"; + } + + public static List GetCodePageStringList(int codepage) + { + List cp_list = new List(); + + switch (codepage) + { + case 1200: + cp_list.Add("UTF-16LE"); + break; + + case 1201: + cp_list.Add("UTF-16BE"); + break; + + case 0x8000: + case 10000: + cp_list.Add("MACROMAN"); + cp_list.Add("MACINTOSH"); + break; + + case -535: + case 65001: + cp_list.Add("UTF-8"); + break; + + case 0x8001: + // According to OOo docs 8001 is a synonym CP1252 + codepage = 1252; + cp_list.Add($"CP{codepage}"); + break; + + default: + cp_list.Add($"CP{codepage}"); + break; + } + + return cp_list; + } + + #endregion + } + + public class GsfMSOleSortingKey + { + #region Properties + + public string Name { get; set; } + + public int Length { get; set; } + + #endregion + + #region Functions + + public static GsfMSOleSortingKey Create(string name) + { + GsfMSOleSortingKey res = new GsfMSOleSortingKey(); + + if (name == null) + name = ""; + + int name_len = name.Length; + + char[] resNameTemp = new char[name_len + 1]; + res.Length = 0; + + // This code is a bit like g_UTF-8_to_utf16. + + for (int p = 0; p < name.Length; p++) + { + char wc = name[p]; + if ((wc & 0x80000000) != 0) + break; // Something invalid or incomplete + + if (wc < 0x10000) + { + wc = char.ToUpper(wc); + + // Let's hope no uppercase char is above 0xffff! + resNameTemp[res.Length++] = wc; + } + else + { + resNameTemp[res.Length++] = (char)((wc - 0x10000) / 0x400 + 0xd800); + resNameTemp[res.Length++] = (char)((wc - 0x10000) % 0x400 + 0xdc00); + } + } + + resNameTemp[res.Length] = '\0'; + res.Name = new string(resNameTemp); + + return res; + } + + public GsfMSOleSortingKey Copy() + { + return new GsfMSOleSortingKey + { + Name = this.Name, + Length = this.Length, + }; + } + + #endregion + } + + #endregion +} diff --git a/BurnOutSharp/External/libgsf/GsfMetaNames.cs b/BurnOutSharp/External/libgsf/GsfMetaNames.cs new file mode 100644 index 00000000..c17d0c7b --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfMetaNames.cs @@ -0,0 +1,378 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-meta-names.h: a list of gsf-meta-names to "generically" represent + * all diversly available implementation-specific + * meta-names. + * + * Author: Veerapuram Varadhan (vvaradhan@novell.com) + * Jody Goldberg (jody@gnome.org) + * + * Copyright (C) 2004-2006 Novell, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +namespace LibGSF +{ + /// + /// The namespace follow this classification: + /// + /// "dc:" - Dublin Core tags + /// "gsf:" - Gnumeric only tags + /// "meta:" - OpenDocument tags shared with Gnumeric + /// "MSOLE:" - OLE tags + /// + public static class GsfMetaNames + { + #region Namespace - dc + + /// + /// (String) An entity primarily responsible for making the content of the + /// resource typically a person, organization, or service. + /// + /// 1.14.0 Moved from "gsf" to "dc". + public const string GSF_META_NAME_CREATOR = "dc:creator"; + + /// + /// (GsfTimestamp) The last time this document was saved. + /// + /// 1.14.0 Moved from dc:date-modified to dc:date. + public const string GSF_META_NAME_DATE_MODIFIED = "dc:date"; + + /// + /// (String) An account of the content of the resource. + /// + public const string GSF_META_NAME_DESCRIPTION = "dc:description"; + + /// + /// (GsfDocPropVector of String) Searchable, indexable keywords. Similar to PDF + /// keywords or HTML's meta block. + /// + public const string GSF_META_NAME_KEYWORDS = "dc:keywords"; + + /// + /// (String) The locale language of the intellectual content of the resource + /// (basically xx_YY form for us). + /// + /// 1.14.0 Clarified that this is unique from _NAME_CODEPAGE in MSOLE + public const string GSF_META_NAME_LANGUAGE = "dc:language"; + + /// + /// (UnsignedShort) The MS codepage to encode strings for metadata + /// + /// 1.14.0 Clarified that this is unique from _NAME_CODEPAGE in MSOLE + public const string GSF_META_NAME_CODEPAGE = "MSOLE:codepage"; + + /// + /// (String) The topic of the content of the resource, + /// typically including keywords. + /// + public const string GSF_META_NAME_SUBJECT = "dc:subject"; + + /// + /// (String) A formal name given to the resource. + /// + public const string GSF_META_NAME_TITLE = "dc:title"; + + #endregion + + #region Namespace - gsf + + /// + /// (Integer) Count of bytes in the document. + /// + public const string GSF_META_NAME_BYTE_COUNT = "gsf:byte-count"; + + /// + /// (Unsigned Integer) Identifier representing the case-sensitiveness. + /// + /// of what ?? why is it an integer ?? + public const string GSF_META_NAME_CASE_SENSITIVE = "gsf:case-sensitivity"; + + /// + /// (String) Category of the document. + /// + /// example??? + public const string GSF_META_NAME_CATEGORY = "gsf:category"; + + /// + /// (Integer) Count of cells in the spread-sheet document, if appropriate. + /// + public const string GSF_META_NAME_CELL_COUNT = "gsf:cell-count"; + + /// + /// (Integer) Count of characters in the document. + /// + /// TODO See how to sync this with ODF's document-statistic + public const string GSF_META_NAME_CHARACTER_COUNT = "gsf:character-count"; + + /// + /// (None) Reserved name (PID) for Dictionary + /// + public const string GSF_META_NAME_DICTIONARY = "gsf:dictionary"; + + /// + /// (Vector of strings) Names of the 'interesting' parts of the document. In + /// spreadsheets this is a list of the sheet names, and the named expressions. + /// + /// From MSOLE + public const string GSF_META_NAME_DOCUMENT_PARTS = "gsf:document-parts"; + + /// + /// (Vector of string value pairs stored in alternating elements) Store the + /// counts of objects in the document as names 'worksheet' and count '4' + /// + /// From MSOLE + public const string GSF_META_NAME_HEADING_PAIRS = "gsf:heading-pairs"; + + /// + /// (Integer) Count of hidden-slides in the presentation document. + /// + public const string GSF_META_NAME_HIDDEN_SLIDE_COUNT = "gsf:hidden-slide-count"; + + /// + /// (Integer) Count of images in the document, if appropriate. + /// + public const string GSF_META_NAME_IMAGE_COUNT = "gsf:image-count"; + + /// + /// (String) The entity that made the last change to the document, typically a + /// person, organization, or service. + /// + public const string GSF_META_NAME_LAST_SAVED_BY = "gsf:last-saved-by"; + + /// + /// (Boolean) ??????? + /// + public const string GSF_META_NAME_LINKS_DIRTY = "gsf:links-dirty"; + + /// + /// (Unsigned Integer) Identifier representing the default system locale. + /// + public const string GSF_META_NAME_LOCALE_SYSTEM_DEFAULT = "gsf:default-locale"; + + /// + /// (String) Name of the manager of "CREATOR" entity. + /// + public const string GSF_META_NAME_MANAGER = "gsf:manager"; + + /// + /// (String) Type of presentation, like "On-screen Show", "SlideView" etc. + /// + public const string GSF_META_NAME_PRESENTATION_FORMAT = "gsf:presentation-format"; + + /// + /// (Boolean) ????? + /// + public const string GSF_META_NAME_SCALE = "gsf:scale"; + + /// + /// (Integer) Level of security. + /// + /// + /// + /// + /// + /// LevelValue + /// + /// + /// None0 + /// Password protected1 + /// Read-only recommended2 + /// Read-only enforced3 + /// Locked for annotations4 + /// + /// + public const string GSF_META_NAME_SECURITY = "gsf:security"; + + /// + /// (GsfClipData) Thumbnail data of the document, typically a + /// preview image of the document. + /// + public const string GSF_META_NAME_THUMBNAIL = "gsf:thumbnail"; + + /// + /// (Integer) Count of liness in the document. + /// + public const string GSF_META_NAME_LINE_COUNT = "gsf:line-count"; + + /// + /// (Integer) Count of "multi-media" clips in the document. + /// + public const string GSF_META_NAME_MM_CLIP_COUNT = "gsf:MM-clip-count"; + + /// + /// (Integer) Count of "notes" in the document. + /// + public const string GSF_META_NAME_NOTE_COUNT = "gsf:note-count"; + + /// + /// (Integer) Count of objects (OLE and other graphics) in the document, if + /// appropriate. + /// + public const string GSF_META_NAME_OBJECT_COUNT = "gsf:object-count"; + + /// + /// (Integer) Count of pages in the document, if appropriate. + /// + public const string GSF_META_NAME_PAGE_COUNT = "gsf:page-count"; + + /// + /// (Integer) Count of paragraphs in the document, if appropriate. + /// + public const string GSF_META_NAME_PARAGRAPH_COUNT = "gsf:paragraph-count"; + + /// + /// (Integer) Count of slides in the presentation document. + /// + public const string GSF_META_NAME_SLIDE_COUNT = "gsf:slide-count"; + + /// + /// (Integer) Count of pages in the document, if appropriate. + /// + public const string GSF_META_NAME_SPREADSHEET_COUNT = "gsf:spreadsheet-count"; + + /// + /// (Integer) Count of tables in the document, if appropriate. + /// + public const string GSF_META_NAME_TABLE_COUNT = "gsf:table-count"; + + /// + /// (Integer) Count of words in the document. + /// + public const string GSF_META_NAME_WORD_COUNT = "gsf:word-count"; + + #endregion + + #region Namespace - MSOLE + + /// + /// (Unknown) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_17 = "MSOLE:unknown-doc-17"; + + /// + /// (Unknown) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_18 = "MSOLE:unknown-doc-18"; + + /// + /// (Boolean) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_19 = "MSOLE:unknown-doc-19"; + + /// + /// (Unknown) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_20 = "MSOLE:unknown-doc-20"; + + /// + /// (Unknown) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_21 = "MSOLE:unknown-doc-21"; + + /// + /// (Boolean) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_22 = "MSOLE:unknown-doc-22"; + + /// + /// (i4) User-defined name + /// + public const string GSF_META_NAME_MSOLE_UNKNOWN_23 = "MSOLE:unknown-doc-23"; + + #endregion + + #region Namespace - meta + + /// + /// (Date as ISO String) A date associated with an event in the life cycle of + /// the resource (creation/publication date). + /// Moved from gsf:date-created to meta:creation-date. This way can be used correctly + /// by OpenDocument and Gnumeric. + /// + public const string GSF_META_NAME_DATE_CREATED = "meta:creation-date"; + + /// + /// (Date as ISO String) The total-time taken until the last modification. + /// Moved from "gsf" to "meta". This way can be used correctly by OpenDocument + /// and Gnumeric. + /// + public const string GSF_META_NAME_EDITING_DURATION = "meta:editing-duration"; + + /// + /// (String) The application that generated this document. AbiWord, Gnumeric, + /// etc... + /// + /// 1.14.0 Moved from "gsf" to "meta". + public const string GSF_META_NAME_GENERATOR = "meta:generator"; + + /// + /// (String) Searchable, indexable keywords. Similar to PDF keywords or HTML's + /// meta block. + /// + public const string GSF_META_NAME_KEYWORD = "meta:keyword"; + + /// + /// (String) Specifies the name of the person who created the document + /// initially. + /// + /// 1.14.0 Moved from "gsf" to "meta". + public const string GSF_META_NAME_INITIAL_CREATOR = "meta:initial-creator"; + + /// + /// (String) Name of the company/organization that the "CREATOR" entity is + /// associated with. + /// + /// 1.14.1 Moved from "gsf:company" to "dc:publisher". + public const string GSF_META_NAME_COMPANY = "dc:publisher"; + + /// + /// (GsfTimestamp) Specifies the date and time when the document was last + /// printed. + /// + public const string GSF_META_NAME_PRINT_DATE = "meta:print-date"; + + /// + /// (GSF_META_NAME_HEADING_PAIRS) The last time this document was printed. + /// + /// + /// 1.14.0 Moved from "gsf" to "dc". + /// 1.14.1 Moved back to "gsf" from "dc". + /// + public const string GSF_META_NAME_LAST_PRINTED = "gsf:last-printed"; + + /// + /// (String) Specifies the name of the last person who printed the document. + /// + /// 1.14.0 Moved from "gsf" to "meta". + public const string GSF_META_NAME_PRINTED_BY = "meta:printed-by"; + + /// + /// (Integer) Count of revision on the document, if appropriate. + /// Moved from gsf:revision-count to meta:editing-cycles. This way can be used + /// correctly by OpenDocument and Gnumeric. + /// + public const string GSF_META_NAME_REVISION_COUNT = "meta:editing-cycles"; + + /// + /// (String) The template file that is been used to generate this document. + /// + /// 1.14.0 Moved from "gsf" to "meta" + public const string GSF_META_NAME_TEMPLATE = "meta:template"; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfOpenDocUtils.cs b/BurnOutSharp/External/libgsf/GsfOpenDocUtils.cs new file mode 100644 index 00000000..4706faab --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfOpenDocUtils.cs @@ -0,0 +1,784 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-opendoc-utils.c: Handle the application neutral portions of OpenDocument + * + * Author: Luciano Wolf (luciano.wolf@indt.org.br) + * + * Copyright (C) 2006 Jody Goldberg (jody@gnome.org) + * Copyright (C) 2005-2006 INdT - Instituto Nokia de Tecnologia + * http://www.indt.org.br + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.Xml; +using LibGSF.Input; +using static LibGSF.GsfMetaNames; + +namespace LibGSF +{ + #region Enums + + public enum OpenDocType + { + OO_NS_OFFICE, + OO_NS_STYLE, + OO_NS_TEXT, + OO_NS_TABLE, + OO_NS_DRAW, + OO_NS_NUMBER, + OO_NS_CHART, + OO_NS_DR3D, + OO_NS_FORM, + OO_NS_SCRIPT, + OO_NS_CONFIG, + OO_NS_MATH, + OO_NS_FO, + OO_NS_DC, + OO_NS_META, + OO_NS_XLINK, + OO_NS_SVG, + + /* new in 2.0 */ + OO_NS_OOO, + OO_NS_OOOW, + OO_NS_OOOC, + OO_NS_DOM, + OO_NS_XFORMS, + OO_NS_XSD, + OO_NS_XSI, + + OO_NS_PRESENT, /* added in gsf-1.14.8 */ + + /* new in 3.0 */ + OO_NS_RPT, + OO_NS_OF, + OO_NS_RDFA, + OO_NS_FIELD, + OO_NS_FORMX, + + /* Other OpenDocument 1.1 */ + OO_NS_ANIM, + OO_NS_DATASTYLE, + OO_NS_MANIFEST, + OO_NS_SMIL, + + /* Symphony 1.3 */ + OO_LOTUS_NS_PRODTOOLS, + + /* KOffice 1.6.3 */ + OO_KDE_NS_KOFFICE, + + /*CleverAge ODF Add-in for Microsoft Office 3.0.5224.0 (11.0.8302)*/ + OO_CLEVERAGE_NS_DC, + + /* Microsoft Excel Formulas */ + OO_MS_NS_MSOXL, + + /* Gnumeric ODF extensions */ + OO_GNUM_NS_EXT, + + /* New in ODF 3.2 */ + OO_NS_GRDDL, + OO_NS_XHTML, + OO_NS_TABLE_OOO, + + /* New in ODF 3.3 */ + OO_NS_CHART_OOO, + + /* New in LOCALC */ + OO_NS_LOCALC_EXT, + OO_NS_LOCALC_EXT2 + } + + #endregion + + #region Classes + + public class GsfODFOut : GsfXMLOut + { + #region Internal Properties + + public int OdfVersion { get; set; } = 100; + + #endregion + + #region Functions + + public string GetVersionString() => $"{OdfVersion / 100}.{OdfVersion % 100}"; + + #endregion + } + + public class GsfOOMetaIn + { + public GsfDocMetaData MetaData { get; set; } + + public List Keywords { get; set; } + + public Exception Err { get; set; } + + public string Name { get; set; } + + public Type Type { get; set; } + + public XmlDocument Doc { get; set; } + } + + public static class GsfOpenDocUtils + { + #region Constants + + public const string OFFICE = "office:"; + + #endregion + + #region Functions + + /// + /// Gives the ODF version used by libgsf when writing Open Document files. + /// + /// The ODF version as a string: "1.2". + public static string GetVersionString() => "1.2"; + + /// + /// Gives the ODF version used by libgsf when writing Open Document files. + /// + /// The ODF version: 102. + public static short GetVersion() => 102; + + #endregion + + #region Extension Functions + + public static void od_meta_generator(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_GENERATOR, typeof(string)); + + public static void od_meta_title(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_TITLE, typeof(string)); + + public static void od_meta_description(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_DESCRIPTION, typeof(string)); + + public static void od_meta_subject(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_SUBJECT, typeof(string)); + + public static void od_meta_initial_creator(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_INITIAL_CREATOR, typeof(string)); + + /// + /// OD considers this the last person to modify the doc, rather than + /// the DC convention of the person primarilly responsible for its creation + /// + public static void od_meta_creator(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_CREATOR, typeof(string)); + + /// + /// Last to print + /// + public static void od_meta_printed_by(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_PRINTED_BY, typeof(string)); + + public static void od_meta_date_created(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_DATE_CREATED, typeof(DateTime)); + + public static void od_meta_date_modified(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_DATE_MODIFIED, typeof(DateTime)); + + public static void od_meta_print_date(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_LAST_PRINTED, typeof(DateTime)); + + public static void od_meta_language(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_LANGUAGE, typeof(string)); + + public static void od_meta_editing_cycles(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_REVISION_COUNT, typeof(uint)); + + // FIXME FIXME FIXME should be durations using format 'PnYnMnDTnHnMnS' + public static void od_meta_editing_duration(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_EDITING_DURATION, typeof(string)); + + /// + /// OD allows multiple keywords, accumulate things and make it an array + /// + public static void od_meta_keyword(this GsfXMLIn xin, GsfXMLBlob blob) + { + GsfOOMetaIn mi = (GsfOOMetaIn)xin.UserState; + + if (mi.Keywords == null) + mi.Keywords = new List(); + + mi.Keywords.Add(xin.Content); + } + + public static void od_meta_user_defined(this GsfXMLIn xin, string[] attrs) + { + GsfOOMetaIn mi = (GsfOOMetaIn)xin.UserState; + mi.Type = null; + mi.Name = null; + + for (int i = 0; i < attrs.Length - 1 && attrs[i] != null && attrs[i + 1] != null; i += 2) + { + if (attrs[i] == "meta:name") + { + mi.Name = attrs[i + 1]; + } + else if (attrs[i] == "meta:value-type" || attrs[i] == "meta:type") + { + // "meta:type" is a typo on the write + // side that was fixed on 20110509. + if (attrs[i + 1] == "boolean") + { + mi.Type = typeof(bool); + } + else if (attrs[i + 1] == "float") + { + mi.Type = typeof(double); + } + else if (attrs[i + 1] == "string") + { + mi.Type = typeof(string); + } + else if (attrs[i + 1] == "date" || attrs[i + 1] == "data") + { + // "data" is a typo on the write side that was + // fixed on 20110311. + mi.Type = typeof(DateTime); + } + else if (attrs[i + 1] == "time") + { + mi.Type = typeof(string); + // We should be able to do better + } + else + { + // What? + } + } + } + + // This should not happen + if (mi.Name == null) + mi.Name = string.Empty; + } + + public static void od_meta_user_defined_end(this GsfXMLIn xin, GsfXMLBlob blob) + { + GsfOOMetaIn mi = (GsfOOMetaIn)xin.UserState; + if (mi.Name != null) + { + object res = new object(); + Type t = mi.Type; + + if (t == null) + t = typeof(string); + + if (!GsfLibXML.ValueFromString(ref res, t, xin.Content)) + { + mi.Name = null; + return; + } + + if (mi.Name.StartsWith("GSF_DOCPROP_VECTOR:")) + { + int true_name = mi.Name.IndexOf(':', 19); + if (true_name != -1 && mi.Name[++true_name] != 0) + { + GsfDocProp prop = mi.MetaData.Lookup(mi.Name.Substring(true_name)); + if (prop == null) + { + List vector = new List(); + vector.Add(prop); + mi.MetaData.Insert(mi.Name.Substring(true_name), vector); + } + else + { + object old = prop.Value; + if (old is List oldList) + { + List newObj = new List(); + newObj.AddRange(oldList); + newObj.Add(res as GsfDocProp); + prop.Value = newObj; + } + else + { + Console.Error.WriteLine($"Property \"{mi.Name.Substring(true_name)}\" used for multiple types!"); + } + + } + + mi.Name = null; + return; + } + } + + mi.MetaData.Insert(mi.Name, res); + mi.Name = null; + } + } + + private static void od_get_meta_prop(this GsfXMLIn xin, string prop_name, Type g_type) + { + object res = new object(); + if (GsfLibXML.ValueFromString(ref res, g_type, xin.Content)) + (xin.UserState as GsfOOMetaIn).MetaData.Insert(prop_name, res); + } + + #endregion + + #region Utilities + + /// + /// Generated based on: + /// http://www.oasis-open.org/committees/download.php/12572/OpenDocument-v1.0-os.pdf + /// and OpenDocument-v1.1.pdf + /// + private static XmlNameTable CreateOpenDocumentNamespaces() + { + NameTable table = new NameTable(); + + // OOo 1.0.x & 1.1.x + table.Add("http://openoffice.org/2000/office"); + table.Add("http://openoffice.org/2000/style"); + table.Add("http://openoffice.org/2000/text"); + table.Add("http://openoffice.org/2000/table"); + table.Add("http://openoffice.org/2000/drawing"); + table.Add("http://openoffice.org/2000/datastyle"); + table.Add("http://openoffice.org/2000/chart"); + table.Add("http://openoffice.org/2000/dr3d"); + table.Add("http://openoffice.org/2000/form"); + table.Add("http://openoffice.org/2000/script"); + table.Add("http://openoffice.org/2001/config"); + table.Add("http://www.w3.org/1998/Math/MathML"); // also in 2.0 + table.Add("http://www.w3.org/1999/XSL/Format"); + table.Add("http://www.w3.org/1999/xlink"); // also in 2.0 + table.Add("http://www.w3.org/2000/svg"); + + // OOo 1.9.x & 2.0.x + table.Add("urn:oasis:names:tc:opendocument:xmlns:office:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:style:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:text:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:table:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:chart:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:form:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:script:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"); + + table.Add("http://purl.org/dc/elements/1.1/"); + table.Add("http://openoffice.org/2004/office"); + table.Add("http://openoffice.org/2004/writer"); + table.Add("http://openoffice.org/2004/calc"); + table.Add("http://www.w3.org/2001/xml-events"); + table.Add("http://www.w3.org/2002/xforms"); + table.Add("http://www.w3.org/2001/XMLSchema"); + table.Add("http://www.w3.org/2001/XMLSchema-instance"); + + // OOo 3.0.x + table.Add("urn:oasis:names:tc:opendocument:xmlns:of:1.2"); + table.Add("urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"); + table.Add("urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:field:1.0"); + table.Add("urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"); + + table.Add("http://openoffice.org/2005/report"); + table.Add("http://docs.oasis-open.org/opendocument/meta/rdfa#"); + + // OOo 3.2.x + table.Add("http://www.w3.org/2003/g/data-view#"); + table.Add("http://www.w3.org/1999/xhtml"); + table.Add("http://openoffice.org/2009/table"); + + // OOo 3.3.x + table.Add("http://openoffice.org/2010/chart"); + + // Other OpenDocument v 1.1 + table.Add("urn:oasis:names:tc:opendocument:xmlns:config:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:animation:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:data style:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"); + table.Add("urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0"); + + // Symphony 1.3 + table.Add("http://www.ibm.com/xmlns/prodtools"); + + // CleverAge ODF Add-in for Microsoft Office 3.0.5224.0 (11.0.8302) + table.Add("http://purl.org/dc/terms/"); + + // KOffice 1.6.3 + table.Add("http://www.koffice.org/2005/"); + + // Microsoft Excel Formulas in ODF + table.Add("http://schemas.microsoft.com/office/excel/formula"); + + // Gnumeric ODF extensions + table.Add("http://www.gnumeric.org/odf-extension/1.0"); + + // LOCALC + table.Add("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"); + table.Add("urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"); + + return table; + } + + private static XmlDocumentType CreateOpenDocumentDTD(XmlDocument doc) + { + // Root node + XmlDocumentType docType = doc.CreateDocumentType("meta", null, null, null); + + // OpenDocument TAGS + docType.AppendChild(doc.CreateElement("generator")); + docType.AppendChild(doc.CreateElement("title")); + docType.AppendChild(doc.CreateElement("description")); + docType.AppendChild(doc.CreateElement("subject")); + docType.AppendChild(doc.CreateElement("keyword")); + docType.AppendChild(doc.CreateElement("initial-creator")); + docType.AppendChild(doc.CreateElement("creator")); + docType.AppendChild(doc.CreateElement("printed-by")); + docType.AppendChild(doc.CreateElement("creation-date")); + docType.AppendChild(doc.CreateElement("date")); + docType.AppendChild(doc.CreateElement("print-date")); + docType.AppendChild(doc.CreateElement("template")); + docType.AppendChild(doc.CreateElement("auto-reload")); + docType.AppendChild(doc.CreateElement("hyperlink-behaviour")); + docType.AppendChild(doc.CreateElement("document-statistic")); + docType.AppendChild(doc.CreateElement("language")); + docType.AppendChild(doc.CreateElement("editing-duration")); + docType.AppendChild(doc.CreateElement("user-defined")); + + return docType; + } + + public static void gsf_opendoc_metadata_subtree_free(GsfXMLIn xin, object old_state) + { + GsfOOMetaIn state = old_state as GsfOOMetaIn; + if (state.Keywords != null) + state.MetaData.Insert(GSF_META_NAME_KEYWORDS, state.Keywords); + } + + /// + /// Extend > so that it can parse a subtree in OpenDoc metadata format + /// + public static void gsf_doc_meta_data_odf_subtree(GsfDocMetaData md, GsfXMLIn doc) + { + if (md == null) + return; + + XmlDocument document = new XmlDocument(CreateOpenDocumentNamespaces()); + XmlDocumentType docType = CreateOpenDocumentDTD(document); + document.AppendChild(docType); + + GsfOOMetaIn state = new GsfOOMetaIn + { + MetaData = md, + Type = null, + Doc = document, + }; + + doc.PushState(state.Doc, state, gsf_opendoc_metadata_subtree_free, null); + } + + /// + /// Extend so that it can parse a subtree in OpenDoc metadata format + /// The current user_state must be a GsfOOMetaIn! + /// + public static void gsf_opendoc_metadata_subtree_internal(GsfXMLIn xin, string[] attrs) + { + GsfOOMetaIn mi = (GsfOOMetaIn)xin.UserState; + if (mi.Doc == null) + { + XmlDocument document = new XmlDocument(CreateOpenDocumentNamespaces()); + XmlDocumentType docType = CreateOpenDocumentDTD(document); + document.AppendChild(docType); + mi.Doc = document; + } + + xin.PushState(mi.Doc, null, null, null); + } + + /** + * gsf_doc_meta_data_read_from_odf: + * @md: #GsfDocMetaData + * @input: #GsfInput + * + * Read an OpenDocument metadata stream from @input and store the properties + * into @md. Overwrite any existing properties with the same id. + * + * Since: 1.14.24 + * + * Returns: (transfer full): a #Exception if there is a problem. + **/ + public static Exception gsf_doc_meta_data_read_from_odf(GsfDocMetaData md, GsfInput input) + { + GsfOOMetaIn state = new GsfOOMetaIn + { + MetaData = md, + Keywords = null, + Err = null, + Name = null, + Doc = null, + }; + + GsfXMLInParser parser = new GsfXMLInParser(input as GsfXMLIn); + + state.Doc = new XmlDocument(CreateOpenDocumentNamespaces()); + XmlDocumentType docType = CreateOpenDocumentDTD(state.Doc); + state.Doc.AppendChild(docType); + + while (parser.Read()) { } + + parser.Close(); + + if (state.Keywords != null) + state.Keywords.Add(md); + + return state.Err; + } + + /** + * gsf_opendoc_metadata_read: (skip) + * @input: #GsfInput + * @md: #GsfDocMetaData + * + * Read an OpenDocument metadata stream from @input and store the properties + * into @md. Overwrite any existing properties with the same id. + * + * Deprecated: 1.14.24, use gsf_doc_meta_data_read_from_odf + * + * Returns: (transfer full): a #Exception if there is a problem. + **/ + public static Exception gsf_opendoc_metadata_read(GsfInput input, GsfDocMetaData md) => gsf_doc_meta_data_read_from_odf(md, input); + + // Shared by all instances and never freed + private static readonly Dictionary od_prop_name_map = new Dictionary + { + { GSF_META_NAME_GENERATOR, "meta:generator" }, + { GSF_META_NAME_TITLE, "dc:title" }, + { GSF_META_NAME_DESCRIPTION, "dc:description" }, + { GSF_META_NAME_SUBJECT, "dc:subject" }, + { GSF_META_NAME_INITIAL_CREATOR,"meta:initial-creator" }, + { GSF_META_NAME_CREATOR, "dc:creator" }, + { GSF_META_NAME_PRINTED_BY, "meta:printed-by" }, + { GSF_META_NAME_DATE_CREATED, "meta:creation-date" }, + { GSF_META_NAME_DATE_MODIFIED, "dc:date" }, + { GSF_META_NAME_LAST_PRINTED, "meta:print-date" }, + { GSF_META_NAME_LANGUAGE, "dc:language" }, + { GSF_META_NAME_REVISION_COUNT, "meta:editing-cycles" }, + { GSF_META_NAME_EDITING_DURATION, "meta:editing-duration" } + }; + + public static string od_map_prop_name(string name) + { + if (!od_prop_name_map.ContainsKey(name)) + return null; + + return od_prop_name_map[name]; + } + + /* + meta:page-count GSF_META_NAME_PAGE_COUNT + meta:table-count GSF_META_NAME_TABLE_COUNT: + meta:draw-count + meta:image-count GSF_META_NAME_IMAGE_COUNT: + meta:ole-object-count GSF_META_NAME_OBJECT_COUNT: + meta:paragraph-count GSF_META_NAME_PARAGRAPH_COUNT: + meta:word-count + meta:character-count GSF_META_NAME_CHARACTER_COUNT + meta:row-count GSF_META_NAME_LINE_COUNT: + meta:frame-count + meta:sentence-count + meta:syllable-count + meta:non-whitespace-character-count + + meta:page-count + GSF_META_NAME_SPREADSHEET_COUNT + meta:table-count + GSF_META_NAME_TABLE_COUNT: + meta:image-count + * GSF_META_NAME_IMAGE_COUNT: + meta:cell-count + GSF_META_NAME_CELL_COUNT + meta:object-count + GSF_META_NAME_OBJECT_COUNT: + + meta:page-count + GSF_META_NAME_SLIDE_COUNT: + meta:image-count + GSF_META_NAME_IMAGE_COUNT: + meta:object-count + GSF_META_NAME_OBJECT_COUNT: + */ + + /// + /// ODF does not like "t" and "f" which we use normally + /// + public static void gsf_xml_out_add_gvalue_for_odf(this GsfXMLOut xout, string id, object val) + { + if (val is bool b) + xout.AddString(id, b ? "true" : "false"); + else + xout.AddValue(id, val); + } + + static void meta_write_props_user_defined(string prop_name, object val, GsfXMLOut output) + { + string type_name = null; + + output.StartElement("meta:user-defined"); + output.AddStringUnchecked("meta:name", prop_name); + + if (null == val) + { + output.EndElement(); + return; + } + + if (val.GetType() == typeof(char)) + type_name = "string"; + else if (val.GetType() == typeof(byte)) + type_name = "string"; + else if (val.GetType() == typeof(string)) + type_name = "string"; + else if (val.GetType().IsEnum) + type_name = "string"; + else if (val.GetType() == typeof(bool)) + type_name = "boolean"; + else if (val.GetType() == typeof(int)) + type_name = "float"; + else if (val.GetType() == typeof(uint)) + type_name = "float"; + else if (val.GetType() == typeof(long)) + type_name = "float"; + else if (val.GetType() == typeof(ulong)) + type_name = "float"; + else if (val.GetType() == typeof(float)) + type_name = "float"; + else if (val.GetType() == typeof(double)) + type_name = "float"; + else if (val.GetType() == typeof(DateTime)) + type_name = "date"; + + if (type_name != null) + output.AddStringUnchecked("meta:value-type", type_name); + if (val != null) + output.gsf_xml_out_add_gvalue_for_odf(null, val); + + output.EndElement(); + } + + static void meta_write_props(string prop_name, GsfDocProp prop, GsfXMLOut output) + { + string mapped_name; + object val = prop.Value; + + // Handle specially + if (prop_name == GSF_META_NAME_KEYWORDS) + { + // OLE2 stores a single string, with no obvious + // standard for seperator + if (val.GetType() == typeof(string)) + { + string str = (string)val; + if (!string.IsNullOrEmpty(str)) + { + output.StartElement("meta:keyword"); + output.AddString(null, str); + output.EndElement(); + } + } + else if (val is List list) + { + for (int i = 0; i < list.Count; i++) + { + string str = list[i].Name; + output.StartElement("meta:keyword"); + output.AddString(null, str); + output.EndElement(); + } + } + return; + } + + if ((mapped_name = od_map_prop_name(prop_name)) == null) + { + if (val is List list) + { + for (int i = 0; i < list.Count; i++) + { + string new_name = $"GSF_DOCPROP_VECTOR:{i:4}:{prop_name}"; + meta_write_props_user_defined(new_name, list[i], output); + } + } + else + { + meta_write_props_user_defined(prop_name, val, output); + } + + return; + } + + // Standardized ODF meta items + output.StartElement(mapped_name); + if (val != null) + gsf_xml_out_add_gvalue_for_odf(output, null, val); + + output.EndElement(); + } + + /** + * gsf_doc_meta_data_write_to_odf: + * @md: #GsfDocMetaData + * @output: (type GsfXMLOut): a pointer to a #GsfOutput. + * + * Since: 1.14.24 + * + * Returns: %true if no error occured. + **/ + static bool gsf_doc_meta_data_write_to_odf(GsfDocMetaData[] md, object output) + { + string ver_str; + + if (output == null) + return false; + + // For compatibility we take a GsfXMLOut argument. It really + // ought to be a GsfODFOut. + GsfXMLOut xout = output as GsfXMLOut; + GsfODFOut oout = (output is GsfODFOut) ? output as GsfODFOut : null; + + ver_str = oout != null ? oout.GetVersionString() : GetVersionString(); + + xout.StartElement($"{OFFICE}document-meta"); + xout.AddStringUnchecked("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0"); + xout.AddStringUnchecked("xmlns:xlink", "http://www.w3.org/1999/xlink"); + xout.AddStringUnchecked("xmlns:dc", "http://purl.org/dc/elements/1.1/"); + xout.AddStringUnchecked("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"); + xout.AddStringUnchecked("xmlns:ooo", "http://openoffice.org/2004/office"); + xout.AddStringUnchecked("office:version", ver_str); + + xout.StartElement($"{OFFICE}meta"); + foreach (GsfDocMetaData data in md) + { + foreach (KeyValuePair kvp in data.Table) + { + meta_write_props(kvp.Key, kvp.Value, xout); + } + } + xout.EndElement(); + + xout.EndElement(); + + return true; + } + + #endregion + } + + #endregion +} diff --git a/BurnOutSharp/External/libgsf/GsfOpenPkgUtils.cs b/BurnOutSharp/External/libgsf/GsfOpenPkgUtils.cs new file mode 100644 index 00000000..1ac96dad --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfOpenPkgUtils.cs @@ -0,0 +1,723 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-open-pkg-utils.c: Utilities for handling Open Package zip files + * from MS Office 2007 or XPS. + * + * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using LibGSF.Input; +using LibGSF.Output; + +namespace LibGSF +{ + public class GsfOpenPkgRel + { + #region Properties + + public string Id { get; set; } = null; + + public string Type { get; set; } = null; + + public string Target { get; set; } = null; + + public bool IsExtern { get; set; } = false; + + #endregion + + #region Functions + + /// A new GsfInput which the called needs to unref, or null and sets + public static GsfInput OpenRelatedPackage(GsfInput opkg, GsfOpenPkgRel rel, ref Exception err) + { + if (opkg == null) + return null; + if (rel == null) + return null; + + GsfInput res = null; + GsfInfile prev_parent; + + // References from the root use children of opkg + // References from a child are relative to siblings of opkg + GsfInfile parent = opkg.Name != null ? opkg.Container : opkg as GsfInfile; + + string target = rel.Target; + if (target[0] == '/') + { + target = target.Substring(1); + + // Handle absolute references by going as far up as we can. + while (true) + { + GsfInfile next_parent = parent.Container; + if (next_parent != null && next_parent.GetType() == parent.GetType()) + parent = next_parent; + else + break; + } + } + + string[] elems = rel.Target.Split('/'); + for (int i = 0; i < elems.Length && elems[i] != null && parent != null; i++) + { + if (elems[i] == "." || elems[i][0] == '\0') + continue; // Ignore '.' and empty + + prev_parent = parent; + if (elems[i] == "..") + { + parent = parent.Container; + res = null; // Only return newly created children + if (parent != null) + { + // Check for attempt to gain access outside the zip file + if (parent.GetType() != prev_parent.GetType()) + { + Console.Error.WriteLine("Broken file: relation access outside container"); + parent = null; + } + } + } + else + { + res = parent.ChildByName(elems[i], ref err); + if (elems[i + 1] != null) + { + if (res == null || !(res is GsfInfile)) + return null; + + parent = res as GsfInfile; + } + } + } + + return res; + } + + /// + /// Finds 's relation with @id + /// + /// Identifier. + /// A GsfOpenPkgRel or null + /// + /// New in 1.14.6 + /// + /// Skipping because gsf_open_pkg_rel_get_type() does not return a GType. + /// + public static GsfOpenPkgRel LookupRelationById(GsfInput opkg, string id) + { + GsfOpenPkgRels rels = GsfOpenPkgRels.LookupRelations(opkg); + if (rels == null) + return null; + + if (!rels.RelationsById.ContainsKey(id)) + return null; + + return rels.RelationsById[id]; + } + + /// + /// Finds _a_ relation of with (no order is guaranteed) + /// + /// Target + /// A GsfOpenPkgRel or null + /// + /// New in 1.14.6 + /// + /// Skipping because gsf_open_pkg_rel_get_type() does not return a GType. + /// + public static GsfOpenPkgRel LookupRelationByType(GsfInput opkg, string type) + { + GsfOpenPkgRels rels = GsfOpenPkgRels.LookupRelations(opkg); + if (rels == null) + return null; + + if (!rels.RelationsByType.ContainsKey(type)) + return null; + + return rels.RelationsByType[type]; + } + + #endregion + } + + public class GsfOpenPkgRels + { + #region Constants + + /// + /// Generated based on: + /// http://www.oasis-open.org/committees/download.php/12572/OpenDocument-v1.0-os.pdf + /// and OpenDocument-v1.1.pdf + /// + private static XmlNameTable CREATE_NAMESPACES() + { + NameTable table = new NameTable(); + + table.Add("http://schemas.openxmlformats.org/package/2006/relationships"); + + return table; + } + + private static XmlDocumentType CREATE_DTD(XmlDocument doc) + { + // Root node + XmlDocumentType docType = doc.CreateDocumentType(null, null, null, null); + + docType.AppendChild(doc.CreateElement("Relationships")); + docType["Relationships"].AppendChild(doc.CreateElement("Relationship")); + + return docType; + } + + #endregion + + #region Properties + + public Dictionary RelationsById { get; set; } = new Dictionary(); + + public Dictionary RelationsByType { get; set; } = new Dictionary(); + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfOpenPkgRels() { } + + #endregion + + #region Functions + + public static GsfOpenPkgRels LookupRelations(GsfInput opkg) + { + if (opkg == null) + return null; + + GsfOpenPkgRels rels = opkg.Relations; + if (rels == null) + { + string part_name = opkg.Name; + + GsfInput rel_stream; + if (part_name != null) + { + GsfInfile container = opkg.Container; + if (container == null) + return null; + + string rel_name = $"{part_name}.rels"; + rel_stream = container.ChildByVariableName("_rels", rel_name); + } + else // The root + { + rel_stream = (opkg as GsfInfile).ChildByVariableName("_rels", ".rels"); + } + + XmlDocument rel_doc; + if (rel_stream != null) + { + rels = new GsfOpenPkgRels + { + RelationsById = new Dictionary(), + RelationsByType = new Dictionary(), + }; + + rel_doc = new XmlDocument(CREATE_NAMESPACES()); + XmlDocumentType docType = CREATE_DTD(rel_doc); + rel_doc.AppendChild(docType); + + // TODO: Enable parsing + //rel_doc.Parse(rel_stream, rels); + } + + opkg.Relations = rels; + } + + return rels; + } + + #endregion + + #region Utilities + + private static void OpenPkgRelBegin(GsfXMLIn xin, string[] attrs) + { + GsfOpenPkgRels rels = xin.UserState as GsfOpenPkgRels; + string id = null; + string type = null; + string target = null; + bool is_extern = false; + + for (int i = 0; i < attrs.Length && attrs[i] != null && attrs[i + 1] != null; i += 2) + { + switch (attrs[i]) + { + case "Id": + id = attrs[i + 1]; + break; + case "Type": + type = attrs[i + 1]; + break; + case "Target": + target = attrs[i + 1]; + break; + case "TargetMode": + is_extern = attrs[i + 1] == "External"; + break; + } + } + + if (id == null) + { + Console.Error.WriteLine("Broken relation: missing id"); + id = "?"; + } + + if (type == null) + { + Console.Error.WriteLine("Broken relation: missing type"); + type = "?"; + } + + if (target == null) + { + Console.Error.WriteLine("Broken relation: missing target"); + target = "?"; + } + + GsfOpenPkgRel rel = new GsfOpenPkgRel + { + Id = id, + Type = type, + Target = target, + IsExtern = is_extern, + }; + + // Make sure we don't point to a freed rel in the type hash. + rels.RelationsByType[rel.Type] = rel; + + // This will free a duplicate rel, so do this last. + rels.RelationsById[rel.Id] = rel; + } + + #endregion + }; + + public class GsfOutfileOpenPkg : GsfOutfile + { + #region Properties + + public GsfOutput Sink { get; set; } = null; + + public bool IsDir { get; set; } = false; + + public string ContentType { get; set; } = null; + + public List Children { get; set; } = null; + + public List Relations { get; set; } = null; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutfileOpenPkg() { } + + /// + /// Convenience routine to create a GsfOutfileOpenPkg inside . + /// + /// + /// A GsfOutfile that the caller is responsible for. + public static GsfOutfileOpenPkg Create(GsfOutfile sink) + { + return new GsfOutfileOpenPkg + { + Sink = sink, + IsDir = true, + }; + } + + /// + /// Destructor + /// + ~GsfOutfileOpenPkg() + { + if (Sink != null) + Sink = null; + + ContentType = null; + + Children.Clear(); + Children = null; + } + + #endregion + + #region Functions + + public new bool Close() + { + GsfOutput dir; + bool res = false; + string rels_name; + + if (Sink == null || Sink.IsClosed) + return true; + + // Generate [Content_types].xml when we close the root dir + if (Name == null) + { + GsfOutput output = (Sink as GsfOutfile).NewChild("[Content_Types].xml", false); + GsfXMLOut xml = GsfXMLOut.Create(output); + + xml.StartElement("Types"); + xml.AddStringUnchecked("xmlns", "http://schemas.openxmlformats.org/package/2006/content-types"); + xml.WriteContentDefault("rels", "application/vnd.openxmlformats-package.relationships+xml"); + xml.WriteContentDefault("xlbin", "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"); + xml.WriteContentDefault("xml", "application/xml"); + xml.WriteContentDefault("vml", "application/vnd.openxmlformats-officedocument.vmlDrawing"); + xml.WriteContentOverride(this, "/"); + xml.EndElement(); + + output.Close(); + + dir = Sink; + rels_name = ".rels"; + } + else + { + res = Sink.Close(); + dir = Sink.Container; + rels_name = $"{Name}.rels"; + } + + if (Relations != null) + { + dir = (dir as GsfOutfile).NewChild("_rels", true); + GsfOutput rels = (dir as GsfOutfile).NewChild(rels_name, false); + GsfXMLOut xml = GsfXMLOut.Create(rels); + + xml.StartElement("Relationships"); + xml.AddStringUnchecked("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); + + foreach (GsfOpenPkgRel rel in Relations) + { + xml.StartElement("Relationship"); + xml.AddString("Id", rel.Id); + xml.AddString("Type", rel.Type); + xml.AddString("Target", rel.Target); + if (rel.IsExtern) + xml.AddStringUnchecked("TargetMode", "External"); + + xml.EndElement(); + } + + Relations.Clear(); + xml.EndElement(); + rels.Close(); + } + + // Close the container + if (Name == null) + return Sink.Close(); + + return res; + } + + public string CreateRelation(string target, string type, bool is_extern) + { + GsfOpenPkgRel rel = new GsfOpenPkgRel + { + Target = target, + Type = type, + Id = $"rID{Relations.Count + 1}", + IsExtern = is_extern, + }; + + Relations.Add(rel); + return rel.Id; + } + + /// + /// Create a relationship between child and of . + /// + /// Target type + /// The relID which the caller does not own but will live as long as . + public string RelatePackages(GsfOutfileOpenPkg parent, string type) + { + int up = -1; + GsfOutfile child_dir; + + // Calculate the path from child to parent + GsfOutfile parent_dir = parent.IsDir ? parent : parent.Container; + do + { + up++; + child_dir = this; + while ((child_dir = child_dir.Container) != null) + { + if (child_dir == parent_dir) + goto found; // Break out of both loops + } + } while ((parent_dir = parent_dir.Container) != null); + + // TODO: Figure out how to best remove this goto + found: + // Yes prepend is slow, this will never be preformance critical + string path = Name; + child_dir = this; + while ((child_dir = child_dir.Container) != null && child_dir.Name != null && child_dir != parent_dir) + { + path = $"{child_dir.Name}/{path}"; + } + + while (up-- != 0) + { + path = $"../{path}"; + } + + return parent.CreateRelation(path, type, false); + } + + /// + /// A convenience wrapper to create a child in of then create + /// a relation to + /// + /// Target name + /// Non-null content type + /// Target type + /// The new part. + public GsfOutput AddRelation(string name, string content_type, GsfOutfile parent, string type) + { + GsfOutfileOpenPkg part = NewChild(name, false) as GsfOutfileOpenPkg; + if (part == null) + return null; + + part.ContentType = content_type; + part.RelatePackages(parent as GsfOutfileOpenPkg, type); + return part; + } + + /// + /// Add an external relation to parent. + /// + /// Target type + /// Target content + /// + /// The id of the relation. The string is + /// managed by the parent and should not be changed or freed by the + /// caller. + /// + public string AddExternalRelation(string target, string content_type) => CreateRelation(target, content_type, true); + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) => Sink.Write(num_bytes, data); + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => Sink.Seek(offset, whence); + + /// + public override GsfOutput NewChild(string name, bool is_dir) + { + if (!is_dir) + return null; + + GsfOutfileOpenPkg child = new GsfOutfileOpenPkg + { + Name = name, + Container = this, + IsDir = is_dir, + }; + + // Holding a ref here is not ideal. It means we won't release any of the + // children until the package is closed. + Children.Add(child); + + return child; + } + + #endregion + } + + public static class GsfOpenPkgUtils + { + #region Delegates + + public delegate void GsfOpenPkgIter(GsfInput opkg, GsfOpenPkgRel rel, object user_data); + + #endregion + + #region Classes + + private class pkg_iter_data + { + public GsfInput opkg { get; set; } + + public GsfOpenPkgIter func { get; set; } + + public object user_data { get; set; } + }; + + #endregion + + #region Functions + + /// + /// Walks each relationship associated with and calls with . + /// + /// New in 1.14.9 + public static void ForeachRelation(this GsfInput opkg, GsfOpenPkgIter func, object user_data) + { + GsfOpenPkgRels rels = GsfOpenPkgRels.LookupRelations(opkg); + if (rels == null) + return; + + pkg_iter_data dat = new pkg_iter_data + { + opkg = opkg, + func = func, + user_data = user_data, + }; + + foreach (KeyValuePair rel in rels.RelationsById) + { + ForeachRelationImpl(rel.Key, rel.Value, dat); + } + } + + /// + /// Open @opkg's relation + /// + /// Target id + /// A new GsfInput or null, and sets if possible. + /// New in 1.14.7 + public static GsfInput RelationById(this GsfInput opkg, string id, ref Exception err) + { + GsfOpenPkgRel rel = GsfOpenPkgRel.LookupRelationById(opkg, id); + if (rel != null) + return GsfOpenPkgRel.OpenRelatedPackage(opkg, rel, ref err); + + err = new Exception($"Unable to find part id='{id}' for '{opkg.Name}'"); + return null; + } + + /// + /// Open one of 's relationships with type=. + /// + /// Target type + /// A new GsfInput or null, and sets if possible. + /// New in 1.14.9 + public static GsfInput RelationByType(this GsfInput opkg, string type, ref Exception err) + { + GsfOpenPkgRel rel = GsfOpenPkgRel.LookupRelationByType(opkg, type); + if (rel != null) + return GsfOpenPkgRel.OpenRelatedPackage(opkg, rel, ref err); + + err = new Exception($"Unable to find part with type='{type}' for '{opkg.Name}'"); + return null; + } + + /// + /// Convenience function to parse a related part. + /// + /// Target id + /// null on success or an Exception on failure. + public static Exception ParseRelationById(this GsfXMLIn xin, string id, XmlDocumentType dtd, XmlNameTable ns) + { + if (xin == null) + return null; + + GsfInput cur_stream = xin.Input; + if (id == null) + return new Exception($"Missing id for part in '{cur_stream.Name}'"); + + Exception res = null; + GsfInput part_stream = RelationById(cur_stream, id, ref res); + if (part_stream != null) + { + XmlDocument doc = new XmlDocument(ns); + doc.AppendChild(dtd); + + // TODO: Enable parsing + //if (!doc.Parse(part_stream, xin.UserState)) + // res = new Exception($"Part '{id}' in '{part_stream.Name}' from '{cur_stream.Name}' is corrupt!"); + } + + return res; + } + + #endregion + + #region Utilties + + private static void ForeachRelationImpl(object id, GsfOpenPkgRel rel, pkg_iter_data dat) + { + dat.func(dat.opkg, rel, dat.user_data); + } + + internal static void WriteContentDefault(this GsfXMLOut xml, string ext, string type) + { + xml.StartElement("Default"); + xml.AddString("Extension", ext); + xml.AddString("ContentType", type); + xml.EndElement(); + } + + internal static void WriteContentOverride(this GsfXMLOut xml, GsfOutfileOpenPkg open_pkg, string baseName) + { + foreach (GsfOutfileOpenPkg child in open_pkg.Children) + { + string path; + if (child.IsDir) + { + path = $"{child.Name}/"; + xml.WriteContentOverride(child, path); + } + else + { + path = $"{baseName}{child.Name}"; + + // Rels files do need content types, the defaults handle them + if (child.ContentType != null) + { + xml.StartElement("Override"); + xml.AddString("PartName", path); + xml.AddString("ContentType", child.ContentType); + xml.EndElement(); + } + } + } + + // Dispose of children here to break link cycles. + open_pkg.Children.Clear(); + open_pkg.Children = null; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfSharedMemory.cs b/BurnOutSharp/External/libgsf/GsfSharedMemory.cs new file mode 100644 index 00000000..c40089d2 --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfSharedMemory.cs @@ -0,0 +1,90 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-shared-memory.c: + * + * Copyright (C) 2002-2006 Morten Welinder (terra@diku.dk) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; + +namespace LibGSF +{ + public class GsfSharedMemory + { + #region Properties + + public byte[] Buf { get; set; } = null; + + public long Size { get; set; } + + public bool NeedsFree { get; set; } + + public bool NeedsUnmap { get; set; } + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfSharedMemory() { } + + public static GsfSharedMemory Create(byte[] buf, long size, bool needs_free) + { + return new GsfSharedMemory + { + Buf = buf, + Size = size, + NeedsFree = needs_free, + NeedsUnmap = false, + }; + } + + public static GsfSharedMemory CreateMemoryMapped(byte[] buf, long size) + { + int msize = (int)size; + if (msize != size) + { + Console.Error.WriteLine("Memory buffer size too large"); + return null; + } + else + { + GsfSharedMemory mem = Create(buf, size, false); + mem.NeedsUnmap = true; + return mem; + } + } + + /// + /// Destructor + /// + ~GsfSharedMemory() + { + if (Buf != null) + { + if (NeedsFree) + Buf = null; + //else if (NeedsUnmap) + //UnmapViewOfFile(mem.buf); + } + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/GsfZipImpl.cs b/BurnOutSharp/External/libgsf/GsfZipImpl.cs new file mode 100644 index 00000000..b70bc396 --- /dev/null +++ b/BurnOutSharp/External/libgsf/GsfZipImpl.cs @@ -0,0 +1,391 @@ +/* THIS IS NOT INSTALLED */ + +/* + * gsf-zip-impl.h: + * + * Copyright (C) 2002-2006 Tambet Ingo (tambet@ximian.com) + * Copyright (C) 2014 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; + +namespace LibGSF +{ + #region Enums + + /// + /// A few well-defined extra-field tags. + /// + public enum ExtraFieldTags : ushort + { + ZIP_DIRENT_EXTRA_FIELD_ZIP64 = 0x0001, + + /// + /// "II" -- gsf defined + /// + ZIP_DIRENT_EXTRA_FIELD_IGNORE = 0x4949, + + /// + /// "UT" + /// + ZIP_DIRENT_EXTRA_FIELD_UNIXTIME = 0x5455, + + /// + /// "ux" + /// + ZIP_DIRENT_EXTRA_FIELD_UIDGID = 0x7875 + }; + + /// + /// OS codes. There are plenty, but this is all we need. + /// + public enum OSCodes + { + ZIP_OS_MSDOS = 0, + ZIP_OS_UNIX = 3 + }; + + /// From gsf-outfile-zip.h + public enum GsfZipCompressionMethod + { + /// + /// Supported for export + /// + GSF_ZIP_STORED = 0, + GSF_ZIP_SHRUNK = 1, + GSF_ZIP_REDUCEDx1 = 2, + GSF_ZIP_REDUCEDx2 = 3, + GSF_ZIP_REDUCEDx3 = 4, + GSF_ZIP_REDUCEDx4 = 5, + GSF_ZIP_IMPLODED = 6, + GSF_ZIP_TOKENIZED = 7, + + /// + /// Supported for export + /// + GSF_ZIP_DEFLATED = 8, + GSF_ZIP_DEFLATED_BETTER = 9, + GSF_ZIP_IMPLODED_BETTER = 10 + } + + #endregion + + #region Classes + + public class GsfZipDirectoryEntry + { + #region Properties + + public string Name { get; set; } + + public int Flags { get; set; } + + public GsfZipCompressionMethod CompressionMethod { get; set; } + + public uint CRC32 { get; set; } + + public long CompressedSize { get; set; } + + public long UncompressedSize { get; set; } + + public long Offset { get; set; } + + public long DataOffset { get; set; } + + public uint DosTime { get; set; } + + public DateTime? ModifiedTime { get; set; } + + /// + /// null = auto, FALSE, TRUE. + /// + public bool? Zip64 { get; set; } + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfZipDirectoryEntry() { } + + /// + /// Doesn't do much, but include for symmetry + /// + public static GsfZipDirectoryEntry Create() => new GsfZipDirectoryEntry(); + + #endregion + + #region Functions + + public void Free() + { + Name = null; + } + + public GsfZipDirectoryEntry Copy() + { + return new GsfZipDirectoryEntry + { + Name = Name, + Flags = Flags, + CompressionMethod = CompressionMethod, + CRC32 = CRC32, + CompressedSize = CompressedSize, + UncompressedSize = UncompressedSize, + Offset = Offset, + DataOffset = DataOffset, + DosTime = DosTime, + ModifiedTime = ModifiedTime, + Zip64 = Zip64, + }; + } + + #endregion + } + + public class GsfZipVDir + { + #region Properties + + public string Name { get; set; } + + public bool IsDirectory { get; set; } + + public GsfZipDirectoryEntry DirectoryEntry { get; set; } + + public List Children { get; set; } + + //public GSList* last_child { get; set; } /* Unused */ + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfZipVDir() { } + + /// The newly created GsfZipVDir. + /// Since: 1.14.24 + public static GsfZipVDir Create(string name, bool is_directory, GsfZipDirectoryEntry dirent) + { + return new GsfZipVDir + { + Name = name, + IsDirectory = is_directory, + DirectoryEntry = dirent, + Children = new List(), + }; + } + + /// + /// Destructor + /// + ~GsfZipVDir() => Free(true); + + #endregion + + #region Functions + + public void Free(bool free_dirent) + { + for (int i = 0; i < Children.Count; i++) + { + GsfZipVDir c = Children[i]; + c.Free(free_dirent); + } + + Children.Clear(); + Name = null; + if (free_dirent && DirectoryEntry != null) + DirectoryEntry.Free(); + } + + public GsfZipVDir Copy() + { + GsfZipVDir res = new GsfZipVDir(); + + // It is not possible to add a ref_count without breaking the API, + // so we need to really copy everything + if (Name != null) + res.Name = Name; + + res.IsDirectory = IsDirectory; + if (DirectoryEntry != null) + res.DirectoryEntry = DirectoryEntry.Copy(); + + for (int i = 0; i < Children.Count; i++) + { + GsfZipVDir c = Children[i]; + res.AddChild(c.Copy()); + } + return res; + } + + public void AddChild(GsfZipVDir child) + { + Children.Add(child); + } + + public GsfZipVDir ChildByName(string name) + { + for (int i = 0; i < Children.Count; i++) + { + GsfZipVDir child = Children[i]; + if (child.Name == name) + return child; + } + + return null; + } + + public GsfZipVDir ChildByIndex(int target) => target < Children.Count ? Children[target] : null; + + public void Insert(string name, GsfZipDirectoryEntry dirent) + { + GsfZipVDir child; + + int p = name.IndexOf(GsfZipImpl.ZIP_NAME_SEPARATOR); + if (p != -1) + { + // A directory + string dirname = name.Substring(p); + child = ChildByName(dirname); + if (child == null) + { + child = Create(dirname, true, null); + AddChild(child); + } + + if (name[p + 1] != '\0') + { + name = name.Substring(p + 1); + child.Insert(name, dirent); + } + } + else + { + // A simple file name + child = Create(name, false, dirent); + AddChild(child); + } + } + + #endregion + } + + public static class GsfZipImpl + { + // Every member file is preceded by a header with this format. + public const uint ZIP_HEADER_SIGNATURE = 0x04034b50; + public const int ZIP_HEADER_SIZE = 30; + public const int ZIP_HEADER_EXTRACT = 4; + public const int ZIP_HEADER_FLAGS = 6; + public const int ZIP_HEADER_COMP_METHOD = 8; + public const int ZIP_HEADER_DOSTIME = 10; + public const int ZIP_HEADER_CRC32 = 14; + public const int ZIP_HEADER_CSIZE = 18; + public const int ZIP_HEADER_USIZE = 22; + public const int ZIP_HEADER_NAME_SIZE = 26; + public const int ZIP_HEADER_EXTRAS_SIZE = 28; + + // Members may have this record after the compressed data. It is meant + // to be used only when it is not possible to seek back and patch the + // right values into the header. + public const uint ZIP_DDESC_SIGNATURE = 0x08074b50; + public const int ZIP_DDESC_SIZE = 16; + public const int ZIP_DDESC_CRC32 = 4; + public const int ZIP_DDESC_CSIZE = 8; + public const int ZIP_DDESC_USIZE = 12; + + // 64-bit version of above. Used when the ZIP64 extra field is present + // in the header. + public const uint ZIP_DDESC64_SIGNATURE = ZIP_DDESC_SIGNATURE; + public const int ZIP_DDESC64_SIZE = 24; + public const int ZIP_DDESC64_CRC32 = 4; + public const int ZIP_DDESC64_CSIZE = 8; + public const int ZIP_DDESC64_USIZE = 16; + + // The whole archive ends with a trailer. + public const uint ZIP_TRAILER_SIGNATURE = 0x06054b50; + public const int ZIP_TRAILER_SIZE = 22; + public const int ZIP_TRAILER_DISK = 4; + public const int ZIP_TRAILER_DIR_DISK = 6; + public const int ZIP_TRAILER_ENTRIES = 8; + public const int ZIP_TRAILER_TOTAL_ENTRIES = 10; + public const int ZIP_TRAILER_DIR_SIZE = 12; + public const int ZIP_TRAILER_DIR_POS = 16; + public const int ZIP_TRAILER_COMMENT_SIZE = 20; + + // A zip64 locator comes immediately before the trailer, if it is present. + public const uint ZIP_ZIP64_LOCATOR_SIGNATURE = 0x07064b50; + public const int ZIP_ZIP64_LOCATOR_SIZE = 20; + public const int ZIP_ZIP64_LOCATOR_DISK = 4; + public const int ZIP_ZIP64_LOCATOR_OFFSET = 8; + public const int ZIP_ZIP64_LOCATOR_DISKS = 16; + + // A zip64 archive has this record somewhere to extend the field sizes. + public const uint ZIP_TRAILER64_SIGNATURE = 0x06064b50; + public const int ZIP_TRAILER64_SIZE = 56; // Or more + public const int ZIP_TRAILER64_RECSIZE = 4; + public const int ZIP_TRAILER64_ENCODER = 12; + public const int ZIP_TRAILER64_EXTRACT = 14; + public const int ZIP_TRAILER64_DISK = 16; + public const int ZIP_TRAILER64_DIR_DISK = 20; + public const int ZIP_TRAILER64_ENTRIES = 24; + public const int ZIP_TRAILER64_TOTAL_ENTRIES = 32; + public const int ZIP_TRAILER64_DIR_SIZE = 40; + public const int ZIP_TRAILER64_DIR_POS = 48; + + // This defines the entries in the central directory. + public const uint ZIP_DIRENT_SIGNATURE = 0x02014b50; + public const int ZIP_DIRENT_SIZE = 46; + public const int ZIP_DIRENT_ENCODER = 4; + public const int ZIP_DIRENT_EXTRACT = 6; + public const int ZIP_DIRENT_FLAGS = 8; + public const int ZIP_DIRENT_COMPR_METHOD = 10; + public const int ZIP_DIRENT_DOSTIME = 12; + public const int ZIP_DIRENT_CRC32 = 16; + public const int ZIP_DIRENT_CSIZE = 20; + public const int ZIP_DIRENT_USIZE = 24; + public const int ZIP_DIRENT_NAME_SIZE = 28; + public const int ZIP_DIRENT_EXTRAS_SIZE = 30; + public const int ZIP_DIRENT_COMMENT_SIZE = 32; + public const int ZIP_DIRENT_DISKSTART = 34; + public const int ZIP_DIRENT_FILE_TYPE = 36; + public const int ZIP_DIRENT_FILE_MODE = 38; + public const int ZIP_DIRENT_OFFSET = 42; + + public const int ZIP_DIRENT_FLAGS_HAS_DDESC = 8; + + public const char ZIP_NAME_SEPARATOR = '/'; + + public const int ZIP_BLOCK_SIZE = 32768; + public const int ZIP_BUF_SIZE = 512; + + /* z_flags */ + //#define ZZIP_IS_ENCRYPTED(p) ((*(unsigned char*)p)&1) + //#define ZZIP_IS_COMPRLEVEL(p) (((*(unsigned char*)p)>>1)&3) + //#define ZZIP_IS_STREAMED(p) (((*(unsigned char*)p)>>3)&1) + } + + #endregion +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfile.cs b/BurnOutSharp/External/libgsf/Input/GsfInfile.cs new file mode 100644 index 00000000..c6c39e01 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfile.cs @@ -0,0 +1,121 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-infile.c : + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; + +namespace LibGSF.Input +{ + public abstract class GsfInfile : GsfInput + { + #region Functions + + /// + /// Apart from argument types, this is the same as ChildByAName. + /// Please see the documentation there. + /// + /// A null terminated array of names (e.g. from g_strsplit) + /// A newly created child which must be unrefed. + /// New in 1.14.9. + public GsfInput ChildByVariableName(params string[] names) + { + GsfInfile tmp = this; + GsfInput child = this as GsfInput; + foreach (string name in names) + { + Exception err = null; + child = tmp.ChildByName(name, ref err); + if (child == null) + break; + + if (!(child is GsfInfile)) + return null; + + tmp = child as GsfInfile; + } + + return child; + } + + /// + /// This function finds a child that is several directory levels down + /// the tree. If, for example, the names "foo", "bar", and "baz" are + /// given, then this function first finds the "foo" directory in the + /// root infile, then locates "bar" within that directory, and finally + /// locates "baz" within that and returns the "baz" child. In other + /// words, this function finds the "foo/bar/baz" child. + /// + /// A null terminated array of names (e.g. from g_strsplit) + /// A newly created child which must be unrefed. + /// New in 1.14.9. + public GsfInput ChildByIndexedName(string[] names, int namesPtr = 0) + { + GsfInput child = this as GsfInput; + GsfInfile tmp = this; + + if (names == null) + return null; + + for (; namesPtr >= names.Length || names[namesPtr] == null; namesPtr++) + { + Exception err = null; + child = tmp.ChildByName(names[namesPtr], ref err); + if (child == null) + break; + + if (!(child is GsfInfile)) + return null; + + tmp = child as GsfInfile; + } + + return child; + } + + #endregion + + #region Virtual Functions + + /// + /// The number of children the storage has, or -1 if the storage can not + /// have children. + /// + public virtual int NumChildren() => -1; + + /// Zero-based index of child to find. + /// The UTF-8 encoded name of the @i-th child + public virtual string NameByIndex(int i) => null; + + /// Target index + /// A newly created child which must be unrefed. + public virtual GsfInput ChildByIndex(int i, ref Exception error) => null; + + /// + /// The function returns a named child of the given infile. This only + /// works for an immediate child. If you need to go several levels + /// down use ChildByAName, for example. + /// + /// Target name + /// A newly created child which must be unrefed. + public virtual GsfInput ChildByName(string name, ref Exception error) => null; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs new file mode 100644 index 00000000..5a8f3a6a --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs @@ -0,0 +1,963 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-infile-MSOLE.c : + * + * Copyright (C) 2002-2004 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +/* + * [MS-CFB]: Compound File Binary File Format + * http://msdn.microsoft.com/en-us/library/dd942138.aspx + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using static LibGSF.GsfMSOleImpl; + +namespace LibGSF.Input +{ + public class MSOleBAT + { + #region Properties + + public uint[] Block { get; set; } = null; + + public int BlockPointer { get; set; } = 0; + + public int NumBlocks { get; set; } + + #endregion + + #region Constructor + + /// + /// Internal constructor + /// + internal MSOleBAT() { } + + /// + /// Walk the linked list of the supplied block allocation table and build up a + /// table for the list starting in . + /// + /// A meta bat to connect to the raw blocks (small or large) + /// An optional guess as to how many blocks are in the file + /// The first block in the list. + /// Where to store the result. + /// true on error. + public static bool Create(MSOleBAT metabat, int size_guess, uint block, out MSOleBAT res) + { + // NOTE : Only use size as a suggestion, sometimes it is wrong + uint[] bat = new uint[size_guess]; + int batPtr = 0; // bat[0] + byte[] used = new byte[1 + metabat.NumBlocks / 8]; + + while (block < metabat.NumBlocks) + { + // Catch cycles in the bat list + if ((used[block / 8] & (1 << (int)(block & 0x7))) != 0) + break; + + used[block / 8] |= (byte)(1 << (int)(block & 0x7)); + + bat[batPtr++] = block; + block = metabat.Block[block]; + } + + res = new MSOleBAT + { + NumBlocks = bat.Length, + Block = bat, + }; + + if (block != BAT_MAGIC_END_OF_CHAIN) + { + Console.WriteLine("This OLE2 file is invalid.\n" + + $"The Block Allocation Table for one of the streams had {block} instead of a terminator ({BAT_MAGIC_END_OF_CHAIN}).\n" + + "We might still be able to extract some data, but you'll want to check the file."); + } + + return false; + } + + #endregion + + #region Functions + + public void Release() + { + if (Block == null) + return; + + NumBlocks = 0; + Block = null; + BlockPointer = 0; + } + + #endregion + } + + public class MSOleInfo + { + #region Classes + + public class MSOLEInfoPrivateStruct + { + public MSOleBAT Bat { get; set; } + + public int Shift { get; set; } + + public uint Filter { get; set; } + + public int Size { get; set; } + } + + #endregion + + #region Properties + + public MSOLEInfoPrivateStruct BigBlock { get; set; } + + public MSOLEInfoPrivateStruct SmallBlock { get; set; } + + public long MaxBlock { get; set; } + + /// + /// Transition between small and big blocks + /// + public uint Threshold { get; set; } + + public uint SbatStart { get; set; } + + public uint NumSbat { get; set; } + + public MSOLEDirectoryEntry RootDir { get; set; } + + public GsfInput SmallBlockFile { get; set; } + + public int RefCount { get; set; } + + #endregion + + #region Functions + + public void Unref() + { + if (RefCount-- != 1) + return; + + BigBlock.Bat.Release(); + SmallBlock.Bat.Release(); + if (RootDir != null) + RootDir = null; + + if (SmallBlockFile != null) + SmallBlockFile = null; + } + + public MSOleInfo Ref() + { + RefCount++; + return this; + } + + #endregion + } + + public class MSOLEDirectoryEntry + { + #region Properties + + public string Name { get; set; } + + public GsfMSOleSortingKey Key { get; set; } + + public int Index { get; set; } + + public int Size { get; set; } + + public bool UseSmallBlock { get; set; } + + public uint FirstBlock { get; set; } + + public bool IsDirectory { get; set; } + + public List Children { get; set; } + + /// + /// 16 byte GUID used by some apps + /// + public byte[] ClassID { get; set; } = new byte[16]; + + public DateTime? ModTime { get; set; } + + #endregion + + #region Functions + + private void Free() + { + foreach (MSOLEDirectoryEntry child in Children) + { + child.Free(); + } + + Children = null; + } + + #endregion + } + + public class GsfInfileMSOLE : GsfInfile + { + #region Properties + + public GsfInput Input { get; private set; } = null; + + public MSOleInfo Info { get; private set; } = null; + + public MSOLEDirectoryEntry DirectoryEntry { get; private set; } + + public MSOleBAT Bat { get; private set; } + + public long CurBlock { get; private set; } = BAT_MAGIC_UNUSED; + + public byte[] Stream { get; private set; } + + #endregion + + #region Destructor + + /// + /// Destructor + /// + ~GsfInfileMSOLE() + { + if (Input != null) + Input = null; + + if (Info != null && Info.SmallBlockFile != this) + { + Info.Unref(); + Info = null; + } + + Bat.Release(); + Stream = null; + } + + #endregion + + #region Functions + + protected override GsfInput DupImpl(ref Exception err) => (Container as GsfInfileMSOLE).CreateChild(DirectoryEntry, ref err); + + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int optional_buffer_ptr) + { + // Small block files are preload + if (DirectoryEntry != null && DirectoryEntry.UseSmallBlock) + { + if (optional_buffer != null) + { + Array.Copy(Stream, CurrentOffset, optional_buffer, optional_buffer_ptr, num_bytes); + return optional_buffer; + } + + byte[] buffer = new byte[num_bytes]; + Array.Copy(Stream, CurrentOffset, buffer, 0, num_bytes); + return buffer; + } + + // GsfInput guarantees that num_bytes > 0 */ + long first_block = (CurrentOffset >> Info.BigBlock.Shift); + long last_block = ((CurrentOffset + num_bytes - 1) >> Info.BigBlock.Shift); + long offset = CurrentOffset & Info.BigBlock.Filter; + + if (last_block >= Bat.NumBlocks) + return null; + + // Optimization: are all the raw blocks contiguous? + long i = first_block; + uint raw_block = Bat.Block[i]; + while (++i <= last_block && ++raw_block == Bat.Block[i]) ; + + if (i > last_block) + { + if (!SeekBlock(Bat.Block[first_block], offset)) + return null; + + CurBlock = last_block; + return Input.Read(num_bytes, optional_buffer, optional_buffer_ptr); + } + + // Damn, we need to copy it block by block + if (optional_buffer == null) + { + if (Stream.Length < num_bytes) + Stream = new byte[num_bytes]; + + optional_buffer = Stream; + optional_buffer_ptr = 0; + } + + byte[] ptr = optional_buffer; + int ptrPtr = optional_buffer_ptr; // ptr[optional_buffer_ptr + 0] + int count; + for (i = first_block; i <= last_block; i++, ptrPtr += count, num_bytes -= count) + { + count = (int)(Info.BigBlock.Size - offset); + if (count > num_bytes) + count = num_bytes; + + if (!SeekBlock(Bat.Block[i], offset)) + return null; + + if (Input.Read(count, ptr, ptrPtr) == null) + return null; + + offset = 0; + } + + CurBlock = BAT_MAGIC_UNUSED; + + return optional_buffer; + } + + public override bool Seek(long offset, SeekOrigin whence) + { + CurBlock = BAT_MAGIC_UNUSED; + return false; + } + + /// + public override GsfInput ChildByIndex(int i, ref Exception error) + { + foreach (MSOLEDirectoryEntry dirent in DirectoryEntry.Children) + { + if (i-- <= 0) + return CreateChild(dirent, ref error); + } + + return null; + } + + /// + public override string NameByIndex(int i) + { + foreach (MSOLEDirectoryEntry dirent in DirectoryEntry.Children) + { + if (i-- <= 0) + return dirent.Name; + } + + return null; + } + + /// + public override GsfInput ChildByName(string name, ref Exception error) + { + foreach (MSOLEDirectoryEntry dirent in DirectoryEntry.Children) + { + if (dirent.Name != null && dirent.Name == name) + return CreateChild(dirent, ref error); + } + + return null; + } + + /// + public override int NumChildren() + { + if (DirectoryEntry == null) + return -1; + + if (!DirectoryEntry.IsDirectory) + return -1; + + return DirectoryEntry.Children.Count; + } + + /// + /// Opens the root directory of an MS OLE file. + /// + /// GsfInput + /// Optional place to store an error + /// The new ole file handler + /// This adds a reference to . + public static GsfInfile Create(GsfInput source, ref Exception err) + { + GsfInfileMSOLE ole = new GsfInfileMSOLE + { + Input = source, + Size = 0, + }; + + long calling_pos = source.CurrentOffset; + if (ole.InitInfo(ref err)) + { + // We do this so other kinds of archives can be tried. + source.Seek(calling_pos, SeekOrigin.Begin); + return null; + } + + return ole; + } + + /// + /// Retrieves the 16 byte indentifier (often a GUID in MS Windows apps) + /// stored within the directory associated with @ole and stores it in . + /// + /// 16 byte identifier (often a GUID in MS Windows apps) + /// true on success + public bool GetClassID(byte[] res) + { + if (DirectoryEntry == null) + return false; + + Array.Copy(DirectoryEntry.ClassID, res, DirectoryEntry.ClassID.Length); + return true; + } + + #endregion + + #region Utilities + + /// false on error. + private bool SeekBlock(uint block, long offset) + { + if (block >= Info.MaxBlock) + return false; + + // OLE_HEADER_SIZE is fixed at 512, but the sector containing the + // header is padded out to BigBlock.Size (sector size) when BigBlock.Size > 512. + if (Input.Seek(Math.Max(OLE_HEADER_SIZE, Info.BigBlock.Size) + (block << Info.BigBlock.Shift) + offset, SeekOrigin.Begin)) + return false; + + return true; + } + + /// + /// Read a block of data from the underlying input. + /// + /// Block number + /// Optionally null + /// Pointer to the buffer or null if there is an error or 0 bytes are requested. + /// Be really anal. + private byte[] GetBlock(uint block, byte[] buffer) + { + if (!SeekBlock(block, 0)) + return null; + + return Input.Read(Info.BigBlock.Size, buffer); + } + + /// + /// A small utility routine to read a set of references to bat blocks + /// either from the OLE header, or a meta-bat block. + /// + /// A pointer to the element after the last position filled + private uint[] ReadMetabat(uint[] bats, int batsPtr, int max_bat, uint[] metabat, int metabatPtr, int metabat_end) + { + for (; metabatPtr < metabat_end; metabatPtr++) + { + if (metabat[metabatPtr] != BAT_MAGIC_UNUSED) + { + byte[] bat = GetBlock(metabat[metabatPtr], null); + if (bat == null) + return null; + + int batPtr = 0; // bat[0] + int end = batPtr + Info.BigBlock.Size; + for (; batPtr < end; batPtr += BAT_INDEX_SIZE, batsPtr++) + { + bats[batsPtr] = BitConverter.ToUInt32(bat, batPtr); + if (bats[batsPtr] >= max_bat && bats[batsPtr] < BAT_MAGIC_METABAT) + { + Console.Error.WriteLine($"Invalid metabat item {bats[batsPtr]}"); + return null; + } + } + } + else + { + // Looks like something in the wild sometimes creates + // 'unused' entries in the metabat. Let's assume that + // corresponds to lots of unused blocks + // http://bugzilla.gnome.org/show_bug.cgi?id=336858 + uint i = (uint)(Info.BigBlock.Size / BAT_INDEX_SIZE); + while (i-- > 0) + { + bats[batsPtr++] = BAT_MAGIC_UNUSED; + } + } + } + + return bats; + } + + /// + /// Copy some some raw data into an array of uint. + /// + private static void GetUnsignedInts(uint[] dst, int dstPtr, byte[] src, int srcPtr, int num_bytes) + { + for (; (num_bytes -= BAT_INDEX_SIZE) >= 0; srcPtr += BAT_INDEX_SIZE) + { + dst[dstPtr++] = BitConverter.ToUInt32(src, srcPtr); + } + } + + private GsfInput GetSmallBlockFile() + { + if (Info.SmallBlockFile != null) + return Info.SmallBlockFile; + + Exception err = null; + Info.SmallBlockFile = CreateChild(Info.RootDir, ref err); + if (Info.SmallBlockFile == null) + return null; + + // Avoid creating a circular reference + ((GsfInfileMSOLE)Info.SmallBlockFile).Info.Unref(); + + if (Info.SmallBlock.Bat.Block != null) + return null; + + if (MSOleBAT.Create(Info.BigBlock.Bat, (int)Info.NumSbat, Info.SbatStart, out MSOleBAT meta_sbat)) + return null; + + Info.SmallBlock.Bat.NumBlocks = meta_sbat.NumBlocks * (Info.BigBlock.Size / BAT_INDEX_SIZE); + Info.SmallBlock.Bat.Block = new uint[Info.SmallBlock.Bat.NumBlocks]; + ReadMetabat(Info.SmallBlock.Bat.Block, 0, Info.SmallBlock.Bat.NumBlocks, meta_sbat.Block, 0, meta_sbat.NumBlocks); + + return Info.SmallBlockFile; + } + + private static int DirectoryEntryCompare(MSOLEDirectoryEntry a, MSOLEDirectoryEntry b) => SortingKeyCompare(a.Key, b.Key); + + private static DateTime? DateTimeFromFileTime(ulong ft) => ft == 0 ? (DateTime?)null : DateTime.FromFileTime((long)ft); + + private GsfInput CreateChild(MSOLEDirectoryEntry dirent, ref Exception err) + { + GsfInfileMSOLE child = PartiallyDuplicate(ref err); + if (child == null) + return null; + + child.DirectoryEntry = dirent; + child.Size = dirent.Size; + child.ModTime = dirent.ModTime; + + // The root dirent defines the small block file + if (dirent.Index != 0) + { + child.Name = dirent.Name; + child.Container = this; + + if (dirent.IsDirectory) + { + // Be wary. It seems as if some implementations pretend that the + // directories contain data + child.Size = 0; + return child; + } + } + + MSOleInfo info = Info; + + MSOleBAT metabat; + int size_guess; + GsfInput sb_file = null; + + // Build the bat + if (dirent.UseSmallBlock) + { + metabat = info.SmallBlock.Bat; + size_guess = dirent.Size >> (int)info.SmallBlock.Shift; + sb_file = GetSmallBlockFile(); + if (sb_file == null) + { + err = new Exception("Failed to access child"); + return null; + } + } + else + { + metabat = info.BigBlock.Bat; + size_guess = dirent.Size >> (int)info.BigBlock.Shift; + } + + if (MSOleBAT.Create(metabat, size_guess + 1, dirent.FirstBlock, out MSOleBAT tempBat)) + return null; + + child.Bat = tempBat; + + if (dirent.UseSmallBlock) + { + if (sb_file == null) + return null; + + int remaining = dirent.Size; + child.Stream = new byte[remaining]; + + for (uint i = 0; remaining > 0 && i < child.Bat.NumBlocks; i++, remaining -= info.SmallBlock.Size) + { + if (sb_file.Seek(child.Bat.Block[i] << (int)info.SmallBlock.Shift, SeekOrigin.Begin) + || sb_file.Read(Math.Min(remaining, info.SmallBlock.Size), child.Stream, (int)(i << (int)info.SmallBlock.Shift)) == null) + { + Console.Error.WriteLine($"Failure reading block {i} for '{dirent.Name}'"); + err = new Exception("Failure reading block"); + return null; + } + } + + if (remaining > 0) + { + err = new Exception("Insufficient blocks"); + Console.Error.WriteLine($"Small-block file '{dirent.Name}' has insufficient blocks ({child.Bat.NumBlocks}) for the stated size ({dirent.Size})"); + return null; + } + } + + return child; + } + + /// + /// Parse dirent number and recursively handle its siblings and children. + /// parent is optional. + private MSOLEDirectoryEntry CreateDirectoryEntry(uint entry, MSOLEDirectoryEntry parent, bool[] seen_before) + { + if (entry >= DIRENT_MAGIC_END) + return null; + + if (entry > uint.MaxValue / DIRENT_SIZE) + return null; + + uint block = ((entry * DIRENT_SIZE) >> Info.BigBlock.Shift); + if (block >= Bat.NumBlocks) + return null; + + if (seen_before[entry]) + return null; + + seen_before[entry] = true; + + byte[] data = GetBlock(Bat.Block[block], null); + if (data == null) + return null; + + int dataPtr = 0; // data[0] + dataPtr += (int)((DIRENT_SIZE * entry) % Info.BigBlock.Size); + + byte type = data[dataPtr + DIRENT_TYPE]; + if (type != DIRENT_TYPE_DIR && type != DIRENT_TYPE_FILE && type != DIRENT_TYPE_ROOTDIR) + { + Console.Error.WriteLine($"Unknown stream type 0x{type:x}"); + return null; + } + + if (parent == null && type != DIRENT_TYPE_ROOTDIR) + { + // See bug 346118. + Console.Error.WriteLine("Root directory is not marked as such."); + type = DIRENT_TYPE_ROOTDIR; + } + + // It looks like directory (and root directory) sizes are sometimes bogus + uint size = BitConverter.ToUInt32(data, dataPtr + DIRENT_FILE_SIZE); + if (!(type == DIRENT_TYPE_DIR || type == DIRENT_TYPE_ROOTDIR || size <= (uint)Input.Size)) + return null; + + ulong ft = BitConverter.ToUInt64(data, dataPtr + DIRENT_MODIFY_TIME); + + MSOLEDirectoryEntry dirent = new MSOLEDirectoryEntry + { + Index = (int)entry, + Size = (int)size, + ModTime = DateTimeFromFileTime(ft), + }; + + // Store the class id which is 16 byte identifier used by some apps + Array.Copy(data, dataPtr + DIRENT_CLSID, dirent.ClassID, 0, dirent.ClassID.Length); + + // Root dir is always big block + dirent.UseSmallBlock = parent != null && (size < Info.Threshold); + dirent.FirstBlock = BitConverter.ToUInt32(data, dataPtr + DIRENT_FIRSTBLOCK); + dirent.IsDirectory = (type != DIRENT_TYPE_FILE); + dirent.Children = null; + + uint prev = BitConverter.ToUInt32(data, dataPtr + DIRENT_PREV); + uint next = BitConverter.ToUInt32(data, dataPtr + DIRENT_NEXT); + uint child = BitConverter.ToUInt32(data, dataPtr + DIRENT_CHILD); + ushort name_len = BitConverter.ToUInt16(data, dataPtr + DIRENT_NAME_LEN); + + dirent.Name = null; + if (0 < name_len && name_len <= DIRENT_MAX_NAME_SIZE) + { + ushort[] uni_name = new ushort[DIRENT_MAX_NAME_SIZE + 1]; + + // !#%!@$#^ + // Sometimes, rarely, people store the stream name as ascii + // rather than utf16. Do a validation first just in case. + int end = 0; + try { end = new UTF8Encoding(false, true).GetCharCount(data); } + catch { end = -1; } + + if (end == -1 || (end + 1) != name_len) + { + byte[] direntNameBytes = Encoding.Convert(Encoding.ASCII, Encoding.UTF8, data, 0, end); + dirent.Name = Encoding.UTF8.GetString(direntNameBytes); + } + else + { + dirent.Name = Encoding.UTF8.GetString(data, 0, end + 1); + } + } + + // Be really anal in the face of screwups + if (dirent.Name == null) + dirent.Name = string.Empty; + + dirent.Key = GsfMSOleSortingKey.Create(dirent.Name); + + if (parent != null) + { + parent.Children.Add(dirent); + parent.Children.Sort(DirectoryEntryCompare); + } + + // NOTE : These links are a tree, not a linked list + CreateDirectoryEntry(prev, parent, seen_before); + CreateDirectoryEntry(next, parent, seen_before); + + if (dirent.IsDirectory) + CreateDirectoryEntry(child, dirent, seen_before); + else if (child != DIRENT_MAGIC_END) + Console.Error.WriteLine("A non directory stream with children ?"); + + return dirent; + } + + /// + /// Utility routine to _partially_ replicate a file. It does NOT copy the bat + /// blocks, or init the dirent. + /// + private GsfInfileMSOLE PartiallyDuplicate(ref Exception err) + { + GsfInput input = Input.Duplicate(ref err); + if (input == null) + { + err = new Exception("Failed to duplicate input stream"); + return null; + } + + GsfInfileMSOLE dst = new GsfInfileMSOLE(); + dst.Input = input; + dst.Info = Info.Ref(); + // buf and buf_size are initialized to null + + return dst; + } + + /// + /// Read an OLE header and do some sanity checking + /// along the way. + /// + /// true on error setting if it is supplied. + private bool InitInfo(ref Exception err) + { + byte[] header; + + // Check the header + byte[] signature = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 }; + if (Input.Seek(0, SeekOrigin.Begin) + || null == (header = Input.Read(OLE_HEADER_SIZE, null)) + || !header.Take(signature.Length).SequenceEqual(signature)) + { + err = new Exception("No OLE2 signature"); + return true; + } + + ushort bb_shift = BitConverter.ToUInt16(header, OLE_HEADER_BB_SHIFT); + ushort sb_shift = BitConverter.ToUInt16(header, OLE_HEADER_SB_SHIFT); + uint num_bat = BitConverter.ToUInt32(header, OLE_HEADER_NUM_BAT); + uint num_sbat = BitConverter.ToUInt32(header, OLE_HEADER_NUM_SBAT); + uint threshold = BitConverter.ToUInt32(header, OLE_HEADER_THRESHOLD); + uint dirent_start = BitConverter.ToUInt32(header, OLE_HEADER_DIRENT_START); + uint metabat_block = BitConverter.ToUInt32(header, OLE_HEADER_METABAT_BLOCK); + uint num_metabat = BitConverter.ToUInt32(header, OLE_HEADER_NUM_METABAT); + + // Some sanity checks + // 1) There should always be at least 1 BAT block + // 2) It makes no sense to have a block larger than 2^31 for now. + // Maybe relax this later, but not much. + if (6 > bb_shift || bb_shift >= 31 || sb_shift > bb_shift || (Input.Size >> bb_shift) < 1) + { + err = new Exception("Unreasonable block sizes"); + return true; + } + + MSOleInfo info = new MSOleInfo + { + RefCount = 1, + BigBlock = new MSOleInfo.MSOLEInfoPrivateStruct + { + Shift = bb_shift, + Size = 1 << bb_shift, + Filter = (uint)(1 << bb_shift) - 1, + Bat = new MSOleBAT(), + }, + SmallBlock = new MSOleInfo.MSOLEInfoPrivateStruct + { + Shift = sb_shift, + Size = 1 << sb_shift, + Filter = (uint)(1 << sb_shift) - 1, + Bat = new MSOleBAT(), + }, + Threshold = threshold, + SbatStart = BitConverter.ToUInt32(header, OLE_HEADER_SBAT_START), + NumSbat = num_sbat, + MaxBlock = (Input.Size - OLE_HEADER_SIZE + (1 << bb_shift) - 1) / (1 << bb_shift), + SmallBlockFile = null, + }; + + Info = info; + + if (info.NumSbat == 0 && info.SbatStart != BAT_MAGIC_END_OF_CHAIN && info.SbatStart != BAT_MAGIC_UNUSED) + Console.Error.WriteLine("There are not supposed to be any blocks in the small block allocation table, yet there is a link to some. Ignoring it."); + + uint[] metabat = null; + uint last; + uint[] ptr; + + // Very rough heuristic, just in case + if (num_bat < info.MaxBlock && info.NumSbat < info.MaxBlock) + { + info.BigBlock.Bat.NumBlocks = (int)(num_bat * (info.BigBlock.Size / BAT_INDEX_SIZE)); + info.BigBlock.Bat.Block = new uint[info.BigBlock.Bat.NumBlocks]; + + metabat = new uint[Math.Max(info.BigBlock.Size, OLE_HEADER_SIZE)]; + + // Reading the elements invalidates this memory, make copy + GetUnsignedInts(metabat, 0, header, OLE_HEADER_START_BAT, OLE_HEADER_SIZE - OLE_HEADER_START_BAT); + last = num_bat; + if (last > OLE_HEADER_METABAT_SIZE) + last = OLE_HEADER_METABAT_SIZE; + + ptr = ReadMetabat(info.BigBlock.Bat.Block, 0, info.BigBlock.Bat.NumBlocks, metabat, 0, (int)last); + num_bat -= last; + } + else + { + ptr = null; + } + + int ptrPtr = 0; // ptr[0] + last = (uint)((info.BigBlock.Size - BAT_INDEX_SIZE) / BAT_INDEX_SIZE); + while (ptr != null && num_metabat-- > 0) + { + byte[] tmp = GetBlock(metabat_block, null); + if (tmp == null) + { + ptr = null; + break; + } + + // Reading the elements invalidates this memory, make copy + GetUnsignedInts(metabat, 0, tmp, 0, info.BigBlock.Size); + + if (num_metabat == 0) + { + if (last < num_bat) + { + // There should be less that a full metabat block remaining + ptr = null; + break; + } + + last = num_bat; + } + else if (num_metabat > 0) + { + metabat_block = metabat[last]; + if (num_bat < last) + { + // ::num_bat and ::num_metabat are + // inconsistent. There are too many metabats + // for the bat count in the header. + ptr = null; + break; + } + + num_bat -= last; + } + + ptr = ReadMetabat(ptr, ptrPtr, info.BigBlock.Bat.NumBlocks, metabat, 0, (int)last); + } + + bool fail = (ptr == null); + + metabat = ptr = null; + + if (fail) + { + err = new Exception("Inconsistent block allocation table"); + return true; + } + + // Read the directory's bat, we do not know the size + if (MSOleBAT.Create(Info.BigBlock.Bat, 0, dirent_start, out MSOleBAT tempBat)) + { + err = new Exception("Problems making block allocation table"); + return true; + } + + Bat = tempBat; + + // Read the directory + bool[] seen_before = new bool[(Bat.NumBlocks << (int)info.BigBlock.Shift) * DIRENT_SIZE + 1]; + DirectoryEntry = info.RootDir = CreateDirectoryEntry(0, null, seen_before); + if (DirectoryEntry == null) + { + err = new Exception("Problems reading directory"); + return true; + } + + // The spec says to ignore modtime for root object. That doesn't + // keep files from actually have a modtime there. + ModTime = DirectoryEntry.ModTime; + + return false; + } + + internal static int SortingKeyCompare(GsfMSOleSortingKey a, GsfMSOleSortingKey b) + { + long diff; + + // According to the docs length is more important than lexical order + if (a.Length != b.Length) + diff = a.Length - b.Length; + else + diff = a.Name.CompareTo(b.Name); + + // Note, that diff might not fit "int" + return diff > 0 ? +1 : (diff < 0 ? -1 : 0); + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileMSVBA.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileMSVBA.cs new file mode 100644 index 00000000..2a290f1a --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileMSVBA.cs @@ -0,0 +1,338 @@ +/* + * gsf-infile-msvba.c : + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +/* Info extracted from + * svx/source/msfilter/msvbasic.cxx + * Costin Raiu, Kaspersky Labs, 'Apple of Discord' + * Virus bulletin's bontchev.pdf, svajcer.pdf + * + * and lots and lots of reading. There are lots of pieces missing still + * but the structure seems to hold together. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibGSF.Input +{ + public class GsfInfileMSVBA : GsfInfile + { + #region Constants + + /// + /// Magic (2 bytes) + /// Version (4 bytes) + /// 0x00, 0xFF (2 bytes) + /// Unknown (22 bytes) + /// + private const int VBA56_DIRENT_RECORD_COUNT = 2 + 4 + 2 + 22; + + /// + /// VBA56_DIRENT_RECORD_COUNT (30 bytes) + /// Type1 Record Count (2 bytes) + /// Unknown (2 bytes) + /// + private const int VBA56_DIRENT_HEADER_SIZE = VBA56_DIRENT_RECORD_COUNT + 2 + 2; + + #endregion + + #region Properties + + public GsfInfile Source { get; private set; } = null; + + public List Children { get; private set; } = null; + + public Dictionary Modules { get; private set; } = null; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfInfileMSVBA() { } + + public static GsfInfileMSVBA Create(GsfInfile source, ref Exception err) + { + if (source == null) + return null; + + GsfInfileMSVBA vba = new GsfInfileMSVBA + { + Source = source, + }; + + // Find the name offset pairs + if (vba.Read(ref err)) + return vba; + + if (err != null) + err = new Exception("Unable to parse VBA header"); + + return null; + } + + #endregion + + #region Functions + + /// + /// A collection of names and source code which the caller is responsible for destroying. + /// + /// A Dictionary of names and source code (unknown encoding). + public Dictionary StealModules() + { + Dictionary res = Modules; + Modules = null; + return res; + } + + /// + /// A utility routine that attempts to find the VBA file withint a stream. + /// + /// A GsfInfile + public static GsfInfileMSVBA FindVBA(GsfInput input, ref Exception err) + { + GsfInput vba = null; + GsfInfile infile; + + if ((infile = GsfInfileMSOLE.Create(input, ref err)) != null) + { + // 1) Try XLS + vba = infile.ChildByVariableName("_VBA_PROJECT_CUR", "VBA"); + + // 2) DOC + if (null == vba) + vba = infile.ChildByVariableName("Macros", "VBA"); + + // TODO : PPT is more complex + } + else if ((infile = GsfInfileZip.Create(input, ref err)) != null) + { + GsfInput main_part = infile.RelationByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", ref err); + if (main_part != null) + { + GsfInput vba_stream = main_part.RelationByType("http://schemas.microsoft.com/office/2006/relationships/vbaProject", ref err); + if (vba_stream != null) + { + GsfInfile ole = GsfInfileMSOLE.Create(vba_stream, ref err); + if (ole != null) + vba = ole.ChildByVariableName("VBA"); + } + } + } + + if (vba != null) + return Create(vba as GsfInfile, ref err); + + return null; + } + + #endregion + + #region Utilities + + private void ExtractModuleSource(string name, uint src_offset) + { + if (name == null) + return; + + Exception err = null; + GsfInput module = Source.ChildByName(name, ref err); + if (module == null) + return; + + byte[] code = module.InflateMSVBA(src_offset, out _, false); + + if (code != null) + { + if (Modules == null) + Modules = new Dictionary(); + + Modules[name] = code; + } + else + { + Console.Error.WriteLine($"Problems extracting the source for {name} @ {src_offset}"); + } + } + + /// + /// Read an VBA dirctory and its project file. + /// along the way. + /// + /// Place to store an Exception if anything goes wrong + /// false on error setting if it is supplied. + private bool Read(ref Exception err) + { + int element_count = -1; + string name, elem_stream = null; + + // 0. Get the stream + GsfInput dir = Source.ChildByName("dir", ref err); + if (dir == null) + { + err = new Exception("Can't find the VBA directory stream"); + return false; + } + + // 1. Decompress it + byte[] inflated_data = dir.InflateMSVBA(0, out int inflated_size, true); + if (inflated_data == null) + { + err = new Exception("Failed to inflate the VBA directory stream"); + return false; + } + + int ptr = 0; // inflated_data[0] + int end = inflated_size; + + // 2. GUESS : based on several xls with macros and XL8GARY this looks like a + // series of sized records. Be _extra_ careful + ushort tag = 0; + do + { + /* I have seen + * type len data + * 1 4 1 0 0 0 + * 2 4 9 4 0 0 + * 3 2 4 e4 + * 4 project name + * 5 0 + * 6 0 + * 7 4 + * 8 4 + * 0x3d 0 + * 0x40 0 + * 0x14 4 9 4 0 0 + * + * 0x0f == number of elements + * 0x1c == (Size 0) + * 0x1e == (Size 4) + * 0x48 == (Size 0) + * 0x31 == stream offset of the compressed source ! + * + * 0x16 == an ascii dependency name + * 0x3e == a unicode dependency name + * 0x33 == a classid for a dependency with no trialing data + * + * 0x2f == a dummy classid + * 0x30 == a classid + * 0x0d == the classid + * 0x2f, and 0x0d appear contain + * uint32 classid_size; + * + * 00 00 00 00 00 00 + * and sometimes some trailing junk + **/ + + if ((ptr + 6) > end) + { + err = new Exception("VBA project header problem"); + return false; + } + + tag = BitConverter.ToUInt16(inflated_data, ptr); + uint len = BitConverter.ToUInt32(inflated_data, ptr + 2); + + ptr += 6; + if ((ptr + len) > end) + { + err = new Exception("VBA project header problem"); + return false; + } + + switch (tag) + { + case 4: + name = Encoding.UTF8.GetString(inflated_data, ptr, (int)len); + break; + + case 9: + // This seems to have an extra two bytes that are not + // part of the length ..?? + len += 2; + break; + + case 0xf: + if (len != 2) + { + Console.Error.WriteLine("Element count is not what we expected"); + break; + } + + if (element_count >= 0) + { + Console.Error.WriteLine("More than one element count ??"); + break; + } + + element_count = BitConverter.ToUInt16(inflated_data, ptr); + break; + + // Dependencies + case 0x0d: break; + case 0x2f: break; + case 0x30: break; + case 0x33: break; + case 0x3e: break; + case 0x16: + break; + + // Elements + case 0x47: break; + case 0x32: break; + case 0x1a: + break; + + case 0x19: + elem_stream = Encoding.UTF8.GetString(inflated_data, ptr, (int)len); + break; + + case 0x31: + if (len != 4) + { + Console.Error.WriteLine("Source offset property is not what we expected"); + break; + } + + ExtractModuleSource(elem_stream, BitConverter.ToUInt32(inflated_data, ptr)); + elem_stream = null; + element_count--; + break; + + default: + break; + } + + ptr += (int)len; + } while (tag != 0x10); + + if (element_count != 0) + Console.Error.WriteLine("Number of elements differs from expectations"); + + return true; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileStdio.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileStdio.cs new file mode 100644 index 00000000..942ef643 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileStdio.cs @@ -0,0 +1,130 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-infile-stdio.c: read a directory tree + * + * Copyright (C) 2004-2006 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; + +namespace LibGSF.Input +{ + public class GsfInfileStdio : GsfInfile + { + #region Properties + + public string Root { get; set; } = null; + + public List Children { get; set; } = new List(); + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfInfileStdio() { } + + /// In locale dependent encoding + /// Optionally null + /// A new file or null. + public static GsfInfileStdio Create(string root, ref Exception err) + { + if (!Directory.Exists(root)) + return null; + + GsfInfileStdio ifs = new GsfInfileStdio + { + Root = root, + }; + + foreach (string child in Directory.EnumerateFileSystemEntries(root)) + { + ifs.Children.Add(child); + } + + ifs.SetNameFromFilename(root); + return ifs; + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + GsfInfileStdio dst = new GsfInfileStdio + { + Root = this.Root, + }; + + dst.Children.AddRange(Children); + + return dst; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) => null; + + /// + public override string NameByIndex(int i) => i < Children.Count ? Children[i] : null; + + /// + public override GsfInput ChildByIndex(int i, ref Exception error) + { + string name = NameByIndex(i); + return name != null ? OpenChild(name, ref error) : null; + } + + /// + public override GsfInput ChildByName(string name, ref Exception error) + { + for (int i = 0; i < Children.Count; i++) + { + string child = Children[i]; + if (child.Equals(name)) + return OpenChild(name, ref error); + } + + return null; + } + + /// + public override int NumChildren() => Children.Count; + + #endregion + + #region Utilities + + private GsfInput OpenChild(string name, ref Exception err) + { + string path = Path.Combine(Root, name); + + GsfInput child = null; + if (Directory.Exists(path) || File.Exists(path)) + child = Create(path, ref err); + + return child; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileTar.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileTar.cs new file mode 100644 index 00000000..044f379e --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileTar.cs @@ -0,0 +1,487 @@ +/* + * gsf-infile-tar.c : + * + * Copyright (C) 2008 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * TODO: + * symlinks + * hardlinks + * weird headers + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LibGSF.Input +{ + public class TarChild + { + public string Name { get; set; } + + public DateTime? ModTime { get; set; } + + /// + /// The location of data + /// + public long Offset { get; set; } + + public long Length { get; set; } + + /// + /// The directory object, or null for a data file + /// + public GsfInfileTar Dir { get; set; } + } + + /// + /// Tar header from POSIX 1003.1-1990. + /// + public class TarHeader + { + public byte[] Name { get; set; } = new byte[100]; /* 0 */ + + public byte[] Mode { get; set; } = new byte[8]; /* 100 (octal) */ + + public byte[] UID { get; set; } = new byte[8]; /* 108 (octal) */ + + public byte[] GID { get; set; } = new byte[8]; /* 116 (octal) */ + + public byte[] Size { get; set; } = new byte[12]; /* 124 (octal) */ + + public byte[] MTime { get; set; } = new byte[12]; /* 136 (octal) */ + + public byte[] Chksum { get; set; } = new byte[8]; /* 148 (octal) */ + + public byte TypeFlag { get; set; } /* 156 */ + + public byte[] Linkname { get; set; } = new byte[100]; /* 157 */ + + public byte[] Magic { get; set; } = new byte[6]; /* 257 */ + + public byte[] Version { get; set; } = new byte[2]; /* 263 */ + + public byte[] UName { get; set; } = new byte[32]; /* 265 */ + + public byte[] GName { get; set; } = new byte[32]; /* 297 */ + + public byte[] DevMajor { get; set; } = new byte[8]; /* 329 (octal) */ + + public byte[] DevMinor { get; set; } = new byte[8]; /* 337 (octal) */ + + public byte[] Prefix { get; set; } = new byte[155]; /* 345 */ + + public byte[] Filler { get; set; } = new byte[12]; /* 500 */ + } + + public class GsfInfileTar : GsfInfile + { + #region Constants + + private const int HEADER_SIZE = 512; // sizeof(TarHeader); + + private const int BLOCK_SIZE = 512; + + private const string MAGIC_LONGNAME = "././@LongLink"; + + #endregion + + #region Properties + + public GsfInput Source { get; set; } = null; + + public List Children { get; set; } = new List(); + + public Exception Err { get; set; } = null; + + #endregion + + #region Constructor and Deconstructor + + /// + /// Private constructor + /// + private GsfInfileTar() => InitInfo(); + + /// + /// Opens the root directory of a Tar file. + /// + /// A base GsfInput + /// An Exception, optionally null + /// The new tar file handler + /// This adds a reference to . + public static GsfInfileTar Create(GsfInput source, ref Exception err) + { + if (source == null) + return null; + + GsfInfileTar tar = new GsfInfileTar + { + Source = source, + }; + + if (tar.Err != null) + { + err = tar.Err; + return null; + } + + return tar; + } + + /// + /// Destructor + /// + ~GsfInfileTar() + { + Source = null; + Err = null; + Children.Clear(); + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + if (Err != null) + { + err = Err; + return null; + } + + GsfInfileTar res = new GsfInfileTar(); + res.Source = Source; + + for (int i = 0; i < Children.Count; i++) + { + // This copies the structure. + TarChild c = new TarChild + { + Name = Children[i].Name, + ModTime = Children[i].ModTime, + Offset = Children[i].Offset, + Length = Children[i].Length, + Dir = Children[i].Dir, + }; + + res.Children.Add(c); + } + + return null; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) => null; + + /// + public override bool Seek(long offset, SeekOrigin whence) => false; + + /// + public override GsfInput ChildByIndex(int i, ref Exception error) + { + if (error != null) + error = null; + + if (i < 0 || i >= Children.Count) + return null; + + TarChild c = Children[i]; + if (c.Dir != null) + { + return c.Dir; + } + else + { + GsfInputProxy input = GsfInputProxy.Create(Source, c.Offset, c.Length); + input.ModTime = c.ModTime; + input.Name = c.Name; + return input; + } + } + + /// + public override string NameByIndex(int i) + { + if (i < 0 || i >= Children.Count) + return null; + + return Children[i].Name; + } + + /// + public override GsfInput ChildByName(string name, ref Exception error) + { + for (int i = 0; i < Children.Count; i++) + { + TarChild c = Children[i]; + if (name == c.Name) + return ChildByIndex(i, ref error); + } + + return null; + } + + /// + public override int NumChildren() => Children.Count; + + #endregion + + #region Utilities + + private long UnpackOctal(byte[] s, int len) + { + // Different specifications differ on what terminating characters + // are allowed. It doesn't hurt for us to allow both space and + // NUL. + if (len == 0 || (s[len - 1] != 0 && s[len - 1] != ' ')) + { + Err = new Exception("Invalid tar header"); + return 0; + } + + len--; + + long res = 0; + int sPtr = 0; // s[0] + while (len-- != 0) + { + byte c = s[sPtr++]; + if (c < '0' || c > '7') + { + Err = new Exception("Invalid tar header"); + return 0; + } + + res = (res << 3) | (c - '0'); + } + + return res; + } + + private GsfInfileTar CreateDirectory(string name) + { + TarChild c = new TarChild + { + Offset = 0, + Length = 0, + Name = name, + ModTime = null, + Dir = new GsfInfileTar + { + Source = this.Source, + Name = name, + } + }; + + // We set the source here, so gsf_infile_tar_constructor doesn't + // start reading the tarfile recursively. + Children.Add(c); + + return c.Dir; + } + + private GsfInfileTar DirectoryForFile(string name, bool last) + { + GsfInfileTar dir = this; + string s = name; + int sPtr = 0; // s[0] + while (true) + { + int s0 = sPtr; + + // Find a directory component, if any. + while (true) + { + if (s[sPtr] == 0) + { + if (last && sPtr != s0) + break; + else + return dir; + } + + // This is deliberately slash-only. + if (s[sPtr] == '/') + break; + + sPtr++; + } + + string dirname = s.Substring(s0, sPtr - s0); + while (s[sPtr] == '/') + { + sPtr++; + } + + if (dirname != ".") + { + Exception err = null; + GsfInput subdir = ChildByName(dirname, ref err); + if (subdir != null) + dir = subdir is GsfInfileTar ? (GsfInfileTar)subdir : dir; + else + dir = dir.CreateDirectory(dirname); + } + } + } + + /// + /// Read tar headers and do some sanity checking + /// along the way. + /// + private void InitInfo() + { + long pos0 = Source.CurrentOffset; + string pending_longname = null; + + TarHeader header = new TarHeader(); + TarHeader end = new TarHeader(); + + byte[] headerBytes = new byte[HEADER_SIZE]; + byte[] endBytes = new byte[HEADER_SIZE]; + + while (Err == null && (headerBytes = Source.Read(HEADER_SIZE, null)) != null) + { + header = new TarHeader(); + Array.Copy(headerBytes, 0, header.Name, 0, 100); + Array.Copy(headerBytes, 100, header.Mode, 0, 8); + Array.Copy(headerBytes, 108, header.UID, 0, 8); + Array.Copy(headerBytes, 116, header.GID, 0, 8); + Array.Copy(headerBytes, 124, header.Size, 0, 12); + Array.Copy(headerBytes, 136, header.MTime, 0, 12); + Array.Copy(headerBytes, 148, header.Chksum, 0, 8); + header.TypeFlag = headerBytes[156]; + Array.Copy(headerBytes, 157, header.Linkname, 0, 100); + Array.Copy(headerBytes, 257, header.Magic, 0, 6); + Array.Copy(headerBytes, 263, header.Version, 0, 2); + Array.Copy(headerBytes, 265, header.UName, 0, 32); + Array.Copy(headerBytes, 297, header.GName, 0, 32); + Array.Copy(headerBytes, 329, header.DevMajor, 0, 8); + Array.Copy(headerBytes, 337, header.DevMinor, 0, 8); + Array.Copy(headerBytes, 345, header.Prefix, 0, 155); + Array.Copy(headerBytes, 500, header.Filler, 0, 12); + + if (header.Filler.Length == end.Filler.Length && header.Filler.SequenceEqual(end.Filler)) + { + Err = new Exception("Invalid tar header"); + break; + } + + if (headerBytes.SequenceEqual(endBytes)) + break; + + string name; + if (pending_longname != null) + { + name = pending_longname; + pending_longname = null; + } + else + { + name = Encoding.UTF8.GetString(header.Name); + } + + long length = UnpackOctal(header.Size, header.Size.Length); + long offset = Source.CurrentOffset; + + long mtime = UnpackOctal(header.MTime, header.MTime.Length); + + switch (header.TypeFlag) + { + case (byte)'0': + case 0: + { + // Regular file. + GsfInfileTar dir; + int n = 0, s; // name[0] + + // This is deliberately slash-only. + while ((s = name.IndexOf('/', n)) != -1) + { + n = s + 1; + } + + TarChild c = new TarChild + { + Name = name.Substring(n), + ModTime = mtime > 0 ? DateTimeOffset.FromUnixTimeSeconds(mtime).UtcDateTime : (DateTime?)null, + Offset = offset, + Length = length, + Dir = null, + }; + + dir = DirectoryForFile(name, false); + dir.Children.Add(c); + break; + } + case (byte)'5': + { + // Directory + DirectoryForFile(name, true); + break; + } + case (byte)'L': + { + if (pending_longname != null || name != MAGIC_LONGNAME) + { + Err = new Exception("Invalid longname header"); + break; + } + + byte[] n = Source.Read((int)length, null); + if (n == null) + { + Err = new Exception("Failed to read longname"); ; + break; + } + + pending_longname = Encoding.UTF8.GetString(n); + break; + } + default: + // Other -- ignore + break; + } + + // Round up to block size + length = (length + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE; + + if (Err == null && Source.Seek(offset + length, SeekOrigin.Begin)) + { + Err = new Exception("Seek failed"); + break; + } + } + + if (pending_longname != null) + { + if (Err == null) + Err = new Exception("Truncated archive"); + } + + if (Err != null) + Source.Seek(pos0, SeekOrigin.Begin); + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileZip.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileZip.cs new file mode 100644 index 00000000..ec5108f2 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileZip.cs @@ -0,0 +1,789 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-infile-zip.c : + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * Tambet Ingo (tambet@ximian.com) + * Copyright (C) 2014 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ComponentAce.Compression.Libs.zlib; +using static ComponentAce.Compression.Libs.zlib.zlibConst; +using static LibGSF.GsfZipImpl; + +namespace LibGSF.Input +{ + public class ZipInfo + { + #region Properties + + public uint Entries { get; set; } + + public long DirPos { get; set; } + + public List DirectoryEntries { get; set; } + + public GsfZipVDir VDir { get; set; } + + public int RefCount { get; set; } + + #endregion + + #region Functions + + public ZipInfo Ref() + { + RefCount++; + return this; + } + + public void Unref() + { + if (RefCount-- != 1) + return; + + VDir.Free(false); + for (int i = 0; i < DirectoryEntries.Count; i++) + { + GsfZipDirectoryEntry e = DirectoryEntries[i]; + e.Free(); + } + + DirectoryEntries = null; + } + + #endregion + } + + public class GsfInfileZip : GsfInfile + { + #region Properties + + public GsfInput Source { get; set; } = null; + + public ZipInfo Info { get; set; } = null; + + public bool Zip64 { get; set; } = false; + + public GsfZipVDir VDir { get; set; } = null; + + public ZStream Stream { get; set; } = null; + + public long RestLen { get; set; } = 0; + + public long CRestLen { get; set; } = 0; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + public long SeekSkipped { get; set; } = 0; + + public Exception Err { get; set; } = null; + + public GsfInfileZip DupParent { get; set; } + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfInfileZip(GsfInfileZip dupParent = null) + { + DupParent = dupParent; + if (DupParent != null) + { + // Special call from zip_dup. + Exception err = null; + Source = DupParent.Source.Duplicate(ref err); + Err = err; + + Info = DupParent.Info.Ref(); + Zip64 = DupParent.Zip64; + DupParent = null; + } + else + { + if (!InitInfo()) + VDir = Info.VDir; + } + } + + /// + /// Opens the root directory of a Zip file. + /// + /// A base GsfInput + /// Place to store an Exception if anything goes wrong + /// The new zip file handler + /// This adds a reference to . + public static GsfInfileZip Create(GsfInput source, ref Exception err) + { + if (source == null) + return null; + + return new GsfInfileZip + { + Source = source, + }; + } + + /// + /// Destructor + /// + ~GsfInfileZip() + { + if (Info != null) + { + Info.Unref(); + Info = null; + } + + if (Stream != null) + { + Stream.inflateEnd(); + Stream = null; + } + + Buf = null; + + SetSource(null); + Err = null; + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + GsfInfileZip dst = PartiallyDuplicate(ref err); + if (dst == null) + return null; + + dst.VDir = VDir; + + if (dst.VDir.DirectoryEntry != null && dst.ChildInit(ref err)) + return null; + + return dst; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + GsfZipVDir vdir = VDir; + long pos; + + if (RestLen < num_bytes) + return null; + + switch (vdir.DirectoryEntry.CompressionMethod) + { + case GsfZipCompressionMethod.GSF_ZIP_STORED: + RestLen -= num_bytes; + pos = VDir.DirectoryEntry.DataOffset + CurrentOffset; + if (Source.Seek(pos, SeekOrigin.Begin)) + return null; + + return Source.Read(num_bytes, optional_buffer); + + case GsfZipCompressionMethod.GSF_ZIP_DEFLATED: + if (optional_buffer == null) + { + if (BufSize < num_bytes) + { + BufSize = Math.Max(num_bytes, 256); + Buf = new byte[BufSize]; + } + + optional_buffer = Buf; + } + + Stream.avail_out = num_bytes; + Stream.next_out = optional_buffer; + + do + { + int err; + long startlen; + + if (CRestLen > 0 && Stream.avail_in == 0) + if (!UpdateStreamInput()) + break; + + startlen = Stream.total_out; + err = Stream.inflate(Z_NO_FLUSH); + + if (err == Z_STREAM_END) + RestLen = 0; + else if (err == Z_OK) + RestLen -= (Stream.total_out - startlen); + else + return null; // Error, probably corrupted + + } while (RestLen != 0 && Stream.avail_out != 0); + + return optional_buffer; + + default: + break; + } + + return null; + } + + /// + /// Global flag -- we don't want one per stream. + /// + private static bool warned = false; + + /// + public override bool Seek(long offset, SeekOrigin whence) + { + long pos = offset; + + // Note, that pos has already been sanity checked. + switch (whence) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: pos += CurrentOffset; break; + case SeekOrigin.End: pos += Size; break; + default: return true; + } + + if (Stream != null) + { + Stream.inflateEnd(); + Stream = new ZStream(); + } + + Exception err = null; + if (ChildInit(ref err)) + { + Console.Error.WriteLine("Failure initializing zip child"); + return true; + } + + CurrentOffset = 0; + if (SeekEmulate(pos)) + return true; + + SeekSkipped += pos; + if (!warned && SeekSkipped != pos && SeekSkipped >= 1000000) + { + warned = true; + Console.Error.WriteLine("Seeking in zip child streams is awfully slow."); + } + + return false; + } + + /// + public override GsfInput ChildByIndex(int i, ref Exception error) + { + GsfZipVDir child_vdir = VDir.ChildByIndex(i); + if (child_vdir != null) + return NewChild(child_vdir, ref error); + + return null; + } + + /// + public override string NameByIndex(int i) + { + GsfZipVDir child_vdir = VDir.ChildByIndex(i); + if (child_vdir != null) + return child_vdir.Name; + + return null; + } + + /// + public override GsfInput ChildByName(string name, ref Exception error) + { + GsfZipVDir child_vdir = VDir.ChildByName(name); + if (child_vdir != null) + return NewChild(child_vdir, ref error); + + return null; + } + + /// + public override int NumChildren() + { + if (VDir == null) + return -1; + + if (!VDir.IsDirectory) + return -1; + + return VDir.Children.Count; + } + + #endregion + + #region Utilities + + private static DateTime? MakeModTime(uint dostime) + { + if (dostime == 0) + { + return null; + } + else + { + int year = (int)(dostime >> 25) + 1980; + int month = (int)(dostime >> 21) & 0x0f; + int day = (int)(dostime >> 16) & 0x1f; + int hour = (int)(dostime >> 11) & 0x0f; + int minute = (int)(dostime >> 5) & 0x3f; + int second = (int)(dostime & 0x1f) * 2; + + DateTime modtime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc); + + return modtime; + } + } + + private long FindTrailer(uint sig, int size) + { + byte sig1 = (byte)(sig & 0xff); + + long filesize = Source.Size; + if (filesize < size) + return -1; + + long trailer_offset = filesize; + long maplen = filesize & (ZIP_BUF_SIZE - 1); + if (maplen == 0) + maplen = ZIP_BUF_SIZE; + + long offset = filesize - maplen; /* offset is now BUFSIZ aligned */ + + while (true) + { + if (Source.Seek(offset, SeekOrigin.Begin)) + return -1; + + byte[] data = Source.Read((int)maplen, null); + if (data == null) + return -1; + + int p = 0; // data[0] + + for (int s = (int)(p + maplen - 1); (s >= p); s--, trailer_offset--) + { + if (data[s] == sig1 && p + maplen - 1 - s > size - 2 && BitConverter.ToUInt32(data, s) == sig) + return --trailer_offset; + } + + // Not found in currently mapped block, so update it if + // there is some room in before. The requirements are.. + // (a) mappings should overlap so that trailer can cross BUFSIZ-boundary + // (b) trailer cannot be farther away than 64K from fileend + + // Outer loop cond + if (offset <= 0) + return -1; + + // Outer loop step + offset -= ZIP_BUF_SIZE / 2; + maplen = Math.Min(filesize - offset, ZIP_BUF_SIZE); + trailer_offset = offset + maplen; + + if (filesize - offset > 64 * 1024) + return -1; + } + } + + private static byte[] DirectoryEntryExtraField(byte[] extra, int extraPtr, int elen, ExtraFieldTags typ, out uint pflen) + { + while (true) + { + if (elen == 0) + { + pflen = 0; + return null; + } + + if (elen < 4) + { + pflen = 0; + return null; + } + + ExtraFieldTags ftyp = (ExtraFieldTags)BitConverter.ToUInt16(extra, extraPtr); + uint flen = BitConverter.ToUInt16(extra, extraPtr + 2); + if (flen > elen - 4) + { + pflen = 0; + return null; + } + + extraPtr += 4; + elen -= 4; + if (ftyp == typ) + { + // Found the extended data. + pflen = flen; + return extra; + } + + extraPtr += (int)flen; + elen -= (int)flen; + } + } + + private GsfZipDirectoryEntry NewDirectoryEntry(ref long offset) + { + byte[] header = new byte[ZIP_DIRENT_SIZE]; + + // Read fixed-length part of data and check the header + byte[] data = header; + if (Source.Seek(offset, SeekOrigin.Begin) + || Source.Read(ZIP_DIRENT_SIZE, header) != null + || BitConverter.ToUInt32(data, 0) != ZIP_DIRENT_SIGNATURE) + { + return null; + } + + ushort name_len = BitConverter.ToUInt16(header, ZIP_DIRENT_NAME_SIZE); + ushort extras_len = BitConverter.ToUInt16(header, ZIP_DIRENT_EXTRAS_SIZE); + ushort comment_len = BitConverter.ToUInt16(header, ZIP_DIRENT_COMMENT_SIZE); + int vlen = name_len + extras_len + comment_len; + + // Read variable part + byte[] variable = Source.Read(vlen, null); + if (variable == null && vlen > 0) + return null; + + byte[] extra = DirectoryEntryExtraField(variable, name_len, extras_len, ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_ZIP64, out uint elen); + int extraPtr = 0; // extra[0]; + bool zip64 = (extra != null); + + uint flags = BitConverter.ToUInt32(header, ZIP_DIRENT_FLAGS); + GsfZipCompressionMethod compression_method = (GsfZipCompressionMethod)BitConverter.ToUInt16(header, ZIP_DIRENT_COMPR_METHOD); + uint dostime = BitConverter.ToUInt32(header, ZIP_DIRENT_DOSTIME); + uint crc32 = BitConverter.ToUInt32(header, ZIP_DIRENT_CRC32); + long csize = BitConverter.ToUInt32(header, ZIP_DIRENT_CSIZE); + long usize = BitConverter.ToUInt32(header, ZIP_DIRENT_USIZE); + long off = BitConverter.ToUInt32(header, ZIP_DIRENT_OFFSET); + uint disk_start = BitConverter.ToUInt16(header, ZIP_DIRENT_DISKSTART); + + if (usize == 0xffffffffu && elen >= 8) + { + usize = (long)BitConverter.ToUInt64(extra, extraPtr); + extraPtr += 8; + elen -= 8; + } + if (csize == 0xffffffffu && elen >= 8) + { + csize = (long)BitConverter.ToUInt64(extra, extraPtr); + extraPtr += 8; + elen -= 8; + } + if (off == 0xffffffffu && elen >= 8) + { + off = (long)BitConverter.ToUInt64(extra, extraPtr); + extraPtr += 8; + elen -= 8; + } + if (disk_start == 0xffffu && elen >= 4) + { + disk_start = BitConverter.ToUInt32(extra, extraPtr); + extraPtr += 4; + elen -= 4; + } + + byte[] name = new byte[name_len + 1]; + Array.Copy(variable, name, name_len); + name[name_len] = 0x00; + + GsfZipDirectoryEntry dirent = GsfZipDirectoryEntry.Create(); + dirent.Name = Encoding.UTF8.GetString(name); + + dirent.Flags = (int)flags; + dirent.CompressionMethod = compression_method; + dirent.CRC32 = crc32; + dirent.CompressedSize = csize; + dirent.UncompressedSize = usize; + dirent.Offset = off; + dirent.DosTime = dostime; + dirent.Zip64 = zip64; + + offset += ZIP_DIRENT_SIZE + vlen; + + return dirent; + } + + /// + /// Returns a partial duplicate. + /// + private GsfInfileZip PartiallyDuplicate(ref Exception err) + { + GsfInfileZip dst = new GsfInfileZip(this); + + if (dst.Err != null) + { + err = dst.Err; + return null; + } + + return dst; + } + + /// + /// Read zip headers and do some sanity checking + /// along the way. + /// + /// true on error setting Err. + private bool ReadDirectoryEntries() + { + // Find and check the trailing header + long offset = FindTrailer(ZIP_TRAILER_SIGNATURE, ZIP_TRAILER_SIZE); + if (offset < ZIP_ZIP64_LOCATOR_SIZE || Source.Seek(offset - ZIP_ZIP64_LOCATOR_SIZE, SeekOrigin.Begin)) + { + Err = new Exception("Broken zip file structure"); + return true; + } + + byte[] locator = Source.Read(ZIP_TRAILER_SIZE + ZIP_ZIP64_LOCATOR_SIZE, null); + if (locator == null) + { + Err = new Exception("Broken zip file structure"); + return true; + } + + int data = ZIP_ZIP64_LOCATOR_SIZE; // locator + ZIP_ZIP64_LOCATOR_SIZE + + ulong entries = BitConverter.ToUInt16(locator, data + ZIP_TRAILER_ENTRIES); + ulong dir_pos = BitConverter.ToUInt32(locator, data + ZIP_TRAILER_DIR_POS); + + if (BitConverter.ToUInt32(locator, 0) == ZIP_ZIP64_LOCATOR_SIGNATURE) + { + Zip64 = true; + + data = 0; // locator[0] + uint disk = BitConverter.ToUInt32(locator, data + ZIP_ZIP64_LOCATOR_DISK); + ulong zip64_eod_offset = BitConverter.ToUInt64(locator, data + ZIP_ZIP64_LOCATOR_OFFSET); + uint disks = BitConverter.ToUInt32(locator, data + ZIP_ZIP64_LOCATOR_DISKS); + + if (disk != 0 || disks != 1) + { + Err = new Exception("Broken zip file structure"); + return true; + } + + if (Source.Seek((int)zip64_eod_offset, SeekOrigin.Begin)) + { + Err = new Exception("Broken zip file structure"); + return true; + } + + locator = Source.Read(ZIP_TRAILER64_SIZE, null); + if (locator == null || BitConverter.ToUInt32(locator, data) != ZIP_TRAILER64_SIGNATURE) + { + Err = new Exception("Broken zip file structure"); + return true; + } + + entries = BitConverter.ToUInt64(locator, data + ZIP_TRAILER64_ENTRIES); + dir_pos = BitConverter.ToUInt64(locator, data + ZIP_TRAILER64_DIR_POS); + } + + Info = new ZipInfo() + { + DirectoryEntries = new List(), + RefCount = 1, + Entries = (uint)entries, + DirPos = (long)dir_pos, + }; + + // Read the directory + uint i = 0; + for (offset = (long)dir_pos; i < entries; i++) + { + GsfZipDirectoryEntry d = NewDirectoryEntry(ref offset); + if (d == null) + { + Err = new Exception("Error reading zip dirent"); + return true; + } + + Info.DirectoryEntries.Add(d); + } + + return false; + } + + private void BuildVirtualDirectories() + { + Info.VDir = GsfZipVDir.Create("", true, null); + for (int i = 0; i < Info.DirectoryEntries.Count; i++) + { + GsfZipDirectoryEntry dirent = Info.DirectoryEntries[i]; + Info.VDir.Insert(dirent.Name, dirent); + } + } + + /// + /// Read zip headers and do some sanity checking + /// along the way. + /// + /// true on error setting Err. + private bool InitInfo() + { + bool ret = ReadDirectoryEntries(); + if (ret != false) + return ret; + + BuildVirtualDirectories(); + + return false; + } + + /// + /// Returns true on error + /// + private bool ChildInit(ref Exception errmsg) + { + byte[] data = null; + + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + + // Skip local header + // Should test tons of other info, but trust that those are correct + + string err = null; + if (Source.Seek(dirent.Offset, SeekOrigin.Begin)) + { + err = "Error seeking to zip header"; + } + else if ((data = Source.Read(ZIP_HEADER_SIZE, null)) == null) + { + err = "Error reading zip header"; + } + else if (BitConverter.ToUInt32(data, 0) != ZIP_HEADER_SIGNATURE) + { + err = "Error incorrect zip header"; + Console.Error.WriteLine("Header is 0x%x\n", BitConverter.ToUInt32(data, 0)); + Console.Error.WriteLine("Expected 0x%x\n", ZIP_HEADER_SIGNATURE); + } + + if (err != null) + { + errmsg = new Exception(err); + return true; + } + + uint name_len = BitConverter.ToUInt16(data, ZIP_HEADER_NAME_SIZE); + uint extras_len = BitConverter.ToUInt16(data, ZIP_HEADER_EXTRAS_SIZE); + + dirent.DataOffset = dirent.Offset + ZIP_HEADER_SIZE + name_len + extras_len; + RestLen = dirent.UncompressedSize; + CRestLen = dirent.CompressedSize; + + if (dirent.CompressionMethod != GsfZipCompressionMethod.GSF_ZIP_STORED) + { + int errno; + + if (Stream == null) + Stream = new ZStream(); + + errno = Stream.inflateInit(-15); + if (errno != Z_OK) + { + errmsg = new Exception("Problem uncompressing stream"); + return true; + } + } + + return false; + } + + private bool UpdateStreamInput() + { + if (CRestLen == 0) + return false; + + uint read_now = (uint)Math.Min(CRestLen, ZIP_BLOCK_SIZE); + + long pos = VDir.DirectoryEntry.DataOffset + Stream.total_in; + if (Source.Seek(pos, SeekOrigin.Begin)) + return false; + + byte[] data = Source.Read((int)read_now, null); + if (data == null) + return false; + + CRestLen -= read_now; + Stream.next_in = data; /* next input byte */ + Stream.avail_in = (int)read_now; /* number of bytes available at next_in */ + + return true; + } + + private GsfInput NewChild(GsfZipVDir vdir, ref Exception err) + { + GsfZipDirectoryEntry dirent = vdir.DirectoryEntry; + GsfInfileZip child = PartiallyDuplicate(ref err); + + if (child == null) + return null; + + child.Name = vdir.Name; + child.Container = this; + child.VDir = vdir; + + if (dirent != null) + { + child.Size = dirent.UncompressedSize; + if (dirent.DosTime != 0) + { + DateTime? modtime = MakeModTime(dirent.DosTime); + child.ModTime = modtime; + } + + if (child.ChildInit(ref err) != false) + return null; + } + else + { + child.Size = 0; + } + + return child; + } + + private void SetSource(GsfInput src) + { + if (src != null) + src = GsfInputProxy.Create(src); + + Source = src; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInput.cs b/BurnOutSharp/External/libgsf/Input/GsfInput.cs new file mode 100644 index 00000000..c6a197cf --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInput.cs @@ -0,0 +1,529 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input.c: interface for used by the ole layer to read raw data + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using LibGSF.Output; +using static LibGSF.GsfMSOleUtils; + +namespace LibGSF.Input +{ + public abstract class GsfInput : IDisposable + { + #region Constants + + private const int GSF_READ_BUFSIZE = 1024 * 4; + + #endregion + + #region Properties + + public long Size { get; protected internal set; } + + public long CurrentOffset { get; protected internal set; } + + public string Name { get; protected internal set; } + + public GsfInfile Container { get; protected internal set; } + + public DateTime? ModTime { get; protected internal set; } + + public GsfOpenPkgRels Relations { get; protected internal set; } + + #endregion + + #region Functions + + public void Dispose() + { + Container = null; + Name = null; + ModTime = default; + } + + public void Init() + { + Size = 0; + CurrentOffset = 0; + Name = null; + Container = null; + } + + /// Place to store an Exception if anything goes wrong + /// The duplicate + public GsfInput Duplicate(ref Exception err) + { + GsfInput dst = DupImpl(ref err); + if (dst != null) + { + if (dst.Size != Size) + { + err = new Exception("Duplicate size mismatch"); + return null; + } + + if (dst.Seek(CurrentOffset, SeekOrigin.Begin)) + { + err = new Exception("Seek failed"); + return null; + } + + dst.Name = Name; + dst.Container = Container; + } + + return dst; + } + + /// + /// Attempts to open a 'sibling' of input. The caller is responsible for + /// managing the resulting object. + /// + /// Name. + /// Place to store an Exception if anything goes wrong + /// A related GsfInput + /// + /// UNIMPLEMENTED BY ANY BACKEND + /// and it is probably unnecessary. Container provides + /// enough power to do what is necessary. + /// + public GsfInput OpenSibling(string name, ref Exception err) => OpenSiblingImpl(name, ref err); + + /// + /// Are we at the end of the file? + /// + /// true if the input is at the eof. + public bool EOF() => CurrentOffset >= Size; + + /// + /// Read at least . Does not change the current position if there + /// is an error. Will only read if the entire amount can be read. Invalidates + /// the buffer associated with previous calls to gsf_input_read. + /// + /// Number of bytes to read + /// Pointer to destination memory area + /// + /// Pointer to the buffer or null if there is + /// an error or 0 bytes are requested. + /// + public byte[] Read(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + long newpos = CurrentOffset + num_bytes; + + if (newpos <= CurrentOffset || newpos > Size) + return null; + + byte[] res = ReadImpl(num_bytes, optional_buffer, bufferPtr); + if (res == null) + return null; + + CurrentOffset = newpos; + return res; + } + + /// + /// Read . Does not change the current position if there + /// is an error. Will only read if the entire amount can be read. + /// + /// Number of bytes to read + /// Copy of + /// The data read. + public byte[] Read0(int num_bytes, out int bytes_read) + { + bytes_read = num_bytes; + + if (num_bytes < 0 || (long)num_bytes > Remaining()) + return null; + + byte[] res = new byte[num_bytes]; + if (Read(num_bytes, res) != null) + return res; + + return null; + } + + /// The number of bytes left in the file. + public long Remaining() => Size - CurrentOffset; + + /// + /// Move the current location in the input stream. + /// + /// Target offset + /// + /// Determines whether the offset is relative to the beginning or + /// the end of the stream, or to the current location. + /// + /// true on error. + public virtual bool Seek(long offset, SeekOrigin whence) + { + long pos = offset; + + switch (whence) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: pos += CurrentOffset; break; + case SeekOrigin.End: pos += Size; break; + default: return true; + } + + if (pos < 0 || pos > Size) + return true; + + // If we go nowhere, just return. This in particular handles null + // seeks for streams with no seek method. + if (pos == CurrentOffset) + return false; + + if (Seek(offset, whence)) + return true; + + CurrentOffset = pos; + return false; + } + + /// The (fs-sys encoded) filename + /// true if the assignment was ok. + public bool SetNameFromFilename(string filename) + { + string name = null; + if (filename != null) + { + byte[] filenameBytes = Encoding.Unicode.GetBytes(filename); + filenameBytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, filenameBytes); + name = Encoding.UTF8.GetString(filenameBytes); + } + + Name = name; + return true; + } + + /// + /// Emulate forward seeks by reading. + /// + /// Absolute position to seek to + /// true if the emulation failed. + public bool SeekEmulate(long pos) + { + if (pos < CurrentOffset) + return true; + + while (pos > CurrentOffset) + { + long readcount = Math.Min(pos - CurrentOffset, 8192); + if (Read((int)readcount, null) == null) + return true; + } + + return false; + } + + /// + /// Copy the contents from input to from their respective + /// current positions. So if you want to be sure to copy *everything*, + /// make sure to call input.Seek(0, SeekOrigin.Begin) and + /// output.Seek(0, SeekOrigin.Begin) first, if applicable. + /// + /// A non-null GsfOutput + /// true on success + public bool Copy(GsfOutput output) + { + if (output == null) + return false; + + bool success = true; + long remaining; + while (success && (remaining = Remaining()) > 0) + { + long toread = Math.Min(remaining, GSF_READ_BUFSIZE); + byte[] buffer = Read((int)toread, null); + if (buffer != null && buffer.Length != 0) + success = output.Write((int)toread, buffer); + else + success = false; + } + + return success; + } + + /// + /// This functions takes ownership of the incoming reference and yields a + /// new one as its output. + /// + /// + /// A stream equivalent to the source stream, + /// but uncompressed if the source was compressed. + /// + public GsfInput Uncompress() + { + long cur_offset = CurrentOffset; + byte[] header = new byte[4]; + + if (Seek(0, SeekOrigin.Begin)) + goto error; + + // Read header up front, so we avoid extra seeks in tests. + if (Read(4, header) == null) + goto error; + + // Let's try gzip. + { + byte[] gzip_sig = new byte[2] { 0x1f, 0x8b }; + if (header.Take(2).SequenceEqual(gzip_sig)) + { + Exception err = null; + GsfInput res = GsfInputGZip.Create(null, ref err); + if (res != null) + return res.Uncompress(); + } + } + + // Let's try bzip. + { + byte[] bzip_sig = new byte[3] { (byte)'B', (byte)'Z', (byte)'h' }; + if (header.Take(3).SequenceEqual(bzip_sig)) + { + Exception err = null; + GsfInput res = GsfInputMemory.CreateFromBzip(null, ref err); + if (res != null) + return res.Uncompress(); + } + } + + // Other methods go here. + + error: + Seek(cur_offset, SeekOrigin.Begin); + return this; + } + + #endregion + + #region GIO + + internal void SetNameFromFile(string file) + { + if (!File.Exists(file)) + return; + + FileInfo info = new FileInfo(file); + SetNameFromFilename(info.Name); + } + + #endregion + + #region MS-OLE + + /// + /// Decompresses an LZ compressed stream. + /// + /// Offset into it for start byte of compresse stream + /// A GByteArray that the caller is responsible for freeing + internal byte[] InflateMSOLE(long offset) + { + uint pos = 0; + uint shift; + byte[] flag = new byte[1]; + byte[] buffer = new byte[VBA_COMPRESSION_WINDOW]; + byte[] tmp = new byte[2]; + bool clean = true; + + if (Seek(offset, SeekOrigin.Begin)) + return null; + + byte[] res = new byte[0]; + + // Explaination from libole2/ms-ole-vba.c + + // The first byte is a flag byte. Each bit in this byte + // determines what the next byte is. If the bit is zero, + // the next byte is a character. Otherwise the next two + // bytes contain the number of characters to copy from the + // umcompresed buffer and where to copy them from (offset, + // length). + + while (Read(1, flag) != null) + { + for (uint mask = 1; mask < 0x100; mask <<= 1) + { + if ((flag[0] & mask) != 0) + { + if ((tmp = Read(2, null)) == null) + break; + + uint win_pos = pos % VBA_COMPRESSION_WINDOW; + if (win_pos <= 0x80) + { + if (win_pos <= 0x20) + shift = (uint)((win_pos <= 0x10) ? 12 : 11); + else + shift = (uint)((win_pos <= 0x40) ? 10 : 9); + } + else + { + if (win_pos <= 0x200) + shift = (uint)((win_pos <= 0x100) ? 8 : 7); + else if (win_pos <= 0x800) + shift = (uint)((win_pos <= 0x400) ? 6 : 5); + else + shift = 4; + } + + ushort token = BitConverter.ToUInt16(tmp, 0); + ushort len = (ushort)((token & ((1 << (int)shift) - 1)) + 3); + uint distance = (uint)(token >> (int)shift); + clean = true; + + if (distance >= pos) + { + Console.Error.WriteLine("Corrupted compressed stream"); + break; + } + + for (uint i = 0; i < len; i++) + { + uint srcpos = (pos - distance - 1) % VBA_COMPRESSION_WINDOW; + byte c = buffer[srcpos]; + buffer[pos++ % VBA_COMPRESSION_WINDOW] = c; + } + } + else + { + if ((pos != 0) && ((pos % VBA_COMPRESSION_WINDOW) == 0) && clean) + { + Read(2, null); + clean = false; + + List temp = new List(res); + temp.AddRange(buffer); + res = temp.ToArray(); + break; + } + + if (Read(1, buffer, (int)(pos % VBA_COMPRESSION_WINDOW)) != null) + pos++; + + clean = true; + } + } + } + + if ((pos % VBA_COMPRESSION_WINDOW) != 0) + { + List temp = new List(res); + temp.AddRange(buffer.Skip((int)(pos % VBA_COMPRESSION_WINDOW))); + res = temp.ToArray(); + } + + return res; + } + + #endregion + + #region MS-VBA + + /// + /// Decompresses VBA stream. + /// + /// Stream to read from + /// Offset into it for start byte of compressed stream + /// Size of the returned array + /// Whenever add or not null at the end of array + /// A pointer to byte array + internal byte[] InflateMSVBA(long offset, out int size, bool add_null_terminator) + { + size = 0; + + byte[] sig = new byte[1]; + Read(1, sig); + if (sig[0] != 1) // Should start with 0x01 + return null; + + offset++; + + List res = new List(); + + long length = Size; + while (offset < length) + { + byte[] tmp = Read(2, null); + if (tmp == null) + break; + + ushort chunk_hdr = BitConverter.ToUInt16(tmp, 0); + offset += 2; + + GsfInput chunk; + if (0xB000 == (chunk_hdr & 0xF000) && (chunk_hdr & 0xFFF) > 0 && (length - offset < 4094)) + { + if (length < offset + (chunk_hdr & 0xFFF)) + break; + + chunk = GsfInputProxy.Create(this, offset, (long)(chunk_hdr & 0xFFF) + 1); + offset += (chunk_hdr & 0xFFF) + 1; + } + else + { + if (length < offset + 4094) + { + chunk = GsfInputProxy.Create(this, offset, length - offset); + offset = length; + } + else + { + chunk = GsfInputProxy.Create(this, offset, 4094); + offset += 4094; + } + } + + if (chunk != null) + { + byte[] tmpres = chunk.InflateMSOLE(0); + Seek(offset, SeekOrigin.Current); + res.AddRange(tmpres); + } + } + + if (res == null) + return null; + if (add_null_terminator) + res.Add(0x00); + + size = res.Count; + + return res.ToArray(); + } + + #endregion + + #region Virtual Functions + + protected virtual GsfInput DupImpl(ref Exception err) => null; + + protected virtual GsfInput OpenSiblingImpl(string name, ref Exception err) => null; + + protected virtual byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) => null; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputBzip.cs b/BurnOutSharp/External/libgsf/Input/GsfInputBzip.cs new file mode 100644 index 00000000..0665e516 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputBzip.cs @@ -0,0 +1,140 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-iochannel.c: wrapper for glib's GIOChannel + * + * Copyright (C) 2003-2006 Dom Lachowicz (cinamod@hotmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; + +namespace LibGSF.Input +{ + public partial class GsfInputMemory + { + #region Constants + + private const int BZ_BUFSIZ = 1024; + + #region bzlib.h + + private const int BZ_RUN = 0; + private const int BZ_FLUSH = 1; + private const int BZ_FINISH = 2; + + private const int BZ_OK = 0; + private const int BZ_RUN_OK = 1; + private const int BZ_FLUSH_OK = 2; + private const int BZ_FINISH_OK = 3; + private const int BZ_STREAM_END = 4; + private const int BZ_SEQUENCE_ERROR = (-1); + private const int BZ_PARAM_ERROR = (-2); + private const int BZ_MEM_ERROR = (-3); + private const int BZ_DATA_ERROR = (-4); + private const int BZ_DATA_ERROR_MAGIC = (-5); + private const int BZ_IO_ERROR = (-6); + private const int BZ_UNEXPECTED_EOF = (-7); + private const int BZ_OUTBUFF_FULL = (-8); + private const int BZ_CONFIG_ERROR = (-9); + + #endregion + + #endregion + + // TODO: Implement BZIP reading + + #region Constructor + + /// Place to store an Exception if anything goes wrong + /// A new GsfInputMemory or null. + public static GsfInputMemory CreateFromBzip(GsfInput source, ref Exception err) + { +#if BZIP2 + bz_stream bzstm; + GsfInput* mem = NULL; + GsfOutput* sink = NULL; + guint8 out_buf[BZ_BUFSIZ]; + int bzerr = BZ_OK; + + g_return_val_if_fail(source != NULL, NULL); + + memset(&bzstm, 0, sizeof(bzstm)); + if (BZ_OK != BZ2_bzDecompressInit(&bzstm, 0, 0)) + { + if (err) + *err = g_error_new(gsf_input_error_id(), 0, + _("BZ2 decompress init failed")); + return NULL; + } + + sink = gsf_output_memory_new(); + + for (; ; ) + { + bzstm.next_out = (char*)out_buf; + bzstm.avail_out = (unsigned int)sizeof(out_buf); + + if (bzstm.avail_in == 0) + { + bzstm.avail_in = (unsigned int)MIN(gsf_input_remaining(source), BZ_BUFSIZ); + bzstm.next_in = (char*)gsf_input_read(source, bzstm.avail_in, NULL); + } + + bzerr = BZ2_bzDecompress(&bzstm); + if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) + { + if (err) + *err = g_error_new(gsf_input_error_id(), 0, + _("BZ2 decompress failed")); + BZ2_bzDecompressEnd(&bzstm); + gsf_output_close(sink); + g_object_unref(sink); + return NULL; + } + + gsf_output_write(sink, BZ_BUFSIZ - bzstm.avail_out, out_buf); + if (bzerr == BZ_STREAM_END) + break; + } + + gsf_output_close(sink); + + if (BZ_OK != BZ2_bzDecompressEnd(&bzstm)) { + if (err) + * err = g_error_new(gsf_input_error_id(), 0, + _("BZ2 decompress end failed")); + g_object_unref(sink); + return NULL; + } + + mem = gsf_input_memory_new_clone( + gsf_output_memory_get_bytes (GSF_OUTPUT_MEMORY (sink)), + gsf_output_size(sink)); + + if (mem != NULL) + gsf_input_set_name(mem, gsf_input_name (source)); + + g_object_unref(sink); + return mem; +#else + err = new Exception("BZ2 support not enabled"); + return null; +#endif + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputGZip.cs b/BurnOutSharp/External/libgsf/Input/GsfInputGZip.cs new file mode 100644 index 00000000..4aca363f --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputGZip.cs @@ -0,0 +1,443 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-gzip.c: wrapper to uncompress gzipped input + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * Copyright (C) 2005-2006 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using ComponentAce.Compression.Libs.zlib; +using static ComponentAce.Compression.Libs.zlib.zlibConst; + +namespace LibGSF.Input +{ + public class GsfInputGZip : GsfInput + { + #region Enums + + [Flags] + private enum GZIP_HEADER_FLAGS : byte + { + /// + /// File contains text ? + /// + GZIP_IS_ASCII = 0x01, + + /// + /// There is a CRC in the header + /// + GZIP_HEADER_CRC = 0x02, + + /// + /// There is an 'extra' field + /// + GZIP_EXTRA_FIELD = 0x04, + + /// + /// The original is stored + /// + GZIP_ORIGINAL_NAME = 0x08, + + /// + /// There is a comment in the header + /// + GZIP_HAS_COMMENT = 0x10, + } + + #endregion + + #region Properties + + /// + /// Compressed data + /// + public GsfInput Source { get; set; } = null; + + /// + /// No header and no trailer. + /// + public bool Raw { get; set; } = false; + + public Exception Err { get; set; } = null; + + public long UncompressedSize { get; set; } = -1; + + public bool StopByteAdded { get; set; } + + public ZStream Stream { get; set; } = new ZStream(); + + public byte[] GZippedData { get; set; } + + /// + /// CRC32 of uncompressed data + /// + public ulong CRC { get; set; } = 0; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + public long HeaderSize { get; set; } + + public long TrailerSize { get; set; } + + public long SeekSkipped { get; set; } = 0; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfInputGZip(GsfInput source = null, bool raw = false, long uncompressedSize = -1) + { + Source = source; + Raw = raw; + UncompressedSize = uncompressedSize; + + Exception tempErr = null; + if (Source == null) + Err = new Exception("Null source"); + else if (Raw && UncompressedSize < 0) + Err = new Exception("Uncompressed size not set"); + else if (InitZip(ref tempErr) != false) + Err = tempErr; + } + + /// + /// Adds a reference to . + /// + /// The underlying data source. + /// Place to store an Exception if anything goes wrong + /// + public static GsfInputGZip Create(GsfInput source, ref Exception err) + { + if (source == null) + return null; + + GsfInputGZip gzip = new GsfInputGZip(source: source) + { + Name = source.Name, + }; + + return gzip; + } + + /// + /// Destructor + /// + ~GsfInputGZip() + { + Source = null; + + if (Stream != null) + Stream.inflateEnd(); + + Err = null; + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + GsfInput src_source_copy; + if (Source != null) + { + src_source_copy = Source.Duplicate(ref err); + if (err != null) + return null; + } + else + { + src_source_copy = null; + } + + GsfInputGZip dst = new GsfInputGZip(source: src_source_copy, raw: (Source as GsfInputGZip)?.Raw ?? false); + + if (Err != null) + { + dst.Err = Err; + } + else if (dst.Err != null) + { + err = dst.Err; + return null; + } + + return dst; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + if (optional_buffer == null) + { + if (BufSize < num_bytes) + { + BufSize = Math.Max(num_bytes, 256); + Buf = new byte[BufSize]; + } + + optional_buffer = Buf; + } + + Stream.next_out = optional_buffer; + Stream.avail_out = num_bytes; + while (Stream.avail_out != 0) + { + int zerr; + if (Stream.avail_in == 0) + { + long remain = Source.Remaining(); + if (remain <= TrailerSize) + { + if (remain < TrailerSize || StopByteAdded) + { + Err = new Exception("Truncated source"); + return null; + } + + // zlib requires an extra byte. + Stream.avail_in = 1; + GZippedData = new byte[0]; + StopByteAdded = true; + } + else + { + int n = (int)Math.Min(remain - TrailerSize, 4096); + + GZippedData = Source.Read(n, null); + if (GZippedData == null) + { + Err = new Exception("Failed to read from source"); + return null; + } + + Stream.avail_in = n; + } + + Stream.next_in = GZippedData; + } + + zerr = Stream.inflate(Z_NO_FLUSH); + if (zerr != Z_OK) + { + if (zerr != Z_STREAM_END) + return null; + + // Premature end of stream. + if (Stream.avail_out != 0) + return null; + } + } + + // TODO: Enable CRC32 calculation + //CRC = crc32(CRC, optional_buffer, (uint)(Stream.next_out - optional_buffer)); + return optional_buffer; + } + + // Global flag -- we don't want one per stream. + private static bool warned = false; + + /// + public override bool Seek(long offset, SeekOrigin whence) + { + long pos = offset; + + // Note, that pos has already been sanity checked. + switch (whence) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: pos += CurrentOffset; break; + case SeekOrigin.End: pos += Size; break; + default: return true; + } + + if (pos < CurrentOffset) + { + if (Source.Seek(HeaderSize, SeekOrigin.Begin)) + return true; + + // TODO: Enable CRC32 calculation + //CRC = crc32(0L, Z_null, 0); + Stream.avail_in = 0; + if (Stream.inflateInit() != Z_OK) + return true; + + CurrentOffset = 0; + } + + if (SeekEmulate(pos)) + return true; + + SeekSkipped += pos; + if (!warned && + SeekSkipped != pos && // Don't warn for single seek. + SeekSkipped >= 1000000) + { + warned = true; + Console.Error.WriteLine("Seeking in gzipped streams is awfully slow."); + } + + return false; + } + + #endregion + + #region Utilities + + private bool CheckHeader() + { + if (Raw) + { + HeaderSize = 0; + TrailerSize = 0; + } + else + { + byte[] data; + uint len; + + // Check signature + byte[] signature = { 0x1f, 0x8b }; + if ((data = Source.Read(2 + 1 + 1 + 6, null)) == null + || !(data[0] == signature[0] && data[1] == signature[1])) + { + return true; + } + + // zlib constant + int Z_DEFLATED = 8; + + // Verify flags and compression type + GZIP_HEADER_FLAGS flags = (GZIP_HEADER_FLAGS)data[3]; + if (data[2] != Z_DEFLATED) + return true; + + uint modutime = BitConverter.ToUInt32(data, 4); + if (modutime != 0) + { + DateTime modtime = DateTimeOffset.FromUnixTimeSeconds(modutime).DateTime; + ModTime = modtime; + } + + // If we have the size, don't bother seeking to the end. + if (UncompressedSize < 0) + { + // Get the uncompressed size + if (Source.Seek(-4, SeekOrigin.End) + || (data = Source.Read(4, null)) == null) + { + return true; + } + + // FIXME, but how? The size read here is modulo 2^32. + UncompressedSize = BitConverter.ToUInt32(data, 0); + + if (UncompressedSize / 1000 > Source.Size) + { + Console.Error.WriteLine("Suspiciously well compressed file with better than 1000:1 ratio.\n" + + "It is probably truncated or corrupt"); + } + } + + if (Source.Seek(2 + 1 + 1 + 6, SeekOrigin.Begin)) + return true; + + if (flags.HasFlag(GZIP_HEADER_FLAGS.GZIP_EXTRA_FIELD)) + { + if ((data = Source.Read(2, null)) == null) + return true; + + len = BitConverter.ToUInt16(data, 0); + if (Source.Read((int)len, null) == null) + return true; + } + + if (flags.HasFlag(GZIP_HEADER_FLAGS.GZIP_ORIGINAL_NAME)) + { + // Skip over the filename (which is in ISO 8859-1 encoding). + do + { + if ((data = Source.Read(1, null)) == null) + return true; + } while (data[0] != 0); + } + + if (flags.HasFlag(GZIP_HEADER_FLAGS.GZIP_HAS_COMMENT)) + { + // Skip over the comment (which is in ISO 8859-1 encoding). + do + { + if ((data = Source.Read(1, null)) == null) + return true; + } while (data[0] != 0); + } + + if (flags.HasFlag(GZIP_HEADER_FLAGS.GZIP_HEADER_CRC) && (data = Source.Read(2, null)) == null) + return true; + + HeaderSize = Source.CurrentOffset; + + // the last 8 bytes are the crc and size. + TrailerSize = 8; + } + + Size = UncompressedSize; + + if (Source.Remaining() < TrailerSize) + return true; // No room for payload + + return false; + } + + private bool InitZip(ref Exception err) + { + if (Z_OK != Stream.inflateInit(-15)) + { + err = new Exception("Unable to initialize zlib"); + return true; + } + + long cur_pos = Source.CurrentOffset; + if (Source.Seek(0, SeekOrigin.Begin)) + { + err = new Exception("Failed to rewind source"); + return true; + } + + if (CheckHeader() != false) + { + err = new Exception("Invalid gzip header"); + if (Source.Seek(cur_pos, SeekOrigin.Begin)) + Console.Error.WriteLine("attempt to restore position failed ??"); + + return true; + } + + return false; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputGio.cs b/BurnOutSharp/External/libgsf/Input/GsfInputGio.cs new file mode 100644 index 00000000..46d98b26 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputGio.cs @@ -0,0 +1,239 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-gio.c: + * + * Copyright (C) 2007 Dom Lachowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Input +{ + public class GsfInputGio : GsfInput + { + #region Properties + + public string File { get; set; } = null; + + public Stream Stream { get; set; } = null; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfInputGio() { } + + /// Place to store an Exception if anything goes wrong + /// A new GsfInputGio or null + public static GsfInput Create(string file, ref Exception err) + { + if (file == null || !System.IO.File.Exists(file)) + return null; + + long filesize; + + Stream stream; + try + { + stream = System.IO.File.OpenRead(file); + } + catch (Exception ex) + { + err = ex; + return null; + } + + if (true) + { + // see https://bugzilla.gnome.org/show_bug.cgi?id=724970 + return MakeLocalCopy(file, stream); + } + + if (!stream.CanSeek) + return MakeLocalCopy(file, stream); + + { + FileInfo info = new FileInfo(file); + filesize = info.Length; + } + + GsfInputGio input = new GsfInputGio + { + Size = filesize, + Stream = stream, + File = file, + Buf = null, + BufSize = 0, + }; + + input.SetNameFromFile(file); + return input; + } + + /// + /// Destructor + /// + ~GsfInputGio() + { + Stream.Close(); + Stream = null; + + File = null; + + if (Buf != null) + { + Buf = null; + BufSize = 0; + } + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + if (File != null) + return null; + + return Create(File, ref err); + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + if (Stream == null) + return null; + + int total_read = 0; + + if (optional_buffer == null) + { + if (BufSize < num_bytes) + { + BufSize = num_bytes; + Buf = new byte[BufSize]; + } + + optional_buffer = Buf; + } + + while (total_read < num_bytes) + { + int try_to_read = Math.Min(int.MaxValue, num_bytes - total_read); + int nread = Stream.Read(optional_buffer, total_read, try_to_read); + if (nread > 0) + { + total_read += nread; + } + else + { + // Getting zero means EOF which isn't supposed to + // happen. Negative means error. + return null; + } + } + + return optional_buffer; + } + + /// + public override bool Seek(long offset, SeekOrigin whence) + { + if (Stream == null) + return true; + + if (!Stream.CanSeek) + return true; + + try + { + + Stream.Seek(offset, whence); + return true; + } + catch + { + return false; + } + } + + #endregion + + #region Utilities + + private static GsfInput MakeLocalCopy(string file, Stream stream) + { + GsfInput copy; + GsfOutput output = GsfOutputMemory.Create(); + while (true) + { + byte[] buf = new byte[4096]; + int nread = stream.Read(buf, 0, buf.Length); + if (nread > 0) + { + if (!output.Write(nread, buf)) + { + copy = null; + output.Close(); + stream.Close(); + return copy; + } + } + else if (nread == 0) + { + break; + } + else + { + copy = null; + output.Close(); + stream.Close(); + return copy; + } + } + + copy = GsfInputMemory.Clone((output as GsfOutputMemory).Buffer, output.CurrentSize); + + if (copy != null) + { + if (System.IO.File.Exists(file)) + { + FileInfo info = new FileInfo(file); + copy.Name = info.Name; + } + } + + output.Close(); + stream.Close(); + copy.SetNameFromFile(file); + + return copy; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputHTTP.cs b/BurnOutSharp/External/libgsf/Input/GsfInputHTTP.cs new file mode 100644 index 00000000..dff69371 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputHTTP.cs @@ -0,0 +1,186 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-http.c: retrieves input via HTTP + * + * Copyright (C) 2006 Michael Lawrence (lawremi@iastate.edu) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Net; + +namespace LibGSF.Input +{ + public class GsfInputHTTP : GsfInput + { + #region Properties + + public string Url { get; private set; } = null; + + public string ContentType { get; private set; } = null; + + public Stream ResponseData { get; private set; } = null; + + public byte[] Buffer { get; private set; } = null; + + public int BufferSize { get; private set; } = 0; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfInputHTTP() { } + + /// + /// + /// + /// A string containing the URL to retrieve + /// Holds any errors encountered when establishing the HTTP connection + /// An open HTTP connection, ready for reading. + public static GsfInput Create(string url, ref Exception error) + { + if (url == null) + return null; + + WebRequest request = WebRequest.Create(url); + WebResponse response = request.GetResponse(); + Stream ctx = response.GetResponseStream(); + + string content_type = response.ContentType; + + // Always make a local copy + // see https://bugzilla.gnome.org/show_bug.cgi?id=724970 + GsfInput input = MakeLocalCopy(ctx); + if (input != null) + { + input.Name = url; + return input; + } + + GsfInputHTTP obj = new GsfInputHTTP + { + Url = url, + ContentType = content_type, + Size = ctx.Length, + ResponseData = ctx, + }; + + return obj; + } + + ~GsfInputHTTP() + { + Url = null; + ContentType = null; + + if (ResponseData != null) + { + ResponseData.Close(); + ResponseData = null; + } + + Buffer = null; + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) => Create(Url, ref err); + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + int nread; + int total_read; + + if (optional_buffer == null) + { + if (BufferSize < num_bytes) + { + BufferSize = num_bytes; + Buffer = new byte[BufferSize]; + } + + optional_buffer = Buffer; + } + + int ptr = 0; + for (total_read = 0; total_read < num_bytes; total_read += nread) + { + nread = ResponseData.Read(optional_buffer, ptr, num_bytes - total_read); + if (nread <= 0) + return null; + + ptr += nread; + } + + return optional_buffer; + } + + /// + public override bool Seek(long offset, SeekOrigin whence) => false; + + #endregion + + #region Utilities + + private static GsfInput MakeLocalCopy(Stream ctx) + { + GsfOutputMemory output = GsfOutputMemory.Create(); + + GsfInput copy; + while (true) + { + byte[] buf = new byte[4096]; + int nread; + + nread = ctx.Read(buf, 0, buf.Length); + + if (nread > 0) + { + if (!output.Write(nread, buf)) + { + copy = null; + output.Close(); + return copy; + } + } + else if (nread == 0) + { + break; + } + else + { + copy = null; + output.Close(); + return copy; + } + } + + copy = GsfInputMemory.Clone(output.Buffer, output.Capacity); + output.Close(); + return copy; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputIoChannel.cs b/BurnOutSharp/External/libgsf/Input/GsfInputIoChannel.cs new file mode 100644 index 00000000..79cf2c9e --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputIoChannel.cs @@ -0,0 +1,43 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-iochannel.c: GIOChannel based input + * + * Copyright (C) 2003-2006 Rodrigo Moya (rodrigo@gnome-db.org) + * Copyright (C) 2003-2006 Dom Lachowicz (cinamod@hotmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; + +namespace LibGSF.Input +{ + public partial class GsfInputMemory + { + // TODO: Enable once GIOChannel is converted + + ///// A new GsfInputMemory or null. + //public static GsfInputMemory CreateFromIoChannel(GIOChannel channel, ref Exception err) + //{ + // if (channel == null) + // return null; + + // if (G_IO_STATUS_NORMAL != channel.ReadToEnd(out byte[] buf, out int len, err)) + // return null; + + // return Create(buf, len, true); + //} + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputMemory.cs b/BurnOutSharp/External/libgsf/Input/GsfInputMemory.cs new file mode 100644 index 00000000..00397c27 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputMemory.cs @@ -0,0 +1,149 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-memory.c: + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Linq; + +namespace LibGSF.Input +{ + public partial class GsfInputMemory : GsfInput + { + #region Properties + + public GsfSharedMemory Shared { get; set; } = null; + + #endregion + + #region Functions + + /// + /// + /// The input bytes + /// The length of + /// Whether you want this memory to be free'd at object destruction + /// A new GsfInputMemory + public static GsfInputMemory Create(byte[] buf, long length, bool needs_free) + { + GsfInputMemory mem = new GsfInputMemory(); + mem.Shared = GsfSharedMemory.Create(buf, length, needs_free); + mem.Size = length; + return mem; + } + + /// The input bytes + /// The length of + /// A new GsfInputMemory + public static GsfInputMemory Clone(byte[] buf, long length) + { + if (buf == null || length < 0) + return null; + + GsfInputMemory mem = new GsfInputMemory(); + byte[] cpy = new byte[Math.Max(1, length)]; + if (buf.Length > 0) + Array.Copy(buf, cpy, length); + + mem.Shared = GsfSharedMemory.Create(cpy, length, true); + mem.Size = length; + return mem; + } + + /// + protected override GsfInput DupImpl(ref Exception err) + { + return new GsfInputMemory + { + Shared = Shared, + Size = Shared.Size, + }; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + byte[] src = Shared.Buf; + if (src == null) + return null; + + if (optional_buffer != null) + { + Array.Copy(src, CurrentOffset, optional_buffer, 0, num_bytes); + return optional_buffer; + } + else + { + return src.Skip((int)CurrentOffset).ToArray(); + } + } + + /// + public override bool Seek(long offset, SeekOrigin whence) => false; + + #endregion + } + + public class GsfInputMemoryMap : GsfInputMemory + { + private const int PROT_READ = 0x1; + + /// The file on disk that you want to mmap + /// An Exception + /// A new GsfInputMemory + public GsfInputMemoryMap Create(string filename, ref Exception err) + { + FileStream fd; + try + { + fd = File.OpenRead(filename); + } + catch (Exception ex) + { + err = ex; + return null; + } + + FileInfo st = new FileInfo(filename); + if (!st.Attributes.HasFlag(FileAttributes.Normal)) + { + err = new Exception($"{filename}: Is not a regular file"); + fd.Close(); + return null; + } + + long size = fd.Length; + byte[] buf = new byte[size]; + fd.Read(buf, 0, (int)size); + + GsfInputMemoryMap mem = new GsfInputMemoryMap + { + Shared = GsfSharedMemory.CreateMemoryMapped(buf, size), + Size = size, + Name = filename, + ModTime = st.LastWriteTime, + }; + + fd.Close(); + + return mem; + } + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputProxy.cs b/BurnOutSharp/External/libgsf/Input/GsfInputProxy.cs new file mode 100644 index 00000000..895a15dd --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputProxy.cs @@ -0,0 +1,111 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-proxy.c: proxy object (with its own current position) + * + * Copyright (C) 2004-2006 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Input +{ + public class GsfInputProxy : GsfInput + { + #region Properties + + public GsfInput Source { get; set; } = null; + + public long Offset { get; set; } = 0; + + #endregion + + #region Functions + + /// + /// This creates a new proxy to a section of the given source. The new + /// object will have its own current position, but any operation on it + /// can change the source's position. + /// + /// If a proxy to a proxy is created, the intermediate proxy is short- + /// circuited. + /// + /// This function will ref the source. + /// + /// The underlying data source. + /// Offset into source for start of section. + /// Length of section. + /// A new input object. + public static GsfInputProxy Create(GsfInput source, long offset, long size) + { + if (source == null) + return null; + if (offset < 0) + return null; + + long source_size = source.Size; + if (offset > source_size) + return null; + if (size > source_size - offset) + return null; + + GsfInputProxy proxy = new GsfInputProxy + { + Offset = offset, + Size = size, + Name = source.Name, + }; + + // Short-circuit multiple proxies. + if (source is GsfInputProxy proxy_source) + { + proxy.Offset += proxy_source.Offset; + source = proxy_source.Source; + } + + proxy.Source = source; + return proxy; + } + + /// + /// This creates a new proxy to the entire, given input source. See + /// Create for details. + /// + /// The underlying data source. + /// A new input object. + public static GsfInput Create(GsfInput source) => Create(source, 0, source.Size); + + /// + protected override GsfInput DupImpl(ref Exception err) => Create(this); + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + // Seek to our position in the source. + if (Source.Seek(Offset + CurrentOffset, SeekOrigin.Begin)) + return null; + + // Read the data. + return Source.Read(num_bytes, optional_buffer, bufferPtr); + } + + /// + public override bool Seek(long offset, SeekOrigin whence) => false; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputStdio.cs b/BurnOutSharp/External/libgsf/Input/GsfInputStdio.cs new file mode 100644 index 00000000..cb3383e7 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputStdio.cs @@ -0,0 +1,226 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-stdio.c: stdio based input + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Input +{ + public class GsfInputStdio : GsfInput + { + #region Properties + + public FileStream File { get; set; } = null; + + public string Filename { get; set; } = null; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + public bool KeepOpen { get; set; } = false; + + #endregion + + #region Functions + + /// + /// Destructor + /// + ~GsfInputStdio() + { + if (File != null && !KeepOpen) + File.Close(); + } + + public static GsfInput MakeLocalCopy(FileStream stream, string filename, ref Exception err) + { + GsfOutputMemory output = GsfOutputMemory.Create(); + + while (true) + { + byte[] buf = new byte[4096]; + int nread = stream.Read(buf, 1, buf.Length); + + if (nread > 0) + { + if (!output.Write(nread, buf)) + { + err = new Exception($"{(filename != null ? filename : "?")}: not a regular file"); + output.Close(); + return null; + } + } + else if (nread == 0) + { + break; + } + else + { + err = new Exception($"{(filename != null ? filename : "?")}: not a regular file"); + output.Close(); + return null; + } + } + + GsfInput copy = GsfInputMemory.Clone(output.Buffer, output.Capacity); + + output.Close(); + + if (filename != null) + copy.SetNameFromFilename(filename); + + return copy; + } + + /// In UTF-8. + /// Place to store an Exception if anything goes wrong + /// A new file or null. + public static GsfInput Create(string filename, ref Exception err) + { + if (filename == null) + return null; + + FileStream file; + try + { + file = System.IO.File.OpenRead(filename); + } + catch (Exception ex) + { + err = ex; + return null; + } + + FileInfo fstat = new FileInfo(filename); + if (!fstat.Attributes.HasFlag(FileAttributes.Normal)) + { + GsfInput res = MakeLocalCopy(file, filename, ref err); + file.Close(); + return res; + } + + long size = fstat.Length; + GsfInputStdio input = new GsfInputStdio + { + File = file, + Filename = filename, + Buf = null, + BufSize = 0, + KeepOpen = false, + Size = size, + ModTime = fstat.LastWriteTime, + }; + + input.SetNameFromFilename(filename); + + return input; + } + + /// + /// Assumes ownership of when succeeding. If is true, + /// ownership reverts to caller when the GsfInput is closed. + /// + /// The filename corresponding to . + /// An existing stdio + /// Should be closed when the wrapper is closed + /// + /// A new GsfInput wrapper for . Note that if the file is not + /// seekable, this function will make a local copy of the entire file. + /// + public static GsfInput Create(string filename, FileStream file, bool keep_open) + { + if (filename == null) + return null; + if (file == null) + return null; + + FileInfo st = new FileInfo(filename); + if (!st.Attributes.HasFlag(FileAttributes.Normal)) + { + Exception err = null; + return MakeLocalCopy(file, filename, ref err); + } + + GsfInputStdio stdio = new GsfInputStdio + { + File = file, + KeepOpen = keep_open, + Filename = filename, + Size = st.Length, + }; + + stdio.SetNameFromFilename(filename); + return stdio; + } + + /// + protected override GsfInput DupImpl(ref Exception err) => Create(Filename, ref err); + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + if (File == null) + return null; + + if (optional_buffer == null) + { + if (BufSize < num_bytes) + { + BufSize = num_bytes; + Buf = new byte[BufSize]; + } + + optional_buffer = Buf; + } + + int total_read = 0; + while (total_read < num_bytes) + { + int nread = File.Read(optional_buffer, total_read, num_bytes - total_read); + total_read += nread; + if (total_read < num_bytes && File.Position >= File.Length) + return null; + } + + return optional_buffer; + } + + /// + public override bool Seek(long offset, SeekOrigin whence) + { + if (File == null) + return true; + + try + { + long pos = File.Seek(offset, whence); + return pos != offset; + } + catch + { + return true; + } + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfInputTextline.cs b/BurnOutSharp/External/libgsf/Input/GsfInputTextline.cs new file mode 100644 index 00000000..4dae7867 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfInputTextline.cs @@ -0,0 +1,212 @@ + +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-input-textline.c: textline based input + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Text; + +namespace LibGSF.Input +{ + public class GsfInputTextline : GsfInput + { + #region Properties + + public GsfInput Source { get; set; } = null; + + public byte[] Remainder { get; set; } = null; + + public int RemainderSize { get; set; } = 0; + + public int MaxLineSize { get; set; } = 512; // An initial guess + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + #endregion + + #region Functions + + /// In some combination of ascii and UTF-8 + /// A new file + /// This adds a reference to @source. + public static GsfInputTextline Create(GsfInput source) + { + if (source == null) + return null; + + return new GsfInputTextline + { + Source = source, + Buf = null, + BufSize = 0, + Size = source.Size, + Name = source.Name, + }; + } + + /// + protected override GsfInput DupImpl(ref Exception err) + { + return new GsfInputTextline() + { + Source = this.Source, + Size = this.Size, + Name = this.Name, + }; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + Remainder = null; + byte[] res = Source.Read(num_bytes, optional_buffer); + CurrentOffset = Source.CurrentOffset; + return res; + } + + /// + public override bool Seek(long offset, SeekOrigin whence) + { + Remainder = null; + bool res = Source.Seek(offset, whence); + CurrentOffset = Source.CurrentOffset; + return res; + } + + #endregion + + #region Utilities + + /// + /// A utility routine to read things line by line from the underlying source. + /// Trailing newlines and carriage returns are stripped, and the resultant buffer + /// can be edited. + /// + /// The string read, or null on eof. + internal string GetStringASCII() => GetStringUTF8(); + + /// + /// A utility routine to read things line by line from the underlying source. + /// Trailing newlines and carriage returns are stripped, and the resultant buffer + /// can be edited. + /// + /// The string read, or null on eof. + internal string GetStringUTF8() + { + int len, ptr, end; + int count = 0; + while (true) + { + if (Remainder == null || RemainderSize == 0) + { + long remain = Source.Remaining(); + len = (int)Math.Min(remain, MaxLineSize); + + Remainder = Source.Read(len, null); + if (Remainder == null) + return null; + + RemainderSize = len; + } + + ptr = 0; // Remainder[0] + end = ptr + RemainderSize; + + for (; ptr < end; ptr++) + { + if (Remainder[ptr] == '\n' || Remainder[ptr] == '\r') + break; + } + + // Copy the remains into the buffer, grow it if necessary + len = ptr; + if (count + len >= BufSize) + { + BufSize = len; + Buf = new byte[BufSize + 1]; + } + + if (Buf == null) + return null; + + Array.Copy(Remainder, 0, Buf, count, len); + count += len; + + if (ptr < end) + { + char last = (char)Remainder[ptr]; + + // Eat the trailing eol marker: \n, \r\n, or \r. + ptr++; + if (ptr >= end && last == '\r') + { + // Be extra careful, the CR is at the bound + if (Source.Remaining() > 0) + { + if (Source.Read(1, Remainder, ptr) == null) + return null; + + RemainderSize = 1; + end = ptr + 1; + } + else + { + ptr = end = -1; + } + } + + if (ptr != -1 && last == '\r' && Remainder[ptr] == '\n') + ptr++; + + break; + } + else if (Source.Remaining() <= 0) + { + ptr = end = -1; + break; + } + else + { + Remainder = null; + } + } + + if (ptr == -1) + { + Remainder = null; + RemainderSize = 0; + } + else + { + RemainderSize = end - ptr; + } + + CurrentOffset = Source.CurrentOffset - (Remainder != null ? RemainderSize : 0); + Buf[count] = 0x00; + + return Encoding.UTF8.GetString(Buf); + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Input/GsfStructuredBlob.cs b/BurnOutSharp/External/libgsf/Input/GsfStructuredBlob.cs new file mode 100644 index 00000000..2d8b2554 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Input/GsfStructuredBlob.cs @@ -0,0 +1,213 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-structured_blob.c : Utility storage to blob in/out a tree of data + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using LibGSF.Output; + +namespace LibGSF.Input +{ + public class GsfStructuredBlob : GsfInfile + { + #region Properties + + public GsfSharedMemory Data { get; set; } = null; + + public GsfStructuredBlob[] Children { get; set; } = null; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfStructuredBlob() { } + + /// + /// Destructor + /// + ~GsfStructuredBlob() + { + if (Data != null) + Data = null; + + if (Children != null) + Children = null; + } + + #endregion + + #region Functions + + /// + protected override GsfInput DupImpl(ref Exception err) + { + GsfStructuredBlob dst = new GsfStructuredBlob(); + + if (Data != null) + dst.Data = Data; + + if (Children != null) + { + dst.Children = new GsfStructuredBlob[Children.Length]; + Array.Copy(Children, dst.Children, Children.Length); + } + + return dst; + } + + /// + protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) + { + byte[] src = Data.Buf; + if (src == null) + return null; + + if (optional_buffer == null) + optional_buffer = new byte[num_bytes]; + + Array.Copy(src, CurrentOffset, optional_buffer, 0, num_bytes); + return optional_buffer; + } + + /// + public override bool Seek(long offset, SeekOrigin whence) => false; + + /// + public override int NumChildren() => Children != null ? Children.Length : -1; + + /// + public override string NameByIndex(int i) + { + if (Children == null) + return null; + + if (i < 0 || i >= Children.Length) + return null; + + return Children[i].Name; + } + + /// + public override GsfInput ChildByIndex(int i, ref Exception error) + { + if (Children == null) + return null; + + if (i < 0 || i >= Children.Length) + return null; + + return Children[i]; + } + + /// + public override GsfInput ChildByName(string name, ref Exception error) + { + if (Children == null) + return null; + + for (int i = 0; i < Children.Length; i++) + { + GsfInput child = Children[i]; + if (child != null && child.Name == name) + return child.Duplicate(ref error); + } + + return null; + } + + /// + /// Create a tree of binary blobs with unknown content from a GsfInput or + /// #GsfInfile and store it in a newly created #GsfStructuredBlob. + /// + /// An input (potentially a GsfInfile) holding the blob + /// A new GsfStructuredBlob object which the caller is responsible for. + public static GsfStructuredBlob Read(GsfInput input) + { + if (input == null) + return null; + + GsfStructuredBlob blob = new GsfStructuredBlob(); + long content_size = input.Remaining(); + if (content_size > 0) + { + byte[] buf = new byte[content_size]; + input.Read((int)content_size, buf); + blob.Data = GsfSharedMemory.Create(buf, content_size, true); + } + + blob.Name = input.Name; + + if (input is GsfInfile infile) + { + int i = infile.NumChildren(); + if (i > 0) + { + Exception err = null; + blob.Children = new GsfStructuredBlob[i]; + while (i-- > 0) + { + GsfInput child = infile.ChildByIndex(i, ref err); + GsfStructuredBlob child_blob = null; + if (child != null) + child_blob = Read(child); + + blob.Children[i] = child_blob; + } + } + } + + return blob; + } + + /// + /// Dumps structured blob @blob onto the . Will fail if the output is + /// not an Outfile and blob has multiple streams. + /// + /// true on success. + public bool Write(GsfOutfile container) + { + if (container == null) + return false; + + bool has_kids = (Children != null && Children.Length > 0); + GsfOutput output = container.NewChild(Name, has_kids); + if (has_kids) + { + for (int i = 0; i < Children.Length; i++) + { + GsfStructuredBlob child_blob = Children[i]; + if (!child_blob.Write(output as GsfOutfile)) + return false; + } + } + + if (Data != null) + output.Write((int)Data.Size, Data.Buf); + + output.Close(); + return true; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutfile.cs b/BurnOutSharp/External/libgsf/Output/GsfOutfile.cs new file mode 100644 index 00000000..a163a416 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutfile.cs @@ -0,0 +1,35 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-outfile.c : + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +namespace LibGSF.Output +{ + public abstract class GsfOutfile : GsfOutput + { + #region Virtual Functions + + /// The name of the new child to create + /// true to create a directory, false to create a plain file + /// A newly created child + public virtual GsfOutput NewChild(string name, bool is_dir) => null; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutfileMSOle.cs b/BurnOutSharp/External/libgsf/Output/GsfOutfileMSOle.cs new file mode 100644 index 00000000..ed6001ad --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutfileMSOle.cs @@ -0,0 +1,930 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-outfile-msole.c: + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using LibGSF.Input; +using static LibGSF.GsfMSOleImpl; + +namespace LibGSF.Output +{ + public class GsfOutfileMSOleBlock + { + public int Shift { get; set; } + + public int Size { get; set; } + } + + public class GsfOutfileMSOle : GsfOutfile + { + #region Constants + + // The most common values + private const int OLE_DEFAULT_THRESHOLD = 0x1000; + private const int OLE_DEFAULT_SB_SHIFT = 6; + private const int OLE_DEFAULT_BB_SHIFT = 9; + private const long OLE_DEFAULT_BB_SIZE = (1u << OLE_DEFAULT_BB_SHIFT); + private const long OLE_DEFAULT_SB_SIZE = (1u << OLE_DEFAULT_SB_SHIFT); + + // Globals to support variable OLE sector size. + + /// + /// 512 and 4096 bytes are the only known values for sector size on + /// Win2k/XP platforms. Attempts to create OLE files on Win2k/XP with + /// other values using StgCreateStorageEx() fail with invalid parameter. + /// This code has been tested with 128,256,512,4096,8192 sizes for + /// libgsf read/write. Interoperability with MS OLE32.DLL has been + /// tested with 512 and 4096 block size for filesizes up to 2GB. + /// + private const int ZERO_PAD_BUF_SIZE = 4096; + + private const uint CHUNK_SIZE = 1024u; + + private static readonly byte[] default_header = + { + /* 0x00 */ 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x18 */ 0x3e, 0x00, 0x03, 0x00, 0xfe, 0xff, 0x09, 0x00, + /* 0x20 */ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x30 */ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + /* 0x38 */ 0x00, 0x10, 0x00, 0x00 /* 0x3c-0x4b: filled on close */ + }; + + #endregion + + #region Enums + + public enum MSOleOutfileType + { + MSOLE_DIR, + MSOLE_SMALL_BLOCK, + MSOLE_BIG_BLOCK + } + + #endregion + + #region Properties + + public GsfOutfile Parent { get; set; } + + public GsfOutput Sink { get; set; } = null; + + public GsfOutfileMSOle Root { get; set; } = null; + + public GsfMSOleSortingKey Key { get; set; } + + public MSOleOutfileType Type { get; set; } = MSOleOutfileType.MSOLE_DIR; + + public int FirstBlock { get; set; } + + public int Blocks { get; set; } + + public int ChildIndex { get; set; } + + public GsfOutfileMSOleBlock BigBlock { get; set; } + + public GsfOutfileMSOleBlock SmallBlock { get; set; } + + #region Union (Content) + + #region Struct (Dir) + + public List Content_Dir_Children { get; set; } = null; + + /// + /// Only valid for the root, ordered + /// + public List Content_Dir_RootOrder { get; set; } = null; + + #endregion + + #region Struct (SmallBlock) + + public byte[] Content_SmallBlock_Buf { get; set; } + + #endregion + + #region Struct (BigBlock) + + /// + /// In bytes + /// + public int Content_BigBlock_StartOffset { get; set; } + + #endregion + + #endregion + + /// + /// 16 byte GUID used by some apps + /// + public byte[] ClassID { get; set; } = new byte[16]; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutfileMSOle() => MakeSortingName(); + + /// + /// Creates the root directory of an MS OLE file and manages the addition of + /// children. + /// + /// A GsfOutput to hold the OLE2 file. + /// Size of large blocks. + /// Size of small blocks. + /// The new ole file handler. + /// This adds a reference to . + public static GsfOutfileMSOle CreateFull(GsfOutput sink, int bb_size, int sb_size) + { + if (sink == null) + return null; + if (sb_size != (1u << ComputeShift(sb_size))) + return null; + if (bb_size != (1u << ComputeShift(bb_size))) + return null; + if (sb_size > bb_size) + return null; + + GsfOutfileMSOle ole = new GsfOutfileMSOle + { + Sink = sink, + SmallBlock = new GsfOutfileMSOleBlock + { + Size = sb_size, + }, + BigBlock = new GsfOutfileMSOleBlock + { + Size = bb_size, + }, + Container = null, + Name = sink.Name, + Type = MSOleOutfileType.MSOLE_DIR, + Content_Dir_RootOrder = new List(), + }; + + ole.RegisterChild(ole); + + // Build the header + byte[] buf = Enumerable.Repeat(0xFF, OLE_HEADER_SIZE).ToArray(); + Array.Copy(default_header, buf, default_header.Length); + + byte[] temp = BitConverter.GetBytes((ushort)ole.BigBlock.Shift); + Array.Copy(temp, 0, buf, OLE_HEADER_BB_SHIFT, temp.Length); + + temp = BitConverter.GetBytes((ushort)ole.SmallBlock.Shift); + Array.Copy(temp, 0, buf, OLE_HEADER_SB_SHIFT, temp.Length); + + // 4k sector OLE files seen in the wild have version 4 + if (ole.BigBlock.Size == 4096) + { + BitConverter.GetBytes((ushort)4); + Array.Copy(temp, 0, buf, OLE_HEADER_MAJOR_VER, temp.Length); + } + + sink.Write(OLE_HEADER_SIZE, buf); + + // Header must be padded out to bb.size with zeros + ole.PadZero(); + + return ole; + } + + /// + /// Creates the root directory of an MS OLE file and manages the addition of + /// children. + /// + /// A GsfOutput to hold the OLE2 file + /// The new ole file handler. + /// This adds a reference to . + public static GsfOutfileMSOle Create(GsfOutput sink) => CreateFull(sink, (int)OLE_DEFAULT_BB_SIZE, (int)OLE_DEFAULT_SB_SIZE); + + ~GsfOutfileMSOle() + { + Key = null; + + switch (Type) + { + case MSOleOutfileType.MSOLE_DIR: + Content_Dir_Children.Clear(); + Content_Dir_Children = null; + if (Content_Dir_RootOrder != null) + Console.Error.WriteLine("Finalizing a MSOle Outfile without closing it."); + + break; + + case MSOleOutfileType.MSOLE_SMALL_BLOCK: + Content_SmallBlock_Buf = null; + break; + + case MSOleOutfileType.MSOLE_BIG_BLOCK: + break; + + default: + throw new Exception("This should not be reached"); + } + } + + #endregion + + #region Functions + + public override void Dispose() + { + if (!IsClosed) + Close(); + + Sink = null; + base.Dispose(); + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) + { + switch (whence) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: offset += CurrentOffset; break; + case SeekOrigin.End: offset += CurrentSize; break; + default: throw new Exception("This should not be reached"); + } + + switch (Type) + { + case MSOleOutfileType.MSOLE_DIR: + if (offset != 0) + { + Console.Error.WriteLine("Attempt to seek a directory"); + return false; + } + + return true; + + case MSOleOutfileType.MSOLE_SMALL_BLOCK: + // It is ok to seek past the big block threshold + // we don't convert until they _write_ something + return true; + + case MSOleOutfileType.MSOLE_BIG_BLOCK: + return Sink.Seek(Content_BigBlock_StartOffset + offset, SeekOrigin.Begin); + + default: + throw new Exception("This should not be reached"); + } + + return false; + } + + /// + protected override bool CloseImpl() + { + if (Container == null) + { + // The root dir + bool ok = WriteDirectory(); + HoistError(); + if (!Sink.Close()) + ok = false; + + return ok; + } + + if (Type == MSOleOutfileType.MSOLE_BIG_BLOCK) + { + Seek(0, SeekOrigin.End); + PadZero(); + Blocks = CurrentBlock() - FirstBlock; + return Unwrap(Sink); + } + + return true; + } + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (Type == MSOleOutfileType.MSOLE_DIR) + return false; + + if (Type == MSOleOutfileType.MSOLE_SMALL_BLOCK) + { + if ((CurrentOffset + num_bytes) < OLE_DEFAULT_THRESHOLD) + { + Array.Copy(Content_SmallBlock_Buf, CurrentOffset, data, 0, num_bytes); + return true; + } + + bool ok = Wrap(Sink); + if (!ok) + return false; + + byte[] buf = Content_SmallBlock_Buf; + Content_SmallBlock_Buf = null; + + long start_offset = Sink.CurrentOffset; + Content_BigBlock_StartOffset = (int)start_offset; + if (Content_BigBlock_StartOffset != start_offset) + { + // Check for overflow + Console.Error.WriteLine("File too big"); + return false; + } + + FirstBlock = CurrentBlock(); + Type = MSOleOutfileType.MSOLE_BIG_BLOCK; + + int wsize = (int)CurrentSize; + if (wsize != CurrentSize) + { + // Check for overflow + Console.Error.WriteLine("File too big"); + return false; + } + + Sink.Write(wsize, buf); + + // If we had a seek then we might not be at the right location. + // This can happen both with a seek beyond the end of file + // (see bug #14) and with a backward seek. + Sink.Seek(Content_BigBlock_StartOffset + CurrentOffset, SeekOrigin.Begin); + } + + if (Type != MSOleOutfileType.MSOLE_BIG_BLOCK) + return false; + + Sink.Write(num_bytes, data); + + return true; + } + + /// + protected override long VPrintFImpl(string format, params string[] args) + { + // An optimization. + if (Type == MSOleOutfileType.MSOLE_BIG_BLOCK) + return Sink.VPrintF(format, args); + + // In other cases, use the gsf_output_real_vprintf fallback method. + // (This eventually calls gsf_outfile_msole_write, which will also + // check that ole.type != MSOLE_DIR.) + return VPrintF(format, args); + } + + /// + public override GsfOutput NewChild(string name, bool is_dir) + { + if (Type != MSOleOutfileType.MSOLE_DIR) + return null; + + GsfOutfileMSOle child = new GsfOutfileMSOle(); + if (is_dir) + { + child.Type = MSOleOutfileType.MSOLE_DIR; + child.Content_Dir_Children = null; + } + else + { + // Start as small block + child.Type = MSOleOutfileType.MSOLE_SMALL_BLOCK; + child.Content_SmallBlock_Buf = new byte[OLE_DEFAULT_THRESHOLD]; + } + + child.Root = Root; + child.Sink = Sink; + + child.SetSmallBlockSize(SmallBlock.Size); + child.SetBigBlockSize(BigBlock.Size); + + child.Name = name; + child.Container = this; + + Content_Dir_Children.Add(child); + Root.RegisterChild(child); + + return child; + } + + /// + /// Write to the directory associated with ole. + /// + /// Identifier (often a GUID in MS Windows apps) + /// true on success. + public bool SetClassID(byte[] clsid) + { + if (Type != MSOleOutfileType.MSOLE_DIR) + return false; + + Array.Copy(clsid, ClassID, ClassID.Length); + return true; + } + + #endregion + + #region Utilities + + /// + /// Returns the number of times 1 must be shifted left to reach value + /// + private static int ComputeShift(int value) + { + int i = 0; + while ((value >> i) > 1) + i++; + + return i; + } + + private void SetBigBlockSize(int size) + { + BigBlock.Size = size; + BigBlock.Shift = ComputeShift(size); + } + + private void SetSmallBlockSize(int size) + { + SmallBlock.Size = size; + SmallBlock.Shift = ComputeShift(size); + } + + /// + /// Static objects are zero-initialized + /// + private static byte[] zero_buf = new byte[ZERO_PAD_BUF_SIZE]; + + /// + /// Calculate the block of the current offset in the file. A useful idiom is to + /// pad_zero to move to the start of the next block, then get the block number. + /// This avoids fence post type problems with partial blocks. + /// + private int CurrentBlock() => (int)((Sink.CurrentOffset - OLE_HEADER_SIZE) >> BigBlock.Shift); + + private int BytesLeftInBlock() + { + // Blocks are multiples of BigBlock.Size (the header is padded out to BigBlock.Size) + long r = Sink.CurrentOffset % BigBlock.Size; + return (int)((r != 0) ? (BigBlock.Size - r) : 0); + } + + private void PadZero() + { + // No need to bounds check. len will always be less than bb.size, and + // we already check that zero_buf is big enough at creation + int len = BytesLeftInBlock(); + if (len > 0) + Sink.Write(len, zero_buf); + } + + /// + /// Utility routine to generate a BAT for a file known to be sequential and + /// continuous. + /// + private static void WriteBat(GsfOutput sink, int block, int blocks) + { + byte[] buf = new byte[BAT_INDEX_SIZE * CHUNK_SIZE]; + int bufi = 0; + + while (blocks-- > 1) + { + block++; + + byte[] temp2 = BitConverter.GetBytes(block); + Array.Copy(temp2, 0, buf, bufi * BAT_INDEX_SIZE, buf.Length); + + bufi++; + if (bufi == CHUNK_SIZE) + { + if (bufi != 0) + sink.Write(bufi * BAT_INDEX_SIZE, buf); + + bufi = 0; + } + } + + byte[] temp = BitConverter.GetBytes(BAT_MAGIC_END_OF_CHAIN); + Array.Copy(temp, 0, buf, bufi * BAT_INDEX_SIZE, buf.Length); + + bufi++; + if (bufi == CHUNK_SIZE) + { + if (bufi != 0) + sink.Write(bufi * BAT_INDEX_SIZE, buf); + + bufi = 0; + } + + if (bufi != 0) + sink.Write(bufi * BAT_INDEX_SIZE, buf); + + bufi = 0; + } + + private static void WriteConst(GsfOutput sink, uint value, int n) + { + byte[] buf = new byte[BAT_INDEX_SIZE * CHUNK_SIZE]; + int bufi = 0; + + while (n-- > 0) + { + byte[] temp = BitConverter.GetBytes(value); + Array.Copy(temp, 0, buf, bufi * BAT_INDEX_SIZE, buf.Length); + + bufi++; + if (bufi == CHUNK_SIZE) + { + if (bufi != 0) + sink.Write(bufi * BAT_INDEX_SIZE, buf); + + bufi = 0; + } + } + + if (bufi != 0) + sink.Write(bufi * BAT_INDEX_SIZE, buf); + + bufi = 0; + } + + private void PadBatUnused(int residual) + { + WriteConst(Sink, BAT_MAGIC_UNUSED, (BytesLeftInBlock() / BAT_INDEX_SIZE) - residual); + } + + /// + /// Write the metadata (dirents, small block, xbats) + /// + private bool WriteDirectory() + { + byte[] buf = new byte[OLE_HEADER_SIZE]; + uint next; + uint xbat_pos; + int metabat_size = BigBlock.Size / BAT_INDEX_SIZE - 1; + List elem = Root.Content_Dir_RootOrder; + + // Write small block data + int blocks = 0; + int sb_data_start = CurrentBlock(); + long data_size = Sink.CurrentOffset; + + foreach (GsfOutfileMSOle child in elem) + { + if (child.Type == MSOleOutfileType.MSOLE_SMALL_BLOCK) + { + long size = child.CurrentSize; + if (size > 0) + { + child.Blocks = (int)(((size - 1) >> SmallBlock.Shift) + 1); + Sink.Write(child.Blocks << SmallBlock.Shift, child.Content_SmallBlock_Buf); + child.FirstBlock = blocks; + blocks += child.Blocks; + } + else + { + child.Blocks = 0; + unchecked + { + child.FirstBlock = (int)BAT_MAGIC_END_OF_CHAIN; + } + } + } + } + + data_size = Sink.CurrentOffset - data_size; + int sb_data_size = (int)data_size; + if (sb_data_size != data_size) + { + // Check for overflow + Console.Error.WriteLine("File too big"); + return false; + } + + PadZero(); + int sb_data_blocks = CurrentBlock() - sb_data_start; + + // Write small block BAT (the meta bat is in a file) + int sbat_start = CurrentBlock(); + foreach (GsfOutfileMSOle child in elem) + { + if (child.Type == MSOleOutfileType.MSOLE_SMALL_BLOCK && child.Blocks > 0) + WriteBat(Sink, child.FirstBlock, child.Blocks); + } + + PadBatUnused(0); + int num_sbat = CurrentBlock() - sbat_start; + + int name_len = 0; + + // Write dirents + int dirent_start = CurrentBlock(); + for (int i = 0; i < elem.Count; i++) + { + GsfOutfileMSOle child = elem[i]; + + buf = Enumerable.Repeat(0, DIRENT_SIZE).ToArray(); + + // Hard code 'Root Entry' for the root + if (i == 0 || child.Name != null) + { + string name = (i == 0) ? "Root Entry" : child.Name; + + byte[] nameUtf16Bytes = Encoding.UTF8.GetBytes(name); + nameUtf16Bytes = Encoding.Convert(Encoding.UTF8, Encoding.Unicode, nameUtf16Bytes); + string nameUtf16 = Encoding.Unicode.GetString(nameUtf16Bytes); + name_len = nameUtf16.Length; + if (name_len >= DIRENT_MAX_NAME_SIZE) + name_len = DIRENT_MAX_NAME_SIZE - 1; + + // Be wary about endianness + for (int j = 0; j < name_len; j++) + { + byte[] nameUtf16Temp = BitConverter.GetBytes((ushort)nameUtf16[j]); + Array.Copy(nameUtf16Temp, 0, buf, j * 2, 2); + } + + name_len++; + } + + byte[] writeTemp = BitConverter.GetBytes((ushort)(name_len * 2)); + Array.Copy(writeTemp, 0, buf, DIRENT_NAME_LEN, writeTemp.Length); + + if (child.Root == child) + { + buf[DIRENT_TYPE] = DIRENT_TYPE_ROOTDIR; + + writeTemp = BitConverter.GetBytes((uint)((sb_data_size > 0) ? (uint)sb_data_start : BAT_MAGIC_END_OF_CHAIN)); + Array.Copy(writeTemp, 0, buf, DIRENT_FIRSTBLOCK, writeTemp.Length); + + writeTemp = BitConverter.GetBytes((uint)(sb_data_size)); + Array.Copy(writeTemp, 0, buf, DIRENT_FILE_SIZE, writeTemp.Length); + + // Write the class id + Array.Copy(child.ClassID, 0, buf, DIRENT_CLSID, child.ClassID.Length); + } + else if (child.Type == MSOleOutfileType.MSOLE_DIR) + { + buf[DIRENT_TYPE] = DIRENT_TYPE_DIR; + + writeTemp = BitConverter.GetBytes((uint)(BAT_MAGIC_END_OF_CHAIN)); + Array.Copy(writeTemp, 0, buf, DIRENT_FIRSTBLOCK, writeTemp.Length); + + writeTemp = BitConverter.GetBytes((uint)(0)); + Array.Copy(writeTemp, 0, buf, DIRENT_FILE_SIZE, writeTemp.Length); + + // Write the class id + Array.Copy(child.ClassID, 0, buf, DIRENT_CLSID, child.ClassID.Length); + } + else + { + uint size = (uint)child.Parent.CurrentSize; + if (size != child.Parent.CurrentSize) + Console.Error.WriteLine("File too big"); + + buf[DIRENT_TYPE] = DIRENT_TYPE_FILE; + + writeTemp = BitConverter.GetBytes((uint)(child.FirstBlock)); + Array.Copy(writeTemp, 0, buf, DIRENT_FIRSTBLOCK, writeTemp.Length); + + writeTemp = BitConverter.GetBytes((uint)(size)); + Array.Copy(writeTemp, 0, buf, DIRENT_FILE_SIZE, writeTemp.Length); + } + + writeTemp = BitConverter.GetBytes((ulong)(child.ModTime?.ToFileTime() ?? 0)); + Array.Copy(writeTemp, 0, buf, DIRENT_MODIFY_TIME, writeTemp.Length); + + // Make everything black (red == 0) + buf[DIRENT_COLOUR] = 1; + + GsfOutfileMSOle tmp = child.Container as GsfOutfileMSOle; + next = DIRENT_MAGIC_END; + if (child.Root != child && tmp != null) + { + for (int j = 0; j < tmp.Content_Dir_Children.Count; j++) + { + GsfOutfileMSOle ptr = tmp.Content_Dir_Children[j]; + if (ptr != child) + continue; + + if (j + 1 != tmp.Content_Dir_Children.Count && tmp.Content_Dir_Children[j + 1] != null) + { + GsfOutfileMSOle sibling = tmp.Content_Dir_Children[j + 1]; + next = (uint)sibling.ChildIndex; + } + + break; + } + } + + // Make linked list rather than tree, only use next + writeTemp = BitConverter.GetBytes((uint)(DIRENT_MAGIC_END)); + Array.Copy(writeTemp, 0, buf, DIRENT_PREV, writeTemp.Length); + + writeTemp = BitConverter.GetBytes((uint)(next)); + Array.Copy(writeTemp, 0, buf, DIRENT_NEXT, writeTemp.Length); + + uint child_index = DIRENT_MAGIC_END; + if (child.Type == MSOleOutfileType.MSOLE_DIR && child.Content_Dir_Children != null) + { + GsfOutfileMSOle first = child.Content_Dir_Children[0]; + child_index = (uint)first.ChildIndex; + } + + writeTemp = BitConverter.GetBytes((uint)(child_index)); + Array.Copy(writeTemp, 0, buf, DIRENT_CHILD, writeTemp.Length); + + Sink.Write(DIRENT_SIZE, buf); + } + + PadZero(); + int num_dirent_blocks = CurrentBlock() - dirent_start; + + // Write BAT + int bat_start = CurrentBlock(); + foreach (GsfOutfileMSOle child in elem) + { + if (child.Type == MSOleOutfileType.MSOLE_BIG_BLOCK) + WriteBat(Sink, child.FirstBlock, child.Blocks); + } + + if (sb_data_blocks > 0) + WriteBat(Sink, sb_data_start, sb_data_blocks); + + if (num_sbat > 0) + WriteBat(Sink, sbat_start, num_sbat); + + WriteBat(Sink, dirent_start, num_dirent_blocks); + + // List the BAT and meta-BAT blocks in the BAT. Doing this may + // increase the size of the bat and hence the metabat, so be + // prepared to iterate. + long num_bat = 0; + long num_xbat = 0; + + while (Sink.Error == null) + { + // If we have an error, then the actual size as reported + // by _tell and .cur_size may be out of sync. We don't + // want to loop forever here. + + long i = ((Sink.CurrentSize + BAT_INDEX_SIZE * (num_bat + num_xbat) - OLE_HEADER_SIZE - 1) >> BigBlock.Shift) + 1; + i -= bat_start; + if (num_bat != i) + { + num_bat = i; + continue; + } + i = 0; + if (num_bat > OLE_HEADER_METABAT_SIZE) + i = 1 + ((num_bat - OLE_HEADER_METABAT_SIZE - 1) + / metabat_size); + if (num_xbat != i) + { + num_xbat = i; + continue; + } + + break; + } + + WriteConst(Sink, BAT_MAGIC_BAT, (int)num_bat); + WriteConst(Sink, BAT_MAGIC_METABAT, (int)num_xbat); + PadBatUnused(0); + + if (num_xbat > 0) + { + xbat_pos = (uint)CurrentBlock(); + blocks = OLE_HEADER_METABAT_SIZE; + } + else + { + xbat_pos = BAT_MAGIC_END_OF_CHAIN; + blocks = (int)num_bat; + } + + byte[] outerTemp; + + // Fix up the header + if (BigBlock.Size == 4096) + { + // Set _cSectDir for 4k sector files + outerTemp = BitConverter.GetBytes((uint)(num_dirent_blocks)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + + Sink.Seek(OLE_HEADER_CSECTDIR, SeekOrigin.Begin); + Sink.Write(4, buf); + } + + Sink.Seek(OLE_HEADER_NUM_BAT, SeekOrigin.Begin); + outerTemp = BitConverter.GetBytes((uint)(num_bat)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + outerTemp = BitConverter.GetBytes((uint)(dirent_start)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + + Sink.Seek(OLE_HEADER_SBAT_START, SeekOrigin.Begin); + outerTemp = BitConverter.GetBytes((uint)((num_sbat > 0) ? (uint)sbat_start : BAT_MAGIC_END_OF_CHAIN)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + outerTemp = BitConverter.GetBytes((uint)(num_sbat)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + outerTemp = BitConverter.GetBytes((uint)(xbat_pos)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + outerTemp = BitConverter.GetBytes((uint)(num_xbat)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(4, buf); + + // Write initial Meta-BAT + for (int i = 0; i < blocks; i++) + { + outerTemp = BitConverter.GetBytes((uint)(bat_start + i)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(BAT_INDEX_SIZE, buf); + } + + // Write extended Meta-BAT + if (num_xbat > 0) + { + Sink.Seek(0, SeekOrigin.End); + for (long i = 0; i++ < num_xbat;) + { + bat_start += blocks; + num_bat -= blocks; + blocks = (int)((num_bat > metabat_size) ? metabat_size : num_bat); + for (int j = 0; j < blocks; j++) + { + outerTemp = BitConverter.GetBytes((uint)(bat_start + j)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(BAT_INDEX_SIZE, buf); + } + + if (i == num_xbat) + { + PadBatUnused(1); + xbat_pos = BAT_MAGIC_END_OF_CHAIN; + } + else + { + xbat_pos++; + } + + outerTemp = BitConverter.GetBytes((uint)(xbat_pos)); + Array.Copy(outerTemp, 0, buf, 0, outerTemp.Length); + Sink.Write(BAT_INDEX_SIZE, buf); + } + } + + // Free the children + Content_Dir_RootOrder = null; + + return true; + } + + private void HoistError() + { + if (Error == null && Sink.Error != null) + Error = Sink.Error; + } + + private void RegisterChild(GsfOutfileMSOle child) + { + child.Root = this; + child.ChildIndex = Content_Dir_RootOrder.Count; + Content_Dir_RootOrder.Add(child); + } + + private static int NameCompare(GsfOutfileMSOle a, GsfOutfileMSOle b) => GsfInfileMSOLE.SortingKeyCompare(a.Key, b.Key); + + private void MakeSortingName() + { + Key = GsfMSOleSortingKey.Create(Name); + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutfileStdio.cs b/BurnOutSharp/External/libgsf/Output/GsfOutfileStdio.cs new file mode 100644 index 00000000..5214434e --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutfileStdio.cs @@ -0,0 +1,97 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-outfile-stdio.c: A directory tree wrapper for Outfile + * + * Copyright (C) 2004-2006 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Output +{ + public class GsfOutfileStdio : GsfOutfile + { + #region Properties + + public string Root { get; set; } = null; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfOutfileStdio() { } + + /// + /// + /// + /// Root directory in utf8. + /// Place to store an Exception if anything goes wrong + /// A new outfile or null. + public static GsfOutfileStdio Create(string root, ref Exception err) + { + if (!Directory.Exists(root)) + { + try + { + Directory.CreateDirectory(root); + } + catch (Exception ex) + { + err = new Exception($"{root}: {ex.Message}"); + return null; + } + } + + GsfOutfileStdio ofs = new GsfOutfileStdio + { + Root = root, + }; + + ofs.SetNameFromFilename(root); + return ofs; + } + + #endregion + + #region Functions + + /// + public override GsfOutput NewChild(string name, bool is_dir) + { + string path = Path.Combine(Root, name); + + Exception err = null; + + GsfOutput child; + if (is_dir) + child = Create(path, ref err); + else + child = GsfOutputStdio.Create(path, ref err); + + return child; + } + + /// + protected override bool CloseImpl() => true; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutfileZip.cs b/BurnOutSharp/External/libgsf/Output/GsfOutfileZip.cs new file mode 100644 index 00000000..4ad3d736 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutfileZip.cs @@ -0,0 +1,881 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-outfile-zip.c: zip archive output. + * + * Copyright (C) 2002-2006 Jon K Hellan (hellan@acm.org) + * Copyright (C) 2014 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ComponentAce.Compression.Libs.zlib; +using static ComponentAce.Compression.Libs.zlib.zlibConst; +using static LibGSF.GsfZipImpl; + +namespace LibGSF.Output +{ + public class GsfOutfileZip : GsfOutfile + { + #region Constants + + public static readonly bool? ZIP_CREATE_DEFAULT_ZIP64 = null; + + public const bool ZIP_ADD_UNIXTIME_FIELD = true; + + #endregion + + #region Properties + + public GsfOutput Sink { get; set; } = null; + + public GsfOutfileZip Root { get; set; } = null; + + public bool? SinkIsSeekable { get; set; } = null; + + public bool? Zip64 { get; set; } = ZIP_CREATE_DEFAULT_ZIP64; + + public string EntryName { get; set; } = null; + + public GsfZipVDir VDir { get; set; } = null; + + /// + /// Only valid for the root, ordered + /// + public List RootOrder { get; set; } = null; + + public ZStream Stream { get; set; } = null; + + public GsfZipCompressionMethod CompressionMethod { get; set; } = GsfZipCompressionMethod.GSF_ZIP_DEFLATED; + + public int DeflateLevel { get; set; } = Z_DEFAULT_COMPRESSION; + + public bool Writing { get; set; } = false; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutfileZip() { } + + /// + /// Creates the root directory of a Zip file and manages the addition of + /// children. + /// + /// A GsfOutput to hold the ZIP file + /// Location to store error, or null; currently unused. + /// The new zip file handler + /// This adds a reference to . + public static GsfOutfileZip Create(GsfOutput sink, ref Exception err) + { + if (sink == null) + return null; + + err = null; + GsfOutfileZip zip = new GsfOutfileZip { Sink = sink }; + if (zip.EntryName == null) + { + zip.VDir = GsfZipVDir.Create(string.Empty, true, null); + zip.RootOrder = new List(); + zip.Root = zip; + + // The names are the same + zip.Name = zip.Sink.Name; + zip.Container = null; + } + + if (zip.ModTime == null) + { + DateTime? modtime = DateTime.UtcNow; + zip.ModTime = modtime; + } + + return zip; + } + + /// + /// Destructor + /// + ~GsfOutfileZip() + { + // If the closing failed, we might have stuff here. + DisconnectChildren(); + + if (Sink != null) + SetSink(null); + + if (Stream != null) + Stream.deflateEnd(); + + if (this == Root) + VDir.Free(true); // Frees vdirs recursively + } + + #endregion + + #region Functions + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => false; + + /// + protected override bool CloseImpl() + { + bool ret; + + // The root dir + if (this == Root) + { + ret = CloseRoot(); + } + else if (VDir.IsDirectory) + { + // Directories: Do nothing. Should change this to actually + // write dirs which don't have children. + ret = true; + } + else + { + ret = CloseStream(); + } + + return ret; + } + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (VDir == null) + return false; + if (VDir.IsDirectory) + return false; + if (data == null) + return false; + + int ret; + + if (!Writing) + { + if (!InitWrite()) + return false; + } + + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + + if (dirent.Zip64 == false && + (num_bytes >= uint.MaxValue || CurrentOffset >= (long)(uint.MaxValue - num_bytes))) + { + // Uncompressed size field would overflow. + return false; + } + + if (CompressionMethod == GsfZipCompressionMethod.GSF_ZIP_DEFLATED) + { + Stream.next_in = data; + Stream.avail_in = num_bytes; + + while (Stream.avail_in > 0) + { + if (Stream.avail_out == 0) + { + if (!OutputBlock()) + return false; + } + + ret = Stream.deflate(Z_NO_FLUSH); + if (ret != Z_OK) + return false; + } + } + else + { + if (!Sink.Write(num_bytes, data)) + return false; + + dirent.CompressedSize += num_bytes; + } + + // TODO: Enable CRC32 calculation + //dirent.CRC32 = crc32(dirent.crc32, data, num_bytes); + dirent.UncompressedSize += num_bytes; + + return true; + } + + public void RootRegisterChild(GsfOutfileZip child) + { + child.Root = this; + if (!child.VDir.IsDirectory) + Root.RootOrder.Add(child); + } + + public void SetSink(GsfOutput sink) + { + Sink = sink; + SinkIsSeekable = null; + } + + public override GsfOutput NewChild(string name, bool is_dir) + { + if (VDir == null) + return null; + if (!VDir.IsDirectory) + return null; + if (string.IsNullOrEmpty(name)) + return null; + + GsfOutfileZip child = new GsfOutfileZip + { + Name = name, + Zip64 = this.Zip64, + VDir = GsfZipVDir.Create(name, is_dir, null), + Container = this, + }; + + if (child.EntryName == null) + { + child.VDir = GsfZipVDir.Create(string.Empty, true, null); + child.RootOrder = new List(); + child.Root = child; + + // The names are the same + child.Name = child.Sink.Name; + child.Container = null; + } + + if (child.ModTime == null) + { + DateTime? modtime = DateTime.UtcNow; + child.ModTime = modtime; + } + + VDir.AddChild(child.VDir); + Root.RootRegisterChild(child); + + return child; + } + + #endregion + + #region Utilities + + private void DisconnectChildren() + { + if (RootOrder == null) + return; + + for (int i = 0; i < RootOrder.Count; i++) + { + RootOrder[i] = null; + } + + RootOrder = null; + } + + /// + /// The "mimetype" member is special for ODF. It cannot have any + /// extra field (and thus cannot be a zip64 member). Hardcode + /// this to help compatibility with libgsf users depending on + /// past behaviour of zip creation. + /// + /// The flip side is that such a file cannot be 4G+. + /// + /// + private static bool SpecialMimetypeDirectoryEntry(GsfZipDirectoryEntry dirent) + { + return (dirent.Offset == 0 + && dirent.Zip64 != true + && dirent.CompressionMethod == GsfZipCompressionMethod.GSF_ZIP_STORED + && dirent.Name == "mimetype"); + } + + private bool DirectoryEntryWrite(GsfZipDirectoryEntry dirent) + { + int nlen = dirent.Name.Length; + List extras = new List(ZIP_DIRENT_SIZE + nlen + 100); + bool offset_in_zip64 = dirent.Offset >= uint.MaxValue; + bool zip64_here = (dirent.Zip64 == true || offset_in_zip64); + byte extract = (byte)(zip64_here ? 45 : 20); // Unsure if dirent.zip64 is enough + + if (zip64_here) + { + List tmp = new List(); + + // We could unconditionally store the offset here, but + // zipinfo has a known bug in which it fails to account + // for differences in extra fields between the global + // and the local headers. So we try to make them the + // same. + + tmp.AddRange(BitConverter.GetBytes((ushort)(ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_ZIP64))); + tmp.AddRange(BitConverter.GetBytes((ushort)((2 + (offset_in_zip64 ? 1 : 0) * 8)))); + tmp.AddRange(BitConverter.GetBytes((ulong)(dirent.UncompressedSize))); + tmp.AddRange(BitConverter.GetBytes((ulong)(dirent.CompressedSize))); + if (offset_in_zip64) + tmp.AddRange(BitConverter.GetBytes((ulong)(dirent.Offset))); + + extras.AddRange(tmp); + } + else if (dirent.Zip64 == null) + { + List tmp = new List(); + + // Match the local header. + tmp.AddRange(BitConverter.GetBytes((ushort)(ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_IGNORE))); + tmp.AddRange(BitConverter.GetBytes((ushort)(2 * 8))); + tmp.AddRange(BitConverter.GetBytes((ulong)(0))); + tmp.AddRange(BitConverter.GetBytes((ulong)(0))); + + extras.AddRange(tmp); + } + + if (ZIP_ADD_UNIXTIME_FIELD && dirent.ModifiedTime != null && !SpecialMimetypeDirectoryEntry(dirent)) + { + // Clearly a year 2038 problem here. + List tmp = new List(); + + tmp.AddRange(BitConverter.GetBytes((ushort)(ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_UNIXTIME))); + tmp.AddRange(BitConverter.GetBytes((ushort)(5))); + tmp.Add(1); + tmp.AddRange(BitConverter.GetBytes((uint)(new DateTimeOffset(dirent.ModifiedTime ?? DateTime.UtcNow).ToUnixTimeSeconds()))); + + extras.AddRange(tmp); + } + + List buf = new List(ZIP_DIRENT_SIZE); + + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_DIRENT_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((ushort)(((int)OSCodes.ZIP_OS_UNIX << 8) + extract))); + buf.AddRange(BitConverter.GetBytes((ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract))); + buf.AddRange(BitConverter.GetBytes((ushort)(dirent.Flags))); + buf.AddRange(BitConverter.GetBytes((ushort)(dirent.CompressionMethod))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.DosTime))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.CRC32))); + buf.AddRange(BitConverter.GetBytes((uint)(zip64_here ? uint.MaxValue : dirent.CompressedSize))); + buf.AddRange(BitConverter.GetBytes((uint)(zip64_here ? uint.MaxValue : dirent.UncompressedSize))); + buf.AddRange(BitConverter.GetBytes((ushort)(nlen))); + buf.AddRange(BitConverter.GetBytes((ushort)(extras.Count))); + buf.AddRange(BitConverter.GetBytes((ushort)(0))); + buf.AddRange(BitConverter.GetBytes((ushort)(0))); + buf.AddRange(BitConverter.GetBytes((ushort)(0))); + + // Hardcode file mode 644 + buf.AddRange(BitConverter.GetBytes((uint)(0100644u << 16))); + buf.AddRange(BitConverter.GetBytes((uint)(offset_in_zip64 ? uint.MaxValue : dirent.Offset))); + + // Stuff everything into buf so we can do just one write. + buf.AddRange(extras); + buf.AddRange(Encoding.ASCII.GetBytes(dirent.Name)); + + return Sink.Write(buf.Count, buf.ToArray()); + } + + private bool TrailerWrite(int entries, long dirpos, long dirsize) + { + List buf = new List(ZIP_TRAILER_SIZE); + + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_TRAILER_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((ushort)(Math.Min(entries, ushort.MaxValue)))); + buf.AddRange(BitConverter.GetBytes((ushort)(Math.Min(dirsize, ushort.MaxValue)))); + buf.AddRange(BitConverter.GetBytes((ushort)(Math.Min(dirpos, ushort.MaxValue)))); + + return Sink.Write(buf.Count, buf.ToArray()); + } + + private bool Trailer64Write(int entries, long dirpos, long dirsize) + { + List buf = new List(ZIP_TRAILER64_SIZE); + byte extract = 45; + + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_TRAILER64_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((ulong)(ZIP_TRAILER64_SIZE - 12))); + buf.AddRange(BitConverter.GetBytes((ushort)(((int)OSCodes.ZIP_OS_UNIX << 8) + extract))); + buf.AddRange(BitConverter.GetBytes((ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract))); + buf.AddRange(BitConverter.GetBytes((uint)(0))); + buf.AddRange(BitConverter.GetBytes((uint)(0))); + buf.AddRange(BitConverter.GetBytes((uint)(entries))); + buf.AddRange(BitConverter.GetBytes((uint)(entries))); + buf.AddRange(BitConverter.GetBytes((ulong)(dirsize))); + buf.AddRange(BitConverter.GetBytes((ulong)(dirpos))); + + return Sink.Write(buf.Count, buf.ToArray()); + } + + private bool Zip64LocatorWrite(long trailerpos) + { + List buf = new List(ZIP_ZIP64_LOCATOR_SIZE); + + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_ZIP64_LOCATOR_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((uint)(0))); + buf.AddRange(BitConverter.GetBytes((ulong)(trailerpos))); + buf.AddRange(BitConverter.GetBytes((uint)(1))); + + return Sink.Write(buf.Count, buf.ToArray()); + } + + private static int OffsetOrdering(GsfOutfileZip a, GsfOutfileZip b) + { + long diff = a.VDir.DirectoryEntry.Offset - b.VDir.DirectoryEntry.Offset; + return diff < 0 ? -1 : diff > 0 ? +1 : 0; + } + + private bool CloseRoot() + { + bool? zip64 = Zip64; + + // Check that children are closed + for (int i = 0; i < RootOrder.Count; i++) + { + GsfOutfileZip child = RootOrder[i]; + GsfZipDirectoryEntry dirent = child.VDir.DirectoryEntry; + if (dirent.Zip64 == true) + zip64 = true; + + if (!child.IsClosed) + { + Console.Error.WriteLine("Child still open"); + return false; + } + } + + if (true) + { + // It is unclear whether we need this. However, the + // zipdetails utility gets utterly confused if we do + // not. + // + // If we do not sort, we will use the ordering in which + // the members were actually being written. Note, that + // merely creating the member doesn't count -- it's the + // actual writing (or closing an empty member) that + // counts. + + RootOrder.Sort(OffsetOrdering); + } + + // Write directory + long dirpos = Sink.CurrentOffset; + for (int i = 0; i < RootOrder.Count; i++) + { + GsfOutfileZip child = RootOrder[i]; + GsfZipDirectoryEntry dirent = child.VDir.DirectoryEntry; + if (!DirectoryEntryWrite(dirent)) + return false; + } + + long dirend = Sink.CurrentOffset; + + if (RootOrder.Count >= ushort.MaxValue || dirend >= uint.MaxValue - ZIP_TRAILER_SIZE) + { + // We don't have a choice; force zip64. + zip64 = true; + } + + DisconnectChildren(); + + if (zip64 == null) + zip64 = false; + + if (zip64 == true) + { + if (!Trailer64Write(RootOrder.Count, dirpos, dirend - dirpos)) + return false; + + if (!Zip64LocatorWrite(dirend)) + return false; + } + + return TrailerWrite(RootOrder.Count, dirpos, dirend - dirpos); + } + + private void StreamNameWriteToBuf(string res) + { + if (this == Root) + return; + + if (Container != null) + { + (Container as GsfOutfileZip).StreamNameWriteToBuf(res); + if (res.Length != 0) + { + // Forward slash is specified by the format. + res += ZIP_NAME_SEPARATOR; + } + } + + if (EntryName != null) + res += EntryName; + } + + private string StreamNameBuild() + { + string str = new string('\0', 80); + StreamNameWriteToBuf(str); + return str; + } + + private static uint ZipTimeMake(DateTime? modtime) => (uint)(modtime ?? DateTime.UtcNow).ToFileTime(); + + private GsfZipDirectoryEntry NewDirectoryEntry() + { + string name = StreamNameBuild(); + + // The spec is a bit vague about the length limit for file names, but + // clearly we should not go beyond 0xffff. + if (name.Length < ushort.MaxValue) + { + GsfZipDirectoryEntry dirent = GsfZipDirectoryEntry.Create(); + DateTime? modtime = ModTime; + + dirent.Name = name; + dirent.CompressionMethod = CompressionMethod; + + if (modtime == null) + modtime = DateTime.UtcNow; + + dirent.DosTime = ZipTimeMake(modtime); + dirent.ModifiedTime = modtime; + dirent.Zip64 = Zip64; + + return dirent; + } + else + { + return null; + } + } + + private bool HeaderWrite() + { + List hbuf = new List(ZIP_HEADER_SIZE); + + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + string name = dirent.Name; + int nlen = name.Length; + + hbuf.AddRange(BitConverter.GetBytes((uint)(ZIP_HEADER_SIGNATURE))); + + if (SinkIsSeekable == null) + { + // We need to figure out if the sink is seekable, but we lack + // an API to check that. Instead, write the signature and + // try to seek back onto it. If seeking back fails, just + // don't rewrite it. + + if (!Sink.Write(4, hbuf.ToArray())) + return false; + + SinkIsSeekable = Sink.Seek(dirent.Offset, SeekOrigin.Begin); + if (SinkIsSeekable == false) + hbuf.Clear(); + } + + // Now figure out if we need a DDESC record. + if (SinkIsSeekable == true) + dirent.Flags &= ~ZIP_DIRENT_FLAGS_HAS_DDESC; + else + dirent.Flags |= ZIP_DIRENT_FLAGS_HAS_DDESC; + + bool has_ddesc = (dirent.Flags & ZIP_DIRENT_FLAGS_HAS_DDESC) != 0; + uint crc32 = has_ddesc ? 0 : dirent.CRC32; + long csize = has_ddesc ? 0 : dirent.CompressedSize; + long usize = has_ddesc ? 0 : dirent.UncompressedSize; + + // Determine if we need a real zip64 extra field. We do so, if + // - forced + // - in auto mode, if usize or csize has overflowed + // - in auto mode, if we use a DDESC + bool real_zip64 = (dirent.Zip64 == true + || (dirent.Zip64 == null + && (has_ddesc + || dirent.UncompressedSize >= uint.MaxValue + || dirent.CompressedSize >= uint.MaxValue))); + + byte extract = 20; + if (real_zip64) + extract = 45; + + List extras = new List(ZIP_HEADER_SIZE + nlen + 100); + + // In the has_ddesc case, we write crc32/size/usize as zero and store + // the right values in the DDESC record that follows the data. + // + // In the !has_ddesc case, we return to the same spot and write the + // header a second time correcting crc32/size/usize, see + // see HeaderPatchSizes. For this reason, we must ensure that + // the record's length does not depend on the the sizes. + // + // In the the has_ddesc case we store zeroes here. No idea what we + // were supposed to write. + + // Auto or forced + if (dirent.Zip64 != false) + { + List tmp = new List(); + ExtraFieldTags typ = real_zip64 + ? ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_ZIP64 + : ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_IGNORE; + + tmp.AddRange(BitConverter.GetBytes((ushort)(typ))); + tmp.AddRange(BitConverter.GetBytes((ushort)(2 * 8))); + tmp.AddRange(BitConverter.GetBytes((ulong)(usize))); + tmp.AddRange(BitConverter.GetBytes((ulong)(csize))); + + extras.AddRange(tmp); + } + + if (ZIP_ADD_UNIXTIME_FIELD && dirent.ModifiedTime != null && !SpecialMimetypeDirectoryEntry(dirent)) + { + List tmp = new List(); + + // Clearly a year 2038 problem here. + tmp.AddRange(BitConverter.GetBytes((ushort)(ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_UNIXTIME))); + tmp.AddRange(BitConverter.GetBytes((ushort)(5))); + tmp.Add(1); + tmp.AddRange(BitConverter.GetBytes((uint)(new DateTimeOffset(dirent.ModifiedTime ?? DateTime.UtcNow).ToUnixTimeSeconds()))); + + extras.AddRange(tmp); + } + + hbuf.AddRange(BitConverter.GetBytes((ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract))); + hbuf.AddRange(BitConverter.GetBytes((ushort)(dirent.Flags))); + hbuf.AddRange(BitConverter.GetBytes((ushort)(dirent.CompressionMethod))); + hbuf.AddRange(BitConverter.GetBytes((uint)(dirent.DosTime))); + hbuf.AddRange(BitConverter.GetBytes((uint)(crc32))); + hbuf.AddRange(BitConverter.GetBytes((uint)(real_zip64 && !has_ddesc ? uint.MaxValue : csize))); + hbuf.AddRange(BitConverter.GetBytes((uint)(real_zip64 && !has_ddesc ? uint.MaxValue : usize))); + hbuf.AddRange(BitConverter.GetBytes((ushort)(nlen))); + hbuf.AddRange(BitConverter.GetBytes((ushort)(extras.Count))); + + // Stuff everything into buf so we can do just one write. + hbuf.AddRange(extras); + hbuf.AddRange(Encoding.ASCII.GetBytes(name)); + + bool ret = Sink.Write(hbuf.Count, hbuf.ToArray()); + if (real_zip64) + dirent.Zip64 = true; + + return ret; + } + + private bool InitWrite() + { + int ret; + + if (Root.Writing) + { + Console.Error.WriteLine("Already writing to another stream in archive"); + return false; + } + + if (!Wrap(Sink)) + return false; + + GsfZipDirectoryEntry dirent = NewDirectoryEntry(); + if (dirent == null) + { + Unwrap(Sink); + return false; + } + + dirent.Offset = Sink.CurrentOffset; + if (SpecialMimetypeDirectoryEntry(dirent)) + dirent.Zip64 = false; + + VDir.DirectoryEntry = dirent; + + HeaderWrite(); + Writing = true; + Root.Writing = true; + + // TODO: Enable CRC32 calculation + // dirent.CRC32 = crc32(0L, Z_null, 0); + if (CompressionMethod == GsfZipCompressionMethod.GSF_ZIP_DEFLATED) + { + if (Stream == null) + Stream = new ZStream(); + + ret = Stream.inflateInit(-15); + if (ret != Z_OK) + return false; + + ret = Stream.deflateParams(DeflateLevel, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + return false; + + if (Buf == null) + { + BufSize = ZIP_BUF_SIZE; + Buf = new byte[BufSize]; + } + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + } + + return true; + } + + private bool OutputBlock() + { + int num_bytes = BufSize - Stream.avail_out; + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + + if (!Sink.Write(num_bytes, Buf)) + return false; + + dirent.CompressedSize += num_bytes; + if (dirent.Zip64 == false && dirent.CompressedSize >= uint.MaxValue) + return false; + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + + return true; + } + + private bool Flush() + { + int zret; + do + { + zret = Stream.deflate(Z_FINISH); + if (zret == Z_OK || (zret == Z_BUF_ERROR && Stream.avail_out == 0)) + { + // In this case Z_OK or Z_BUF_ERROR means more buffer space is needed + if (!OutputBlock()) + return false; + } + } while (zret == Z_OK || zret == Z_BUF_ERROR); + + if (zret != Z_STREAM_END) + return false; + + if (!OutputBlock()) + return false; + + return true; + } + + /// + /// Write the per stream data descriptor + /// + private bool DataDescriptorWrite() + { + List buf = new List(Math.Max(ZIP_DDESC_SIZE, ZIP_DDESC64_SIZE)); + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + int size; + + // Documentation says signature is not official. + + if (dirent.Zip64 != false) + { + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_DDESC64_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.CRC32))); + buf.AddRange(BitConverter.GetBytes((ulong)(dirent.CompressedSize))); + buf.AddRange(BitConverter.GetBytes((ulong)(dirent.UncompressedSize))); + size = ZIP_DDESC64_SIZE; + } + else + { + buf.AddRange(BitConverter.GetBytes((uint)(ZIP_DDESC_SIGNATURE))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.CRC32))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.CompressedSize))); + buf.AddRange(BitConverter.GetBytes((uint)(dirent.UncompressedSize))); + size = ZIP_DDESC_SIZE; + } + + if (!Sink.Write(size, buf.ToArray())) + return false; + + return true; + } + + private bool HeaderPatchSizes() + { + GsfZipDirectoryEntry dirent = VDir.DirectoryEntry; + long pos = Sink.CurrentOffset; + + // Rewrite the header in the same location again. + bool ok = (Sink.Seek(dirent.Offset, SeekOrigin.Begin) + && HeaderWrite() + && Sink.Seek(pos, SeekOrigin.Begin)); + + if (ok && dirent.Zip64 == null) + { + // We just wrote the final header. Since we still are in + // auto-mode, the header did not use a real zip64 extra + // field. Hence we don't need such a field. + dirent.Zip64 = false; + } + + return ok; + } + + private bool CloseStream() + { + if (!Writing) + { + if (!InitWrite()) + return false; + } + + if (CompressionMethod == GsfZipCompressionMethod.GSF_ZIP_DEFLATED) + { + if (!Flush()) + return false; + } + + if ((VDir.DirectoryEntry.Flags & ZIP_DIRENT_FLAGS_HAS_DDESC) != 0) + { + // Write data descriptor + if (!DataDescriptorWrite()) + return false; + } + else + { + // Write crc, sizes + if (!HeaderPatchSizes()) + return false; + } + + Root.Writing = false; + + bool result = Unwrap(Sink); + + // Free unneeded memory + if (Stream != null) + { + Stream.deflateEnd(); + Stream = null; + Buf = null; + } + + return result; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutput.cs b/BurnOutSharp/External/libgsf/Output/GsfOutput.cs new file mode 100644 index 00000000..ef76a92a --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutput.cs @@ -0,0 +1,286 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output.c: interface for storing data + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Text; + +namespace LibGSF.Output +{ + public abstract class GsfOutput : IDisposable + { + #region Properties + + public long CurrentSize { get; protected set; } = 0; + + public long CurrentOffset { get; protected set; } = 0; + + public string Name { get; protected set; } = null; + + public object WrappedBy { get; protected set; } = null; + + public GsfOutfile Container { get; protected set; } = null; + + public Exception Error { get; protected set; } = null; + + public bool IsClosed { get; protected set; } = false; + + public string PrintFBuf { get; protected set; } = null; + + public int PrintFBufSize { get; protected set; } = 0; + + public DateTime? ModTime { get; protected set; } + + #endregion + + #region Functions + + /// + /// Close a stream. + /// + /// false on error + public bool Close() + { + if (IsClosed) + return SetError(0, ""); + + // The implementation will log any errors, but we can never try to + // close multiple times even on failure. + bool res = CloseImpl(); + IsClosed = true; + return res; + } + + /// + /// Reposition in output stream @output. @whence specifies what the offset is + /// relative to: the beginning of the stream (SeekOrigin.Begin), current position in + /// the stream (SeekOrigin.Current) or the end of the stream (SeekOrigin.End). + /// This function is similar to fseek(3) + /// + /// Relative amount to reposition + /// What the offset is relative to. + /// false on error. + public bool Seek(long offset, SeekOrigin whence) + { + long pos = offset; + + switch (whence) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: pos += CurrentOffset; break; + case SeekOrigin.End: pos += CurrentSize; break; + default: + Console.Error.WriteLine($"Invalid seek type {whence}"); + return false; + } + + if (pos < 0) + { + Console.Error.WriteLine($"Invalid seek position {pos}, which is before the start of the file"); + return false; + } + + // If we go nowhere, just return. This in particular handles null + // seeks for streams with no seek method. + if (pos == CurrentOffset) + return true; + + if (SeekImpl(offset, whence)) + { + // NOTE : it is possible for the current pos to be beyond the + // end of the file. The intervening space is not filled with 0 + // until something is written. + CurrentOffset = pos; + return true; + } + + // The implementation should have assigned whatever errors are necessary + return false; + } + + /// + /// Write of to output. + /// + /// Number of bytes to write + /// Data to write. + /// %false on error. + public bool Write(int num_bytes, byte[] data) + { + if (num_bytes == 0) + return true; + + if (WriteImpl(num_bytes, data)) + return IncrementCurrentOffset(num_bytes); + + // The implementation should have assigned whatever errors are necessary + return false; + } + + /// true if the wrapping succeeded. + public bool Wrap(object wrapper) + { + if (wrapper == null) + return false; + + if (WrappedBy != null) + { + Console.Error.WriteLine("Attempt to wrap an output that is already wrapped."); + return false; + } + + WrappedBy = wrapper; + return true; + } + + public bool Unwrap(object wrapper) + { + if (WrappedBy != wrapper) + return false; + + WrappedBy = null; + return true; + } + + /// + /// Output to output using the format string , similar to printf(3) + /// + /// The printf-style format string + /// The arguments for @format + /// true if successful, false if not + public bool PrintF(string format, params string[] va) => VPrintF(format, va) >= 0; + + /// + /// Output to output using the format string , similar to vprintf(3) + /// + /// The printf-style format string + /// The arguments for @format + /// Number of bytes printed, a negative value if not successful + public long VPrintF(string format, params string[] args) + { + if (format == null) + return -1; + + long num_bytes = VPrintFImpl(format, args); + if (num_bytes >= 0) + { + if (!IncrementCurrentOffset(num_bytes)) + return -1; + } + + return num_bytes; + } + + /// + /// Like fputs, this assumes that the line already ends with a newline + /// + /// Nul terminated string to write + /// %true if successful, %false if not + public bool PutString(string line) + { + if (line == null) + return false; + + int nbytes = line.Length; + return Write(nbytes, Encoding.UTF8.GetBytes($"{line}")); + } + + #endregion + + #region Virtual Functions + + public virtual void Dispose() + { + if (!IsClosed) + { + Console.Error.WriteLine("Disposing of an unclosed stream"); + Close(); + } + + Container = null; + Name = null; + ModTime = null; + + PrintFBuf = null; + + Error = null; + } + + protected virtual bool CloseImpl() => false; + + protected virtual bool SeekImpl(long offset, SeekOrigin whence) => false; + + protected virtual bool WriteImpl(int num_bytes, byte[] data) => false; + + protected virtual long VPrintFImpl(string format, params string[] args) => -1; + + #endregion + + #region Utilities + + private bool IncrementCurrentOffset(long num_bytes) + { + CurrentOffset += num_bytes; + if (CurrentOffset < num_bytes) + return SetError(0, "Output size overflow."); + + if (CurrentSize < CurrentOffset) + CurrentSize = CurrentOffset; + + return true; + } + + /// The (fs-sys encoded) filename + /// %true if the assignment was ok. + /// This is a utility routine that should only be used by derived outputs. + protected bool SetNameFromFilename(string filename) + { + string name = null; + if (filename != null) + { + byte[] filenameBytes = Encoding.Unicode.GetBytes(filename); + filenameBytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, filenameBytes); + name = Encoding.UTF8.GetString(filenameBytes); + } + + Name = name; + return true; + } + + /// The error id + /// printf style format string + /// arguments for @format + /// Always returns false to facilitate its use. + /// This is a utility routine that should only be used by derived outputs. + protected bool SetError(int code, string format, params string[] va) + { + Error = null; + if (format != null) + { + string message = string.Format(format, va); + Error = new Exception(message); // TODO: How to include `code`? + } + + return false; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputBzip.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputBzip.cs new file mode 100644 index 00000000..a62192ed --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputBzip.cs @@ -0,0 +1,212 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-bzip.c: wrapper to compress to bzipped output + * + * Copyright (C) 2003-2006 Dom Lachowicz (cinamod@hotmail.com) + * 2002-2006 Jon K Hellan (hellan@acm.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Output +{ + // TODO: Implement BZIP writing + public class GsfOutputBzip : GsfOutput + { + #region Constants + + private const int BZ_BUFSIZE = 1024; + + #endregion + + #region Properties + +#if BZIP2 + /// + /// Compressed data + /// + public GsfOutput Sink { get; set; } = null; + + public bz_stream Stream { get; set; } = new bz_stream(); + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; +#endif + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfOutputBzip() { } + + /// The underlying data source. + /// A new file or null. + /// Adds a reference to . + public static GsfOutputBzip Create(GsfOutput sink, ref Exception err) + { +#if BZIP2 + if (sink == null) + return null; + + GsfOutputBzip bzip = new GsfOutputBzip + { + Sink = sink, + }; + + if (!InitBzip(ref err)) + return null; + + return bzip; +#else + err = new Exception("BZ2 support not enabled"); + return null; +#endif + } + + #endregion + + #region Functions + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { +#if BZIP2 + if (data == null) + return false; + + Stream.next_in = data; + Stream.avail_in = num_bytes; + + while (Stream.avail_in > 0) + { + if (Stream.avail_out == 0) + { + if (!OutputBlock()) + return false; + } + + int zret = BZ2_bzCompress(&bzip.stream, BZ_RUN); + if (zret != BZ_RUN_OK) + { + Console.Error.WriteLine($"Unexpected error code {zret} from bzlib during compression."); + return false; + } + } + + if (Stream.avail_out == 0) + { + if (!OutputBlock()) + return false; + } + + return true; +#else + return false; +#endif + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => false; + + protected override bool CloseImpl() + { +#if BZIP2 + bool rt = Flush(); + BZ2_bzCompressEnd(Stream); + + return rt; +#else + return false; +#endif + } + + #endregion + + #region Utilities + +#if BZIP2 + private bool InitBzip(ref Exception err) + { + int ret = BZ2_bzCompressInit(Stream, 6, 0, 0); + + if (ret != BZ_OK) + { + err = new Exception("Unable to initialize BZ2 library"); + return false; + } + + if (Buf == null) + { + BufSize = BZ_BUFSIZE; + Buf = new byte[BufSize]; + } + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + + return true; + } + + private bool OutputBlock() + { + int num_bytes = BufSize - Stream.avail_out; + + if (!Sink.Write(num_bytes, Buf)) + return false; + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + + return true; + } + + private bool Flush() + { + int zret; + + do + { + zret = BZ2_bzCompress(Stream, BZ_FINISH); + if (zret == BZ_FINISH_OK) + { + // In this case BZ_FINISH_OK means more buffer space needed + if (!OutputBlock()) + return false; + } + } while (zret == BZ_FINISH_OK); + + if (zret != BZ_STREAM_END) + { + Console.Error.WriteLine($"Unexpected error code {zret} from bzlib during compression."); + return false; + } + + if (!OutputBlock()) + return false; + + return true; + } +#endif + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputCsv.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputCsv.cs new file mode 100644 index 00000000..2add96c0 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputCsv.cs @@ -0,0 +1,181 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-csv.c: a GsfOutput to write .csv style files. + * + * Copyright (C) 2005-2006 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System.IO; +using System.Text; + +namespace LibGSF.Output +{ + #region Enums + + /// + /// Controls when to add quotes around fields. + /// + public enum GsfOutputCsvQuotingMode + { + /// + /// Never add quotes around fields + /// + GSF_OUTPUT_CSV_QUOTING_MODE_NEVER, + + /// + /// Add quotes around fields when needed + /// + GSF_OUTPUT_CSV_QUOTING_MODE_AUTO, + + /// + /// Always add quotes around fields + /// + GSF_OUTPUT_CSV_QUOTING_MODE_ALWAYS + } + + #endregion + + #region Classes + + public class GsfOutputCsv : GsfOutput + { + #region Properties + + public GsfOutput Sink { get; set; } + + public string Quote { get; set; } + + public GsfOutputCsvQuotingMode QuotingMode { get; set; } + + public string QuotingTriggers { get; set; } = string.Empty; + + public string EndOfLine { get; set; } = "\n"; + + public string Separator { get; set; } + + public bool FieldsOnLine { get; set; } + + public string Buf { get; set; } = null; + + public bool? QuotingOnWhitespace { get; set; } + + #endregion + + #region Functions + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) => Sink.Write(num_bytes, data); + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => Sink.Seek(offset, whence); + + /// + protected override bool CloseImpl() => true; + + public bool WriteField(string field, int len) + { + if (field == null) + return false; + + bool quote; + bool ok; + + if (len == -1) + len = field.Length; + + int end = len; + if (FieldsOnLine && Separator.Length != 0) + Buf += Separator; + + FieldsOnLine = true; + + switch (QuotingMode) + { + default: + case GsfOutputCsvQuotingMode.GSF_OUTPUT_CSV_QUOTING_MODE_NEVER: + quote = false; + break; + + case GsfOutputCsvQuotingMode.GSF_OUTPUT_CSV_QUOTING_MODE_ALWAYS: + quote = true; + break; + + case GsfOutputCsvQuotingMode.GSF_OUTPUT_CSV_QUOTING_MODE_AUTO: + { + int p = 0; // field[0] + quote = false; + while (p < end) + { + if (QuotingTriggers.Contains(field[p].ToString())) + { + quote = true; + break; + } + + p++; + } + + if (!quote + && field[0] != '\0' + && (char.IsWhiteSpace(field[0]) || char.IsWhiteSpace(field[p - 1])) + && QuotingOnWhitespace != null) + { + quote = true; + } + + break; + } + } + + if (quote && Quote.Length > 0) + { + Buf += quote; + int fieldPtr = 0; // field[0] + while (fieldPtr < end) + { + char c = field[fieldPtr]; + if (this.Quote.Contains(c.ToString())) + Buf += this.Quote; + + Buf += c; + fieldPtr++; + } + + Buf += quote; + } + else + { + Buf += field; + } + + ok = Sink.Write(Buf.Length, Encoding.UTF8.GetBytes(Buf)); + Buf = string.Empty; + + return ok; + } + + public bool WriteEndOfLine() + { + FieldsOnLine = false; + return Sink.Write(EndOfLine.Length, Encoding.UTF8.GetBytes(EndOfLine)); + } + + #endregion + } + + #endregion +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputGZip.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputGZip.cs new file mode 100644 index 00000000..6fbd1540 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputGZip.cs @@ -0,0 +1,298 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-gzip.c: wrapper to compress to gzipped output. See rfc1952. + * + * Copyright (C) 2002-2006 Jon K Hellan (hellan@acm.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ComponentAce.Compression.Libs.zlib; +using static ComponentAce.Compression.Libs.zlib.zlibConst; + +namespace LibGSF.Output +{ + public class GsfOutputGZip : GsfOutput + { + #region Constants + + /// + /// GZip flag byte - The original is stored + /// + private const byte GZIP_ORIGINAL_NAME = 0x08; + + #endregion + + #region Properties + + /// + /// Compressed data + /// + public GsfOutput Sink { get; set; } = null; + + /// + /// No header and no trailer. + /// + public bool Raw { get; set; } + + /// + /// zlib compression level + /// + public int DeflateLevel { get; set; } = Z_DEFAULT_COMPRESSION; + + public ZStream Stream { get; set; } + + /// + /// CRC32 of uncompressed data + /// + public uint CRC { get; set; } = 0; + + public int ISize { get; set; } = 0; + + public bool Setup { get; set; } = false; + + public byte[] Buf { get; set; } = null; + + public int BufSize { get; set; } = 0; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutputGZip() { } + + /// The underlying data source. + /// Optionally null. + /// A new file or null + /// Adds a reference to . + public static GsfOutputGZip Create(GsfOutput sink, ref Exception err) + { + if (sink == null) + return null; + + GsfOutputGZip output = new GsfOutputGZip + { + Sink = sink, + }; + + if (output.Error != null) + { + err = output.Error; + return null; + } + + return output; + } + + /// + /// Destructor + /// + ~GsfOutputGZip() + { + // FIXME: check for error? + Stream.deflateEnd(); + } + + #endregion + + #region Functions + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (data == null) + return false; + + // Write header, if needed + SetupImpl(); + + Stream.next_in = data; + Stream.avail_in = num_bytes; + + while (Stream.avail_in > 0) + { + if (Stream.avail_out == 0) + { + if (!OutputBlock()) + return false; + } + + int zret = Stream.deflate(Z_NO_FLUSH); + if (zret != Z_OK) + { + Error = new Exception("Unexpected compression failure"); + Console.Error.WriteLine($"Unexpected error code {zret} from zlib during compression."); + return false; + } + } + + // TODO: Enable CRC32 calculation + //CRC = crc32(gzip.crc, data, num_bytes); + ISize += num_bytes; + + if (Stream.avail_out == 0) + { + if (!OutputBlock()) + return false; + } + + return true; + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => false; + + /// + protected override bool CloseImpl() + { + // Just in case nothing was ever written + SetupImpl(); + + if (Error != null) + { + if (!Flush()) + return false; + + if (!Raw) + { + List buf = new List(); + + buf.AddRange(BitConverter.GetBytes((uint)(CRC))); + buf.AddRange(BitConverter.GetBytes((uint)(ISize))); + if (!Sink.Write(8, buf.ToArray())) + return false; + } + } + + return true; + } + + #endregion + + #region Utilities + + private bool InitGZip() + { + int ret = Stream.deflateInit(DeflateLevel); + if (ret != Z_OK) + return false; + + ret = Stream.deflateParams(DeflateLevel, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + return false; + + if (Buf == null) + { + BufSize = 0x100; + Buf = new byte[BufSize]; + } + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + + return true; + } + + private bool OutputHeader() + { + List buf = new List(3 + 1 + 4 + 2); + + DateTime? modtime = ModTime; + ulong mtime = (ulong)(modtime != null ? new DateTimeOffset(modtime.Value).ToUnixTimeSeconds() : 0); + + string name = Sink.Name; + // FIXME: What to do about gz extension ... ? + int nlen = 0; // name ? strlen (name) : 0; + + buf.AddRange(new byte[] { 0x1f, 0x8b, 0x08 }); + + if (nlen > 0) + buf.Add(GZIP_ORIGINAL_NAME); + + buf.AddRange(BitConverter.GetBytes((uint)(mtime))); + buf.Add(3); // UNIX + bool ret = Sink.Write(buf.Count, buf.ToArray()); + if (ret && name != null && nlen > 0) + ret = Sink.Write(nlen, Encoding.ASCII.GetBytes(name)); + + return ret; + } + + private void SetupImpl() + { + if (Setup) + return; + + if (!InitGZip()) + Error = new Exception("Failed to initialize zlib structure"); + else if (!Raw && !OutputHeader()) + Error = new Exception("Failed to write gzip header"); + + Setup = true; + } + + private bool OutputBlock() + { + int num_bytes = BufSize - Stream.avail_out; + if (!Sink.Write(num_bytes, Buf)) + { + Error = new Exception("Failed to write"); + return false; + } + + Stream.next_out = Buf; + Stream.avail_out = BufSize; + + return true; + } + + private bool Flush() + { + int zret; + + do + { + zret = Stream.deflate(Z_FINISH); + if (zret == Z_OK) + { + // In this case Z_OK means more buffer space needed + if (!OutputBlock()) + return false; + } + } while (zret == Z_OK); + + if (zret != Z_STREAM_END) + { + Error = new Exception("Unexpected compression failure"); + Console.Error.WriteLine($"Unexpected error code {zret} from zlib during compression."); + return false; + } + + if (!OutputBlock()) + return false; + + return true; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputGio.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputGio.cs new file mode 100644 index 00000000..1ae2b1b7 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputGio.cs @@ -0,0 +1,144 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-gio.c: + * + * Copyright (C) 2007 Dom Lachowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Output +{ + public class GsfOutputGio : GsfOutput + { + #region Properties + + public Stream Stream { get; set; } = null; + + public bool CanSeek { get; set; } = false; + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutputGio() { } + + /// An existing GFile + /// A new GsfOutputGio or null + public static GsfOutputGio Create(string file) + { + Exception err = null; + return Create(file, ref err); + } + + /// An existing GFile + /// A new GsfOutputGio or null + public static GsfOutputGio Create(string file, ref Exception err) + { + if (file == null) + return null; + + try + { + Stream stream = File.OpenWrite(file); + return new GsfOutputGio + { + Stream = stream, + CanSeek = CanSeekSafe(stream), + }; + } + catch + { + return null; + } + } + + /// + /// Destructor + /// + ~GsfOutputGio() => Close(); + + #endregion + + #region Functions + + /// + protected override bool CloseImpl() + { + if (Stream != null) + { + Stream.Close(); + Stream = null; + + return true; + } + + return false; + } + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (Stream == null) + return false; + + if (num_bytes <= 0) + return true; + + try + { + Stream.Write(data, 0, num_bytes); + return true; + } + catch + { + return false; + } + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) + { + if (Stream == null) + return false; + if (CanSeek) + return false; + + try + { + Stream.Seek(offset, whence); ; + return true; + } + catch + { + return false; + } + } + + #endregion + + #region Utilities + + private static bool CanSeekSafe(Stream stream) => stream?.CanSeek ?? false; + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputIOChannel.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputIOChannel.cs new file mode 100644 index 00000000..9e103d0f --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputIOChannel.cs @@ -0,0 +1,93 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-iochannel.c + * + * Copyright (C) 2002-2006 Dom Lachowicz (cinamod@hotmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; + +namespace LibGSF.Output +{ + public class GsfOutputIOChannel : GsfOutput + { + // TODO: Enable once GIOChannel is converted + + //#region Properties + + //public GIOChannel Channel { get; set; } = null; + + //#endregion + + //#region Constructor and Destructor + + ///// A new file or null. + //public static GsfOutputIOChannel Create(GIOChannel channel) + //{ + // if (channel == null) + // return null; + + // return new GsfOutputIOChannel + // { + // Channel = channel, + // }; + //} + + //#endregion + + //#region Functions + + ///// + //protected override bool CloseImpl() + //{ + // g_io_channel_shutdown(Channel, true, null); + // return true; + //} + + ///// + //protected override bool SeekImpl(long offset, SeekOrigin whence) + //{ + // if (!Channel.IsSeekable) + // return false; + + // GIOStatus status = g_io_channel_seek_position(Channel, offset, whence, null); + // if (status == G_IO_STATUS_NORMAL) + // return true; + + // Error = new Exception($"{status}?"); + // return false; + //} + + ///// + //protected override bool WriteImpl(int num_bytes, byte[] data) + //{ + // GIOStatus status = G_IO_STATUS_NORMAL; + // int bytes_written = 0, total_written = 0; + + // while ((status == G_IO_STATUS_NORMAL) && (total_written < num_bytes)) + // { + // status = g_io_channel_write_chars(Channel, data + total_written, num_bytes - total_written, ref bytes_written, null); + // total_written += bytes_written; + // } + + // return (status == G_IO_STATUS_NORMAL && total_written == num_bytes); + //} + + //#endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputIconv.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputIconv.cs new file mode 100644 index 00000000..74a2cc88 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputIconv.cs @@ -0,0 +1,171 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-iconv.c: wrapper to convert character sets. + * + * Copyright (C) 2005-2006 Morten Welinder (terra@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Text; + +namespace LibGSF.Output +{ + public class GsfOutputIconv : GsfOutput + { + #region Constants + + public const int BUF_SIZE = 0x400; + + #endregion + + #region Properties + + public GsfOutput Sink { get; set; } + + public Encoding InputCharset { get; set; } + + public Encoding OutputCharset { get; set; } + + /// + /// Either null or a UTF-8 string (representable in the target encoding) + /// to convert and output in place of characters that cannot be represented + /// in the target encoding. null means use \u1234 or \U12345678 format. + /// + public string Fallback { get; set; } + + public byte[] Buf { get; set; } = new byte[BUF_SIZE]; + + public int BufLen { get; set; } = 0; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfOutputIconv() { } + + /// + /// + /// + /// The underlying data source. + /// The target character set. + /// he source character set. + /// A new GsfOutput object or null. + /// Adds a reference to . + public static GsfOutputIconv Create(GsfOutput sink, Encoding dst, Encoding src) + { + if (sink == null) + return null; + + if (dst == null) + dst = Encoding.UTF8; + if (src == null) + src = Encoding.UTF8; + + return new GsfOutputIconv + { + Sink = sink, + InputCharset = src, + OutputCharset = dst, + }; + } + + #endregion + + #region Functions + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (data == null) + return false; + + int dataPtr = 0; // data[0] + while (num_bytes > 0) + { + if (Error != null) + return false; + + if (BufLen == BUF_SIZE) + { + Flush(false); + } + else + { + int count = Math.Min(BUF_SIZE - BufLen, num_bytes); + Array.Copy(data, dataPtr, Buf, BufLen, count); + BufLen += count; + num_bytes -= count; + dataPtr += count; + } + } + + return true; + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => false; + + /// + protected override bool CloseImpl() + { + if (Error != null) + return true; + + return Flush(true); + } + + #endregion + + #region Utilities + + private bool Flush(bool must_empty) + { + if (Error != null) + return false; + + if (BufLen <= 0) + return true; + + bool ok = true; + + byte[] data = Encoding.Convert(InputCharset, OutputCharset, Buf, 0, BufLen); + if (data == null || data.Length <= 0) + { + Error = new Exception("Failed to convert string"); + ok = false; + } + else if (!Sink.Write(data.Length, data)) + { + Error = new Exception("Failed to write"); + ok = false; + } + else + { + BufLen = 0; + ok = true; + } + + return ok && (!must_empty || BufLen == 0); + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputMemory.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputMemory.cs new file mode 100644 index 00000000..0063ec47 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputMemory.cs @@ -0,0 +1,157 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-memory.c: + * + * Copyright (C) 2002-2006 Dom Lachowicz (cinamod@hotmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Text; + +namespace LibGSF.Output +{ + public class GsfOutputMemory : GsfOutput + { + #region Constants + + public const int MIN_BLOCK = 512; + + public const int MAX_STEP = MIN_BLOCK * 128; + + #endregion + + #region Properties + + public byte[] Buffer { get; set; } = null; + + public int Capacity { get; set; } = 0; + + #endregion + + #region Constructor + + /// + /// Private constructor + /// + private GsfOutputMemory() { } + + /// A new file. + public static GsfOutputMemory Create() => new GsfOutputMemory(); + + #endregion + + #region Functions + + /// + protected override bool CloseImpl() => true; + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) => true; + + public bool Expand(long needed) + { + // If we need >= MAX_STEP, align to a next multiple of MAX_STEP. + // Since MAX_STEP is probably a power of two, this computation + // should reduce to "dec, shr, inc, shl", which is probably + // quicker then branching. + + long capacity = Math.Max(Capacity, MIN_BLOCK); + + if (needed < MAX_STEP) + { + while (capacity < needed) + capacity *= 2; + } + else + { + capacity = ((needed - 1) / MAX_STEP + 1) * MAX_STEP; + } + + // Check for overflow: g_renew() casts its parameters to int. + int lcapacity = (int)capacity; + if ((long)lcapacity != capacity || capacity < 0) + { + Console.Error.WriteLine("Overflow in Expand"); + return false; + } + + byte[] tempBuffer = new byte[lcapacity]; + Array.Copy(Buffer, tempBuffer, Buffer.Length); + Buffer = tempBuffer; + Capacity = lcapacity; + + return true; + } + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (Buffer == null) + { + Buffer = new byte[MIN_BLOCK]; + Capacity = MIN_BLOCK; + } + + if (num_bytes + CurrentOffset > Capacity) + { + if (!Expand(CurrentOffset + num_bytes)) + return false; + } + + Array.Copy(data, 0, Buffer, CurrentOffset, num_bytes); + return true; + } + + /// + protected override long VPrintFImpl(string format, params string[] args) + { + if (Buffer != null) + { + byte[] temp = Encoding.UTF8.GetBytes(string.Format(format, args)); + long len = Math.Min(temp.Length, Capacity - CurrentOffset); + Array.Copy(temp, 0, Buffer, CurrentOffset, len); + + // There was insufficient space + if (temp.Length >= len) + len = base.VPrintFImpl(format, args); + + return len; + } + + return base.VPrintFImpl(format, args); + } + + /// + /// + /// + /// + /// The data that has been written to mem. + /// The caller takes ownership and the buffer belonging to mem is set + /// to NULL. + /// + public byte[] StealBytes() + { + byte[] bytes = Buffer; + Buffer = null; + Capacity = 0; + return bytes; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/Output/GsfOutputStdio.cs b/BurnOutSharp/External/libgsf/Output/GsfOutputStdio.cs new file mode 100644 index 00000000..42034711 --- /dev/null +++ b/BurnOutSharp/External/libgsf/Output/GsfOutputStdio.cs @@ -0,0 +1,260 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gsf-output-stdio.c: stdio based output + * + * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +using System; +using System.IO; +using System.Text; + +namespace LibGSF.Output +{ + public class GsfOutputStdio : GsfOutput + { + #region Constants + + private static int W_OK = 2; + + private static int GSF_MAX_LINK_LEVEL = 256; + + #endregion + + #region Properties + + public FileStream FileStream { get; set; } = null; + + public string RealFilename { get; set; } + + public string TempFilename { get; set; } + + public bool CreateBackupCopy { get; set; } = false; + + public bool KeepOpen { get; set; } = false; + + public FileInfo Stat { get; set; } + + #endregion + + #region Constructor and Destructor + + /// + /// Private constructor + /// + private GsfOutputStdio() { } + + public static GsfOutputStdio Create(string filename, ref Exception err) + { + try + { + FileStream fs = File.OpenWrite(filename); + return new GsfOutputStdio + { + FileStream = fs, + RealFilename = filename, + }; + } + catch (Exception ex) + { + err = ex; + return null; + } + } + + #endregion + + #region Functions + + /// + protected override bool CloseImpl() + { + bool res; + string backup_filename = null; + + if (FileStream == null) + return false; + + if (Error != null) + { + res = true; + if (!KeepOpen && !CloseFileHelper(false)) + res = false; + + if (!UnlinkFileHelper()) + res = false; + + return res; + } + + if (KeepOpen) + { + FileStream.Flush(); + FileStream = null; + return true; + } + + res = CloseFileHelper(true); + + // short circuit our when dealing with raw FILE + if (RealFilename == null) + return res; + + if (!res) + { + UnlinkFileHelper(); + return false; + } + + // Move the original file to a backup + if (CreateBackupCopy) + { + backup_filename = $"{RealFilename}.bak"; + int result = RenameWrapper(RealFilename, backup_filename); + if (result != 0) + { + Error = new Exception($"Could not backup the original as {backup_filename}."); + return false; + } + } + + // Move the temp file to the original file + if (RenameWrapper(TempFilename, RealFilename) != 0) + { + Error = new Exception(); + return false; + } + + DateTime? modtime = ModTime; + if (modtime != null) + new FileInfo(RealFilename).LastWriteTime = modtime.Value; + + // Restore permissions. There is not much error checking we + // can do here, I'm afraid. The final data is saved anyways. + // Note the order: mode, uid+gid, gid, uid, mode. + new FileInfo(RealFilename).Attributes = Stat.Attributes; + return res; + } + + /// + protected override bool SeekImpl(long offset, SeekOrigin whence) + { + if (FileStream == null) + return SetError(0, "Missing file"); + + if (!FileStream.CanSeek) + return SetError(0, "Stream can't seek"); + + try + { + FileStream.Seek(offset, whence); + return true; + } + catch (Exception ex) + { + return SetError(0, $"Stream can't seek to {offset} from {whence}: {ex.Message}"); + } + } + + /// + protected override bool WriteImpl(int num_bytes, byte[] data) + { + if (FileStream == null) + return false; + + try + { + FileStream.Write(data, 0, num_bytes); + return true; + } + catch (Exception ex) + { + return SetError(0, $"Stream can't write {num_bytes}: {ex.Message}"); + } + } + + protected override long VPrintFImpl(string format, params string[] args) + { + if (FileStream == null) + return -1; + + string temp = string.Format(format, args); + byte[] tempBytes = Encoding.UTF8.GetBytes(temp); + FileStream.Write(tempBytes, 0, tempBytes.Length); + return temp.Length; + } + + #endregion + + #region Utilities + + private static int RenameWrapper(string oldfilename, string newfilename) + { + try + { + System.IO.File.Move(oldfilename, newfilename); + return 0; + } + catch + { + return 1; + } + } + + private static string FollowSymlinks(string filename, ref Exception error) + { + FileAttributes fa = System.IO.File.GetAttributes(filename); + while (fa.HasFlag(FileAttributes.ReparsePoint)) + { + // TODO: This should actually try to follow links + break; + } + + return filename; + } + + private bool CloseFileHelper(bool seterr) + { + try + { + FileStream.Close(); + return true; + } + catch (Exception ex) + { + + Error = new Exception($"Failed to close file: {ex.Message}"); + return false; + } + finally + { + FileStream = null; + } + } + + private bool UnlinkFileHelper() + { + if (TempFilename == null) + return true; + + // TODO: This should actually try to unlink + return true; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libgsf/README.md b/BurnOutSharp/External/libgsf/README.md new file mode 100644 index 00000000..121f6ac7 --- /dev/null +++ b/BurnOutSharp/External/libgsf/README.md @@ -0,0 +1,10 @@ +# LibMSPackSharp + + + + +C# port of the GNOME I/O [libgsf](https://github.com/GNOME/libgsf/tree/master/gsf) plus cleanup and commenting. This currently compiles as a library so it can be used in any C# application. + +## Contributions + +Contributions to the project are welcome. Please follow the current coding styles and please do not add any keys or legally dubious things to the code. \ No newline at end of file