Partial implementation of raw MAPI access.

This commit is contained in:
Patrick Simpson 2017-02-22 10:20:20 +01:00
parent d1eaaba84e
commit f5ea05189c
15 changed files with 1068 additions and 489 deletions

View File

@ -276,9 +276,15 @@
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
<Compile Include="GlobalOptions.cs" />
<Compile Include="Logging.cs" />
<Compile Include="Native\MAPI.cs" />
<Compile Include="Native\IOleWindow.cs" />
<Compile Include="Native\MAPI\Binary.cs" />
<Compile Include="Native\MAPI\IMAPIContainer.cs" />
<Compile Include="Native\MAPI\IMAPIFolder.cs" />
<Compile Include="Native\MAPI\IMAPIProp.cs" />
<Compile Include="Native\MAPI\Property.cs" />
<Compile Include="Native\MAPI\Restriction.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="SearchQuery.cs" />
<Compile Include="Stubs\Enums.cs" />
<Compile Include="Stubs\IAccount.cs" />
<Compile Include="Stubs\IAddIn.cs" />

View File

@ -1,486 +0,0 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
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 "<unknown>";
}
}
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<SBinaryArray>(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);
} */
}

View File

@ -0,0 +1,62 @@

using Acacia.Utils;
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
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;
}
}
}

View File

@ -0,0 +1,74 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
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 SearchCriteriaState : UInt32
{
NONE = 0,
SEARCH_RUNNING = 1,
SEARCH_REBUILD = 2,
SEARCH_RECURSIVE = 4,
SEARCH_FOREGROUND = 8
}
[Flags]
public enum SearchCriteriaFlags : 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 : IMAPIProp
{
// IMAPIProp
new void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
new void SaveChanges(SaveChangesFlags flags);
new void GetProps();
new void GetPropList();
new void OpenProperty();
new void SetProps();
new void DeleteProps();
new void CopyTo();
new void CopyProps();
new void GetNamesFromIDs();
new void GetIDsFromNames();
// IMAPIContainer
void GetContentsTable(UInt32 flags, out IntPtr table);
void GetHierarchyTable();
void OpenEntry();
void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SearchCriteriaFlags flags);
void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out SearchCriteriaState state);
}
}

View File

@ -0,0 +1,64 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
[ComImport]
[Guid("0002030C-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe public interface IMAPIFolder : IMAPIContainer
{
// IMAPIProp
new void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
new void SaveChanges(SaveChangesFlags flags);
new void GetProps();
new void GetPropList();
new void OpenProperty();
new void SetProps();
new void DeleteProps();
new void CopyTo();
new void CopyProps();
new void GetNamesFromIDs();
new void GetIDsFromNames();
// IMAPIContainer
new void GetContentsTable(UInt32 flags, out IntPtr table);
new void GetHierarchyTable();
new void OpenEntry();
new void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SearchCriteriaFlags flags);
new void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out SearchCriteriaState state);
// IMAPIFolder
void CreateMessage();
void CopyMessages();
void DeleteMessages();
void CreateFolder();
void CopyFolder();
void DeleteFolder();
void SetReadFlags();
void GetMessageStatus();
void SetMessageStatus();
void SaveContentsSort();
void EmptyFolder();
}
}

View File

@ -0,0 +1,53 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
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();
}
}

View File

@ -0,0 +1,135 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
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 + ">";
}
public SearchQuery.PropertyIdentifier ToPropertyIdentifier()
{
return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)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()
{
return ToObject()?.ToString() ?? "<unknown>";
}
public object ToObject()
{
switch (ulPropTag.type)
{
case PropType.BOOLEAN:
return b;
case PropType.STRING8:
return new string(lpszA);
case PropType.BINARY:
return bin;
//case PropType.UNICODE:
// return lpszW.ToString();
}
return null;
}
}
}

View File

@ -0,0 +1,331 @@

using Acacia.Stubs;
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
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;
}
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyCompare(ulPropTag.ToPropertyIdentifier(),
(SearchQuery.ComparisonOperation)(int)relop,
prop->ToObject());
}
}
[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;
}
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyContent(ulPropTag.ToPropertyIdentifier(),
(uint)fuzzy, // TODO
prop->ToObject());
}
}
// 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;
}
public SearchQuery ToSearchQuery(bool and)
{
SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or(); ;
for (uint i = 0; i < cb; ++i)
{
oper.Add(ptr[i].ToSearchQuery());
}
return oper;
}
}
unsafe public struct NotRestriction
{
public uint dwReserved;
public SRestriction* ptr;
public string ToString(int depth)
{
return ptr->ToString(depth);
}
public SearchQuery ToSearchQuery()
{
return new SearchQuery.Not(ptr->ToSearchQuery());
}
}
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";
}
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), bmr == BMR.EQZ, mask);
}
}
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";
}
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyExists(prop.ToPropertyIdentifier());
}
}
[StructLayout(LayoutKind.Explicit)]
unsafe public struct SRestriction
{
[FieldOffset(0)]
public RestrictionType rt;
// And/Or
[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 SearchQuery ToSearchQuery()
{
switch (rt)
{
case RestrictionType.AND:
return sub.ToSearchQuery(true);
case RestrictionType.OR:
return sub.ToSearchQuery(false);
case RestrictionType.NOT:
return not.ToSearchQuery();
case RestrictionType.CONTENT:
return content.ToSearchQuery();
case RestrictionType.PROPERTY:
return prop.ToSearchQuery();
case RestrictionType.BITMASK:
return bitMask.ToSearchQuery();
case RestrictionType.EXIST:
return exist.ToSearchQuery();
/* TODO COMPAREPROPS,
BITMASK,
SIZE,
SUBRESTRICTION,
COMMENT,
COUNT,
ANNOTATION*/
}
return null;
}
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;
}
}
/* 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<SBinaryArray>(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);
} */
}

View File

@ -0,0 +1,278 @@
/// Copyright 2017 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,
/// 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acacia.Native.MAPI;
namespace Acacia
{
public interface ISearchEncoder
{
void Encode(SearchQuery.PropertyBitMask part);
void Encode(SearchQuery.PropertyCompare part);
void Encode(SearchQuery.PropertyContent part);
void Encode(SearchQuery.PropertyExists part);
void Encode(SearchQuery.And part);
void Encode(SearchQuery.Or part);
void Encode(SearchQuery.Not part);
}
public class ToStringEncoder : ISearchEncoder
{
private readonly StringBuilder _builder = new StringBuilder();
private int _indent;
private void Indent()
{
_builder.Append(new String(' ', _indent));
}
public void Encode(SearchQuery.And part)
{
EncodeMulti(part, "AND");
}
public void Encode(SearchQuery.Or part)
{
EncodeMulti(part, "OR");
}
public void Encode(SearchQuery.Not part)
{
_builder.Append("NOT ");
part.Operand.Encode(this);
}
private void EncodeMulti(SearchQuery.MultiOperator part, string oper)
{
Indent();
_builder.Append(oper).Append("\n");
Indent();
_builder.Append("{\n");
++_indent;
foreach (SearchQuery operand in part.Operands)
operand.Encode(this);
--_indent;
Indent();
_builder.Append("}\n");
}
public void Encode(SearchQuery.PropertyBitMask part)
{
_builder.Append("BITMASK:").Append(part.Property); // TODO: operator/value
}
public void Encode(SearchQuery.PropertyCompare part)
{
_builder.Append("COMPARE:").Append(part.Property); // TODO: operator/value
}
public void Encode(SearchQuery.PropertyContent part)
{
_builder.Append("CONTENT:").Append(part.Property); // TODO: operator/value
}
public void Encode(SearchQuery.PropertyExists part)
{
_builder.Append("EXISTS:").Append(part.Property);
}
public string GetValue()
{
return _builder.ToString();
}
}
abstract public class SearchQuery
{
#region Interface
override public string ToString()
{
ToStringEncoder encoder = new ToStringEncoder();
Encode(encoder);
return encoder.GetValue();
}
abstract public void Encode(ISearchEncoder encoder);
#endregion
#region Implementations
abstract public class MultiOperator : SearchQuery
{
private readonly List<SearchQuery> _operands = new List<SearchQuery>();
public void Add(SearchQuery operand)
{
if (operand == null)
throw new ArgumentNullException();
_operands.Add(operand);
}
public IEnumerable<SearchQuery> Operands
{
get { return _operands; }
}
}
public class And : MultiOperator
{
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
public class Or : MultiOperator
{
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
public class Not : SearchQuery
{
public SearchQuery Operand
{
get;
set;
}
public Not(SearchQuery operand)
{
this.Operand = operand;
}
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
/// <summary>
/// TODO: this is globally useful
/// </summary>
public class PropertyIdentifier
{
private string _id;
public PropertyIdentifier(string id)
{
this._id = id;
}
public static PropertyIdentifier FromTag(ushort prop, ushort type)
{
return new PropertyIdentifier(string.Format("{0:4X}{1:4X}", prop, type));
}
}
abstract public class PropertyQuery : SearchQuery
{
public PropertyIdentifier Property { get; set; }
protected PropertyQuery(PropertyIdentifier property)
{
this.Property = property;
}
}
/// <summary>
/// Order matches MAPI RELOP_ constants
/// </summary>
public enum ComparisonOperation : uint
{
Smaller,
SmallerEqual,
Greater,
GreaterEqual,
Equal,
NotEqual,
Like
}
public class PropertyCompare : PropertyQuery
{
public ComparisonOperation Operation { get; set; }
public object Value { get; set; }
public PropertyCompare(PropertyIdentifier property, ComparisonOperation operation, object value) : base(property)
{
this.Operation = operation;
this.Value = value;
}
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
public class PropertyExists : PropertyQuery
{
public PropertyExists(PropertyIdentifier property) : base(property)
{
}
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
public class PropertyContent : PropertyQuery
{
public PropertyContent(PropertyIdentifier property, uint options, object content) : base(property)
{
// TODO
}
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
public class PropertyBitMask : PropertyQuery
{
public PropertyBitMask(PropertyIdentifier property, bool wantZero, uint mask) : base(property)
{
// TODO
}
public override void Encode(ISearchEncoder encoder)
{
encoder.Encode(this);
}
}
#endregion
}
}

View File

@ -62,6 +62,13 @@ namespace Acacia.Stubs
SuggestedContacts = 30
}
// Replacement for OlSpecialFolders
public enum SpecialFolder
{
AllTasks = 0,
Reminders = 1
}
public enum AccountType
{
// TODO

View File

@ -114,5 +114,11 @@ namespace Acacia.Stubs
ZPushFolder ZPush { get; set; }
IFolder Clone();
SearchQuery SearchCriteria
{
get;
set;
}
}
}

View File

@ -53,10 +53,14 @@ namespace Acacia.Stubs
ISearchField AddField(string name, bool isUserField = false);
}
public interface ISearch<ItemType> : ISearchOperator, IDisposable
where ItemType : IItem
public interface ISearchQuery : ISearchOperator
{
ISearchOperator AddOperator(SearchOperator oper);
}
public interface ISearch<ItemType> : ISearchQuery, IDisposable
where ItemType : IItem
{
IEnumerable<ItemType> Search(int maxResults = int.MaxValue);

View File

@ -40,6 +40,12 @@ namespace Acacia.Stubs
/// </summary>
string GetDefaultFolderId(DefaultFolder folder);
/// <summary>
/// Returns a special folder.
/// </summary>
/// <returns>The special folder. The caller is responsible for disposing.</returns>
IFolder GetSpecialFolder(SpecialFolder folder);
IItem GetItemFromID(string id);
string DisplayName { get; }
string StoreID { get; }

View File

@ -23,6 +23,7 @@ using System.Collections;
using Acacia.Utils;
using Acacia.ZPush;
using NSOutlook = Microsoft.Office.Interop.Outlook;
using Acacia.Native.MAPI;
namespace Acacia.Stubs.OutlookWrappers
{
@ -371,5 +372,31 @@ namespace Acacia.Stubs.OutlookWrappers
get;
set;
}
unsafe public SearchQuery SearchCriteria
{
get
{
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
try
{
SearchCriteriaState state;
SBinaryArray* sb1;
SRestriction* restrict;
imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString());
return restrict->ToSearchQuery();
}
finally
{
ComRelease.Release(imapi);
}
}
set
{
}
}
}
}

View File

@ -67,6 +67,18 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
public IFolder GetSpecialFolder(SpecialFolder folder)
{
try
{
return _item.GetSpecialFolder((NSOutlook.OlSpecialFolders)(int)folder).Wrap();
}
catch (Exception)
{
return null;
}
}
public IItem GetItemFromID(string id)
{
using (ComRelease com = new ComRelease())