xref: /reactos/dll/win32/mapi32/prop.c (revision 19b18ce2)
1 /*
2  * Property functions
3  *
4  * Copyright 2004 Jon Griffiths
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <stdarg.h>
22 
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winreg.h"
26 #include "winerror.h"
27 #include "winternl.h"
28 #include "objbase.h"
29 #include "shlwapi.h"
30 #include "wine/list.h"
31 #include "wine/debug.h"
32 #include "wine/unicode.h"
33 #include "mapival.h"
34 
35 WINE_DEFAULT_DEBUG_CHANNEL(mapi);
36 
37 BOOL WINAPI FBadRglpszA(LPSTR*,ULONG);
38 
39 /* Internal: Check if a property value array is invalid */
40 static inline ULONG PROP_BadArray(LPSPropValue lpProp, size_t elemSize)
41 {
42     return IsBadReadPtr(lpProp->Value.MVi.lpi, lpProp->Value.MVi.cValues * elemSize);
43 }
44 
45 /*************************************************************************
46  * PropCopyMore@16 (MAPI32.76)
47  *
48  * Copy a property value.
49  *
50  * PARAMS
51  *  lpDest [O] Destination for the copied value
52  *  lpSrc  [I] Property value to copy to lpDest
53  *  lpMore [I] Linked memory allocation function (pass MAPIAllocateMore())
54  *  lpOrig [I] Original allocation to which memory will be linked
55  *
56  * RETURNS
57  *  Success: S_OK. lpDest contains a deep copy of lpSrc.
58  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid,
59  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails.
60  *
61  * NOTES
62  *  Any elements within the property returned should not be individually
63  *  freed, as they will be freed when lpOrig is.
64  */
65 SCODE WINAPI PropCopyMore(LPSPropValue lpDest, LPSPropValue lpSrc,
66                           ALLOCATEMORE *lpMore, LPVOID lpOrig)
67 {
68     ULONG ulLen, i;
69     SCODE scode = S_OK;
70 
71     TRACE("(%p,%p,%p,%p)\n", lpDest, lpSrc, lpMore, lpOrig);
72 
73     if (!lpDest || IsBadWritePtr(lpDest, sizeof(SPropValue)) ||
74         FBadProp(lpSrc) || !lpMore)
75         return MAPI_E_INVALID_PARAMETER;
76 
77     /* Shallow copy first, this is sufficient for properties without pointers */
78     *lpDest = *lpSrc;
79 
80    switch (PROP_TYPE(lpSrc->ulPropTag))
81     {
82     case PT_CLSID:
83         scode = lpMore(sizeof(GUID), lpOrig, (LPVOID*)&lpDest->Value.lpguid);
84         if (SUCCEEDED(scode))
85             *lpDest->Value.lpguid = *lpSrc->Value.lpguid;
86         break;
87     case PT_STRING8:
88         ulLen = lstrlenA(lpSrc->Value.lpszA) + 1u;
89         scode = lpMore(ulLen, lpOrig, (LPVOID*)&lpDest->Value.lpszA);
90         if (SUCCEEDED(scode))
91             memcpy(lpDest->Value.lpszA, lpSrc->Value.lpszA, ulLen);
92         break;
93     case PT_UNICODE:
94         ulLen = (strlenW(lpSrc->Value.lpszW) + 1u) * sizeof(WCHAR);
95         scode = lpMore(ulLen, lpOrig, (LPVOID*)&lpDest->Value.lpszW);
96         if (SUCCEEDED(scode))
97             memcpy(lpDest->Value.lpszW, lpSrc->Value.lpszW, ulLen);
98         break;
99     case PT_BINARY:
100         scode = lpMore(lpSrc->Value.bin.cb, lpOrig, (LPVOID*)&lpDest->Value.bin.lpb);
101         if (SUCCEEDED(scode))
102             memcpy(lpDest->Value.bin.lpb, lpSrc->Value.bin.lpb, lpSrc->Value.bin.cb);
103         break;
104     default:
105         if (lpSrc->ulPropTag & MV_FLAG)
106         {
107             ulLen = UlPropSize(lpSrc);
108 
109             if (PROP_TYPE(lpSrc->ulPropTag) == PT_MV_STRING8 ||
110                 PROP_TYPE(lpSrc->ulPropTag) == PT_MV_UNICODE)
111             {
112                 /* UlPropSize doesn't account for the string pointers */
113                 ulLen += lpSrc->Value.MVszA.cValues * sizeof(char*);
114             }
115             else if (PROP_TYPE(lpSrc->ulPropTag) == PT_MV_BINARY)
116             {
117                /* UlPropSize doesn't account for the SBinary structs */
118                ulLen += lpSrc->Value.MVbin.cValues * sizeof(SBinary);
119             }
120 
121             lpDest->Value.MVi.cValues = lpSrc->Value.MVi.cValues;
122             scode = lpMore(ulLen, lpOrig, (LPVOID*)&lpDest->Value.MVi.lpi);
123             if (FAILED(scode))
124                 break;
125 
126             /* Note that we could allocate the memory for each value in a
127              * multi-value property separately, however if an allocation failed
128              * we would be left with a bunch of allocated memory, which (while
129              * not really leaked) is unusable until lpOrig is freed. So for
130              * strings and binary arrays we make a single allocation for all
131              * of the data. This is consistent since individual elements can't
132              * be freed anyway.
133              */
134 
135             switch (PROP_TYPE(lpSrc->ulPropTag))
136             {
137             case PT_MV_STRING8:
138             {
139                 char *lpNextStr = (char*)(lpDest->Value.MVszA.lppszA +
140                                           lpDest->Value.MVszA.cValues);
141 
142                 for (i = 0; i < lpSrc->Value.MVszA.cValues; i++)
143                 {
144                     ULONG ulStrLen = lstrlenA(lpSrc->Value.MVszA.lppszA[i]) + 1u;
145 
146                     lpDest->Value.MVszA.lppszA[i] = lpNextStr;
147                     memcpy(lpNextStr, lpSrc->Value.MVszA.lppszA[i], ulStrLen);
148                     lpNextStr += ulStrLen;
149                 }
150                 break;
151             }
152             case PT_MV_UNICODE:
153             {
154                 WCHAR *lpNextStr = (WCHAR*)(lpDest->Value.MVszW.lppszW +
155                                             lpDest->Value.MVszW.cValues);
156 
157                 for (i = 0; i < lpSrc->Value.MVszW.cValues; i++)
158                 {
159                     ULONG ulStrLen = strlenW(lpSrc->Value.MVszW.lppszW[i]) + 1u;
160 
161                     lpDest->Value.MVszW.lppszW[i] = lpNextStr;
162                     memcpy(lpNextStr, lpSrc->Value.MVszW.lppszW[i], ulStrLen * sizeof(WCHAR));
163                     lpNextStr += ulStrLen;
164                 }
165                 break;
166             }
167             case PT_MV_BINARY:
168             {
169                 LPBYTE lpNext = (LPBYTE)(lpDest->Value.MVbin.lpbin +
170                                          lpDest->Value.MVbin.cValues);
171 
172                 for (i = 0; i < lpSrc->Value.MVszW.cValues; i++)
173                 {
174                     lpDest->Value.MVbin.lpbin[i].cb = lpSrc->Value.MVbin.lpbin[i].cb;
175                     lpDest->Value.MVbin.lpbin[i].lpb = lpNext;
176                     memcpy(lpNext, lpSrc->Value.MVbin.lpbin[i].lpb, lpDest->Value.MVbin.lpbin[i].cb);
177                     lpNext += lpDest->Value.MVbin.lpbin[i].cb;
178                 }
179                 break;
180             }
181             default:
182                 /* No embedded pointers, just copy the data over */
183                 memcpy(lpDest->Value.MVi.lpi, lpSrc->Value.MVi.lpi, ulLen);
184                 break;
185             }
186             break;
187         }
188     }
189     return scode;
190 }
191 
192 /*************************************************************************
193  * UlPropSize@4 (MAPI32.77)
194  *
195  * Determine the size of a property in bytes.
196  *
197  * PARAMS
198  *  lpProp [I] Property to determine the size of
199  *
200  * RETURNS
201  *  Success: The size of the value in lpProp.
202  *  Failure: 0, if a multi-value (array) property is invalid or the type of lpProp
203  *           is unknown.
204  *
205  * NOTES
206  *  - The size returned does not include the size of the SPropValue struct
207  *    or the size of the array of pointers for multi-valued properties that
208  *    contain pointers (such as PT_MV_STRING8 or PT-MV_UNICODE).
209  *  - MSDN incorrectly states that this function returns MAPI_E_CALL_FAILED if
210  *    lpProp is invalid. In reality no checking is performed and this function
211  *    will crash if passed an invalid property, or return 0 if the property
212  *    type is PT_OBJECT or is unknown.
213  */
214 ULONG WINAPI UlPropSize(LPSPropValue lpProp)
215 {
216     ULONG ulRet = 1u, i;
217 
218     TRACE("(%p)\n", lpProp);
219 
220     switch (PROP_TYPE(lpProp->ulPropTag))
221     {
222     case PT_MV_I2:       ulRet = lpProp->Value.MVi.cValues;
223                          /* fall through */
224     case PT_BOOLEAN:
225     case PT_I2:          ulRet *= sizeof(USHORT);
226                          break;
227     case PT_MV_I4:       ulRet = lpProp->Value.MVl.cValues;
228                          /* fall through */
229     case PT_ERROR:
230     case PT_I4:          ulRet *= sizeof(LONG);
231                          break;
232     case PT_MV_I8:       ulRet = lpProp->Value.MVli.cValues;
233                          /* fall through */
234     case PT_I8:          ulRet *= sizeof(LONG64);
235                          break;
236     case PT_MV_R4:       ulRet = lpProp->Value.MVflt.cValues;
237                          /* fall through */
238     case PT_R4:          ulRet *= sizeof(float);
239                          break;
240     case PT_MV_APPTIME:
241     case PT_MV_R8:       ulRet = lpProp->Value.MVdbl.cValues;
242                          /* fall through */
243     case PT_APPTIME:
244     case PT_R8:          ulRet *= sizeof(double);
245                          break;
246     case PT_MV_CURRENCY: ulRet = lpProp->Value.MVcur.cValues;
247                          /* fall through */
248     case PT_CURRENCY:    ulRet *= sizeof(CY);
249                          break;
250     case PT_MV_SYSTIME:  ulRet = lpProp->Value.MVft.cValues;
251                          /* fall through */
252     case PT_SYSTIME:     ulRet *= sizeof(FILETIME);
253                          break;
254     case PT_MV_CLSID:    ulRet = lpProp->Value.MVguid.cValues;
255                          /* fall through */
256     case PT_CLSID:       ulRet *= sizeof(GUID);
257                          break;
258     case PT_MV_STRING8:  ulRet = 0u;
259                          for (i = 0; i < lpProp->Value.MVszA.cValues; i++)
260                              ulRet += (lstrlenA(lpProp->Value.MVszA.lppszA[i]) + 1u);
261                          break;
262     case PT_STRING8:     ulRet = lstrlenA(lpProp->Value.lpszA) + 1u;
263                          break;
264     case PT_MV_UNICODE:  ulRet = 0u;
265                          for (i = 0; i < lpProp->Value.MVszW.cValues; i++)
266                              ulRet += (strlenW(lpProp->Value.MVszW.lppszW[i]) + 1u);
267                          ulRet *= sizeof(WCHAR);
268                          break;
269     case PT_UNICODE:     ulRet = (lstrlenW(lpProp->Value.lpszW) + 1u) * sizeof(WCHAR);
270                          break;
271     case PT_MV_BINARY:   ulRet = 0u;
272                          for (i = 0; i < lpProp->Value.MVbin.cValues; i++)
273                              ulRet += lpProp->Value.MVbin.lpbin[i].cb;
274                          break;
275     case PT_BINARY:      ulRet = lpProp->Value.bin.cb;
276                          break;
277     case PT_OBJECT:
278     default:             ulRet = 0u;
279                          break;
280     }
281 
282     return ulRet;
283 }
284 
285 /*************************************************************************
286  * FPropContainsProp@12 (MAPI32.78)
287  *
288  * Find a property with a given property tag in a property array.
289  *
290  * PARAMS
291  *  lpHaystack [I] Property to match to
292  *  lpNeedle   [I] Property to find in lpHaystack
293  *  ulFuzzy    [I] Flags controlling match type and strictness (FL_* flags from "mapidefs.h")
294  *
295  * RETURNS
296  *  TRUE, if lpNeedle matches lpHaystack according to the criteria of ulFuzzy.
297  *
298  * NOTES
299  *  Only property types of PT_STRING8 and PT_BINARY are handled by this function.
300  */
301 BOOL WINAPI FPropContainsProp(LPSPropValue lpHaystack, LPSPropValue lpNeedle, ULONG ulFuzzy)
302 {
303     TRACE("(%p,%p,0x%08x)\n", lpHaystack, lpNeedle, ulFuzzy);
304 
305     if (FBadProp(lpHaystack) || FBadProp(lpNeedle) ||
306         PROP_TYPE(lpHaystack->ulPropTag) != PROP_TYPE(lpNeedle->ulPropTag))
307         return FALSE;
308 
309     /* FIXME: Do later versions support Unicode as well? */
310 
311     if (PROP_TYPE(lpHaystack->ulPropTag) == PT_STRING8)
312     {
313         DWORD dwFlags = 0, dwNeedleLen, dwHaystackLen;
314 
315         if (ulFuzzy & FL_IGNORECASE)
316             dwFlags |= NORM_IGNORECASE;
317         if (ulFuzzy & FL_IGNORENONSPACE)
318             dwFlags |= NORM_IGNORENONSPACE;
319         if (ulFuzzy & FL_LOOSE)
320             dwFlags |= (NORM_IGNORECASE|NORM_IGNORENONSPACE|NORM_IGNORESYMBOLS);
321 
322         dwNeedleLen = lstrlenA(lpNeedle->Value.lpszA);
323         dwHaystackLen = lstrlenA(lpHaystack->Value.lpszA);
324 
325         if ((ulFuzzy & (FL_SUBSTRING|FL_PREFIX)) == FL_PREFIX)
326         {
327             if (dwNeedleLen <= dwHaystackLen &&
328                 CompareStringA(LOCALE_USER_DEFAULT, dwFlags,
329                                lpHaystack->Value.lpszA, dwNeedleLen,
330                                lpNeedle->Value.lpszA, dwNeedleLen) == CSTR_EQUAL)
331                 return TRUE; /* needle is a prefix of haystack */
332         }
333         else if ((ulFuzzy & (FL_SUBSTRING|FL_PREFIX)) == FL_SUBSTRING)
334         {
335             LPSTR (WINAPI *pStrChrFn)(LPCSTR,WORD) = StrChrA;
336             LPSTR lpStr = lpHaystack->Value.lpszA;
337 
338             if (dwFlags & NORM_IGNORECASE)
339                 pStrChrFn = StrChrIA;
340 
341             while ((lpStr = pStrChrFn(lpStr, *lpNeedle->Value.lpszA)) != NULL)
342             {
343                 dwHaystackLen -= (lpStr - lpHaystack->Value.lpszA);
344                 if (dwNeedleLen <= dwHaystackLen &&
345                     CompareStringA(LOCALE_USER_DEFAULT, dwFlags,
346                                lpStr, dwNeedleLen,
347                                lpNeedle->Value.lpszA, dwNeedleLen) == CSTR_EQUAL)
348                     return TRUE; /* needle is a substring of haystack */
349                 lpStr++;
350             }
351         }
352         else if (CompareStringA(LOCALE_USER_DEFAULT, dwFlags,
353                                 lpHaystack->Value.lpszA, dwHaystackLen,
354                                 lpNeedle->Value.lpszA, dwNeedleLen) == CSTR_EQUAL)
355             return TRUE; /* full string match */
356     }
357     else if (PROP_TYPE(lpHaystack->ulPropTag) == PT_BINARY)
358     {
359         if ((ulFuzzy & (FL_SUBSTRING|FL_PREFIX)) == FL_PREFIX)
360         {
361             if (lpNeedle->Value.bin.cb <= lpHaystack->Value.bin.cb &&
362                 !memcmp(lpNeedle->Value.bin.lpb, lpHaystack->Value.bin.lpb,
363                         lpNeedle->Value.bin.cb))
364                 return TRUE; /* needle is a prefix of haystack */
365         }
366         else if ((ulFuzzy & (FL_SUBSTRING|FL_PREFIX)) == FL_SUBSTRING)
367         {
368             ULONG ulLen = lpHaystack->Value.bin.cb;
369             LPBYTE lpb = lpHaystack->Value.bin.lpb;
370 
371             while ((lpb = memchr(lpb, *lpNeedle->Value.bin.lpb, ulLen)) != NULL)
372             {
373                 ulLen = lpHaystack->Value.bin.cb - (lpb - lpHaystack->Value.bin.lpb);
374                 if (lpNeedle->Value.bin.cb <= ulLen &&
375                     !memcmp(lpNeedle->Value.bin.lpb, lpb, lpNeedle->Value.bin.cb))
376                     return TRUE; /* needle is a substring of haystack */
377                 lpb++;
378             }
379         }
380         else if (!LPropCompareProp(lpHaystack, lpNeedle))
381             return TRUE; /* needle is an exact match with haystack */
382 
383     }
384     return FALSE;
385 }
386 
387 /*************************************************************************
388  * FPropCompareProp@12 (MAPI32.79)
389  *
390  * Compare two properties.
391  *
392  * PARAMS
393  *  lpPropLeft  [I] Left hand property to compare to lpPropRight
394  *  ulOp        [I] Comparison operator (RELOP_* enum from "mapidefs.h")
395  *  lpPropRight [I] Right hand property to compare to lpPropLeft
396  *
397  * RETURNS
398  *  TRUE, if the comparison is true, FALSE otherwise.
399  */
400 BOOL WINAPI FPropCompareProp(LPSPropValue lpPropLeft, ULONG ulOp, LPSPropValue lpPropRight)
401 {
402     LONG iCmp;
403 
404     TRACE("(%p,%d,%p)\n", lpPropLeft, ulOp, lpPropRight);
405 
406     if (ulOp > RELOP_RE || FBadProp(lpPropLeft) || FBadProp(lpPropRight))
407         return FALSE;
408 
409     if (ulOp == RELOP_RE)
410     {
411         FIXME("Comparison operator RELOP_RE not yet implemented!\n");
412         return FALSE;
413     }
414 
415     iCmp = LPropCompareProp(lpPropLeft, lpPropRight);
416 
417     switch (ulOp)
418     {
419     case RELOP_LT: return iCmp <  0;
420     case RELOP_LE: return iCmp <= 0;
421     case RELOP_GT: return iCmp >  0;
422     case RELOP_GE: return iCmp >= 0;
423     case RELOP_EQ: return iCmp == 0;
424     case RELOP_NE: return iCmp != 0;
425     }
426     return FALSE;
427 }
428 
429 /*************************************************************************
430  * LPropCompareProp@8 (MAPI32.80)
431  *
432  * Compare two properties.
433  *
434  * PARAMS
435  *  lpPropLeft  [I] Left hand property to compare to lpPropRight
436  *  lpPropRight [I] Right hand property to compare to lpPropLeft
437  *
438  * RETURNS
439  *  An integer less than, equal to or greater than 0, indicating that
440  *  lpszStr is less than, the same, or greater than lpszComp.
441  */
442 LONG WINAPI LPropCompareProp(LPSPropValue lpPropLeft, LPSPropValue lpPropRight)
443 {
444     LONG iRet;
445 
446     TRACE("(%p->0x%08x,%p->0x%08x)\n", lpPropLeft, lpPropLeft->ulPropTag,
447           lpPropRight, lpPropRight->ulPropTag);
448 
449     /* If the properties are not the same, sort by property type */
450     if (PROP_TYPE(lpPropLeft->ulPropTag) != PROP_TYPE(lpPropRight->ulPropTag))
451         return (LONG)PROP_TYPE(lpPropLeft->ulPropTag) - (LONG)PROP_TYPE(lpPropRight->ulPropTag);
452 
453     switch (PROP_TYPE(lpPropLeft->ulPropTag))
454     {
455     case PT_UNSPECIFIED:
456     case PT_NULL:
457         return 0; /* NULLs are equal */
458     case PT_I2:
459         return lpPropLeft->Value.i - lpPropRight->Value.i;
460     case PT_I4:
461         return lpPropLeft->Value.l - lpPropRight->Value.l;
462     case PT_I8:
463         if (lpPropLeft->Value.li.QuadPart > lpPropRight->Value.li.QuadPart)
464             return 1;
465         if (lpPropLeft->Value.li.QuadPart == lpPropRight->Value.li.QuadPart)
466             return 0;
467         return -1;
468     case PT_R4:
469         if (lpPropLeft->Value.flt > lpPropRight->Value.flt)
470             return 1;
471         if (lpPropLeft->Value.flt == lpPropRight->Value.flt)
472             return 0;
473         return -1;
474     case PT_APPTIME:
475     case PT_R8:
476         if (lpPropLeft->Value.dbl > lpPropRight->Value.dbl)
477             return 1;
478         if (lpPropLeft->Value.dbl == lpPropRight->Value.dbl)
479             return 0;
480         return -1;
481     case PT_CURRENCY:
482         if (lpPropLeft->Value.cur.int64 > lpPropRight->Value.cur.int64)
483             return 1;
484         if (lpPropLeft->Value.cur.int64 == lpPropRight->Value.cur.int64)
485             return 0;
486         return -1;
487     case PT_SYSTIME:
488         return CompareFileTime(&lpPropLeft->Value.ft, &lpPropRight->Value.ft);
489     case PT_BOOLEAN:
490         return (lpPropLeft->Value.b ? 1 : 0) - (lpPropRight->Value.b ? 1 : 0);
491     case PT_BINARY:
492         if (lpPropLeft->Value.bin.cb == lpPropRight->Value.bin.cb)
493             iRet = memcmp(lpPropLeft->Value.bin.lpb, lpPropRight->Value.bin.lpb,
494                           lpPropLeft->Value.bin.cb);
495         else
496         {
497             iRet = memcmp(lpPropLeft->Value.bin.lpb, lpPropRight->Value.bin.lpb,
498                           min(lpPropLeft->Value.bin.cb, lpPropRight->Value.bin.cb));
499 
500             if (!iRet)
501                 iRet = lpPropLeft->Value.bin.cb - lpPropRight->Value.bin.cb;
502         }
503         return iRet;
504     case PT_STRING8:
505         return lstrcmpA(lpPropLeft->Value.lpszA, lpPropRight->Value.lpszA);
506     case PT_UNICODE:
507         return strcmpW(lpPropLeft->Value.lpszW, lpPropRight->Value.lpszW);
508     case PT_ERROR:
509         if (lpPropLeft->Value.err > lpPropRight->Value.err)
510             return 1;
511         if (lpPropLeft->Value.err == lpPropRight->Value.err)
512             return 0;
513         return -1;
514     case PT_CLSID:
515         return memcmp(lpPropLeft->Value.lpguid, lpPropRight->Value.lpguid,
516                       sizeof(GUID));
517     }
518     FIXME("Unhandled property type %d\n", PROP_TYPE(lpPropLeft->ulPropTag));
519     return 0;
520 }
521 
522 /*************************************************************************
523  * HrGetOneProp@8 (MAPI32.135)
524  *
525  * Get a property value from an IMAPIProp object.
526  *
527  * PARAMS
528  *  lpIProp   [I] IMAPIProp object to get the property value in
529  *  ulPropTag [I] Property tag of the property to get
530  *  lppProp   [O] Destination for the returned property
531  *
532  * RETURNS
533  *  Success: S_OK. *lppProp contains the property value requested.
534  *  Failure: MAPI_E_NOT_FOUND, if no property value has the tag given by ulPropTag.
535  */
536 HRESULT WINAPI HrGetOneProp(LPMAPIPROP lpIProp, ULONG ulPropTag, LPSPropValue *lppProp)
537 {
538     SPropTagArray pta;
539     ULONG ulCount;
540     HRESULT hRet;
541 
542     TRACE("(%p,%d,%p)\n", lpIProp, ulPropTag, lppProp);
543 
544     pta.cValues = 1u;
545     pta.aulPropTag[0] = ulPropTag;
546     hRet = IMAPIProp_GetProps(lpIProp, &pta, 0u, &ulCount, lppProp);
547     if (hRet == MAPI_W_ERRORS_RETURNED)
548     {
549         MAPIFreeBuffer(*lppProp);
550         *lppProp = NULL;
551         hRet = MAPI_E_NOT_FOUND;
552     }
553     return hRet;
554 }
555 
556 /*************************************************************************
557  * HrSetOneProp@8 (MAPI32.136)
558  *
559  * Set a property value in an IMAPIProp object.
560  *
561  * PARAMS
562  *  lpIProp [I] IMAPIProp object to set the property value in
563  *  lpProp  [I] Property value to set
564  *
565  * RETURNS
566  *  Success: S_OK. The value in lpProp is set in lpIProp.
567  *  Failure: An error result from IMAPIProp_SetProps().
568  */
569 HRESULT WINAPI HrSetOneProp(LPMAPIPROP lpIProp, LPSPropValue lpProp)
570 {
571     TRACE("(%p,%p)\n", lpIProp, lpProp);
572 
573     return IMAPIProp_SetProps(lpIProp, 1u, lpProp, NULL);
574 }
575 
576 /*************************************************************************
577  * FPropExists@8 (MAPI32.137)
578  *
579  * Find a property with a given property tag in an IMAPIProp object.
580  *
581  * PARAMS
582  *  lpIProp   [I] IMAPIProp object to find the property tag in
583  *  ulPropTag [I] Property tag to find
584  *
585  * RETURNS
586  *  TRUE, if ulPropTag matches a property held in lpIProp,
587  *  FALSE, otherwise.
588  *
589  * NOTES
590  *  if ulPropTag has a property type of PT_UNSPECIFIED, then only the property
591  *  Ids need to match for a successful match to occur.
592  */
593  BOOL WINAPI FPropExists(LPMAPIPROP lpIProp, ULONG ulPropTag)
594  {
595     BOOL bRet = FALSE;
596 
597     TRACE("(%p,%d)\n", lpIProp, ulPropTag);
598 
599     if (lpIProp)
600     {
601         LPSPropTagArray lpTags;
602         ULONG i;
603 
604         if (FAILED(IMAPIProp_GetPropList(lpIProp, 0u, &lpTags)))
605             return FALSE;
606 
607         for (i = 0; i < lpTags->cValues; i++)
608         {
609             if (!FBadPropTag(lpTags->aulPropTag[i]) &&
610                 (lpTags->aulPropTag[i] == ulPropTag ||
611                  (PROP_TYPE(ulPropTag) == PT_UNSPECIFIED &&
612                   PROP_ID(lpTags->aulPropTag[i]) == lpTags->aulPropTag[i])))
613             {
614                 bRet = TRUE;
615                 break;
616             }
617         }
618         MAPIFreeBuffer(lpTags);
619     }
620     return bRet;
621 }
622 
623 /*************************************************************************
624  * PpropFindProp@12 (MAPI32.138)
625  *
626  * Find a property with a given property tag in a property array.
627  *
628  * PARAMS
629  *  lpProps   [I] Property array to search
630  *  cValues   [I] Number of properties in lpProps
631  *  ulPropTag [I] Property tag to find
632  *
633  * RETURNS
634  *  A pointer to the matching property, or NULL if none was found.
635  *
636  * NOTES
637  *  if ulPropTag has a property type of PT_UNSPECIFIED, then only the property
638  *  Ids need to match for a successful match to occur.
639  */
640 LPSPropValue WINAPI PpropFindProp(LPSPropValue lpProps, ULONG cValues, ULONG ulPropTag)
641 {
642     TRACE("(%p,%d,%d)\n", lpProps, cValues, ulPropTag);
643 
644     if (lpProps && cValues)
645     {
646         ULONG i;
647         for (i = 0; i < cValues; i++)
648         {
649             if (!FBadPropTag(lpProps[i].ulPropTag) &&
650                 (lpProps[i].ulPropTag == ulPropTag ||
651                  (PROP_TYPE(ulPropTag) == PT_UNSPECIFIED &&
652                   PROP_ID(lpProps[i].ulPropTag) == PROP_ID(ulPropTag))))
653                 return &lpProps[i];
654         }
655     }
656     return NULL;
657 }
658 
659 /*************************************************************************
660  * FreePadrlist@4 (MAPI32.139)
661  *
662  * Free the memory used by an address book list.
663  *
664  * PARAMS
665  *  lpAddrs [I] Address book list to free
666  *
667  * RETURNS
668  *  Nothing.
669  */
670 VOID WINAPI FreePadrlist(LPADRLIST lpAddrs)
671 {
672     TRACE("(%p)\n", lpAddrs);
673 
674     /* Structures are binary compatible; use the same implementation */
675     FreeProws((LPSRowSet)lpAddrs);
676 }
677 
678 /*************************************************************************
679  * FreeProws@4 (MAPI32.140)
680  *
681  * Free the memory used by a row set.
682  *
683  * PARAMS
684  *  lpRowSet [I] Row set to free
685  *
686  * RETURNS
687  *  Nothing.
688  */
689 VOID WINAPI FreeProws(LPSRowSet lpRowSet)
690 {
691     TRACE("(%p)\n", lpRowSet);
692 
693     if (lpRowSet)
694     {
695         ULONG i;
696 
697         for (i = 0; i < lpRowSet->cRows; i++)
698             MAPIFreeBuffer(lpRowSet->aRow[i].lpProps);
699 
700         MAPIFreeBuffer(lpRowSet);
701     }
702 }
703 
704 /*************************************************************************
705  * ScCountProps@12 (MAPI32.170)
706  *
707  * Validate and determine the length of an array of properties.
708  *
709  * PARAMS
710  *  iCount  [I] Length of the lpProps array
711  *  lpProps [I] Array of properties to validate/size
712  *  pcBytes [O] If non-NULL, destination for the size of the property array
713  *
714  * RETURNS
715  *  Success: S_OK. If pcBytes is non-NULL, it contains the size of the
716  *           properties array.
717  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid or validation
718  *           of the property array fails.
719  */
720 SCODE WINAPI ScCountProps(INT iCount, LPSPropValue lpProps, ULONG *pcBytes)
721 {
722     ULONG i, ulCount = iCount, ulBytes = 0;
723 
724     TRACE("(%d,%p,%p)\n", iCount, lpProps, pcBytes);
725 
726     if (iCount <= 0 || !lpProps ||
727         IsBadReadPtr(lpProps, iCount * sizeof(SPropValue)))
728         return MAPI_E_INVALID_PARAMETER;
729 
730     for (i = 0; i < ulCount; i++)
731     {
732         ULONG ulPropSize = 0;
733 
734         if (FBadProp(&lpProps[i]) || lpProps[i].ulPropTag == PROP_ID_NULL ||
735             lpProps[i].ulPropTag == PROP_ID_INVALID)
736             return MAPI_E_INVALID_PARAMETER;
737 
738         if (PROP_TYPE(lpProps[i].ulPropTag) != PT_OBJECT)
739         {
740             ulPropSize = UlPropSize(&lpProps[i]);
741             if (!ulPropSize)
742                 return MAPI_E_INVALID_PARAMETER;
743         }
744 
745         switch (PROP_TYPE(lpProps[i].ulPropTag))
746         {
747         case PT_STRING8:
748         case PT_UNICODE:
749         case PT_CLSID:
750         case PT_BINARY:
751         case PT_MV_I2:
752         case PT_MV_I4:
753         case PT_MV_I8:
754         case PT_MV_R4:
755         case PT_MV_R8:
756         case PT_MV_CURRENCY:
757         case PT_MV_SYSTIME:
758         case PT_MV_APPTIME:
759             ulPropSize += sizeof(SPropValue);
760             break;
761         case PT_MV_CLSID:
762             ulPropSize += lpProps[i].Value.MVszA.cValues * sizeof(char*) + sizeof(SPropValue);
763             break;
764         case PT_MV_STRING8:
765         case PT_MV_UNICODE:
766             ulPropSize += lpProps[i].Value.MVszA.cValues * sizeof(char*) + sizeof(SPropValue);
767             break;
768         case PT_MV_BINARY:
769             ulPropSize += lpProps[i].Value.MVbin.cValues * sizeof(SBinary) + sizeof(SPropValue);
770             break;
771         default:
772             ulPropSize = sizeof(SPropValue);
773             break;
774         }
775         ulBytes += ulPropSize;
776     }
777     if (pcBytes)
778         *pcBytes = ulBytes;
779 
780     return S_OK;
781 }
782 
783 /*************************************************************************
784  * ScCopyProps@16 (MAPI32.171)
785  *
786  * Copy an array of property values into a buffer suited for serialisation.
787  *
788  * PARAMS
789  *  cValues   [I] Number of properties in lpProps
790  *  lpProps   [I] Property array to copy
791  *  lpDst     [O] Destination for the serialised data
792  *  lpCount   [O] If non-NULL, destination for the number of bytes of data written to lpDst
793  *
794  * RETURNS
795  *  Success: S_OK. lpDst contains the serialised data from lpProps.
796  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
797  *
798  * NOTES
799  *  The resulting property value array is stored in a contiguous block starting at lpDst.
800  */
801 SCODE WINAPI ScCopyProps(int cValues, LPSPropValue lpProps, LPVOID lpDst, ULONG *lpCount)
802 {
803     LPSPropValue lpDest = (LPSPropValue)lpDst;
804     char *lpDataDest = (char *)(lpDest + cValues);
805     ULONG ulLen, i;
806     int iter;
807 
808     TRACE("(%d,%p,%p,%p)\n", cValues, lpProps, lpDst, lpCount);
809 
810     if (!lpProps || cValues < 0 || !lpDest)
811         return MAPI_E_INVALID_PARAMETER;
812 
813     memcpy(lpDst, lpProps, cValues * sizeof(SPropValue));
814 
815     for (iter = 0; iter < cValues; iter++)
816     {
817         switch (PROP_TYPE(lpProps->ulPropTag))
818         {
819         case PT_CLSID:
820             lpDest->Value.lpguid = (LPGUID)lpDataDest;
821             *lpDest->Value.lpguid = *lpProps->Value.lpguid;
822             lpDataDest += sizeof(GUID);
823             break;
824         case PT_STRING8:
825             ulLen = lstrlenA(lpProps->Value.lpszA) + 1u;
826             lpDest->Value.lpszA = lpDataDest;
827             memcpy(lpDest->Value.lpszA, lpProps->Value.lpszA, ulLen);
828             lpDataDest += ulLen;
829             break;
830         case PT_UNICODE:
831             ulLen = (strlenW(lpProps->Value.lpszW) + 1u) * sizeof(WCHAR);
832             lpDest->Value.lpszW = (LPWSTR)lpDataDest;
833             memcpy(lpDest->Value.lpszW, lpProps->Value.lpszW, ulLen);
834             lpDataDest += ulLen;
835             break;
836         case PT_BINARY:
837             lpDest->Value.bin.lpb = (LPBYTE)lpDataDest;
838             memcpy(lpDest->Value.bin.lpb, lpProps->Value.bin.lpb, lpProps->Value.bin.cb);
839             lpDataDest += lpProps->Value.bin.cb;
840             break;
841         default:
842             if (lpProps->ulPropTag & MV_FLAG)
843             {
844                 lpDest->Value.MVi.cValues = lpProps->Value.MVi.cValues;
845                 /* Note: Assignment uses lppszA but covers all cases by union aliasing */
846                 lpDest->Value.MVszA.lppszA = (char**)lpDataDest;
847 
848                 switch (PROP_TYPE(lpProps->ulPropTag))
849                 {
850                 case PT_MV_STRING8:
851                 {
852                     lpDataDest += lpProps->Value.MVszA.cValues * sizeof(char *);
853 
854                     for (i = 0; i < lpProps->Value.MVszA.cValues; i++)
855                     {
856                         ULONG ulStrLen = lstrlenA(lpProps->Value.MVszA.lppszA[i]) + 1u;
857 
858                         lpDest->Value.MVszA.lppszA[i] = lpDataDest;
859                         memcpy(lpDataDest, lpProps->Value.MVszA.lppszA[i], ulStrLen);
860                         lpDataDest += ulStrLen;
861                     }
862                     break;
863                 }
864                 case PT_MV_UNICODE:
865                 {
866                     lpDataDest += lpProps->Value.MVszW.cValues * sizeof(WCHAR *);
867 
868                     for (i = 0; i < lpProps->Value.MVszW.cValues; i++)
869                     {
870                         ULONG ulStrLen = (strlenW(lpProps->Value.MVszW.lppszW[i]) + 1u) * sizeof(WCHAR);
871 
872                         lpDest->Value.MVszW.lppszW[i] = (LPWSTR)lpDataDest;
873                         memcpy(lpDataDest, lpProps->Value.MVszW.lppszW[i], ulStrLen);
874                         lpDataDest += ulStrLen;
875                     }
876                     break;
877                 }
878                 case PT_MV_BINARY:
879                 {
880                     lpDataDest += lpProps->Value.MVszW.cValues * sizeof(SBinary);
881 
882                     for (i = 0; i < lpProps->Value.MVszW.cValues; i++)
883                     {
884                         lpDest->Value.MVbin.lpbin[i].cb = lpProps->Value.MVbin.lpbin[i].cb;
885                         lpDest->Value.MVbin.lpbin[i].lpb = (LPBYTE)lpDataDest;
886                         memcpy(lpDataDest, lpProps->Value.MVbin.lpbin[i].lpb, lpDest->Value.MVbin.lpbin[i].cb);
887                         lpDataDest += lpDest->Value.MVbin.lpbin[i].cb;
888                     }
889                     break;
890                 }
891                 default:
892                     /* No embedded pointers, just copy the data over */
893                     ulLen = UlPropSize(lpProps);
894                     memcpy(lpDest->Value.MVi.lpi, lpProps->Value.MVi.lpi, ulLen);
895                     lpDataDest += ulLen;
896                     break;
897                 }
898                 break;
899             }
900         }
901         lpDest++;
902         lpProps++;
903     }
904     if (lpCount)
905         *lpCount = lpDataDest - (char *)lpDst;
906 
907     return S_OK;
908 }
909 
910 /*************************************************************************
911  * ScRelocProps@20 (MAPI32.172)
912  *
913  * Relocate the pointers in an array of property values after it has been copied.
914  *
915  * PARAMS
916  *  cValues   [I] Number of properties in lpProps
917  *  lpProps   [O] Property array to relocate the pointers in.
918  *  lpOld     [I] Position where the data was copied from
919  *  lpNew     [I] Position where the data was copied to
920  *  lpCount   [O] If non-NULL, destination for the number of bytes of data at lpDst
921  *
922  * RETURNS
923  *  Success: S_OK. Any pointers in lpProps are relocated.
924  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
925  *
926  * NOTES
927  *  MSDN states that this function can be used for serialisation by passing
928  *  NULL as either lpOld or lpNew, thus converting any pointers in lpProps
929  *  between offsets and pointers. This does not work in native (it crashes),
930  *  and cannot be made to work in Wine because the original interface design
931  *  is deficient. The only use left for this function is to remap pointers
932  *  in a contiguous property array that has been copied with memcpy() to
933  *  another memory location.
934  */
935 SCODE WINAPI ScRelocProps(int cValues, LPSPropValue lpProps, LPVOID lpOld,
936                           LPVOID lpNew, ULONG *lpCount)
937 {
938     static const BOOL bBadPtr = TRUE; /* Windows bug - Assumes source is bad */
939     LPSPropValue lpDest = lpProps;
940     ULONG ulCount = cValues * sizeof(SPropValue);
941     ULONG ulLen, i;
942     int iter;
943 
944     TRACE("(%d,%p,%p,%p,%p)\n", cValues, lpProps, lpOld, lpNew, lpCount);
945 
946     if (!lpProps || cValues < 0 || !lpOld || !lpNew)
947         return MAPI_E_INVALID_PARAMETER;
948 
949     /* The reason native doesn't work as MSDN states is that it assumes that
950      * the lpProps pointer contains valid pointers. This is obviously not
951      * true if the array is being read back from serialisation (the pointers
952      * are just offsets). Native can't actually work converting the pointers to
953      * offsets either, because it converts any array pointers to offsets then
954      * _dereferences the offset_ in order to convert the array elements!
955      *
956      * The code below would handle both cases except that the design of this
957      * function makes it impossible to know when the pointers in lpProps are
958      * valid. If both lpOld and lpNew are non-NULL, native reads the pointers
959      * after converting them, so we must do the same. It seems this
960      * functionality was never tested by MS.
961      */
962 
963 #define RELOC_PTR(p) (((char*)(p)) - (char*)lpOld + (char*)lpNew)
964 
965     for (iter = 0; iter < cValues; iter++)
966     {
967         switch (PROP_TYPE(lpDest->ulPropTag))
968         {
969         case PT_CLSID:
970             lpDest->Value.lpguid = (LPGUID)RELOC_PTR(lpDest->Value.lpguid);
971             ulCount += sizeof(GUID);
972             break;
973         case PT_STRING8:
974             ulLen = bBadPtr ? 0 : lstrlenA(lpDest->Value.lpszA) + 1u;
975             lpDest->Value.lpszA = RELOC_PTR(lpDest->Value.lpszA);
976             if (bBadPtr)
977                 ulLen = lstrlenA(lpDest->Value.lpszA) + 1u;
978             ulCount += ulLen;
979             break;
980         case PT_UNICODE:
981             ulLen = bBadPtr ? 0 : (lstrlenW(lpDest->Value.lpszW) + 1u) * sizeof(WCHAR);
982             lpDest->Value.lpszW = (LPWSTR)RELOC_PTR(lpDest->Value.lpszW);
983             if (bBadPtr)
984                 ulLen = (strlenW(lpDest->Value.lpszW) + 1u) * sizeof(WCHAR);
985             ulCount += ulLen;
986             break;
987         case PT_BINARY:
988             lpDest->Value.bin.lpb = (LPBYTE)RELOC_PTR(lpDest->Value.bin.lpb);
989             ulCount += lpDest->Value.bin.cb;
990             break;
991         default:
992             if (lpDest->ulPropTag & MV_FLAG)
993             {
994                 /* Since we have to access the array elements, don't map the
995                  * array unless it is invalid (otherwise, map it at the end)
996                  */
997                 if (bBadPtr)
998                     lpDest->Value.MVszA.lppszA = (LPSTR*)RELOC_PTR(lpDest->Value.MVszA.lppszA);
999 
1000                 switch (PROP_TYPE(lpProps->ulPropTag))
1001                 {
1002                 case PT_MV_STRING8:
1003                 {
1004                     ulCount += lpDest->Value.MVszA.cValues * sizeof(char *);
1005 
1006                     for (i = 0; i < lpDest->Value.MVszA.cValues; i++)
1007                     {
1008                         ULONG ulStrLen = bBadPtr ? 0 : lstrlenA(lpDest->Value.MVszA.lppszA[i]) + 1u;
1009 
1010                         lpDest->Value.MVszA.lppszA[i] = RELOC_PTR(lpDest->Value.MVszA.lppszA[i]);
1011                         if (bBadPtr)
1012                             ulStrLen = lstrlenA(lpDest->Value.MVszA.lppszA[i]) + 1u;
1013                         ulCount += ulStrLen;
1014                     }
1015                     break;
1016                 }
1017                 case PT_MV_UNICODE:
1018                 {
1019                     ulCount += lpDest->Value.MVszW.cValues * sizeof(WCHAR *);
1020 
1021                     for (i = 0; i < lpDest->Value.MVszW.cValues; i++)
1022                     {
1023                         ULONG ulStrLen = bBadPtr ? 0 : (strlenW(lpDest->Value.MVszW.lppszW[i]) + 1u) * sizeof(WCHAR);
1024 
1025                         lpDest->Value.MVszW.lppszW[i] = (LPWSTR)RELOC_PTR(lpDest->Value.MVszW.lppszW[i]);
1026                         if (bBadPtr)
1027                             ulStrLen = (strlenW(lpDest->Value.MVszW.lppszW[i]) + 1u) * sizeof(WCHAR);
1028                         ulCount += ulStrLen;
1029                     }
1030                     break;
1031                 }
1032                 case PT_MV_BINARY:
1033                 {
1034                     ulCount += lpDest->Value.MVszW.cValues * sizeof(SBinary);
1035 
1036                     for (i = 0; i < lpDest->Value.MVszW.cValues; i++)
1037                     {
1038                         lpDest->Value.MVbin.lpbin[i].lpb = (LPBYTE)RELOC_PTR(lpDest->Value.MVbin.lpbin[i].lpb);
1039                         ulCount += lpDest->Value.MVbin.lpbin[i].cb;
1040                     }
1041                     break;
1042                 }
1043                 default:
1044                     ulCount += UlPropSize(lpDest);
1045                     break;
1046                 }
1047                 if (!bBadPtr)
1048                     lpDest->Value.MVszA.lppszA = (LPSTR*)RELOC_PTR(lpDest->Value.MVszA.lppszA);
1049                 break;
1050             }
1051         }
1052         lpDest++;
1053     }
1054     if (lpCount)
1055         *lpCount = ulCount;
1056 
1057     return S_OK;
1058 }
1059 
1060 /*************************************************************************
1061  * LpValFindProp@12 (MAPI32.173)
1062  *
1063  * Find a property with a given property id in a property array.
1064  *
1065  * PARAMS
1066  *  ulPropTag [I] Property tag containing property id to find
1067  *  cValues   [I] Number of properties in lpProps
1068  *  lpProps   [I] Property array to search
1069  *
1070  * RETURNS
1071  *  A pointer to the matching property, or NULL if none was found.
1072  *
1073  * NOTES
1074  *  This function matches only on the property id and does not care if the
1075  *  property types differ.
1076  */
1077 LPSPropValue WINAPI LpValFindProp(ULONG ulPropTag, ULONG cValues, LPSPropValue lpProps)
1078 {
1079     TRACE("(%d,%d,%p)\n", ulPropTag, cValues, lpProps);
1080 
1081     if (lpProps && cValues)
1082     {
1083         ULONG i;
1084         for (i = 0; i < cValues; i++)
1085         {
1086             if (PROP_ID(ulPropTag) == PROP_ID(lpProps[i].ulPropTag))
1087                 return &lpProps[i];
1088         }
1089     }
1090     return NULL;
1091 }
1092 
1093 /*************************************************************************
1094  * ScDupPropset@16 (MAPI32.174)
1095  *
1096  * Duplicate a property value array into a contiguous block of memory.
1097  *
1098  * PARAMS
1099  *  cValues   [I] Number of properties in lpProps
1100  *  lpProps   [I] Property array to duplicate
1101  *  lpAlloc   [I] Memory allocation function, use MAPIAllocateBuffer()
1102  *  lpNewProp [O] Destination for the newly duplicated property value array
1103  *
1104  * RETURNS
1105  *  Success: S_OK. *lpNewProp contains the duplicated array.
1106  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid,
1107  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails.
1108  */
1109 SCODE WINAPI ScDupPropset(int cValues, LPSPropValue lpProps,
1110                           LPALLOCATEBUFFER lpAlloc, LPSPropValue *lpNewProp)
1111 {
1112     ULONG ulCount;
1113     SCODE sc;
1114 
1115     TRACE("(%d,%p,%p,%p)\n", cValues, lpProps, lpAlloc, lpNewProp);
1116 
1117     sc = ScCountProps(cValues, lpProps, &ulCount);
1118     if (SUCCEEDED(sc))
1119     {
1120         sc = lpAlloc(ulCount, (LPVOID*)lpNewProp);
1121         if (SUCCEEDED(sc))
1122             sc = ScCopyProps(cValues, lpProps, *lpNewProp, &ulCount);
1123     }
1124     return sc;
1125 }
1126 
1127 /*************************************************************************
1128  * FBadRglpszA@8 (MAPI32.175)
1129  *
1130  * Determine if an array of strings is invalid
1131  *
1132  * PARAMS
1133  *  lppszStrs [I] Array of strings to check
1134  *  ulCount   [I] Number of strings in lppszStrs
1135  *
1136  * RETURNS
1137  *  TRUE, if lppszStrs is invalid, FALSE otherwise.
1138  */
1139 BOOL WINAPI FBadRglpszA(LPSTR *lppszStrs, ULONG ulCount)
1140 {
1141     ULONG i;
1142 
1143     TRACE("(%p,%d)\n", lppszStrs, ulCount);
1144 
1145     if (!ulCount)
1146         return FALSE;
1147 
1148     if (!lppszStrs || IsBadReadPtr(lppszStrs, ulCount * sizeof(LPWSTR)))
1149         return TRUE;
1150 
1151     for (i = 0; i < ulCount; i++)
1152     {
1153         if (!lppszStrs[i] || IsBadStringPtrA(lppszStrs[i], -1))
1154             return TRUE;
1155     }
1156     return FALSE;
1157 }
1158 
1159 /*************************************************************************
1160  * FBadRglpszW@8 (MAPI32.176)
1161  *
1162  * See FBadRglpszA.
1163  */
1164 BOOL WINAPI FBadRglpszW(LPWSTR *lppszStrs, ULONG ulCount)
1165 {
1166     ULONG i;
1167 
1168     TRACE("(%p,%d)\n", lppszStrs, ulCount);
1169 
1170     if (!ulCount)
1171         return FALSE;
1172 
1173     if (!lppszStrs || IsBadReadPtr(lppszStrs, ulCount * sizeof(LPWSTR)))
1174         return TRUE;
1175 
1176     for (i = 0; i < ulCount; i++)
1177     {
1178         if (!lppszStrs[i] || IsBadStringPtrW(lppszStrs[i], -1))
1179             return TRUE;
1180     }
1181     return FALSE;
1182 }
1183 
1184 /*************************************************************************
1185  * FBadRowSet@4 (MAPI32.177)
1186  *
1187  * Determine if a row is invalid
1188  *
1189  * PARAMS
1190  *  lpRow [I] Row to check
1191  *
1192  * RETURNS
1193  *  TRUE, if lpRow is invalid, FALSE otherwise.
1194  */
1195 BOOL WINAPI FBadRowSet(LPSRowSet lpRowSet)
1196 {
1197     ULONG i;
1198     TRACE("(%p)\n", lpRowSet);
1199 
1200     if (!lpRowSet || IsBadReadPtr(lpRowSet, CbSRowSet(lpRowSet)))
1201         return TRUE;
1202 
1203     for (i = 0; i < lpRowSet->cRows; i++)
1204     {
1205         if (FBadRow(&lpRowSet->aRow[i]))
1206             return TRUE;
1207     }
1208     return FALSE;
1209 }
1210 
1211 /*************************************************************************
1212  * FBadPropTag@4 (MAPI32.179)
1213  *
1214  * Determine if a property tag is invalid
1215  *
1216  * PARAMS
1217  *  ulPropTag [I] Property tag to check
1218  *
1219  * RETURNS
1220  *  TRUE, if ulPropTag is invalid, FALSE otherwise.
1221  */
1222 ULONG WINAPI FBadPropTag(ULONG ulPropTag)
1223 {
1224     TRACE("(0x%08x)\n", ulPropTag);
1225 
1226     switch (ulPropTag & (~MV_FLAG & PROP_TYPE_MASK))
1227     {
1228     case PT_UNSPECIFIED:
1229     case PT_NULL:
1230     case PT_I2:
1231     case PT_LONG:
1232     case PT_R4:
1233     case PT_DOUBLE:
1234     case PT_CURRENCY:
1235     case PT_APPTIME:
1236     case PT_ERROR:
1237     case PT_BOOLEAN:
1238     case PT_OBJECT:
1239     case PT_I8:
1240     case PT_STRING8:
1241     case PT_UNICODE:
1242     case PT_SYSTIME:
1243     case PT_CLSID:
1244     case PT_BINARY:
1245         return FALSE;
1246     }
1247     return TRUE;
1248 }
1249 
1250 /*************************************************************************
1251  * FBadRow@4 (MAPI32.180)
1252  *
1253  * Determine if a row is invalid
1254  *
1255  * PARAMS
1256  *  lpRow [I] Row to check
1257  *
1258  * RETURNS
1259  *  TRUE, if lpRow is invalid, FALSE otherwise.
1260  */
1261 ULONG WINAPI FBadRow(LPSRow lpRow)
1262 {
1263     ULONG i;
1264     TRACE("(%p)\n", lpRow);
1265 
1266     if (!lpRow || IsBadReadPtr(lpRow, sizeof(SRow)) || !lpRow->lpProps ||
1267         IsBadReadPtr(lpRow->lpProps, lpRow->cValues * sizeof(SPropValue)))
1268         return TRUE;
1269 
1270     for (i = 0; i < lpRow->cValues; i++)
1271     {
1272         if (FBadProp(&lpRow->lpProps[i]))
1273             return TRUE;
1274     }
1275     return FALSE;
1276 }
1277 
1278 /*************************************************************************
1279  * FBadProp@4 (MAPI32.181)
1280  *
1281  * Determine if a property is invalid
1282  *
1283  * PARAMS
1284  *  lpProp [I] Property to check
1285  *
1286  * RETURNS
1287  *  TRUE, if lpProp is invalid, FALSE otherwise.
1288  */
1289 ULONG WINAPI FBadProp(LPSPropValue lpProp)
1290 {
1291     if (!lpProp || IsBadReadPtr(lpProp, sizeof(SPropValue)) ||
1292         FBadPropTag(lpProp->ulPropTag))
1293         return TRUE;
1294 
1295     switch (PROP_TYPE(lpProp->ulPropTag))
1296     {
1297     /* Single value properties containing pointers */
1298     case PT_STRING8:
1299         if (!lpProp->Value.lpszA || IsBadStringPtrA(lpProp->Value.lpszA, -1))
1300             return TRUE;
1301         break;
1302     case PT_UNICODE:
1303         if (!lpProp->Value.lpszW || IsBadStringPtrW(lpProp->Value.lpszW, -1))
1304             return TRUE;
1305         break;
1306     case PT_BINARY:
1307         if (IsBadReadPtr(lpProp->Value.bin.lpb, lpProp->Value.bin.cb))
1308             return TRUE;
1309         break;
1310     case PT_CLSID:
1311         if (IsBadReadPtr(lpProp->Value.lpguid, sizeof(GUID)))
1312             return TRUE;
1313         break;
1314 
1315     /* Multiple value properties (arrays) containing no pointers */
1316     case PT_MV_I2:
1317         return PROP_BadArray(lpProp, sizeof(SHORT));
1318     case PT_MV_LONG:
1319         return PROP_BadArray(lpProp, sizeof(LONG));
1320     case PT_MV_LONGLONG:
1321         return PROP_BadArray(lpProp, sizeof(LONG64));
1322     case PT_MV_FLOAT:
1323         return PROP_BadArray(lpProp, sizeof(float));
1324     case PT_MV_SYSTIME:
1325         return PROP_BadArray(lpProp, sizeof(FILETIME));
1326     case PT_MV_APPTIME:
1327     case PT_MV_DOUBLE:
1328         return PROP_BadArray(lpProp, sizeof(double));
1329     case PT_MV_CURRENCY:
1330         return PROP_BadArray(lpProp, sizeof(CY));
1331     case PT_MV_CLSID:
1332         return PROP_BadArray(lpProp, sizeof(GUID));
1333 
1334     /* Multiple value properties containing pointers */
1335     case PT_MV_STRING8:
1336         return FBadRglpszA(lpProp->Value.MVszA.lppszA,
1337                            lpProp->Value.MVszA.cValues);
1338     case PT_MV_UNICODE:
1339         return FBadRglpszW(lpProp->Value.MVszW.lppszW,
1340                            lpProp->Value.MVszW.cValues);
1341     case PT_MV_BINARY:
1342         return FBadEntryList(&lpProp->Value.MVbin);
1343     }
1344     return FALSE;
1345 }
1346 
1347 /*************************************************************************
1348  * FBadColumnSet@4 (MAPI32.182)
1349  *
1350  * Determine if an array of property tags is invalid
1351  *
1352  * PARAMS
1353  *  lpCols [I] Property tag array to check
1354  *
1355  * RETURNS
1356  *  TRUE, if lpCols is invalid, FALSE otherwise.
1357  */
1358 ULONG WINAPI FBadColumnSet(LPSPropTagArray lpCols)
1359 {
1360     ULONG ulRet = FALSE, i;
1361 
1362     TRACE("(%p)\n", lpCols);
1363 
1364     if (!lpCols || IsBadReadPtr(lpCols, CbSPropTagArray(lpCols)))
1365         ulRet = TRUE;
1366     else
1367     {
1368         for (i = 0; i < lpCols->cValues; i++)
1369         {
1370             if ((lpCols->aulPropTag[i] & PROP_TYPE_MASK) == PT_ERROR ||
1371                 FBadPropTag(lpCols->aulPropTag[i]))
1372             {
1373                 ulRet = TRUE;
1374                 break;
1375             }
1376         }
1377     }
1378     TRACE("Returning %s\n", ulRet ? "TRUE" : "FALSE");
1379     return ulRet;
1380 }
1381 
1382 
1383 /**************************************************************************
1384  *  IPropData {MAPI32}
1385  *
1386  * A default Mapi interface to provide manipulation of object properties.
1387  *
1388  * DESCRIPTION
1389  *  This object provides a default interface suitable in some cases as an
1390  *  implementation of the IMAPIProp interface (which has no default
1391  *  implementation). In addition to the IMAPIProp() methods inherited, this
1392  *  interface allows read/write control over access to the object and its
1393  *  individual properties.
1394  *
1395  *  To obtain the default implementation of this interface from Mapi, call
1396  *  CreateIProp().
1397  *
1398  * METHODS
1399  */
1400 
1401 /* A single property in a property data collection */
1402 typedef struct
1403 {
1404   struct list  entry;
1405   ULONG        ulAccess; /* The property value access level */
1406   LPSPropValue value;    /* The property value */
1407 } IPropDataItem, *LPIPropDataItem;
1408 
1409  /* The main property data collection structure */
1410 typedef struct
1411 {
1412     IPropData        IPropData_iface;
1413     LONG             lRef;        /* Reference count */
1414     ALLOCATEBUFFER  *lpAlloc;     /* Memory allocation routine */
1415     ALLOCATEMORE    *lpMore;      /* Linked memory allocation routine */
1416     FREEBUFFER      *lpFree;      /* Memory free routine */
1417     ULONG            ulObjAccess; /* Object access level */
1418     ULONG            ulNumValues; /* Number of items in values list */
1419     struct list      values;      /* List of property values */
1420     CRITICAL_SECTION cs;          /* Lock for thread safety */
1421 } IPropDataImpl;
1422 
1423 static inline IPropDataImpl *impl_from_IPropData(IPropData *iface)
1424 {
1425     return CONTAINING_RECORD(iface, IPropDataImpl, IPropData_iface);
1426 }
1427 
1428 /* Internal - Get a property value, assumes lock is held */
1429 static IPropDataItem *IMAPIPROP_GetValue(IPropDataImpl *This, ULONG ulPropTag)
1430 {
1431     struct list *cursor;
1432 
1433     LIST_FOR_EACH(cursor, &This->values)
1434     {
1435         LPIPropDataItem current = LIST_ENTRY(cursor, IPropDataItem, entry);
1436         /* Note that property types don't have to match, just Id's */
1437         if (PROP_ID(current->value->ulPropTag) == PROP_ID(ulPropTag))
1438             return current;
1439     }
1440     return NULL;
1441 }
1442 
1443 /* Internal - Add a new property value, assumes lock is held */
1444 static IPropDataItem *IMAPIPROP_AddValue(IPropDataImpl *This,
1445                                          LPSPropValue lpProp)
1446 {
1447     LPVOID lpMem;
1448     LPIPropDataItem lpNew;
1449     HRESULT hRet;
1450 
1451     hRet = This->lpAlloc(sizeof(IPropDataItem), &lpMem);
1452 
1453     if (SUCCEEDED(hRet))
1454     {
1455         lpNew = lpMem;
1456         lpNew->ulAccess = IPROP_READWRITE;
1457 
1458         /* Allocate the value separately so we can update it easily */
1459         lpMem = NULL;
1460         hRet = This->lpAlloc(sizeof(SPropValue), &lpMem);
1461         if (SUCCEEDED(hRet))
1462         {
1463             lpNew->value = lpMem;
1464 
1465             hRet = PropCopyMore(lpNew->value, lpProp, This->lpMore, lpMem);
1466             if (SUCCEEDED(hRet))
1467             {
1468                 list_add_tail(&This->values, &lpNew->entry);
1469                 This->ulNumValues++;
1470                 return lpNew;
1471             }
1472             This->lpFree(lpNew->value);
1473         }
1474         This->lpFree(lpNew);
1475     }
1476     return NULL;
1477 }
1478 
1479 /* Internal - Lock an IPropData object */
1480 static inline void IMAPIPROP_Lock(IPropDataImpl *This)
1481 {
1482     EnterCriticalSection(&This->cs);
1483 }
1484 
1485 /* Internal - Unlock an IPropData object */
1486 static inline void IMAPIPROP_Unlock(IPropDataImpl *This)
1487 {
1488     LeaveCriticalSection(&This->cs);
1489 }
1490 
1491 /* This one seems to be missing from mapidefs.h */
1492 #define CbNewSPropProblemArray(c) \
1493     (offsetof(SPropProblemArray,aProblem)+(c)*sizeof(SPropProblem))
1494 
1495 /**************************************************************************
1496  *  IPropData_QueryInterface {MAPI32}
1497  *
1498  * Inherited method from the IUnknown Interface.
1499  * See IUnknown_QueryInterface.
1500  */
1501 static HRESULT WINAPI IPropData_fnQueryInterface(LPPROPDATA iface, REFIID riid, LPVOID *ppvObj)
1502 {
1503     IPropDataImpl *This = impl_from_IPropData(iface);
1504 
1505     TRACE("(%p,%s,%p)\n", This, debugstr_guid(riid), ppvObj);
1506 
1507     if (!ppvObj || !riid)
1508         return MAPI_E_INVALID_PARAMETER;
1509 
1510     *ppvObj = NULL;
1511 
1512     if(IsEqualIID(riid, &IID_IUnknown) ||
1513        IsEqualIID(riid, &IID_IMAPIProp) ||
1514        IsEqualIID(riid, &IID_IMAPIPropData))
1515     {
1516         *ppvObj = &This->IPropData_iface;
1517         IPropData_AddRef(iface);
1518         TRACE("returning %p\n", *ppvObj);
1519         return S_OK;
1520     }
1521 
1522     TRACE("returning E_NOINTERFACE\n");
1523     return MAPI_E_INTERFACE_NOT_SUPPORTED;
1524 }
1525 
1526 /**************************************************************************
1527  *  IPropData_AddRef {MAPI32}
1528  *
1529  * Inherited method from the IUnknown Interface.
1530  * See IUnknown_AddRef.
1531  */
1532 static ULONG WINAPI IPropData_fnAddRef(LPPROPDATA iface)
1533 {
1534     IPropDataImpl *This = impl_from_IPropData(iface);
1535 
1536     TRACE("(%p)->(count before=%u)\n", This, This->lRef);
1537 
1538     return InterlockedIncrement(&This->lRef);
1539 }
1540 
1541 /**************************************************************************
1542  *  IPropData_Release {MAPI32}
1543  *
1544  * Inherited method from the IUnknown Interface.
1545  * See IUnknown_Release.
1546  */
1547 static ULONG WINAPI IPropData_fnRelease(LPPROPDATA iface)
1548 {
1549     IPropDataImpl *This = impl_from_IPropData(iface);
1550     LONG lRef;
1551 
1552     TRACE("(%p)->(count before=%u)\n", This, This->lRef);
1553 
1554     lRef = InterlockedDecrement(&This->lRef);
1555     if (!lRef)
1556     {
1557         TRACE("Destroying IPropData (%p)\n",This);
1558 
1559         /* Note: No need to lock, since no other thread is referencing iface */
1560         while (!list_empty(&This->values))
1561         {
1562             struct list *head = list_head(&This->values);
1563             LPIPropDataItem current = LIST_ENTRY(head, IPropDataItem, entry);
1564             list_remove(head);
1565             This->lpFree(current->value);
1566             This->lpFree(current);
1567         }
1568         This->cs.DebugInfo->Spare[0] = 0;
1569         DeleteCriticalSection(&This->cs);
1570         This->lpFree(This);
1571     }
1572     return (ULONG)lRef;
1573 }
1574 
1575 /**************************************************************************
1576  *  IPropData_GetLastError {MAPI32}
1577  *
1578  * Get information about the last error that occurred in an IMAPIProp object.
1579  *
1580  * PARAMS
1581  *  iface    [I] IMAPIProp object that experienced the error
1582  *  hRes     [I] Result of the call that returned an error
1583  *  ulFlags  [I] 0=return Ascii strings, MAPI_UNICODE=return Unicode strings
1584  *  lppError [O] Destination for detailed error information
1585  *
1586  * RETURNS
1587  *  Success: S_OK. *lppError contains details about the last error.
1588  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid,
1589  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails.
1590  *
1591  * NOTES
1592  *  - If this function succeeds, the returned information in *lppError must be
1593  *  freed using MAPIFreeBuffer() once the caller is finished with it.
1594  *  - It is possible for this function to succeed and set *lppError to NULL,
1595  *  if there is no further information to report about hRes.
1596  */
1597 static HRESULT WINAPI IPropData_fnGetLastError(LPPROPDATA iface, HRESULT hRes, ULONG ulFlags,
1598                                                LPMAPIERROR *lppError)
1599 {
1600     TRACE("(%p,0x%08X,0x%08X,%p)\n", iface, hRes, ulFlags, lppError);
1601 
1602     if (!lppError  || SUCCEEDED(hRes) || (ulFlags & ~MAPI_UNICODE))
1603         return MAPI_E_INVALID_PARAMETER;
1604 
1605     *lppError = NULL;
1606     return S_OK;
1607 }
1608 
1609 /**************************************************************************
1610  *  IPropData_SaveChanges {MAPI32}
1611  *
1612  * Update any changes made to a transactional IMAPIProp object.
1613  *
1614  * PARAMS
1615  *  iface    [I] IMAPIProp object to update
1616  *  ulFlags  [I] Flags controlling the update.
1617  *
1618  * RETURNS
1619  *  Success: S_OK. Any outstanding changes are committed to the object.
1620  *  Failure: An HRESULT error code describing the error.
1621  */
1622 static HRESULT WINAPI IPropData_fnSaveChanges(LPPROPDATA iface, ULONG ulFlags)
1623 {
1624     TRACE("(%p,0x%08X)\n", iface, ulFlags);
1625 
1626      /* Since this object is not transacted we do not need to implement this */
1627      /* FIXME: Should we set the access levels to clean? */
1628     return S_OK;
1629 }
1630 
1631 /**************************************************************************
1632  *  IPropData_GetProps {MAPI32}
1633  *
1634  * Get property values from an IMAPIProp object.
1635  *
1636  * PARAMS
1637  *  iface    [I] IMAPIProp object to get the property values from
1638  *  lpTags   [I] Property tags of property values to be retrieved
1639  *  ulFlags  [I] Return 0=Ascii MAPI_UNICODE=Unicode strings for
1640  *                 unspecified types
1641  *  lpCount  [O] Destination for number of properties returned
1642  *  lppProps [O] Destination for returned property values
1643  *
1644  * RETURNS
1645  *  Success: S_OK. *lppProps and *lpCount are updated.
1646  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
1647  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails, or
1648  *           MAPI_W_ERRORS_RETURNED if not all properties were retrieved
1649  *           successfully.
1650  * NOTES
1651  *  - If MAPI_W_ERRORS_RETURNED is returned, any properties that could not be
1652  *    retrieved from iface are present in lppProps with their type
1653  *    changed to PT_ERROR and Id unchanged.
1654  */
1655 static HRESULT WINAPI IPropData_fnGetProps(LPPROPDATA iface, LPSPropTagArray lpTags, ULONG ulFlags,
1656                                            ULONG *lpCount, LPSPropValue *lppProps)
1657 {
1658     IPropDataImpl *This = impl_from_IPropData(iface);
1659     ULONG i;
1660     HRESULT hRet = S_OK;
1661 
1662     TRACE("(%p,%p,0x%08x,%p,%p) stub\n", iface, lpTags, ulFlags,
1663           lpCount, lppProps);
1664 
1665     if (!iface || ulFlags & ~MAPI_UNICODE || !lpTags || *lpCount || !lppProps)
1666         return MAPI_E_INVALID_PARAMETER;
1667 
1668     FIXME("semi-stub, flags not supported\n");
1669 
1670     *lpCount = lpTags->cValues;
1671     *lppProps = NULL;
1672 
1673     if (*lpCount)
1674     {
1675         hRet = MAPIAllocateBuffer(*lpCount * sizeof(SPropValue), (LPVOID*)lppProps);
1676         if (FAILED(hRet))
1677             return hRet;
1678 
1679         IMAPIPROP_Lock(This);
1680 
1681         for (i = 0; i < lpTags->cValues; i++)
1682         {
1683             HRESULT hRetTmp = E_INVALIDARG;
1684             LPIPropDataItem item;
1685 
1686             item = IMAPIPROP_GetValue(This, lpTags->aulPropTag[i]);
1687 
1688             if (item)
1689                 hRetTmp = PropCopyMore(&(*lppProps)[i], item->value,
1690                                        This->lpMore, *lppProps);
1691             if (FAILED(hRetTmp))
1692             {
1693                 hRet = MAPI_W_ERRORS_RETURNED;
1694                 (*lppProps)[i].ulPropTag =
1695                     CHANGE_PROP_TYPE(lpTags->aulPropTag[i], PT_ERROR);
1696             }
1697         }
1698 
1699         IMAPIPROP_Unlock(This);
1700     }
1701     return hRet;
1702 }
1703 
1704 /**************************************************************************
1705  *  MAPIProp_GetPropList {MAPI32}
1706  *
1707  * Get the list of property tags for all values in an IMAPIProp object.
1708  *
1709  * PARAMS
1710  *  iface   [I] IMAPIProp object to get the property tag list from
1711  *  ulFlags [I] Return 0=Ascii MAPI_UNICODE=Unicode strings for
1712  *              unspecified types
1713  *  lppTags [O] Destination for the retrieved property tag list
1714  *
1715  * RETURNS
1716  *  Success: S_OK. *lppTags contains the tags for all available properties.
1717  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
1718  *           MAPI_E_BAD_CHARWIDTH, if Ascii or Unicode strings are requested
1719  *           and that type of string is not supported.
1720  */
1721 static HRESULT WINAPI IPropData_fnGetPropList(LPPROPDATA iface, ULONG ulFlags,
1722                                               LPSPropTagArray *lppTags)
1723 {
1724     IPropDataImpl *This = impl_from_IPropData(iface);
1725     ULONG i;
1726     HRESULT hRet;
1727 
1728     TRACE("(%p,0x%08x,%p) stub\n", iface, ulFlags, lppTags);
1729 
1730     if (!iface || ulFlags & ~MAPI_UNICODE || !lppTags)
1731         return MAPI_E_INVALID_PARAMETER;
1732 
1733     FIXME("semi-stub, flags not supported\n");
1734 
1735     *lppTags = NULL;
1736 
1737     IMAPIPROP_Lock(This);
1738 
1739     hRet = MAPIAllocateBuffer(CbNewSPropTagArray(This->ulNumValues),
1740                               (LPVOID*)lppTags);
1741     if (SUCCEEDED(hRet))
1742     {
1743         struct list *cursor;
1744 
1745         i = 0;
1746         LIST_FOR_EACH(cursor, &This->values)
1747         {
1748             LPIPropDataItem current = LIST_ENTRY(cursor, IPropDataItem, entry);
1749             (*lppTags)->aulPropTag[i] = current->value->ulPropTag;
1750             i++;
1751         }
1752         (*lppTags)->cValues = This->ulNumValues;
1753     }
1754 
1755     IMAPIPROP_Unlock(This);
1756     return hRet;
1757 }
1758 
1759 /**************************************************************************
1760  *  IPropData_OpenProperty {MAPI32}
1761  *
1762  * Not documented at this time.
1763  *
1764  * RETURNS
1765  *  An HRESULT success/failure code.
1766  */
1767 static HRESULT WINAPI IPropData_fnOpenProperty(LPPROPDATA iface, ULONG ulPropTag, LPCIID iid,
1768                                                ULONG ulOpts, ULONG ulFlags, LPUNKNOWN *lpUnk)
1769 {
1770     FIXME("(%p,%u,%s,%u,0x%08x,%p) stub\n", iface, ulPropTag,
1771           debugstr_guid(iid), ulOpts, ulFlags, lpUnk);
1772     return MAPI_E_NO_SUPPORT;
1773 }
1774 
1775 
1776 /**************************************************************************
1777  *  IPropData_SetProps {MAPI32}
1778  *
1779  * Add or edit the property values in an IMAPIProp object.
1780  *
1781  * PARAMS
1782  *  iface    [I] IMAPIProp object to get the property tag list from
1783  *  ulValues [I] Number of properties in lpProps
1784  *  lpProps  [I] Property values to set
1785  *  lppProbs [O] Optional destination for any problems that occurred
1786  *
1787  * RETURNS
1788  *  Success: S_OK. The properties in lpProps are added to iface if they don't
1789  *           exist, or changed to the values in lpProps if they do
1790  *  Failure: An HRESULT error code describing the error
1791  */
1792 static HRESULT WINAPI IPropData_fnSetProps(LPPROPDATA iface, ULONG ulValues, LPSPropValue lpProps,
1793                                            LPSPropProblemArray *lppProbs)
1794 {
1795     IPropDataImpl *This = impl_from_IPropData(iface);
1796     HRESULT hRet = S_OK;
1797     ULONG i;
1798 
1799     TRACE("(%p,%u,%p,%p)\n", iface, ulValues, lpProps, lppProbs);
1800 
1801     if (!iface || !lpProps)
1802       return MAPI_E_INVALID_PARAMETER;
1803 
1804     for (i = 0; i < ulValues; i++)
1805     {
1806         if (FBadProp(&lpProps[i]) ||
1807             PROP_TYPE(lpProps[i].ulPropTag) == PT_OBJECT ||
1808             PROP_TYPE(lpProps[i].ulPropTag) == PT_NULL)
1809           return MAPI_E_INVALID_PARAMETER;
1810     }
1811 
1812     IMAPIPROP_Lock(This);
1813 
1814     /* FIXME: Under what circumstances is lpProbs created? */
1815     for (i = 0; i < ulValues; i++)
1816     {
1817         LPIPropDataItem item = IMAPIPROP_GetValue(This, lpProps[i].ulPropTag);
1818 
1819         if (item)
1820         {
1821             HRESULT hRetTmp;
1822             LPVOID lpMem = NULL;
1823 
1824             /* Found, so update the existing value */
1825             if (item->value->ulPropTag != lpProps[i].ulPropTag)
1826                 FIXME("semi-stub, overwriting type (not coercing)\n");
1827 
1828             hRetTmp = This->lpAlloc(sizeof(SPropValue), &lpMem);
1829             if (SUCCEEDED(hRetTmp))
1830             {
1831                 hRetTmp = PropCopyMore(lpMem, &lpProps[i], This->lpMore, lpMem);
1832                 if (SUCCEEDED(hRetTmp))
1833                 {
1834                     This->lpFree(item->value);
1835                     item->value = lpMem;
1836                     continue;
1837                 }
1838                 This->lpFree(lpMem);
1839             }
1840             hRet = hRetTmp;
1841         }
1842         else
1843         {
1844             /* Add new value */
1845             if (!IMAPIPROP_AddValue(This, &lpProps[i]))
1846                 hRet = MAPI_E_NOT_ENOUGH_MEMORY;
1847         }
1848     }
1849 
1850     IMAPIPROP_Unlock(This);
1851     return hRet;
1852 }
1853 
1854 /**************************************************************************
1855  *  IPropData_DeleteProps {MAPI32}
1856  *
1857  * Delete one or more property values from an IMAPIProp object.
1858  *
1859  * PARAMS
1860  *  iface    [I] IMAPIProp object to remove property values from.
1861  *  lpTags   [I] Collection of property Id's to remove from iface.
1862  *  lppProbs [O] Destination for problems encountered, if any.
1863  *
1864  * RETURNS
1865  *  Success: S_OK. Any properties in iface matching property Id's in lpTags have
1866  *           been deleted. If lppProbs is non-NULL it contains details of any
1867  *           errors that occurred.
1868  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
1869  *           E_ACCESSDENIED, if this object was created using CreateIProp() and
1870  *           a subsequent call to IPropData_SetObjAccess() was made specifying
1871  *           IPROP_READONLY as the access type.
1872  *
1873  * NOTES
1874  *  - lppProbs will not be populated for cases where a property Id is present
1875  *    in lpTags but not in iface.
1876  *  - lppProbs should be deleted with MAPIFreeBuffer() if returned.
1877  */
1878 static HRESULT WINAPI IPropData_fnDeleteProps(LPPROPDATA iface, LPSPropTagArray lpTags,
1879                                               LPSPropProblemArray *lppProbs)
1880 {
1881     IPropDataImpl *This = impl_from_IPropData(iface);
1882     ULONG i, numProbs = 0;
1883     HRESULT hRet = S_OK;
1884 
1885     TRACE("(%p,%p,%p)\n", iface, lpTags, lppProbs);
1886 
1887     if (!iface || !lpTags)
1888         return MAPI_E_INVALID_PARAMETER;
1889 
1890     if (lppProbs)
1891         *lppProbs = NULL;
1892 
1893     for (i = 0; i < lpTags->cValues; i++)
1894     {
1895         if (FBadPropTag(lpTags->aulPropTag[i]) ||
1896             PROP_TYPE(lpTags->aulPropTag[i]) == PT_OBJECT ||
1897             PROP_TYPE(lpTags->aulPropTag[i]) == PT_NULL)
1898           return MAPI_E_INVALID_PARAMETER;
1899     }
1900 
1901     IMAPIPROP_Lock(This);
1902 
1903     if (This->ulObjAccess != IPROP_READWRITE)
1904     {
1905         IMAPIPROP_Unlock(This);
1906         return E_ACCESSDENIED;
1907     }
1908 
1909     for (i = 0; i < lpTags->cValues; i++)
1910     {
1911         LPIPropDataItem item = IMAPIPROP_GetValue(This, lpTags->aulPropTag[i]);
1912 
1913         if (item)
1914         {
1915             if (item->ulAccess & IPROP_READWRITE)
1916             {
1917                 /* Everything hunky-dory, remove the item */
1918                 list_remove(&item->entry);
1919                 This->lpFree(item->value); /* Also frees value pointers */
1920                 This->lpFree(item);
1921                 This->ulNumValues--;
1922             }
1923             else if (lppProbs)
1924             {
1925                  /* Can't write the value. Create/populate problems array */
1926                  if (!*lppProbs)
1927                  {
1928                      /* Create problems array */
1929                      ULONG ulSize = CbNewSPropProblemArray(lpTags->cValues - i);
1930                      HRESULT hRetTmp = MAPIAllocateBuffer(ulSize, (LPVOID*)lppProbs);
1931                      if (FAILED(hRetTmp))
1932                          hRet = hRetTmp;
1933                  }
1934                  if (*lppProbs)
1935                  {
1936                      LPSPropProblem lpProb = &(*lppProbs)->aProblem[numProbs];
1937                      lpProb->ulIndex = i;
1938                      lpProb->ulPropTag = lpTags->aulPropTag[i];
1939                      lpProb->scode = E_ACCESSDENIED;
1940                      numProbs++;
1941                  }
1942             }
1943         }
1944     }
1945     if (lppProbs && *lppProbs)
1946         (*lppProbs)->cProblem = numProbs;
1947 
1948     IMAPIPROP_Unlock(This);
1949     return hRet;
1950 }
1951 
1952 
1953 /**************************************************************************
1954  *  IPropData_CopyTo {MAPI32}
1955  *
1956  * Not documented at this time.
1957  *
1958  * RETURNS
1959  *  An HRESULT success/failure code.
1960  */
1961 static HRESULT WINAPI IPropData_fnCopyTo(LPPROPDATA iface, ULONG niids, LPCIID lpiidExcl,
1962                                          LPSPropTagArray lpPropsExcl, ULONG ulParam,
1963                                          LPMAPIPROGRESS lpIProgress, LPCIID lpIfaceIid,
1964                                          LPVOID lpDstObj, ULONG ulFlags,
1965                                          LPSPropProblemArray *lppProbs)
1966 {
1967     FIXME("(%p,%u,%p,%p,%x,%p,%s,%p,0x%08X,%p) stub\n", iface, niids,
1968           lpiidExcl, lpPropsExcl, ulParam, lpIProgress,
1969           debugstr_guid(lpIfaceIid), lpDstObj, ulFlags, lppProbs);
1970     return MAPI_E_NO_SUPPORT;
1971 }
1972 
1973 /**************************************************************************
1974  *  IPropData_CopyProps {MAPI32}
1975  *
1976  * Not documented at this time.
1977  *
1978  * RETURNS
1979  *  An HRESULT success/failure code.
1980  */
1981 static HRESULT WINAPI IPropData_fnCopyProps(LPPROPDATA iface, LPSPropTagArray lpInclProps,
1982                                             ULONG ulParam, LPMAPIPROGRESS lpIProgress,
1983                                             LPCIID lpIface, LPVOID lpDstObj, ULONG ulFlags,
1984                                             LPSPropProblemArray *lppProbs)
1985 {
1986     FIXME("(%p,%p,%x,%p,%s,%p,0x%08X,%p) stub\n", iface, lpInclProps,
1987           ulParam, lpIProgress, debugstr_guid(lpIface), lpDstObj, ulFlags,
1988           lppProbs);
1989     return MAPI_E_NO_SUPPORT;
1990 }
1991 
1992 /**************************************************************************
1993  *  IPropData_GetNamesFromIDs {MAPI32}
1994  *
1995  * Get the names of properties from their identifiers.
1996  *
1997  * PARAMS
1998  *  iface       [I]   IMAPIProp object to operate on
1999  *  lppPropTags [I/O] Property identifiers to get the names for, or NULL to
2000  *                    get all names
2001  *  iid         [I]   Property set identifier, or NULL
2002  *  ulFlags     [I]   MAPI_NO_IDS=Don't return numeric named properties,
2003  *                    or MAPI_NO_STRINGS=Don't return strings
2004  *  lpCount     [O]   Destination for number of properties returned
2005  *  lpppNames   [O]   Destination for returned names
2006  *
2007  * RETURNS
2008  *  Success: S_OK. *lppPropTags and lpppNames contain the returned
2009  *           name/identifiers.
2010  *  Failure: MAPI_E_NO_SUPPORT, if the object does not support named properties,
2011  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails, or
2012  *           MAPI_W_ERRORS_RETURNED if not all properties were retrieved
2013  *           successfully.
2014  */
2015 static HRESULT WINAPI IPropData_fnGetNamesFromIDs(LPPROPDATA iface, LPSPropTagArray *lppPropTags,
2016                                                   LPGUID iid, ULONG ulFlags, ULONG *lpCount,
2017                                                   LPMAPINAMEID **lpppNames)
2018 {
2019     FIXME("(%p,%p,%s,0x%08X,%p,%p) stub\n", iface, lppPropTags,
2020           debugstr_guid(iid), ulFlags, lpCount, lpppNames);
2021     return MAPI_E_NO_SUPPORT;
2022 }
2023 
2024 /**************************************************************************
2025  *  IPropData_GetIDsFromNames {MAPI32}
2026  *
2027  * Get property identifiers associated with one or more named properties.
2028  *
2029  * PARAMS
2030  *  iface       [I] IMAPIProp object to operate on
2031  *  ulNames     [I] Number of names in lppNames
2032  *  lppNames    [I] Names to query or create, or NULL to query all names
2033  *  ulFlags     [I] Pass MAPI_CREATE to create new named properties
2034  *  lppPropTags [O] Destination for queried or created property identifiers
2035  *
2036  * RETURNS
2037  *  Success: S_OK. *lppPropTags contains the property tags created or requested.
2038  *  Failure: MAPI_E_NO_SUPPORT, if the object does not support named properties,
2039  *           MAPI_E_TOO_BIG, if the object cannot process the number of
2040  *           properties involved.
2041  *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails, or
2042  *           MAPI_W_ERRORS_RETURNED if not all properties were retrieved
2043  *           successfully.
2044  */
2045 static HRESULT WINAPI IPropData_fnGetIDsFromNames(LPPROPDATA iface, ULONG ulNames,
2046                                                   LPMAPINAMEID *lppNames, ULONG ulFlags,
2047                                                   LPSPropTagArray *lppPropTags)
2048 {
2049     FIXME("(%p,%d,%p,0x%08X,%p) stub\n",
2050           iface, ulNames, lppNames, ulFlags, lppPropTags);
2051     return MAPI_E_NO_SUPPORT;
2052 }
2053 
2054 /**************************************************************************
2055  *  IPropData_HrSetObjAccess {MAPI32}
2056  *
2057  * Set the access level of an IPropData object.
2058  *
2059  * PARAMS
2060  *  iface    [I] IPropData object to set the access on
2061  *  ulAccess [I] Either IPROP_READONLY or IPROP_READWRITE for read or
2062  *               read/write access respectively.
2063  *
2064  * RETURNS
2065  *  Success: S_OK. The objects access level is changed.
2066  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
2067  */
2068 static HRESULT WINAPI
2069 IPropData_fnHrSetObjAccess(LPPROPDATA iface, ULONG ulAccess)
2070 {
2071     IPropDataImpl *This = impl_from_IPropData(iface);
2072 
2073     TRACE("(%p,%x)\n", iface, ulAccess);
2074 
2075     if (!iface || ulAccess < IPROP_READONLY || ulAccess > IPROP_READWRITE)
2076         return MAPI_E_INVALID_PARAMETER;
2077 
2078     IMAPIPROP_Lock(This);
2079 
2080     This->ulObjAccess = ulAccess;
2081 
2082     IMAPIPROP_Unlock(This);
2083     return S_OK;
2084 }
2085 
2086 /* Internal - determine if an access value is bad */
2087 static inline BOOL PROP_IsBadAccess(ULONG ulAccess)
2088 {
2089     switch (ulAccess)
2090     {
2091     case IPROP_READONLY|IPROP_CLEAN:
2092     case IPROP_READONLY|IPROP_DIRTY:
2093     case IPROP_READWRITE|IPROP_CLEAN:
2094     case IPROP_READWRITE|IPROP_DIRTY:
2095         return FALSE;
2096     }
2097     return TRUE;
2098 }
2099 
2100 /**************************************************************************
2101  *  IPropData_HrSetPropAccess {MAPI32}
2102  *
2103  * Set the access levels for a group of property values in an IPropData object.
2104  *
2105  * PARAMS
2106  *  iface    [I] IPropData object to set access levels in.
2107  *  lpTags   [I] List of property Id's to set access for.
2108  *  lpAccess [O] Access level for each property in lpTags.
2109  *
2110  * RETURNS
2111  *  Success: S_OK. The access level of each property value in lpTags that is
2112  *           present in iface is changed.
2113  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid.
2114  *
2115  * NOTES
2116  *  - Each access level in lpAccess must contain at least one of IPROP_READONLY
2117  *    or IPROP_READWRITE, but not both, and also IPROP_CLEAN or IPROP_DIRTY,
2118  *    but not both. No other bits should be set.
2119  *  - If a property Id in lpTags is not present in iface, it is ignored.
2120  */
2121 static HRESULT WINAPI
2122 IPropData_fnHrSetPropAccess(LPPROPDATA iface, LPSPropTagArray lpTags,
2123                             ULONG *lpAccess)
2124 {
2125     IPropDataImpl *This = impl_from_IPropData(iface);
2126     ULONG i;
2127 
2128     TRACE("(%p,%p,%p)\n", iface, lpTags, lpAccess);
2129 
2130     if (!iface || !lpTags || !lpAccess)
2131         return MAPI_E_INVALID_PARAMETER;
2132 
2133     for (i = 0; i < lpTags->cValues; i++)
2134     {
2135         if (FBadPropTag(lpTags->aulPropTag[i]) || PROP_IsBadAccess(lpAccess[i]))
2136             return MAPI_E_INVALID_PARAMETER;
2137     }
2138 
2139     IMAPIPROP_Lock(This);
2140 
2141     for (i = 0; i < lpTags->cValues; i++)
2142     {
2143         LPIPropDataItem item = IMAPIPROP_GetValue(This, lpTags->aulPropTag[i]);
2144 
2145         if (item)
2146             item->ulAccess = lpAccess[i];
2147     }
2148 
2149     IMAPIPROP_Unlock(This);
2150     return S_OK;
2151 }
2152 
2153 /**************************************************************************
2154  *  IPropData_HrGetPropAccess {MAPI32}
2155  *
2156  * Get the access levels for a group of property values in an IPropData object.
2157  *
2158  * PARAMS
2159  *  iface     [I] IPropData object to get access levels from.
2160  *  lppTags   [O] Destination for the list of property Id's in iface.
2161  *  lppAccess [O] Destination for access level for each property in lppTags.
2162  *
2163  * RETURNS
2164  *  Success: S_OK. lppTags and lppAccess contain the property Id's and the
2165  *           Access level of each property value in iface.
2166  *  Failure: MAPI_E_INVALID_PARAMETER, if any parameter is invalid, or
2167  *           MAPI_E_NOT_ENOUGH_MEMORY if memory allocation fails.
2168  *
2169  * NOTES
2170  *  - *lppTags and *lppAccess should be freed with MAPIFreeBuffer() by the caller.
2171  */
2172 static HRESULT WINAPI
2173 IPropData_fnHrGetPropAccess(LPPROPDATA iface, LPSPropTagArray *lppTags,
2174                             ULONG **lppAccess)
2175 {
2176     IPropDataImpl *This = impl_from_IPropData(iface);
2177     LPVOID lpMem;
2178     HRESULT hRet;
2179     ULONG i;
2180 
2181     TRACE("(%p,%p,%p) stub\n", iface, lppTags, lppAccess);
2182 
2183     if (!iface || !lppTags || !lppAccess)
2184         return MAPI_E_INVALID_PARAMETER;
2185 
2186     *lppTags = NULL;
2187     *lppAccess = NULL;
2188 
2189     IMAPIPROP_Lock(This);
2190 
2191     hRet = This->lpAlloc(CbNewSPropTagArray(This->ulNumValues), &lpMem);
2192     if (SUCCEEDED(hRet))
2193     {
2194         *lppTags = lpMem;
2195 
2196         hRet = This->lpAlloc(This->ulNumValues * sizeof(ULONG), &lpMem);
2197         if (SUCCEEDED(hRet))
2198         {
2199             struct list *cursor;
2200 
2201             *lppAccess = lpMem;
2202             (*lppTags)->cValues = This->ulNumValues;
2203 
2204             i = 0;
2205             LIST_FOR_EACH(cursor, &This->values)
2206             {
2207                 LPIPropDataItem item = LIST_ENTRY(cursor, IPropDataItem, entry);
2208                 (*lppTags)->aulPropTag[i] = item->value->ulPropTag;
2209                 (*lppAccess)[i] = item->ulAccess;
2210                 i++;
2211             }
2212             IMAPIPROP_Unlock(This);
2213             return S_OK;
2214         }
2215         This->lpFree(*lppTags);
2216         *lppTags = 0;
2217     }
2218     IMAPIPROP_Unlock(This);
2219     return MAPI_E_NOT_ENOUGH_MEMORY;
2220 }
2221 
2222 /**************************************************************************
2223  *  IPropData_HrAddObjProps {MAPI32}
2224  *
2225  * Not documented at this time.
2226  *
2227  * RETURNS
2228  *  An HRESULT success/failure code.
2229  */
2230 static HRESULT WINAPI
2231 IPropData_fnHrAddObjProps(LPPROPDATA iface, LPSPropTagArray lpTags,
2232                           LPSPropProblemArray *lppProbs)
2233 {
2234 #if 0
2235     ULONG i;
2236     HRESULT hRet;
2237     LPSPropValue lpValues;
2238 #endif
2239 
2240     FIXME("(%p,%p,%p) stub\n", iface, lpTags, lppProbs);
2241 
2242     if (!iface || !lpTags)
2243         return MAPI_E_INVALID_PARAMETER;
2244 
2245     /* FIXME: Below is the obvious implementation, adding all the properties
2246      *        in lpTags to the object. However, it doesn't appear that this
2247      *        is what this function does.
2248      */
2249     return S_OK;
2250 #if 0
2251     if (!lpTags->cValues)
2252         return S_OK;
2253 
2254     lpValues = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
2255                          lpTags->cValues * sizeof(SPropValue));
2256     if (!lpValues)
2257         return MAPI_E_NOT_ENOUGH_MEMORY;
2258 
2259     for (i = 0; i < lpTags->cValues; i++)
2260         lpValues[i].ulPropTag = lpTags->aulPropTag[i];
2261 
2262     hRet = IPropData_SetProps(iface, lpTags->cValues, lpValues, lppProbs);
2263     HeapFree(GetProcessHeap(), 0, lpValues);
2264     return hRet;
2265 #endif
2266 }
2267 
2268 static const IPropDataVtbl IPropDataImpl_vtbl =
2269 {
2270     IPropData_fnQueryInterface,
2271     IPropData_fnAddRef,
2272     IPropData_fnRelease,
2273     IPropData_fnGetLastError,
2274     IPropData_fnSaveChanges,
2275     IPropData_fnGetProps,
2276     IPropData_fnGetPropList,
2277     IPropData_fnOpenProperty,
2278     IPropData_fnSetProps,
2279     IPropData_fnDeleteProps,
2280     IPropData_fnCopyTo,
2281     IPropData_fnCopyProps,
2282     IPropData_fnGetNamesFromIDs,
2283     IPropData_fnGetIDsFromNames,
2284     IPropData_fnHrSetObjAccess,
2285     IPropData_fnHrSetPropAccess,
2286     IPropData_fnHrGetPropAccess,
2287     IPropData_fnHrAddObjProps
2288 };
2289 
2290 /*************************************************************************
2291  * CreateIProp@24 (MAPI32.60)
2292  *
2293  * Create an IPropData object.
2294  *
2295  * PARAMS
2296  *  iid         [I] GUID of the object to create. Use &IID_IMAPIPropData or NULL
2297  *  lpAlloc     [I] Memory allocation function. Use MAPIAllocateBuffer()
2298  *  lpMore      [I] Linked memory allocation function. Use MAPIAllocateMore()
2299  *  lpFree      [I] Memory free function. Use MAPIFreeBuffer()
2300  *  lpReserved  [I] Reserved, set to NULL
2301  *  lppPropData [O] Destination for created IPropData object
2302  *
2303  * RETURNS
2304  *  Success: S_OK. *lppPropData contains the newly created object.
2305  *  Failure: MAPI_E_INTERFACE_NOT_SUPPORTED, if iid is non-NULL and not supported,
2306  *           MAPI_E_INVALID_PARAMETER, if any parameter is invalid
2307  */
2308 SCODE WINAPI CreateIProp(LPCIID iid, ALLOCATEBUFFER *lpAlloc,
2309                          ALLOCATEMORE *lpMore, FREEBUFFER *lpFree,
2310                          LPVOID lpReserved, LPPROPDATA *lppPropData)
2311 {
2312     IPropDataImpl *lpPropData;
2313     SCODE scode;
2314 
2315     TRACE("(%s,%p,%p,%p,%p,%p)\n", debugstr_guid(iid), lpAlloc, lpMore, lpFree,
2316           lpReserved, lppPropData);
2317 
2318     if (lppPropData)
2319         *lppPropData = NULL;
2320 
2321     if (iid && !IsEqualGUID(iid, &IID_IMAPIPropData))
2322         return MAPI_E_INTERFACE_NOT_SUPPORTED;
2323 
2324     if (!lpAlloc || !lpMore || !lpFree || lpReserved || !lppPropData)
2325         return MAPI_E_INVALID_PARAMETER;
2326 
2327     scode = lpAlloc(sizeof(IPropDataImpl), (LPVOID*)&lpPropData);
2328 
2329     if (SUCCEEDED(scode))
2330     {
2331         lpPropData->IPropData_iface.lpVtbl = &IPropDataImpl_vtbl;
2332         lpPropData->lRef = 1;
2333         lpPropData->lpAlloc = lpAlloc;
2334         lpPropData->lpMore = lpMore;
2335         lpPropData->lpFree = lpFree;
2336         lpPropData->ulObjAccess = IPROP_READWRITE;
2337         lpPropData->ulNumValues = 0;
2338         list_init(&lpPropData->values);
2339         InitializeCriticalSection(&lpPropData->cs);
2340         lpPropData->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": IPropDataImpl.cs");
2341         *lppPropData = &lpPropData->IPropData_iface;
2342     }
2343     return scode;
2344 }
2345