Commit 18b26d89 authored by Jon Griffiths's avatar Jon Griffiths Committed by Alexandre Julliard

Implement DllMain, DllCanUnloadNow, WrapProgress,

MAPIGetDefaultMalloc, IsBadBoundedStringPtr, UFromSz, UlFromSzHex, CbOfEncoded. Add tests for the above.
parent 30bebfdc
......@@ -3,10 +3,11 @@ TOPOBJDIR = ../..
SRCDIR = @srcdir@
VPATH = @srcdir@
MODULE = mapi32.dll
IMPORTS = shlwapi ole32 kernel32
IMPORTS = shlwapi ole32 user32 kernel32
EXTRALIBS = -luuid $(LIBUNICODE)
C_SRCS = \
imalloc.c \
mapi32_main.c \
prop.c \
util.c
......
/*
* MAPI Default IMalloc implementation
*
* Copyright 2004 Jon Griffiths
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdarg.h>
#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winuser.h"
#include "winerror.h"
#include "winternl.h"
#include "objbase.h"
#include "shlwapi.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(mapi);
static const IMallocVtbl MAPI_IMalloc_vt;
typedef struct
{
const IMallocVtbl *lpVtbl;
LONG lRef;
} MAPI_IMALLOC;
static MAPI_IMALLOC MAPI_IMalloc = { &MAPI_IMalloc_vt, 0u };
extern LONG MAPI_ObjectCount; /* In mapi32_main.c */
/*************************************************************************
* MAPIGetDefaultMalloc@0 (MAPI32.59)
*
* Get the default MAPI IMalloc interface.
*
* PARAMS
* None.
*
* RETURNS
* A pointer to the MAPI default allocator.
*/
LPMALLOC WINAPI MAPIGetDefaultMalloc(void)
{
TRACE("()\n");
IMalloc_AddRef((LPMALLOC)&MAPI_IMalloc);
return (LPMALLOC)&MAPI_IMalloc;
}
/**************************************************************************
* IMAPIMalloc_QueryInterface
*/
static HRESULT WINAPI IMAPIMalloc_fnQueryInterface(LPMALLOC iface, REFIID refiid,
LPVOID *ppvObj)
{
TRACE("(%s,%p)\n", debugstr_guid(refiid), ppvObj);
if (IsEqualIID(refiid, &IID_IUnknown) ||
IsEqualIID(refiid, &IID_IMalloc))
{
*ppvObj = (LPMALLOC) &MAPI_IMalloc;
TRACE("Returning IMalloc (%p)\n", *ppvObj);
return S_OK;
}
TRACE("Returning E_NOINTERFACE\n");
return E_NOINTERFACE;
}
/**************************************************************************
* IMAPIMalloc_AddRef
*/
static ULONG WINAPI IMAPIMalloc_fnAddRef(LPMALLOC iface)
{
TRACE("(%p)\n", iface);
InterlockedIncrement(&MAPI_ObjectCount);
return 1u;
}
/**************************************************************************
* IMAPIMalloc_Release
*/
static ULONG WINAPI IMAPIMalloc_fnRelease(LPMALLOC iface)
{
TRACE("(%p)\n", iface);
InterlockedDecrement(&MAPI_ObjectCount);
return 1u;
}
/**************************************************************************
* IMAPIMalloc_Alloc
*/
static LPVOID WINAPI IMAPIMalloc_fnAlloc(LPMALLOC iface, DWORD cb)
{
TRACE("(%p)->(%ld)\n", iface, cb);
return LocalAlloc(GMEM_FIXED, cb);
}
/**************************************************************************
* IMAPIMalloc_Realloc
*/
static LPVOID WINAPI IMAPIMalloc_fnRealloc(LPMALLOC iface, LPVOID pv, DWORD cb)
{
TRACE("(%p)->(%p, %ld)\n", iface, pv, cb);
if (!pv)
return LocalAlloc(GMEM_FIXED, cb);
if (cb)
return LocalReAlloc((HANDLE) pv, cb, GMEM_MOVEABLE);
LocalFree((HANDLE) pv);
return NULL;
}
/**************************************************************************
* IMAPIMalloc_Free
*/
static void WINAPI IMAPIMalloc_fnFree(LPMALLOC iface, LPVOID pv)
{
TRACE("(%p)->(%p)\n", iface, pv);
LocalFree((HANDLE) pv);
}
/**************************************************************************
* IMAPIMalloc_GetSize
*/
static DWORD WINAPI IMAPIMalloc_fnGetSize(LPMALLOC iface, LPVOID pv)
{
TRACE("(%p)->(%p)\n", iface, pv);
return LocalSize((HANDLE) pv);
}
/**************************************************************************
* IMAPIMalloc_DidAlloc
*/
static INT WINAPI IMAPIMalloc_fnDidAlloc(LPMALLOC iface, LPVOID pv)
{
TRACE("(%p)->(%p)\n", iface, pv);
return -1;
}
/**************************************************************************
* IMAPIMalloc_HeapMinimize
*/
static void WINAPI IMAPIMalloc_fnHeapMinimize(LPMALLOC iface)
{
TRACE("(%p)\n", iface);
}
static const IMallocVtbl MAPI_IMalloc_vt =
{
IMAPIMalloc_fnQueryInterface,
IMAPIMalloc_fnAddRef,
IMAPIMalloc_fnRelease,
IMAPIMalloc_fnAlloc,
IMAPIMalloc_fnRealloc,
IMAPIMalloc_fnFree,
IMAPIMalloc_fnGetSize,
IMAPIMalloc_fnDidAlloc,
IMAPIMalloc_fnHeapMinimize
};
......@@ -17,7 +17,7 @@
25 stub LAUNCHWIZARD
26 stub LaunchWizard@20
27 stub DllGetClassObject
28 stub DllCanUnloadNow
28 stdcall -private DllCanUnloadNow() MAPI32_DllCanUnloadNow
29 stub MAPIOpenFormMgr
30 stub MAPIOpenFormMgr@8
31 stub MAPIOpenLocalFormContainer
......@@ -26,7 +26,7 @@
34 stdcall DeinitMapiUtil@0() DeinitMapiUtil
35 stub ScGenerateMuid@4
36 stub HrAllocAdviseSink@12
41 stub WrapProgress@20
41 stdcall WrapProgress@20(ptr ptr ptr ptr ptr) WrapProgress
42 stdcall HrThisThreadAdviseSink@8(ptr ptr) HrThisThreadAdviseSink
43 stub ScBinFromHexBounded@12
44 stdcall FBinFromHex@8(ptr ptr) FBinFromHex
......@@ -41,7 +41,7 @@
53 stub EnableIdleRoutine@8
54 stub DeregisterIdleRoutine@4
55 stub ChangeIdleRoutine@28
59 stub MAPIGetDefaultMalloc@0
59 stdcall MAPIGetDefaultMalloc@0() MAPIGetDefaultMalloc
60 stub CreateIProp@24
61 stub CreateTable@36
62 stdcall MNLS_lstrlenW@4(wstr) MNLS_lstrlenW
......@@ -53,7 +53,7 @@
68 stdcall MNLS_IsBadStringPtrW@8(ptr long) kernel32.IsBadStringPtrW
72 stdcall FEqualNames@8(ptr ptr) FEqualNames
73 stub WrapStoreEntryID@24
74 stub IsBadBoundedStringPtr@8
74 stdcall IsBadBoundedStringPtr@8(ptr long) IsBadBoundedStringPtr
75 stub HrQueryAllRows@24
76 stdcall PropCopyMore@16(ptr ptr ptr ptr) PropCopyMore
77 stdcall UlPropSize@4(ptr) UlPropSize
......@@ -74,7 +74,7 @@
130 stdcall SzFindCh@8(str long) shlwapi.StrChrA
131 stdcall SzFindLastCh@8(str str long) shlwapi.StrRChrA
132 stdcall SzFindSz@8(str str) shlwapi.StrStrA
133 stub UFromSz@4
133 stdcall UFromSz@4(str) UFromSz
135 stdcall HrGetOneProp@12(ptr long ptr) HrGetOneProp
136 stdcall HrSetOneProp@8(ptr ptr) HrSetOneProp
137 stdcall FPropExists@8(ptr long) FPropExists
......@@ -95,7 +95,7 @@
152 stub OpenTnefStreamEx
153 stub GetTnefStreamCodepage@12
154 stub GetTnefStreamCodepage
155 stub UlFromSzHex@4
155 stdcall UlFromSzHex@4(ptr) UlFromSzHex
156 stub UNKOBJ_ScAllocate@12
157 stub UNKOBJ_ScAllocateMore@16
158 stub UNKOBJ_Free@8
......@@ -144,7 +144,7 @@
204 stub EncodeID@12
205 stub FDecodeID@12
206 stub CchOfEncoding@4
207 stub CbOfEncoded@4
207 stdcall CbOfEncoded@4(ptr) CbOfEncoded
208 stub MAPISendDocuments
209 stdcall MAPILogon(long ptr ptr long long ptr)
210 stub MAPILogoff
......
......@@ -29,6 +29,44 @@
WINE_DEFAULT_DEBUG_CHANNEL(mapi);
LONG MAPI_ObjectCount = 0;
/***********************************************************************
* DllMain (MAPI32.init)
*/
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID fImpLoad)
{
TRACE("(%p,0x%lx,%p)\n", hinstDLL, fdwReason, fImpLoad);
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hinstDLL);
break;
case DLL_PROCESS_DETACH:
TRACE("DLL_PROCESS_DETACH: %ld objects remaining\n", MAPI_ObjectCount);
break;
}
return TRUE;
}
/***********************************************************************
* DllCanUnloadNow (MAPI32.28)
*
* Determine if this dll can be unloaded from the callers address space.
*
* PARAMS
* None.
*
* RETURNS
* S_OK, if the dll can be unloaded,
* S_FALSE, otherwise.
*/
HRESULT WINAPI MAPI32_DllCanUnloadNow(void)
{
return MAPI_ObjectCount == 0 ? S_OK : S_FALSE;
}
HRESULT WINAPI MAPIInitialize ( LPVOID lpMapiInit )
{
ERR("Stub\n");
......
Makefile
imalloc.ok
prop.ok
testlist.c
util.ok
......@@ -7,6 +7,7 @@ IMPORTS = mapi32
EXTRALIBS = -luuid
CTESTS = \
imalloc.c \
prop.c \
util.c
......
/*
* Unit test suite for MAPI IMalloc functions
*
* Copyright 2004 Jon Griffiths
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "wine/test.h"
#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winerror.h"
#include "winnt.h"
#include "mapiutil.h"
static HMODULE hMapi32 = 0;
static SCODE (WINAPI *pScInitMapiUtil)(ULONG);
static LPMALLOC (WINAPI *pMAPIGetDefaultMalloc)(void);
static void test_IMalloc(void)
{
LPVOID lpMem;
ULONG ulRef;
int iRet;
HRESULT hRet;
LPMALLOC lpMalloc;
LPVOID lpVoid;
pMAPIGetDefaultMalloc = (void*)GetProcAddress(hMapi32,
"MAPIGetDefaultMalloc@0");
if (!pMAPIGetDefaultMalloc)
return;
lpMalloc = pMAPIGetDefaultMalloc();
ok (lpMalloc != NULL, "iMAPIGetDefaultMalloc returned null\n");
if (!lpMalloc)
return;
lpVoid = NULL;
hRet = IMalloc_QueryInterface(lpMalloc, &IID_IUnknown, &lpVoid);
ok (hRet == S_OK && lpVoid != NULL,
"IID_IUnknown: exepected S_OK, non-null, got 0x%08lx, %p\n",
hRet, lpVoid);
lpVoid = NULL;
hRet = IMalloc_QueryInterface(lpMalloc, &IID_IMalloc, &lpVoid);
ok (hRet == S_OK && lpVoid != NULL,
"IID_IIMalloc: exepected S_OK, non-null, got 0x%08lx, %p\n",
hRet, lpVoid);
/* Prove that native mapi uses LocalAlloc/LocalFree */
lpMem = IMalloc_Alloc(lpMalloc, 61);
ok (lpMem && IMalloc_GetSize(lpMalloc, lpMem) ==
LocalSize((HANDLE)lpMem),
"Expected non-null, same size, got %p, %s size\n", lpMem,
lpMem ? "different" : "same");
iRet = IMalloc_DidAlloc(lpMalloc, lpMem);
ok (iRet == -1, "DidAlloc, expected -1. got %d\n", iRet);
IMalloc_HeapMinimize(lpMalloc);
LocalFree(lpMem);
ulRef = IMalloc_AddRef(lpMalloc);
ok (ulRef == 1u, "AddRef expected 1, returned %ld\n", ulRef);
ulRef = IMalloc_Release(lpMalloc);
ok (ulRef == 1u, "AddRef expected 1, returned %ld\n", ulRef);
IMalloc_Release(lpMalloc);
}
START_TEST(imalloc)
{
hMapi32 = LoadLibraryA("mapi32.dll");
pScInitMapiUtil = (void*)GetProcAddress(hMapi32, "ScInitMapiUtil@4");
if (!pScInitMapiUtil)
return;
pScInitMapiUtil(0);
test_IMalloc();
}
......@@ -36,6 +36,10 @@ static void (WINAPI *pSwapPword)(PUSHORT,ULONG);
static void (WINAPI *pSwapPlong)(PULONG,ULONG);
static void (WINAPI *pHexFromBin)(LPBYTE,int,LPWSTR);
static void (WINAPI *pFBinFromHex)(LPWSTR,LPBYTE);
static UINT (WINAPI *pUFromSz)(LPCSTR);
static ULONG (WINAPI *pUlFromSzHex)(LPCSTR);
static ULONG (WINAPI *pCbOfEncoded)(LPCSTR);
static BOOL (WINAPI *pIsBadBoundedStringPtr)(LPCSTR,ULONG);
static void test_SwapPword(void)
{
......@@ -107,6 +111,63 @@ static void test_HexFromBin(void)
ok(bOk == TRUE, "FBinFromHex: Result differs\n");
}
static void test_UFromSz(void)
{
pUFromSz = (void*)GetProcAddress(hMapi32, "UFromSz@4");
if (!pUFromSz)
return;
ok(pUFromSz("105679") == 105679u,
"UFromSz: expected 105679, got %d\n", pUFromSz("105679"));
ok(pUFromSz(" 4") == 0, "UFromSz: exected 0. got %d\n",
pUFromSz(" 4"));
}
static void test_UlFromSzHex(void)
{
pUlFromSzHex = (void*)GetProcAddress(hMapi32, "UlFromSzHex@4");
if (!pUlFromSzHex)
return;
ok(pUlFromSzHex("fF") == 0xffu,
"UlFromSzHex: expected 0xff, got 0x%lx\n", pUlFromSzHex("fF"));
ok(pUlFromSzHex(" c") == 0, "UlFromSzHex: exected 0x0. got 0x%lx\n",
pUlFromSzHex(" c"));
}
static void test_CbOfEncoded(void)
{
char buff[129];
size_t i;
pCbOfEncoded = (void*)GetProcAddress(hMapi32, "CbOfEncoded@4");
if (!pCbOfEncoded)
return;
for (i = 0; i < sizeof(buff) - 1; i++)
{
ULONG ulRet, ulExpected = (((i | 3) >> 2) + 1) * 3;
memset(buff, '\0', sizeof(buff));
memset(buff, '?', i);
ulRet = pCbOfEncoded(buff);
ok(ulRet == ulExpected, "CbOfEncoded(length %d): expected %ld, got %ld\n",
i, ulExpected, ulRet);
}
}
static void test_IsBadBoundedStringPtr(void)
{
pIsBadBoundedStringPtr = (void*)GetProcAddress(hMapi32, "IsBadBoundedStringPtr@8");
if (!pIsBadBoundedStringPtr)
return;
ok(pIsBadBoundedStringPtr(NULL, 0) == TRUE, "IsBadBoundedStringPtr: expected TRUE\n");
ok(pIsBadBoundedStringPtr("TEST", 4) == TRUE, "IsBadBoundedStringPtr: expected TRUE\n");
ok(pIsBadBoundedStringPtr("TEST", 5) == FALSE, "IsBadBoundedStringPtr: expected FALSE\n");
}
START_TEST(util)
{
......@@ -120,4 +181,8 @@ START_TEST(util)
test_SwapPword();
test_SwapPlong();
test_HexFromBin();
test_UFromSz();
test_UlFromSzHex();
test_CbOfEncoded();
test_IsBadBoundedStringPtr();
}
......@@ -23,10 +23,10 @@
#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winuser.h"
#include "winerror.h"
#include "winternl.h"
#include "objbase.h"
......@@ -38,6 +38,12 @@
WINE_DEFAULT_DEBUG_CHANNEL(mapi);
static const BYTE digitsToHex[] = {
0,1,2,3,4,5,6,7,8,9,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,14,15,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,
14,15 };
/**************************************************************************
* ScInitMapiUtil (MAPI32.33)
*
......@@ -203,6 +209,15 @@ ULONG WINAPI MAPIFreeBuffer(LPVOID lpBuffer)
return S_OK;
}
/**************************************************************************
* WrapProgress@20 (MAPI32.41)
*/
HRESULT WINAPI WrapProgress(PVOID unk1, PVOID unk2, PVOID unk3, PVOID unk4, PVOID unk5)
{
/* Native does not implement this function */
return MAPI_E_NO_SUPPORT;
}
/*************************************************************************
* HrThisThreadAdviseSink@8 (MAPI32.42)
*
......@@ -251,11 +266,6 @@ HRESULT WINAPI HrThisThreadAdviseSink(LPMAPIADVISESINK lpSink, LPMAPIADVISESINK*
*/
BOOL WINAPI FBinFromHex(LPWSTR lpszHex, LPBYTE lpOut)
{
static const BYTE digitsToHex[] = {
0,1,2,3,4,5,6,7,8,9,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,14,15,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,
14,15 };
LPSTR lpStr = (LPSTR)lpszHex;
TRACE("(%p,%p)\n", lpszHex, lpOut);
......@@ -458,6 +468,26 @@ BOOL WINAPI FEqualNames(LPMAPINAMEID lpName1, LPMAPINAMEID lpName2)
}
/**************************************************************************
* IsBadBoundedStringPtr@8 (MAPI32.71)
*
* Determine if a string pointer is valid.
*
* PARAMS
* lpszStr [I] String to check
* ulLen [I] Maximum length of lpszStr
*
* RETURNS
* TRUE, if lpszStr is invalid or longer than ulLen,
* FALSE, otherwise.
*/
BOOL WINAPI IsBadBoundedStringPtr(LPCSTR lpszStr, ULONG ulLen)
{
if (!lpszStr || IsBadStringPtrA(lpszStr, -1) || strlen(lpszStr) >= ulLen)
return TRUE;
return FALSE;
}
/**************************************************************************
* FtAddFt@16 (MAPI32.121)
*
* Add two FILETIME's together.
......@@ -545,7 +575,7 @@ LONGLONG WINAPI MAPI32_FtMulDwDw(DWORD dwLeft, DWORD dwRight)
LONGLONG WINAPI MAPI32_FtNegFt(FILETIME ft)
{
LONGLONG *p = (LONGLONG*)&ft;
return - *p;
}
......@@ -597,6 +627,39 @@ ULONG WINAPI UlRelease(void *lpUnk)
return IUnknown_Release((LPUNKNOWN)lpUnk);
}
/**************************************************************************
* UFromSz@4 (MAPI32.133)
*
* Read an integer from a string
*
* PARAMS
* lpszStr [I] String to read the integer from.
*
* RETURNS
* Success: The integer read from lpszStr.
* Failure: 0, if the first character in lpszStr is not 0-9.
*
* NOTES
* This function does not accept whitespace and stops at the first non-digit
* character.
*/
UINT WINAPI UFromSz(LPCSTR lpszStr)
{
ULONG ulRet = 0;
TRACE("(%s)\n", debugstr_a(lpszStr));
if (lpszStr)
{
while (*lpszStr >= '0' && *lpszStr <= '9')
{
ulRet = ulRet * 10 + (*lpszStr - '0');
lpszStr = CharNextA(lpszStr);
}
}
return ulRet;
}
/*************************************************************************
* OpenStreamOnFile@24 (MAPI32.147)
*
......@@ -649,6 +712,62 @@ HRESULT WINAPI OpenStreamOnFile(LPALLOCATEBUFFER lpAlloc, LPFREEBUFFER lpFree,
return hRet;
}
/*************************************************************************
* UlFromSzHex@4 (MAPI32.155)
*
* Read an integer from a hexadecimal string.
*
* PARAMS
* lpSzHex [I] String containing the hexidecimal number to read
*
* RETURNS
* Success: The number represented by lpszHex.
* Failure: 0, if lpszHex does not contain a hex string.
*
* NOTES
* This function does not accept whitespace and stops at the first non-hex
* character.
*/
ULONG WINAPI UlFromSzHex(LPCWSTR lpszHex)
{
LPSTR lpStr = (LPSTR)lpszHex;
ULONG ulRet = 0;
TRACE("(%s)\n", debugstr_a(lpStr));
while (*lpStr)
{
if (lpStr[0] < '0' || lpStr[0] > 'f' || digitsToHex[lpStr[0] - '0'] == 0xff ||
lpStr[1] < '0' || lpStr[1] > 'f' || digitsToHex[lpStr[1] - '0'] == 0xff)
break;
ulRet = ulRet * 16 + ((digitsToHex[lpStr[0] - '0'] << 4) | digitsToHex[lpStr[1] - '0']);
lpStr += 2;
}
return ulRet;
}
/*************************************************************************
* CbOfEncoded@4 (MAPI32.207)
*
* Return the length of an encoded string.
*
* PARAMS
* lpSzEnc [I] Encoded string to get the length of.
*
* RETURNS
* The length of the encoded string in bytes.
*/
ULONG WINAPI CbOfEncoded(LPCSTR lpszEnc)
{
ULONG ulRet = 0;
TRACE("(%s)\n", debugstr_a(lpszEnc));
if (lpszEnc)
ulRet = (((strlen(lpszEnc) | 3) >> 2) + 1) * 3;
return ulRet;
}
/*************************************************************************
* cmc_query_configuration (MAPI32.235)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment