From 8f5c08e4a9990ed6ea27c6239eb06c410ef3ec97 Mon Sep 17 00:00:00 2001 From: Patrick Simpson Date: Mon, 27 Feb 2017 19:04:41 +0100 Subject: [PATCH] [KOE-12] Finished encoding of search criteria for COM. Added code to modify search query to exclude shared folders, still need to add code to allow for selected folders. --- .../AcaciaZPushPlugin.csproj | 2 + .../SharedFolders/SharedCalendarReminders.cs | 80 ++++++++ .../Features/Signatures/FeatureSignatures.cs | 2 +- .../AcaciaZPushPlugin/Native/MAPI/Binary.cs | 14 +- .../AcaciaZPushPlugin/Native/MAPI/Property.cs | 139 ++++++++------ .../Native/MAPI/Restriction.cs | 178 +++++++++++++++--- .../AcaciaZPushPlugin/Native/NativeEncoder.cs | 118 ++++++++++-- .../AcaciaZPushPlugin/SearchQuery.cs | 126 +++++++++++-- .../Stubs/OutlookWrappers/FolderWrapper.cs | 16 +- 9 files changed, 554 insertions(+), 121 deletions(-) create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj index 69353fd..4f0feb6 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj @@ -274,6 +274,7 @@ + UserControl @@ -291,6 +292,7 @@ + diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs new file mode 100644 index 0000000..7db25c1 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs @@ -0,0 +1,80 @@ +using Acacia.Native.MAPI; +using Acacia.Stubs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Features.SharedFolders +{ + public class SharedCalendarReminders : LogContext + { + private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F)); + + private readonly LogContext _context; + public string LogContextId + { + get + { + return _context.LogContextId; + } + } + + public SharedCalendarReminders(LogContext context) + { + this._context = context; + } + + public void Initialise(IStore store) + { + using (IFolder reminders = store.GetSpecialFolder(SpecialFolder.Reminders)) + { + SearchQuery.Or custom = FindCustomQuery(reminders, true); + } + } + + private SearchQuery.Or FindCustomQuery(IFolder reminders, bool addIfNeeded) + { + SearchQuery query = reminders.SearchCriteria; + if (!(query is SearchQuery.And)) + return null; + Logger.Instance.Trace(this, "Current query1: {0}", query.ToString()); + + SearchQuery.And root = (SearchQuery.And)query; + // TODO: more strict checking of query + if (root.Operands.Count == 3) + { + SearchQuery.Or custom = root.Operands.ElementAt(2) as SearchQuery.Or; + if (custom != null) + { + // TODO: check property test + return custom; + } + } + + // We have the root, but not the custom query. Create it if needed. + if (addIfNeeded) + { + Logger.Instance.Debug(this, "Creating custom query"); + Logger.Instance.Trace(this, "Current query: {0}", root.ToString()); + SearchQuery.Or custom = new SearchQuery.Or(); + + // Add the prefix exclusion for shared folders + custom.Add( + new SearchQuery.Not( + new SearchQuery.PropertyContent( + PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, "S" + ) + ) + ); + + root.Operands.Add(custom); + Logger.Instance.Trace(this, "Modified query: {0}", root.ToString()); + reminders.SearchCriteria = root; + Logger.Instance.Trace(this, "Modified query2: {0}", reminders.SearchCriteria.ToString()); + } + return null; + } + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Signatures/FeatureSignatures.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Signatures/FeatureSignatures.cs index e4e0cd0..5da2521 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Signatures/FeatureSignatures.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Signatures/FeatureSignatures.cs @@ -122,7 +122,7 @@ namespace Acacia.Features.Signatures /// The signature hash. If null, the hash will not be checked and a hard sync will be done. private void SyncSignatures(ZPushAccount account, string serverSignatureHash) { - if (account == null || !account.Capabilities.Has("signatures")) + if (account?.Capabilities == null || !account.Capabilities.Has("signatures")) return; // Check hash if needed diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs index 9cbf51e..ee8702b 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs @@ -33,10 +33,22 @@ namespace Acacia.Native.MAPI public byte[] Unmarshal() { byte[] result = new byte[cb]; - Marshal.Copy((IntPtr)ptr, result, 0, result.Length); + System.Runtime.InteropServices.Marshal.Copy((IntPtr)ptr, result, 0, result.Length); return result; } + /// + /// Returns an instance with the data allocated in the enocder. + /// + public SBinary Marshal(NativeEncoder encoder) + { + return new SBinary() + { + cb = cb, + ptr = (byte*)encoder.Allocate(Unmarshal()) + }; + } + public override string ToString() { byte[] b = Unmarshal(); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs index ac60294..384c845 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using static Acacia.Native.NativeEncoder; namespace Acacia.Native.MAPI { @@ -52,7 +53,7 @@ namespace Acacia.Native.MAPI public SearchQuery.PropertyIdentifier ToPropertyIdentifier() { - return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)type); + return new SearchQuery.PropertyIdentifier(this); } public static PropTag FromInt(int v) @@ -65,80 +66,110 @@ namespace Acacia.Native.MAPI } } - - - [StructLayout(LayoutKind.Explicit)] - unsafe public struct PropValue + // TODO: align is probably wrong for 32-bit + [StructLayout(LayoutKind.Sequential)] + public struct PropValue { - [FieldOffset(0)] - public PropTag ulPropTag; + [StructLayout(LayoutKind.Sequential)] + public struct Header + { + public PropTag ulPropTag; + } - [FieldOffset(4)] - public uint dwAlignPad; + [StructLayout(LayoutKind.Explicit)] + unsafe public struct Data + { + // short int i; /* case PT_I2 */ + // LONG l; /* case PT_LONG */ + // ULONG ul; /* alias for PT_LONG */ + // LPVOID lpv; /* alias for PT_PTR */ + // float flt; /* case PT_R4 */ + // double dbl; /* case PT_DOUBLE */ + // unsigned short int b; /* case PT_BOOLEAN */ + [FieldOffset(0), MarshalAs(UnmanagedType.U2)] + public bool b; - // short int i; /* case PT_I2 */ - // LONG l; /* case PT_LONG */ - // ULONG ul; /* alias for PT_LONG */ - // LPVOID lpv; /* alias for PT_PTR */ - // float flt; /* case PT_R4 */ - // double dbl; /* case PT_DOUBLE */ - // unsigned short int b; /* case PT_BOOLEAN */ - [FieldOffset(8), MarshalAs(UnmanagedType.U2)] - public bool b; + // CURRENCY cur; /* case PT_CURRENCY */ + // double at; /* case PT_APPTIME */ + // FILETIME ft; /* case PT_SYSTIME */ - // CURRENCY cur; /* case PT_CURRENCY */ - // double at; /* case PT_APPTIME */ - // FILETIME ft; /* case PT_SYSTIME */ + // LPSTR lpszA; /* case PT_STRING8 */ + [FieldOffset(0), MarshalAs(UnmanagedType.LPStr)] + public sbyte* lpszA; - // LPSTR lpszA; /* case PT_STRING8 */ - [FieldOffset(8), MarshalAs(UnmanagedType.LPStr)] - public sbyte* lpszA; + // SBinary bin; /* case PT_BINARY */ + [FieldOffset(0)] + public SBinary bin; - // SBinary bin; /* case PT_BINARY */ - [FieldOffset(8)] - public SBinary bin; + // LPWSTR lpszW; /* case PT_UNICODE */ + [FieldOffset(0), MarshalAs(UnmanagedType.LPWStr)] + public char* lpszW; - // LPWSTR lpszW; /* case PT_UNICODE */ - [FieldOffset(8), MarshalAs(UnmanagedType.LPWStr)] - public char* lpszW; + // LPGUID lpguid; /* case PT_CLSID */ + // LARGE_INTEGER li; /* case PT_I8 */ + // SShortArray MVi; /* case PT_MV_I2 */ + // SLongArray MVl; /* case PT_MV_LONG */ + // SRealArray MVflt; /* case PT_MV_R4 */ + // SDoubleArray MVdbl; /* case PT_MV_DOUBLE */ + // SCurrencyArray MVcur; /* case PT_MV_CURRENCY */ + // SAppTimeArray MVat; /* case PT_MV_APPTIME */ + // SDateTimeArray MVft; /* case PT_MV_SYSTIME */ + // SBinaryArray MVbin; /* case PT_MV_BINARY */ + // SLPSTRArray MVszA; /* case PT_MV_STRING8 */ + // SWStringArray MVszW; /* case PT_MV_UNICODE */ - // LPGUID lpguid; /* case PT_CLSID */ - // LARGE_INTEGER li; /* case PT_I8 */ - // SShortArray MVi; /* case PT_MV_I2 */ - // SLongArray MVl; /* case PT_MV_LONG */ - // SRealArray MVflt; /* case PT_MV_R4 */ - // SDoubleArray MVdbl; /* case PT_MV_DOUBLE */ - // SCurrencyArray MVcur; /* case PT_MV_CURRENCY */ - // SAppTimeArray MVat; /* case PT_MV_APPTIME */ - // SDateTimeArray MVft; /* case PT_MV_SYSTIME */ - // SBinaryArray MVbin; /* case PT_MV_BINARY */ - // SLPSTRArray MVszA; /* case PT_MV_STRING8 */ - // SWStringArray MVszW; /* case PT_MV_UNICODE */ + // SGuidArray MVguid; /* case PT_MV_CLSID */ + // SLargeIntegerArray MVli; /* case PT_MV_I8 */ + // SCODE err; /* case PT_ERROR */ + // LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */ + } - // SGuidArray MVguid; /* case PT_MV_CLSID */ - // SLargeIntegerArray MVli; /* case PT_MV_I8 */ - // SCODE err; /* case PT_ERROR */ - // LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */ + public Header header; + public Data data; public override string ToString() { return ToObject()?.ToString() ?? ""; } - public object ToObject() + unsafe public object ToObject() { - switch (ulPropTag.type) + switch (header.ulPropTag.type) { case PropType.BOOLEAN: - return b; + return data.b; case PropType.STRING8: - return new string(lpszA); + return new string(data.lpszA); + case PropType.UNICODE: + return new string(data.lpszW); case PropType.BINARY: - return bin; - //case PropType.UNICODE: - // return lpszW.ToString(); + return data.bin; + } + throw new NotImplementedException(); + } + + unsafe public static IntPtr MarshalFromObject(NativeEncoder encoder, PropTag prop, object value) + { + PropValue obj = new PropValue(); + obj.header.ulPropTag = prop; + + switch (prop.type) + { + case PropType.BOOLEAN: + obj.data.b = (bool)value; + return encoder.Allocate(obj.header, obj.data.b); + case PropType.STRING8: + IntPtr ptrA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), new byte[] { 0 }); + return encoder.Allocate(obj.header, ptrA); + case PropType.UNICODE: + IntPtr ptrW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), new byte[] { 0, 0 }); + return encoder.Allocate(obj.header, ptrW); + case PropType.BINARY: + obj.data.bin = ((SBinary)value).Marshal(encoder); + return encoder.Allocate(obj.header, obj.data.bin); + default: + throw new NotImplementedException(); } - return null; } } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs index 6c48a3b..d17eabf 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs @@ -1,5 +1,6 @@  using Acacia.Stubs; +using Acacia.Utils; /// Copyright 2017 Kopano b.v. /// /// This program is free software: you can redistribute it and/or modify @@ -69,14 +70,14 @@ namespace Acacia.Native.MAPI unsafe public struct ContentRestriction { - public FuzzyLevel fuzzy; + public FuzzyLevel ulFuzzyLevel; public PropTag ulPropTag; public PropValue* prop; public string ToString(int depth) { string indent = new string(' ', depth); - string s = indent + fuzzy + ":" + ulPropTag.ToString(); + string s = indent + ulFuzzyLevel + ":" + ulPropTag.ToString(); s += ":" + prop->ToString(); s += "\n"; return s; @@ -85,9 +86,15 @@ namespace Acacia.Native.MAPI public SearchQuery ToSearchQuery() { return new SearchQuery.PropertyContent(ulPropTag.ToPropertyIdentifier(), - (uint)fuzzy, // TODO + (SearchQuery.ContentMatchOperation)((uint)ulFuzzyLevel & 0xF), + (SearchQuery.ContentMatchModifiers)(((uint)ulFuzzyLevel & 0xF0000) >> 16), prop->ToObject()); } + + public static FuzzyLevel FuzzyLevelFromSearchQuery(SearchQuery.PropertyContent search) + { + return (FuzzyLevel)((int)search.Operation | ((int)search.Modifiers << 16)); + } } // TODO: merge with ISearch @@ -174,7 +181,7 @@ namespace Acacia.Native.MAPI public SearchQuery ToSearchQuery() { - return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), bmr == BMR.EQZ, mask); + return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), (SearchQuery.BitMaskOperation)(int)bmr, mask); } } @@ -305,28 +312,153 @@ namespace Acacia.Native.MAPI } } - /* Example search code + /// + /// Encodes a search as an SRestriction. Note that as memory needs to be managed for the miscellaneous structures, + /// the SRestriction is only valid until RestrictionEncoder is disposed. + /// + unsafe public class RestrictionEncoder : NativeEncoder, ISearchEncoder { - MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders); - dynamic obj = folder.MAPIOBJECT; - IMAPIFolder imapi = obj as IMAPIFolder; + private class EncodingStack + { + public SRestriction[] array; + public int index; + public SRestriction* ptr; - //imapi.GetSearchCriteria(0, IntPtr.Zero, IntPtr.Zero, ref state); - GetSearchCriteriaState state; - //imapi.GetContentsTable(0, out p); - SBinaryArray* sb1; - SRestriction* restrict; - imapi.GetSearchCriteria(0, &restrict, &sb1, out state); - Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString()); + public EncodingStack(int count, Allocation alloc) + { + array = alloc.Object; + index = 0; + ptr = (SRestriction*)alloc.Pointer; + } + } + private readonly Stack _current = new Stack(); + private readonly EncodingStack _root; - restrict->rt = RestrictionType.AND; - imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE); + public RestrictionEncoder() + { + // Create an object for the root element + _root = Begin(1); + } + protected override void DoRelease() + { + base.DoRelease(); + } - //SBinaryArray sb = Marshal.PtrToStructure(p2); - //byte[][] ids = sb.Unmarshal(); - //Logger.Instance.Warning(this, "SEARCH: {0}", StringUtil.BytesToHex(ids[0])); - //imapi.GetLastError(0, 0, out p2); - //imapi.SaveChanges(SaveChangesFlags.FORCE_SAVE); - } */ + public SRestriction Restriction + { + get { return _root.array[0]; } + } + + private SRestriction* Current + { + get + { + EncodingStack top = _current.Peek(); + return top.ptr + top.index; + } + } + + public void Encode(SearchQuery.PropertyExists part) + { + Current->rt = RestrictionType.EXIST; + Current->exist.prop = part.Property.Tag; + } + + public void Encode(SearchQuery.Or part) + { + Current->rt = RestrictionType.OR; + Current->sub.cb = (uint)part.Operands.Count; + Current->sub.ptr = EncodePointer(part.Operands); + } + + public void Encode(SearchQuery.PropertyIdentifier part) + { + // This should be unreachable + throw new InvalidProgramException(); + } + + public void Encode(SearchQuery.Not part) + { + Current->rt = RestrictionType.NOT; + Current->not.ptr = EncodePointer(new[] { part.Operand }); + } + + public void Encode(SearchQuery.And part) + { + Current->rt = RestrictionType.AND; + Current->sub.cb = (uint)part.Operands.Count; + Current->sub.ptr = EncodePointer(part.Operands); + } + + private SRestriction* EncodePointer(IEnumerable operands) + { + EncodingStack alloc = Begin(operands.Count()); + try + { + foreach (SearchQuery operand in operands) + { + operand.Encode(this); + ++alloc.index; + } + } + finally + { + End(); + } + return alloc.ptr; + } + + private EncodingStack Begin(int count) + { + // Allocate and push the array + EncodingStack alloc = new EncodingStack(count, Allocate(new SRestriction[count])); + _current.Push(alloc); + + return alloc; + } + + private void End() + { + _current.Pop(); + } + + public void Encode(SearchQuery.PropertyContent part) + { + Current->rt = RestrictionType.CONTENT; + Current->content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part); + Current->content.ulPropTag = part.Property.Tag; + Current->content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content); + } + + public void Encode(SearchQuery.PropertyCompare part) + { + Current->rt = RestrictionType.PROPERTY; + Current->prop.relop = (SearchOperation)part.Operation; + Current->prop.ulPropTag = part.Property.Tag; + Current->prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value); + } + + public void Encode(SearchQuery.PropertyBitMask part) + { + Current->rt = RestrictionType.BITMASK; + Current->bitMask.bmr = (BMR)(int)part.Operation; + Current->bitMask.prop = part.Property.Tag; + Current->bitMask.mask = part.Mask; + } + } + + public static class RestrictionExensions + { + /// + /// Encodes the search as an SRestriction. + /// + /// The encoder containing the restriction. The caller is responsible for disposing. + public static RestrictionEncoder ToRestriction(this SearchQuery search) + { + RestrictionEncoder encoder = new RestrictionEncoder(); + search.Encode(encoder); + return encoder; + } + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs index d6e73cc..cf38e2a 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Acacia.Native.MAPI; namespace Acacia.Native { @@ -13,7 +14,7 @@ namespace Acacia.Native /// abstract public class NativeEncoder : DisposableWrapper { - abstract protected class AllocationBase + protected class AllocationBase : IDisposable { protected readonly object _obj; protected readonly GCHandle _handle; @@ -31,15 +32,24 @@ namespace Acacia.Native _ptr = _handle.AddrOfPinnedObject(); } - // TODO: release + public IntPtr Pointer { get { return _ptr; } } + + public void Dispose() + { + if (_handle.IsAllocated) + _handle.Free(); + else + Marshal.FreeHGlobal(_ptr); + } } + unsafe protected class Allocation : AllocationBase { - public Allocation(int size) : base(typeof(ObjType), size) + internal Allocation(int size) : base(typeof(ObjType), size) { } - public Allocation(ObjType obj) : base(obj) + internal Allocation(ObjType obj) : base(obj) { } @@ -50,26 +60,21 @@ namespace Acacia.Native return (ObjType)_obj; } } - - public IntPtr Pointer - { - get - { - return _ptr; - } - } } private readonly List _allocs = new List(); - /// - /// Allocates an object of the specified type. The allocation is managed by this encoder. - /// - /// If larger than 0, the size to allocate. Otherwise, the size of the object is used. - /// The allocated object. - protected Allocation Allocate(int size = -1) + override protected void DoRelease() { - throw new NotImplementedException(); + foreach(AllocationBase alloc in _allocs) + alloc.Dispose(); + } + + protected AllocationBase Allocate(int size) + { + AllocationBase alloc = new AllocationBase(typeof(object), size); + _allocs.Add(alloc); + return alloc; } protected Allocation Allocate(ObjType obj) @@ -78,5 +83,80 @@ namespace Acacia.Native _allocs.Add(alloc); return alloc; } + + protected Allocation Allocate() + { + return Allocate(Activator.CreateInstance()); + } + + // TODO: put in lib + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + public IntPtr Allocate(ElementType[] obj, params ElementType[][] additional) + { + ElementType[][] all = new ElementType[][] { obj }.Concat(additional).ToArray(); + + int size = 0; + int[] starts = new int[all.Length]; + int[] sizes = new int[all.Length]; + for (int i = 0; i < all.Length; ++i) + { + starts[i] = size; + int thisSize = ((Array)all[i]).Length * Marshal.SizeOf(); + sizes[i] = thisSize; + size += thisSize; + } + + AllocationBase alloc = Allocate(size); + IntPtr ptr = alloc.Pointer; + for (int i = 0; i < all.Length; ++i) + { + GCHandle handle = GCHandle.Alloc(all[i], GCHandleType.Pinned); + try + { + CopyMemory(ptr + starts[i], handle.AddrOfPinnedObject(), (uint)sizes[i]); + } + finally + { + handle.Free(); + } + } + return alloc.Pointer; + } + + /// + /// Returns a block of memory containing all specified objects sequentially. + /// + public IntPtr Allocate(object obj, params object[] additional) + { + object[] all = new object[] { obj }.Concat(additional).ToArray(); + + int size = 0; + int[] starts = new int[all.Length]; + for (int i = 0; i < all.Length; ++i) + { + starts[i] = Align(size); + int thisSize = Marshal.SizeOf(all[i]); + size = starts[i] + thisSize; + } + + AllocationBase alloc = Allocate(size); + IntPtr ptr = alloc.Pointer; + for (int i = 0; i < all.Length; ++i) + { + Marshal.StructureToPtr(all[i], ptr + starts[i], false); + } + return alloc.Pointer; + } + + + + private int Align(int size) + { + int align = Marshal.SizeOf(); + int additional = (align - (size % align)) % align; + return size + additional; + } } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs index c010631..45e9f5d 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs @@ -32,6 +32,7 @@ namespace Acacia void Encode(SearchQuery.And part); void Encode(SearchQuery.Or part); void Encode(SearchQuery.Not part); + void Encode(SearchQuery.PropertyIdentifier part); } public class ToStringEncoder : ISearchEncoder @@ -46,21 +47,20 @@ namespace Acacia public void Encode(SearchQuery.And part) { - EncodeMulti(part, "AND"); + EncodeMulti("AND", part.Operands); } public void Encode(SearchQuery.Or part) { - EncodeMulti(part, "OR"); + EncodeMulti("OR", part.Operands); } public void Encode(SearchQuery.Not part) { - _builder.Append("NOT "); - part.Operand.Encode(this); + EncodeMulti("NOT", new[] { part.Operand }); } - private void EncodeMulti(SearchQuery.MultiOperator part, string oper) + private void EncodeMulti(string oper, IEnumerable parts) { Indent(); _builder.Append(oper).Append("\n"); @@ -69,7 +69,7 @@ namespace Acacia ++_indent; - foreach (SearchQuery operand in part.Operands) + foreach (SearchQuery operand in parts) operand.Encode(this); --_indent; @@ -80,22 +80,59 @@ namespace Acacia public void Encode(SearchQuery.PropertyBitMask part) { - _builder.Append("BITMASK:").Append(part.Property); // TODO: operator/value + Indent(); + _builder.Append("BITMASK{"); + part.Property.Encode(this); + _builder.Append(" ").Append(part.Operation).Append(" "); + _builder.Append(part.Mask.ToString("X8")); + _builder.Append("}\n"); } + private static readonly string[] COMPARISON_OPERATORS = {"<", "<=", ">", ">=", "==", "!=", "LIKE"}; + public void Encode(SearchQuery.PropertyCompare part) { - _builder.Append("COMPARE:").Append(part.Property); // TODO: operator/value + Indent(); + _builder.Append("COMPARE{"); + part.Property.Encode(this); + _builder.Append(" ").Append(COMPARISON_OPERATORS[(int)part.Operation]).Append(" "); + _builder.Append(part.Value); + _builder.Append("}\n"); } public void Encode(SearchQuery.PropertyContent part) { - _builder.Append("CONTENT:").Append(part.Property); // TODO: operator/value + Indent(); + _builder.Append("CONTENT{"); + part.Property.Encode(this); + + List options = new List(); + if (part.Operation != SearchQuery.ContentMatchOperation.Full) + options.Add(part.Operation.ToString()); + + if (part.Modifiers != SearchQuery.ContentMatchModifiers.None) + { + options.Add(part.Modifiers.ToString()); + } + + string optionsString = options.Count == 0 ? "" : ("(" + string.Join(",", options) + ")"); + + _builder.Append(" ==").Append(optionsString).Append(" "); + _builder.Append(part.Content); + _builder.Append("}\n"); } public void Encode(SearchQuery.PropertyExists part) { - _builder.Append("EXISTS:").Append(part.Property); + Indent(); + _builder.Append("EXISTS{"); + part.Property.Encode(this); + _builder.Append("}\n"); + } + + public void Encode(SearchQuery.PropertyIdentifier part) + { + _builder.Append(part.Id); } public string GetValue() @@ -133,7 +170,7 @@ namespace Acacia _operands.Add(operand); } - public IEnumerable Operands + public ICollection Operands { get { return _operands; } } @@ -180,16 +217,23 @@ namespace Acacia /// public class PropertyIdentifier { - private string _id; + public string Id { get; private set; } + public PropTag Tag { get; private set; } - public PropertyIdentifier(string id) + public PropertyIdentifier(PropTag tag) { - this._id = id; + this.Tag = tag; + Id = string.Format("{0:X4}{1:X4}", tag.prop, (int)tag.type); } - public static PropertyIdentifier FromTag(ushort prop, ushort type) + public void Encode(ISearchEncoder encoder) { - return new PropertyIdentifier(string.Format("{0:4X}{1:4X}", prop, type)); + encoder.Encode(this); + } + + public override string ToString() + { + return Id; } } @@ -247,11 +291,44 @@ namespace Acacia } } + public enum ContentMatchOperation + { + /// + /// Match full content + /// + Full, + + /// + /// Match part of the content + /// + SubString, + + /// + /// Match the start of the content + /// + Prefix + } + + [Flags] + public enum ContentMatchModifiers + { + None = 0, + CaseInsensitive = 1, + IgnoreNonSpace = 2, + Loose = 4 + } + public class PropertyContent : PropertyQuery { - public PropertyContent(PropertyIdentifier property, uint options, object content) : base(property) + public ContentMatchOperation Operation { get; set; } + public ContentMatchModifiers Modifiers { get; set; } + public object Content { get; set; } + + public PropertyContent(PropertyIdentifier property, ContentMatchOperation operation, ContentMatchModifiers modifiers, object content) : base(property) { - // TODO + this.Operation = operation; + this.Modifiers = modifiers; + this.Content = content; } public override void Encode(ISearchEncoder encoder) @@ -260,11 +337,20 @@ namespace Acacia } } + public enum BitMaskOperation + { + EQZ, NEZ + } + public class PropertyBitMask : PropertyQuery { - public PropertyBitMask(PropertyIdentifier property, bool wantZero, uint mask) : base(property) + public BitMaskOperation Operation { get; set; } + public uint Mask { get; set; } + + public PropertyBitMask(PropertyIdentifier property, BitMaskOperation operation, uint mask) : base(property) { - // TODO + this.Operation = operation; + this.Mask = mask; } public override void Encode(ISearchEncoder encoder) diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs index ff8499f..af92cbd 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs @@ -403,7 +403,6 @@ namespace Acacia.Stubs.OutlookWrappers SBinaryArray* sb1; SRestriction* restrict; imapi.GetSearchCriteria(0, &restrict, &sb1, out state); - Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString()); return restrict->ToSearchQuery(); } finally @@ -414,8 +413,19 @@ namespace Acacia.Stubs.OutlookWrappers set { - // TODO - throw new NotImplementedException(); + IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder; + try + { + using (RestrictionEncoder res = value.ToRestriction()) + { + SRestriction restrict = res.Restriction; + imapi.SetSearchCriteria(&restrict, null, SearchCriteriaFlags.NONE); + } + } + finally + { + ComRelease.Release(imapi); + } } } }