Remove libgsf/libmsi/ComHandler

This commit is contained in:
Matt Nadareski
2022-06-23 13:57:41 -07:00
parent 66da32b5c1
commit 3c1623cb22
69 changed files with 0 additions and 31173 deletions

View File

@@ -4,8 +4,5 @@ This directory contains multiple external libraries. Here is the status of each:
| Directory | Library | Status |
| --------- | ------- | ------ |
| libgsf | [libgsf](https://github.com/GNOME/libgsf) | 90% ported to C#, currently unused |
| libmsi | [msitools](https://github.com/GNOME/msitools) | 100% ported to C#, breaking bugs, currently unused |
| psxt001z | [psxt001z](https://github.com/Dremora/psxt001z) | 90% ported to C#, device reading disabled |
| SevenZip | [7-Zip (ComHandler.cpp)](https://github.com/mcmilk/7-Zip/blob/63128687a9dffbe60091d04df3baf38af60dc2fa/CPP/7zip/Archive/ComHandler.cpp) | 25% ported to C#, currently unused |
| stormlibsharp | [stormlibsharp](https://github.com/robpaveza/stormlibsharp) | External submodule |

View File

@@ -1,907 +0,0 @@
// ComHandler.cpp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace NArchive
{
enum kProps : byte
{
kpidPath,
kpidSize,
kpidPackSize,
kpidCTime,
kpidMTime
};
enum kArcProps : byte
{
kpidExtension,
kpidClusterSize,
kpidSectorSize
};
}
//namespace NArchive
//{
// // #define BitConverter.ToUInt16(p) GetUi16(p)
// // #define BitConverter.ToUInt32(p) GetUi32(p)
// class CHandler : IInArchive, IInArchiveGetStream, CMyUnknownImp
// {
// private CMyComPtr<IInStream> _stream;
// NArchive.NCom.CDatabase _db;
// public MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream);
// public INTERFACE_IInArchive();
// public STDMETHOD(GetStream)(uint index, ISequentialInStream** stream);
// }
// IMP_IInArchive_Props
// IMP_IInArchive_ArcProps
// STDMETHODIMP CHandler::GetArchiveProperty(kProps propID, PROPVARIANT* value)
// {
// COM_TRY_BEGIN
// NWindows::NCOM::CPropVariant prop;
// switch (propID)
// {
// case kpidExtension: prop = kExtensions[(ulong)_db.Type]; break;
// case kpidPhySize: prop = _db.PhySize; break;
// case kpidClusterSize: prop = (uint)1 << _db.SectorSizeBits; break;
// case kpidSectorSize: prop = (uint)1 << _db.MiniSectorSizeBits; break;
// case kpidMainSubfile: if (_db.MainSubfile >= 0) prop = (uint)_db.MainSubfile; break;
// case kpidIsNotArcType: if (_db.IsNotArcType()) prop = true; break;
// }
// prop.Detach(value);
// return true;
// COM_TRY_END
// }
// STDMETHODIMP CHandler::GetProperty(uint index, PROPID propID, PROPVARIANT* value)
// {
// COM_TRY_BEGIN
// NWindows::NCOM::CPropVariant prop;
// const CRef &ref = _db.Refs[index];
// const CItem &item = _db.Items[ref.Did];
// switch (propID)
// {
// case kpidPath: prop = _db.GetItemPath(index); break;
// case kpidIsDir: prop = item.IsDir(); break;
// case kpidCTime: prop = item.CTime; break;
// case kpidMTime: prop = item.MTime; break;
// case kpidPackSize: if (!item.IsDir()) prop = _db.GetItemPackSize(item.Size); break;
// case kpidSize: if (!item.IsDir()) prop = item.Size; break;
// }
// prop.Detach(value);
// return true;
// COM_TRY_END
// }
// STDMETHODIMP CHandler::Open(Stream inStream,
// const ulong* /* maxCheckStartPosition */,
// IArchiveOpenCallback* /* openArchiveCallback */)
// {
// COM_TRY_BEGIN
// Close();
// try
// {
// if (_db.Open(inStream) != true)
// return false;
// _stream = inStream;
// }
// catch (...) { return false; }
// return true;
// COM_TRY_END
// }
// STDMETHODIMP CHandler::Close()
// {
// _db.Clear();
// _stream.Release();
// return true;
// }
// STDMETHODIMP CHandler::Extract(const uint* indices, uint numItems,
// int testMode, IArchiveExtractCallback*extractCallback)
//{
// COM_TRY_BEGIN
// bool allFilesMode = (numItems == (uint)(int)-1);
// if (allFilesMode)
// numItems = _db.Refs.Size();
// if (numItems == 0)
// return true;
// uint i;
// ulong totalSize = 0;
// for (i = 0; i < numItems; i++)
// {
// const CItem &item = _db.Items[_db.Refs[allFilesMode ? i : indices[i]].Did];
// if (!item.IsDir())
// totalSize += item.Size;
// }
// RINOK(extractCallback.SetTotal(totalSize));
// ulong totalPackSize;
// totalSize = totalPackSize = 0;
// NCompress::CCopyCoder* copyCoderSpec = new NCompress::CCopyCoder();
// CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
// CLocalProgress* lps = new CLocalProgress;
// CMyComPtr<ICompressProgressInfo> progress = lps;
// lps.Init(extractCallback, false);
// for (i = 0; i < numItems; i++)
// {
// lps.InSize = totalPackSize;
// lps.OutSize = totalSize;
// RINOK(lps.SetCur());
// int index = allFilesMode ? i : indices[i];
// const CItem &item = _db.Items[_db.Refs[index].Did];
// CMyComPtr<ISequentialOutStream> outStream;
// int askMode = testMode ?
// NExtract::NAskMode::kTest :
// NExtract::NAskMode::kExtract;
// RINOK(extractCallback.GetStream(index, &outStream, askMode));
// if (item.IsDir())
// {
// RINOK(extractCallback.PrepareOperation(askMode));
// RINOK(extractCallback.SetOperationResult(NExtract::NOperationResult::kOK));
// continue;
// }
// totalPackSize += _db.GetItemPackSize(item.Size);
// totalSize += item.Size;
// if (!testMode && !outStream)
// continue;
// RINOK(extractCallback.PrepareOperation(askMode));
// int res = NExtract::NOperationResult::kDataError;
// CMyComPtr<ISequentialInStream> inStream;
// uint hres = GetStream(index, &inStream);
// if (hres == false)
// res = NExtract::NOperationResult::kDataError;
// else if (hres == E_NOTIMPL)
// res = NExtract::NOperationResult::kUnsupportedMethod;
// else
// {
// RINOK(hres);
// if (inStream)
// {
// RINOK(copyCoder.Code(inStream, outStream, null, null, progress));
// if (copyCoderSpec.TotalSize == item.Size)
// res = NExtract::NOperationResult::kOK;
// }
// }
// outStream.Release();
// RINOK(extractCallback.SetOperationResult(res));
// }
// return true;
// COM_TRY_END
//}
// STDMETHODIMP CHandler::GetNumberOfItems(uint * numItems)
// {
// *numItems = _db.Refs.Size();
// return true;
// }
// STDMETHODIMP CHandler::GetStream(uint index, ISequentialInStream * *stream)
// {
// COM_TRY_BEGIN
// * stream = 0;
// uint itemIndex = _db.Refs[index].Did;
// const CItem &item = _db.Items[itemIndex];
// CClusterInStream* streamSpec = new CClusterInStream;
// CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
// streamSpec.Stream = _stream;
// streamSpec.StartOffset = 0;
// bool isLargeStream = (itemIndex == 0 || _db.IsLargeStream(item.Size));
// int bsLog = isLargeStream ? _db.SectorSizeBits : _db.MiniSectorSizeBits;
// streamSpec.BlockSizeLog = bsLog;
// streamSpec.Size = item.Size;
// uint clusterSize = (uint)1 << bsLog;
// ulong numClusters64 = (item.Size + clusterSize - 1) >> bsLog;
// if (numClusters64 >= ((uint)1 << 31))
// return E_NOTIMPL;
// streamSpec.Vector.ClearAndReserve((ulong)numClusters64);
// uint sid = item.Sid;
// ulong size = item.Size;
// if (size != 0)
// {
// for (; ; size -= clusterSize)
// {
// if (isLargeStream)
// {
// if (sid >= _db.FatSize)
// return false;
// streamSpec.Vector.AddInReserved(sid + 1);
// sid = _db.Fat[sid];
// }
// else
// {
// ulong val = 0;
// if (sid >= _db.MatSize || !_db.GetMiniCluster(sid, val) || val >= (ulong)1 << 32)
// return false;
// streamSpec.Vector.AddInReserved((uint)val);
// sid = _db.Mat[sid];
// }
// if (size <= clusterSize)
// break;
// }
// }
// if (sid != NFatID.kEndOfChain)
// return false;
// RINOK(streamSpec.InitAndSeek());
// *stream = streamTemp.Detach();
// return true;
// COM_TRY_END
// }
//}
namespace NArchive.NCom
{
public enum EType
{
k_Type_Common,
k_Type_Msi,
k_Type_Msp,
k_Type_Doc,
k_Type_Ppt,
k_Type_Xls
};
public enum NFatID : uint
{
kFree = 0xFFFFFFFF,
kEndOfChain = 0xFFFFFFFE,
kFatSector = 0xFFFFFFFD,
kMatSector = 0xFFFFFFFC,
kMaxValue = 0xFFFFFFFA,
}
public enum NItemType : byte
{
kEmpty = 0,
kStorage = 1,
kStream = 2,
kLockbytes = 3,
kProperty = 4,
kRootStorage = 5,
}
public class CItem
{
#region Properties
public byte[] Name { get; set; } = new byte[CDatabase.kNameSizeMax];
// public ushort NameSize { get; set; }
// public uint Flags { get; set; }
public DateTime? CTime { get; set; }
public DateTime? MTime { get; set; }
public ulong Size { get; set; }
public uint LeftDid { get; set; }
public uint RightDid { get; set; }
public uint SonDid { get; set; }
public uint Sid { get; set; }
public NItemType Type { get; set; }
#endregion
#region Functions
public bool IsEmpty() => Type == NItemType.kEmpty;
public bool IsDir() => Type == NItemType.kStorage || Type == NItemType.kRootStorage;
public void Parse(in byte[] p, int offset, bool mode64bit)
{
Array.Copy(p, offset, Name, 0, CDatabase.kNameSizeMax);
// NameSize = BitConverter.ToUInt16(p, offset + 64);
Type = (NItemType)p[offset + 66];
LeftDid = BitConverter.ToUInt32(p, offset + 68);
RightDid = BitConverter.ToUInt32(p, offset + 72);
SonDid = BitConverter.ToUInt32(p, offset + 76);
// Flags = BitConverter.ToUInt32(p + offset + 96);
CDatabase.GetFileTimeFromMem(p, offset + 100, out DateTime? ctime);
CTime = ctime;
CDatabase.GetFileTimeFromMem(p, offset + 108, out DateTime? mtime);
MTime = mtime;
Sid = BitConverter.ToUInt32(p, offset + 116);
Size = BitConverter.ToUInt32(p, offset + 120);
if (mode64bit)
Size |= ((ulong)BitConverter.ToUInt32(p, offset + 124) << 32);
}
#endregion
}
public class CRef
{
public int Parent { get; set; }
public uint Did { get; set; }
}
public class CDatabase
{
#region Constants
internal static readonly byte[] kSignature = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
internal static readonly string[] kExtensions =
{
"compound",
"msi",
"msp",
"doc",
"ppt",
"xls",
};
internal const int kNameSizeMax = 64;
internal const uint kNoDid = 0xFFFFFFFF;
internal const string k_Msi_Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._";
// static const char * const k_Msi_ID = ""; // "{msi}";
internal const char k_Msi_SpecChar = '!';
internal const int k_Msi_NumBits = 6;
internal const int k_Msi_NumChars = 1 << k_Msi_NumBits;
internal const int k_Msi_CharMask = k_Msi_NumChars - 1;
internal const char k_Msi_StartUnicodeChar = (char)0x3800;
internal const int k_Msi_UnicodeRange = k_Msi_NumChars * (k_Msi_NumChars + 1);
// There is name "[!]MsiPatchSequence" in msp files
internal const int kMspSequence_Size = 18;
internal static readonly byte[] kMspSequence = new byte[kMspSequence_Size]
{
0x40, 0x48, 0x96, 0x45, 0x6C, 0x3E, 0xE4, 0x45,
0xE6, 0x42, 0x16, 0x42, 0x37, 0x41, 0x27, 0x41,
0x37, 0x41
};
#endregion
#region Properties
internal uint NumSectorsInMiniStream { get; set; }
internal uint[] MiniSids { get; set; }
public uint[] Fat { get; set; }
public uint FatSize { get; set; }
public uint[] Mat { get; set; }
public uint MatSize { get; set; }
public List<CItem> Items { get; set; }
public List<CRef> Refs { get; set; }
public uint LongStreamMinSize { get; set; }
public int SectorSizeBits { get; set; }
public int MiniSectorSizeBits { get; set; }
public int MainSubfile { get; set; }
public long PhySize { get; set; }
public EType Type { get; set; }
#endregion
#region Functions
public bool IsNotArcType() => Type != EType.k_Type_Msi && Type != EType.k_Type_Msp;
public void UpdatePhySize(long val)
{
if (PhySize < val)
PhySize = val;
}
public bool ReadSector(Stream inStream, byte[] buf, int sectorSizeBits, uint sid)
{
UpdatePhySize(((long)sid + 2) << sectorSizeBits);
long offset = ((long)sid + 1) << sectorSizeBits;
if (inStream.Seek(offset, SeekOrigin.Begin) != offset)
return false;
return inStream.Read(buf, 0, 1 << sectorSizeBits) == 1 << sectorSizeBits;
}
public bool ReadIDs(Stream inStream, byte[] buf, int sectorSizeBits, uint sid, uint[] dest, int destPtr)
{
ReadSector(inStream, buf, sectorSizeBits, sid);
int sectorSize = 1 << sectorSizeBits;
for (int i = destPtr, t = 0; t < sectorSize; i++, t += 4)
{
dest[i] = BitConverter.ToUInt32(buf, t);
}
return true;
}
public bool Update_PhySize_WithItem(int index)
{
CItem item = Items[index];
bool isLargeStream = (index == 0 || IsLargeStream(item.Size));
if (!isLargeStream)
return true;
int bsLog = isLargeStream ? SectorSizeBits : MiniSectorSizeBits;
// streamSpec.Size = item.Size;
uint clusterSize = (uint)1 << bsLog;
ulong numClusters64 = (item.Size + clusterSize - 1) >> bsLog;
if (numClusters64 >= ((uint)1 << 31))
return false;
uint sid = item.Sid;
ulong size = item.Size;
if (size != 0)
{
for (; ; size -= clusterSize)
{
// if (isLargeStream)
{
if (sid >= FatSize)
return false;
UpdatePhySize((sid + 2) << bsLog);
sid = Fat[(int)sid];
}
if (size <= clusterSize)
break;
}
}
if (sid != (uint)NFatID.kEndOfChain)
return false;
return true;
}
public void Clear()
{
PhySize = 0;
Array.Clear(Fat, 0, Fat.Length);
Array.Clear(MiniSids, 0, MiniSids.Length);
Array.Clear(Mat, 0, Mat.Length);
Items.Clear();
Refs.Clear();
}
public bool IsLargeStream(ulong size) => size >= LongStreamMinSize;
public string GetItemPath(uint index)
{
string s = string.Empty;
while (index != kNoDid)
{
CRef cref = Refs[(int)index];
CItem item = Items[(int)cref.Did];
if (!string.IsNullOrEmpty(s))
s = Path.DirectorySeparatorChar + s;
s.Insert(0, ConvertName(item.Name));
index = (uint)cref.Parent;
}
return s;
}
public ulong GetItemPackSize(ulong size)
{
ulong mask = (ulong)(1 << (IsLargeStream(size) ? SectorSizeBits : MiniSectorSizeBits)) - 1;
return (size + mask) & ~mask;
}
public bool GetMiniCluster(uint sid, ref ulong res)
{
int subBits = SectorSizeBits - MiniSectorSizeBits;
uint fid = sid >> (int)subBits;
if (fid >= NumSectorsInMiniStream)
return false;
res = (ulong)(((MiniSids[(int)fid] + 1) << subBits) + (sid & ((1 << subBits) - 1)));
return true;
}
public bool Open(Stream inStream)
{
MainSubfile = -1;
Type = EType.k_Type_Common;
int kHeaderSize = 512;
byte[] p = new byte[kHeaderSize];
PhySize = kHeaderSize;
if (inStream.Read(p, 0, kHeaderSize) != kHeaderSize)
return false;
if (!p.Take(kHeaderSize).SequenceEqual(kSignature))
return false;
if (BitConverter.ToUInt16(p, 0x1A) > 4) // majorVer
return false;
if (BitConverter.ToUInt16(p, 0x1C) != 0xFFFE) // Little-endian
return false;
int sectorSizeBits = BitConverter.ToUInt16(p, 0x1E);
bool mode64bit = (sectorSizeBits >= 12);
int miniSectorSizeBits = BitConverter.ToUInt16(p, 0x20);
SectorSizeBits = sectorSizeBits;
MiniSectorSizeBits = miniSectorSizeBits;
if (sectorSizeBits > 24 ||
sectorSizeBits < 7 ||
miniSectorSizeBits > 24 ||
miniSectorSizeBits < 2 ||
miniSectorSizeBits > sectorSizeBits)
return false;
uint numSectorsForFAT = BitConverter.ToUInt32(p, 0x2C); // SAT
LongStreamMinSize = BitConverter.ToUInt32(p, 0x38);
uint sectSize = (uint)1 << sectorSizeBits;
byte[] sect = new byte[sectSize];
int ssb2 = sectorSizeBits - 2;
int numSidsInSec = 1 << ssb2;
int numFatItems = (int)(numSectorsForFAT << ssb2);
if ((numFatItems >> ssb2) != numSectorsForFAT)
return false;
FatSize = (uint)numFatItems;
{
uint numSectorsForBat = BitConverter.ToUInt32(p, 0x48); // master sector allocation table
const uint kNumHeaderBatItems = 109;
uint numBatItems = kNumHeaderBatItems + (numSectorsForBat << ssb2);
if (numBatItems < kNumHeaderBatItems || ((numBatItems - kNumHeaderBatItems) >> ssb2) != numSectorsForBat)
return false;
uint[] bat = new uint[numBatItems];
uint i;
for (i = 0; i < kNumHeaderBatItems; i++)
bat[i] = BitConverter.ToUInt32(p, (int)(0x4c + i * 4));
uint sid = BitConverter.ToUInt32(p, 0x44);
for (uint s = 0; s < numSectorsForBat; s++)
{
if (!ReadIDs(inStream, sect, sectorSizeBits, sid, bat, (int)i))
return false;
i += (uint)numSidsInSec - 1;
sid = bat[i];
}
numBatItems = i;
Fat = new uint[numFatItems];
uint j = 0;
for (i = 0; i < numFatItems; j++, i += (uint)numSidsInSec)
{
if (j >= numBatItems)
return false;
if (!ReadIDs(inStream, sect, sectorSizeBits, bat[j], Fat, (int)i))
return false;
}
numFatItems = (int)i;
FatSize = i;
}
uint numMatItems;
{
uint numSectorsForMat = BitConverter.ToUInt32(p, 0x40);
numMatItems = numSectorsForMat << ssb2;
if ((numMatItems >> ssb2) != numSectorsForMat)
return false;
Mat = new uint[numMatItems];
uint i;
uint sid = BitConverter.ToUInt32(p, 0x3C); // short-sector table SID
for (i = 0; i < numMatItems; i += (uint)numSidsInSec)
{
if (!ReadIDs(inStream, sect, sectorSizeBits, sid, Mat, (int)i))
return false;
if (sid >= numFatItems)
return false;
sid = Fat[sid];
}
if (sid != (uint)NFatID.kEndOfChain)
return false;
}
{
byte[] used = new byte[numFatItems];
for (uint i = 0; i < numFatItems; i++)
used[i] = 0;
uint sid = BitConverter.ToUInt32(p, 0x30); // directory stream SID
for (; ; )
{
if (sid >= numFatItems)
return false;
if (used[sid] != 0)
return false;
used[sid] = 1;
if (!ReadSector(inStream, sect, sectorSizeBits, sid))
return false;
for (uint i = 0; i < sectSize; i += 128)
{
CItem item = new CItem();
item.Parse(sect, (int)i, mode64bit);
Items.Add(item);
}
sid = Fat[sid];
if (sid == (uint)NFatID.kEndOfChain)
break;
}
}
CItem root = Items[0];
{
uint numSectorsInMiniStream;
{
ulong numSatSects64 = (root.Size + sectSize - 1) >> sectorSizeBits;
if (numSatSects64 > (uint)NFatID.kMaxValue)
return false;
numSectorsInMiniStream = (uint)numSatSects64;
}
NumSectorsInMiniStream = numSectorsInMiniStream;
MiniSids = new uint[numSectorsInMiniStream];
{
ulong matSize64 = (root.Size + ((ulong)1 << miniSectorSizeBits) - 1) >> miniSectorSizeBits;
if (matSize64 > (uint)NFatID.kMaxValue)
return false;
MatSize = (uint)matSize64;
if (numMatItems < MatSize)
return false;
}
uint sid = root.Sid;
for (uint i = 0; ; i++)
{
if (sid == (uint)NFatID.kEndOfChain)
{
if (i != numSectorsInMiniStream)
return false;
break;
}
if (i >= numSectorsInMiniStream)
return false;
MiniSids[i] = sid;
if (sid >= numFatItems)
return false;
sid = Fat[sid];
}
}
if (!AddNode(-1, root.SonDid))
return false;
ulong numCabs = 0;
for (int i = 0; i < Refs.Count; i++)
{
CItem item = Items[(int)Refs[i].Did];
if (item.IsDir() || numCabs > 1)
continue;
bool isMsiName = false;
string msiName = ConvertName(item.Name, ref isMsiName);
if (isMsiName && !string.IsNullOrEmpty(msiName))
{
// bool isThereExt = (msiName.Find(L'.') >= 0);
bool isMsiSpec = (msiName[0] == k_Msi_SpecChar);
string extension = Path.GetExtension(msiName).Trim('.');
if ((msiName.Length >= 4 && extension.Equals("cab", StringComparison.OrdinalIgnoreCase)
|| (!isMsiSpec && msiName.Length >= 3 && extension.Equals("exe", StringComparison.OrdinalIgnoreCase)))
// || (!isMsiSpec && !isThereExt)
)
{
numCabs++;
MainSubfile = i;
}
}
}
if (numCabs > 1)
MainSubfile = -1;
{
for (int t = 0; t < Items.Count; t++)
{
Update_PhySize_WithItem(t);
}
}
{
for (int t = 0; t < Items.Count; t++)
{
CItem item = Items[t];
if (IsMsiName(item.Name))
{
Type = EType.k_Type_Msi;
if (item.Name.Take(kMspSequence_Size).SequenceEqual(kMspSequence))
{
Type = EType.k_Type_Msp;
break;
}
continue;
}
if (AreEqualNames(item.Name, "WordDocument"))
{
Type = EType.k_Type_Doc;
break;
}
if (AreEqualNames(item.Name, "PowerPoint Document"))
{
Type = EType.k_Type_Ppt;
break;
}
if (AreEqualNames(item.Name, "Workbook"))
{
Type = EType.k_Type_Xls;
break;
}
}
}
return true;
}
#endregion
#region Utilities
internal bool AddNode(int parent, uint did)
{
if (did == kNoDid)
return true;
if (did >= (uint)Items.Count)
return false;
CItem item = Items[(int)did];
if (item.IsEmpty())
return false;
CRef cref = new CRef();
cref.Parent = parent;
cref.Did = did;
Refs.Add(cref);
int index = Refs.Count - 1;
if (Refs.Count > Items.Count)
return false;
if (!AddNode(parent, item.LeftDid))
return false;
if (!AddNode(parent, item.RightDid))
return false;
if (item.IsDir() && !AddNode(index, item.SonDid))
return false;
return true;
}
internal static void GetFileTimeFromMem(in byte[] p, int offset, out DateTime? ft)
{
long time = BitConverter.ToInt64(p, offset);
ft = DateTime.FromFileTime(time);
}
internal static string CompoundNameToFileName(in string s)
{
string res = string.Empty;
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if (c < 0x20)
res += $"[{c}]";
else
res += c;
}
return res;
}
internal static bool IsMsiName(in byte[] p)
{
uint c = BitConverter.ToUInt16(p, 0);
return c >= k_Msi_StartUnicodeChar && c <= k_Msi_StartUnicodeChar + k_Msi_UnicodeRange;
}
internal static bool AreEqualNames(in byte[] rawName, in string asciiName)
{
for (int i = 0; i < kNameSizeMax / 2; i++)
{
char c = (char)BitConverter.ToUInt16(rawName, i * 2);
char c2 = asciiName[i];
if (c != c2)
return false;
if (c == 0)
return true;
}
return false;
}
internal static bool CompoundMsiNameToFileName(in string name, ref string res)
{
res = string.Empty;
for (int i = 0; i < name.Length; i++)
{
char c = name[i];
if (c < (char)k_Msi_StartUnicodeChar || c > (char)(k_Msi_StartUnicodeChar + k_Msi_UnicodeRange))
return false;
/*
if (i == 0)
res += k_Msi_ID;
*/
c -= k_Msi_StartUnicodeChar;
int c0 = c & k_Msi_CharMask;
int c1 = c >> k_Msi_NumBits;
if (c1 <= k_Msi_NumChars)
{
res += k_Msi_Chars[c0];
if (c1 == k_Msi_NumChars)
break;
res += k_Msi_Chars[c1];
}
else
res += k_Msi_SpecChar;
}
return true;
}
internal static string ConvertName(in byte[] p, ref bool isMsi)
{
isMsi = false;
string s = string.Empty;
for (int i = 0; i < kNameSizeMax; i += 2)
{
char c = (char)BitConverter.ToUInt16(p, i);
if (c == 0)
break;
s += c;
}
string msiName = string.Empty;
if (CompoundMsiNameToFileName(s, ref msiName))
{
isMsi = true;
return msiName;
}
return CompoundNameToFileName(s);
}
internal static string ConvertName(in byte[] p)
{
bool isMsi = false;
return ConvertName(p, ref isMsi);
}
#endregion
}
}

View File

@@ -1,81 +0,0 @@
/* 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
{
// TODO: Can this be made internal?
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;
if (error != 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

@@ -1,289 +0,0 @@
/* 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;
using static LibGSF.GsfUtils;
namespace LibGSF
{
#region Enums
// TODO: Can this be made internal?
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
}
// TODO: Can this be made internal?
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
// TODO: Can this be made internal?
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)
{
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 = GSF_LE_GET_GUINT32(data, 0);
switch (value)
{
case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_METAFILE:
return CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_METAFILE, "Windows Metafile format", size, ref error);
case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_DIB:
case 2: /* CF_BITMAP */
return CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_DIB, "Windows DIB or BITMAP format", size, ref error);
case (uint)GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE:
return CheckFormatWindows(GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE, "Windows Enhanced Metafile format", size, ref error);
default:
return GsfClipFormatWindows.GSF_CLIP_FORMAT_WINDOWS_UNKNOWN;
}
}
/// <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;
// GetWindowsClipboardFormat() 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();
}
#endregion
#region Utilities
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;
for (int 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

@@ -1,619 +0,0 @@
/*
* 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;
using static LibGSF.GsfUtils;
namespace LibGSF
{
// TODO: Can this be made internal?
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;
}
/// <summary>
/// A debugging utility to dump prop as text via Console
/// </summary>
/// <remarks>New in 1.14.2</remarks>
public void Dump()
{
if (Value is List<GsfDocProp> va)
{
for (int i = 0; i < va.Count; i++)
{
Console.WriteLine($"\t[{i}] = Name: {va[i].Name}, Value: {va[i].Value}, Link: {va[i].LinkedTo}, Ref: {va[i].RefCount}");
}
}
else
{
Console.WriteLine($"\t= {Value}");
}
}
#endregion
}
// TODO: Can this be made internal?
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>
/// 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 (prop != Lookup(prop.Name))
return;
Table[prop.Name] = prop;
}
/// <summary>
/// Iterate through each (key, value) pair in this collection
/// </summary>
/// <param name="func">The function called once for each element in the collection</param>
/// <param name="user_data">Any supplied user data</param>
public void ForEach(Action<string, GsfDocProp, object> func, object user_data)
{
if (Table.Count == 0)
return;
// Sort the pairs by property name in order to generate consistent files
List<KeyValuePair<string, GsfDocProp>> pairs = Table.ToList();
pairs.Sort((a, b) => DerefStrcmp(a.Key, b.Key));
for (int i = 0; i < pairs.Count; i++)
{
func(pairs[i].Key, pairs[i].Value, user_data);
}
}
/// <returns>The number of items in this collection</returns>
public int Size() => Table.Count;
/// <summary>
/// A debugging utility to dump the content of meta via Console
/// </summary>
public void Dump()
{
ForEach(PrintProperty, null);
}
#endregion
#region MS-OLE
/// <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 = GSF_LE_GET_GUINT16(data, 6);
ushort version = GSF_LE_GET_GUINT16(data, 2);
uint num_sections = BitConverter.ToUInt32(data, 24);
if (GSF_LE_GET_GUINT16(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></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
GSF_LE_SET_GUINT32(buf, 0, (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)
GSF_LE_SET_GUINT32(buf, 0, (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)
{
GSF_LE_SET_GUINT32(buf, 0, 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;
GSF_LE_SET_GUINT32(buf, 0, (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 static int DerefStrcmp(string a, string b) => a.CompareTo(b);
private static void PrintProperty(string name, GsfDocProp prop, object user_data)
{
if (prop.LinkedTo != null)
Console.WriteLine($"prop '{name}' LINKED TO -> {prop.LinkedTo}");
else
Console.WriteLine($"prop '{name}'");
prop.Dump();
}
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

@@ -1,864 +0,0 @@
/* 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

@@ -1,663 +0,0 @@
/* 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
*/
using System;
using System.Linq;
using System.Text;
using static LibGSF.GsfUtils;
namespace LibGSF
{
#region Enums
internal enum DIRENT_TYPE : byte
{
DIRENT_TYPE_INVALID = 0,
DIRENT_TYPE_DIR = 1,
DIRENT_TYPE_FILE = 2,
DIRENT_TYPE_LOCKBYTES = 3,
DIRENT_TYPE_PROPERTY = 4,
DIRENT_TYPE_ROOTDIR = 5,
}
#endregion
#region Classes
/// <summary>
/// MS-OLE Header (.msi)
/// </summary>
internal class MSOleHeader
{
#region Constants
/// <summary>
/// Independent of big block size size
/// </summary>
public const int OLE_HEADER_SIZE = 0x200;
/// <summary>
/// OLE Signature as a byte array
/// </summary>
public static readonly byte[] SIGNATURE_BYTES = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 };
/// <summary>
/// OLE Signature as an unsigned Int64
/// </summary>
public const ulong SIGNATURE_VALUE = 0x00E11AB1A1E011CFD0;
#region Offsets
private const int OLE_HEADER_SIGNATURE = 0x00;
private const int OLE_HEADER_CLSID = 0x08;
private const int OLE_HEADER_MINOR_VER = 0x18;
private const int OLE_HEADER_MAJOR_VER = 0x1a;
private const int OLE_HEADER_BYTE_ORDER = 0x1c;
private const int OLE_HEADER_BB_SHIFT = 0x1e;
private const int OLE_HEADER_SB_SHIFT = 0x20;
private const int OLE_HEADER_RESERVED = 0x22;
private const int OLE_HEADER_CSECTDIR = 0x28;
private const int OLE_HEADER_NUM_BAT = 0x2c;
private const int OLE_HEADER_DIRENT_START = 0x30;
private const int OLE_HEADER_TRANSACTING_SIGNATURE = 0x34;
private const int OLE_HEADER_THRESHOLD = 0x38;
private const int OLE_HEADER_SBAT_START = 0x3c;
private const int OLE_HEADER_NUM_SBAT = 0x40;
private const int OLE_HEADER_METABAT_BLOCK = 0x44;
private const int OLE_HEADER_NUM_METABAT = 0x48;
private const int OLE_HEADER_START_BAT = 0x4c;
#endregion
#endregion
#region Properties
/// <summary>
/// MS-OLE header signature
/// </summary>
/// <remarks>0x00</remarks>
public byte[] SIGNATURE { get; set; }
/// <summary>
/// See ReadClassStg
/// </summary>
/// <remarks>0x08</remarks>
public byte[] CLSID { get; set; }
/// <summary>
/// 0x33 and 0x3E have been seen
/// </summary>
/// <remarks>0x18</remarks>
public ushort MINOR_VER { get; set; }
/// <summary>
/// 0x03 been seen in wild
/// </summary>
/// <remarks>0x1A</remarks>
public ushort MAJOR_VER { get; set; }
/// <summary>
/// 0xFE 0xFF == Intel Little Endian
/// </summary>
/// <remarks>0x1C</remarks>
public ushort BYTE_ORDER { get; set; }
/// <summary>
/// Number of bits to shift to get a big block size
/// </summary>
/// <remarks>0x1E</remarks>
public ushort BB_SHIFT { get; set; }
/// <summary>
/// Number of bits to shift to get a small block size
/// </summary>
/// <remarks>0x20</remarks>
public ushort SB_SHIFT { get; set; }
/// <summary>
/// 0x22..0x27 reserved == 0
/// </summary>
/// <remarks>0x22</remarks>
public byte[] RESERVED { get; set; }
/// <remarks>0x28</remarks>
public uint CSECTDIR { get; set; }
/// <summary>
/// Reported number of big block BATs in the file
/// </summary>
/// <remarks>0x2C</remarks>
public uint NUM_BAT { get; set; }
/// <summary>
/// Directory entry start offset
/// </summary>
/// <remarks>0x30</remarks>
public uint DIRENT_START { get; set; }
/// <summary>
/// 0x34..0x37 transacting signature must be 0
/// </summary>
/// <remarks>0x34</remarks>
public uint TRANSACTING_SIGNATURE { get; set; }
/// <summary>
/// File size transition between small and big blocks
/// </summary>
/// <remarks>0x38</remarks>
public uint THRESHOLD { get; set; }
/// <summary>
/// Offset where small block BATs start
/// </summary>
/// <remarks>0x3C</remarks>
public uint SBAT_START { get; set; }
/// <summary>
/// Reported number of small block BATs in the file
/// </summary>
/// <remarks>0x40</remarks>
public uint NUM_SBAT { get; set; }
/// <summary>
/// Block ID of the first MetaBAT block
/// </summary>
/// <remarks>0x44</remarks>
public uint METABAT_BLOCK { get; set; }
/// <summary>
/// Number of MetaBAT blocks in the file
/// </summary>
/// <remarks>0x48</remarks>
public uint NUM_METABAT { get; set; }
/// <summary>
/// Block ID of the first BAT block
/// </summary>
/// <remarks>0x4C</remarks>
public uint START_BAT { get; set; }
#endregion
#region Derived Properties
/// <summary>
/// Indicate if the contents are Intel Little-Endian
/// </summary>
public bool LITTLE_ENDIAN => BYTE_ORDER == 0xFFFE;
/// <summary>
/// Size of a big block, in bytes
/// </summary>
public int BB_SIZE => 1 << BB_SHIFT;
public int BB_FILTER => BB_SIZE << 1;
/// <summary>
/// Size of a small block, in bytes
/// </summary>
public int SB_SIZE => 1 << SB_SHIFT;
public int SB_FILTER => SB_SIZE << 1;
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private MSOleHeader() { }
/// <summary>
/// Create a new default MSOleHeader
/// </summary>
public static MSOleHeader CreateDefault()
{
MSOleHeader header = new MSOleHeader();
header.SIGNATURE = SIGNATURE_BYTES;
header.CLSID = new byte[16];
header.MINOR_VER = 0x003E;
header.MAJOR_VER = 0x0003;
header.BYTE_ORDER = 0xFFFE;
header.BB_SHIFT = 0x0009;
header.SB_SHIFT = 0x0006;
header.RESERVED = new byte[6];
header.CSECTDIR = 0x00000000;
header.NUM_BAT = 0x00000000;
header.DIRENT_START = 0xFFFFFFFF;
header.TRANSACTING_SIGNATURE = 0x00000000;
header.THRESHOLD = 0x00001000;
// Remainder have default values for their types
return header;
}
/// <summary>
/// Create a new MSOleHeader from data
/// </summary>
public static MSOleHeader Create(byte[] data, int ptr, ref Exception err)
{
if (data == null || data.Length < OLE_HEADER_SIZE)
return null;
MSOleHeader header = new MSOleHeader();
header.SIGNATURE = new byte[8];
Array.Copy(data, ptr + OLE_HEADER_SIGNATURE, header.SIGNATURE, 0, 8);
if (!header.SIGNATURE.SequenceEqual(SIGNATURE_BYTES))
{
err = new ArgumentException("Signature is malformed");
return null;
}
header.CLSID = new byte[16];
Array.Copy(data, ptr + OLE_HEADER_CLSID, header.CLSID, 0, 16);
header.MINOR_VER = GSF_LE_GET_GUINT16(data, ptr + OLE_HEADER_MINOR_VER);
header.MAJOR_VER = GSF_LE_GET_GUINT16(data, ptr + OLE_HEADER_MAJOR_VER);
header.BYTE_ORDER = GSF_LE_GET_GUINT16(data, ptr + OLE_HEADER_BYTE_ORDER);
header.BB_SHIFT = GSF_LE_GET_GUINT16(data, ptr + OLE_HEADER_BB_SHIFT);
header.SB_SHIFT = GSF_LE_GET_GUINT16(data, ptr + OLE_HEADER_SB_SHIFT);
// It makes no sense to have a block larger than 2^31 for now.
// Maybe relax this later, but not much.
if (6 > header.BB_SHIFT || header.BB_SHIFT >= 31 || header.SB_SHIFT > header.BB_SHIFT)
{
err = new ArgumentException("Unreasonable block sizes");
return null;
}
header.RESERVED = new byte[6];
Array.Copy(data, ptr + OLE_HEADER_RESERVED, header.RESERVED, 0, 6);
header.CSECTDIR = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_CSECTDIR);
header.NUM_BAT = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_NUM_BAT);
header.DIRENT_START = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_DIRENT_START);
header.TRANSACTING_SIGNATURE = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_TRANSACTING_SIGNATURE);
if (header.TRANSACTING_SIGNATURE != 0)
{
err = new ArgumentException("Transacting signature must be 0");
return null;
}
header.THRESHOLD = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_THRESHOLD);
header.SBAT_START = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_SBAT_START);
header.NUM_SBAT = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_NUM_SBAT);
header.METABAT_BLOCK = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_METABAT_BLOCK);
header.NUM_METABAT = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_NUM_METABAT);
header.START_BAT = GSF_LE_GET_GUINT32(data, ptr + OLE_HEADER_START_BAT);
if (header.NUM_SBAT == 0
&& header.SBAT_START != GsfMSOleImpl.BAT_MAGIC_END_OF_CHAIN
&& header.SBAT_START != GsfMSOleImpl.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.");
}
return header;
}
/// <summary>
/// Write to data from an existing MSOleHeader
/// </summary>
public bool Write(byte[] data, int ptr)
{
if (data == null || data.Length < OLE_HEADER_SIZE)
return false;
Array.Copy(SIGNATURE, 0, data, ptr + OLE_HEADER_SIGNATURE, SIGNATURE.Length);
Array.Copy(CLSID, 0, data, ptr + OLE_HEADER_CLSID, CLSID.Length);
GSF_LE_SET_GUINT16(data, ptr + OLE_HEADER_MINOR_VER, MINOR_VER);
GSF_LE_SET_GUINT16(data, ptr + OLE_HEADER_MAJOR_VER, MAJOR_VER);
GSF_LE_SET_GUINT16(data, ptr + OLE_HEADER_BYTE_ORDER, BYTE_ORDER);
GSF_LE_SET_GUINT16(data, ptr + OLE_HEADER_BB_SHIFT, BB_SHIFT);
GSF_LE_SET_GUINT16(data, ptr + OLE_HEADER_SB_SHIFT, SB_SHIFT);
Array.Copy(RESERVED, 0, data, ptr + OLE_HEADER_RESERVED, RESERVED.Length);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_CSECTDIR, CSECTDIR);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_NUM_BAT, NUM_BAT);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_DIRENT_START, DIRENT_START);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_TRANSACTING_SIGNATURE, TRANSACTING_SIGNATURE);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_THRESHOLD, THRESHOLD);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_SBAT_START, SBAT_START);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_NUM_SBAT, NUM_SBAT);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_METABAT_BLOCK, METABAT_BLOCK);
GSF_LE_SET_GUINT32(data, ptr + OLE_HEADER_START_BAT, START_BAT);
return true;
}
#endregion
}
/// <summary>
/// MS-OLE Directory Entry (.msi)
/// </summary>
internal class MSOleDirectoryEntry
{
#region Constants
/// <summary>
/// Maximum length of the entry name including the null terminator, in bytes
/// </summary>
public const int DIRENT_MAX_NAME_SIZE = 0x40;
/// <summary>
/// Size of the non-name parts of directory entry header, in bytes
/// </summary>
public const int DIRENT_DETAILS_SIZE = 0x40;
/// <summary>
/// Total size of the directory entry header
/// </summary>
public const int DIRENT_SIZE = (DIRENT_MAX_NAME_SIZE + DIRENT_DETAILS_SIZE);
/// <summary>
/// Magic number indicating the termination of a node
/// </summary>
public const uint DIRENT_MAGIC_END = 0xffffffff;
#region Offsets
private const int DIRENT_NAME = 0x00;
private const int DIRENT_NAME_LEN = 0x40;
private const int DIRENT_TYPE_FLAG = 0x42;
private const int DIRENT_COLOR = 0x43;
private const int DIRENT_PREV = 0x44;
private const int DIRENT_NEXT = 0x48;
private const int DIRENT_CHILD = 0x4c;
private const int DIRENT_CLSID = 0x50;
private const int DIRENT_USERFLAGS = 0x60;
private const int DIRENT_CREATE_TIME = 0x64;
private const int DIRENT_MODIFY_TIME = 0x6C;
private const int DIRENT_FIRSTBLOCK = 0x74;
private const int DIRENT_FILE_SIZE = 0x78;
private const int DIRENT_RESERVED = 0x7C;
#endregion
#endregion
#region Properties
/// <summary>
/// Name of the entry as a byte array
/// </summary>
/// <remarks>0x00</remarks>
public byte[] NAME { get; set; }
/// <summary>
/// Length of the entry name including the null terminator, in bytes
/// </summary>
/// <remarks>0x40</remarks>
public ushort NAME_LEN { get; set; }
/// <summary>
/// Indicates the type of directory entry
/// </summary>
/// <remarks>0x42</remarks>
public DIRENT_TYPE TYPE_FLAG { get; set; }
/// <remarks>0x43</remarks>
public byte COLOR { get; set; }
/// <summary>
/// Index of the previous item in the linked list, DIRENT_MAGIC_END if none
/// </summary>
/// <remarks>0x44</remarks>
public uint PREV { get; set; }
/// <summary>
/// Index of the next item in the linked list, DIRENT_MAGIC_END if none
/// </summary>
/// <remarks>0x48</remarks>
public uint NEXT { get; set; }
/// <summary>
/// Index of the first child directory entry, DIRENT_MAGIC_END if none
/// </summary>
/// <remarks>0x4C</remarks>
public uint CHILD { get; set; }
/// <summary>
/// 16 byte GUID used by some apps; Only for dirs
/// </summary>
/// <remarks>0x50</remarks>
public byte[] CLSID { get; set; }
/// <summary>
/// Only for dirs
/// </summary>
/// <remarks>0x60</remarks>
public uint USERFLAGS { get; set; }
/// <summary>
/// Entry creation time; For files
/// </summary>
/// <remarks>0x64</remarks>
public ulong CREATE_TIME { get; set; }
/// <summary>
/// Entry modification time; For files
/// </summary>
/// <remarks>0x6C</remarks>
public ulong MODIFY_TIME { get; set; }
/// <summary>
/// Index of the first block that contains this directory entry
/// </summary>
/// <remarks>0x74</remarks>
public uint FIRSTBLOCK { get; set; }
/// <summary>
/// Size of the file contained within this directory entry
/// </summary>
/// <remarks>0x78</remarks>
public uint FILE_SIZE { get; set; }
/// <summary>
/// 0x7c..0x7f reserved == 0
/// </summary>
/// <remarks>0x7C</remarks>
public uint RESERVED { get; set; }
#endregion
#region Derived Properties
/// <summary>
/// Directory Entry name as a UTF-8 encoded string
/// </summary>
public string NAME_STRING
{
get
{
if (NAME_LEN <= 0 || NAME_LEN > DIRENT_MAX_NAME_SIZE)
return string.Empty;
// !#%!@$#^
// Sometimes, rarely, people store the stream name as ascii
// rather than utf16. Do a validation first just in case.
int end;
try { end = new UnicodeEncoding(false, true).GetCharCount(NAME); }
catch { end = -1; }
if (end == -1)
{
byte[] direntNameBytes = Encoding.Convert(Encoding.ASCII, Encoding.UTF8, NAME);
return Encoding.UTF8.GetString(direntNameBytes).TrimEnd('\0');
}
else
{
byte[] direntNameBytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, NAME);
return Encoding.UTF8.GetString(direntNameBytes).TrimEnd('\0');
}
}
}
/// <summary>
/// Last modified time as a nullable DateTime
/// </summary>
public DateTime? MODIFY_DATETIME => MODIFY_TIME == 0 ? (DateTime?)null : DateTime.FromFileTime((long)MODIFY_TIME);
/// <summary>
/// Determine if this entry is a directory
/// </summary>
public bool IS_DIRECTORY => TYPE_FLAG != DIRENT_TYPE.DIRENT_TYPE_FILE;
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private MSOleDirectoryEntry() { }
/// <summary>
/// Create a new default MSOleDirectoryEntry
/// </summary>
public static MSOleDirectoryEntry CreateDefault()
{
// TODO: Figure out if there are any sane defaults
return new MSOleDirectoryEntry();
}
/// <summary>
/// Create a new MSOleDirectoryEntry from data
/// </summary>
public static MSOleDirectoryEntry Create(byte[] data, int ptr, ref Exception err)
{
if (data == null || data.Length < DIRENT_SIZE)
return null;
MSOleDirectoryEntry directoryEntry = new MSOleDirectoryEntry();
directoryEntry.NAME = new byte[DIRENT_MAX_NAME_SIZE];
Array.Copy(data, ptr + DIRENT_NAME, directoryEntry.NAME, 0, directoryEntry.NAME.Length);
directoryEntry.NAME_LEN = GSF_LE_GET_GUINT16(data, ptr + DIRENT_NAME_LEN);
directoryEntry.TYPE_FLAG = (DIRENT_TYPE)GSF_LE_GET_GUINT8(data, ptr + DIRENT_TYPE_FLAG);
if (directoryEntry.TYPE_FLAG != DIRENT_TYPE.DIRENT_TYPE_DIR
&& directoryEntry.TYPE_FLAG != DIRENT_TYPE.DIRENT_TYPE_FILE
&& directoryEntry.TYPE_FLAG != DIRENT_TYPE.DIRENT_TYPE_ROOTDIR)
{
err = new Exception($"Unknown stream type 0x{directoryEntry.TYPE_FLAG:x}");
return null;
}
directoryEntry.COLOR = GSF_LE_GET_GUINT8(data, ptr + DIRENT_COLOR);
directoryEntry.PREV = GSF_LE_GET_GUINT32(data, ptr + DIRENT_PREV);
directoryEntry.NEXT = GSF_LE_GET_GUINT32(data, ptr + DIRENT_NEXT);
directoryEntry.CHILD = GSF_LE_GET_GUINT32(data, ptr + DIRENT_CHILD);
directoryEntry.CLSID = new byte[0x10];
Array.Copy(data, ptr + DIRENT_CLSID, directoryEntry.CLSID, 0, directoryEntry.CLSID.Length);
directoryEntry.USERFLAGS = GSF_LE_GET_GUINT32(data, ptr + DIRENT_USERFLAGS);
directoryEntry.CREATE_TIME = GSF_LE_GET_GUINT64(data, ptr + DIRENT_CREATE_TIME);
directoryEntry.MODIFY_TIME = GSF_LE_GET_GUINT64(data, ptr + DIRENT_MODIFY_TIME);
directoryEntry.FIRSTBLOCK = GSF_LE_GET_GUINT32(data, ptr + DIRENT_FIRSTBLOCK);
directoryEntry.FILE_SIZE = GSF_LE_GET_GUINT32(data, ptr + DIRENT_FILE_SIZE);
directoryEntry.RESERVED = GSF_LE_GET_GUINT32(data, ptr + DIRENT_RESERVED);
return directoryEntry;
}
/// <summary>
/// Write to data from an existing MSOleHeader
/// </summary>
public bool Write(byte[] data, int ptr)
{
if (data == null || data.Length < DIRENT_SIZE)
return false;
Array.Copy(NAME, 0, data, ptr + DIRENT_NAME, NAME.Length);
GSF_LE_SET_GUINT16(data, ptr + DIRENT_NAME_LEN, NAME_LEN);
GSF_LE_SET_GUINT8(data, ptr + DIRENT_TYPE_FLAG, (byte)TYPE_FLAG);
GSF_LE_SET_GUINT8(data, ptr + DIRENT_COLOR, COLOR);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_PREV, PREV);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_NEXT, NEXT);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_CHILD, CHILD);
Array.Copy(CLSID, 0, data, ptr + DIRENT_CLSID, CLSID.Length);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_USERFLAGS, USERFLAGS);
GSF_LE_SET_GUINT64(data, ptr + DIRENT_CREATE_TIME, CREATE_TIME);
GSF_LE_SET_GUINT64(data, ptr + DIRENT_MODIFY_TIME, MODIFY_TIME);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_FIRSTBLOCK, FIRSTBLOCK);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_FILE_SIZE, FILE_SIZE);
GSF_LE_SET_GUINT32(data, ptr + DIRENT_RESERVED, RESERVED);
return true;
}
#endregion
}
public static class GsfMSOleImpl
{
public const int OLE_HEADER_CSECTDIR = 0x28;
public const int OLE_HEADER_NUM_BAT = 0x2C;
public const int OLE_HEADER_SBAT_START = 0x3C;
public const int OLE_HEADER_START_BAT = 0x4C;
public const int BAT_INDEX_SIZE = 4;
public const int OLE_HEADER_METABAT_SIZE = ((MSOleHeader.OLE_HEADER_SIZE - OLE_HEADER_START_BAT) / BAT_INDEX_SIZE);
/* 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 */
}
#endregion
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,378 +0,0 @@
/* 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

@@ -1,784 +0,0 @@
/* 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

@@ -1,723 +0,0 @@
/* 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

@@ -1,90 +0,0 @@
/* 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

@@ -1,266 +0,0 @@
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* gsf-utils.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
{
/// <summary>
/// Do this the ugly way so that we don't have to worry about alignment
/// </summary>
internal static class GsfUtils
{
#region GET
/// <summary>
/// Interpret binary data as an unsigned 8-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static byte GSF_LE_GET_GUINT8(byte[] data, int p)
=> data[p];
/// <summary>
/// Interpret binary data as an unsigned 16-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static ushort GSF_LE_GET_GUINT16(byte[] data, int p)
=> (ushort)((data[p] << 0)
| (data[p + 1] << 8));
/// <summary>
/// Interpret binary data as an unsigned 32-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static uint GSF_LE_GET_GUINT32(byte[] data, int p)
=> (uint)((data[p] << 0)
| (data[p + 1] << 8)
| (data[p + 2] << 16)
| (data[p + 3] << 24));
/// <summary>
/// Interpret binary data as an unsigned 64-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static ulong GSF_LE_GET_GUINT64(byte[] data, int p)
=> (uint)((data[p] << 0)
| (data[p + 1] << 8)
| (data[p + 2] << 16)
| (data[p + 3] << 24)
| (data[p + 4] << 32)
| (data[p + 5] << 40)
| (data[p + 6] << 48)
| (data[p + 7] << 56));
/// <summary>
/// Interpret binary data as a signed 8-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static sbyte GSF_LE_GET_GINT8(byte[] data, int p) => (sbyte)GSF_LE_GET_GINT8(data, p);
/// <summary>
/// Interpret binary data as a signed 16-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static short GSF_LE_GET_GINT16(byte[] data, int p) => (short)GSF_LE_GET_GUINT16(data, p);
/// <summary>
/// Interpret binary data as a signed 32-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static int GSF_LE_GET_GINT32(byte[] data, int p) => (int)GSF_LE_GET_GUINT32(data, p);
/// <summary>
/// Interpret binary data as a signed 64-bit integer in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static long GSF_LE_GET_GINT64(byte[] data, int p) => (long)GSF_LE_GET_GUINT64(data, p);
/// <summary>
/// Interpret binary data as a float in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static float GSF_LE_GET_FLOAT(byte[] data, int p)
{
byte[] temp = new ReadOnlySpan<byte>(data, p, 4).ToArray();
if (!BitConverter.IsLittleEndian)
Array.Reverse(temp);
return BitConverter.ToSingle(temp, 0);
}
/// <summary>
/// Interpret binary data as a double in little endian order.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <returns>Interpreted data</returns>
public static double GSF_LE_GET_DOUBLE(byte[] data, int p)
{
byte[] temp = new ReadOnlySpan<byte>(data, p, 8).ToArray();
if (!BitConverter.IsLittleEndian)
Array.Reverse(temp);
return BitConverter.ToDouble(temp, 0);
}
#endregion
#region SET
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">8-bit unsigned integer</param>
public static void GSF_LE_SET_GUINT8(byte[] data, int p, byte dat)
{
data[p] = (byte)(dat & 0xff);
}
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">16-bit unsigned integer</param>
public static void GSF_LE_SET_GUINT16(byte[] data, int p, ushort dat)
{
data[p + 0] = (byte)(dat & 0xff);
data[p + 1] = (byte)((dat >> 8) & 0xff);
}
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">32-bit unsigned integer</param>
public static void GSF_LE_SET_GUINT32(byte[] data, int p, uint dat)
{
data[p + 0] = (byte)(dat & 0xff);
data[p + 1] = (byte)((dat >> 8) & 0xff);
data[p + 2] = (byte)((dat >> 16) & 0xff);
data[p + 3] = (byte)((dat >> 24) & 0xff);
}
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">64-bit unsigned integer</param>
public static void GSF_LE_SET_GUINT64(byte[] data, int p, ulong dat)
{
data[p + 0] = (byte)(dat & 0xff);
data[p + 1] = (byte)((dat >> 8) & 0xff);
data[p + 2] = (byte)((dat >> 16) & 0xff);
data[p + 3] = (byte)((dat >> 24) & 0xff);
data[p + 4] = (byte)((dat >> 32) & 0xff);
data[p + 5] = (byte)((dat >> 40) & 0xff);
data[p + 6] = (byte)((dat >> 48) & 0xff);
data[p + 7] = (byte)((dat >> 56) & 0xff);
}
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">8-bit signed integer</param>
public static void GSF_LE_SET_GINT8(byte[] data, int p, sbyte dat) => GSF_LE_SET_GUINT8(data, p, (byte)dat);
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">16-bit signed integer</param>
public static void GSF_LE_SET_GINT16(byte[] data, int p, short dat) => GSF_LE_SET_GUINT16(data, p, (ushort)dat);
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">32-bit signed integer</param>
public static void GSF_LE_SET_GINT32(byte[] data, int p, int dat) => GSF_LE_SET_GUINT32(data, p, (uint)dat);
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">64-bit signed integer</param>
public static void GSF_LE_SET_GINT64(byte[] data, int p, long dat) => GSF_LE_SET_GUINT64(data, p, (ulong)dat);
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">Float to be stored</param>
public static void GSF_LE_SET_FLOAT(byte[] data, int p, float dat)
{
byte[] temp = BitConverter.GetBytes(dat);
if (!BitConverter.IsLittleEndian)
Array.Reverse(temp);
Array.Copy(temp, 0, data, p, 4);
}
/// <summary>
/// Store <paramref name="dat"/> in little endian order in memory pointed to by <paramref name="p"/>.
/// </summary>
/// <param name="data">Storage</param>
/// <param name="p">Pointer to storage</param>
/// <param name="dat">Double to be stored</param>
public static void GSF_LE_SET_DOUBLE(byte[] data, int p, double dat)
{
byte[] temp = BitConverter.GetBytes(dat);
if (!BitConverter.IsLittleEndian)
Array.Reverse(temp);
Array.Copy(temp, 0, data, p, 8);
}
#endregion
}
}

View File

@@ -1,389 +0,0 @@
/* 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)
{
int p = name.IndexOf(GsfZipImpl.ZIP_NAME_SEPARATOR);
if (p != -1)
{
// A directory
string dirname = name.Substring(0, p);
GsfZipVDir child = ChildByName(dirname);
if (child == null)
{
child = Create(dirname, true, null);
AddChild(child);
}
if (p + 1 < name.Length && name[p + 1] != '\0')
{
name = name.Substring(p + 1);
child.Insert(name, dirent);
}
}
else
{
// A simple file name
GsfZipVDir 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

@@ -1,121 +0,0 @@
/* 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

@@ -1,872 +0,0 @@
/* 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 static LibGSF.GsfMSOleImpl;
using static LibGSF.GsfUtils;
namespace LibGSF.Input
{
internal class MSOleBAT
{
#region Properties
public uint[] Blocks { get; set; } = null;
#endregion
#region Derived Properties
/// <summary>
/// Number of blocks in the BAT as an unsigned Int32
/// </summary>
public uint NumBlocks => (uint)(Blocks?.Length ?? 0);
#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="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(in MSOleBAT metabat, uint block, out MSOleBAT res)
{
List<uint> bat = new List<uint>();
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.Add(block);
block = metabat.Blocks[block];
}
res = new MSOleBAT { Blocks = bat.ToArray() };
if (block != BAT_MAGIC_END_OF_CHAIN)
{
Console.Error.WriteLine("This OLE2 file is invalid.");
Console.Error.WriteLine($"The Block Allocation Table for one of the streams had {block} instead of a terminator ({BAT_MAGIC_END_OF_CHAIN}).");
Console.Error.WriteLine("We might still be able to extract some data, but you'll want to check the file.");
}
return false;
}
#endregion
}
internal class MSOleDirent
{
#region Properties
/// <summary>
/// Internal representation of the MS-OLE directory entry header
/// </summary>
public MSOleDirectoryEntry Header { get; set; }
public GsfMSOleSortingKey Key { get; set; }
public uint Index { get; set; }
public bool UseSmallBlock { get; set; }
public List<MSOleDirent> Children { get; set; } = new List<MSOleDirent>();
#endregion
#region Functions
public void Free()
{
Key = null;
foreach (MSOleDirent child in Children)
{
child.Free();
}
Children = null;
Header = null;
}
#endregion
}
internal class MSOleInfo
{
#region Properties
/// <summary>
/// Internal representation of the MS-OLE header
/// </summary>
public MSOleHeader Header { get; set; }
public MSOleBAT BigBlockBat { get; set; }
public MSOleBAT SmallBlockBat { get; set; }
/// <summary>
/// MetaBAT for all BATs in the file
/// </summary>
public uint[] MetaBAT { get; set; }
/// <summary>
/// Maximum number of blocks derived from total input length and block size
/// </summary>
public long MaxBlock { get; set; }
public MSOleDirent RootDir { get; set; }
public GsfInput SmallBlockFile { get; set; }
public int RefCount { get; set; }
#endregion
#region Functions
public void Unref()
{
if (RefCount-- != 1)
return;
Header = null;
if (RootDir != null)
{
RootDir.Free();
RootDir = null;
}
if (SmallBlockFile != null)
SmallBlockFile = null;
}
public MSOleInfo Ref()
{
RefCount++;
return this;
}
#endregion
}
public class GsfInfileMSOle : GsfInfile
{
#region Properties
internal GsfInput Input { get; private set; } = null;
internal MSOleInfo Info { get; private set; } = null;
internal MSOleDirent DirectoryEntry { get; private set; }
internal MSOleBAT Bat { get; private set; }
internal long CurBlock { get; private set; } = BAT_MAGIC_UNUSED;
/// <remarks>Actually `{ byte[] Buf, long BufSize }`</remarks>
internal byte[] Stream { get; private set; }
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private GsfInfileMSOle() { }
/// <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)
{
if (source == null)
return null;
GsfInfileMSOle ole = new GsfInfileMSOle
{
Input = GsfInputProxy.Create(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>
/// Destructor
/// </summary>
~GsfInfileMSOle()
{
Input = null;
if (Info != null && Info.SmallBlockFile != this)
{
Info.Unref();
Info = null;
}
Stream = null;
}
#endregion
#region Functions
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err) => (Container as GsfInfileMSOle)?.NewChild(DirectoryEntry, ref err);
/// <inheritdoc/>
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.Header.BB_SHIFT);
long last_block = ((CurrentOffset + num_bytes - 1) >> Info.Header.BB_SHIFT);
long offset = CurrentOffset & Info.Header.BB_FILTER;
if (last_block >= Bat.NumBlocks)
return null;
// Optimization: are all the raw blocks contiguous?
long i = first_block;
long raw_block = Bat.Blocks[i];
while (++i <= last_block && ++raw_block == Bat.Blocks[i]) ;
if (i > last_block)
{
if (!SeekBlock(Bat.Blocks[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;
}
int ptr = optional_buffer_ptr; // optional_buffer[optional_buffer_ptr]
int count;
for (i = first_block; i <= last_block; i++, ptr += count, num_bytes -= count)
{
count = (int)Math.Min(Info.Header.BB_SIZE - offset, num_bytes);
if (!SeekBlock(Bat.Blocks[i], offset))
return null;
if (Input.Read(count, optional_buffer, ptr) == null)
return null;
offset = 0;
}
CurBlock = BAT_MAGIC_UNUSED;
return optional_buffer;
}
/// <inheritdoc/>
protected override bool SeekImpl(long offset, SeekOrigin whence)
{
CurBlock = BAT_MAGIC_UNUSED;
return false;
}
/// <inheritdoc/>
public override GsfInput ChildByIndex(int i, ref Exception error)
{
foreach (MSOleDirent dirent in DirectoryEntry.Children)
{
if (i-- <= 0)
return NewChild(dirent, ref error);
}
return null;
}
/// <inheritdoc/>
public override string NameByIndex(int i)
{
foreach (MSOleDirent dirent in DirectoryEntry.Children)
{
if (i-- <= 0)
return dirent.Header.NAME_STRING;
}
return null;
}
/// <inheritdoc/>
public override GsfInput ChildByName(string name, ref Exception error)
{
foreach (MSOleDirent dirent in DirectoryEntry.Children)
{
if (dirent.Header.NAME_STRING != null && dirent.Header.NAME_STRING == name)
return NewChild(dirent, ref error);
}
return null;
}
/// <inheritdoc/>
public override int NumChildren()
{
if (DirectoryEntry == null)
return -1;
if (!DirectoryEntry.Header.IS_DIRECTORY)
return -1;
return DirectoryEntry.Children.Count;
}
/// <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.Header.CLSID, res, DirectoryEntry.Header.CLSID.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 BB_SIZE (sector size) when BB_SIZE > 512.
if (Input.Seek(Math.Max(MSOleHeader.OLE_HEADER_SIZE, Info.Header.BB_SIZE) + (block << Info.Header.BB_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.Header.BB_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 int? ReadMetabat(uint[] bats, int batsPtr, uint max_bat, in uint[] metabat, int metabatPtr, in uint 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 end = Info.Header.BB_SIZE;
for (int batPtr = 0; batPtr < end; batPtr += BAT_INDEX_SIZE, batsPtr++)
{
bats[batsPtr] = GSF_LE_GET_GUINT32(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.Header.BB_SIZE / BAT_INDEX_SIZE);
while (i-- > 0)
{
bats[batsPtr++] = BAT_MAGIC_UNUSED;
}
}
}
return batsPtr;
}
/// <summary>
/// Copy some some raw data into an array of uint.
/// </summary>
private static void GetUnsignedInts(uint[] dst, int dstPtr, in byte[] src, int srcPtr, int num_bytes)
{
for (; (num_bytes -= BAT_INDEX_SIZE) >= 0; srcPtr += BAT_INDEX_SIZE)
{
dst[dstPtr++] = GSF_LE_GET_GUINT32(src, srcPtr);
}
}
private GsfInput GetSmallBlockFile()
{
if (Info.SmallBlockFile != null)
return Info.SmallBlockFile;
Exception err = null;
Info.SmallBlockFile = NewChild(Info.RootDir, ref err);
if (Info.SmallBlockFile == null)
return null;
// Avoid creating a circular reference
if (Info.SmallBlockFile is GsfInfileMSOle sbFile)
sbFile.Info.Unref();
if (Info.SmallBlockBat.Blocks != null)
return null;
if (MSOleBAT.Create(Info.BigBlockBat, Info.Header.SBAT_START, out MSOleBAT meta_sbat))
return null;
Info.SmallBlockBat.Blocks = new uint[meta_sbat.NumBlocks * (Info.Header.BB_SIZE / BAT_INDEX_SIZE)];
ReadMetabat(Info.SmallBlockBat.Blocks, 0, Info.SmallBlockBat.NumBlocks, meta_sbat.Blocks, 0, meta_sbat.NumBlocks);
return Info.SmallBlockFile;
}
private static int DirectoryEntryCompare(MSOleDirent a, MSOleDirent b) => SortingKeyCompare(a.Key, b.Key);
/// <summary>
/// Parse dirent number <paramref name="entry"/> and recursively handle its siblings and children.
/// parent is optional.
private MSOleDirent CreateDirectoryEntry(uint entry, MSOleDirent parent, bool[] seen_before)
{
if (entry >= MSOleDirectoryEntry.DIRENT_MAGIC_END)
return null;
if (entry > uint.MaxValue / MSOleDirectoryEntry.DIRENT_SIZE)
return null;
uint block = (entry * MSOleDirectoryEntry.DIRENT_SIZE) >> Info.Header.BB_SHIFT;
if (block >= Bat.NumBlocks)
return null;
if (seen_before[entry])
return null;
seen_before[entry] = true;
byte[] data = GetBlock(Bat.Blocks[block], null);
if (data == null)
return null;
int dataPtr = 0; // data[0]
dataPtr += (int)((MSOleDirectoryEntry.DIRENT_SIZE * entry) % Info.Header.BB_SIZE);
Exception err = null;
MSOleDirectoryEntry directoryEntry = MSOleDirectoryEntry.Create(data, dataPtr, ref err);
if (err != null)
{
Console.Error.WriteLine(err.Message);
return null;
}
if (parent == null && directoryEntry.TYPE_FLAG != DIRENT_TYPE.DIRENT_TYPE_ROOTDIR)
{
// See bug 346118.
Console.Error.WriteLine("Root directory is not marked as such.");
directoryEntry.TYPE_FLAG = DIRENT_TYPE.DIRENT_TYPE_ROOTDIR;
}
// It looks like directory (and root directory) sizes are sometimes bogus
if (!(directoryEntry.TYPE_FLAG == DIRENT_TYPE.DIRENT_TYPE_DIR || directoryEntry.TYPE_FLAG == DIRENT_TYPE.DIRENT_TYPE_ROOTDIR || directoryEntry.FILE_SIZE <= (uint)Input.Size))
return null;
MSOleDirent dirent = new MSOleDirent
{
Header = directoryEntry,
Key = GsfMSOleSortingKey.Create(directoryEntry.NAME_STRING),
Index = entry,
// Root dir is always big block
UseSmallBlock = parent != null && (directoryEntry.FILE_SIZE < Info.Header.THRESHOLD),
Children = new List<MSOleDirent>(),
};
if (parent != null)
{
parent.Children.Add(dirent);
parent.Children.Sort(DirectoryEntryCompare);
}
// NOTE : These links are a tree, not a linked list
CreateDirectoryEntry(directoryEntry.PREV, parent, seen_before);
CreateDirectoryEntry(directoryEntry.NEXT, parent, seen_before);
if (dirent.Header.IS_DIRECTORY)
CreateDirectoryEntry(directoryEntry.CHILD, dirent, seen_before);
else if (directoryEntry.CHILD != MSOleDirectoryEntry.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
{
Input = input,
Info = Info.Ref(),
};
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)
{
// Seek to the header
if (Input.Seek(0, SeekOrigin.Begin))
{
err = new Exception("Cannot seek to header");
return true;
}
byte[] header = Input.Read(MSOleHeader.OLE_HEADER_SIZE, null);
if (header == null)
{
err = new Exception("Header could not be read");
return true;
}
MSOleHeader headerImpl = MSOleHeader.Create(header, 0, ref err);
if (headerImpl == null)
{
err = new Exception("Header could not be parsed");
return true;
}
// There should always be at least 1 BAT block
if ((Input.Size >> headerImpl.BB_SHIFT) < 1)
{
err = new Exception("Unreasonable block sizes");
return true;
}
Info = new MSOleInfo
{
Header = headerImpl,
BigBlockBat = new MSOleBAT(),
SmallBlockBat = new MSOleBAT(),
MetaBAT = null,
MaxBlock = (Input.Size - MSOleHeader.OLE_HEADER_SIZE + headerImpl.BB_SIZE - 1) / headerImpl.BB_SIZE,
RootDir = null,
SmallBlockFile = null,
RefCount = 1,
};
int metabatPtr = 0; // MetaBAT[0]
uint last;
int? ptr = null;
// Very rough heuristic, just in case
uint num_bat = headerImpl.NUM_BAT;
if (num_bat < Info.MaxBlock && headerImpl.NUM_SBAT < Info.MaxBlock)
{
Info.BigBlockBat.Blocks = new uint[num_bat * (headerImpl.BB_SIZE / BAT_INDEX_SIZE)];
Info.MetaBAT = new uint[Math.Max(headerImpl.BB_SIZE, MSOleHeader.OLE_HEADER_SIZE)];
// Reading the elements invalidates this memory, make copy
GetUnsignedInts(Info.MetaBAT, metabatPtr, header, OLE_HEADER_START_BAT, MSOleHeader.OLE_HEADER_SIZE - OLE_HEADER_START_BAT);
last = Math.Min(num_bat, OLE_HEADER_METABAT_SIZE);
ptr = ReadMetabat(Info.BigBlockBat.Blocks, 0, Info.BigBlockBat.NumBlocks, Info.MetaBAT, metabatPtr, (uint)(metabatPtr + last));
num_bat -= last;
}
uint metabat_block = headerImpl.METABAT_BLOCK;
uint num_metabat = headerImpl.NUM_METABAT;
last = (uint)((Info.Header.BB_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(Info.MetaBAT, metabatPtr, tmp, 0, Info.Header.BB_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 = Info.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(Info.BigBlockBat.Blocks, ptr.Value, Info.BigBlockBat.NumBlocks, Info.MetaBAT, metabatPtr, (uint)(metabatPtr + last));
}
bool fail = (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.BigBlockBat, headerImpl.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 << Info.Header.BB_SHIFT) * MSOleDirectoryEntry.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.Header.MODIFY_DATETIME;
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);
}
private GsfInput NewChild(MSOleDirent dirent, ref Exception err)
{
GsfInfileMSOle child = PartiallyDuplicate(ref err);
if (child == null)
return null;
child.DirectoryEntry = dirent;
child.Size = dirent.Header.FILE_SIZE;
child.ModTime = dirent.Header.MODIFY_DATETIME;
// The root dirent defines the small block file
if (dirent.Index != 0)
{
child.Name = dirent.Header.NAME_STRING;
child.Container = this;
if (dirent.Header.IS_DIRECTORY)
{
// Be wary. It seems as if some implementations pretend that the
// directories contain data
child.Size = 0;
return child;
}
}
MSOleBAT metabat;
GsfInput sb_file = null;
MSOleInfo info = Info;
// Build the bat
if (dirent.UseSmallBlock)
{
sb_file = GetSmallBlockFile();
metabat = info.SmallBlockBat;
if (sb_file == null)
{
err = new Exception("Failed to access child");
return null;
}
}
else
{
metabat = info.BigBlockBat;
}
if (MSOleBAT.Create(metabat, dirent.Header.FIRSTBLOCK, out MSOleBAT tempBat))
return null;
child.Bat = tempBat;
if (dirent.UseSmallBlock)
{
if (sb_file == null)
return null;
int remaining = (int)dirent.Header.FILE_SIZE;
child.Stream = new byte[remaining];
for (uint i = 0; remaining > 0 && i < child.Bat.NumBlocks; i++, remaining -= info.Header.SB_SIZE)
{
if (sb_file.Seek(child.Bat.Blocks[i] << info.Header.SB_SHIFT, SeekOrigin.Begin))
{
Console.Error.WriteLine($"Failure seeking to block {i} for '{dirent.Header.NAME_STRING}'");
err = new Exception("Failure seeking block");
return null;
}
if (sb_file.Read(Math.Min(remaining, info.Header.SB_SIZE), child.Stream, (int)(i << info.Header.SB_SHIFT)) == null)
{
Console.Error.WriteLine($"Failure reading block {i} for '{dirent.Header.NAME_STRING}'");
err = new Exception("Failure reading block");
return null;
}
}
if (remaining > 0)
{
err = new Exception("Insufficient blocks");
Console.Error.WriteLine($"Small-block file '{dirent.Header.NAME_STRING}' has insufficient blocks ({child.Bat.NumBlocks}) for the stated size ({dirent.Header.FILE_SIZE})");
return null;
}
}
return child;
}
#endregion
}
}

View File

@@ -1,373 +0,0 @@
/*
* 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;
using static LibGSF.GsfUtils;
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 and Destructor
/// <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.DirectoryRead(ref err))
return vba;
if (err != null)
err = new Exception("Unable to parse VBA header");
return null;
}
/// <summary>
/// Destructor
/// </summary>
~GsfInfileMSVBA()
{
if (Modules != null)
{
Modules.Clear();
Modules = null;
}
if (Source != null)
Source = 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 = GsfInfileMSOle.Create(input, ref err);
if (infile != null)
{
// 1) Try XLS
vba = infile.ChildByVariableName("_VBA_PROJECT_CUR", "VBA");
// 2) DOC
if (vba == null)
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 int inflatedSize, 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 DirectoryRead(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;
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)
{
//#ifdef OLD_VBA_DUMP
//puts("</project>");
//#endif
err = new Exception("VBA project header problem");
return false;
}
tag = GSF_LE_GET_GUINT16(inflated_data, ptr);
uint len = GSF_LE_GET_GUINT32(inflated_data, ptr + 2);
ptr += 6;
if ((ptr + len) > end)
{
//#ifdef OLD_VBA_DUMP
//puts("</project>");
//#endif
err = new Exception("VBA project header problem");
return false;
}
switch (tag)
{
case 4:
name = Encoding.UTF8.GetString(inflated_data, ptr, (int)len);
//#ifdef OLD_VBA_DUMP
//puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
//printf("<project name=\"%s\">", name);
//#endif
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 = GSF_LE_GET_GUINT16(inflated_data, ptr);
break;
// Dependencies
case 0x0d: break;
case 0x2f: break;
case 0x30: break;
case 0x33: break;
case 0x3e: break;
case 0x16:
//name = g_strndup(ptr, len);
//g_print("Depend Name : '%s'\n", name);
break;
// Elements
case 0x47: break;
case 0x32: break;
case 0x1a:
//name = g_strndup(ptr, len);
//g_print("Element Name : '%s'\n", name);
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, GSF_LE_GET_GUINT32(inflated_data, ptr));
elem_stream = null;
element_count--;
break;
default:
//g_print("tag %hx : len %u\n", tag, len);
//gsf_mem_dump(ptr, len);
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

@@ -1,142 +0,0 @@
/* 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 and Destructor
/// <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;
}
/// <summary>
/// Destructor
/// </summary>
~GsfInfileStdio()
{
Root = null;
Children.Clear();
Children = null;
}
#endregion
#region Functions
/// <inheritdoc/>
protected override GsfInput DupImpl(ref Exception err)
{
GsfInfileStdio dst = new GsfInfileStdio
{
Root = 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;
if (Directory.Exists(path))
child = GsfInfileStdio.Create(path, ref err);
else
child = GsfInputStdio.Create(path, ref err);
return child;
}
#endregion
}
}

View File

@@ -1,494 +0,0 @@
/*
* 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
{
// TODO: Can this be made internal?
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>
// TODO: Can this be made internal?
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() { }
/// <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 = GsfInputProxy.Create(source),
};
if (tar.Err != null)
{
err = tar.Err;
return null;
}
tar.InitInfo();
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
{
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/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => false;
/// <inheritdoc/>
public override GsfInput ChildByIndex(int i, ref Exception error)
{
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 = 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;
int s = 0; // name[0]
while (true)
{
int s0 = s;
// Find a directory component, if any.
while (true)
{
if (name[s] == 0)
{
if (last && s != s0)
break;
else
return dir;
}
// This is deliberately slash-only.
if (name[s] == '/')
break;
s++;
}
string dirname = name.Substring(s0, s - s0);
while (name[s] == '/')
{
s++;
}
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;
TarHeader end = new TarHeader();
byte[] headerBytes;
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.
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,
};
GsfInfileTar 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

@@ -1,797 +0,0 @@
/* 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.GsfUtils;
using static LibGSF.GsfZipImpl;
namespace LibGSF.Input
{
// TODO: Can this be made internal?
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++)
{
DirectoryEntries[i].Free();
}
DirectoryEntries.Clear();
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 PartiallyDuplicate.
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;
GsfInfileZip zip = new GsfInfileZip
{
Source = source,
};
if (zip.Err != null)
{
err = zip.Err;
return null;
}
return zip;
}
/// <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/>
protected override bool SeekImpl(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 && GSF_LE_GET_GUINT32(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)GSF_LE_GET_GUINT16(extra, extraPtr);
uint flen = GSF_LE_GET_GUINT16(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
|| GSF_LE_GET_GUINT32(data, 0) != ZIP_DIRENT_SIGNATURE)
{
return null;
}
ushort name_len = GSF_LE_GET_GUINT16(header, ZIP_DIRENT_NAME_SIZE);
ushort extras_len = GSF_LE_GET_GUINT16(header, ZIP_DIRENT_EXTRAS_SIZE);
ushort comment_len = GSF_LE_GET_GUINT16(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 = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_FLAGS);
GsfZipCompressionMethod compression_method = (GsfZipCompressionMethod)GSF_LE_GET_GUINT16(header, ZIP_DIRENT_COMPR_METHOD);
uint dostime = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_DOSTIME);
uint crc32 = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_CRC32);
ulong csize = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_CSIZE);
ulong usize = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_USIZE);
ulong off = GSF_LE_GET_GUINT32(header, ZIP_DIRENT_OFFSET);
uint disk_start = GSF_LE_GET_GUINT16(header, ZIP_DIRENT_DISKSTART);
if (usize == 0xffffffffu && elen >= 8)
{
usize = GSF_LE_GET_GUINT64(extra, extraPtr);
extraPtr += 8;
elen -= 8;
}
if (csize == 0xffffffffu && elen >= 8)
{
csize = GSF_LE_GET_GUINT64(extra, extraPtr);
extraPtr += 8;
elen -= 8;
}
if (off == 0xffffffffu && elen >= 8)
{
off = GSF_LE_GET_GUINT64(extra, extraPtr);
extraPtr += 8;
elen -= 8;
}
if (disk_start == 0xffffu && elen >= 4)
{
disk_start = GSF_LE_GET_GUINT32(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 = (long)csize;
dirent.UncompressedSize = (long)usize;
dirent.Offset = (long)off;
dirent.DosTime = dostime;
dirent.Zip64 = zip64;
//g_print("%s = 0x%x @ %" GSF_OFF_T_FORMAT "\n", name, off, *offset);
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[0] + ZIP_ZIP64_LOCATOR_SIZE
ulong entries = GSF_LE_GET_GUINT16(locator, data + ZIP_TRAILER_ENTRIES);
ulong dir_pos = GSF_LE_GET_GUINT32(locator, data + ZIP_TRAILER_DIR_POS);
if (GSF_LE_GET_GUINT32(locator, 0) == ZIP_ZIP64_LOCATOR_SIGNATURE)
{
Zip64 = true;
data = 0; // locator[0]
uint disk = GSF_LE_GET_GUINT32(locator, data + ZIP_ZIP64_LOCATOR_DISK);
long zip64_eod_offset = (long)GSF_LE_GET_GUINT64(locator, data + ZIP_ZIP64_LOCATOR_OFFSET);
uint disks = GSF_LE_GET_GUINT32(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 || GSF_LE_GET_GUINT32(locator, data) != ZIP_TRAILER64_SIGNATURE)
{
Err = new Exception("Broken zip file structure");
return true;
}
entries = GSF_LE_GET_GUINT64(locator, data + ZIP_TRAILER64_ENTRIES);
dir_pos = GSF_LE_GET_GUINT64(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(string.Empty, 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;
}
/// <returns>Returns true on error</returns>
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 (GSF_LE_GET_GUINT32(data, 0) != ZIP_HEADER_SIGNATURE)
{
err = "Error incorrect zip header";
Console.Error.WriteLine($"Header is 0x{GSF_LE_GET_GUINT32(data, 0):x}");
Console.Error.WriteLine($"Expected 0x{ZIP_HEADER_SIGNATURE:x}");
}
if (err != null)
{
errmsg = new Exception(err);
return true;
}
uint name_len = GSF_LE_GET_GUINT16(data, ZIP_HEADER_NAME_SIZE);
uint extras_len = GSF_LE_GET_GUINT16(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)
{
if (Stream == null)
Stream = new ZStream();
int 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

@@ -1,531 +0,0 @@
/* 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;
using static LibGSF.GsfUtils;
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 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 (SeekImpl(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 = GSF_LE_GET_GUINT16(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="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 = GSF_LE_GET_GUINT16(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;
protected virtual bool SeekImpl(long offset, SeekOrigin whence) => false;
#endregion
}
}

View File

@@ -1,140 +0,0 @@
/* 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

@@ -1,444 +0,0 @@
/* 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;
using static LibGSF.GsfUtils;
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/>
protected override bool SeekImpl(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 = GSF_LE_GET_GUINT32(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 = GSF_LE_GET_GUINT32(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 = GSF_LE_GET_GUINT16(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

@@ -1,240 +0,0 @@
/* 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;
using LibGSF.Output;
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/>
protected override bool SeekImpl(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

@@ -1,187 +0,0 @@
/* 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;
using LibGSF.Output;
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/>
protected override bool SeekImpl(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

@@ -1,43 +0,0 @@
/* 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

@@ -1,149 +0,0 @@
/* 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, bufferPtr, num_bytes);
return optional_buffer;
}
else
{
return new ReadOnlySpan<byte>(src, (int)CurrentOffset, num_bytes).ToArray();
}
}
/// <inheritdoc/>
protected override bool SeekImpl(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

@@ -1,111 +0,0 @@
/* 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/>
protected override bool SeekImpl(long offset, SeekOrigin whence) => false;
#endregion
}
}

View File

@@ -1,227 +0,0 @@
/* 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;
using LibGSF.Output;
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, 0, 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/>
protected override bool SeekImpl(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

@@ -1,212 +0,0 @@
/* 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/>
protected override bool SeekImpl(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

@@ -1,213 +0,0 @@
/* 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/>
protected override bool SeekImpl(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

@@ -1,35 +0,0 @@
/* 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

@@ -1,878 +0,0 @@
/* 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;
using static LibGSF.GsfUtils;
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, MSOleHeader.OLE_HEADER_SIZE).ToArray();
MSOleHeader header = MSOleHeader.CreateDefault();
header.BB_SHIFT = (ushort)ole.BigBlock.Shift;
header.SB_SHIFT = (ushort)ole.SmallBlock.Shift;
// 4k sector OLE files seen in the wild have version 4
if (ole.BigBlock.Size == 4096)
header.MAJOR_VER = 4;
header.Write(buf, 0);
sink.Write(MSOleHeader.OLE_HEADER_SIZE, buf);
// Header must be padded out to BigBlock.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 - MSOleHeader.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);
}
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[MSOleHeader.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;
// Write dirents
int dirent_start = CurrentBlock();
for (int i = 0; i < elem.Count; i++)
{
GsfOutfileMSOle child = elem[i];
buf = Enumerable.Repeat<byte>(0, MSOleDirectoryEntry.DIRENT_SIZE).ToArray();
MSOleDirectoryEntry directoryEntry = MSOleDirectoryEntry.CreateDefault();
// 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);
ushort name_len = (ushort)nameUtf16Bytes.Length;
if (name_len >= MSOleDirectoryEntry.DIRENT_MAX_NAME_SIZE)
name_len = MSOleDirectoryEntry.DIRENT_MAX_NAME_SIZE - 1;
directoryEntry.NAME = nameUtf16Bytes;
directoryEntry.NAME_LEN = (ushort)(name_len + 1);
}
if (child.Root == child)
{
directoryEntry.TYPE_FLAG = DIRENT_TYPE.DIRENT_TYPE_ROOTDIR;
directoryEntry.FIRSTBLOCK = (sb_data_size > 0) ? (uint)sb_data_start : BAT_MAGIC_END_OF_CHAIN;
directoryEntry.FILE_SIZE = (uint)sb_data_size;
directoryEntry.CLSID = child.ClassID;
}
else if (child.Type == MSOleOutfileType.MSOLE_DIR)
{
directoryEntry.TYPE_FLAG = DIRENT_TYPE.DIRENT_TYPE_DIR;
directoryEntry.FIRSTBLOCK = BAT_MAGIC_END_OF_CHAIN;
directoryEntry.FILE_SIZE = 0;
directoryEntry.CLSID = child.ClassID;
}
else
{
uint size = (uint)child.Parent.CurrentSize;
if (size != child.Parent.CurrentSize)
Console.Error.WriteLine("File too big");
directoryEntry.TYPE_FLAG = DIRENT_TYPE.DIRENT_TYPE_FILE;
directoryEntry.FIRSTBLOCK = (uint)child.FirstBlock;
directoryEntry.FILE_SIZE = size;
directoryEntry.CLSID = new byte[0x10];
}
directoryEntry.MODIFY_TIME = (ulong)(child.ModTime?.ToFileTime() ?? 0);
// Make everything black (red == 0)
directoryEntry.COLOR = 1;
GsfOutfileMSOle tmp = child.Container as GsfOutfileMSOle;
next = MSOleDirectoryEntry.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
directoryEntry.PREV = MSOleDirectoryEntry.DIRENT_MAGIC_END;
directoryEntry.NEXT = next;
uint child_index = MSOleDirectoryEntry.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;
}
directoryEntry.CHILD = child_index;
directoryEntry.Write(buf, 0);
Sink.Write(MSOleDirectoryEntry.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) - MSOleHeader.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;
}
// Fix up the header
if (BigBlock.Size == 4096)
{
// Set _cSectDir for 4k sector files
GSF_LE_SET_GUINT32(buf, 0, (uint)num_dirent_blocks);
Sink.Seek(OLE_HEADER_CSECTDIR, SeekOrigin.Begin);
Sink.Write(4, buf);
}
GSF_LE_SET_GUINT32(buf, 0, (uint)num_bat);
GSF_LE_SET_GUINT32(buf, 4, (uint)dirent_start);
Sink.Seek(OLE_HEADER_NUM_BAT, SeekOrigin.Begin);
Sink.Write(8, buf);
GSF_LE_SET_GUINT32(buf, 0x0, (num_sbat > 0) ? (uint)sbat_start : BAT_MAGIC_END_OF_CHAIN);
GSF_LE_SET_GUINT32(buf, 0x4, (uint)num_sbat);
GSF_LE_SET_GUINT32(buf, 0x8, xbat_pos);
GSF_LE_SET_GUINT32(buf, 0xc, (uint)num_xbat);
Sink.Seek(OLE_HEADER_SBAT_START, SeekOrigin.Begin);
Sink.Write(0x10, buf);
// Write initial Meta-BAT
for (int i = 0; i < blocks; i++)
{
GSF_LE_SET_GUINT32(buf, 0, (uint)(bat_start + i));
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++)
{
GSF_LE_SET_GUINT32(buf, 0, (uint)(bat_start + j));
Sink.Write(BAT_INDEX_SIZE, buf);
}
if (i == num_xbat)
{
PadBatUnused(1);
xbat_pos = BAT_MAGIC_END_OF_CHAIN;
}
else
{
xbat_pos++;
}
GSF_LE_SET_GUINT32(buf, 0, xbat_pos);
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

@@ -1,97 +0,0 @@
/* 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

@@ -1,895 +0,0 @@
/* 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.GsfUtils;
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)
{
byte[] tmp = new byte[8];
// 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.
GSF_LE_SET_GUINT16(tmp, 0, (ushort)ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_ZIP64);
GSF_LE_SET_GUINT16(tmp, 2, (ushort)(2 + (offset_in_zip64 ? 1 : 0) * 8));
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, (ulong)dirent.UncompressedSize);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, (ulong)dirent.CompressedSize);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
if (offset_in_zip64)
{
GSF_LE_SET_GUINT64(tmp, 0, (ulong)dirent.Offset);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
}
}
else if (dirent.Zip64 == null)
{
byte[] tmp = new byte[8];
// Match the local header.
GSF_LE_SET_GUINT16(tmp, 0, (ushort)ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_IGNORE);
GSF_LE_SET_GUINT16(tmp, 2, 2 * 8);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, 0);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, 0);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
}
if (ZIP_ADD_UNIXTIME_FIELD && dirent.ModifiedTime != null && !SpecialMimetypeDirectoryEntry(dirent))
{
// Clearly a year 2038 problem here.
byte[] tmp = new byte[4];
GSF_LE_SET_GUINT16(tmp, 0, (ushort)(ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_UNIXTIME));
GSF_LE_SET_GUINT16(tmp, 2, 5);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
tmp[0] = 1;
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 1).ToArray());
GSF_LE_SET_GUINT32(tmp, 0, (uint)new DateTimeOffset(dirent.ModifiedTime ?? DateTime.UtcNow).ToUnixTimeSeconds());
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
}
byte[] buf = new byte[ZIP_DIRENT_SIZE];
GSF_LE_SET_GUINT32(buf, 0, ZIP_DIRENT_SIGNATURE);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_ENCODER, (ushort)(((int)OSCodes.ZIP_OS_UNIX << 8) + extract));
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_EXTRACT, (ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract));
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_FLAGS, (ushort)dirent.Flags);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_COMPR_METHOD, (ushort)dirent.CompressionMethod);
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_DOSTIME, dirent.DosTime);
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_CRC32, dirent.CRC32);
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_CSIZE, (uint)(zip64_here ? uint.MaxValue : dirent.CompressedSize));
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_USIZE, (uint)(zip64_here ? uint.MaxValue : dirent.UncompressedSize));
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_NAME_SIZE, (ushort)nlen);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_EXTRAS_SIZE, (ushort)extras.Count);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_COMMENT_SIZE, 0);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_DISKSTART, 0);
GSF_LE_SET_GUINT16(buf, ZIP_DIRENT_FILE_TYPE, 0);
// Hardcode file mode 644
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_FILE_MODE, 0100644u << 16);
GSF_LE_SET_GUINT32(buf, ZIP_DIRENT_OFFSET, (uint)(offset_in_zip64 ? uint.MaxValue : dirent.Offset));
// Stuff everything into buf so we can do just one write.
List<byte> header = new List<byte>();
header.AddRange(buf);
header.AddRange(extras);
header.AddRange(Encoding.ASCII.GetBytes(dirent.Name));
return Sink.Write(header.Count, header.ToArray());
}
private bool TrailerWrite(int entries, long dirpos, long dirsize)
{
byte[] buf = new byte[ZIP_TRAILER_SIZE];
GSF_LE_SET_GUINT32(buf, 0, ZIP_TRAILER_SIGNATURE);
GSF_LE_SET_GUINT16(buf, ZIP_TRAILER_ENTRIES, (ushort)Math.Min(entries, ushort.MaxValue));
GSF_LE_SET_GUINT16(buf, ZIP_TRAILER_TOTAL_ENTRIES, (ushort)Math.Min(entries, ushort.MaxValue));
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER_DIR_SIZE, (uint)Math.Min(dirsize, uint.MaxValue));
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER_DIR_POS, (uint)Math.Min(dirpos, uint.MaxValue));
return Sink.Write(buf.Length, buf);
}
private bool Trailer64Write(int entries, long dirpos, long dirsize)
{
byte[] buf = new byte[ZIP_TRAILER64_SIZE];
byte extract = 45;
GSF_LE_SET_GUINT32(buf, 0, ZIP_TRAILER64_SIGNATURE);
GSF_LE_SET_GUINT64(buf, ZIP_TRAILER64_RECSIZE, ZIP_TRAILER64_SIZE - 12);
GSF_LE_SET_GUINT16(buf, ZIP_TRAILER64_ENCODER, (ushort)(((int)OSCodes.ZIP_OS_UNIX << 8) + extract));
GSF_LE_SET_GUINT16(buf, ZIP_TRAILER64_EXTRACT, (ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract));
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER64_DISK, 0);
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER64_DIR_DISK, 0);
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER64_ENTRIES, (uint)entries);
GSF_LE_SET_GUINT32(buf, ZIP_TRAILER64_TOTAL_ENTRIES, (uint)entries);
GSF_LE_SET_GUINT64(buf, ZIP_TRAILER64_DIR_SIZE, (ulong)dirsize);
GSF_LE_SET_GUINT64(buf, ZIP_TRAILER64_DIR_POS, (ulong)dirpos);
return Sink.Write(buf.Length, buf);
}
private bool Zip64LocatorWrite(long trailerpos)
{
byte[] buf = new byte[ZIP_ZIP64_LOCATOR_SIZE];
GSF_LE_SET_GUINT32(buf, 0, ZIP_ZIP64_LOCATOR_SIGNATURE);
GSF_LE_SET_GUINT32(buf, ZIP_ZIP64_LOCATOR_DISK, 0);
GSF_LE_SET_GUINT64(buf, ZIP_TRAILER64_DIR_POS, (ulong)trailerpos);
GSF_LE_SET_GUINT32(buf, ZIP_ZIP64_LOCATOR_DISKS, 1);
return Sink.Write(buf.Length, buf);
}
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()
{
byte[] hbuf = new byte[ZIP_HEADER_SIZE];
GsfZipDirectoryEntry dirent = VDir.DirectoryEntry;
string name = dirent.Name;
int nlen = name.Length;
GSF_LE_SET_GUINT32(hbuf, 0, 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))
return false;
SinkIsSeekable = Sink.Seek(dirent.Offset, SeekOrigin.Begin);
if (SinkIsSeekable == false)
hbuf = new byte[ZIP_HEADER_SIZE];
}
// 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)
{
byte[] tmp = new byte[8];
ExtraFieldTags typ = real_zip64
? ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_ZIP64
: ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_IGNORE;
GSF_LE_SET_GUINT16(tmp, 0, (ushort)typ);
GSF_LE_SET_GUINT16(tmp, 2, 2 * 8);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, (ulong)usize);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
GSF_LE_SET_GUINT64(tmp, 0, (ulong)csize);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 8).ToArray());
}
if (ZIP_ADD_UNIXTIME_FIELD && dirent.ModifiedTime != null && !SpecialMimetypeDirectoryEntry(dirent))
{
byte[] tmp = new byte[4];
// Clearly a year 2038 problem here.
GSF_LE_SET_GUINT16(tmp, 0, (ushort)ExtraFieldTags.ZIP_DIRENT_EXTRA_FIELD_UNIXTIME);
GSF_LE_SET_GUINT16(tmp, 2, 5);
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
tmp[0] = 1;
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 1).ToArray());
GSF_LE_SET_GUINT32(tmp, 0, (uint)new DateTimeOffset(dirent.ModifiedTime ?? DateTime.UtcNow).ToUnixTimeSeconds());
extras.AddRange(new ReadOnlySpan<byte>(tmp, 0, 4).ToArray());
}
GSF_LE_SET_GUINT16(hbuf, ZIP_HEADER_EXTRACT, (ushort)(((int)OSCodes.ZIP_OS_MSDOS << 8) + extract));
GSF_LE_SET_GUINT16(hbuf, ZIP_HEADER_FLAGS, (ushort)dirent.Flags);
GSF_LE_SET_GUINT16(hbuf, ZIP_HEADER_COMP_METHOD, (ushort)dirent.CompressionMethod);
GSF_LE_SET_GUINT32(hbuf, ZIP_HEADER_DOSTIME, dirent.DosTime);
GSF_LE_SET_GUINT32(hbuf, ZIP_HEADER_CRC32, crc32);
GSF_LE_SET_GUINT32(hbuf, ZIP_HEADER_CSIZE, (uint)(real_zip64 && !has_ddesc ? uint.MaxValue : csize));
GSF_LE_SET_GUINT32(hbuf, ZIP_HEADER_USIZE, (uint)(real_zip64 && !has_ddesc ? uint.MaxValue : usize));
GSF_LE_SET_GUINT16(hbuf, ZIP_HEADER_NAME_SIZE, (ushort)nlen);
GSF_LE_SET_GUINT16(hbuf, ZIP_HEADER_EXTRAS_SIZE, (ushort)extras.Count);
// Stuff everything into buf so we can do just one write.
List<byte> header = new List<byte>();
header.AddRange(hbuf);
header.AddRange(extras);
header.AddRange(Encoding.ASCII.GetBytes(name));
bool ret = Sink.Write(header.Count, header.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()
{
byte[] buf = new 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)
{
GSF_LE_SET_GUINT32(buf, 0, ZIP_DDESC64_SIGNATURE);
GSF_LE_SET_GUINT32(buf, ZIP_DDESC64_CRC32, dirent.CRC32);
GSF_LE_SET_GUINT64(buf, ZIP_DDESC64_CSIZE, (ulong)dirent.CompressedSize);
GSF_LE_SET_GUINT64(buf, ZIP_DDESC64_USIZE, (ulong)dirent.UncompressedSize);
size = ZIP_DDESC64_SIZE;
}
else
{
GSF_LE_SET_GUINT32(buf, 0, ZIP_DDESC_SIGNATURE);
GSF_LE_SET_GUINT32(buf, ZIP_DDESC_CRC32, dirent.CRC32);
GSF_LE_SET_GUINT32(buf, ZIP_DDESC_CSIZE, (uint)dirent.CompressedSize);
GSF_LE_SET_GUINT32(buf, ZIP_DDESC_USIZE, (uint)dirent.UncompressedSize);
size = ZIP_DDESC_SIZE;
}
if (!Sink.Write(size, buf))
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

@@ -1,286 +0,0 @@
/* 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

@@ -1,212 +0,0 @@
/* 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

@@ -1,181 +0,0 @@
/* 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

@@ -1,298 +0,0 @@
/* 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;
using static LibGSF.GsfUtils;
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)
{
byte[] buf = new byte[8];
GSF_LE_SET_GUINT32(buf, 0, CRC);
GSF_LE_SET_GUINT32(buf, 4, (uint)(ISize));
if (!Sink.Write(8, buf))
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()
{
byte[] buf = new 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;
Array.Copy(new byte[] { 0x1f, 0x8b, 0x08 }, buf, 3);
if (nlen > 0)
buf[3] = GZIP_ORIGINAL_NAME;
GSF_LE_SET_GUINT32(buf, 4, (uint)mtime);
buf[9] = 3; // UNIX
bool ret = Sink.Write(buf.Length, buf);
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

@@ -1,144 +0,0 @@
/* 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

@@ -1,93 +0,0 @@
/* 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

@@ -1,171 +0,0 @@
/* 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

@@ -1,157 +0,0 @@
/* 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

@@ -1,260 +0,0 @@
/* 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
}
}

View File

@@ -1,10 +0,0 @@
# 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.

View File

@@ -1,94 +0,0 @@
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using System.IO;
using LibGSF.Input;
namespace LibMSI.Internal
{
internal class LibmsiIStream : Stream
{
#region Properties
public GsfInput Input { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiIStream() { }
public static LibmsiIStream Create(GsfInput input)
{
Exception err = null;
GsfInput dup = input.Duplicate(ref err);
if (dup == null)
return null;
return new LibmsiIStream
{
Input = dup,
};
}
#endregion
#region Functions
public override long Seek(long offset, SeekOrigin type) => (Input?.Seek(offset, type) ?? false) ? offset : -1;
public override int Read(byte[] buffer, int offset, int count)
{
int remaining = (int)(Input?.Remaining() ?? 0);
if (remaining == 0)
return 0;
count = Math.Min(count, remaining);
if (Input.Read(count, buffer, offset) == null)
return -1;
return count;
}
public override void Close() { }
#endregion
#region Stream Implementation
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length => Input.Size;
public override long Position { get => Input.CurrentOffset; set => Input.CurrentOffset = value; }
public override void Flush() { }
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,100 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2005 Mike McCormack for CodeWeavers
* Copyright 2005 Aric Stewart for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace LibMSI.Internal
{
#region Enums
internal enum LibmsiCondition
{
LIBMSI_CONDITION_FALSE = 0,
LIBMSI_CONDITION_TRUE = 1,
LIBMSI_CONDITION_NONE = 2,
LIBMSI_CONDITION_ERROR = 3,
}
internal enum LibmsiOLEVariantType
{
OLEVT_EMPTY = 0,
OLEVT_NULL = 1,
OLEVT_I2 = 2,
OLEVT_I4 = 3,
OLEVT_LPSTR = 30,
OLEVT_FILETIME = 64,
}
internal enum StringPersistence
{
StringPersistent = 0,
StringNonPersistent = 1
}
#endregion
#region Delegates
internal delegate LibmsiResult record_func(LibmsiRecord record, object o);
#endregion
#region Classes
internal static class MsiPriv
{
#region Constants
public const ushort MSI_DATASIZEMASK = 0x00ff;
public const ushort MSITYPE_VALID = 0x0100;
public const ushort MSITYPE_LOCALIZABLE = 0x200;
public const ushort MSITYPE_STRING = 0x0800;
public const ushort MSITYPE_NULLABLE = 0x1000;
public const ushort MSITYPE_KEY = 0x2000;
public const ushort MSITYPE_TEMPORARY = 0x4000;
public const ushort MSITYPE_UNKNOWN = 0x8000;
public const int MAX_STREAM_NAME_LEN = 62;
public const int LONG_STR_BYTES = 3;
public const int MSI_INITIAL_MEDIA_TRANSFORM_OFFSET = 10000;
public const int MSI_INITIAL_MEDIA_TRANSFORM_DISKID = 30000;
public const int MSI_MAX_PROPS = 20;
/* common strings */
public static readonly string szEmpty = "";
public static readonly string szStreams = "_Streams";
public static readonly string szStorages = "_Storages";
public static readonly string szStringData = "_StringData";
public static readonly string szStringPool = "_StringPool";
public static readonly string szName = "Name";
public static readonly string szData = "Data";
#endregion
#region Functions
public static bool MSITYPE_IS_BINARY(int type) => ((type & ~MSITYPE_NULLABLE) == (MSITYPE_STRING | MSITYPE_VALID));
#endregion
}
#endregion
}

View File

@@ -1,684 +0,0 @@
/*
* String Table Functions
*
* Copyright 2002-2004, Mike McCormack for CodeWeavers
* Copyright 2007 Robert Shearman for CodeWeavers
* Copyright 2010 Hans Leidekker for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using System.Text;
using LibGSF.Input;
using static LibMSI.Internal.LibmsiTable;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Internal
{
internal class msistring
{
public ushort PersistentRefCount { get; set; }
public ushort NonPersistentRefCount { get; set; }
public string Str { get; set; }
}
internal class StringTable
{
#region Constants
private const int CP_ACP = 0;
#endregion
#region Properties
/// <summary>
/// The number of strings
/// </summary>
public int MaxCount { get; set; }
public int FreeSlot { get; set; }
public int CodePage { get; set; }
public int SortCount { get; set; }
/// <summary>
/// An array of strings
/// </summary>
public msistring[] Strings { get; set; }
/// <summary>
/// Index
/// </summary>
public int[] Sorted { get; set; }
#endregion
#region Functions
public void Destroy()
{
Strings = null;
Sorted = null;
}
public int AddString(string data, int len, ushort refcount, StringPersistence persistence)
{
if (string.IsNullOrEmpty(data) || data[0] == '\0')
return 0;
if (IdFromStringUTF8(data, out int n) == LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
if (persistence == StringPersistence.StringPersistent)
Strings[n].PersistentRefCount += refcount;
else
Strings[n].NonPersistentRefCount += refcount;
return n;
}
n = FindFreeEntry();
if (n == -1)
return -1;
// Allocate a new string
if (len < 0)
len = data.Length;
SetEntry(n, data + '\0', refcount, persistence);
return n;
}
/// <summary>
/// Find the string identified by an id - return null if there's none
/// </summary>
public string LookupId(int id)
{
if (id == 0)
return szEmpty;
if (id >= MaxCount)
return null;
if (id != 0 && Strings[id].PersistentRefCount == 0 && Strings[id].NonPersistentRefCount == 0)
return null;
return Strings[id].Str;
}
/// <param name="str">String to find in the string table</param>
/// <param name="id">Id of the string, if found</param>
public LibmsiResult IdFromStringUTF8(string str, out int id)
{
int low = 0, high = SortCount - 1;
while (low <= high)
{
int i = (low + high) / 2;
int c = str.CompareTo(Strings[Sorted[i]].Str);
if (c < 0)
{
high = i - 1;
}
else if (c > 0)
{
low = i + 1;
}
else
{
id = Sorted[i];
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
}
id = 0;
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
}
public static StringTable InitStringTable(out int bytes_per_strref)
{
bytes_per_strref = sizeof(ushort);
return InitStringTable(1, CP_ACP);
}
public static StringTable LoadStringTable(GsfInfile stg, out int bytes_per_strref)
{
int len;
ushort refs;
bytes_per_strref = 0;
LibmsiResult r = ReadStreamData(stg, szStringPool, out byte[] poolBytes, out int poolsize);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return null;
r = ReadStreamData(stg, szStringData, out byte[] data, out int datasize);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return null;
if ((poolsize > 4) && (BitConverter.ToUInt16(poolBytes, 2) & 0x8000) != 0)
bytes_per_strref = LONG_STR_BYTES;
else
bytes_per_strref = sizeof(ushort);
int count = poolsize / 4;
int codepage;
if (poolsize > 4)
codepage = BitConverter.ToUInt16(poolBytes, 0) | ((BitConverter.ToUInt16(poolBytes, 2) & ~0x8000) << 16);
else
codepage = CP_ACP;
StringTable st = InitStringTable(count, codepage);
if (st == null)
return null;
int offset = 0;
int n = 1;
int i = 1;
while (i < count)
{
// The string reference count is always the second word
refs = BitConverter.ToUInt16(poolBytes, ((i * 2 + 1) * 2));
// Empty entries have two zeros, still have a string id
if (BitConverter.ToUInt16(poolBytes, ((i * 2) * 2)) == 0 && refs == 0)
{
i++;
n++;
continue;
}
// If a string is over 64k, the previous string entry is made null
// and the high word of the length is inserted in the null string's
// reference count field.
if (BitConverter.ToUInt16(poolBytes, ((i * 2) * 2)) == 0)
{
len = (BitConverter.ToUInt16(poolBytes, ((i * 2 + 3 * 2))) << 16) + BitConverter.ToUInt16(poolBytes, ((i * 2 + 2) * 2));
i += 2;
}
else
{
len = BitConverter.ToUInt16(poolBytes, ((i * 2) * 2));
i += 1;
}
if ((offset + len) > datasize)
{
Console.Error.WriteLine("String table corrupt?");
break;
}
int s = st.AddString(n, data, offset, len, refs, StringPersistence.StringPersistent);
if (s != n)
Console.Error.WriteLine($"Failed to add string {n}");
n++;
offset += len;
}
if (datasize != offset)
Console.Error.WriteLine($"String table load failed! ({datasize} != {offset}), please report");
return st;
}
public LibmsiResult SaveStringTable(LibmsiDatabase db, out int bytes_per_strref)
{
// Construct the new table in memory first
StringTotalSize(out int datasize, out int poolsize);
byte[] data = new byte[datasize];
byte[] pool = new byte[poolsize];
int used = 0;
int codepage = CodePage;
pool[0] = (byte)(codepage & 0xff);
pool[1] = (byte)(codepage >> 8);
pool[2] = (byte)(codepage >> 16);
pool[3] = (byte)(codepage >> 24);
if (MaxCount > 0xffff)
{
pool[3] |= 0x80;
bytes_per_strref = LONG_STR_BYTES;
}
else
{
bytes_per_strref = sizeof(ushort);
}
int i = 1;
for (int n = 1; n < MaxCount; n++)
{
if (Strings[n].PersistentRefCount == 0)
{
pool[i * 4 + 0] = 0;
pool[i * 4 + 1] = 0;
pool[i * 4 + 2] = 0;
pool[i * 4 + 3] = 0;
i++;
continue;
}
int sz = datasize - used;
LibmsiResult s = StringId(n, ref data, ref used, ref sz);
if (s != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine("Failed to fetch string");
sz = 0;
}
if (sz == 0)
{
pool[i * 4 + 0] = 0;
pool[i * 4 + 1] = 0;
pool[i * 4 + 2] = 0;
pool[i * 4 + 3] = 0;
i++;
continue;
}
if (sz >= 0x10000)
{
// Write a dummy entry, with the high part of the length
// in the reference count.
pool[i * 4 + 0] = 0;
pool[i * 4 + 1] = 0;
pool[i * 4 + 2] = (byte)(sz >> 16);
pool[i * 4 + 3] = (byte)(sz >> 24);
i++;
}
pool[i * 4 + 0] = (byte)sz;
pool[i * 4 + 1] = (byte)(sz >> 8);
pool[i * 4 + 2] = (byte)Strings[n].PersistentRefCount;
pool[i * 4 + 3] = (byte)(Strings[n].PersistentRefCount >> 8);
i++;
used += sz;
if (used > datasize)
{
Console.Error.WriteLine($"Oops overran {used} >= {datasize}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
}
if (used != datasize)
{
Console.Error.WriteLine($"Oops used {used} != datasize {datasize}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
// Write the streams
LibmsiResult r = WriteStreamData(db, szStringData, data, datasize);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
r = WriteStreamData(db, szStringPool, pool, poolsize);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
public int GetCodePage() => CodePage;
public LibmsiResult SetCodePage(int codepage)
{
if (ValidateCodePage(codepage))
{
CodePage = codepage;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
#endregion
#region Utilities
private static bool ValidateCodePage(int codepage)
{
switch (codepage)
{
case CP_ACP:
case 37:
case 424:
case 437:
case 500:
case 737:
case 775:
case 850:
case 852:
case 855:
case 856:
case 857:
case 860:
case 861:
case 862:
case 863:
case 864:
case 865:
case 866:
case 869:
case 874:
case 875:
case 878:
case 932:
case 936:
case 949:
case 950:
case 1006:
case 1026:
case 1250:
case 1251:
case 1252:
case 1253:
case 1254:
case 1255:
case 1256:
case 1257:
case 1258:
case 1361:
case 10000:
case 10006:
case 10007:
case 10029:
case 10079:
case 10081:
case 20127:
case 20866:
case 20932:
case 21866:
case 28591:
case 28592:
case 28593:
case 28594:
case 28595:
case 28596:
case 28597:
case 28598:
case 28599:
case 28600:
case 28603:
case 28604:
case 28605:
case 28606:
case 65000:
case 65001:
return true;
default:
return false;
}
}
private static StringTable InitStringTable(int entries, int codepage)
{
if (!ValidateCodePage(codepage))
return null;
if (entries < 1)
entries = 1;
return new StringTable
{
Strings = new msistring[entries],
Sorted = new int[entries],
MaxCount = entries,
FreeSlot = 1,
CodePage = codepage,
SortCount = 0,
};
}
private int FindFreeEntry()
{
if (FreeSlot != 0)
{
for (int i = FreeSlot; i < MaxCount; i++)
{
if (Strings[i].PersistentRefCount == 0 && Strings[i].NonPersistentRefCount == 0)
return i;
}
}
for (int i = 1; i < MaxCount; i++)
{
if (Strings[i].PersistentRefCount == 0 && Strings[i].NonPersistentRefCount == 0)
return i;
}
// Dynamically resize
int sz = MaxCount + 1 + MaxCount / 2;
msistring[] p = Strings;
Array.Resize(ref p, sz);
Strings = p;
int[] s = Sorted;
Array.Resize(ref s, sz);
Sorted = s;
FreeSlot = MaxCount;
MaxCount = sz;
if (Strings[FreeSlot].PersistentRefCount != 0 || Strings[FreeSlot].NonPersistentRefCount != 0)
Console.Error.WriteLine("Oops. expected freeslot to be free...");
return FreeSlot;
}
private int FindInsertIndex(int string_id)
{
int low = 0, high = SortCount - 1;
while (low <= high)
{
int i = (low + high) / 2;
int c = Strings[string_id].Str.CompareTo(Strings[Sorted[i]].Str);
if (c < 0)
high = i - 1;
else if (c > 0)
low = i + 1;
else
return -1; // Already exists
}
return high + 1;
}
private void InsertStringSorted(int string_id)
{
int i = FindInsertIndex(string_id);
if (i == -1)
return;
int[] temp = new int[SortCount - i];
Array.Copy(Sorted, i, temp, 0, SortCount - i);
Array.Copy(temp, 0, Sorted, i + 1, SortCount - i);
Sorted[i] = string_id;
SortCount++;
}
private void SetEntry(int n, string str, ushort refcount, StringPersistence persistence)
{
if (str == null)
return;
if (Strings[n] == null)
Strings[n] = new msistring();
if (persistence == StringPersistence.StringPersistent)
{
Strings[n].PersistentRefCount = refcount;
Strings[n].NonPersistentRefCount = 0;
}
else
{
Strings[n].PersistentRefCount = 0;
Strings[n].NonPersistentRefCount = refcount;
}
Strings[n].Str = str;
InsertStringSorted(n);
if (n < MaxCount)
FreeSlot = n + 1;
}
private LibmsiResult IdFromString(byte[] buffer, int offset, out int id)
{
id = 0;
if (buffer == null || buffer[offset] == 0)
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
int codepage = CodePage != 0 ? CodePage : Encoding.Default.CodePage;
Encoding cpconv = Encoding.GetEncoding(codepage);
string str = cpconv.GetString(new ReadOnlySpan<byte>(buffer, offset, buffer.Length - offset).ToArray());
if (str == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
LibmsiResult r = IdFromStringUTF8(str, out id);
return r;
}
private int AddString(int n, byte[] data, int offset, int len, ushort refcount, StringPersistence persistence)
{
if (data == null || data[offset] == '\0')
return 0;
if (n > 0)
{
if (Strings[n] != null && (Strings[n].PersistentRefCount != 0 || Strings[n].NonPersistentRefCount != 0))
return -1;
}
else
{
if (IdFromString(data, offset, out n) == LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
if (Strings[n] == null)
Strings[n] = new msistring();
if (persistence == StringPersistence.StringPersistent)
Strings[n].PersistentRefCount += refcount;
else
Strings[n].NonPersistentRefCount += refcount;
return n;
}
n = FindFreeEntry();
if (n == -1)
return -1;
}
if (n < 1)
{
Console.Error.WriteLine($"Invalid index adding {data} ({n})");
return -1;
}
// Allocate a new string
int codepage = CodePage != 0 ? CodePage : Encoding.Default.CodePage;
Encoding cpconv = Encoding.GetEncoding(codepage);
string str = cpconv.GetString(data);
SetEntry(n, str, refcount, persistence);
return n;
}
/// <param name="st">Pointer to the string table</param>
/// <param name="id">Id of the string to retrieve</param>
/// <param name="buffer">Destination of the UTF8 string</param>
/// <param name="sz">
/// Number of bytes available in the buffer on input
/// Number of bytes used on output
/// </param>
/// <remarks>Returned string is not NUL-terminated.</remarks>
private LibmsiResult StringId(int id, ref byte[] buffer, ref int offset, ref int sz)
{
string str_utf8 = LookupId(id);
if (str_utf8 == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
int codepage = CodePage != 0 ? CodePage : Encoding.Default.CodePage;
Encoding cpconv = Encoding.GetEncoding(codepage);
byte[] str = Encoding.Convert(Encoding.UTF8, cpconv, Encoding.UTF8.GetBytes(str_utf8));
int len = str.Length;
if (sz < len)
{
sz = len;
return LibmsiResult.LIBMSI_RESULT_MORE_DATA;
}
sz = len;
Array.Copy(str, 0, buffer, offset, str.Length);
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private void StringTotalSize(out int datasize, out int poolsize)
{
if (Strings[0].Str != null || Strings[0].PersistentRefCount != 0 || Strings[0].NonPersistentRefCount != 0)
Console.Error.WriteLine("Oops. element 0 has a string");
int codepage = CodePage != 0 ? CodePage : Encoding.Default.CodePage;
poolsize = 4;
datasize = 0;
int holesize = 0;
for (int i = 1; i < MaxCount; i++)
{
if (Strings[i].PersistentRefCount == 0)
{
poolsize += 4;
}
else if (Strings[i].Str != null)
{
Encoding cpconv = Encoding.GetEncoding(codepage);
string str = cpconv.GetString(Encoding.UTF8.GetBytes(Strings[i].Str));
datasize += str.Length;
if (str.Length > 0xffff)
poolsize += 4;
poolsize += holesize + 4;
holesize = 0;
}
else
{
holesize += 4;
}
}
}
#endregion
}
}

View File

@@ -1,320 +0,0 @@
/*
** 2001 September 15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** A tokenizer for SQL
**
** This file contains C code that splits an SQL input string up into
** individual tokens and sends those tokens one-by-one over to the
** parser for analysis.
*/
using System;
using System.Linq;
namespace LibMSI.Internal
{
/// <summary>
/// The token value for this keyword
/// </summary>
internal class Keyword
{
/// <summary>
/// All the keywords of the SQL language are stored as in a hash
/// table composed of instances of the following structure.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The token value for this keyword
/// </summary>
public sql_tokentype TokenType { get; set; }
}
internal class Tokenize
{
#region Constants
public const int MAX_TOKEN_LEN = 11;
/// <summary>
/// These are the keywords
/// They MUST be in alphabetical order
/// </summary>
public static readonly Keyword[] KeywordTable = new Keyword[]
{
new Keyword { Name = "ADD", TokenType = sql_tokentype.TK_ADD },
new Keyword { Name = "ALTER", TokenType = sql_tokentype.TK_ALTER },
new Keyword { Name = "AND", TokenType = sql_tokentype.TK_AND },
new Keyword { Name = "BY", TokenType = sql_tokentype.TK_BY },
new Keyword { Name = "CHAR", TokenType = sql_tokentype.TK_CHAR },
new Keyword { Name = "CHARACTER", TokenType = sql_tokentype.TK_CHAR },
new Keyword { Name = "CREATE", TokenType = sql_tokentype.TK_CREATE },
new Keyword { Name = "DELETE", TokenType = sql_tokentype.TK_DELETE },
new Keyword { Name = "DISTINCT", TokenType = sql_tokentype.TK_DISTINCT },
new Keyword { Name = "DROP", TokenType = sql_tokentype.TK_DROP },
new Keyword { Name = "FREE", TokenType = sql_tokentype.TK_FREE },
new Keyword { Name = "FROM", TokenType = sql_tokentype.TK_FROM },
new Keyword { Name = "HOLD", TokenType = sql_tokentype.TK_HOLD },
new Keyword { Name = "INSERT", TokenType = sql_tokentype.TK_INSERT },
new Keyword { Name = "INT", TokenType = sql_tokentype.TK_INT },
new Keyword { Name = "INTEGER", TokenType = sql_tokentype.TK_INT },
new Keyword { Name = "INTO", TokenType = sql_tokentype.TK_INTO },
new Keyword { Name = "IS", TokenType = sql_tokentype.TK_IS },
new Keyword { Name = "KEY", TokenType = sql_tokentype.TK_KEY },
new Keyword { Name = "LIKE", TokenType = sql_tokentype.TK_LIKE },
new Keyword { Name = "LOCALIZABLE", TokenType = sql_tokentype.TK_LOCALIZABLE },
new Keyword { Name = "LONG", TokenType = sql_tokentype.TK_LONG },
new Keyword { Name = "LONGCHAR", TokenType = sql_tokentype.TK_LONGCHAR },
new Keyword { Name = "NOT", TokenType = sql_tokentype.TK_NOT },
new Keyword { Name = "NULL", TokenType = sql_tokentype.TK_NULL },
new Keyword { Name = "OBJECT", TokenType = sql_tokentype.TK_OBJECT },
new Keyword { Name = "OR", TokenType = sql_tokentype.TK_OR },
new Keyword { Name = "ORDER", TokenType = sql_tokentype.TK_ORDER },
new Keyword { Name = "PRIMARY", TokenType = sql_tokentype.TK_PRIMARY },
new Keyword { Name = "SELECT", TokenType = sql_tokentype.TK_SELECT },
new Keyword { Name = "SET", TokenType = sql_tokentype.TK_SET },
new Keyword { Name = "SHORT", TokenType = sql_tokentype.TK_SHORT },
new Keyword { Name = "TABLE", TokenType = sql_tokentype.TK_TABLE },
new Keyword { Name = "TEMPORARY", TokenType = sql_tokentype.TK_TEMPORARY },
new Keyword { Name = "UPDATE", TokenType = sql_tokentype.TK_UPDATE },
new Keyword { Name = "VALUES", TokenType = sql_tokentype.TK_VALUES },
new Keyword { Name = "WHERE", TokenType = sql_tokentype.TK_WHERE },
};
public static readonly int KEYWORD_COUNT = KeywordTable.Length;
/// <summary>
/// If X is a character that can be used in an identifier then
/// IsIdChar[X] will be 1. Otherwise IsIdChar[X] will be 0.
///
/// In this implementation, an identifier can be a string of
/// alphabetic characters, digits, and "_" plus any character
/// with the high-order bit set. The latter rule means that
/// any sequence of UTF-8 characters or characters taken from
/// an extended ISO8859 character set can form an identifier.
/// </summary>
public static readonly byte[] IsIdChar = new byte[]
{
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 2x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Ax */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Bx */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Cx */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Dx */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Ex */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Fx */
};
#endregion
#region Functions
/// <summary>
/// Comparison function for binary search.
/// </summary>
public static int CompareKeyword(string m1, Keyword m2) => string.Compare(m1, m2.Name, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// This function looks up an identifier to determine if it is a
/// keyword. If it is a keyword, the token code of that keyword is
/// returned. If the input is not a keyword, TK_ID is returned.
/// </summary>
public static sql_tokentype FindKeyword(string z, int n)
{
if (n > MAX_TOKEN_LEN)
return sql_tokentype.TK_ID;
string str = z.Substring(0, n);
Keyword r = KeywordTable.FirstOrDefault(k => CompareKeyword(str, k) == 0);
if (r != default)
return r.TokenType;
return sql_tokentype.TK_ID;
}
/// <summary>
/// Return the length of the token that begins at <paramref name="zz"/>[0]. Return
/// -1 if the token is (or might be) incomplete. Store the token
/// type in *tokenType before returning.
/// </summary>
public static int GetToken(string zz, int ptr, out sql_tokentype tokenType, out int skip)
{
int z = ptr; // zz[0]
int i = 0;
skip = 0;
switch (zz[z])
{
// TK_SPACE
case ' ': case '\t': case '\n': case '\f':
for (i = 1; char.IsWhiteSpace(zz[z + i]) && zz[z + i] != '\r'; i++);
tokenType = sql_tokentype.TK_SPACE;
return i;
// TK_MINUS
case '-':
if (z + 1 >= zz.Length || zz[z + 1] == 0)
{
tokenType = sql_tokentype.TK_ILLEGAL;
return -1;
}
tokenType = sql_tokentype.TK_MINUS;
return 1;
// TK_LP
case '(':
tokenType = sql_tokentype.TK_LP;
return 1;
// TK_RP
case ')':
tokenType = sql_tokentype.TK_RP;
return 1;
// TK_STAR
case '*':
tokenType = sql_tokentype.TK_STAR;
return 1;
// TK_EQ
case '=':
tokenType = sql_tokentype.TK_EQ;
return 1;
// TK_LT, TK_LE, TK_NE
case '<':
if (zz[z + 1] == '=')
{
tokenType = sql_tokentype.TK_LE;
return 2;
}
else if (zz[z + 1] == '>')
{
tokenType = sql_tokentype.TK_NE;
return 2;
}
else
{
tokenType = sql_tokentype.TK_LT;
return 1;
}
// TK_GT, TK_GE
case '>':
if (zz[z + 1] == '=')
{
tokenType = sql_tokentype.TK_GE;
return 2;
}
else
{
tokenType = sql_tokentype.TK_GT;
return 1;
}
// TK_NE
case '!':
if (zz[z + 1] != '=')
{
tokenType = sql_tokentype.TK_ILLEGAL;
return 2;
}
else
{
tokenType = sql_tokentype.TK_NE;
return 2;
}
// TK_WILDCARD
case '?':
tokenType = sql_tokentype.TK_WILDCARD;
return 1;
// TK_COMMA
case ',':
tokenType = sql_tokentype.TK_COMMA;
return 1;
// TK_ID, TK_STRING
case '`': case '\'':
{
int delim = zz[z];
for (i = 1; i < zz.Length && zz[z + i] != 0; i++)
{
if (zz[z + i] == delim)
break;
}
if (zz[z + i] != 0)
i++;
if (delim == '`')
tokenType = sql_tokentype.TK_ID;
else
tokenType = sql_tokentype.TK_STRING;
return i;
}
// TK_DOT, TK_INTEGER
case '.':
if (!char.IsDigit(zz[z + 1]))
{
tokenType = sql_tokentype.TK_DOT;
return 1;
}
tokenType = sql_tokentype.TK_INTEGER;
for (i = 1; char.IsDigit(zz[z + i]); i++);
return i;
// TK_INTEGER
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
tokenType = sql_tokentype.TK_INTEGER;
for (i = 1; char.IsDigit(zz[z + i]); i++);
return i;
// TK_ID
case '[':
for(i = 1; zz[z + i] != 0 && zz[z + i - 1] != ']'; i++);
tokenType = sql_tokentype.TK_ID;
return i;
default:
if (IsIdChar[zz[z]] == 0)
break;
for (i = 1; IsIdChar[zz[z + i]] != 0; i++);
tokenType = FindKeyword(zz, z + i);
if (tokenType == sql_tokentype.TK_ID && zz[z + i] == '`')
skip = 1;
return i;
}
tokenType = sql_tokentype.TK_ILLEGAL;
return 1;
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,511 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2005 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using System.Collections.Generic;
using LibGSF.Input;
using LibMSI.Internal;
using LibMSI.Views;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI
{
public class LibmsiQuery
{
#region Properties
internal LibmsiView View { get; set; }
internal int Row { get; set; }
internal LibmsiDatabase Database { get; set; }
internal string Query { get; set; }
internal LinkedList<object> Mem { get; set; } = new LinkedList<object>();
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiQuery() { }
/// <summary>
/// Create a SQL query for <paramref name="database"/>.
/// </summary>
/// <param name="database">A LibmsiDatabase</param>
/// <param name="query">A SQL query</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>A new LibmsiQuery on success, null on failure</returns>
public static LibmsiQuery Create(LibmsiDatabase database, string query, ref Exception error)
{
if (database == null)
return null;
if (query == null)
return null;
if (error != null)
return null;
LibmsiQuery self = new LibmsiQuery
{
Database = database,
Query = query,
};
if (!self.Init(ref error))
return null;
return self;
}
/// <summary>
/// Destructor
/// </summary>
~LibmsiQuery()
{
if (View != null)
try { View.Delete(); } catch { }
Mem.Clear();
}
#endregion
#region Functions
/// <summary>
/// Return the next query result. null is returned when there
/// is no more results.
/// </summary>
/// <param name="error">Return location for the error</param>
/// <returns>A newly allocated LibmsiRecord or null when no results or failure.</returns>
public LibmsiRecord Fetch(ref Exception error)
{
if (error != null)
return null;
LibmsiResult ret = Fetch(out LibmsiRecord record);
// FIXME: raise error when it happens
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS && ret != LibmsiResult.NO_MORE_ITEMS)
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return record;
}
/// <summary>
/// Execute the query with the arguments from <paramref name="rec"/>.
/// </summary>
/// <param name="rec">A LibmsiRecord containing query arguments, or null if no arguments needed</param>
/// <param name="error">Return location for the error</param>
/// <returns>True on success</returns>
public bool Execute(LibmsiRecord rec, ref Exception error)
{
if (error != null)
return false;
LibmsiResult ret = Execute(rec);
// FIXME: raise error when it happens
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <summary>
/// Release the current result set.
/// </summary>
/// <param name="error">Return location for the error</param>
/// <returns>True on success</returns>
public bool Close(ref Exception error)
{
if (error != null)
return false;
if (View == null)
return true;
try
{
LibmsiResult ret = View.Close();
// FIXME: raise error when it happens
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
catch
{
return true;
}
}
/// <summary>
/// Call this to get more information on the last query error.
/// </summary>
/// <param name="column">Location to store the allocated column name</param>
/// <param name="error">Return location for the error</param>
public void GetError(out string column, ref Exception error)
{
column = null;
if (error != null)
return;
if (View.Error != LibmsiDBError.LIBMSI_DB_ERROR_SUCCESS)
{
// FIXME: view could have a GError with message?
error = new Exception($"LIBMSI_DB_ERROR: {View.Error}");
column = View.ErrorColumn;
}
}
/// <summary>
/// Get column informations, returned as record string fields.
/// </summary>
/// <param name="info">A LibmsiColInfo specifying the type of information to return</param>
/// <param name="error">Return location for the error</param>
/// <returns>A LibmsiRecord containing informations, or null on error</returns>
public LibmsiRecord GetColumnInfo(LibmsiColInfo info, ref Exception error)
{
if (info != LibmsiColInfo.LIBMSI_COL_INFO_NAMES && info != LibmsiColInfo.LIBMSI_COL_INFO_TYPES)
return null;
if (error != null)
return null;
LibmsiResult r = GetColumnInfo(info, out LibmsiRecord rec);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {r}");
return rec;
}
#endregion
#region Internal Functions
// TODO: Move to LibmsiView
internal static LibmsiResult ViewFindColumn(LibmsiView table, string name, string table_name, out int n)
{
n = 0;
LibmsiResult r = table.GetDimensions(out _, out int count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
for (int i = 1; i <= count; i++)
{
r = table.GetColumnInfo(i, out string col_name, out _, out _, out string haystack_table_name);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
bool x = name == col_name;
if (table_name != null)
x |= table_name == haystack_table_name;
if (!x)
{
n = i;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
}
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
}
// TODO: Move to LibmsiDatabase
internal static LibmsiResult DatabaseOpenQuery(LibmsiDatabase db, string szQuery, out LibmsiQuery pView)
{
Exception err = null;
pView = LibmsiQuery.Create(db, szQuery, ref err);
return pView != null ? LibmsiResult.LIBMSI_RESULT_SUCCESS : LibmsiResult.LIBMSI_RESULT_BAD_QUERY_SYNTAX;
}
// TODO: Move to LibmsiDatabase
internal static LibmsiResult QueryOpen(LibmsiDatabase db, out LibmsiQuery view, string query)
{
Exception err = null;
view = Create(db, query, ref err);
if (err != null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiResult IterateRecords(ref int count, record_func func, object param)
{
LibmsiResult r = Execute(null);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
int max = 0;
if (count != 0)
max = count;
// Iterate a query
int n = 0;
for (; (max == 0) || (n < max); n++)
{
r = Fetch(out LibmsiRecord rec);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
if (func != null)
r = func(rec, param);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
}
Exception error = null; // FIXME: move error handling to caller
Close(ref error);
if (error != null)
Console.Error.WriteLine(error);
count = n;
if (r == LibmsiResult.NO_MORE_ITEMS)
r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
return r;
}
/// <summary>
/// Return a single record from a query
/// </summary>
// TODO: Move to LibmsiDatabase
internal static LibmsiRecord QueryGetRecord(LibmsiDatabase db, string query)
{
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
Exception error = null; // FIXME: move error to caller
LibmsiQuery view = Create(db, query, ref error);
if (error != null)
r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
error = null;
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
view.Execute(null);
view.Fetch(out LibmsiRecord rec);
view.Close(ref error);
if (error != null)
Console.Error.WriteLine(error);
return rec;
}
return null;
}
// TODO: Move to LibmsiDatabase
internal static LibmsiResult MsiViewGetRow(LibmsiDatabase db, LibmsiView view, int row, out LibmsiRecord rec)
{
rec = null;
LibmsiResult ret = view.GetDimensions(out int row_count, out int col_count);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return ret;
if (col_count == 0)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (row >= row_count)
return LibmsiResult.NO_MORE_ITEMS;
rec = LibmsiRecord.Create(col_count);
if (rec == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
for (int i = 1; i <= col_count; i++)
{
ret = view.GetColumnInfo(i, out _, out int type, out _, out _);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"Error getting column type for {i}");
continue;
}
if (MSITYPE_IS_BINARY(type))
{
ret = view.FetchStream(row, i, out GsfInput stm);
if ((ret == LibmsiResult.LIBMSI_RESULT_SUCCESS) && stm != null)
rec.SetGsfInput(i, stm);
else
Console.Error.WriteLine("Failed to get stream");
continue;
}
ret = view.FetchInt(row, i, out int ival);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"Error fetching data for {i}");
continue;
}
if ((type & MSITYPE_VALID) == 0)
Console.Error.WriteLine("Invalid type!");
// Check if it's nul (0) - if so, don't set anything
if (ival == 0)
continue;
if ((type & MSITYPE_STRING) != 0)
{
string sval = db.Strings.LookupId(ival);
rec.SetString(i, sval);
}
else
{
if ((type & MSI_DATASIZEMASK) == 2)
rec.SetInt(i, ival - (1 << 15));
else
rec.SetInt(i, ival - (1 << 31));
}
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiResult Fetch(out LibmsiRecord prec)
{
prec = null;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = MsiViewGetRow(Database, View, Row, out prec);
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
Row++;
return r;
}
internal LibmsiResult Execute(LibmsiRecord rec)
{
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
Row = 0;
try
{
return View.Execute(rec);
}
catch
{
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
}
internal LibmsiResult GetColumnInfo(LibmsiColInfo info, out LibmsiRecord prec)
{
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
prec = null;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
int count;
try
{
r = View.GetDimensions(out _, out count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
}
catch
{
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
if (count == 0)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
LibmsiRecord rec = LibmsiRecord.Create(count);
if (rec == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
for (int i = 0; i < count; i++)
{
r = View.GetColumnInfo(i + 1, out string name, out int type, out bool temporary, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
continue;
if (info == LibmsiColInfo.LIBMSI_COL_INFO_NAMES)
rec.SetString(i + 1, name);
else
SetRecordTypeString(rec, i + 1, type, temporary);
}
prec = rec;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Utilities
private bool Init(ref Exception error)
{
LibmsiResult r = LibmsiSQLInput.ParseSQL(Database, Query, out LibmsiView view, Mem);
View = view;
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {r}");
return r == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
// TODO: Move to LibmsiRecord
private static void SetRecordTypeString(LibmsiRecord rec, int field, int type, bool temporary)
{
string szType;
if (MSITYPE_IS_BINARY(type))
szType = "v";
else if ((type & MSITYPE_LOCALIZABLE) != 0)
szType = "l";
else if ((type & MSITYPE_UNKNOWN) != 0)
szType = "f";
else if ((type & MSITYPE_STRING) != 0)
{
if (temporary)
szType = "g";
else
szType = "s";
}
else
{
if (temporary)
szType = "j";
else
szType = "i";
}
if ((type & MSITYPE_NULLABLE) != 0)
szType = ((char)(szType[0] & ~0x20)).ToString();
szType += (type & 0xff).ToString();
rec.SetString(field, szType);
}
#endregion
}
}

View File

@@ -1,645 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2004 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using System.IO;
using LibGSF.Input;
using LibMSI.Internal;
using static LibMSI.LibmsiTypes;
namespace LibMSI
{
/* maybe we can use a Variant instead of doing it ourselves? */
internal class LibmsiField
{
public int Type { get; set; }
#region Union (u)
public int iVal
{
get
{
try { return (int)u; }
catch { return default; }
}
set => u = value;
}
public string szVal
{
get => u as string;
set => u = value;
}
public GsfInput Stream
{
get => u as GsfInput;
set => u = value;
}
private object u = new object();
#endregion
}
public class LibmsiRecord
{
#region Constants
private const int LIBMSI_FIELD_TYPE_NULL = 0;
private const int LIBMSI_FIELD_TYPE_INT = 1;
private const int LIBMSI_FIELD_TYPE_STR = 3;
private const int LIBMSI_FIELD_TYPE_STREAM = 4;
#endregion
#region Properties
/// <summary>
/// as passed to libmsi_record_new
/// </summary>
internal int Count { get; set; }
/// <summary>
/// nb. array size is count+1
/// </summary>
internal LibmsiField[] Fields { get; set; }
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiRecord() { }
public static LibmsiRecord Create(int count)
{
if (count >= 65535)
return null;
return new LibmsiRecord
{
Count = count,
Fields = new LibmsiField[count],
};
}
#endregion
#region Functions
/// <summary>
/// Clear record fields.
/// <summary>
/// <returns>True on success.</returns>
public bool Clear()
{
for (int i = 0; i <= Count; i++)
{
Fields[i].Type = LIBMSI_FIELD_TYPE_NULL;
Fields[i].iVal = 0;
}
return true;
}
/// <returns>The number of record fields.</returns>
public int GetFieldCount() => Count;
/// <param name="field">A field identifier</param>
/// <returns>True if the field is null (or <paramref name="field"/> > record field count)</returns>
public bool IsNull(int field) => (field > Count) || (Fields[field].Type == LIBMSI_FIELD_TYPE_NULL);
/// <summary>
/// Set the <paramref name="field"/> to the integer value <paramref name="iVal"/>.
/// <summary>
/// <param name="field">A field identifier</param>
/// <param name="iVal">Value to set field to</param>
/// <returns>True on success.</returns>
public bool SetInt(int field, int iVal)
{
if (field > Count)
return false;
Fields[field].Type = LIBMSI_FIELD_TYPE_INT;
Fields[field].iVal = iVal;
return true;
}
/// <summary>
/// Get the integer value of <paramref name="field"/>. If the field is a string
/// representing an integer, it will be converted to an integer value.
/// Other values and types will return LIBMSI_NULL_INT.
/// <summary>
/// <param name="field">A field identifier</param>
/// <returns>The integer value, or LIBMSI_NULL_INT if the field is not an integer.</returns>
public int GetInt(int field)
{
if (field > Count)
unchecked { return (int)LIBMSI_NULL_INT; }
switch (Fields[field].Type)
{
case LIBMSI_FIELD_TYPE_INT:
return Fields[field].iVal;
case LIBMSI_FIELD_TYPE_STR:
if (ExprIntFromString(Fields[field].szVal, out int ret))
return ret;
unchecked { return (int)LIBMSI_NULL_INT; }
default:
Console.Error.WriteLine($"Invalid field type {Fields[field].Type}");
break;
}
unchecked { return (int)LIBMSI_NULL_INT; }
}
/// <summary>
/// Set the <paramref name="field"/> value to <paramref name="szValue"/> string.
/// <summary>
/// <param name="field">A field identifier</param>
/// <param name="szValue">A string or null</param>
/// <returns>True on success.</returns>
public bool SetString(int field, string szValue)
{
if (field > Count)
return false;
if (szValue != null && szValue[0] != '\0')
{
Fields[field].Type = LIBMSI_FIELD_TYPE_STR;
Fields[field].szVal = szValue;
}
else
{
Fields[field].Type = LIBMSI_FIELD_TYPE_NULL;
Fields[field].szVal = null;
}
return true;
}
/// <summary>
/// Get a string representation of <paramref name="field"/>.
/// <summary>
/// <param name="field">A field identifier</param>
/// <returns>A string, or null on error.</returns>
public string GetString(int field)
{
if (field > Count)
return string.Empty; // FIXME: really?
switch (Fields[field].Type)
{
case LIBMSI_FIELD_TYPE_INT:
return Fields[field].iVal.ToString();
case LIBMSI_FIELD_TYPE_STR:
return Fields[field].szVal;
case LIBMSI_FIELD_TYPE_NULL:
return string.Empty;
default:
Console.Error.WriteLine($"Invalid type {Fields[field].Type}");
break;
}
return null;
}
/// <summary>
/// Load the file content as a stream in <paramref name="field"/>.
/// <summary>
/// <param name="field">A field identifier</param>
/// <param name="szFilename">A filename or null</param>
/// <returns>True on success.</returns>
public bool LoadStream(int field, string szFilename)
{
LibmsiResult ret = LoadStreamFromFile(field, szFilename);
return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <summary>
/// Set the stream content from <paramref name="input"/> stream.
/// <summary>
/// <param name="field">A field identifier</param>
/// <param name="input">A GInputStream</param>
/// <param name="count">The number of bytes to read from <paramref name="input"/></param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success.</returns>
public bool SetStream(int field, Stream input, int count, ref Exception error)
{
if (!input.CanRead)
return false;
if (field <= 0 || field > Count)
return false;
if (count <= 0)
return false;
if (error != null)
return false;
byte[] data = new byte[count];
int bytes_read = input.Read(data, 0, count);
if (bytes_read != count)
return false;
GsfInput stm = GsfInputMemory.Create(data, count, true);
if (LoadStream(field, stm) != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return false;
return true;
}
/// <summary>
/// Get the stream associated with the given record <paramref name="field"/>.
/// <summary>
/// <param name="field">A field identifier</param>
/// <returns>True on success.</returns>
public Stream GetStream(int field)
{
Exception err = null;
GsfInput stm = GetStream(field, ref err);
if (stm == null)
return null;
return LibmsiIStream.Create(stm);
}
#endregion
#region Internal Functions
internal LibmsiResult CopyField(int in_n, LibmsiRecord out_rec, int out_n)
{
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
if (in_n > Count || out_n > out_rec.Count)
{
r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
else if (this != out_rec || in_n != out_n)
{
LibmsiField input = Fields[in_n];
LibmsiField output = out_rec.Fields[out_n];
switch (input.Type)
{
case LIBMSI_FIELD_TYPE_NULL:
break;
case LIBMSI_FIELD_TYPE_INT:
output.iVal = input.iVal;
break;
case LIBMSI_FIELD_TYPE_STR:
output.szVal = input.szVal;
break;
case LIBMSI_FIELD_TYPE_STREAM:
output.Stream = input.Stream;
break;
default:
Console.Error.WriteLine($"Invalid field type {input.Type}");
break;
}
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
output.Type = input.Type;
}
return r;
}
internal string GetStringRaw(int field)
{
if (field > Count)
return null;
if (Fields[field].Type != LIBMSI_FIELD_TYPE_STR)
return null;
return Fields[field].szVal;
}
internal LibmsiResult GetString(int field, out string szValue, ref int pcchValue)
{
if (field > Count)
{
if (pcchValue > 0)
szValue = "\0";
else
szValue = null;
pcchValue = 0;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
int len;
switch (Fields[field].Type)
{
case LIBMSI_FIELD_TYPE_INT:
szValue = Fields[field].iVal.ToString();
len = szValue.Length;
break;
case LIBMSI_FIELD_TYPE_STR:
szValue = Fields[field].szVal;
len = Fields[field].szVal.Length;
break;
case LIBMSI_FIELD_TYPE_NULL:
if (pcchValue > 0)
szValue = "\0";
else
szValue = null;
len = 0;
break;
default:
szValue = null;
len = 0;
break;
}
LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_SUCCESS;
if (szValue != null && pcchValue <= len)
ret = LibmsiResult.LIBMSI_RESULT_MORE_DATA;
pcchValue = len;
return ret;
}
internal LibmsiResult LoadStream(int field, GsfInput stream)
{
if ((field == 0) || (field > Count))
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
Fields[field].Type = LIBMSI_FIELD_TYPE_STREAM;
Fields[field].Stream = stream;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiResult LoadStreamFromFile(int field, string szFilename)
{
GsfInput stm;
LibmsiResult r;
if ((field == 0) || (field > Count))
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
// No filename means we should seek back to the start of the stream
if (szFilename == null)
{
if (Fields[field].Type != LIBMSI_FIELD_TYPE_STREAM)
return LibmsiResult.LIBMSI_RESULT_INVALID_FIELD;
stm = Fields[field].Stream;
if (stm == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_FIELD;
stm.Seek(0, SeekOrigin.Begin);
}
else
{
// Read the file into a stream and save the stream in the record
r = AddStreamFromFile(szFilename, out stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// If all's good, store it in the record
LoadStream(field, stm);
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiResult SetGsfInput(int field, GsfInput stm)
{
if (field > Count)
return LibmsiResult.LIBMSI_RESULT_INVALID_FIELD;
Fields[field].Type = LIBMSI_FIELD_TYPE_STREAM;
Fields[field].Stream = stm;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiResult GetGsfInput(int field, out GsfInput pstm)
{
pstm = null;
if (field > Count)
return LibmsiResult.LIBMSI_RESULT_INVALID_FIELD;
if (Fields[field].Type != LIBMSI_FIELD_TYPE_STREAM)
return LibmsiResult.LIBMSI_RESULT_INVALID_FIELD;
pstm = Fields[field].Stream;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
internal LibmsiRecord Clone()
{
int count = GetFieldCount();
LibmsiRecord clone = Create(count);
if (clone == null)
return null;
for (int i = 0; i <= count; i++)
{
if (Fields[i].Type == LIBMSI_FIELD_TYPE_STREAM)
{
Exception err = null;
GsfInput stm = Fields[i].Stream.Duplicate(ref err);
if (stm == null)
return null;
clone.Fields[i].Stream = stm;
clone.Fields[i].Type = LIBMSI_FIELD_TYPE_STREAM;
}
else
{
LibmsiResult r = CopyField(i, clone, i);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return null;
}
}
return clone;
}
internal static bool RecordCompareFields(LibmsiRecord a, LibmsiRecord b, int field)
{
if (a.Fields[field].Type != b.Fields[field].Type)
return false;
switch (a.Fields[field].Type)
{
case LIBMSI_FIELD_TYPE_NULL:
return true;
case LIBMSI_FIELD_TYPE_INT:
return (a.Fields[field].iVal == b.Fields[field].iVal);
case LIBMSI_FIELD_TYPE_STR:
return (a.Fields[field].szVal == b.Fields[field].szVal);
case LIBMSI_FIELD_TYPE_STREAM:
default:
return false;
}
}
internal static bool RecordCompare(LibmsiRecord a, LibmsiRecord b)
{
if (a.Count != b.Count)
return false;
for (int i = 0; i <= a.Count; i++)
{
if (!RecordCompareFields(a, b, i))
return false;
}
return true;
}
internal string DupRecordField(int field)
{
if (IsNull(field))
return null;
int sz = 0;
LibmsiResult r = GetString(field, out _, ref sz);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return null;
sz++;
r = GetString(field, out string str, ref sz);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine("Failed to get string!");
return null;
}
return str;
}
#endregion
#region Utilities
private static bool ExprIntFromString(string str, out int output)
{
output = 0;
int x = 0;
int p = 0; // str[0]
// Skip the minus sign
if (str[p] == '-')
p++;
while (p < str.Length && str[p] != '\0')
{
if ((str[p] < '0') || (str[p] > '9'))
return false;
x *= 10;
x += (str[p] - '0');
p++;
}
// Check if it's negative
if (str[0] == '-')
x = -x;
output = x;
return true;
}
/// <summary>
/// Read the data in a file into a memory-backed GsfInput
/// </summary>
private static LibmsiResult AddStreamFromFile(string szFile, out GsfInput pstm)
{
pstm = null;
Exception err = null;
GsfInput stm = GsfInputStdio.Create(szFile, ref err);
if (stm == null)
{
Console.Error.WriteLine($"Open file failed for {szFile}");
return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED;
}
long sz = stm.Size;
byte[] data;
if (sz == 0)
{
data = new byte[1];
}
else
{
data = new byte[sz];
if (stm.Read((int)sz, data) == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
pstm = GsfInputMemory.Create(data, sz, true);
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private GsfInput GetStream(int field, ref Exception error)
{
if (field > Count)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER}");
return null;
}
if (Fields[field].Type == LIBMSI_FIELD_TYPE_NULL)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_INVALID_DATA}");
return null;
}
if (Fields[field].Type != LIBMSI_FIELD_TYPE_STREAM)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_INVALID_DATATYPE}");
return null;
}
GsfInput stm = Fields[field].Stream;
if (stm == null)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER}");
return null;
}
return stm;
}
#endregion
}
}

View File

@@ -1,958 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002, 2005 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, 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 LibGSF.Input;
using LibMSI.Internal;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI
{
// TODO: Make this a proper variant type OR split it meaningfully
internal class LibmsiOLEVariant
{
public LibmsiOLEVariantType VariantType { get; set; }
#region Union ()
public int IntVal
{
get
{
try { return (int)o; }
catch { return default; }
}
set => o = value;
}
public string StrVal
{
get => o as string;
set => o = value;
}
public ulong FileTime
{
get
{
try { return (ulong)o; }
catch { return default; }
}
set => o = value;
}
private object o = new object();
#endregion
}
public class LibmsiSummaryInfo
{
#region Constants
private static readonly string szSumInfo = (char)0x05 + "SummaryInformation";
private static readonly byte[] fmtid_SummaryInformation = new byte[]
{ 0xe0, 0x85, 0x9f, 0xf2, 0xf9, 0x4f, 0x68, 0x10, 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9};
#endregion
#region Properties
internal LibmsiDatabase Database { get; set; }
/// <summary>
/// Number of changes allowed
/// </summary>
internal int UpdateCount { get; set; }
internal LibmsiOLEVariant[] Properties { get; set; } = new LibmsiOLEVariant[MSI_MAX_PROPS];
#endregion
#region Constructor and Destructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiSummaryInfo() { }
// TODO: Investigate why Create isn't populating the properties
/// <summary>
/// If @database is provided, the summary informations will be
/// populated during creation, and the libmsi_summary_info_persist()
/// function will save the properties to it. If @database is null, you
/// may still populate properties and then save them to a particular
/// database with the libmsi_summary_info_save() function.
/// </summary>
/// <param name="database">An optional associated LibmsiDatabase</param>
/// <param name="update_count">Number of changes allowed</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>A LibmsiSummaryInfo or null on failure</returns>
public static LibmsiSummaryInfo Create(LibmsiDatabase database, int update_count, ref Exception error)
{
if (error != null)
return null;
LibmsiSummaryInfo self = new LibmsiSummaryInfo
{
Database = database,
UpdateCount = update_count,
};
// Read the stream... if we fail, we'll start with an empty property set
if (self.Database != null)
{
LibmsiResult r = self.Database.GetRawStream(szSumInfo, out GsfInput stm);
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
self.LoadSummaryInfo(stm);
}
return self;
}
/// <summary>
/// Destructor
/// </summary>
~LibmsiSummaryInfo()
{
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
FreeProp(Properties[i]);
}
}
#endregion
#region Functions
/// <param name="prop">A LibmsiProperty to get</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>The property type associated for <param name="prop"/>.</returns.
public LibmsiPropertyType GetPropertyType(LibmsiProperty prop, ref Exception error)
{
if (error != null)
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_EMPTY;
if ((int)prop >= MSI_MAX_PROPS)
{
error = new Exception($"Unknown property: {prop}");
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_EMPTY;
}
switch (Properties[(int)prop].VariantType)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_INT;
case LibmsiOLEVariantType.OLEVT_LPSTR:
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_STRING;
case LibmsiOLEVariantType.OLEVT_FILETIME:
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_FILETIME;
case LibmsiOLEVariantType.OLEVT_EMPTY:
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_EMPTY;
default:
error = new Exception($"Unknown type: {Properties[(int)prop].VariantType}");
return LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_EMPTY;
}
}
/// <param name="prop">A LibmsiProperty to get</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>The property value or null on failure.</returns.
public string GetString(LibmsiProperty prop, ref Exception error)
{
if (error != null)
return null;
GetProperty(prop, out LibmsiPropertyType type, out _, out _, out _, out _, out string val, ref error);
return val;
}
/// <param name="prop">A LibmsiProperty to get</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>The property value or -1 on failure.</returns.
public int GetInt(LibmsiProperty prop, ref Exception error)
{
if (error != null)
return -1;
GetProperty(prop, out LibmsiPropertyType type, out int val, out _, out _, out _, out _, ref error);
return val;
}
/// <param name="prop">A LibmsiProperty to get</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>The property value or 0 on failure.</returns.
public ulong GetFiletime(LibmsiProperty prop, ref Exception error)
{
if (error != null)
return 0;
GetProperty(prop, out LibmsiPropertyType type, out _, out ulong val, out _, out _, out _, ref error);
return val;
}
/// <summary>
/// Set string property <paramref name="prop"/>.
/// </summary>
/// <param name="prop">A LibmsiProperty to set</param>
/// <param name="value">A string value</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success</returns>
public bool SetString(LibmsiProperty prop, string value, ref Exception error)
{
if (error != null)
return false;
if (GetType(prop) != LibmsiOLEVariantType.OLEVT_LPSTR)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH}");
return false;
}
LibmsiResult ret = SetProperty(prop, LibmsiOLEVariantType.OLEVT_LPSTR, 0, 0, value);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return false;
}
return true;
}
/// <summary>
/// Set integer property <paramref name="prop"/>.
/// </summary>
/// <param name="prop">A LibmsiProperty to set</param>
/// <param name="value">A value</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success</returns>
public bool SetInt(LibmsiProperty prop, int value, ref Exception error)
{
if (error != null)
return false;
LibmsiOLEVariantType type = GetType(prop);
if (type != LibmsiOLEVariantType.OLEVT_I2 && type != LibmsiOLEVariantType.OLEVT_I4)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH}");
return false;
}
LibmsiResult ret = SetProperty(prop, type, value, 0, null);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return false;
}
return true;
}
/// <summary>
/// Set integer property <paramref name="prop"/>.
/// </summary>
/// <param name="prop">A LibmsiProperty to set</param>
/// <param name="value">A value</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success</returns>
public bool SetFiletime(LibmsiProperty prop, ulong value, ref Exception error)
{
if (error != null)
return false;
if (GetType(prop) != LibmsiOLEVariantType.OLEVT_FILETIME)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH}");
return false;
}
LibmsiResult ret = SetProperty(prop, LibmsiOLEVariantType.OLEVT_FILETIME, 0, value, null);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return false;
}
return true;
}
/// <summary>
/// Save summary informations to the associated database.
/// </summary>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success</returns>
public bool Persist(ref Exception error)
{
if (error != null)
return false;
if (Database == null)
{
error = new Exception("No database associated");
return false;
}
LibmsiResult ret = Persist(Database);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <summary>
/// Save summary informations to the associated database.
/// </summary>
/// <param name="db">A LibmsiDatabase to save to</param>
/// <param name="error">Exception to set on error, or null</param>
/// <returns>True on success</returns>
public bool Save(LibmsiDatabase db, ref Exception error)
{
if (db == null)
return false;
if (error != null)
return false;
LibmsiResult ret = Persist(db);
if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS)
error = new Exception($"LIBMSI_RESULT_ERROR: {ret}");
return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <returns>A new list with the list of set properties</returns>
public List<int> GetProperties()
{
List<int> props = new List<int>();
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
if (Properties[i].VariantType != LibmsiOLEVariantType.OLEVT_EMPTY)
props.Add(i);
}
return props;
}
#endregion
#region Internal Functions
internal string SummaryInfoAsString(int uiProperty)
{
LibmsiOLEVariant prop = Properties[uiProperty];
switch (prop.VariantType)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
return prop.IntVal.ToString();
case LibmsiOLEVariantType.OLEVT_LPSTR:
return prop.StrVal;
case LibmsiOLEVariantType.OLEVT_FILETIME:
return FileTimeToString(prop.FileTime);
case LibmsiOLEVariantType.OLEVT_EMPTY:
return string.Empty;
default:
Console.Error.WriteLine($"Unknown type {prop.VariantType}");
break;
}
return null;
}
// TODO: Move to LibmsiDatabase
internal static LibmsiResult AddSummaryInfo(LibmsiDatabase db, string[][] records, int num_records, int num_columns)
{
Exception err = null;
LibmsiSummaryInfo si = Create(db, num_records * (num_columns / 2), ref err);
if (si == null)
{
Console.Error.WriteLine("No summary information!");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
for (int i = 0; i < num_records; i++)
{
for (int j = 0; j < num_columns; j += 2)
{
LibmsiResult r = ParseProp(records[i][j], records[i][j + 1], out LibmsiProperty pid, out int int_value, out ulong ft_value, out string str_value);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
//assert(GetType(pid) != LibmsiOLEVariantType.OLEVT_EMPTY);
r = si.SetProperty(pid, GetType(pid), int_value, ft_value, str_value);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
}
}
return si.Persist(db);
}
#endregion
#region Utilities
// TODO: Move to LibmsiOLEVariant
private static void FreeProp(LibmsiOLEVariant prop)
{
prop.VariantType = LibmsiOLEVariantType.OLEVT_EMPTY;
}
private static LibmsiOLEVariantType GetType(LibmsiProperty uiProperty)
{
switch (uiProperty)
{
case LibmsiProperty.LIBMSI_PROPERTY_CODEPAGE:
return LibmsiOLEVariantType.OLEVT_I2;
case LibmsiProperty.LIBMSI_PROPERTY_SUBJECT:
case LibmsiProperty.LIBMSI_PROPERTY_AUTHOR:
case LibmsiProperty.LIBMSI_PROPERTY_KEYWORDS:
case LibmsiProperty.LIBMSI_PROPERTY_COMMENTS:
case LibmsiProperty.LIBMSI_PROPERTY_TEMPLATE:
case LibmsiProperty.LIBMSI_PROPERTY_LASTAUTHOR:
case LibmsiProperty.LIBMSI_PROPERTY_UUID:
case LibmsiProperty.LIBMSI_PROPERTY_APPNAME:
case LibmsiProperty.LIBMSI_PROPERTY_TITLE:
return LibmsiOLEVariantType.OLEVT_LPSTR;
case LibmsiProperty.LIBMSI_PROPERTY_EDITTIME:
case LibmsiProperty.LIBMSI_PROPERTY_LASTPRINTED:
case LibmsiProperty.LIBMSI_PROPERTY_CREATED_TM:
case LibmsiProperty.LIBMSI_PROPERTY_LASTSAVED_TM:
return LibmsiOLEVariantType.OLEVT_FILETIME;
case LibmsiProperty.LIBMSI_PROPERTY_SOURCE:
case LibmsiProperty.LIBMSI_PROPERTY_RESTRICT:
case LibmsiProperty.LIBMSI_PROPERTY_SECURITY:
case LibmsiProperty.LIBMSI_PROPERTY_VERSION:
return LibmsiOLEVariantType.OLEVT_I4;
default:
Console.Error.WriteLine($"Invalid type {uiProperty}");
break;
}
return LibmsiOLEVariantType.OLEVT_EMPTY;
}
private static int GetPropertyCount(LibmsiOLEVariant[] property)
{
int n = 0;
if (property == null)
return n;
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
if (property[i].VariantType != LibmsiOLEVariantType.OLEVT_EMPTY)
n++;
}
return n;
}
private static ushort ReadWORD(byte[] data, ref int ofs)
{
ushort val = 0;
val = data[ofs];
val |= (ushort)(data[ofs + 1] << 8);
ofs += 2;
return val;
}
private static uint ReadDWORD(byte[] data, ref int ofs)
{
uint val = 0;
val = data[ofs];
val |= (uint)(data[ofs + 1] << 8);
val |= (uint)(data[ofs + 2] << 16);
val |= (uint)(data[ofs + 3] << 24);
ofs += 4;
return val;
}
private static string FileTimeToString(ulong ft)
{
DateTimeOffset dt = DateTimeOffset.FromUnixTimeSeconds((long)ft);
return dt.ToString("yyyy/MM/dd hh:mm:ss");
}
private static void ParseFileTime(string str, out ulong ft)
{
ft = 0;
if (!DateTimeOffset.TryParse(str, out DateTimeOffset dt))
return;
ft = (ulong)dt.ToUnixTimeSeconds();
}
// FIXME: doesn't deal with endian conversion
private static void ReadPropertiesFromData(LibmsiOLEVariant[] prop, byte[] data, int sz, int cProperties)
{
int idofs = 8;
// Now set all the properties
for (int i = 0; i < cProperties; i++)
{
bool valid = true;
LibmsiProperty propid = (LibmsiProperty)ReadDWORD(data, ref idofs);
int dwOffset = (int)ReadDWORD(data, ref idofs);
if ((int)propid >= MSI_MAX_PROPS)
{
Console.Error.WriteLine($"Unknown property ID {propid}");
break;
}
LibmsiOLEVariantType type = GetType(propid);
if (type == LibmsiOLEVariantType.OLEVT_EMPTY)
{
Console.Error.WriteLine($"propid {propid} has unknown type");
break;
}
LibmsiOLEVariant property = prop[(int)propid];
if (dwOffset + 4 > sz)
{
Console.Error.WriteLine($"Not enough data for type {dwOffset} {sz}");
break;
}
LibmsiOLEVariantType proptype = (LibmsiOLEVariantType)ReadDWORD(data, ref dwOffset);
if (dwOffset + 4 > sz)
{
Console.Error.WriteLine($"Not enough data for type {dwOffset} {sz}");
break;
}
string str = null;
switch (proptype)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
property.IntVal = (int)ReadDWORD(data, ref dwOffset);
break;
case LibmsiOLEVariantType.OLEVT_FILETIME:
if (dwOffset + 8 > sz)
{
Console.Error.WriteLine($"Not enough data for type {dwOffset} {sz}");
valid = false;
break;
}
property.FileTime = (ulong)ReadDWORD(data, ref dwOffset);
property.FileTime |= (ulong)ReadDWORD(data, ref dwOffset) << 32;
break;
case LibmsiOLEVariantType.OLEVT_LPSTR:
int len = (int)ReadDWORD(data, ref dwOffset);
if (len == 0 || dwOffset + len > sz)
{
Console.Error.WriteLine($"Not enough data for type {dwOffset} {len} {sz}");
valid = false;
break;
}
str = Encoding.ASCII.GetString(data, dwOffset, len - 1) + "\0";
break;
default:
Console.Error.WriteLine($"Invalid type {proptype}");
break;
}
if (valid == false)
break;
// Check the type is the same as we expect
if (type == LibmsiOLEVariantType.OLEVT_LPSTR && proptype == LibmsiOLEVariantType.OLEVT_LPSTR)
{
property.StrVal = str;
}
else if (type == proptype)
{
// No-op
}
else if (proptype == LibmsiOLEVariantType.OLEVT_LPSTR)
{
if (str == null)
return;
if (type == LibmsiOLEVariantType.OLEVT_I2 || type == LibmsiOLEVariantType.OLEVT_I4)
{
property.IntVal = int.Parse(new string(str.SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray()));
}
else if (type == LibmsiOLEVariantType.OLEVT_FILETIME)
{
ParseFileTime(str, out ulong ft);
property.FileTime = ft;
}
else
{
Console.Error.WriteLine($"Invalid type, it can't be converted");
break;
}
proptype = type;
}
else
{
Console.Error.WriteLine("Invalid type");
break;
}
// Now we now the type is valid, store it
property.VariantType = proptype;
}
}
private LibmsiResult LoadSummaryInfo(GsfInput stm)
{
long sz = stm.Size;
if (sz == 0)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
byte[] data = new byte[stm.Size];
if (stm.Read((int)sz, data) == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
// Process the set header
int ofs = 0;
if (ReadWORD(data, ref ofs) != 0xfffe)
{
Console.Error.WriteLine("Property set not little-endian");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
// Process the format header
// Check the format id is correct
ofs = 28;
if (fmtid_SummaryInformation.SequenceEqual(new ReadOnlySpan<byte>(data, ofs, 16).ToArray()))
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
// Seek to the location of the section
ofs += 16;
int dwOffset = (int)ReadDWORD(data, ref ofs);
// Read the section itself
ofs = dwOffset;
int cbSection = (int)ReadDWORD(data, ref ofs);
int cProperties = (int)ReadDWORD(data, ref ofs);
if (cProperties > MSI_MAX_PROPS)
{
Console.Error.WriteLine($"Too many properties {cProperties}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
// Read all the data in one go
ReadPropertiesFromData(Properties, data.Skip(dwOffset).ToArray(), cbSection, cProperties);
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private static int WriteWORD(byte[] data, int ofs, ushort val)
{
if (data != null)
{
data[ofs++] = (byte)(val & 0xff);
data[ofs++] = (byte)((val >> 8) & 0xff);
}
return 2;
}
private static int WriteDWORD(byte[] data, int ofs, uint val)
{
if (data != null)
{
data[ofs++] = (byte)(val & 0xff);
data[ofs++] = (byte)((val >> 8) & 0xff);
data[ofs++] = (byte)((val >> 16) & 0xff);
data[ofs++] = (byte)((val >> 24) & 0xff);
}
return 4;
}
private static int WriteFileTime(byte[] data, int ofs, ulong ft)
{
WriteDWORD(data, ofs, (uint)(ft & 0xFFFFFFFFUL));
WriteDWORD(data, ofs + 4, (uint)(ft >> 32));
return 8;
}
private static int WriteString(byte[] data, int ofs, string str)
{
int len = str.Length + 1;
WriteDWORD(data, ofs, (uint)len);
if (data != null)
Array.Copy(Encoding.ASCII.GetBytes(str + "\0"), 0, data, ofs + 4, len);
return (7 + len) & ~3;
}
// TODO: Move to LibmsiOLEVariant
private static int WritePropertyToData(LibmsiOLEVariant prop, byte[] data, int ofs)
{
int sz = ofs;
if (prop.VariantType == LibmsiOLEVariantType.OLEVT_EMPTY)
return sz;
// Add the type
sz += WriteDWORD(data, sz, (uint)prop.VariantType);
switch (prop.VariantType)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
sz += WriteDWORD(data, sz, (uint)prop.IntVal);
break;
case LibmsiOLEVariantType.OLEVT_FILETIME:
sz += WriteFileTime(data, sz, prop.FileTime);
break;
case LibmsiOLEVariantType.OLEVT_LPSTR:
sz += WriteString(data, sz, prop.StrVal);
break;
default:
Console.Error.WriteLine($"Invalid type {prop.VariantType}");
break;
}
return sz;
}
private LibmsiResult Persist(LibmsiDatabase database)
{
// Add up how much space the data will take and calculate the offsets
int cProperties = GetPropertyCount(Properties);
int cbSection = 8 + cProperties * 8;
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
cbSection += WritePropertyToData(Properties[i], null, 0);
}
int sz = 28 + 20 + cbSection;
byte[] data = new byte[sz];
// Write the set header
sz = 0;
sz += WriteWORD(data, sz, 0xfffe); // wByteOrder
sz += WriteWORD(data, sz, 0); // wFormat
sz += WriteDWORD(data, sz, 0x00020005); // dwOSVer - build 5, platform id 2
sz += 16; // clsID
sz += WriteDWORD(data, sz, 1); // reserved
// Write the format header
Array.Copy(fmtid_SummaryInformation, 0, data, sz, 16);
sz += 16;
sz += WriteDWORD(data, sz, 28 + 20); // dwOffset
//assert(sz == 28 + 20);
// Write the section header
sz += WriteDWORD(data, sz, (uint)cbSection);
sz += WriteDWORD(data, sz, (uint)cProperties);
//assert(sz == 28 + 20 + 8);
int dwOffset = 8 + cProperties * 8;
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
int propsz = WritePropertyToData(Properties[i], null, 0);
if (propsz == 0)
continue;
sz += WriteDWORD(data, sz, (uint)i);
sz += WriteDWORD(data, sz, (uint)dwOffset);
dwOffset += propsz;
}
//assert(dwOffset == cbSection);
// Write out the data
for (int i = 0; i < MSI_MAX_PROPS; i++)
{
sz += WritePropertyToData(Properties[i], data, sz);
}
//assert(sz == 28 + 20 + cbSection);
LibmsiResult r = database.WriteRawStreamData(szSumInfo, data, sz, out GsfInput stm);
return r;
}
private void GetProperty(
LibmsiProperty uiProperty,
out LibmsiPropertyType puiDataType,
out int pintvalue,
out ulong pftValue,
out string szValueBuf,
out int pcchValueBuf,
out string str,
ref Exception error)
{
puiDataType = 0; pintvalue = 0; pftValue = 0; szValueBuf = null; pcchValueBuf = 0; str = null;
if ((int)uiProperty >= MSI_MAX_PROPS)
{
error = new Exception($"Unknown property: {uiProperty}");
return;
}
LibmsiOLEVariant prop = Properties[(int)uiProperty];
LibmsiPropertyType type;
switch (prop.VariantType)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
type = LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_INT;
pintvalue = prop.IntVal;
break;
case LibmsiOLEVariantType.OLEVT_LPSTR:
type = LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_STRING;
str = prop.StrVal;
int len = prop.StrVal.Length;
szValueBuf = prop.StrVal.Substring(Math.Min(len, pcchValueBuf));
if (len >= pcchValueBuf)
error = new Exception("The given string is too small");
pcchValueBuf = len;
break;
case LibmsiOLEVariantType.OLEVT_FILETIME:
type = LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_FILETIME;
pftValue = prop.FileTime;
break;
case LibmsiOLEVariantType.OLEVT_EMPTY:
// FIXME: should be replaced by a has_property() instead?
error = new Exception("Empty property");
type = LibmsiPropertyType.LIBMSI_PROPERTY_TYPE_EMPTY;
break;
default:
return;
}
puiDataType = type;
}
private LibmsiResult SetProperty(LibmsiProperty uiProperty, LibmsiOLEVariantType type, int intvalue, ulong pftValue, string szValue)
{
if (type == LibmsiOLEVariantType.OLEVT_LPSTR && szValue == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (type == LibmsiOLEVariantType.OLEVT_FILETIME && pftValue == 0)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
LibmsiOLEVariant prop = Properties[(int)uiProperty];
if (prop.VariantType == LibmsiOLEVariantType.OLEVT_EMPTY)
{
LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (UpdateCount == 0)
return ret;
UpdateCount--;
}
else if (prop.VariantType != type)
{
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
FreeProp(prop);
prop.VariantType = type;
switch (type)
{
case LibmsiOLEVariantType.OLEVT_I2:
case LibmsiOLEVariantType.OLEVT_I4:
prop.IntVal = intvalue;
break;
case LibmsiOLEVariantType.OLEVT_FILETIME:
prop.FileTime = pftValue;
break;
case LibmsiOLEVariantType.OLEVT_LPSTR:
prop.StrVal = szValue;
break;
default:
Console.Error.WriteLine($"Invalid type {type}");
break;
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private static LibmsiResult ParseProp(string prop, string value, out LibmsiProperty pid, out int int_value, out ulong ft_value, out string str_value)
{
pid = (LibmsiProperty)int.Parse(new string(prop.SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray()));
int_value = 0; ft_value = 0; str_value = null;
switch (pid)
{
case LibmsiProperty.LIBMSI_PROPERTY_CODEPAGE:
case LibmsiProperty.LIBMSI_PROPERTY_SOURCE:
case LibmsiProperty.LIBMSI_PROPERTY_RESTRICT:
case LibmsiProperty.LIBMSI_PROPERTY_SECURITY:
case LibmsiProperty.LIBMSI_PROPERTY_VERSION:
int_value = int.Parse(new string(value.SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray()));
break;
case LibmsiProperty.LIBMSI_PROPERTY_EDITTIME:
case LibmsiProperty.LIBMSI_PROPERTY_LASTPRINTED:
case LibmsiProperty.LIBMSI_PROPERTY_CREATED_TM:
case LibmsiProperty.LIBMSI_PROPERTY_LASTSAVED_TM:
ParseFileTime(value, out ft_value);
break;
case LibmsiProperty.LIBMSI_PROPERTY_SUBJECT:
case LibmsiProperty.LIBMSI_PROPERTY_AUTHOR:
case LibmsiProperty.LIBMSI_PROPERTY_KEYWORDS:
case LibmsiProperty.LIBMSI_PROPERTY_COMMENTS:
case LibmsiProperty.LIBMSI_PROPERTY_TEMPLATE:
case LibmsiProperty.LIBMSI_PROPERTY_LASTAUTHOR:
case LibmsiProperty.LIBMSI_PROPERTY_UUID:
case LibmsiProperty.LIBMSI_PROPERTY_APPNAME:
case LibmsiProperty.LIBMSI_PROPERTY_TITLE:
str_value = value;
break;
default:
Console.Error.WriteLine($"Unhandled prop id {pid}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright (C) 2002,2003 Mike McCormack
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace LibMSI
{
/// <remarks>Also known as LibmsiResultError</remarks>
public enum LibmsiResult
{
LIBMSI_RESULT_SUCCESS, /* FIXME: remove me */
LIBMSI_RESULT_ACCESS_DENIED,
LIBMSI_RESULT_INVALID_HANDLE,
LIBMSI_RESULT_NOT_ENOUGH_MEMORY,
LIBMSI_RESULT_INVALID_DATA,
LIBMSI_RESULT_OUTOFMEMORY,
LIBMSI_RESULT_INVALID_PARAMETER,
LIBMSI_RESULT_OPEN_FAILED,
LIBMSI_RESULT_CALL_NOT_IMPLEMENTED,
LIBMSI_RESULT_MORE_DATA,
LIBMSI_RESULT_NOT_FOUND,
LIBMSI_RESULT_CONTINUE,
LIBMSI_RESULT_UNKNOWN_PROPERTY,
LIBMSI_RESULT_BAD_QUERY_SYNTAX,
LIBMSI_RESULT_INVALID_FIELD,
LIBMSI_RESULT_FUNCTION_FAILED,
LIBMSI_RESULT_INVALID_TABLE,
LIBMSI_RESULT_DATATYPE_MISMATCH,
LIBMSI_RESULT_INVALID_DATATYPE,
NO_MORE_ITEMS = int.MaxValue,
}
public enum LibmsiPropertyType
{
LIBMSI_PROPERTY_TYPE_EMPTY = 0,
LIBMSI_PROPERTY_TYPE_INT = 1,
LIBMSI_PROPERTY_TYPE_STRING = 2,
LIBMSI_PROPERTY_TYPE_FILETIME = 3,
}
public enum LibmsiColInfo
{
LIBMSI_COL_INFO_NAMES = 0,
LIBMSI_COL_INFO_TYPES = 1
}
public enum LibmsiDbFlags
{
LIBMSI_DB_FLAGS_READONLY = 1 << 0,
LIBMSI_DB_FLAGS_CREATE = 1 << 1,
LIBMSI_DB_FLAGS_TRANSACT = 1 << 2,
LIBMSI_DB_FLAGS_PATCH = 1 << 3,
}
public enum LibmsiDBError
{
LIBMSI_DB_ERROR_SUCCESS, /* FIXME: remove me */
LIBMSI_DB_ERROR_INVALIDARG,
LIBMSI_DB_ERROR_MOREDATA,
LIBMSI_DB_ERROR_FUNCTIONERROR,
LIBMSI_DB_ERROR_DUPLICATEKEY,
LIBMSI_DB_ERROR_REQUIRED,
LIBMSI_DB_ERROR_BADLINK,
LIBMSI_DB_ERROR_OVERFLOW,
LIBMSI_DB_ERROR_UNDERFLOW,
LIBMSI_DB_ERROR_NOTINSET,
LIBMSI_DB_ERROR_BADVERSION,
LIBMSI_DB_ERROR_BADCASE,
LIBMSI_DB_ERROR_BADGUID,
LIBMSI_DB_ERROR_BADWILDCARD,
LIBMSI_DB_ERROR_BADIDENTIFIER,
LIBMSI_DB_ERROR_BADLANGUAGE,
LIBMSI_DB_ERROR_BADFILENAME,
LIBMSI_DB_ERROR_BADPATH,
LIBMSI_DB_ERROR_BADCONDITION,
LIBMSI_DB_ERROR_BADFORMATTED,
LIBMSI_DB_ERROR_BADTEMPLATE,
LIBMSI_DB_ERROR_BADDEFAULTDIR,
LIBMSI_DB_ERROR_BADREGPATH,
LIBMSI_DB_ERROR_BADCUSTOMSOURCE,
LIBMSI_DB_ERROR_BADPROPERTY,
LIBMSI_DB_ERROR_MISSINGDATA,
LIBMSI_DB_ERROR_BADCATEGORY,
LIBMSI_DB_ERROR_BADKEYTABLE,
LIBMSI_DB_ERROR_BADMAXMINVALUES,
LIBMSI_DB_ERROR_BADCABINET,
LIBMSI_DB_ERROR_BADSHORTCUT,
LIBMSI_DB_ERROR_STRINGOVERFLOW,
LIBMSI_DB_ERROR_BADLOCALIZEATTRIB
}
public enum LibmsiProperty
{
LIBMSI_PROPERTY_DICTIONARY = 0,
LIBMSI_PROPERTY_CODEPAGE = 1,
LIBMSI_PROPERTY_TITLE = 2,
LIBMSI_PROPERTY_SUBJECT = 3,
LIBMSI_PROPERTY_AUTHOR = 4,
LIBMSI_PROPERTY_KEYWORDS = 5,
LIBMSI_PROPERTY_COMMENTS = 6,
LIBMSI_PROPERTY_TEMPLATE = 7,
LIBMSI_PROPERTY_LASTAUTHOR = 8,
LIBMSI_PROPERTY_UUID = 9,
LIBMSI_PROPERTY_EDITTIME = 10,
LIBMSI_PROPERTY_LASTPRINTED = 11,
LIBMSI_PROPERTY_CREATED_TM = 12,
LIBMSI_PROPERTY_LASTSAVED_TM = 13,
LIBMSI_PROPERTY_VERSION = 14,
LIBMSI_PROPERTY_SOURCE = 15,
LIBMSI_PROPERTY_RESTRICT = 16,
LIBMSI_PROPERTY_THUMBNAIL = 17,
LIBMSI_PROPERTY_APPNAME = 18,
LIBMSI_PROPERTY_SECURITY = 19
}
public static class LibmsiTypes
{
public const uint LIBMSI_NULL_INT = 0x80000000;
}
}

View File

@@ -1,215 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2006 Mike McCormack
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using LibGSF.Input;
using static LibMSI.LibmsiQuery;
namespace LibMSI.Views
{
internal class LibmsiAlterView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView Table { get; set; }
public column_info ColInfo { get; set; }
public int Hold { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiAlterView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, string name, column_info colinfo, int hold)
{
LibmsiAlterView av = new LibmsiAlterView();
LibmsiResult r = LibmsiTableView.Create(db, name, out LibmsiView table);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
view = null;
return r;
}
av.Table = table;
if (colinfo != null)
colinfo.Table = name;
// Fill the structure
av.Database = db;
av.Hold = hold;
av.ColInfo = colinfo;
view = av;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = default;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult GetRow(int row, out LibmsiRecord rec) => Table.GetRow(row, out rec);
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (Hold == 1)
{
Table.AddRef();
}
else if (Hold == -1)
{
int r = Table.Release();
if (r == 0)
Table = null;
}
if (ColInfo != null)
return AddColumn();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (Table != null)
Table.Delete();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
#endregion
#region Utilities
private static LibmsiResult CountIter(LibmsiRecord row, object param)
{
if (param is int)
param = (int)param + 1;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private bool CheckColumnExists(LibmsiDatabase db, string table, string column)
{
string query = $"SELECT * FROM `_Columns` WHERE `Table`='{table}' AND `Name`='{column}'";
LibmsiResult r = QueryOpen(db, out LibmsiQuery view, query);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return false;
r = view.Execute(null);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
goto done;
r = view.Fetch(out LibmsiRecord rec);
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
rec = null;
done:
view = null;
return (r == LibmsiResult.LIBMSI_RESULT_SUCCESS);
}
private LibmsiResult AddColumn()
{
int colnum = 1;
string szColumns = "_Columns";
LibmsiResult r = LibmsiTableView.Create(Database, szColumns, out LibmsiView columns);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
if (CheckColumnExists(Database, ColInfo.Table, ColInfo.Column))
{
columns.Delete();
return LibmsiResult.LIBMSI_RESULT_BAD_QUERY_SYNTAX;
}
string query = $"SELECT * FROM `_Columns` WHERE `Table`='{ColInfo.Table}' ORDER BY `Number`";
r = QueryOpen(Database, out LibmsiQuery view, query);
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
int count = 0;
r = view.IterateRecords(ref count, CountIter, colnum);
view = null;
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
columns.Delete();
return r;
}
}
r = columns.AddColumn(ColInfo.Table, colnum, ColInfo.Column, ColInfo.Type, (Hold == 1));
columns.Delete();
return r;
}
#endregion
}
}

View File

@@ -1,161 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2004 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using LibMSI.Internal;
using static LibMSI.Internal.LibmsiTable;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
internal class LibmsiCreateView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public string Name { get; set; }
public bool IsTemp { get; set; }
public bool Hold { get; set; }
public column_info ColInfo { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiCreateView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, string table, column_info col_info, bool hold)
{
view = null;
LibmsiResult r = CheckColumns(col_info);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
LibmsiCreateView cv = new LibmsiCreateView();
bool temp = true;
bool tempprim = false;
column_info col;
for (col = col_info; col != null; col = col.Next)
{
if (col.Table == null)
col.Table = table;
if (!col.Temporary)
temp = false;
else if ((col.Type & MSITYPE_KEY) != 0)
tempprim = true;
}
if (!temp && tempprim)
{
cv = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
// Fill the structure
cv.Database = db;
cv.Name = table;
cv.ColInfo = col_info;
cv.IsTemp = temp;
cv.Hold = hold;
view = cv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
LibmsiCondition persist = (IsTemp) ? LibmsiCondition.LIBMSI_CONDITION_FALSE : LibmsiCondition.LIBMSI_CONDITION_TRUE;
if (IsTemp && !Hold)
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
return CreateTable(Database, Name, ColInfo, persist);
}
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
Database = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Utilities
private static LibmsiResult CheckColumns(column_info col_info)
{
column_info c1, c2;
// Check for two columns with the same name
for (c1 = col_info; c1 != null; c1 = c1.Next )
{
for (c2 = c1.Next; c2 != null; c2 = c2.Next)
{
if (c1.Column == c2.Column)
return LibmsiResult.LIBMSI_RESULT_BAD_QUERY_SYNTAX;
}
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
}
}

View File

@@ -1,154 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2005 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using LibGSF.Input;
namespace LibMSI.Views
{
/// <summary>
/// Code to delete rows from a table.
/// <summary>
/// <remarks>
/// We delete rows by blanking them out rather than trying to remove the row.
/// This appears to be what the native MSI does (or tries to do). For the query:
///
/// delete from Property
///
/// some non-zero entries are left in the table by native MSI. I'm not sure if
/// that's a bug in the way I'm running the query, or a just a bug.
internal class LibmsiDeleteView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView Table { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiDeleteView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, LibmsiView table)
{
LibmsiDeleteView dv = new LibmsiDeleteView
{
Database = db,
Table = table,
};
view = dv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = Table.Execute(record);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = Table.GetDimensions(out int rows, out int cols);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Blank out all the rows that match */
for (int i = 0; i < rows; i++)
{
Table.DeleteRow(i);
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Close()
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return Table.Close();
}
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
rows = 0;
return Table.GetDimensions(out _, out cols);
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return Table.GetColumnInfo(n, out name, out type, out temporary, out table_name);
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (Table != null)
Table.Delete();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
#endregion
}
}

View File

@@ -1,260 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
namespace LibMSI.Views
{
internal class LibmsiDistinctSet
{
public int Val { get; set; }
public int Count { get; set; }
public int Row { get; set; }
public LibmsiDistinctSet NextRow { get; set; }
public LibmsiDistinctSet NextCol { get; set; }
}
internal class LibmsiDistinctView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView Table { get; set; }
public int RowCount { get; set; }
public int[] Translation { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiDistinctView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, LibmsiView table)
{
view = null;
LibmsiResult r = table.GetDimensions(out _, out int count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine("Can't get table dimensions");
return r;
}
LibmsiDistinctView dv = new LibmsiDistinctView
{
Database = db,
Table = table,
Translation = null,
RowCount = 0,
};
view = dv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (row >= RowCount)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
row = Translation[row];
return Table.FetchInt(row, col, out val);
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = Table.Execute(record);
if( r != LibmsiResult.LIBMSI_RESULT_SUCCESS )
return r;
r = Table.GetDimensions(out int r_count, out int c_count );
if( r != LibmsiResult.LIBMSI_RESULT_SUCCESS )
return r;
Translation = new int[r_count];
// Build it
LibmsiDistinctSet rowset = null;
for (int i = 0; i < r_count; i++ )
{
LibmsiDistinctSet x = rowset;
for (int j = 1; j <= c_count; j++)
{
r = Table.FetchInt(i, j, out int val);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"Failed to fetch int at {i} {j}");
DistinctFree(rowset);
return r;
}
x = DistinctInsert(x, val, i);
if(x == null)
{
Console.Error.WriteLine($"Failed to insert at {i} {j}");
DistinctFree(rowset);
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
if (j != c_count)
x = x.NextCol;
}
// Check if it was distinct and if so, include it
if (x.Row == i )
Translation[RowCount++] = i;
}
DistinctFree( rowset );
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Close()
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
Translation = null;
RowCount = 0;
return Table.Close();
}
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (Translation == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
rows = RowCount;
return Table.GetDimensions(out _, out cols);
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return Table.GetColumnInfo(n, out name, out type, out temporary, out table_name);
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (Table != null)
Table.Delete();
Translation = null;
Database = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = Table.FindMatchingRows(col, val, out row, ref handle);
if (row > RowCount)
return LibmsiResult.NO_MORE_ITEMS;
row = Translation[row];
return r;
}
#endregion
#region Utilities
private static LibmsiDistinctSet DistinctInsert(LibmsiDistinctSet x, int val, int row)
{
// Horrible O(n) find
while (x != null)
{
if (x.Val == val)
{
x.Count++;
return x;
}
x = x.NextRow;
}
// Nothing found, so add one
x = new LibmsiDistinctSet
{
Val = val,
Count = 1,
Row = row,
NextRow = null,
NextCol = null,
};
return x;
}
private static void DistinctFree(LibmsiDistinctSet x)
{
while (x != null)
{
LibmsiDistinctSet next = x.NextRow;
DistinctFree(x.NextCol);
x = next;
}
}
#endregion
}
}

View File

@@ -1,102 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2008 James Hawkins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace LibMSI.Views
{
internal class LibmsiDropView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView Table { get; set; }
public column_info ColInfo { get; set; }
public int Hold { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary
private LibmsiDropView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, string name)
{
view = null;
LibmsiDropView dv = new LibmsiDropView();
LibmsiResult r = LibmsiTableView.Create(db, name, out LibmsiView table);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
dv = null;
return r;
}
dv.Table = table;
dv.Database = db;
view = dv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = Table.Execute(record);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
return Table.Drop();
}
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (Table != null)
Table.Delete();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
}
}

View File

@@ -1,318 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2004 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using static LibMSI.Internal.LibmsiSQLInput;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
internal class LibmsiInsertView : LibmsiView
{
#region Properties
public LibmsiView Table { get; set; }
public LibmsiDatabase Database { get; set; }
public bool IsTemp { get; set; }
public LibmsiView View { get; set; }
public column_info Vals { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiInsertView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, string table, column_info columns, column_info values, bool temp)
{
view = null;
// There should be one value for each column
if (CountColumnInfo(columns) != CountColumnInfo(values))
return LibmsiResult.LIBMSI_RESULT_BAD_QUERY_SYNTAX;
LibmsiResult r = LibmsiTableView.Create(db, table, out LibmsiView tv);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = LibmsiSelectView.Create(db, out LibmsiView sv, tv, columns);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
if (tv != null)
tv.Delete();
return r;
}
LibmsiInsertView iv = new LibmsiInsertView
{
Table = tv,
Database = db,
Vals = values,
IsTemp = temp,
View = sv,
};
view = iv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = View.Execute(null);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = View.GetDimensions(out _, out int col_count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Merge the wildcard values into the list of values provided
// in the query, and create a record containing both.
LibmsiRecord values = MsiQueryMergeRecord(col_count, Vals, record);
if (values == null)
return r;
r = ArrangeRecord(ref values);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Rows with NULL primary keys are inserted at the beginning of the table
int row = -1;
if (RowHasNullPrimaryKeys(values))
row = 0;
return Table.InsertRow(values, row, IsTemp);
}
/// <inheritdoc/>
public override LibmsiResult Close()
{
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.Close();
}
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.GetDimensions(out rows, out cols);
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.GetColumnInfo(n, out name, out type, out temporary, out table_name);
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (View != null)
View.Delete();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
#endregion
#region Internal Functions
/// <summary>
/// Merge a value_list and a record to create a second record.
/// Replace wildcard entries in the valuelist with values from the record
/// </summary>
internal static LibmsiRecord MsiQueryMergeRecord(int fields, column_info vl, LibmsiRecord rec)
{
int wildcard_count = 1;
LibmsiRecord merged = LibmsiRecord.Create(fields);
for (int i = 1; i <= fields; i++)
{
if (vl == null)
return null;
switch (vl.Val.Type)
{
case EXPR_SVAL:
merged.SetString(i, vl.Val.SVal);
break;
case EXPR_IVAL:
merged.SetInt(i, vl.Val.IVal);
break;
case EXPR_WILDCARD:
if (rec == null)
return null;
rec.CopyField(wildcard_count, merged, i);
wildcard_count++;
break;
default:
Console.Error.WriteLine($"Unknown expression type {vl.Val.Type}");
break;
}
vl = vl.Next;
}
return merged;
}
#endregion
#region Utilities
/// <summary>
/// Checks to see if the column order specified in the INSERT query
/// matches the column order of the table
/// </summary>
private bool ColumnsInOrder(int col_count)
{
for (int i = 1; i <= col_count; i++)
{
View.GetColumnInfo(i, out string a, out _, out _, out _);
Table.GetColumnInfo(i, out string b, out _, out _, out _);
if (a != b)
return false;
}
return true;
}
/// <summary>
/// Rearranges the data in the record to be inserted based on column order,
/// and pads the record for any missing columns in the INSERT query
/// </summary>
private LibmsiResult ArrangeRecord(ref LibmsiRecord values)
{
LibmsiResult r = Table.GetDimensions(out _, out int col_count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
int val_count = values.GetFieldCount();
// Check to see if the columns are arranged already
// to avoid unnecessary copying
if (col_count == val_count && ColumnsInOrder(col_count))
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
LibmsiRecord padded = LibmsiRecord.Create(col_count);
if (padded == null)
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
for (int colidx = 1; colidx <= val_count; colidx++)
{
r = View.GetColumnInfo(colidx, out string a, out _, out _, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
for (int i = 1; i <= col_count; i++)
{
r = Table.GetColumnInfo(i, out string b, out _, out _, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
if (a == b)
{
values.CopyField(colidx, padded, i);
break;
}
}
}
values = padded;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private bool RowHasNullPrimaryKeys(LibmsiRecord row)
{
LibmsiResult r = Table.GetDimensions(out _, out int col_count );
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return false;
for (int i = 1; i <= col_count; i++)
{
r = Table.GetColumnInfo(i, out _, out int type, out _, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return false;
if ((type & MSITYPE_KEY) == 0)
continue;
if (row.IsNull(i))
return true;
}
return false;
}
private static int CountColumnInfo(column_info ci)
{
int n = 0;
for (; ci != null; ci = ci.Next)
{
n++;
}
return n;
}
#endregion
}
}

View File

@@ -1,323 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2004 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using LibGSF.Input;
using static LibMSI.LibmsiQuery;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
/// <summary>
/// Below is the query interface to a table
/// </summary>
internal class LibmsiSelectView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView Table { get; set; }
public int NumCols { get; set; }
public int MaxCols { get; set; }
public int[] Cols { get; set; } = new int[1];
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiSelectView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, LibmsiView table, column_info columns)
{
int count = SelectCountColumns(columns);
LibmsiSelectView sv = new LibmsiSelectView
{
Database = db,
Table = table,
NumCols = 0,
MaxCols = count,
};
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
while (columns != null)
{
r = sv.AddColumn(columns.Column, columns.Table);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
columns = columns.Next;
}
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS )
view = sv;
else
view = null;
return r;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (col == 0 || col > NumCols)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
col = Cols[col - 1];
if (col == 0)
{
val = 0;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
return Table.FetchInt(row, col, out val);
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (col == 0 || col > NumCols)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
col = Cols[col - 1];
if (col == 0)
{
stm = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
return Table.FetchStream(row, col, out stm);
}
/// <inheritdoc/>
public override LibmsiResult GetRow(int row, out LibmsiRecord rec)
{
rec = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return MsiViewGetRow(Database, this, row, out rec);
}
/// <inheritdoc/>
public override LibmsiResult SetRow(int row, LibmsiRecord rec, int mask)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
// Test if any of the mask bits are invalid
if (mask >= (1 << NumCols))
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
// Find the number of columns in the table below
LibmsiResult r = Table.GetDimensions(out _, out int col_count);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Expand the record to the right size for the underlying table
LibmsiRecord expanded = LibmsiRecord.Create(col_count);
if (expanded == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
// Move the right fields across
int expanded_mask = 0;
for (int i = 0; i < NumCols; i++ )
{
r = rec.CopyField(i + 1, expanded, Cols[i]);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
expanded_mask |= (1 << (Cols[i] - 1));
}
// Set the row in the underlying table
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
r = Table.SetRow(row, expanded, expanded_mask);
return r;
}
/// <inheritdoc/>
public override LibmsiResult InsertRow(LibmsiRecord record, int row, bool temporary)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
// Rearrange the record to suit the table
LibmsiResult r = Table.GetDimensions(out _, out int table_cols);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
LibmsiRecord outrec = LibmsiRecord.Create(table_cols + 1);
for (int i = 0; i < NumCols; i++)
{
r = record.CopyField(i + 1, outrec, Cols[i] );
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
}
return Table.InsertRow(outrec, row, temporary );
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return Table.Execute(record);
}
/// <inheritdoc/>
public override LibmsiResult Close()
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return Table.Close();
}
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
cols = NumCols;
return Table.GetDimensions(out rows, out _);
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (n == 0 || n > NumCols)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
n = Cols[ n - 1 ];
if (n != 0)
{
name = string.Empty;
type = MSITYPE_UNKNOWN | MSITYPE_VALID;
temporary = false;
table_name = szEmpty;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
return Table.GetColumnInfo(n, out name, out type, out temporary, out table_name);
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (Table != null)
Table.Delete();
Table = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if ((col == 0) || (col > NumCols))
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
col = Cols[ col - 1 ];
return Table.FindMatchingRows(col, val, out row, ref handle);
}
#endregion
#region Utilities
private LibmsiResult AddColumn(string name, string table_name)
{
LibmsiView table = Table;
if (table == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
try { table.GetDimensions(out _, out _); }
catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; }
try { table.GetColumnInfo(0, out _, out _, out _, out _); }
catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; }
if (NumCols >= MaxCols)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
int n;
if (name[0] == '\0')
{
n = 0;
}
else
{
LibmsiResult r = ViewFindColumn(table, name, table_name, out n);
if( r != LibmsiResult.LIBMSI_RESULT_SUCCESS )
return r;
}
Cols[NumCols++] = n;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private static int SelectCountColumns(column_info col)
{
int n;
for (n = 0; col != null; col = col.Next)
{
n++;
}
return n;
}
#endregion
}
}

View File

@@ -1,320 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2008 James Hawkins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using LibGSF.Input;
using LibMSI.Internal;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
internal class STORAGE
{
public int StrIndex { get; set; }
}
internal class LibmsiStorageView : LibmsiView
{
#region Constants
private const int NUM_STORAGES_COLS = 2;
private const int MAX_STORAGES_NAME_LEN = 62;
#endregion
#region Properties
public LibmsiDatabase Database { get; set; }
public STORAGE[] Storages { get; set; }
public int MaxStorages { get; set; }
public int NumRows { get; set; }
public int RowSize { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiStorageView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view)
{
view = null;
LibmsiStorageView sv = new LibmsiStorageView
{
Database = db,
};
LibmsiResult r = sv.AddStoragesToTable();
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
view = sv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
private bool StoragesSetTableSize(int size)
{
if (size >= MaxStorages)
{
MaxStorages *= 2;
STORAGE[] temp = new STORAGE[MaxStorages];
Array.Copy(Storages, temp, Storages.Length);
Storages = temp;
}
return true;
}
private STORAGE CreateStorage(string name)
{
return new STORAGE
{
StrIndex = Database.Strings.AddString(name, -1, 1, StringPersistence.StringNonPersistent)
};
}
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
if (col != 1)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (row >= NumRows)
return LibmsiResult.NO_MORE_ITEMS;
val = Storages[row].StrIndex;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
if (row >= NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return LibmsiResult.LIBMSI_RESULT_INVALID_DATA;
}
/// <inheritdoc/>
public override LibmsiResult GetRow(int row, out LibmsiRecord rec)
{
rec = null;
return LibmsiResult.LIBMSI_RESULT_CALL_NOT_IMPLEMENTED;
}
/// <inheritdoc/>
public override LibmsiResult SetRow(int row, LibmsiRecord rec, int mask)
{
STORAGE storage;
string name = null;
if (row > NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = rec.GetGsfInput(2, out GsfInput stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
if (Storages[row] != null)
{
if ((mask & 1) != 0)
{
Console.Error.WriteLine("FIXME: renaming storage via UPDATE on _Storages table");
return r;
}
storage = Storages[row];
name = Database.Strings.LookupId(storage.StrIndex);
}
else
{
name = rec.GetStringRaw(1);
}
if (name == null)
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
Database.CreateStorage(name, stm);
storage = CreateStorage(name);
if (storage == null)
r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
Storages[row] = storage;
return r;
}
/// <inheritdoc/>
public override LibmsiResult InsertRow(LibmsiRecord record, int row, bool temporary)
{
if (!StoragesSetTableSize(++NumRows))
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (row == -1)
row = NumRows - 1;
// FIXME have to readjust rows
return SetRow(row, record, 0);
}
/// <inheritdoc/>
public override LibmsiResult DeleteRow(int row)
{
if (row > NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
string name = Database.Strings.LookupId(Storages[row].StrIndex);
if (name == null)
{
Console.Error.WriteLine("Failed to retrieve storage name");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
Database.DestroyStorage(name);
// Shift the remaining rows
for (int i = row + 1; i < NumRows; i++)
{
Storages[i - 1] = Storages[i];
}
NumRows--;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record) => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
cols = NUM_STORAGES_COLS;
rows = NumRows;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (n == 0 || n > NUM_STORAGES_COLS)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
switch (n)
{
case 1:
name = szName;
type = MSITYPE_STRING | MSITYPE_VALID | MAX_STORAGES_NAME_LEN;
break;
case 2:
name = szData;
type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE;
break;
default:
Console.Error.WriteLine($"Invalid case {n}");
break;
}
table_name = szStorages;
temporary = false;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
Storages = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
if (col == 0 || col > NUM_STORAGES_COLS)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
while (handle.Row < NumRows)
{
if (Storages[handle.Row].StrIndex == val)
{
row = handle.Row;
break;
}
handle = handle.Next;
}
handle = handle.Next;
if (handle.Row >= NumRows)
return LibmsiResult.NO_MORE_ITEMS;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Utilities
private static LibmsiResult AddStorageToTable(string name, GsfInfile stg, object opaque)
{
LibmsiStorageView sv = (LibmsiStorageView)opaque;
STORAGE storage = sv.CreateStorage(name);
if (storage == null)
return LibmsiResult.LIBMSI_RESULT_NOT_ENOUGH_MEMORY;
if (!sv.StoragesSetTableSize(++sv.NumRows))
return LibmsiResult.LIBMSI_RESULT_NOT_ENOUGH_MEMORY;
sv.Storages[sv.NumRows - 1] = storage;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult AddStoragesToTable()
{
MaxStorages = 1;
Storages = new STORAGE[1];
return Database.EnumDbStorages(AddStorageToTable, this);
}
#endregion
}
}

View File

@@ -1,336 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2007 James Hawkins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using LibGSF.Input;
using LibMSI.Internal;
using static LibMSI.LibmsiQuery;
using static LibMSI.Internal.LibmsiTable;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
internal class STREAM
{
public int StrIndex { get; set; }
public GsfInput Stream { get; set; }
}
internal class LibmsiStreamsView : LibmsiView
{
#region Constants
private const int NUM_STREAMS_COLS = 2;
#endregion
#region Properties
public LibmsiDatabase Database { get; set; }
public STREAM[] Streams { get; set; }
public int MaxStreams { get; set; }
public int NumRows { get; set; }
public int RowSize { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiStreamsView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view)
{
view = null;
LibmsiStreamsView sv = new LibmsiStreamsView
{
Database = db,
};
LibmsiResult r = sv.AddStreamsToTable();
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
view = sv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
if (col != 1)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (row >= NumRows)
return LibmsiResult.NO_MORE_ITEMS;
val = Streams[row].StrIndex;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
if (row >= NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
stm = Streams[row].Stream;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult GetRow(int row, out LibmsiRecord rec) => MsiViewGetRow(Database, this, row, out rec);
/// <inheritdoc/>
public override LibmsiResult SetRow(int row, LibmsiRecord rec, int mask)
{
if (row > NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = rec.GetGsfInput(2, out GsfInput stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
STREAM stream = null;
string name = null;
if (Streams[row] != null)
{
if ((mask & 1) != 0)
{
Console.Error.WriteLine("FIXME: renaming stream via UPDATE on _Streams table");
return r;
}
stream = Streams[row];
name = Database.Strings.LookupId(stream.StrIndex);
}
else
{
name = rec.GetStringRaw(1);
if (name == null)
{
Console.Error.WriteLine("Failed to retrieve stream name");
return r;
}
stream = CreateStream(name, false, stm);
}
if (stream == null)
return r;
r = Database.CreateStream(name, stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"Failed to create stream: {r}");
return r;
}
Streams[row] = stream;
return r;
}
/// <inheritdoc/>
public override LibmsiResult InsertRow(LibmsiRecord record, int row, bool temporary)
{
if (!StreamsSetTableSize(++NumRows))
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (row == -1)
row = NumRows - 1;
// Shift the rows to make room for the new row
for (int i = NumRows - 1; i > row; i--)
{
Streams[i] = Streams[i - 1];
}
return SetRow(row, record, 0);
}
/// <inheritdoc/>
public override LibmsiResult DeleteRow(int row)
{
string name;
string encname;
if (row > NumRows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
name = Database.Strings.LookupId(Streams[row].StrIndex);
if (name == null)
{
Console.Error.WriteLine("Failed to retrieve stream name");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
encname = EncodeStreamName(false, name).TrimEnd('\0');
Database.DestroyStream(encname);
// Shift the remaining rows
for (int i = row + 1; i < NumRows; i++)
{
Streams[i - 1] = Streams[i];
}
NumRows--;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record) => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
cols = NUM_STREAMS_COLS;
rows = NumRows;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (n == 0 || n > NUM_STREAMS_COLS)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
switch (n)
{
case 1:
name = szName;
type = MSITYPE_STRING | MSITYPE_VALID | MAX_STREAM_NAME_LEN;
break;
case 2:
name = szData;
type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE;
break;
default:
Console.Error.WriteLine($"Invalid value: {n}");
break;
}
table_name = szStreams;
temporary = false;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Delete() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
if (col == 0 || col > NUM_STREAMS_COLS)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
while (handle.Row < NumRows)
{
if (Streams[handle.Row].StrIndex == val)
{
row = handle.Row;
break;
}
handle = handle.Next;
}
handle = handle.Next;
if (handle.Row >= NumRows)
return LibmsiResult.NO_MORE_ITEMS;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Helpers
private bool StreamsSetTableSize(int size)
{
if (size >= MaxStreams)
{
STREAM[] temp = new STREAM[MaxStreams * 2];
Array.Copy(Streams, temp, Streams.Length);
Streams = temp;
}
return true;
}
private STREAM CreateStream(string name, bool encoded, GsfInput stm)
{
STREAM stream = new STREAM();
string decoded = null;
if (encoded)
{
decoded = DecodeStreamName(name);
name = decoded;
}
stream.StrIndex = Database.Strings.AddString(name, -1, 1, StringPersistence.StringNonPersistent);
stream.Stream = stm;
return stream;
}
private static LibmsiResult AddStreamToTable(string name, GsfInput stm, object opaque)
{
LibmsiStreamsView sv = (LibmsiStreamsView)opaque;
STREAM stream = sv.CreateStream(name, true, stm);
if (stream == null)
return LibmsiResult.LIBMSI_RESULT_NOT_ENOUGH_MEMORY;
if (!sv.StreamsSetTableSize(++sv.NumRows))
return LibmsiResult.LIBMSI_RESULT_NOT_ENOUGH_MEMORY;
sv.Streams[sv.NumRows - 1] = stream;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult AddStreamsToTable()
{
MaxStreams = 1;
Streams = new STREAM[1];
return Database.EnumDbStreams(AddStreamToTable, this);
}
#endregion
}
}

View File

@@ -1,850 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2005 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using System.Linq;
using LibGSF.Input;
using LibMSI.Internal;
using static LibMSI.LibmsiQuery;
using static LibMSI.LibmsiTypes;
using static LibMSI.Internal.LibmsiTable;
using static LibMSI.Internal.MsiPriv;
namespace LibMSI.Views
{
internal class LibmsiColumnHashEntry
{
public LibmsiColumnHashEntry Next { get; set; }
public int Value { get; set; }
public int Row { get; set; }
}
internal class LibmsiTableView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiTable Table { get; set; }
public LibmsiColumnInfo[] Columns { get; set; }
public int NumCols { get; set; }
public int RowSize { get; set; }
public string Name { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiTableView() { }
public static LibmsiResult Create(LibmsiDatabase db, string name, out LibmsiView view)
{
if (name == szStreams)
return LibmsiStreamsView.Create(db, out view);
else if (name == szStorages)
return LibmsiStorageView.Create(db, out view);
LibmsiTableView tv = new LibmsiTableView();
LibmsiResult r = GetTable(db, name, out LibmsiTable table);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
view = null;
Console.Error.WriteLine("Table not found");
return r;
}
// Fill the structure
tv.Table = table;
tv.Database = db;
tv.Columns = tv.Table.ColInfo;
tv.NumCols = tv.Table.ColCount;
tv.RowSize = GetRowSize(tv.Table.ColInfo, tv.Table.ColCount, LONG_STR_BYTES);
tv.Name = name;
view = tv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if ((col == 0) || (col > NumCols))
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
// How many rows are there?
if (row >= Table.RowCount)
return LibmsiResult.NO_MORE_ITEMS;
if (Columns[col - 1].Offset >= RowSize)
{
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
Console.Error.WriteLine($"{this} {Columns}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
int n = BytesPerColumn(Columns[col - 1], LONG_STR_BYTES);
if (n != 2 && n != 3 && n != 4)
{
Console.Error.WriteLine("oops! what is %d bytes per column?\n", n );
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
int offset = Columns[col-1].Offset;
val = ReadTableInt(Table.Data, row, offset, n);
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
{
stm = null;
LibmsiResult r = MsiStreamName(row, out string full_name);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"Fetching stream, error = {r}");
return r;
}
string encname = EncodeStreamName(false, full_name).TrimEnd('\0');
r = Database.GetRawStream(encname, out stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
Console.Error.WriteLine($"Fetching stream {full_name}, error = {r}");
if (stm != null)
stm.Name = full_name;
return r;
}
/// <inheritdoc/>
public override LibmsiResult GetRow(int row, out LibmsiRecord rec)
{
rec = null;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
return MsiViewGetRow(Database, this, row, out rec);
}
/// <inheritdoc/>
public override LibmsiResult SetRow(int row, LibmsiRecord rec, int mask)
{
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
// Test if any of the mask bits are invalid
if (mask >= (1 << NumCols))
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
for (int i = 0; i < NumCols; i++ )
{
// Only update the fields specified in the mask
if ((mask & (1 << i)) == 0)
continue;
bool persistent = (Table.Persistent != LibmsiCondition.LIBMSI_CONDITION_FALSE) && (Table.DataPersistent[row]);
// FIXME: should we allow updating keys?
int val = 0;
if (rec.IsNull(i + 1))
{
r = GetTableValueFromRecord(rec, i + 1, out val);
if (MSITYPE_IS_BINARY(Columns[i].Type))
{
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
r = rec.GetGsfInput(i + 1, out GsfInput stm);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = MsiStreamName(row, out string stname);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = AddStream(Database, stname, stm);
if ( r != LibmsiResult.LIBMSI_RESULT_SUCCESS )
return r;
}
else if ((Columns[i].Type & MSITYPE_STRING) != 0)
{
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
string sval = rec.GetStringRaw(i + 1);
val = Database.Strings.AddString(sval, -1, 1, persistent ? StringPersistence.StringPersistent : StringPersistence.StringNonPersistent );
}
else
{
FetchInt(row, i + 1, out int x);
if (val == x)
continue;
}
}
else
{
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
}
r = SetInt(row, i + 1, val);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
}
return r;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record) => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0;
cols = NumCols;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
rows = Table.RowCount;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (n == 0 || n > NumCols)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
name = Columns[n - 1].ColName;
if (name == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
table_name = Columns[n - 1].TableName;
if (table_name == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
type = Columns[n - 1].Type;
temporary = Columns[n - 1].Temporary;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult InsertRow(LibmsiRecord rec, int row, bool temporary)
{
// Check that the key is unique - can we find a matching row?
LibmsiResult r = TableValidateNew(rec, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
if (row == -1)
row = FindInsertIndex(rec);
r = TableCreateNewRow(ref row, temporary);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Shift the rows to make room for the new row
for (int i = Table.RowCount - 1; i > row; i--)
{
byte[] temp = new byte[RowSize];
Array.Copy(Table.Data[i - 1], Table.Data[i], RowSize);
Table.Data[i - 1] = Enumerable.Repeat<byte>(0x00, RowSize).ToArray();
Table.DataPersistent[i] = Table.DataPersistent[i - 1];
}
// Re-set the persistence flag
Table.DataPersistent[row] = !temporary;
return SetRow(row, rec, (1 << NumCols) - 1);
}
/// <inheritdoc/>
public override LibmsiResult DeleteRow(int row)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
LibmsiResult r = GetDimensions(out int num_rows, out int num_cols);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
if (row >= num_rows)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
num_rows = Table.RowCount;
Table.RowCount--;
// Reset the hash tables
for (int i = 0; i < NumCols; i++)
{
Columns[i].HashTable = null;
}
for (int i = row + 1; i < num_rows; i++)
{
Array.Copy(Table.Data[i], Table.Data[i - 1], RowSize);
Table.DataPersistent[i - 1] = Table.DataPersistent[i];
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
Table = null;
Columns = null;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
LibmsiColumnHashEntry entry;
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (col == 0 || col > NumCols)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (Columns[col - 1].HashTable == null)
{
int num_rows = Table.RowCount;
if (Columns[col - 1].Offset >= RowSize)
{
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
Console.Error.WriteLine($"{this} {Columns}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
// Allocate contiguous memory for the table and its entries so we
// don't have to do an expensive cleanup
LibmsiColumnHashEntry[] hash_table = new LibmsiColumnHashEntry[num_rows];
Columns[col - 1].HashTable = hash_table;
int new_entry = 0;
for (int i = 0; i < num_rows; i++, new_entry++)
{
if (FetchInt(i, col, out int row_value) != LibmsiResult.LIBMSI_RESULT_SUCCESS)
continue;
hash_table[new_entry] = new LibmsiColumnHashEntry
{
Next = null,
Value = row_value,
Row = i,
};
if (hash_table[row_value] != null)
{
LibmsiColumnHashEntry prev_entry = hash_table[row_value];
while (prev_entry.Next != null)
{
prev_entry = prev_entry.Next;
}
prev_entry.Next = hash_table[new_entry];
}
else
{
hash_table[row_value] = hash_table[new_entry];
}
}
}
if (handle == null)
entry = Columns[col - 1].HashTable[val];
else
entry = handle.Next;
while (entry != null && entry.Value != val)
{
entry = entry.Next;
}
handle = entry;
if (entry == null)
return LibmsiResult.NO_MORE_ITEMS;
row = entry.Row;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override int AddRef()
{
for (int i = 0; i < Table.ColCount; i++)
{
if ((Table.ColInfo[i].Type & MSITYPE_TEMPORARY) != 0)
Table.ColInfo[i].RefCount++;
}
return ++Table.RefCount;
}
/// <inheritdoc/>
public override LibmsiResult RemoveColumn(string table, int number)
{
LibmsiRecord rec = LibmsiRecord.Create(2);
if (rec == null)
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
rec.SetString(1, table);
rec.SetInt(2, number);
LibmsiResult r = Create(Database, szColumns, out LibmsiView columns);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = FindRow(columns as LibmsiTableView, rec, out int row, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
columns.Delete();
return r;
}
r = (columns as LibmsiTableView).DeleteRow(row);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
columns.Delete();
return r;
}
UpdateTableColumns(Database, table);
columns.Delete();
return r;
}
/// <inheritdoc/>
public override int Release()
{
int ref_count = Table.RefCount;
for (int i = 0; i < Table.ColCount; i++)
{
if ((Table.ColInfo[i].Type & MSITYPE_TEMPORARY) != 0)
{
ref_count = --Table.ColInfo[i].RefCount;
if (ref_count == 0)
{
LibmsiResult r = RemoveColumn(Table.ColInfo[i].TableName, Table.ColInfo[i].Number);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
}
}
}
ref_count = --Table.RefCount;
if (ref_count == 0)
{
if (Table.RowCount == 0)
Delete();
}
return ref_count;
}
/// <inheritdoc/>
public override LibmsiResult AddColumn(string table, int number, string column, int type, bool hold)
{
LibmsiRecord rec = LibmsiRecord.Create(4);
if (rec == null)
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
rec.SetString(1, table);
rec.SetInt(2, number);
rec.SetString(3, column);
rec.SetInt(4, type);
LibmsiResult r = InsertRow(rec, -1, false);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
UpdateTableColumns(Database, table);
if (!hold)
return r;
LibmsiTable msitable = FindCachedTable(Database, table);
for (int i = 0; i < msitable.ColCount; i++)
{
if (msitable.ColInfo[i].ColName == column)
{
msitable.ColInfo[i].RefCount++;
break;
}
}
return r;
}
/// <inheritdoc/>
public override LibmsiResult Drop()
{
LibmsiResult r;
for (int i = Table.ColCount - 1; i >= 0; i--)
{
r = RemoveColumn(Table.ColInfo[i].TableName, Table.ColInfo[i].Number);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
}
LibmsiRecord rec = LibmsiRecord.Create(1);
if (rec == null)
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
rec.SetString(1, Name);
r = Create(Database, szTables, out LibmsiView tables);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = FindRow((tables as LibmsiTableView), rec, out int row, out _);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
tables.Delete();
return r;
}
r = tables.DeleteRow(row);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
tables.Delete();
return r;
}
tables.Delete();
return r;
}
#endregion
#region Utilities
private LibmsiResult MsiStreamName(int row, out string pstname)
{
int len = Name.Length + 1;
string stname = Name;
LibmsiResult r;
for (int i = 0; i < NumCols; i++)
{
int type = Columns[i].Type;
if ((type & MSITYPE_KEY) != 0)
{
r = FetchInt(row, i + 1, out int ival);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
pstname = null;
return r;
}
string sval = string.Empty;
if ((Columns[i].Type & MSITYPE_STRING) != 0)
{
sval = Database.Strings.LookupId(ival );
if (sval == null)
{
pstname = null;
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
}
}
else
{
int n = BytesPerColumn(Columns[i], LONG_STR_BYTES);
switch (n)
{
case 2:
sval = (ival - 0x8000).ToString();
break;
case 4:
sval = (ival ^ 0x80000000).ToString();
break;
default:
Console.Error.WriteLine($"Oops - unknown column width {n}");
pstname = null;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
}
len += szDot.Length + sval.Length;
stname += szDot + sval;
}
else
{
continue;
}
}
pstname = stname;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult SetInt(int row, int col, int val)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (col == 0 || col > NumCols)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (row >= Table.RowCount)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
if (Columns[col - 1].Offset >= RowSize)
{
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
Console.Error.WriteLine($"{this} {Columns}");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
Columns[col - 1].HashTable = null;
int n = BytesPerColumn(Columns[col - 1], LONG_STR_BYTES);
if (n != 2 && n != 3 && n != 4)
{
Console.Error.WriteLine($"Oops! what is {n} bytes per column?");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
int offset = Columns[col - 1].Offset;
for (int i = 0; i < n; i++)
{
Table.Data[row][offset + i] = (byte)((val >> i * 8) & 0xff);
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult GetTableValueFromRecord(LibmsiRecord rec, int iField, out int pvalue)
{
LibmsiResult r;
pvalue = 0;
if (iField <= 0 || iField > NumCols || rec.IsNull(iField))
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiColumnInfo columninfo = Columns[iField - 1];
if (MSITYPE_IS_BINARY(columninfo.Type))
{
pvalue = 1; // Refers to the first key column
}
else if ((columninfo.Type & MSITYPE_STRING) != 0)
{
string sval = rec.GetStringRaw(iField);
if (sval != null)
{
r = Database.Strings.IdFromStringUTF8(sval, out pvalue);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_NOT_FOUND;
}
else
{
pvalue = 0;
}
}
else if (BytesPerColumn(columninfo, LONG_STR_BYTES) == 2)
{
pvalue = 0x8000 + rec.GetInt(iField);
if ((pvalue & 0xffff0000) != 0)
{
Console.Error.WriteLine($"Field {iField} value {pvalue - 0x8000} out of range");
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
}
else
{
int ival = rec.GetInt(iField);
pvalue = (int)(ival ^ 0x80000000);
}
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult TableCreateNewRow(ref int num, bool temporary)
{
if (Table == null)
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
byte[] row = new byte[RowSize];
int data_ptr = 0; // Table.Data[0][]
int data_persist_ptr = 0; // Table.DataPersistent[0];
if (num == -1)
num = Table.RowCount;
int sz = Table.RowCount + 1;
if (Table.Data[data_ptr] != null)
{
byte[] p = Table.Data[data_ptr];
Array.Resize(ref p, sz);
Table.Data[data_ptr] = p;
}
else
{
Table.Data[data_ptr] = new byte[sz];
}
sz = Table.RowCount + 1;
if (Table.DataPersistent != null)
{
bool[] b = Table.DataPersistent;
Array.Resize(ref b, sz);
Table.DataPersistent = b;
}
else
{
Table.DataPersistent = new bool[sz];
}
Table.Data[data_ptr + Table.RowCount] = row;
Table.DataPersistent[data_persist_ptr + Table.RowCount] = !temporary;
Table.RowCount++;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private LibmsiResult TableValidateNew(LibmsiRecord rec, out int column)
{
// Check there's no null values where they're not allowed
for (int i = 0; i < NumCols; i++ )
{
if ((Columns[i].Type & MSITYPE_NULLABLE) != 0)
continue;
if (MSITYPE_IS_BINARY(Columns[i].Type))
{
// Skip binary columns
}
else if ((Columns[i].Type & MSITYPE_STRING) != 0)
{
string str = rec.GetStringRaw(i + 1);
if (str == null || str[0] == 0)
{
column = i;
return LibmsiResult.LIBMSI_RESULT_INVALID_DATA;
}
}
else
{
int n = rec.GetInt(i + 1);
if (n == LIBMSI_NULL_INT)
{
column = i;
return LibmsiResult.LIBMSI_RESULT_INVALID_DATA;
}
}
}
// Check there's no duplicate keys
LibmsiResult r = FindRow(this, rec, out int row, out column);
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
private int CompareRecord(int row, LibmsiRecord rec)
{
for (int i = 0; i < NumCols; i++ )
{
if ((Columns[i].Type & MSITYPE_KEY) == 0)
continue;
LibmsiResult r = GetTableValueFromRecord(rec, i + 1, out int ivalue);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return 1;
r = FetchInt(row, i + 1, out int x);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
Console.Error.WriteLine($"FetchInt should not fail here {r}");
return -1;
}
if (ivalue > x)
{
return 1;
}
else if (ivalue == x)
{
if (i < NumCols - 1)
continue;
return 0;
}
else
{
return -1;
}
}
return 1;
}
private int FindInsertIndex(LibmsiRecord rec)
{
int low = 0, high = Table.RowCount - 1;
while (low <= high)
{
int idx = (low + high) / 2;
int c = CompareRecord(idx, rec);
if (c < 0)
high = idx - 1;
else if (c > 0)
low = idx + 1;
else
return idx;
}
return high + 1;
}
#endregion
}
}

View File

@@ -1,196 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2004 Mike McCormack for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using LibMSI.Internal;
namespace LibMSI.Views
{
/// <summary>
/// Below is the query interface to a table
/// </summary>
internal class LibmsiUpdateView : LibmsiView
{
#region Properties
public LibmsiDatabase Database { get; set; }
public LibmsiView View { get; set; }
public column_info Vals { get; set; }
#endregion
#region Constructor
/// <summary>
/// Private constructor
/// </summary>
private LibmsiUpdateView() { }
public static LibmsiResult Create(LibmsiDatabase db, out LibmsiView view, string table, column_info columns, expr expr)
{
view = null;
LibmsiResult r;
LibmsiView sv = null, wv = null;
if (expr != null)
r = LibmsiWhereView.Create(db, out wv, table, expr);
else
r = LibmsiTableView.Create(db, table, out wv);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
// Then select the columns we want
r = LibmsiSelectView.Create(db, out sv, wv, columns);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
{
wv.Delete();
return r;
}
LibmsiUpdateView uv = new LibmsiUpdateView
{
Database = db,
Vals = columns,
View = sv,
};
view = uv;
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
#endregion
#region Functions
/// <inheritdoc/>
public override LibmsiResult FetchInt(int row, int col, out int val)
{
val = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
/// <inheritdoc/>
public override LibmsiResult Execute(LibmsiRecord record)
{
int i;
LibmsiRecord where = null;
int cols_count, where_count;
column_info col = Vals;
// Extract the where markers from the record
if (record != null)
{
int s = record.GetFieldCount();
for (i = 0; col != null; col = col.Next)
{
i++;
}
cols_count = i;
where_count = s - i;
if (where_count > 0)
{
where = LibmsiRecord.Create(where_count);
if (where != null)
{
for (i = 1; i <= where_count; i++)
{
record.CopyField(cols_count + i, where, i);
}
}
}
}
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
LibmsiResult r = View.Execute(where);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
r = View.GetDimensions(out int row_count, out int col_count );
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
return r;
LibmsiRecord values = LibmsiInsertView.MsiQueryMergeRecord(col_count, Vals, record);
if (values == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
for (i = 0; i < row_count; i++)
{
r = View.SetRow(i, values, (1 << col_count) - 1);
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
break;
}
return r;
}
/// <inheritdoc/>
public override LibmsiResult Close()
{
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.Close();
}
/// <inheritdoc/>
public override LibmsiResult GetDimensions(out int rows, out int cols)
{
rows = 0; cols = 0;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.GetDimensions(out rows, out cols);
}
/// <inheritdoc/>
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
{
name = null; type = 0; temporary = false; table_name = null;
if (View == null)
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
return View.GetColumnInfo(n, out name, out type, out temporary, out table_name);
}
/// <inheritdoc/>
public override LibmsiResult Delete()
{
if (View != null)
View.Delete();
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
}
/// <inheritdoc/>
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
{
row = 0;
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
}
#endregion
}
}

View File

@@ -1,173 +0,0 @@
/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2002-2005 Mike McCormack for CodeWeavers
* Copyright 2005 Aric Stewart for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
using System;
using LibGSF.Input;
using LibMSI.Internal;
namespace LibMSI.Views
{
internal class column_info
{
public string Table { get; set; }
public string Column { get; set; }
public int Type { get; set; }
public bool Temporary { get; set; }
public expr Val { get; set; }
public column_info Next { get; set; }
}
internal abstract class LibmsiView
{
#region Properties
public LibmsiDBError Error { get; set; }
public string ErrorColumn { get; set; }
#endregion
#region Virtual Functions
/// <summary>
/// Reads one integer from {row,col} in the table
/// </summary>
/// <remarks>
/// This function should be called after the execute method.
/// Data returned by the function should not change until
/// close or delete is called.
/// To get a string value, query the database's string table with
/// the integer value returned from this function.
/// </remarks>
public virtual LibmsiResult FetchInt(int row, int col, out int val) => throw new NotImplementedException();
/// <summary>
/// Gets a stream from {row,col} in the table
/// </summary>
/// <remarks>
/// This function is similar to FetchInt, except fetches a
/// stream instead of an integer.
/// </remarks>
public virtual LibmsiResult FetchStream(int row, int col, out GsfInput stm) => throw new NotImplementedException();
/// <summary>
/// Gets values from a row
/// </summary>
public virtual LibmsiResult GetRow(int row, out LibmsiRecord rec) => throw new NotImplementedException();
/// <summary>
/// Sets values in a row as specified by mask
/// </summary>
/// <remarks>Similar semantics to fetch_int</remarks>
public virtual LibmsiResult SetRow(int row, LibmsiRecord rec, int mask) => throw new NotImplementedException();
/// <summary>
/// Inserts a new row into the database from the records contents
/// </summary>
public virtual LibmsiResult InsertRow(LibmsiRecord record, int row, bool temporary) => throw new NotImplementedException();
/// <summary>
/// Deletes a row from the database
/// </summary>
public virtual LibmsiResult DeleteRow(int row) => throw new NotImplementedException();
/// <summary>
/// Loads the underlying data into memory so it can be read
/// </summary>
public virtual LibmsiResult Execute(LibmsiRecord record) => throw new NotImplementedException();
/// <summary>
/// Clears the data read by execute from memory
/// </summary>
public virtual LibmsiResult Close() => throw new NotImplementedException();
/// <summary>
/// Returns the number of rows or columns in a table.
/// </summary>
/// <remarks>
/// The number of rows can only be queried after the execute method
/// is called. The number of columns can be queried at any time.
/// </remarks>
public virtual LibmsiResult GetDimensions(out int rows, out int cols) => throw new NotImplementedException();
/// <summary>
/// Returns the name and type of a specific column
/// </summary>
/// <remarks>The column information can be queried at any time.</remarks>
public virtual LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name) => throw new NotImplementedException();
/// <summary>
/// Destroys the structure completely
/// </summary>
public virtual LibmsiResult Delete() => throw new NotImplementedException();
/// <summary>
/// Iterates through rows that match a value
/// </summary>
/// <remarks>
/// If the column type is a string then a string ID should be passed in.
/// If the value to be looked up is an integer then no transformation of
/// the input value is required, except if the column is a string, in which
/// case a string ID should be passed in.
/// The handle is an input/output parameter that keeps track of the current
/// position in the iteration. It must be initialised to zero before the
/// first call and continued to be passed in to subsequent calls.
/// </remarks>
public virtual LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle) => throw new NotImplementedException();
/// <summary>
/// Increases the reference count of the table
/// </summary>
public virtual int AddRef() => throw new NotImplementedException();
/// <summary>
/// Decreases the reference count of the table
/// </summary>
public virtual int Release() => throw new NotImplementedException();
/// <summary>
/// Adds a column to the table
/// </summary>
public virtual LibmsiResult AddColumn(string table, int number, string column, int type, bool hold) => throw new NotImplementedException();
/// <summary>
/// Removes the column represented by table name and column number from the table
/// </summary>
public virtual LibmsiResult RemoveColumn(string table, int number) => throw new NotImplementedException();
/// <summary>
/// Orders the table by columns
/// </summary>
public virtual LibmsiResult Sort(column_info columns) => throw new NotImplementedException();
/// <summary>
/// Drops the table from the database
/// </summary>
public virtual LibmsiResult Drop() => throw new NotImplementedException();
#endregion
}
}

File diff suppressed because it is too large Load Diff