32c83f9a34d3e1064bf7567fbd7d87d9d30d2f8a
[gpgol.git] / src / oomhelp.cpp
1 /* oomhelp.cpp - Helper functions for the Outlook Object Model
2  *      Copyright (C) 2009 g10 Code GmbH
3  *      Copyright (C) 2015 Intevation GmbH
4  * 
5  * This file is part of GpgOL.
6  * 
7  * GpgOL is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  * 
12  * GpgOL is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  * 
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <windows.h>
26 #include <olectl.h>
27 #include <string>
28
29 #include "myexchext.h"
30 #include "common.h"
31
32 #include "oomhelp.h"
33 #include "gpgoladdin.h"
34
35
36 /* Return a malloced string with the utf-8 encoded name of the object
37    or NULL if not available.  */
38 char *
39 get_object_name (LPUNKNOWN obj)
40 {
41   HRESULT hr;
42   LPDISPATCH disp = NULL;
43   LPTYPEINFO tinfo = NULL;
44   BSTR bstrname;
45   char *name = NULL;
46
47   if (!obj)
48     goto leave;
49
50   obj->QueryInterface (IID_IDispatch, (void **)&disp);
51   if (!disp)
52     goto leave;
53
54   disp->GetTypeInfo (0, 0, &tinfo);
55   if (!tinfo)
56     {
57       log_debug ("%s:%s: no typeinfo found for object\n", 
58                  SRCNAME, __func__);
59       goto leave;
60     }
61
62   bstrname = NULL;
63   hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0);
64   if (hr || !bstrname)
65     log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", 
66                SRCNAME, __func__, hr);
67   if (bstrname)
68     {
69       name = wchar_to_utf8 (bstrname);
70       SysFreeString (bstrname);
71     }
72
73  leave:
74   if (tinfo)
75     gpgol_release (tinfo);
76   if (disp)
77     gpgol_release (disp);
78
79   return name;
80 }
81
82
83 /* Lookup the dispid of object PDISP for member NAME.  Returns
84    DISPID_UNKNOWN on error.  */
85 DISPID
86 lookup_oom_dispid (LPDISPATCH pDisp, const char *name)
87 {
88   HRESULT hr;
89   DISPID dispid;
90   wchar_t *wname;
91
92   if (!pDisp || !name)
93     return DISPID_UNKNOWN; /* Error: Invalid arg.  */
94
95   wname = utf8_to_wchar (name);
96   if (!wname)
97     return DISPID_UNKNOWN;/* Error:  Out of memory.  */
98
99   hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, 
100                              LOCALE_SYSTEM_DEFAULT, &dispid);
101   xfree (wname);
102   if (hr != S_OK || dispid == DISPID_UNKNOWN)
103     log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n",
104                SRCNAME, __func__, name, (int)dispid, (unsigned int)hr);
105   if (hr != S_OK)
106     dispid = DISPID_UNKNOWN;
107
108   return dispid;
109 }
110
111 static void
112 init_excepinfo (EXCEPINFO *err)
113 {
114   if (!err)
115     {
116       return;
117     }
118   err->wCode = 0;
119   err->wReserved = 0;
120   err->bstrSource = nullptr;
121   err->bstrDescription = nullptr;
122   err->bstrHelpFile = nullptr;
123   err->dwHelpContext = 0;
124   err->pvReserved = nullptr;
125   err->pfnDeferredFillIn = nullptr;
126   err->scode = 0;
127 }
128
129 void
130 dump_excepinfo (EXCEPINFO err)
131 {
132   log_debug ("%s:%s: Exception: \n"
133              "              wCode: 0x%x\n"
134              "              wReserved: 0x%x\n"
135              "              source: %S\n"
136              "              desc: %S\n"
137              "              help: %S\n"
138              "              helpCtx: 0x%x\n"
139              "              deferredFill: %p\n"
140              "              scode: 0x%x\n",
141              SRCNAME, __func__, (unsigned int) err.wCode,
142              (unsigned int) err.wReserved,
143              err.bstrSource ? err.bstrSource : L"null",
144              err.bstrDescription ? err.bstrDescription : L"null",
145              err.bstrHelpFile ? err.bstrDescription : L"null",
146              (unsigned int) err.dwHelpContext,
147              err.pfnDeferredFillIn,
148              (unsigned int) err.scode);
149 }
150
151 /* Return the OOM object's IDispatch interface described by FULLNAME.
152    Returns NULL if not found.  PSTART is the object where the search
153    starts.  FULLNAME is a dot delimited sequence of object names.  If
154    an object name has a "(foo)" suffix this passes it as a parameter
155    to the invoke function (i.e. using (DISPATCH|PROPERTYGET)).  Object
156    names including the optional suffix are truncated at 127 byte.  */
157 LPDISPATCH
158 get_oom_object (LPDISPATCH pStart, const char *fullname)
159 {
160   HRESULT hr;
161   LPDISPATCH pObj = pStart;
162   LPDISPATCH pDisp = NULL;
163
164   log_oom ("%s:%s: looking for %p->`%s'",
165            SRCNAME, __func__, pStart, fullname);
166
167   while (pObj)
168     {
169       DISPPARAMS dispparams;
170       VARIANT aVariant[4];
171       VARIANT vtResult;
172       wchar_t *wname;
173       char name[128];
174       int n_parms = 0;
175       BSTR parmstr = NULL;
176       INT  parmint = 0;
177       DISPID dispid;
178       char *p, *pend;
179       int dispmethod;
180       unsigned int argErr = 0;
181       EXCEPINFO execpinfo;
182
183       if (pDisp)
184         {
185           gpgol_release (pDisp);
186           pDisp = NULL;
187         }
188       pObj->QueryInterface (IID_IDispatch, (LPVOID*)&pDisp);
189       if (pObj != pStart)
190         gpgol_release (pObj);
191       pObj = NULL;
192       if (!pDisp)
193         return NULL;  /* The object has no IDispatch interface.  */
194       if (!*fullname)
195         {
196           log_oom ("%s:%s:         got %p",SRCNAME, __func__, pDisp);
197           return pDisp; /* Ready.  */
198         }
199       
200       /* Break out the next name part.  */
201       {
202         const char *dot;
203         size_t n;
204         
205         dot = strchr (fullname, '.');
206         if (dot == fullname)
207           {
208             gpgol_release (pDisp);
209             return NULL;  /* Empty name part: error.  */
210           }
211         else if (dot)
212           n = dot - fullname;
213         else
214           n = strlen (fullname);
215         
216         if (n >= sizeof name)
217           n = sizeof name - 1;
218         strncpy (name, fullname, n);
219         name[n] = 0;
220         
221         if (dot)
222           fullname = dot + 1;
223         else
224           fullname += strlen (fullname);
225       }
226       
227       if (!strncmp (name, "get_", 4) && name[4])
228         {
229           dispmethod = DISPATCH_PROPERTYGET;
230           memmove (name, name+4, strlen (name+4)+1);
231         }
232       else if ((p = strchr (name, '(')))
233         {
234           *p++ = 0;
235           pend = strchr (p, ')');
236           if (pend)
237             *pend = 0;
238
239           if (*p == ',' && p[1] != ',')
240             {
241               /* We assume this is "foo(,30007)".  I.e. the frst arg
242                  is not given and the second one is an integer.  */
243               parmint = (int)strtol (p+1, NULL, 10);
244               n_parms = 4;
245             }
246           else
247             {
248               wname = utf8_to_wchar (p);
249               if (wname)
250                 {
251                   parmstr = SysAllocString (wname);
252                   xfree (wname);
253                 }
254               if (!parmstr)
255                 {
256                   gpgol_release (pDisp);
257                   return NULL; /* Error:  Out of memory.  */
258                 }
259               n_parms = 1;
260             }
261           dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET;
262         }
263       else
264         dispmethod = DISPATCH_METHOD;
265
266       /* Lookup the dispid.  */
267       dispid = lookup_oom_dispid (pDisp, name);
268       if (dispid == DISPID_UNKNOWN)
269         {
270           if (parmstr)
271             SysFreeString (parmstr);
272           gpgol_release (pDisp);
273           return NULL;  /* Name not found.  */
274         }
275
276       /* Invoke the method.  */
277       dispparams.rgvarg = aVariant;
278       dispparams.cArgs = 0;
279       if (n_parms)
280         {
281           if (n_parms == 4)
282             {
283               dispparams.rgvarg[0].vt = VT_ERROR; 
284               dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; 
285               dispparams.rgvarg[1].vt = VT_ERROR; 
286               dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; 
287               dispparams.rgvarg[2].vt = VT_INT; 
288               dispparams.rgvarg[2].intVal = parmint; 
289               dispparams.rgvarg[3].vt = VT_ERROR; 
290               dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
291               dispparams.cArgs = n_parms;
292             }
293           else if (n_parms == 1 && parmstr)
294             {
295               dispparams.rgvarg[0].vt = VT_BSTR;
296               dispparams.rgvarg[0].bstrVal = parmstr;
297               dispparams.cArgs++;
298             }
299         }
300       dispparams.cNamedArgs = 0;
301       VariantInit (&vtResult);
302       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
303                           dispmethod, &dispparams,
304                           &vtResult, &execpinfo, &argErr);
305       if (parmstr)
306         SysFreeString (parmstr);
307       if (hr != S_OK || vtResult.vt != VT_DISPATCH)
308         {
309           log_debug ("%s:%s:       error: '%s' p=%p vt=%d hr=0x%x argErr=0x%x",
310                      SRCNAME, __func__,
311                      name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
312                      (unsigned int)argErr);
313           dump_excepinfo (execpinfo);
314           VariantClear (&vtResult);
315           if (parmstr)
316             SysFreeString (parmstr);
317           gpgol_release (pDisp);
318           return NULL;  /* Invoke failed.  */
319         }
320
321       pObj = vtResult.pdispVal;
322     }
323   log_debug ("%s:%s:       error: no object", SRCNAME, __func__);
324   return NULL;
325 }
326
327
328 /* Helper for put_oom_icon.  */
329 static int
330 put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask)
331 {
332   HRESULT hr;
333   PICTDESC pdesc;
334   LPDISPATCH pPict;
335   DISPID dispid_put = DISPID_PROPERTYPUT;
336   UINT fuload;
337   DISPID dispid;
338   DISPPARAMS dispparams;
339   VARIANT aVariant[2];
340
341   /* When loading the mask we need to set the monochrome flag.  We
342      better create a DIB section to avoid possible rendering
343      problems.  */
344   fuload = LR_CREATEDIBSECTION | LR_SHARED;
345   if (is_mask)
346     fuload |= LR_MONOCHROME;
347   
348   memset (&pdesc, 0, sizeof pdesc);
349   pdesc.cbSizeofstruct = sizeof pdesc;
350   pdesc.picType = PICTYPE_BITMAP;
351   pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst,
352                                            MAKEINTRESOURCE (resource),
353                                            IMAGE_BITMAP, size, size, fuload);
354   if (!pdesc.bmp.hbitmap)
355     {
356       log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", 
357                      SRCNAME, __func__, resource);
358       return -1;
359     }
360
361   /* Wrap the image into an OLE object.  */
362   hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, 
363                                  TRUE, (void **) &pPict);
364   if (hr != S_OK || !pPict)
365     {
366       log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
367                  SRCNAME, __func__, hr);
368       return -1;
369     }
370         
371   /* Store to the Picture or Mask property of the CommandBarButton.  */
372   dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture");
373
374   dispparams.rgvarg = aVariant;
375   dispparams.rgvarg[0].vt = VT_DISPATCH;
376   dispparams.rgvarg[0].pdispVal = pPict;
377   dispparams.cArgs = 1;
378   dispparams.rgdispidNamedArgs = &dispid_put;
379   dispparams.cNamedArgs = 1;
380   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
381                       DISPATCH_PROPERTYPUT, &dispparams,
382                       NULL, NULL, NULL);
383   if (hr != S_OK)
384     {
385       log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr);
386       return -1;
387     }
388   return 0;
389 }
390
391
392 /* Update the icon of PDISP using the bitmap with RESOURCE ID.  The
393    function adds the system pixel size to the resource id to compute
394    the actual icon size.  The resource id of the mask is the N+1.  */
395 int
396 put_oom_icon (LPDISPATCH pDisp, int resource_id, int size)
397 {
398   int rc;
399
400   /* This code is only relevant for Outlook < 2010.
401     Ideally it should grab the system pixel size and use an
402     icon of the appropiate size (e.g. 32 or 64px)
403   */
404
405   rc = put_picture_or_mask (pDisp, resource_id, size, 0);
406   if (!rc)
407     rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1);
408
409   return rc;
410 }
411
412
413 /* Set the boolean property NAME to VALUE.  */
414 int
415 put_oom_bool (LPDISPATCH pDisp, const char *name, int value)
416 {
417   HRESULT hr;
418   DISPID dispid_put = DISPID_PROPERTYPUT;
419   DISPID dispid;
420   DISPPARAMS dispparams;
421   VARIANT aVariant[1];
422
423   dispid = lookup_oom_dispid (pDisp, name);
424   if (dispid == DISPID_UNKNOWN)
425     return -1;
426
427   dispparams.rgvarg = aVariant;
428   dispparams.rgvarg[0].vt = VT_BOOL;
429   dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE;
430   dispparams.cArgs = 1;
431   dispparams.rgdispidNamedArgs = &dispid_put;
432   dispparams.cNamedArgs = 1;
433   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
434                       DISPATCH_PROPERTYPUT, &dispparams,
435                       NULL, NULL, NULL);
436   if (hr != S_OK)
437     {
438       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
439                  SRCNAME, __func__, name, hr);
440       return -1;
441     }
442   return 0;
443 }
444
445
446 /* Set the property NAME to VALUE.  */
447 int
448 put_oom_int (LPDISPATCH pDisp, const char *name, int value)
449 {
450   HRESULT hr;
451   DISPID dispid_put = DISPID_PROPERTYPUT;
452   DISPID dispid;
453   DISPPARAMS dispparams;
454   VARIANT aVariant[1];
455
456   dispid = lookup_oom_dispid (pDisp, name);
457   if (dispid == DISPID_UNKNOWN)
458     return -1;
459
460   dispparams.rgvarg = aVariant;
461   dispparams.rgvarg[0].vt = VT_INT;
462   dispparams.rgvarg[0].intVal = value;
463   dispparams.cArgs = 1;
464   dispparams.rgdispidNamedArgs = &dispid_put;
465   dispparams.cNamedArgs = 1;
466   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
467                       DISPATCH_PROPERTYPUT, &dispparams,
468                       NULL, NULL, NULL);
469   if (hr != S_OK)
470     {
471       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
472                  SRCNAME, __func__, name, hr);
473       return -1;
474     }
475   return 0;
476 }
477
478
479 /* Set the property NAME to STRING.  */
480 int
481 put_oom_string (LPDISPATCH pDisp, const char *name, const char *string)
482 {
483   HRESULT hr;
484   DISPID dispid_put = DISPID_PROPERTYPUT;
485   DISPID dispid;
486   DISPPARAMS dispparams;
487   VARIANT aVariant[1];
488   BSTR bstring;
489   EXCEPINFO execpinfo;
490
491   dispid = lookup_oom_dispid (pDisp, name);
492   if (dispid == DISPID_UNKNOWN)
493     return -1;
494
495   {
496     wchar_t *tmp = utf8_to_wchar (string);
497     bstring = tmp? SysAllocString (tmp):NULL;
498     xfree (tmp);
499     if (!bstring)
500       {
501         log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
502         return -1;
503       }
504   }
505
506   dispparams.rgvarg = aVariant;
507   dispparams.rgvarg[0].vt = VT_BSTR;
508   dispparams.rgvarg[0].bstrVal = bstring;
509   dispparams.cArgs = 1;
510   dispparams.rgdispidNamedArgs = &dispid_put;
511   dispparams.cNamedArgs = 1;
512   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
513                       DISPATCH_PROPERTYPUT, &dispparams,
514                       NULL, &execpinfo, NULL);
515   SysFreeString (bstring);
516   if (hr != S_OK)
517     {
518       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
519                  SRCNAME, __func__, name, hr);
520       dump_excepinfo (execpinfo);
521       return -1;
522     }
523   return 0;
524 }
525
526
527 /* Get the boolean property NAME of the object PDISP.  Returns False if
528    not found or if it is not a boolean property.  */
529 int
530 get_oom_bool (LPDISPATCH pDisp, const char *name)
531 {
532   HRESULT hr;      
533   int result = 0;
534   DISPID dispid;
535   
536   dispid = lookup_oom_dispid (pDisp, name);
537   if (dispid != DISPID_UNKNOWN)
538     {
539       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
540       VARIANT rVariant;
541
542       VariantInit (&rVariant);
543       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
544                           DISPATCH_PROPERTYGET, &dispparams,
545                           &rVariant, NULL, NULL);
546       if (hr != S_OK)
547         log_debug ("%s:%s: Property '%s' not found: %#lx",
548                    SRCNAME, __func__, name, hr);
549       else if (rVariant.vt != VT_BOOL)
550         log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)",
551                    SRCNAME, __func__, name, rVariant.vt);
552       else
553         result = !!rVariant.boolVal;
554       VariantClear (&rVariant);
555     }
556
557   return result;
558 }
559
560
561 /* Get the integer property NAME of the object PDISP.  Returns 0 if
562    not found or if it is not an integer property.  */
563 int
564 get_oom_int (LPDISPATCH pDisp, const char *name)
565 {
566   HRESULT hr;      
567   int result = 0;
568   DISPID dispid;
569   
570   dispid = lookup_oom_dispid (pDisp, name);
571   if (dispid != DISPID_UNKNOWN)
572     {
573       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
574       VARIANT rVariant;
575
576       VariantInit (&rVariant);
577       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
578                           DISPATCH_PROPERTYGET, &dispparams,
579                           &rVariant, NULL, NULL);
580       if (hr != S_OK)
581         log_debug ("%s:%s: Property '%s' not found: %#lx",
582                    SRCNAME, __func__, name, hr);
583       else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4)
584         log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)",
585                    SRCNAME, __func__, name, rVariant.vt);
586       else
587         result = rVariant.intVal;
588       VariantClear (&rVariant);
589     }
590
591   return result;
592 }
593
594
595 /* Get the string property NAME of the object PDISP.  Returns NULL if
596    not found or if it is not a string property.  */
597 char *
598 get_oom_string (LPDISPATCH pDisp, const char *name)
599 {
600   HRESULT hr;      
601   char *result = NULL;
602   DISPID dispid;
603   
604   dispid = lookup_oom_dispid (pDisp, name);
605   if (dispid != DISPID_UNKNOWN)
606     {
607       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
608       VARIANT rVariant;
609
610       VariantInit (&rVariant);
611       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
612                           DISPATCH_PROPERTYGET, &dispparams,
613                           &rVariant, NULL, NULL);
614       if (hr != S_OK)
615         log_debug ("%s:%s: Property '%s' not found: %#lx",
616                    SRCNAME, __func__, name, hr);
617       else if (rVariant.vt != VT_BSTR)
618         log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
619                    SRCNAME, __func__, name, rVariant.vt);
620       else if (rVariant.bstrVal)
621         result = wchar_to_utf8 (rVariant.bstrVal);
622       VariantClear (&rVariant);
623     }
624
625   return result;
626 }
627
628
629 /* Get the object property NAME of the object PDISP.  Returns NULL if
630    not found or if it is not an object perty.  */
631 LPUNKNOWN
632 get_oom_iunknown (LPDISPATCH pDisp, const char *name)
633 {
634   HRESULT hr;      
635   DISPID dispid;
636   
637   dispid = lookup_oom_dispid (pDisp, name);
638   if (dispid != DISPID_UNKNOWN)
639     {
640       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
641       VARIANT rVariant;
642
643       VariantInit (&rVariant);
644       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
645                           DISPATCH_PROPERTYGET, &dispparams,
646                           &rVariant, NULL, NULL);
647       if (hr != S_OK)
648         log_debug ("%s:%s: Property '%s' not found: %#lx",
649                    SRCNAME, __func__, name, hr);
650       else if (rVariant.vt != VT_UNKNOWN)
651         log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)",
652                    SRCNAME, __func__, name, rVariant.vt);
653       else
654         return rVariant.punkVal;
655
656       VariantClear (&rVariant);
657     }
658
659   return NULL;
660 }
661
662
663 /* Return the control object described by the tag property with value
664    TAG. The object POBJ must support the FindControl method.  Returns
665    NULL if not found.  */
666 LPDISPATCH
667 get_oom_control_bytag (LPDISPATCH pDisp, const char *tag)
668 {
669   HRESULT hr;      
670   DISPID dispid;
671   DISPPARAMS dispparams;
672   VARIANT aVariant[4];
673   VARIANT rVariant;
674   BSTR bstring;
675   LPDISPATCH result = NULL;
676
677   dispid = lookup_oom_dispid (pDisp, "FindControl");
678   if (dispid == DISPID_UNKNOWN)
679     {
680       log_debug ("%s:%s: Object %p has no FindControl method",
681                  SRCNAME, __func__, pDisp);
682       return NULL;
683     }
684
685   {
686     wchar_t *tmp = utf8_to_wchar (tag);
687     bstring = tmp? SysAllocString (tmp):NULL;
688     xfree (tmp);
689     if (!bstring)
690       {
691         log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
692         return NULL;
693       }
694   }
695   dispparams.rgvarg = aVariant;
696   dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */
697   dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; 
698   dispparams.rgvarg[1].vt = VT_BSTR;  /* Tag */
699   dispparams.rgvarg[1].bstrVal = bstring;
700   dispparams.rgvarg[2].vt = VT_ERROR; /* Id */
701   dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
702   dispparams.rgvarg[3].vt = VT_ERROR;/* Type */
703   dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
704   dispparams.cArgs = 4;
705   dispparams.cNamedArgs = 0;
706   VariantInit (&rVariant);
707   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
708                       DISPATCH_METHOD, &dispparams,
709                       &rVariant, NULL, NULL);
710   SysFreeString (bstring);
711   if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal)
712     {
713       rVariant.pdispVal->QueryInterface (IID_IDispatch, (LPVOID*)&result);
714       gpgol_release (rVariant.pdispVal);
715       if (!result)
716         log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.",
717                    SRCNAME, __func__, tag);
718     }
719   else
720     {
721       log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx",
722                  SRCNAME, __func__, tag, rVariant.vt, hr);
723       VariantClear (&rVariant);
724     }
725
726   return result;
727 }
728
729
730 /* Add a new button to an object which supports the add method.
731    Returns the new object or NULL on error.  */
732 LPDISPATCH
733 add_oom_button (LPDISPATCH pObj)
734 {
735   HRESULT hr;      
736   DISPID dispid;
737   DISPPARAMS dispparams;
738   VARIANT aVariant[5];
739   VARIANT rVariant;
740
741   dispid = lookup_oom_dispid (pObj, "Add");
742
743   dispparams.rgvarg = aVariant;
744   dispparams.rgvarg[0].vt = VT_BOOL;  /* Temporary */
745   dispparams.rgvarg[0].boolVal = VARIANT_TRUE;
746   dispparams.rgvarg[1].vt = VT_ERROR;  /* Before */
747   dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; 
748   dispparams.rgvarg[2].vt = VT_ERROR;  /* Parameter */
749   dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; 
750   dispparams.rgvarg[3].vt = VT_ERROR;  /* Id */
751   dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
752   dispparams.rgvarg[4].vt = VT_INT;    /* Type */
753   dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON;
754   dispparams.cArgs = 5;
755   dispparams.cNamedArgs = 0;
756   VariantInit (&rVariant);
757   hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
758                      DISPATCH_METHOD, &dispparams,
759                      &rVariant, NULL, NULL);
760   if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal)
761     {
762       log_error ("%s:%s: Adding Control failed: %#lx - vt=%d",
763                  SRCNAME, __func__, hr, rVariant.vt);
764       VariantClear (&rVariant);
765       return NULL;
766     }
767   return rVariant.pdispVal;
768 }
769
770
771 /* Add a new button to an object which supports the add method.
772    Returns the new object or NULL on error.  */
773 void
774 del_oom_button (LPDISPATCH pObj)
775 {
776   HRESULT hr;      
777   DISPID dispid;
778   DISPPARAMS dispparams;
779   VARIANT aVariant[5];
780
781   dispid = lookup_oom_dispid (pObj, "Delete");
782
783   dispparams.rgvarg = aVariant;
784   dispparams.rgvarg[0].vt = VT_BOOL;  /* Temporary */
785   dispparams.rgvarg[0].boolVal = VARIANT_FALSE;
786   dispparams.cArgs = 1;
787   dispparams.cNamedArgs = 0;
788   hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
789                      DISPATCH_METHOD, &dispparams,
790                      NULL, NULL, NULL);
791   if (hr != S_OK)
792     log_error ("%s:%s: Deleting Control failed: %#lx",
793                SRCNAME, __func__, hr);
794 }
795
796 /* Gets the current contexts HWND. Returns NULL on error */
797 HWND
798 get_oom_context_window (LPDISPATCH context)
799 {
800   LPOLEWINDOW actExplorer;
801   HWND ret = NULL;
802   actExplorer = (LPOLEWINDOW) get_oom_object(context,
803                                              "Application.ActiveExplorer");
804   if (actExplorer)
805     actExplorer->GetWindow (&ret);
806   else
807     {
808       log_debug ("%s:%s: Could not find active window",
809                  SRCNAME, __func__);
810     }
811   gpgol_release (actExplorer);
812   return ret;
813 }
814
815 int
816 set_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value)
817 {
818   LPDISPATCH propertyAccessor;
819   VARIANT cVariant[2];
820   VARIANT rVariant;
821   DISPID dispid;
822   DISPPARAMS dispparams;
823   HRESULT hr;
824   EXCEPINFO execpinfo;
825   BSTR b_property;
826   wchar_t *w_property;
827   unsigned int argErr = 0;
828
829   init_excepinfo (&execpinfo);
830
831   log_oom ("%s:%s: Looking up property: %s;",
832              SRCNAME, __func__, dasl_id);
833
834   propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
835   if (!propertyAccessor)
836     {
837       log_error ("%s:%s: Failed to look up property accessor.",
838                  SRCNAME, __func__);
839       return -1;
840     }
841
842   dispid = lookup_oom_dispid (propertyAccessor, "SetProperty");
843
844   if (dispid == DISPID_UNKNOWN)
845   {
846     log_error ("%s:%s: could not find SetProperty DISPID",
847                SRCNAME, __func__);
848     return -1;
849   }
850
851   /* Prepare the parameter */
852   w_property = utf8_to_wchar (dasl_id);
853   b_property = SysAllocString (w_property);
854   xfree (w_property);
855
856   /* Variant 0 carries the data. */
857   VariantCopy (&cVariant[0], value);
858
859   /* Variant 1 is the DASL as found out by experiments. */
860   cVariant[1].vt = VT_BSTR;
861   cVariant[1].bstrVal = b_property;
862   dispparams.rgvarg = cVariant;
863   dispparams.cArgs = 2;
864   dispparams.cNamedArgs = 0;
865   VariantInit (&rVariant);
866
867   hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
868                                  DISPATCH_METHOD, &dispparams,
869                                  &rVariant, &execpinfo, &argErr);
870   SysFreeString (b_property);
871   VariantClear (&cVariant[0]);
872   gpgol_release (propertyAccessor);
873   if (hr != S_OK)
874     {
875       log_debug ("%s:%s: error: invoking SetProperty p=%p vt=%d"
876                  " hr=0x%x argErr=0x%x",
877                  SRCNAME, __func__,
878                  rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
879                  (unsigned int)argErr);
880       dump_excepinfo (execpinfo);
881       VariantClear (&rVariant);
882       return -1;
883     }
884   return 0;
885 }
886
887 /* Get a MAPI property through OOM using the PropertyAccessor
888  * interface and the DASL Uid. Returns -1 on error.
889  * Variant has to be cleared with VariantClear.
890  * rVariant must be a pointer to a Variant.
891  */
892 int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant)
893 {
894   LPDISPATCH propertyAccessor;
895   VARIANT cVariant[1];
896   DISPID dispid;
897   DISPPARAMS dispparams;
898   HRESULT hr;
899   EXCEPINFO execpinfo;
900   BSTR b_property;
901   wchar_t *w_property;
902   unsigned int argErr = 0;
903
904   log_oom ("%s:%s: Looking up property: %s;",
905              SRCNAME, __func__, dasl_id);
906
907   propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
908   if (!propertyAccessor)
909     {
910       log_error ("%s:%s: Failed to look up property accessor.",
911                  SRCNAME, __func__);
912       return -1;
913     }
914
915   dispid = lookup_oom_dispid (propertyAccessor, "GetProperty");
916
917   if (dispid == DISPID_UNKNOWN)
918   {
919     log_error ("%s:%s: could not find GetProperty DISPID",
920                SRCNAME, __func__);
921     return -1;
922   }
923
924   /* Prepare the parameter */
925   w_property = utf8_to_wchar (dasl_id);
926   b_property = SysAllocString (w_property);
927   xfree (w_property);
928
929   cVariant[0].vt = VT_BSTR;
930   cVariant[0].bstrVal = b_property;
931   dispparams.rgvarg = cVariant;
932   dispparams.cArgs = 1;
933   dispparams.cNamedArgs = 0;
934   VariantInit (rVariant);
935
936   hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
937                                  DISPATCH_METHOD, &dispparams,
938                                  rVariant, &execpinfo, &argErr);
939   SysFreeString (b_property);
940   gpgol_release (propertyAccessor);
941   if (hr != S_OK)
942     {
943       log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d"
944                  " hr=0x%x argErr=0x%x",
945                  SRCNAME, __func__,
946                  rVariant->pdispVal, rVariant->vt, (unsigned int)hr,
947                  (unsigned int)argErr);
948       dump_excepinfo (execpinfo);
949       VariantClear (rVariant);
950       return -1;
951     }
952   return 0;
953 }
954
955 /* Get a property string by using the PropertyAccessor of pDisp
956  * returns NULL on error or a newly allocated result. */
957 char *
958 get_pa_string (LPDISPATCH pDisp, const char *property)
959 {
960   VARIANT rVariant;
961   char *result = NULL;
962
963   if (get_pa_variant (pDisp, property, &rVariant))
964     {
965       return NULL;
966     }
967
968   if (rVariant.vt == VT_BSTR && rVariant.bstrVal)
969     {
970       result = wchar_to_utf8 (rVariant.bstrVal);
971     }
972   else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF))
973     {
974       LONG uBound, lBound;
975       VARTYPE vt;
976       char *data;
977       SafeArrayGetVartype(rVariant.parray, &vt);
978
979       if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK ||
980           SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK ||
981           vt != VT_UI1)
982         {
983           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
984           VariantClear (&rVariant);
985           return NULL;
986         }
987
988       result = (char *)xmalloc (uBound - lBound + 1);
989       data = (char *) rVariant.parray->pvData;
990       memcpy (result, data + lBound, uBound - lBound);
991       result[uBound - lBound] = '\0';
992     }
993   else
994     {
995       log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
996                  SRCNAME, __func__, property, rVariant.vt);
997     }
998
999   VariantClear (&rVariant);
1000
1001   return result;
1002 }
1003
1004 int
1005 get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt)
1006 {
1007   VARIANT rVariant;
1008
1009   if (get_pa_variant (pDisp, property, &rVariant))
1010     {
1011       return -1;
1012     }
1013
1014   if (rVariant.vt != VT_I4)
1015     {
1016       log_debug ("%s:%s: Property `%s' is not a int (vt=%d)",
1017                  SRCNAME, __func__, property, rVariant.vt);
1018       return -1;
1019     }
1020
1021   *rInt = rVariant.lVal;
1022
1023   VariantClear (&rVariant);
1024   return 0;
1025 }
1026
1027 /* Gets a malloced NULL terminated array of recipent strings from
1028    an OOM recipients Object. */
1029 char **
1030 get_oom_recipients (LPDISPATCH recipients)
1031 {
1032   int recipientsCnt = get_oom_int (recipients, "Count");
1033   char **recipientAddrs = NULL;
1034   int i;
1035
1036   if (!recipientsCnt)
1037     {
1038       return NULL;
1039     }
1040
1041   /* Get the recipients */
1042   recipientAddrs = (char**) xmalloc((recipientsCnt + 1) * sizeof(char*));
1043   recipientAddrs[recipientsCnt] = NULL;
1044   for (i = 1; i <= recipientsCnt; i++)
1045     {
1046       char buf[16];
1047       LPDISPATCH recipient;
1048       snprintf (buf, sizeof (buf), "Item(%i)", i);
1049       recipient = get_oom_object (recipients, buf);
1050       if (!recipient)
1051         {
1052           /* Should be impossible */
1053           recipientAddrs[i-1] = NULL;
1054           log_error ("%s:%s: could not find Item %i;",
1055                      SRCNAME, __func__, i);
1056           break;
1057         }
1058       else
1059         {
1060           char *address,
1061                *resolved;
1062           address = get_oom_string (recipient, "Address");
1063           resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
1064           if (resolved)
1065             {
1066               xfree (address);
1067               recipientAddrs[i-1] = resolved;
1068               continue;
1069             }
1070           log_debug ("%s:%s: Failed to look up SMTP Address;",
1071                      SRCNAME, __func__);
1072           recipientAddrs[i-1] = address;
1073         }
1074     }
1075   return recipientAddrs;
1076 }
1077
1078 /* Add an attachment to the outlook dispatcher disp
1079    that has an Attachment property.
1080    inFile is the path to the attachment. Name is the
1081    name that should be used in outlook. */
1082 int
1083 add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW,
1084                     const wchar_t* displayName)
1085 {
1086   LPDISPATCH attachments = get_oom_object (disp, "Attachments");
1087
1088   DISPID dispid;
1089   DISPPARAMS dispparams;
1090   VARIANT vtResult;
1091   VARIANT aVariant[4];
1092   HRESULT hr;
1093   BSTR inFileB = nullptr,
1094        dispNameB = nullptr;
1095   unsigned int argErr = 0;
1096   EXCEPINFO execpinfo;
1097
1098   dispid = lookup_oom_dispid (attachments, "Add");
1099
1100   if (dispid == DISPID_UNKNOWN)
1101   {
1102     log_error ("%s:%s: could not find attachment dispatcher",
1103                SRCNAME, __func__);
1104     return -1;
1105   }
1106
1107   if (inFileW)
1108     {
1109       inFileB = SysAllocString (inFileW);
1110     }
1111   if (displayName)
1112     {
1113       dispNameB = SysAllocString (displayName);
1114     }
1115
1116   dispparams.rgvarg = aVariant;
1117
1118   /* Contrary to the documentation the Source is the last
1119      parameter and not the first. Additionally DisplayName
1120      is documented but gets ignored by Outlook since Outlook
1121      2003 */
1122   dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */
1123   dispparams.rgvarg[0].bstrVal = dispNameB;
1124   dispparams.rgvarg[1].vt = VT_INT;  /* Position */
1125   dispparams.rgvarg[1].intVal = 1;
1126   dispparams.rgvarg[2].vt = VT_INT;  /* Type */
1127   dispparams.rgvarg[2].intVal = 1;
1128   dispparams.rgvarg[3].vt = VT_BSTR; /* Source */
1129   dispparams.rgvarg[3].bstrVal = inFileB;
1130   dispparams.cArgs = 4;
1131   dispparams.cNamedArgs = 0;
1132   VariantInit (&vtResult);
1133   hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
1134                             DISPATCH_METHOD, &dispparams,
1135                             &vtResult, &execpinfo, &argErr);
1136   if (hr != S_OK)
1137     {
1138       log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x",
1139                  SRCNAME, __func__,
1140                  vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
1141                  (unsigned int)argErr);
1142       dump_excepinfo (execpinfo);
1143     }
1144
1145   if (inFileB)
1146     SysFreeString (inFileB);
1147   if (dispNameB)
1148     SysFreeString (dispNameB);
1149   VariantClear (&vtResult);
1150   gpgol_release (attachments);
1151
1152   return hr == S_OK ? 0 : -1;
1153 }
1154
1155 LPDISPATCH
1156 get_object_by_id (LPDISPATCH pDisp, REFIID id)
1157 {
1158   LPDISPATCH disp = NULL;
1159
1160   if (!pDisp)
1161     return NULL;
1162
1163   if (pDisp->QueryInterface (id, (void **)&disp) != S_OK)
1164     return NULL;
1165   return disp;
1166 }
1167
1168 LPMESSAGE
1169 get_oom_message (LPDISPATCH mailitem)
1170 {
1171   LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject");
1172   if (!mapi_obj)
1173     {
1174       log_error ("%s:%s: Failed to obtain MAPI Message.",
1175                  SRCNAME, __func__);
1176       return NULL;
1177     }
1178   return (LPMESSAGE) mapi_obj;
1179 }
1180
1181 static LPMESSAGE
1182 get_oom_base_message_from_mapi (LPDISPATCH mapi_message)
1183 {
1184   HRESULT hr;
1185   LPDISPATCH secureItem = NULL;
1186   LPMESSAGE message = NULL;
1187   LPMAPISECUREMESSAGE secureMessage = NULL;
1188
1189   secureItem = get_object_by_id (mapi_message,
1190                                  IID_IMAPISecureMessage);
1191   if (!secureItem)
1192     {
1193       log_error ("%s:%s: Failed to obtain SecureItem.",
1194                  SRCNAME, __func__);
1195       return NULL;
1196     }
1197
1198   secureMessage = (LPMAPISECUREMESSAGE) secureItem;
1199
1200   /* The call to GetBaseMessage is pretty much a jump
1201      in the dark. So it would not be surprising to get
1202      crashes here in the future. */
1203   log_oom_extra("%s:%s: About to call GetBaseMessage.",
1204                 SRCNAME, __func__);
1205   hr = secureMessage->GetBaseMessage (&message);
1206   gpgol_release (secureMessage);
1207   if (hr != S_OK)
1208     {
1209       log_error_w32 (hr, "Failed to GetBaseMessage.");
1210       return NULL;
1211     }
1212
1213   return message;
1214 }
1215
1216 LPMESSAGE
1217 get_oom_base_message (LPDISPATCH mailitem)
1218 {
1219   LPMESSAGE mapi_message = get_oom_message (mailitem);
1220   LPMESSAGE ret = NULL;
1221   if (!mapi_message)
1222     {
1223       log_error ("%s:%s: Failed to obtain mapi_message.",
1224                  SRCNAME, __func__);
1225       return NULL;
1226     }
1227   ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message);
1228   gpgol_release (mapi_message);
1229   return ret;
1230 }
1231
1232 int
1233 invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant)
1234 {
1235   HRESULT hr;
1236   DISPID dispid;
1237
1238   dispid = lookup_oom_dispid (pDisp, name);
1239   if (dispid != DISPID_UNKNOWN)
1240     {
1241       EXCEPINFO execpinfo;
1242       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
1243
1244       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
1245                           DISPATCH_METHOD, &dispparams,
1246                           rVariant, &execpinfo, NULL);
1247       if (hr != S_OK)
1248         {
1249           log_debug ("%s:%s: Method '%s' invokation failed: %#lx",
1250                      SRCNAME, __func__, name, hr);
1251           dump_excepinfo (execpinfo);
1252           return -1;
1253         }
1254     }
1255
1256   return 0;
1257 }
1258
1259 LPMAPISESSION
1260 get_oom_mapi_session ()
1261 {
1262   LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
1263   LPDISPATCH oom_session = NULL;
1264   LPMAPISESSION session = NULL;
1265   LPUNKNOWN mapiobj = NULL;
1266   HRESULT hr;
1267
1268   if (!application)
1269     {
1270       log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
1271       return NULL;
1272     }
1273
1274   oom_session = get_oom_object (application, "Session");
1275   if (!oom_session)
1276     {
1277       log_error ("%s:%s: session object not found", SRCNAME, __func__);
1278       return NULL;
1279     }
1280   mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT");
1281   gpgol_release (oom_session);
1282
1283   if (!mapiobj)
1284     {
1285       log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__);
1286       return NULL;
1287     }
1288   session = NULL;
1289   hr = mapiobj->QueryInterface (IID_IMAPISession, (void**)&session);
1290   gpgol_release (mapiobj);
1291   if (hr != S_OK || !session)
1292     {
1293       log_error ("%s:%s: error getting IMAPISession: hr=%#lx",
1294                  SRCNAME, __func__, hr);
1295       return NULL;
1296     }
1297   return session;
1298 }
1299
1300 static int
1301 create_category (LPDISPATCH categories, const char *category, int color)
1302 {
1303   VARIANT cVariant[3];
1304   VARIANT rVariant;
1305   DISPID dispid;
1306   DISPPARAMS dispparams;
1307   HRESULT hr;
1308   EXCEPINFO execpinfo;
1309   BSTR b_name;
1310   wchar_t *w_name;
1311   unsigned int argErr = 0;
1312
1313   init_excepinfo (&execpinfo);
1314
1315   if (!categories || !category)
1316     {
1317       TRACEPOINT;
1318       return 1;
1319     }
1320
1321   dispid = lookup_oom_dispid (categories, "Add");
1322   if (dispid == DISPID_UNKNOWN)
1323   {
1324     log_error ("%s:%s: could not find Add DISPID",
1325                SRCNAME, __func__);
1326     return -1;
1327   }
1328
1329   /* Do the string dance */
1330   w_name = utf8_to_wchar (category);
1331   b_name = SysAllocString (w_name);
1332   xfree (w_name);
1333
1334   /* Variants are in reverse order
1335      ShortcutKey -> 0 / Int
1336      Color -> 1 / Int
1337      Name -> 2 / Bstr */
1338   VariantInit (&cVariant[2]);
1339   cVariant[2].vt = VT_BSTR;
1340   cVariant[2].bstrVal = b_name;
1341
1342   VariantInit (&cVariant[1]);
1343   cVariant[1].vt = VT_INT;
1344   cVariant[1].intVal = color;
1345
1346   VariantInit (&cVariant[0]);
1347   cVariant[0].vt = VT_INT;
1348   cVariant[0].intVal = 0;
1349
1350   dispparams.cArgs = 3;
1351   dispparams.cNamedArgs = 0;
1352   dispparams.rgvarg = cVariant;
1353
1354   hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
1355                            DISPATCH_METHOD, &dispparams,
1356                            &rVariant, &execpinfo, &argErr);
1357   SysFreeString (b_name);
1358   VariantClear (&cVariant[0]);
1359   VariantClear (&cVariant[1]);
1360   VariantClear (&cVariant[2]);
1361   if (hr != S_OK)
1362     {
1363       log_debug ("%s:%s: error: invoking Add p=%p vt=%d"
1364                  " hr=0x%x argErr=0x%x",
1365                  SRCNAME, __func__,
1366                  rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
1367                  (unsigned int)argErr);
1368       dump_excepinfo (execpinfo);
1369       VariantClear (&rVariant);
1370       return -1;
1371     }
1372   VariantClear (&rVariant);
1373   log_debug ("%s:%s: Created category '%s'",
1374              SRCNAME, __func__, category);
1375   return 0;
1376 }
1377
1378 void
1379 ensure_category_exists (LPDISPATCH application, const char *category, int color)
1380 {
1381   if (!application || !category)
1382     {
1383       TRACEPOINT;
1384       return;
1385     }
1386
1387   log_debug ("Ensure category exists called for %s, %i", category, color);
1388
1389   LPDISPATCH stores = get_oom_object (application, "Session.Stores");
1390   if (!stores)
1391     {
1392       log_error ("%s:%s: No stores found.",
1393                  SRCNAME, __func__);
1394       return;
1395     }
1396   auto store_count = get_oom_int (stores, "Count");
1397
1398   for (int n = 1; n <= store_count; n++)
1399     {
1400       const auto store_str = std::string("Item(") + std::to_string(n) + ")";
1401       LPDISPATCH store = get_oom_object (stores, store_str.c_str());
1402
1403       if (!store)
1404         {
1405           TRACEPOINT;
1406           continue;
1407         }
1408
1409       LPDISPATCH categories = get_oom_object (store, "Categories");
1410       gpgol_release (store);
1411       if (!categories)
1412         {
1413           TRACEPOINT;
1414           continue;
1415         }
1416
1417       auto count = get_oom_int (categories, "Count");
1418       bool found = false;
1419       for (int i = 1; i <= count && !found; i++)
1420         {
1421           const auto item_str = std::string("Item(") + std::to_string(i) + ")";
1422           LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str());
1423           if (!category_obj)
1424             {
1425               TRACEPOINT;
1426               break;
1427             }
1428           char *name = get_oom_string (category_obj, "Name");
1429           if (name && !strcmp (category, name))
1430             {
1431               log_debug ("%s:%s: Found category '%s'",
1432                          SRCNAME, __func__, name);
1433               found = true;
1434             }
1435           /* We don't check the color here as the user may change that. */
1436           gpgol_release (category_obj);
1437           xfree (name);
1438         }
1439
1440       if (!found)
1441         {
1442           if (create_category (categories, category, color))
1443             {
1444               log_debug ("%s:%s: Found category '%s'",
1445                          SRCNAME, __func__, category);
1446             }
1447         }
1448       /* Otherwise we have to create the category */
1449       gpgol_release (categories);
1450     }
1451   gpgol_release (stores);
1452 }
1453
1454 int
1455 add_category (LPDISPATCH mail, const char *category)
1456 {
1457   char *tmp = get_oom_string (mail, "Categories");
1458   if (!tmp)
1459     {
1460       TRACEPOINT;
1461       return 1;
1462     }
1463
1464   if (strstr (tmp, category))
1465     {
1466       log_debug ("%s:%s: category '%s' already added.",
1467                  SRCNAME, __func__, category);
1468       return 0;
1469     }
1470
1471   std::string newstr (tmp);
1472   xfree (tmp);
1473   if (!newstr.empty ())
1474     {
1475       newstr += ", ";
1476     }
1477   newstr += category;
1478
1479   return put_oom_string (mail, "Categories", newstr.c_str ());
1480 }
1481
1482 int
1483 remove_category (LPDISPATCH mail, const char *category)
1484 {
1485   char *tmp = get_oom_string (mail, "Categories");
1486   if (!tmp)
1487     {
1488       TRACEPOINT;
1489       return 1;
1490     }
1491   std::string newstr (tmp);
1492   xfree (tmp);
1493   std::string cat (category);
1494
1495   size_t pos1 = newstr.find (cat);
1496   size_t pos2 = newstr.find (std::string(", ") + cat);
1497   if (pos1 == std::string::npos && pos2 == std::string::npos)
1498     {
1499       log_debug ("%s:%s: category '%s' not found.",
1500                  SRCNAME, __func__, category);
1501       return 0;
1502     }
1503
1504   size_t len = cat.size();
1505   if (pos2)
1506     {
1507       len += 2;
1508     }
1509   newstr.erase (pos2 != std::string::npos ? pos2 : pos1, len);
1510   log_debug ("%s:%s: removing category '%s'",
1511              SRCNAME, __func__, category);
1512
1513   return put_oom_string (mail, "Categories", newstr.c_str ());
1514 }