diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Common.cs b/sources/OpenMcdf.Extensions/OLEProperties/Common.cs index c46b0315..4ba65449 100644 --- a/sources/OpenMcdf.Extensions/OLEProperties/Common.cs +++ b/sources/OpenMcdf.Extensions/OLEProperties/Common.cs @@ -36,4 +36,9 @@ public enum PropertyType { TypedPropertyValue = 0, DictionaryProperty = 1 } + + internal static class CodePages + { + public const int CP_WINUNICODE = 0x04B0; + } } diff --git a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs index 9b76b754..bf1e4936 100644 --- a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs +++ b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs @@ -7,8 +7,6 @@ namespace OpenMcdf.Extensions.OLEProperties { public class DictionaryEntry { - private const int CP_WINUNICODE = 0x04B0; - int codePage; public DictionaryEntry(int codePage) @@ -27,7 +25,7 @@ public void Read(BinaryReader br) PropertyIdentifier = br.ReadUInt32(); Length = br.ReadInt32(); - if (codePage != CP_WINUNICODE) + if (codePage != CodePages.CP_WINUNICODE) { nameBytes = br.ReadBytes(Length); } diff --git a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs index 3b8fc71d..1930cfc9 100644 --- a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs +++ b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs @@ -59,19 +59,85 @@ public void Read(BinaryReader br) } + /// + /// Write the dictionary and all its values into the specified . + /// + /// + /// Based on the Microsoft specifications at https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/99127b7f-c440-4697-91a4-c853086d6b33 + /// + /// A writer to write the dictionary into. public void Write(BinaryWriter bw) { + long curPos = bw.BaseStream.Position; + bw.Write(entries.Count); foreach (KeyValuePair kv in entries) { - bw.Write(kv.Key); - string s = kv.Value; - if (!s.EndsWith("\0")) - s += "\0"; - bw.Write(Encoding.GetEncoding(this.codePage).GetBytes(s)); + WriteEntry(bw, kv.Key, kv.Value); } + var size = (int)(bw.BaseStream.Position - curPos); + WritePaddingIfNeeded(bw, size); + } + + // Write a single entry to the dictionary, and handle and required null termination and padding. + private void WriteEntry(BinaryWriter bw, uint propertyIdentifier, string name) + { + // Write the PropertyIdentifier + bw.Write(propertyIdentifier); + + // Encode string data with the current codepage + var nameBytes = Encoding.GetEncoding(this.codePage).GetBytes(name); + uint byteLength = (uint)nameBytes.Length; + + // If the code page is WINUNICODE, write the length as the number of characters and pad the length to a multiple of 4 bytes + // Otherwise, write the length as the number of bytes and don't pad. + // In either case, the string must be null terminated + if (codePage == CodePages.CP_WINUNICODE) + { + bool addNullTerminator = + byteLength == 0 || nameBytes[byteLength - 1] != '\0' || nameBytes[byteLength - 2] != '\0'; + + if (addNullTerminator) + byteLength += 2; + + bw.Write((uint)byteLength / 2); + bw.Write(nameBytes); + + if (addNullTerminator) + { + bw.Write((byte)0); + bw.Write((byte)0); + } + + WritePaddingIfNeeded(bw, (int)byteLength); + } + else + { + bool addNullTerminator = + byteLength == 0 || nameBytes[byteLength - 1] != '\0'; + + if (addNullTerminator) + byteLength += 1; + + bw.Write(byteLength); + bw.Write(nameBytes); + + if (addNullTerminator) + bw.Write((byte)0); + } + } + + // Write as much padding as needed to pad fieldLength to a multiple of 4 bytes + private void WritePaddingIfNeeded(BinaryWriter bw, int fieldLength) + { + var m = fieldLength % 4; + + if (m > 0) + for (int i = 0; i < 4 - m; i++) // padding + bw.Write((byte)0); } } } +