LibGSF (nw)

This commit is contained in:
Matt Nadareski
2022-06-10 15:29:58 -07:00
parent 8cedd3e469
commit 7e69a56892
43 changed files with 15743 additions and 0 deletions

View File

@@ -30,6 +30,7 @@
</PackageReference>
<PackageReference Include="UnshieldSharp" Version="1.6.8" />
<PackageReference Include="WiseUnpacker" Version="1.0.3" />
<PackageReference Include="zlib.net-mutliplatform" Version="1.0.6" />
</ItemGroup>
<!-- These are needed for dealing with submodules -->

78
BurnOutSharp/External/libgsf/GsfBlob.cs vendored Normal file
View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
private GsfBlob() { }
/// <summary>
/// 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.
/// </summary>
/// <param name="size">Size of the data in bytes.</param>
/// <param name="data_to_copy">Data which will be copied into the blob, or null if <paramref name="size"/> is zero.</param>
/// <param name="error">Location to store error, or null.</param>
/// <returns>A newly-created GsfBlob, or null if the data could not be copied.</returns>
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
}
}

View File

@@ -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
{
/// <summary>
/// Windows clipboard format
/// </summary>
GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD = -1,
/// <summary>
/// Macintosh clipboard format
/// </summary>
GSF_CLIP_FORMAT_MACINTOSH_CLIPBOARD = -2,
/// <summary>
/// GUID that contains a format identifier
/// </summary>
GSF_CLIP_FORMAT_GUID = -3,
/// <summary>
/// No clipboard data
/// </summary>
GSF_CLIP_FORMAT_NO_DATA = 0,
/// <summary>
/// Custom clipboard format
/// </summary>
/// <remarks>
/// In the file it's actually any positive integer
/// </remarks>
GSF_CLIP_FORMAT_CLIPBOARD_FORMAT_NAME = 1,
/// <summary>
/// Unknown clipboard type or invalid data
/// </summary>
/// <remarks>
/// This is our own value for unknown types or invalid data
/// </remarks>
GSF_CLIP_FORMAT_UNKNOWN
}
public enum GsfClipFormatWindows
{
/// <summary>
/// Our own value
/// </summary>
GSF_CLIP_FORMAT_WINDOWS_ERROR = -1,
/// <summary>
/// Our own value
/// </summary>
GSF_CLIP_FORMAT_WINDOWS_UNKNOWN = -2,
/// <summary>
/// CF_METAFILEPICT
/// </summary>
GSF_CLIP_FORMAT_WINDOWS_METAFILE = 3,
/// <summary>
/// CF_DIB
/// </summary>
GSF_CLIP_FORMAT_WINDOWS_DIB = 8,
/// <summary>
/// CF_ENHMETAFILE
/// </summary>
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
/// <summary>
/// Private constructor
/// </summary>
private GsfClipData() { }
/// <summary>
/// Creates a new GsfClipData object. This function acquires a reference to the
/// <paramref name="data_blob"/>, so you should unref the blob on your own if you no longer need it
/// directly.
/// </summary>
/// <param name="format">Format for the data inside the <paramref name="data_blob"/></param>
/// <param name="data_blob">Object which holds the binary contents for the GsfClipData</param>
/// <returns>A newly-created GsfClipData.</returns>
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
/// <summary>
/// Queries the Windows clipboard data format for a GsfClipData. The <paramref name="clip_data"/> must
/// have been created with #GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD.
/// </summary>
/// <param name="error">Location to store error, or NULL</param>
/// <returns>A GsfClipFormatWindows value.</returns>
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;
}
/// <summary>
/// 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 <paramref name="clip_data"/>. 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.
/// </summary>
/// <param name="ret_size">Location to return the size of the returned data buffer.</param>
/// <param name="error">Location to store error, or NULL.</param>
/// <returns>
/// Pointer to the real clipboard data. The size in bytes of this
/// buffer is returned in the <paramref name="ret_size"/> argument.
/// </returns>
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<byte>(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;
}
/// <summary>
/// Checks that the specified blob size matches the expected size for the format.
/// </summary>
/// <returns>
/// The same format if the size is correct, or
/// GSF_CLIP_FORMAT_WINDOWS_ERROR if the size is too small.
/// </returns>
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
}
}

View File

@@ -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; }
/// <summary>
/// Optionally NULL
/// </summary>
public string LinkedTo { get; set; }
public uint RefCount { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private GsfDocProp() { }
/// <param name="name">The name of the property.</param>
/// <returns>A new GsfDocProp.</returns>
public static GsfDocProp Create(string name)
{
if (name == null)
return null;
return new GsfDocProp
{
Name = name,
Value = null,
LinkedTo = null,
};
}
#endregion
#region Functions
/// <summary>
/// Release the given property.
/// </summary>
public void Free()
{
RefCount--;
if (RefCount == 0)
{
LinkedTo = null;
if (Value != null)
Value = null;
Name = null;
}
}
public GsfDocProp Reference()
{
RefCount++;
return this;
}
/// <returns>The current value of prop, and replaces it with <paramref name="val"/>.</returns>
public object SwapValue(object val)
{
object old_val = Value;
Value = val;
return old_val;
}
#endregion
}
public class GsfDocMetaData
{
#region Properties
internal Dictionary<string, GsfDocProp> Table = new Dictionary<string, GsfDocProp>();
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private GsfDocMetaData() { }
/// <returns>
/// A new metadata property collection
/// </returns>
public static GsfDocMetaData Create() => new GsfDocMetaData();
#endregion
#region Functions
/// <returns>
/// The property with <paramref name="name"/> in meta. The caller can
/// modify the property value and link but not the name.
/// </returns>
public GsfDocProp Lookup(string name)
{
if (name == null)
return null;
if (!Table.ContainsKey(name))
return null;
return Table[name];
}
/// <summary>
/// Take ownership of <paramref name="name"/> and <paramref name="value"/> and insert a property into meta.
/// If a property exists with @name, it is replaced (The link is lost)
/// </summary>
/// <param name="name">The id.</param>
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;
}
/// <summary>
/// Read a stream formated as a set of MS OLE properties from <paramref name="input"/> and store the
/// results in <paramref name="accum"/>.
/// </summary>
/// <returns>an Exception if there was an error.</returns>
/// <remarks>Since: 1.14.24</remarks>
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<byte>(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<GsfMSOleMetaDataProp> tempProps = new List<GsfMSOleMetaDataProp>(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;
}
/// <summary>
/// If <paramref name="name"/> does not exist in the collection, do nothing. If @name does exist,
/// remove it and its value from the collection
/// </summary>
/// <param name="name">The non-null string name of the property</param>
public void Remove(string name)
{
if (name == null)
return;
if (!Table.ContainsKey(name))
return;
Table.Remove(name);
}
/// <returns>The property with <paramref name="name"/> in meta.</returns>
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;
}
/// <returns>The number of items in this collection</returns>
public int Size() => Table.Count;
/// <summary></summary>
/// <param name="doc_not_component">A kludge to differentiate DocumentSummary from Summary</param>
/// <returns>true on success</returns>
/// <remarks>Since: 1.14.24</remarks>
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
}
}

View File

@@ -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
/// <summary>
/// Controls the handling of character data within a parser node.
/// </summary>
public enum GsfXMLContent
{
/// <summary>
/// Node has no cstr contents
/// </summary>
GSF_XML_NO_CONTENT = 0,
/// <summary>
/// Node has cstr contents
/// </summary>
GSF_XML_CONTENT,
/// <summary>
/// Node has contents that is shared with children
/// </summary>
GSF_XML_SHARED_CONTENT,
/// <summary>
/// Node is second or later occurrence
/// </summary>
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
/// <summary>
/// User data
/// </summary>
public object UserState { get; set; }
/// <summary>
/// The current node content
/// </summary>
public string Content { get; set; }
/// <summary>
/// Current document being parsed
/// </summary>
public XmlDocument Doc { get; set; }
/// <summary>
/// Current node (not on the stack)
/// </summary>
public XmlNode Node { get; set; }
public Stack<XmlNode> NodeStack { get; private set; }
#endregion
#region Internal Properties
public GsfInput Input { get; set; }
public Stack<string> ContentsStack { get; internal set; }
public bool Initialized { get; internal set; }
#endregion
#region Functions
/// <summary>
/// Take the first node from <paramref name="doc"/> as the current node and call its start handler.
/// </summary>
/// <param name="new_state">Arbitrary content for the parser</param>
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;
}
/// <summary>
/// This function will not be called when parsing an empty document.
/// </summary>
public void StartDocument()
{
Initialized = true;
Node = Doc.FirstChild;
NodeStack = new Stack<XmlNode>();
ContentsStack = new Stack<string>();
}
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<GsfXMLIn, string[]> 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
/// <summary>
/// Parent GsfXMLIn for reading
/// </summary>
public GsfXMLIn Parent { get; internal set; }
/// <summary>
/// Current user state
/// </summary>
public GsfXMLIn UserState { get; internal set; }
/// <summary>
/// Internal reader instance for unhandled functionality
/// </summary>
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
/// <summary>
/// Constructor
/// </summary>
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();
}
/// <summary>
/// Destructor
/// </summary>
~GsfXMLInParser()
{
Parent.EndDocument();
}
#endregion
#region XmlReader Custom Implementation
/// <inheritdoc/>
public override void ReadStartElement() => Parent.StartElement(null, null);
/// <inheritdoc/>
public override void ReadStartElement(string name) => Parent.StartElement(name, null);
/// <inheritdoc/>
public override void ReadStartElement(string localname, string ns) => Parent.StartElement(localname, ns);
/// <inheritdoc/>
public override void ReadEndElement() => Parent.EndElement();
/// <inheritdoc/>
public override string ReadElementContentAsString() => Parent.Node.InnerText;
#endregion
#region XmlReader Default Implementation
/// <inheritdoc/>
public override string GetAttribute(int i) => inst.GetAttribute(i);
/// <inheritdoc/>
public override string GetAttribute(string name) => inst.GetAttribute(name);
/// <inheritdoc/>
public override string GetAttribute(string name, string namespaceURI) => inst.GetAttribute(name, namespaceURI);
/// <inheritdoc/>
public override string LookupNamespace(string prefix) => inst.LookupNamespace(prefix);
/// <inheritdoc/>
public override bool MoveToAttribute(string name) => inst.MoveToAttribute(name);
/// <inheritdoc/>
public override bool MoveToAttribute(string name, string ns) => inst.MoveToAttribute(name, ns);
/// <inheritdoc/>
public override bool MoveToElement() => inst.MoveToElement();
/// <inheritdoc/>
public override bool MoveToFirstAttribute() => inst.MoveToFirstAttribute();
/// <inheritdoc/>
public override bool MoveToNextAttribute() => inst.MoveToNextAttribute();
/// <inheritdoc/>
public override bool Read() => inst.Read();
/// <inheritdoc/>
public override bool ReadAttributeValue() => inst.ReadAttributeValue();
/// <inheritdoc/>
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<string> Stack { get; private set; } = new Stack<string>();
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
/// <summary>
/// Private constructor
/// </summary>
protected GsfXMLOut() { }
/// <summary>
/// Create an XML output stream.
/// </summary>
public static GsfXMLOut Create(GsfOutput output)
{
if (output == null)
return null;
return new GsfXMLOut()
{
Output = output
};
}
#endregion
#region Functions
/// <summary>
/// Write the document start
/// </summary>
public void StartDocument()
{
string header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
Output.Write(header.Length, Encoding.UTF8.GetBytes(header));
if (DocType != null)
Output.PutString(DocType);
}
/// <summary>
/// Output a start element <paramref name="id"/>, if necessary preceeded by an XML declaration.
/// </summary>
/// <param name="id">Element name</param>
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;
}
/// <summary>
/// Closes/ends an XML element.
/// </summary>
/// <returns>The element that has been closed.</returns>
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($"</{id}>\n");
else
Output.PrintF($"</{id}>");
break;
case GsfXMLOutState.GSF_XML_OUT_CHILD:
case GsfXMLOutState.GSF_XML_OUT_CONTENT:
if (PrettyPrint)
Output.PrintF($"</{id}>\n");
else
Output.PrintF($"</{id}>");
break;
}
State = PrettyPrint ? GsfXMLOutState.GSF_XML_OUT_CHILD_PRETTY : GsfXMLOutState.GSF_XML_OUT_CHILD;
return id;
}
/// <param name="pp">New state of pretty-print flag.</param>
/// <returns>The previous state of the pretty-print flag.</returns>
public bool SetPrettyPrint(bool pp)
{
bool res = PrettyPrint;
if (pp != res)
PrettyPrint = pp;
return res;
}
/// <summary>
/// Convenience routine to output a simple <paramref name="id"/> element with content <paramref name="content"/>.
/// </summary>
/// <param name="id">Element name</param>
/// <param name="content">Content of the element</param>
public void OutSimpleElement(string id, string content)
{
StartElement(id);
if (content != null)
AddString(null, content);
EndElement();
}
/// <summary>
/// Convenience routine to output an element <paramref name="id"/> with integer value <paramref name="val"/>.
/// </summary>
/// <param name="id">Element name</param>
/// <param name="val">Element value</param>
public void OutSimpleSignedElement(string id, long val)
{
StartElement(id);
AddSigned(null, val);
EndElement();
}
/// <summary>
/// Convenience routine to output an element <paramref name="id"/> with float value <paramref name="val"/> using
/// <paramref name="precision"/> significant digits.
/// </summary>
/// <param name="id">Element name</param>
/// <param name="val">Element value</param>
/// <param name="precision">The number of significant digits to use, -1 meaning "enough".</param>
public void OutSimpleFloatElement(string id, double val, int precision)
{
StartElement(id);
AddFloat(null, val, precision);
EndElement();
}
/// <summary>
/// Dump <paramref name="valUtf8"/> 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
/// <paramref name="valUtf8"/> is null do nothing (no warning, no output)
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="valUtf8">A UTF-8 encoded string to export</param>
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}\"");
}
}
/// <summary>
/// Dump <paramref name="valUtf8"/> to an attribute named <paramref name="id"/> or as the nodes content escaping
/// characters as necessary. If @valUtf8 is %null do nothing (no warning, no
/// output)
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="valUtf8">A UTF-8 encoded string</param>
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("&lt;"));
}
else if (valUtf8[cur] == '>')
{
if (cur != start)
Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8));
start = ++cur;
Output.Write(4, Encoding.UTF8.GetBytes("&gt;"));
}
else if (valUtf8[cur] == '&')
{
if (cur != start)
Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8));
start = ++cur;
Output.Write(5, Encoding.UTF8.GetBytes("&amp;"));
}
else if (valUtf8[cur] == '"')
{
if (cur != start)
Output.Write(cur - start, Encoding.UTF8.GetBytes(valUtf8));
start = ++cur;
Output.Write(6, Encoding.UTF8.GetBytes("&quot;"));
}
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("\""));
}
/// <summary>
/// Dump <paramref name="len"/> bytes in <paramref name="data"/> into the content of node <paramref name="id"/> using base64
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="data">Data to be written</param>
/// <param name="len">Length of data</param>
public void AddBase64(string id, byte[] data, int len) => AddStringUnchecked(id, Convert.ToBase64String(data, 0, len));
/// <summary>
/// Dump boolean value <paramref name="val"/> to an attribute named <paramref name="id"/> or as the nodes content
/// </summary>
/// <param name="id">Tag id, or %null for node content</param>
/// <param name="val">A boolean</param>
/// <remarks>Use '1' or '0' to simplify import</remarks>
public void AddBool(string id, bool val) => AddStringUnchecked(id, val ? "1" : "0");
/// <summary>
/// Dump integer value <paramref name="val"/> to an attribute named <paramref name="id"/> or as the nodes content
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="val">The value</param>
public void AddSigned(string id, long val) => AddStringUnchecked(id, val.ToString());
/// <summary>
/// Dump unsigned integer value <paramref name="val"/> to an attribute named <paramref name="id"/> or as the nodes
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="val">The value</param>
public void AddUnsigned(string id, ulong val) => AddStringUnchecked(id, val.ToString());
/// <summary>
/// Dump float value <paramref name="val"/> to an attribute named <paramref name="id"/> or as the nodes
/// content with precision <paramref name="precision"/>. The number will be formattted
/// according to the "C" locale.
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="val">The value</param>
/// <param name="precision">The number of significant digits to use, -1 meaning "enough".</param>
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}"));
}
/// <summary>
/// Dump Color <paramref name="r"/>.<paramref name="g"/>.<paramref name="b"/> to an attribute named <paramref name="id"/> or as the nodes content
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
/// <param name="r">Red value</param>
/// <param name="g">Green value</param>
/// <param name="b">Blue value</param>
public void AddColor(string id, uint r, uint g, uint b)
{
string buf = $"{r:X}:{g:X}:{b:X}\0";
AddStringUnchecked(id, buf);
}
/// <summary>
/// Output the value of <paramref name="val"/> as a string. Does NOT store any type information
/// with the string, just the value.
/// </summary>
/// <param name="id">Tag id, or null for node content</param>
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
{
/// <summary>
/// Try to parse <paramref name="str"/> as a value of type <paramref name="t"/> into <paramref name="res"/>.
/// </summary>
/// <param name="res">Result value</param>
/// <param name="t">Type of data</param>
/// <param name="str">Value string</param>
/// <returns>
/// True when parsing of <paramref name="str"/> as a value of type <paramref name="t"/> was succesfull;
/// false otherwise.
/// </returns>
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
}

View File

@@ -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 */
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
{
/// <summary>
/// The namespace follow this classification:
///
/// "dc:" - Dublin Core tags
/// "gsf:" - Gnumeric only tags
/// "meta:" - OpenDocument tags shared with Gnumeric
/// "MSOLE:" - OLE tags
/// </summary>
public static class GsfMetaNames
{
#region Namespace - dc
/// <summary>
/// (String) An entity primarily responsible for making the content of the
/// resource typically a person, organization, or service.
/// </summary>
/// <remarks>1.14.0 Moved from "gsf" to "dc".</remarks>
public const string GSF_META_NAME_CREATOR = "dc:creator";
/// <summary>
/// (GsfTimestamp) The last time this document was saved.
/// </summary>
/// <remarks>1.14.0 Moved from dc:date-modified to dc:date.</remarks>
public const string GSF_META_NAME_DATE_MODIFIED = "dc:date";
/// <summary>
/// (String) An account of the content of the resource.
/// </summary>
public const string GSF_META_NAME_DESCRIPTION = "dc:description";
/// <summary>
/// (GsfDocPropVector of String) Searchable, indexable keywords. Similar to PDF
/// keywords or HTML's meta block.
/// </summary>
public const string GSF_META_NAME_KEYWORDS = "dc:keywords";
/// <summary>
/// (String) The locale language of the intellectual content of the resource
/// (basically xx_YY form for us).
/// </summary>
/// <remarks>1.14.0 Clarified that this is unique from _NAME_CODEPAGE in MSOLE</remarks>
public const string GSF_META_NAME_LANGUAGE = "dc:language";
/// <summary>
/// (UnsignedShort) The MS codepage to encode strings for metadata
/// </summary>
/// <remarks>1.14.0 Clarified that this is unique from _NAME_CODEPAGE in MSOLE</remarks>
public const string GSF_META_NAME_CODEPAGE = "MSOLE:codepage";
/// <summary>
/// (String) The topic of the content of the resource,
/// <emphasis>typically</emphasis> including keywords.
/// </summary>
public const string GSF_META_NAME_SUBJECT = "dc:subject";
/// <summary>
/// (String) A formal name given to the resource.
/// </summary>
public const string GSF_META_NAME_TITLE = "dc:title";
#endregion
#region Namespace - gsf
/// <summary>
/// (Integer) Count of bytes in the document.
/// </summary>
public const string GSF_META_NAME_BYTE_COUNT = "gsf:byte-count";
/// <summary>
/// (Unsigned Integer) Identifier representing the case-sensitiveness.
/// </summary>
/// <remarks>of what ?? why is it an integer ??</remarks>
public const string GSF_META_NAME_CASE_SENSITIVE = "gsf:case-sensitivity";
/// <summary>
/// (String) Category of the document.
/// </summary>
/// <remarks>example???</remarks>
public const string GSF_META_NAME_CATEGORY = "gsf:category";
/// <summary>
/// (Integer) Count of cells in the spread-sheet document, if appropriate.
/// </summary>
public const string GSF_META_NAME_CELL_COUNT = "gsf:cell-count";
/// <summary>
/// (Integer) Count of characters in the document.
/// </summary>
/// <remarks>TODO See how to sync this with ODF's document-statistic</remarks>
public const string GSF_META_NAME_CHARACTER_COUNT = "gsf:character-count";
/// <summary>
/// (None) Reserved name (PID) for Dictionary
/// </summary>
public const string GSF_META_NAME_DICTIONARY = "gsf:dictionary";
/// <summary>
/// (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.
/// </summary>
/// <remarks>From MSOLE</remarks>
public const string GSF_META_NAME_DOCUMENT_PARTS = "gsf:document-parts";
/// <summary>
/// (Vector of string value pairs stored in alternating elements) Store the
/// counts of objects in the document as names 'worksheet' and count '4'
/// </summary>
/// <remarks>From MSOLE</remarks>
public const string GSF_META_NAME_HEADING_PAIRS = "gsf:heading-pairs";
/// <summary>
/// (Integer) Count of hidden-slides in the presentation document.
/// </summary>
public const string GSF_META_NAME_HIDDEN_SLIDE_COUNT = "gsf:hidden-slide-count";
/// <summary>
/// (Integer) Count of images in the document, if appropriate.
/// </summary>
public const string GSF_META_NAME_IMAGE_COUNT = "gsf:image-count";
/// <summary>
/// (String) The entity that made the last change to the document, typically a
/// person, organization, or service.
/// </summary>
public const string GSF_META_NAME_LAST_SAVED_BY = "gsf:last-saved-by";
/// <summary>
/// (Boolean) ???????
/// </summary>
public const string GSF_META_NAME_LINKS_DIRTY = "gsf:links-dirty";
/// <summary>
/// (Unsigned Integer) Identifier representing the default system locale.
/// </summary>
public const string GSF_META_NAME_LOCALE_SYSTEM_DEFAULT = "gsf:default-locale";
/// <summary>
/// (String) Name of the manager of "CREATOR" entity.
/// </summary>
public const string GSF_META_NAME_MANAGER = "gsf:manager";
/// <summary>
/// (String) Type of presentation, like "On-screen Show", "SlideView" etc.
/// </summary>
public const string GSF_META_NAME_PRESENTATION_FORMAT = "gsf:presentation-format";
/// <summary>
/// (Boolean) ?????
/// </summary>
public const string GSF_META_NAME_SCALE = "gsf:scale";
/// <summary>
/// (Integer) Level of security.
/// </summary>
/// <remarks>
/// <informaltable frame="none" role="params">
/// <tgroup cols = "2" >
/// <thead>
/// <row><entry align="left">Level</entry><entry>Value</entry></row>
/// </thead>
/// <tbody>
/// <row><entry>None</entry><entry>0</entry></row>
/// <row><entry>Password protected</entry><entry>1</entry></row>
/// <row><entry>Read-only recommended</entry><entry>2</entry></row>
/// <row><entry>Read-only enforced</entry><entry>3</entry></row>
/// <row><entry>Locked for annotations</entry><entry>4</entry></row>
/// </tbody></tgroup></informaltable>
/// </remarks>
public const string GSF_META_NAME_SECURITY = "gsf:security";
/// <summary>
/// (GsfClipData) Thumbnail data of the document, typically a
/// preview image of the document.
/// </summary>
public const string GSF_META_NAME_THUMBNAIL = "gsf:thumbnail";
/// <summary>
/// (Integer) Count of liness in the document.
/// </summary>
public const string GSF_META_NAME_LINE_COUNT = "gsf:line-count";
/// <summary>
/// (Integer) Count of "multi-media" clips in the document.
/// </summary>
public const string GSF_META_NAME_MM_CLIP_COUNT = "gsf:MM-clip-count";
/// <summary>
/// (Integer) Count of "notes" in the document.
/// </summary>
public const string GSF_META_NAME_NOTE_COUNT = "gsf:note-count";
/// <summary>
/// (Integer) Count of objects (OLE and other graphics) in the document, if
/// appropriate.
/// </summary>
public const string GSF_META_NAME_OBJECT_COUNT = "gsf:object-count";
/// <summary>
/// (Integer) Count of pages in the document, if appropriate.
/// </summary>
public const string GSF_META_NAME_PAGE_COUNT = "gsf:page-count";
/// <summary>
/// (Integer) Count of paragraphs in the document, if appropriate.
/// </summary>
public const string GSF_META_NAME_PARAGRAPH_COUNT = "gsf:paragraph-count";
/// <summary>
/// (Integer) Count of slides in the presentation document.
/// </summary>
public const string GSF_META_NAME_SLIDE_COUNT = "gsf:slide-count";
/// <summary>
/// (Integer) Count of pages in the document, if appropriate.
/// </summary>
public const string GSF_META_NAME_SPREADSHEET_COUNT = "gsf:spreadsheet-count";
/// <summary>
/// (Integer) Count of tables in the document, if appropriate.
/// </summary>
public const string GSF_META_NAME_TABLE_COUNT = "gsf:table-count";
/// <summary>
/// (Integer) Count of words in the document.
/// </summary>
public const string GSF_META_NAME_WORD_COUNT = "gsf:word-count";
#endregion
#region Namespace - MSOLE
/// <summary>
/// (Unknown) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_17 = "MSOLE:unknown-doc-17";
/// <summary>
/// (Unknown) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_18 = "MSOLE:unknown-doc-18";
/// <summary>
/// (Boolean) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_19 = "MSOLE:unknown-doc-19";
/// <summary>
/// (Unknown) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_20 = "MSOLE:unknown-doc-20";
/// <summary>
/// (Unknown) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_21 = "MSOLE:unknown-doc-21";
/// <summary>
/// (Boolean) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_22 = "MSOLE:unknown-doc-22";
/// <summary>
/// (i4) User-defined name
/// </summary>
public const string GSF_META_NAME_MSOLE_UNKNOWN_23 = "MSOLE:unknown-doc-23";
#endregion
#region Namespace - meta
/// <summary>
/// (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.
/// </summary>
public const string GSF_META_NAME_DATE_CREATED = "meta:creation-date";
/// <summary>
/// (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.
/// </summary>
public const string GSF_META_NAME_EDITING_DURATION = "meta:editing-duration";
/// <summary>
/// (String) The application that generated this document. AbiWord, Gnumeric,
/// etc...
/// </summary>
/// <remarks>1.14.0 Moved from "gsf" to "meta".</remarks>
public const string GSF_META_NAME_GENERATOR = "meta:generator";
/// <summary>
/// (String) Searchable, indexable keywords. Similar to PDF keywords or HTML's
/// meta block.
/// </summary>
public const string GSF_META_NAME_KEYWORD = "meta:keyword";
/// <summary>
/// (String) Specifies the name of the person who created the document
/// initially.
/// </summary>
/// <remarks>1.14.0 Moved from "gsf" to "meta".</remarks>
public const string GSF_META_NAME_INITIAL_CREATOR = "meta:initial-creator";
/// <summary>
/// (String) Name of the company/organization that the "CREATOR" entity is
/// associated with.
/// </summary>
/// <remarks>1.14.1 Moved from "gsf:company" to "dc:publisher".</remarks>
public const string GSF_META_NAME_COMPANY = "dc:publisher";
/// <summary>
/// (GsfTimestamp) Specifies the date and time when the document was last
/// printed.
/// </summary>
public const string GSF_META_NAME_PRINT_DATE = "meta:print-date";
/// <summary>
/// (GSF_META_NAME_HEADING_PAIRS) The last time this document was printed.
/// </summary>
/// <remarks>
/// 1.14.0 Moved from "gsf" to "dc".
/// 1.14.1 Moved back to "gsf" from "dc".
/// </remarks>
public const string GSF_META_NAME_LAST_PRINTED = "gsf:last-printed";
/// <summary>
/// (String) Specifies the name of the last person who printed the document.
/// </summary>
/// <remarks>1.14.0 Moved from "gsf" to "meta".</remarks>
public const string GSF_META_NAME_PRINTED_BY = "meta:printed-by";
/// <summary>
/// (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.
/// </summary>
public const string GSF_META_NAME_REVISION_COUNT = "meta:editing-cycles";
/// <summary>
/// (String) The template file that is been used to generate this document.
/// </summary>
/// <remarks>1.14.0 Moved from "gsf" to "meta"</remarks>
public const string GSF_META_NAME_TEMPLATE = "meta:template";
#endregion
}
}

View File

@@ -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<object> 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
/// <summary>
/// Gives the ODF version used by libgsf when writing Open Document files.
/// </summary>
/// <returns>The ODF version as a string: "1.2".</returns>
public static string GetVersionString() => "1.2";
/// <summary>
/// Gives the ODF version used by libgsf when writing Open Document files.
/// </summary>
/// <returns>The ODF version: 102.</returns>
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));
/// <summary>
/// OD considers this the last person to modify the doc, rather than
/// the DC convention of the person primarilly responsible for its creation
/// </summary>
public static void od_meta_creator(this GsfXMLIn xin, GsfXMLBlob blob) => xin.od_get_meta_prop(GSF_META_NAME_CREATOR, typeof(string));
/// <summary>
/// Last to print
/// </summary>
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));
/// <summary>
/// OD allows multiple keywords, accumulate things and make it an array
/// </summary>
public static void od_meta_keyword(this GsfXMLIn xin, GsfXMLBlob blob)
{
GsfOOMetaIn mi = (GsfOOMetaIn)xin.UserState;
if (mi.Keywords == null)
mi.Keywords = new List<object>();
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<GsfDocProp> vector = new List<GsfDocProp>();
vector.Add(prop);
mi.MetaData.Insert(mi.Name.Substring(true_name), vector);
}
else
{
object old = prop.Value;
if (old is List<GsfDocProp> oldList)
{
List<GsfDocProp> newObj = new List<GsfDocProp>();
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
/// <summary>
/// Generated based on:
/// http://www.oasis-open.org/committees/download.php/12572/OpenDocument-v1.0-os.pdf
/// and OpenDocument-v1.1.pdf
/// </summary>
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);
}
/// <summary>
/// Extend <paramref name="doc"/>> so that it can parse a subtree in OpenDoc metadata format
/// </summary>
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);
}
/// <summary>
/// Extend <paramref name="xin"/> so that it can parse a subtree in OpenDoc metadata format
/// The current user_state must be a GsfOOMetaIn!
/// </summary>
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<string, string> od_prop_name_map = new Dictionary<string, string>
{
{ 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:
*/
/// <summary>
/// ODF does not like "t" and "f" which we use normally
/// </summary>
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<GsfDocProp> 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<GsfDocProp> 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<string, GsfDocProp> kvp in data.Table)
{
meta_write_props(kvp.Key, kvp.Value, xout);
}
}
xout.EndElement();
xout.EndElement();
return true;
}
#endregion
}
#endregion
}

View File

@@ -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
/// <returns>A new GsfInput which the called needs to unref, or null and sets <paramref name="err"/></returns>
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;
}
/// <summary>
/// Finds <paramref name="opkg"/>'s relation with @id
/// </summary>
/// <param name="id">Identifier.</param>
/// <returns>A GsfOpenPkgRel or null</returns>
/// <remarks>
/// New in 1.14.6
///
/// Skipping because gsf_open_pkg_rel_get_type() does not return a GType.
/// </remarks>
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];
}
/// <summary>
/// Finds _a_ relation of <paramref name="opkg"/> with <paramref name="type"/> (no order is guaranteed)
/// </summary>
/// <param name="type">Target</param>
/// <returns>A GsfOpenPkgRel or null</returns>
/// <remarks>
/// New in 1.14.6
///
/// Skipping because gsf_open_pkg_rel_get_type() does not return a GType.
/// </remarks>
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
/// <summary>
/// Generated based on:
/// http://www.oasis-open.org/committees/download.php/12572/OpenDocument-v1.0-os.pdf
/// and OpenDocument-v1.1.pdf
/// </summary>
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<string, GsfOpenPkgRel> RelationsById { get; set; } = new Dictionary<string, GsfOpenPkgRel>();
public Dictionary<string, GsfOpenPkgRel> RelationsByType { get; set; } = new Dictionary<string, GsfOpenPkgRel>();
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
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<string, GsfOpenPkgRel>(),
RelationsByType = new Dictionary<string, GsfOpenPkgRel>(),
};
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<GsfOutfileOpenPkg> Children { get; set; } = null;
public List<GsfOpenPkgRel> Relations { get; set; } = null;
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private GsfOutfileOpenPkg() { }
/// <summary>
/// Convenience routine to create a GsfOutfileOpenPkg inside <paramref name="sink"/>.
/// </summary>
/// <param name="sink"></param>
/// <returns>A GsfOutfile that the caller is responsible for.</returns>
public static GsfOutfileOpenPkg Create(GsfOutfile sink)
{
return new GsfOutfileOpenPkg
{
Sink = sink,
IsDir = true,
};
}
/// <summary>
/// Destructor
/// </summary>
~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;
}
/// <summary>
/// Create a relationship between child and <paramref name="parent"/> of <paramref name="type"/>.
/// </summary>
/// <param name="type">Target type</param>
/// <returns>The relID which the caller does not own but will live as long as <paramref name="parent"/>.</returns>
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);
}
/// <summary>
/// A convenience wrapper to create a child in <paramref name="dir"/> of <paramref name="content_type"/> then create
/// a <paramref name="type"/> relation to <paramref name="parent"/>
/// </summary>
/// <param name="name">Target name</param>
/// <param name="content_type">Non-null content type</param>
/// <param name="type">Target type</param>
/// <returns>The new part.</returns>
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;
}
/// <summary>
/// Add an external relation to parent.
/// </summary>
/// <param name="target">Target type</param>
/// <param name="content_type">Target content</param>
/// <returns>
/// The id of the relation. The string is
/// managed by the parent and should not be changed or freed by the
/// caller.
/// </returns>
public string AddExternalRelation(string target, string content_type) => CreateRelation(target, content_type, true);
/// <inheritdoc/>
protected override bool WriteImpl(int num_bytes, byte[] data) => Sink.Write(num_bytes, data);
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => Sink.Seek(offset, whence);
/// <inheritdoc/>
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
/// <summary>
/// Walks each relationship associated with <paramref name="opkg"/> and calls <paramref name="func"/> with <paramref name="user_data"/>.
/// </summary>
/// <remarks>New in 1.14.9</remarks>
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<string, GsfOpenPkgRel> rel in rels.RelationsById)
{
ForeachRelationImpl(rel.Key, rel.Value, dat);
}
}
/// <summary>
/// Open @opkg's relation <paramref name="id"/>
/// </summary>
/// <param name="id">Target id</param>
/// <returns>A new GsfInput or null, and sets <paramref name="err"/> if possible.</returns>
/// <remarks>New in 1.14.7</remarks>
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;
}
/// <summary>
/// Open one of <paramref name="opkg"/>'s relationships with type=<paramref name="type"/>.
/// </summary>
/// <param name="type">Target type</param>
/// <returns>A new GsfInput or null, and sets <paramref name="err"/> if possible.</returns>
/// <remarks>New in 1.14.9</remarks>
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;
}
/// <summary>
/// Convenience function to parse a related part.
/// </summary>
/// <param name="id">Target id</param>
/// <returns>null on success or an Exception on failure.</returns>
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
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
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;
}
}
/// <summary>
/// Destructor
/// </summary>
~GsfSharedMemory()
{
if (Buf != null)
{
if (NeedsFree)
Buf = null;
//else if (NeedsUnmap)
//UnmapViewOfFile(mem.buf);
}
}
#endregion
}
}

View File

@@ -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
/// <summary>
/// A few well-defined extra-field tags.
/// </summary>
public enum ExtraFieldTags : ushort
{
ZIP_DIRENT_EXTRA_FIELD_ZIP64 = 0x0001,
/// <summary>
/// "II" -- gsf defined
/// </summary>
ZIP_DIRENT_EXTRA_FIELD_IGNORE = 0x4949,
/// <summary>
/// "UT"
/// </summary>
ZIP_DIRENT_EXTRA_FIELD_UNIXTIME = 0x5455,
/// <summary>
/// "ux"
/// </summary>
ZIP_DIRENT_EXTRA_FIELD_UIDGID = 0x7875
};
/// <summary>
/// OS codes. There are plenty, but this is all we need.
/// </summary>
public enum OSCodes
{
ZIP_OS_MSDOS = 0,
ZIP_OS_UNIX = 3
};
/// <remarks>From gsf-outfile-zip.h</remarks>
public enum GsfZipCompressionMethod
{
/// <summary>
/// Supported for export
/// </summary>
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,
/// <summary>
/// Supported for export
/// </summary>
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; }
/// <summary>
/// null = auto, FALSE, TRUE.
/// </summary>
public bool? Zip64 { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private GsfZipDirectoryEntry() { }
/// <summary>
/// Doesn't do much, but include for symmetry
/// </summary>
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<GsfZipVDir> Children { get; set; }
//public GSList* last_child { get; set; } /* Unused */
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private GsfZipVDir() { }
/// <returns>The newly created GsfZipVDir.</returns>
/// <remarks>Since: 1.14.24</remarks>
public static GsfZipVDir Create(string name, bool is_directory, GsfZipDirectoryEntry dirent)
{
return new GsfZipVDir
{
Name = name,
IsDirectory = is_directory,
DirectoryEntry = dirent,
Children = new List<GsfZipVDir>(),
};
}
/// <summary>
/// Destructor
/// </summary>
~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
}

View File

@@ -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
/// <summary>
/// Apart from argument types, this is the same as ChildByAName.
/// Please see the documentation there.
/// </summary>
/// <param name="names">A null terminated array of names (e.g. from g_strsplit)</param>
/// <returns>A newly created child which must be unrefed.</returns>
/// <remarks>New in 1.14.9.</remarks>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="names">A null terminated array of names (e.g. from g_strsplit)</param>
/// <returns>A newly created child which must be unrefed.</returns>
/// <remarks>New in 1.14.9.</remarks>
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
/// <returns>
/// The number of children the storage has, or -1 if the storage can not
/// have children.
/// </returns>
public virtual int NumChildren() => -1;
/// <param name="i">Zero-based index of child to find.</param>
/// <returns>The UTF-8 encoded name of the @i-th child</returns>
public virtual string NameByIndex(int i) => null;
/// <param name="i">Target index</param>
/// <returns>A newly created child which must be unrefed.</returns>
public virtual GsfInput ChildByIndex(int i, ref Exception error) => null;
/// <summary>
/// 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.
/// </summary>
/// <param name="name">Target name</param>
/// <returns>A newly created child which must be unrefed.</returns>
public virtual GsfInput ChildByName(string name, ref Exception error) => null;
#endregion
}
}

View File

@@ -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
/// <summary>
/// Internal constructor
/// </summary>
internal MSOleBAT() { }
/// <summary>
/// Walk the linked list of the supplied block allocation table and build up a
/// table for the list starting in <paramref name="block"/>.
/// </summary>
/// <param name="metabat">A meta bat to connect to the raw blocks (small or large)</param>
/// <param name="size_guess">An optional guess as to how many blocks are in the file</param>
/// <param name="block">The first block in the list.</param>
/// <param name="res">Where to store the result.</param>
/// <returns>true on error.</returns>
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; }
/// <summary>
/// Transition between small and big blocks
/// </summary>
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<MSOLEDirectoryEntry> Children { get; set; }
/// <summary>
/// 16 byte GUID used by some apps
/// </summary>
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
/// <summary>
/// Destructor
/// </summary>
~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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override string NameByIndex(int i)
{
foreach (MSOLEDirectoryEntry dirent in DirectoryEntry.Children)
{
if (i-- <= 0)
return dirent.Name;
}
return null;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override int NumChildren()
{
if (DirectoryEntry == null)
return -1;
if (!DirectoryEntry.IsDirectory)
return -1;
return DirectoryEntry.Children.Count;
}
/// <summary>
/// Opens the root directory of an MS OLE file.
/// </summary>
/// <param name="source">GsfInput</param>
/// <param name="err">Optional place to store an error</param>
/// <returns>The new ole file handler</returns>
/// <remarks>This adds a reference to <paramref name="source"/>.</remarks>
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;
}
/// <summary>
/// Retrieves the 16 byte indentifier (often a GUID in MS Windows apps)
/// stored within the directory associated with @ole and stores it in <paramref name="res"/>.
/// </summary>
/// <param name="res">16 byte identifier (often a GUID in MS Windows apps)</param>
/// <returns>true on success</returns>
public bool GetClassID(byte[] res)
{
if (DirectoryEntry == null)
return false;
Array.Copy(DirectoryEntry.ClassID, res, DirectoryEntry.ClassID.Length);
return true;
}
#endregion
#region Utilities
/// <returns>false on error.</returns>
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;
}
/// <summary>
/// Read a block of data from the underlying input.
/// </summary>
/// <param name="block">Block number</param>
/// <param name="buffer">Optionally null</param>
/// <returns>Pointer to the buffer or null if there is an error or 0 bytes are requested.</returns>
/// <remarks>Be really anal.</remarks>
private byte[] GetBlock(uint block, byte[] buffer)
{
if (!SeekBlock(block, 0))
return null;
return Input.Read(Info.BigBlock.Size, buffer);
}
/// <summary>
/// A small utility routine to read a set of references to bat blocks
/// either from the OLE header, or a meta-bat block.
/// </summary>
/// <returns>A pointer to the element after the last position filled</returns>
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;
}
/// <summary>
/// Copy some some raw data into an array of uint.
/// </summary>
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;
}
/// <summary>
/// Parse dirent number <paramref name="entry"/> 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;
}
/// <summary>
/// Utility routine to _partially_ replicate a file. It does NOT copy the bat
/// blocks, or init the dirent.
/// </summary>
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;
}
/// <summary>
/// Read an OLE header and do some sanity checking
/// along the way.
/// </summary>
/// <returns>true on error setting <paramref name="err"/> if it is supplied.</returns>
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
}
}

View File

@@ -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
/// <summary>
/// Magic (2 bytes)
/// Version (4 bytes)
/// 0x00, 0xFF (2 bytes)
/// Unknown (22 bytes)
/// </summary>
private const int VBA56_DIRENT_RECORD_COUNT = 2 + 4 + 2 + 22;
/// <summary>
/// VBA56_DIRENT_RECORD_COUNT (30 bytes)
/// Type1 Record Count (2 bytes)
/// Unknown (2 bytes)
/// </summary>
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<GsfInfile> Children { get; private set; } = null;
public Dictionary<string, byte[]> Modules { get; private set; } = null;
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
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
/// <summary>
/// A collection of names and source code which the caller is responsible for destroying.
/// </summary>
/// <returns>A Dictionary of names and source code (unknown encoding).</returns>
public Dictionary<string, byte[]> StealModules()
{
Dictionary<string, byte[]> res = Modules;
Modules = null;
return res;
}
/// <summary>
/// A utility routine that attempts to find the VBA file withint a stream.
/// </summary>
/// <returns>A GsfInfile</returns>
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<string, byte[]>();
Modules[name] = code;
}
else
{
Console.Error.WriteLine($"Problems extracting the source for {name} @ {src_offset}");
}
}
/// <summary>
/// Read an VBA dirctory and its project file.
/// along the way.
/// </summary>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>false on error setting <paramref name="err"/> if it is supplied.</returns>
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 <var> 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;
* <classid>
* 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
}
}

View File

@@ -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<string> Children { get; set; } = new List<string>();
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private GsfInfileStdio() { }
/// <param name="root">In locale dependent encoding</param>
/// <param name="err">Optionally null</param>
/// <returns>A new file or null.</returns>
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
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err)
{
GsfInfileStdio dst = new GsfInfileStdio
{
Root = this.Root,
};
dst.Children.AddRange(Children);
return dst;
}
/// <inheritdoc/>
protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) => null;
/// <inheritdoc/>
public override string NameByIndex(int i) => i < Children.Count ? Children[i] : null;
/// <inheritdoc/>
public override GsfInput ChildByIndex(int i, ref Exception error)
{
string name = NameByIndex(i);
return name != null ? OpenChild(name, ref error) : null;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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
}
}

View File

@@ -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; }
/// <summary>
/// The location of data
/// </summary>
public long Offset { get; set; }
public long Length { get; set; }
/// <summary>
/// The directory object, or null for a data file
/// </summary>
public GsfInfileTar Dir { get; set; }
}
/// <summary>
/// Tar header from POSIX 1003.1-1990.
/// </summary>
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<TarChild> Children { get; set; } = new List<TarChild>();
public Exception Err { get; set; } = null;
#endregion
#region Constructor and Deconstructor
/// <summary>
/// Private constructor
/// </summary>
private GsfInfileTar() => InitInfo();
/// <summary>
/// Opens the root directory of a Tar file.
/// </summary>
/// <param name="source">A base GsfInput</param>
/// <param name="err">An Exception, optionally null</param>
/// <returns>The new tar file handler</returns>
/// <remarks>This adds a reference to <paramref name="source"/>.</remarks>
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;
}
/// <summary>
/// Destructor
/// </summary>
~GsfInfileTar()
{
Source = null;
Err = null;
Children.Clear();
}
#endregion
#region Functions
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override byte[] ReadImpl(int num_bytes, byte[] optional_buffer, int bufferPtr = 0) => null;
/// <inheritdoc/>
public override bool Seek(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
public override string NameByIndex(int i)
{
if (i < 0 || i >= Children.Count)
return null;
return Children[i].Name;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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);
}
}
}
/// <summary>
/// Read tar headers and do some sanity checking
/// along the way.
/// </summary>
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
}
}

View File

@@ -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<GsfZipDirectoryEntry> 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
/// <summary>
/// Private constructor
/// </summary>
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;
}
}
/// <summary>
/// Opens the root directory of a Zip file.
/// </summary>
/// <param name="source">A base GsfInput</param>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>The new zip file handler</returns>
/// <remarks>This adds a reference to <paramref name="source"/>.</remarks>
public static GsfInfileZip Create(GsfInput source, ref Exception err)
{
if (source == null)
return null;
return new GsfInfileZip
{
Source = source,
};
}
/// <summary>
/// Destructor
/// </summary>
~GsfInfileZip()
{
if (Info != null)
{
Info.Unref();
Info = null;
}
if (Stream != null)
{
Stream.inflateEnd();
Stream = null;
}
Buf = null;
SetSource(null);
Err = null;
}
#endregion
#region Functions
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Global flag -- we don't want one per stream.
/// </summary>
private static bool warned = false;
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override string NameByIndex(int i)
{
GsfZipVDir child_vdir = VDir.ChildByIndex(i);
if (child_vdir != null)
return child_vdir.Name;
return null;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns a partial duplicate.
/// </summary>
private GsfInfileZip PartiallyDuplicate(ref Exception err)
{
GsfInfileZip dst = new GsfInfileZip(this);
if (dst.Err != null)
{
err = dst.Err;
return null;
}
return dst;
}
/// <summary>
/// Read zip headers and do some sanity checking
/// along the way.
/// </summary>
/// <returns>true on error setting Err.</returns>
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<GsfZipDirectoryEntry>(),
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);
}
}
/// <summary>
/// Read zip headers and do some sanity checking
/// along the way.
/// </summary>
/// <returns>true on error setting Err.</returns>
private bool InitInfo()
{
bool ret = ReadDirectoryEntries();
if (ret != false)
return ret;
BuildVirtualDirectories();
return false;
}
/// <summary>
/// Returns true on error
/// </summary>
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
}
}

View File

@@ -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;
}
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>The duplicate</returns>
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;
}
/// <summary>
/// Attempts to open a 'sibling' of input. The caller is responsible for
/// managing the resulting object.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>A related GsfInput</returns>
/// <remarks>
/// UNIMPLEMENTED BY ANY BACKEND
/// and it is probably unnecessary. Container provides
/// enough power to do what is necessary.
/// </remarks>
public GsfInput OpenSibling(string name, ref Exception err) => OpenSiblingImpl(name, ref err);
/// <summary>
/// Are we at the end of the file?
/// </summary>
/// <returns>true if the input is at the eof.</returns>
public bool EOF() => CurrentOffset >= Size;
/// <summary>
/// Read at least <paramref name="num_bytes"/>. 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.
/// </summary>
/// <param name="num_bytes">Number of bytes to read</param>
/// <param name="optional_buffer">Pointer to destination memory area</param>
/// <returns>
/// Pointer to the buffer or null if there is
/// an error or 0 bytes are requested.
/// </returns>
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;
}
/// <summary>
/// Read <paramref name="num_bytes"/>. Does not change the current position if there
/// is an error. Will only read if the entire amount can be read.
/// </summary>
/// <param name="num_bytes">Number of bytes to read</param>
/// <param name="bytes_read">Copy of <paramref name="num_bytes"/></param>
/// <returns>The data read.</returns>
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;
}
/// <returns>The number of bytes left in the file.</returns>
public long Remaining() => Size - CurrentOffset;
/// <summary>
/// Move the current location in the input stream.
/// </summary>
/// <param name="offset">Target offset</param>
/// <param name="whence">
/// Determines whether the offset is relative to the beginning or
/// the end of the stream, or to the current location.
/// </param>
/// <returns>true on error.</returns>
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;
}
/// <param name="filename">The (fs-sys encoded) filename</param>
/// <returns>true if the assignment was ok.</returns>
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;
}
/// <summary>
/// Emulate forward seeks by reading.
/// </summary>
/// <param name="pos">Absolute position to seek to</param>
/// <returns>true if the emulation failed.</returns>
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;
}
/// <summary>
/// Copy the contents from input to <paramref name="output"/> 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.
/// </summary>
/// <param name="output">A non-null GsfOutput</param>
/// <returns>true on success</returns>
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;
}
/// <summary>
/// This functions takes ownership of the incoming reference and yields a
/// new one as its output.
/// </summary>
/// <returns>
/// A stream equivalent to the source stream,
/// but uncompressed if the source was compressed.
/// </returns>
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
/// <summary>
/// Decompresses an LZ compressed stream.
/// </summary>
/// <param name="offset">Offset into it for start byte of compresse stream</param>
/// <returns>A GByteArray that the caller is responsible for freeing</returns>
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<byte> temp = new List<byte>(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<byte> temp = new List<byte>(res);
temp.AddRange(buffer.Skip((int)(pos % VBA_COMPRESSION_WINDOW)));
res = temp.ToArray();
}
return res;
}
#endregion
#region MS-VBA
/// <summary>
/// Decompresses VBA stream.
/// </summary>
/// <param name="input">Stream to read from</param>
/// <param name="offset">Offset into it for start byte of compressed stream</param>
/// <param name="size">Size of the returned array</param>
/// <param name="add_null_terminator">Whenever add or not null at the end of array</param>
/// <returns>A pointer to byte array</returns>
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<byte> res = new List<byte>();
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
}
}

View File

@@ -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
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>A new GsfInputMemory or null.</returns>
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
}
}

View File

@@ -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
{
/// <summary>
/// File contains text ?
/// </summary>
GZIP_IS_ASCII = 0x01,
/// <summary>
/// There is a CRC in the header
/// </summary>
GZIP_HEADER_CRC = 0x02,
/// <summary>
/// There is an 'extra' field
/// </summary>
GZIP_EXTRA_FIELD = 0x04,
/// <summary>
/// The original is stored
/// </summary>
GZIP_ORIGINAL_NAME = 0x08,
/// <summary>
/// There is a comment in the header
/// </summary>
GZIP_HAS_COMMENT = 0x10,
}
#endregion
#region Properties
/// <summary>
/// Compressed data
/// </summary>
public GsfInput Source { get; set; } = null;
/// <summary>
/// No header and no trailer.
/// </summary>
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; }
/// <summary>
/// CRC32 of uncompressed data
/// </summary>
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
/// <summary>
/// Private constructor
/// </summary>
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;
}
/// <summary>
/// Adds a reference to <paramref name="source"/>.
/// </summary>
/// <param name="source">The underlying data source.</param>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns></returns>
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;
}
/// <summary>
/// Destructor
/// </summary>
~GsfInputGZip()
{
Source = null;
if (Stream != null)
Stream.inflateEnd();
Err = null;
}
#endregion
#region Functions
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
/// <inheritdoc/>
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
}
}

View File

@@ -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 <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.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
/// <summary>
/// Private constructor
/// </summary>
private GsfInputGio() { }
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>A new GsfInputGio or null</returns>
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;
}
/// <summary>
/// Destructor
/// </summary>
~GsfInputGio()
{
Stream.Close();
Stream = null;
File = null;
if (Buf != null)
{
Buf = null;
BufSize = 0;
}
}
#endregion
#region Functions
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err)
{
if (File != null)
return null;
return Create(File, ref err);
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
private GsfInputHTTP() { }
/// <summary>
///
/// </summary>
/// <param name="url">A string containing the URL to retrieve</param>
/// <param name="error">Holds any errors encountered when establishing the HTTP connection</param>
/// <returns>An open HTTP connection, ready for reading.</returns>
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
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err) => Create(Url, ref err);
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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
}
}

View File

@@ -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
///// <returns>A new GsfInputMemory or null.</returns>
//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);
//}
}
}

View File

@@ -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
/// <summary>
/// </summary>
/// <param name="buf">The input bytes</param>
/// <param name="length">The length of <paramref name="buf"/></param>
/// <param name="needs_free">Whether you want this memory to be free'd at object destruction</param>
/// <returns>A new GsfInputMemory</returns>
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;
}
/// <param name="buf">The input bytes</param>
/// <param name="length">The length of <paramref name="buf"/></param>
/// <returns>A new GsfInputMemory</returns>
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;
}
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err)
{
return new GsfInputMemory
{
Shared = Shared,
Size = Shared.Size,
};
}
/// <inheritdoc/>
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();
}
}
/// <inheritdoc/>
public override bool Seek(long offset, SeekOrigin whence) => false;
#endregion
}
public class GsfInputMemoryMap : GsfInputMemory
{
private const int PROT_READ = 0x1;
/// <param name="filename">The file on disk that you want to mmap</param>
/// <param name="err">An Exception</param>
/// <returns>A new GsfInputMemory</returns>
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;
}
}
}

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The underlying data source.</param>
/// <param name="offset">Offset into source for start of section.</param>
/// <param name="size">Length of section.</param>
/// <returns>A new input object.</returns>
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;
}
/// <summary>
/// This creates a new proxy to the entire, given input source. See
/// Create for details.
/// </summary>
/// <param name="source">The underlying data source.</param>
/// <returns>A new input object.</returns>
public static GsfInput Create(GsfInput source) => Create(source, 0, source.Size);
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err) => Create(this);
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public override bool Seek(long offset, SeekOrigin whence) => false;
#endregion
}
}

View File

@@ -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
/// <summary>
/// Destructor
/// </summary>
~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;
}
/// <param name="filename">In UTF-8.</param>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>A new file or null.</returns>
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;
}
/// <summary>
/// Assumes ownership of <paramref name="file"/> when succeeding. If <paramref name="keep_open"/> is true,
/// ownership reverts to caller when the GsfInput is closed.
/// </summary>
/// <param name="filename">The filename corresponding to <paramref name="file"/>.</param>
/// <param name="file">An existing stdio <see cref="FileStream"/></param>
/// <param name="keep_open">Should <paramref name="file"/> be closed when the wrapper is closed</param>
/// <returns>
/// A new GsfInput wrapper for <paramref name="file"/>. Note that if the file is not
/// seekable, this function will make a local copy of the entire file.
/// </returns>
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;
}
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err) => Create(Filename, ref err);
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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
}
}

View File

@@ -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
/// <param name="source">In some combination of ascii and UTF-8</param>
/// <returns>A new file</returns>
/// <remarks>This adds a reference to @source.</remarks>
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,
};
}
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err)
{
return new GsfInputTextline()
{
Source = this.Source,
Size = this.Size,
Name = this.Name,
};
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool Seek(long offset, SeekOrigin whence)
{
Remainder = null;
bool res = Source.Seek(offset, whence);
CurrentOffset = Source.CurrentOffset;
return res;
}
#endregion
#region Utilities
/// <summary>
/// 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.
/// </summary>
/// <returns>The string read, or null on eof.</returns>
internal string GetStringASCII() => GetStringUTF8();
/// <summary>
/// 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.
/// </summary>
/// <returns>The string read, or null on eof.</returns>
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
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
private GsfStructuredBlob() { }
/// <summary>
/// Destructor
/// </summary>
~GsfStructuredBlob()
{
if (Data != null)
Data = null;
if (Children != null)
Children = null;
}
#endregion
#region Functions
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool Seek(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
public override int NumChildren() => Children != null ? Children.Length : -1;
/// <inheritdoc/>
public override string NameByIndex(int i)
{
if (Children == null)
return null;
if (i < 0 || i >= Children.Length)
return null;
return Children[i].Name;
}
/// <inheritdoc/>
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];
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Create a tree of binary blobs with unknown content from a GsfInput or
/// #GsfInfile and store it in a newly created #GsfStructuredBlob.
/// </summary>
/// <param name="input">An input (potentially a GsfInfile) holding the blob</param>
/// <returns>A new GsfStructuredBlob object which the caller is responsible for.</returns>
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;
}
/// <summary>
/// Dumps structured blob @blob onto the <paramref name="container"/>. Will fail if the output is
/// not an Outfile and blob has multiple streams.
/// </summary>
/// <returns>true on success.</returns>
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
}
}

View File

@@ -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
/// <param name="name">The name of the new child to create</param>
/// <param name="is_dir">true to create a directory, false to create a plain file</param>
/// <returns>A newly created child</returns>
public virtual GsfOutput NewChild(string name, bool is_dir) => null;
#endregion
}
}

View File

@@ -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.
/// <summary>
/// 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.
/// </summary>
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<GsfOutfileMSOle> Content_Dir_Children { get; set; } = null;
/// <summary>
/// Only valid for the root, ordered
/// </summary>
public List<GsfOutfileMSOle> Content_Dir_RootOrder { get; set; } = null;
#endregion
#region Struct (SmallBlock)
public byte[] Content_SmallBlock_Buf { get; set; }
#endregion
#region Struct (BigBlock)
/// <summary>
/// In bytes
/// </summary>
public int Content_BigBlock_StartOffset { get; set; }
#endregion
#endregion
/// <summary>
/// 16 byte GUID used by some apps
/// </summary>
public byte[] ClassID { get; set; } = new byte[16];
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private GsfOutfileMSOle() => MakeSortingName();
/// <summary>
/// Creates the root directory of an MS OLE file and manages the addition of
/// children.
/// </summary>
/// <param name="sink">A GsfOutput to hold the OLE2 file.</param>
/// <param name="bb_size">Size of large blocks.</param>
/// <param name="sb_size">Size of small blocks.</param>
/// <returns>The new ole file handler.</returns>
/// <remarks>This adds a reference to <paramref name="sink"/>.</remarks>
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<GsfOutfileMSOle>(),
};
ole.RegisterChild(ole);
// Build the header
byte[] buf = Enumerable.Repeat<byte>(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;
}
/// <summary>
/// Creates the root directory of an MS OLE file and manages the addition of
/// children.
/// </summary>
/// <param name="sink">A GsfOutput to hold the OLE2 file</param>
/// <returns>The new ole file handler.</returns>
/// <remarks>This adds a reference to <paramref name="sink"/>.</remarks>
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();
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Write <paramref name="clsid"/> to the directory associated with ole.
/// </summary>
/// <param name="clsid">Identifier (often a GUID in MS Windows apps)</param>
/// <returns>true on success.</returns>
public bool SetClassID(byte[] clsid)
{
if (Type != MSOleOutfileType.MSOLE_DIR)
return false;
Array.Copy(clsid, ClassID, ClassID.Length);
return true;
}
#endregion
#region Utilities
/// <summary>
/// Returns the number of times 1 must be shifted left to reach value
/// </summary>
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);
}
/// <summary>
/// Static objects are zero-initialized
/// </summary>
private static byte[] zero_buf = new byte[ZERO_PAD_BUF_SIZE];
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// Utility routine to generate a BAT for a file known to be sequential and
/// continuous.
/// </summary>
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);
}
/// <summary>
/// Write the metadata (dirents, small block, xbats)
/// </summary>
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<GsfOutfileMSOle> 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<byte>(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
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
private GsfOutfileStdio() { }
/// <summary>
///
/// </summary>
/// <param name="root">Root directory in utf8.</param>
/// <param name="err">Place to store an Exception if anything goes wrong</param>
/// <returns>A new outfile or null.</returns>
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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override bool CloseImpl() => true;
#endregion
}
}

View File

@@ -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;
/// <summary>
/// Only valid for the root, ordered
/// </summary>
public List<GsfOutfileZip> 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
/// <summary>
/// Private constructor
/// </summary>
private GsfOutfileZip() { }
/// <summary>
/// Creates the root directory of a Zip file and manages the addition of
/// children.
/// </summary>
/// <param name="sink">A GsfOutput to hold the ZIP file</param>
/// <param name="err">Location to store error, or null; currently unused.</param>
/// <returns>The new zip file handler</returns>
/// <remarks>This adds a reference to <paramref name="sink"/>.</remarks>
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<GsfOutfileZip>();
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;
}
/// <summary>
/// Destructor
/// </summary>
~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
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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<GsfOutfileZip>();
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;
}
/// <summary>
/// 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+.
/// </summary>
/// <returns></returns>
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<byte> extras = new List<byte>(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<byte> tmp = new List<byte>();
// 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<byte> tmp = new List<byte>();
// 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<byte> tmp = new List<byte>();
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<byte> buf = new List<byte>(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<byte> buf = new List<byte>(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<byte> buf = new List<byte>(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<byte> buf = new List<byte>(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<byte> hbuf = new List<byte>(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<byte> extras = new List<byte>(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<byte> tmp = new List<byte>();
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<byte> tmp = new List<byte>();
// 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;
}
/// <summary>
/// Write the per stream data descriptor
/// </summary>
private bool DataDescriptorWrite()
{
List<byte> buf = new List<byte>(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
}
}

View File

@@ -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
/// <summary>
/// Close a stream.
/// </summary>
/// <returns>false on error</returns>
public bool Close()
{
if (IsClosed)
return SetError(0, "<internal>");
// 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;
}
/// <summary>
/// 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)
/// </summary>
/// <param name="offset">Relative amount to reposition</param>
/// <param name="whence">What the offset is relative to.</param>
/// <returns>false on error.</returns>
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;
}
/// <summary>
/// Write <paramref name="num_bytes"/> of <paramref name="data"/> to output.
/// </summary>
/// <param name="num_bytes">Number of bytes to write</param>
/// <param name="data">Data to write.</param>
/// <returns>%false on error.</returns>
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;
}
/// <returns>true if the wrapping succeeded.</returns>
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;
}
/// <summary>
/// Output <paramref name="va"/> to output using the format string <paramref name="format"/>, similar to printf(3)
/// </summary>
/// <param name="format">The printf-style format string</param>
/// <param name="va">The arguments for @format</param>
/// <returns>true if successful, false if not</returns>
public bool PrintF(string format, params string[] va) => VPrintF(format, va) >= 0;
/// <summary>
/// Output <paramref name="args"/> to output using the format string <paramref name="format"/>, similar to vprintf(3)
/// </summary>
/// <param name="format">The printf-style format string</param>
/// <param name="args">The arguments for @format</param>
/// <returns>Number of bytes printed, a negative value if not successful</returns>
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;
}
/// <summary>
/// Like fputs, this assumes that the line already ends with a newline
/// </summary>
/// <param name="line">Nul terminated string to write</param>
/// <returns>%true if successful, %false if not</returns>
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;
}
/// <param name="filename">The (fs-sys encoded) filename</param>
/// <returns> %true if the assignment was ok.</returns>
/// <remarks>This is a utility routine that should only be used by derived outputs.</remarks>
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;
}
/// <param name="code">The error id</param>
/// <param name="format">printf style format string</param>
/// <param name="va">arguments for @format</param>
/// <returns>Always returns false to facilitate its use.</returns>
/// <remarks>This is a utility routine that should only be used by derived outputs.</remarks>
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
}
}

View File

@@ -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
/// <summary>
/// Compressed data
/// </summary>
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
/// <summary>
/// Private constructor
/// </summary>
private GsfOutputBzip() { }
/// <param name="sink">The underlying data source.</param>
/// <returns>A new file or null.</returns>
/// <remarks>Adds a reference to <paramref name="sink"/>.</remarks>
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
/// <inheritdoc/>
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
}
/// <inheritdoc/>
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
}
}

View File

@@ -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
/// <summary>
/// Controls when to add quotes around fields.
/// </summary>
public enum GsfOutputCsvQuotingMode
{
/// <summary>
/// Never add quotes around fields
/// </summary>
GSF_OUTPUT_CSV_QUOTING_MODE_NEVER,
/// <summary>
/// Add quotes around fields when needed
/// </summary>
GSF_OUTPUT_CSV_QUOTING_MODE_AUTO,
/// <summary>
/// Always add quotes around fields
/// </summary>
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
/// <inheritdoc/>
protected override bool WriteImpl(int num_bytes, byte[] data) => Sink.Write(num_bytes, data);
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => Sink.Seek(offset, whence);
/// <inheritdoc/>
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
}

View File

@@ -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
/// <summary>
/// GZip flag byte - The original is stored
/// </summary>
private const byte GZIP_ORIGINAL_NAME = 0x08;
#endregion
#region Properties
/// <summary>
/// Compressed data
/// </summary>
public GsfOutput Sink { get; set; } = null;
/// <summary>
/// No header and no trailer.
/// </summary>
public bool Raw { get; set; }
/// <summary>
/// zlib compression level
/// </summary>
public int DeflateLevel { get; set; } = Z_DEFAULT_COMPRESSION;
public ZStream Stream { get; set; }
/// <summary>
/// CRC32 of uncompressed data
/// </summary>
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
/// <summary>
/// Private constructor
/// </summary>
private GsfOutputGZip() { }
/// <param name="sink">The underlying data source.</param>
/// <param name="err">Optionally null.</param>
/// <returns>A new file or null</returns>
/// <remarks>Adds a reference to <paramref name="sink"/>.</remarks>
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;
}
/// <summary>
/// Destructor
/// </summary>
~GsfOutputGZip()
{
// FIXME: check for error?
Stream.deflateEnd();
}
#endregion
#region Functions
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
protected override bool CloseImpl()
{
// Just in case nothing was ever written
SetupImpl();
if (Error != null)
{
if (!Flush())
return false;
if (!Raw)
{
List<byte> buf = new List<byte>();
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<byte> buf = new List<byte>(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
}
}

View File

@@ -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 <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 GsfOutputGio : GsfOutput
{
#region Properties
public Stream Stream { get; set; } = null;
public bool CanSeek { get; set; } = false;
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private GsfOutputGio() { }
/// <param name="file">An existing GFile</param>
/// <returns>A new GsfOutputGio or null</returns>
public static GsfOutputGio Create(string file)
{
Exception err = null;
return Create(file, ref err);
}
/// <param name="file">An existing GFile</param>
/// <returns>A new GsfOutputGio or null</returns>
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;
}
}
/// <summary>
/// Destructor
/// </summary>
~GsfOutputGio() => Close();
#endregion
#region Functions
/// <inheritdoc/>
protected override bool CloseImpl()
{
if (Stream != null)
{
Stream.Close();
Stream = null;
return true;
}
return false;
}
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
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
}
}

View File

@@ -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
///// <returns>A new file or null.</returns>
//public static GsfOutputIOChannel Create(GIOChannel channel)
//{
// if (channel == null)
// return null;
// return new GsfOutputIOChannel
// {
// Channel = channel,
// };
//}
//#endregion
//#region Functions
///// <inheritdoc/>
//protected override bool CloseImpl()
//{
// g_io_channel_shutdown(Channel, true, null);
// return true;
//}
///// <inheritdoc/>
//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;
//}
///// <inheritdoc/>
//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
}
}

View File

@@ -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; }
/// <summary>
/// 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.
/// </summary>
public string Fallback { get; set; }
public byte[] Buf { get; set; } = new byte[BUF_SIZE];
public int BufLen { get; set; } = 0;
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private GsfOutputIconv() { }
/// <summary>
///
/// </summary>
/// <param name="sink">The underlying data source.</param>
/// <param name="dst">The target character set.</param>
/// <param name="src">he source character set.</param>
/// <returns>A new GsfOutput object or null.</returns>
/// <remarks>Adds a reference to <paramref name="sink"/>.</remarks>
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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
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
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
private GsfOutputMemory() { }
/// <returns>A new file.</returns>
public static GsfOutputMemory Create() => new GsfOutputMemory();
#endregion
#region Functions
/// <inheritdoc/>
protected override bool CloseImpl() => true;
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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);
}
/// <summary>
///
/// </summary>
/// <returns>
/// The data that has been written to mem.
/// The caller takes ownership and the buffer belonging to mem is set
/// to NULL.
/// </returns>
public byte[] StealBytes()
{
byte[] bytes = Buffer;
Buffer = null;
Capacity = 0;
return bytes;
}
#endregion
}
}

View File

@@ -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
/// <summary>
/// Private constructor
/// </summary>
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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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}");
}
}
/// <inheritdoc/>
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
}
}

10
BurnOutSharp/External/libgsf/README.md vendored Normal file
View File

@@ -0,0 +1,10 @@
# LibMSPackSharp
<!-- [![Build status](https://ci.appveyor.com/api/projects/status/pukddvnyowwqvbis?svg=true)](https://ci.appveyor.com/project/mnadareski/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.