From 87f3534a897719167d3c3f8f26150c169f443adc Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Tue, 7 Feb 2017 09:02:35 +0100
Subject: [PATCH] [KOE-12] Rough draft of MAPI interfaces for SearchCriteria
access.
---
.../AcaciaZPushPlugin.csproj | 1 +
.../Features/WebApp/FeatureWebApp.cs | 15 +-
.../AcaciaZPushPlugin/Native/MAPI.cs | 470 ++++++++++++++++++
.../AcaciaZPushPlugin/Stubs/ISearch.cs | 19 +-
4 files changed, 486 insertions(+), 19 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index 831ef3d..27ee2cd 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -276,6 +276,7 @@
+
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs
index 4b837ba..1be82ae 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs
@@ -1,4 +1,6 @@
-/// Copyright 2016 Kopano b.v.
+
+using Acacia.Native.MAPI;
+/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@@ -13,7 +15,6 @@
/// along with this program.If not, see.
///
/// Consult LICENSE file for details
-
using Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.Utils;
@@ -73,14 +74,6 @@ namespace Acacia.Features.WebApp
}
}
- private void Check_AutoDiscover(ZPushAccount account)
- {
- AutoDiscover(account);
-
- // Update button state
- AccountChange(account);
- }
-
private void OpenWebApp()
{
ZPushAccount account = Watcher.CurrentZPushAccount();
@@ -113,7 +106,7 @@ namespace Acacia.Features.WebApp
account.SetFeatureData(this, TXT_KDISCOVER, new URLCached(url));
return url;
}
- catch (Exception e)
+ catch (System.Exception e)
{
Logger.Instance.Warning(this, "Exception during kdiscover: {0}: {1}", account.DomainName, e);
account.SetFeatureData(this, TXT_KDISCOVER, null);
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
new file mode 100644
index 0000000..439f7c0
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
@@ -0,0 +1,470 @@
+using Acacia.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+ [Flags]
+ public enum SaveChangesFlags : UInt32
+ {
+ NONE = 0,
+ KEEP_OPEN_READONLY = 1,
+ KEEP_OPEN_READWRITE = 2,
+ FORCE_SAVE = 4,
+ MAPI_DEFERRED_ERRORS = 8
+ }
+
+ [ComImport]
+ [Guid("00020303-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IMAPIProp
+ {
+ void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
+ void SaveChanges(SaveChangesFlags flags);
+ void GetProps();
+ void GetPropList();
+ void OpenProperty();
+ void SetProps();
+ void DeleteProps();
+ void CopyTo();
+ void CopyProps();
+ void GetNamesFromIDs();
+ void GetIDsFromNames();
+ }
+
+ unsafe public struct SBinary
+ {
+ public uint cb;
+ public byte* ptr;
+
+ public byte[] Unmarshal()
+ {
+ byte[] result = new byte[cb];
+ Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
+ return result;
+ }
+
+ public override string ToString()
+ {
+ byte[] b = Unmarshal();
+ return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
+ }
+ }
+
+ unsafe public struct SBinaryArray
+ {
+ public uint count;
+ public SBinary* ptr;
+
+ public byte[][] Unmarshal()
+ {
+ byte[][] result = new byte[count][];
+ for (uint i = 0; i < count; ++i)
+ {
+ result[i] = ptr[i].Unmarshal();
+ }
+ return result;
+ }
+ }
+
+ public enum PropType : ushort
+ {
+ BOOLEAN = 0x000B,
+ BINARY = 0x0102,
+ MV_BINARY = 1102,
+ DOUBLE = 0x0005,
+ LONG = 0x0003,
+ OBJECT = 0x000D,
+ STRING8 = 0x001E,
+ MV_STRING8 = 0x101E,
+ SYSTIME = 0x0040,
+ UNICODE = 0x001F,
+ MV_UNICODE = 0x101f
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct PropTag
+ {
+ public PropType type;
+ public ushort prop;
+
+ public override string ToString()
+ {
+ return "<" + prop.ToString("X4") + ":" + type + ">";
+ }
+ }
+
+
+
+ [StructLayout(LayoutKind.Explicit)]
+ unsafe public struct PropValue
+ {
+ [FieldOffset(0)]
+ public PropTag ulPropTag;
+
+ [FieldOffset(4)]
+ public uint dwAlignPad;
+
+ // 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 */
+
+ // LPSTR lpszA; /* case PT_STRING8 */
+ [FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
+ public sbyte* lpszA;
+
+ // SBinary bin; /* case PT_BINARY */
+ [FieldOffset(8)]
+ public SBinary bin;
+
+ // 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 */
+
+ // 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 override string ToString()
+ {
+ switch(ulPropTag.type)
+ {
+ case PropType.BOOLEAN:
+ return b.ToString();
+ case PropType.STRING8:
+ return new string(lpszA);
+ case PropType.BINARY:
+ return bin.ToString();
+ //case PropType.UNICODE:
+ // return lpszW.ToString();
+ }
+ return "";
+ }
+ }
+
+ unsafe public struct CommentRestriction
+ {
+ public uint cValues;
+ public SRestriction* res;
+ public PropValue* prop;
+ }
+
+ unsafe public struct PropertyRestriction
+ {
+ public Acacia.Stubs.SearchOperation relop;
+ public PropTag ulPropTag;
+ public PropValue* prop;
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + relop + ":" + ulPropTag.ToString();
+ s += ":" + prop->ToString();
+ s += "\n";
+ return s;
+ }
+ }
+
+ [Flags]
+ public enum FuzzyLevel : uint
+ {
+ FULLSTRING = 0,
+ SUBSTRING = 1,
+ PREFIX = 2,
+
+ IGNORECASE = 0x00010000,
+ IGNORENONSPACE = 0x00020000,
+ LOOSE = 0x00040000
+ }
+
+ unsafe public struct ContentRestriction
+ {
+ public FuzzyLevel fuzzy;
+ public PropTag ulPropTag;
+ public PropValue* prop;
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + fuzzy + ":" + ulPropTag.ToString();
+ s += ":" + prop->ToString();
+ s += "\n";
+ return s;
+ }
+ }
+
+ // TODO: merge with ISearch
+ public enum RestrictionType : UInt32
+ {
+ AND,
+ OR,
+ NOT,
+ CONTENT,
+ PROPERTY,
+ COMPAREPROPS,
+ BITMASK,
+ SIZE,
+ EXIST,
+ SUBRESTRICTION,
+ COMMENT,
+ COUNT,
+ ANNOTATION
+ }
+
+ unsafe public struct SubRestriction
+ {
+ public uint cb;
+ public SRestriction* ptr;
+
+ public string ToString(int depth)
+ {
+ string s = "";
+ for (uint i = 0; i < cb; ++i)
+ {
+ s += ptr[i].ToString(depth);
+ }
+ return s;
+ }
+ }
+
+ unsafe public struct NotRestriction
+ {
+ public uint dwReserved;
+ public SRestriction* ptr;
+
+ public string ToString(int depth)
+ {
+ return ptr->ToString(depth);
+ }
+ }
+
+ public enum BMR : uint
+ {
+ EQZ = 0,
+ NEZ = 1
+ }
+
+ unsafe public struct BitMaskRestriction
+ {
+ public BMR bmr;
+ public PropTag prop;
+ public uint mask;
+
+ override public string ToString()
+ {
+ return bmr.ToString() + ":" + prop + mask.ToString("X8");
+ }
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ return indent + ToString() + "\n";
+ }
+ }
+
+ unsafe public struct ExistRestriction
+ {
+ public uint dwReserved1;
+ public PropTag prop;
+ public uint dwReserved2;
+
+ override public string ToString()
+ {
+ return prop.ToString();
+ }
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ return indent + prop.ToString() + "\n";
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ unsafe public struct SRestriction
+ {
+ [FieldOffset(0)]
+ public RestrictionType rt;
+
+ [FieldOffset(8)]
+ public SubRestriction sub;
+
+ [FieldOffset(8)]
+ public NotRestriction not;
+
+ [FieldOffset(8)]
+ public ContentRestriction content;
+
+ [FieldOffset(8)]
+ public PropertyRestriction prop;
+
+ [FieldOffset(8)]
+ public BitMaskRestriction bitMask;
+
+ [FieldOffset(8)]
+ public ExistRestriction exist;
+
+ [FieldOffset(8)]
+ public CommentRestriction comment;
+
+ public override string ToString()
+ {
+ return ToString(0);
+ }
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + rt.ToString() + "\n" + indent + "{\n";
+ switch(rt)
+ {
+ case RestrictionType.AND:
+ case RestrictionType.OR:
+ s += sub.ToString(depth + 1);
+ break;
+ case RestrictionType.NOT:
+ s += not.ToString(depth + 1);
+ break;
+ case RestrictionType.CONTENT:
+ s += content.ToString(depth + 1);
+ break;
+ case RestrictionType.PROPERTY:
+ s += prop.ToString(depth + 1);
+ break;
+ case RestrictionType.BITMASK:
+ s += bitMask.ToString(depth + 1);
+ break;
+ case RestrictionType.EXIST:
+ s += exist.ToString(depth + 1);
+ break;
+
+ /* TODO COMPAREPROPS,
+ BITMASK,
+ SIZE,
+ SUBRESTRICTION,
+ COMMENT,
+ COUNT,
+ ANNOTATION*/
+
+ }
+ s += indent + "}\n";
+ return s;
+ }
+ }
+
+ [Flags]
+ public enum GetSearchCriteriaState : UInt32
+ {
+ NONE = 0,
+ SEARCH_RUNNING = 1,
+ SEARCH_REBUILD = 2,
+ SEARCH_RECURSIVE = 4,
+ SEARCH_FOREGROUND = 8
+ }
+
+ [Flags]
+ public enum SetSearchCriteriaFlags : UInt32
+ {
+ NONE = 0,
+ STOP_SEARCH = 0x00000001,
+ RESTART_SEARCH = 0x00000002,
+ RECURSIVE_SEARCH = 0x00000004,
+ SHALLOW_SEARCH = 0x00000008,
+ FOREGROUND_SEARCH = 0x00000010,
+ BACKGROUND_SEARCH = 0x00000020,
+ }
+
+ [ComImport]
+ [Guid("0002030B-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ unsafe public interface IMAPIContainer// TODO : IMAPIProp
+ {
+ // IMAPIProp
+ void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
+ void SaveChanges(SaveChangesFlags flags);
+ void GetProps();
+ void GetPropList();
+ void OpenProperty();
+ void SetProps();
+ void DeleteProps();
+ void CopyTo();
+ void CopyProps();
+ void GetNamesFromIDs();
+ void GetIDsFromNames();
+
+ void GetContentsTable(UInt32 flags, out IntPtr table);
+ void GetHierarchyTable();
+ void OpenEntry();
+ void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SetSearchCriteriaFlags flags);
+ void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out GetSearchCriteriaState state);
+ }
+
+ [ComImport]
+ [Guid("0002030C-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IMAPIFolder : IMAPIContainer
+ {
+ void CreateMessage();
+ void CopyMessages();
+ void DeleteMessages();
+ void CreateFolder();
+ void CopyFolder();
+ void DeleteFolder();
+ void SetReadFlags();
+ void GetMessageStatus();
+ void SetMessageStatus();
+ void SaveContentsSort();
+ void EmptyFolder();
+ }
+
+ /* Example search code
+ {
+ MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders);
+ dynamic obj = folder.MAPIOBJECT;
+ IMAPIFolder imapi = obj as IMAPIFolder;
+
+ //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());
+
+ restrict->rt = RestrictionType.AND;
+ imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE);
+
+
+ //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);
+ } */
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
index 13335ac..18d5aef 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
@@ -22,14 +22,17 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
- public enum SearchOperation
+ ///
+ /// Order matches MAPI RELOP_ constants
+ ///
+ public enum SearchOperation : uint
{
+ Smaller,
+ SmallerEqual,
+ Greater,
+ GreaterEqual,
Equal,
NotEqual,
- SmallerEqual,
- Smaller,
- GreaterEqual,
- Greater,
Like
}
@@ -41,7 +44,8 @@ namespace Acacia.Stubs
public enum SearchOperator
{
Or,
- And
+ And,
+ Not
}
public interface ISearchOperator
@@ -49,8 +53,7 @@ namespace Acacia.Stubs
ISearchField AddField(string name, bool isUserField = false);
}
- public interface ISearch
- : ISearchOperator
+ public interface ISearch : ISearchOperator
where ItemType : IItem
{
ISearchOperator AddOperator(SearchOperator oper);