mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
611 lines
20 KiB
611 lines
20 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
|
//
|
|
// File: cps.cpp
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
//
|
|
// File: select.cpp
|
|
//
|
|
// Description: This file contains the implmentation code for the
|
|
// "Certificate Select" dialog.
|
|
//
|
|
|
|
#pragma warning (disable: 4201) // nameless struct/union
|
|
#pragma warning (disable: 4514) // remove inline functions
|
|
#pragma warning (disable: 4127) // conditional expression is constant
|
|
|
|
#include "global.hxx"
|
|
#include <dbgdef.h>
|
|
|
|
extern HINSTANCE HinstDll;
|
|
extern HMODULE HmodRichEdit;
|
|
|
|
typedef struct _CPS_STATE_STRUCT {
|
|
LPWSTR pwszURL;
|
|
LPWSTR pwszDisplayText;
|
|
DWORD dwChainError;
|
|
BOOL fNoCOM;
|
|
} CPS_STATE_STRUCT, *PCPS_STATE_STRUCT;
|
|
|
|
|
|
INT_PTR CALLBACK CPSDlgProc(HWND hwndDlg, UINT msg,
|
|
WPARAM wParam, LPARAM lParam)
|
|
{
|
|
|
|
BOOL f;
|
|
CPS_STATE_STRUCT *pcpsStateStruct;
|
|
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
|
|
pcpsStateStruct = (CPS_STATE_STRUCT *) lParam;
|
|
SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR) pcpsStateStruct);
|
|
|
|
// set the text for the CPS
|
|
CryptUISetRicheditTextW(hwndDlg, IDC_CPS_TEXT, pcpsStateStruct->pwszDisplayText);
|
|
|
|
// if there is no URL then hide the more info button,
|
|
//
|
|
// DSIE: Bug 364742, also hide if the URL is not safe to be execute.
|
|
if (!pcpsStateStruct->pwszURL ||
|
|
!IsOKToFormatAsLinkW(pcpsStateStruct->pwszURL, pcpsStateStruct->dwChainError))
|
|
{
|
|
EnableWindow(GetDlgItem(hwndDlg, ID_MORE_INFO), FALSE);
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
//if (((NMHDR FAR *) lParam)->code == EN_LINK)
|
|
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam)) {
|
|
case ID_MORE_INFO:
|
|
|
|
if (HIWORD(wParam) == BN_CLICKED)
|
|
{
|
|
DWORD numBytes = 0;
|
|
LPSTR pszURL = NULL;
|
|
|
|
pcpsStateStruct = (CPS_STATE_STRUCT *) GetWindowLongPtr(hwndDlg, DWLP_USER);
|
|
|
|
pszURL = CertUIMkMBStr(pcpsStateStruct->pwszURL);
|
|
|
|
if (pszURL == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
CryptuiGoLink(hwndDlg, pszURL, pcpsStateStruct->fNoCOM);
|
|
free(pszURL);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
case IDOK_CPS:
|
|
case IDCANCEL_CPS:
|
|
EndDialog(hwndDlg, IDOK);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Use the default handler -- we don't do anything for it
|
|
//
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
DWORD GetCPSInfo(PCCERT_CONTEXT pCertContext, LPWSTR * ppwszUrlString, LPWSTR * ppwszDisplayText)
|
|
{
|
|
DWORD dwRetCode = 0;
|
|
PCERT_EXTENSION pExt = NULL;
|
|
DWORD i,j,k;
|
|
DWORD cbCertPolicyInfo = 0;
|
|
CERT_POLICIES_INFO * pCertPolicyInfo = NULL;
|
|
CERT_NAME_VALUE * pCertName = NULL;
|
|
DWORD cbCertName = 0;
|
|
CERT_POLICY_QUALIFIER_USER_NOTICE * pUserNotice = NULL;
|
|
DWORD cbUserNotice = 0;
|
|
PSPC_SP_AGENCY_INFO pInfo = NULL;
|
|
DWORD cbInfo = 0;
|
|
CERT_POLICY95_QUALIFIER1 * pCertPolicy95Qualifier = NULL;
|
|
DWORD cbCertPolicy95Qualifier = 0;
|
|
|
|
//
|
|
// Check parameters and intialize return values.
|
|
//
|
|
if (NULL == pCertContext || NULL == ppwszUrlString || NULL == ppwszDisplayText)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
*ppwszUrlString = *ppwszDisplayText = NULL;
|
|
|
|
//
|
|
// first look for the PKIX policy extension and see if it has an URL
|
|
//
|
|
if ((pExt = CertFindExtension(szOID_CERT_POLICIES,
|
|
pCertContext->pCertInfo->cExtension,
|
|
pCertContext->pCertInfo->rgExtension)))
|
|
{
|
|
//
|
|
// decode the policy extension
|
|
//
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
NULL,
|
|
&cbCertPolicyInfo);
|
|
|
|
if (!(pCertPolicyInfo = (CERT_POLICIES_INFO *) malloc(cbCertPolicyInfo)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
pCertPolicyInfo,
|
|
&cbCertPolicyInfo)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// look for display text and/or and URL in the extension
|
|
//
|
|
for (i = 0; i < pCertPolicyInfo->cPolicyInfo ; i++)
|
|
{
|
|
for (j = 0; j < pCertPolicyInfo->rgPolicyInfo[i].cPolicyQualifier; j++)
|
|
{
|
|
// check to see what type of qualifier it is
|
|
if (0 == strcmp(szOID_PKIX_POLICY_QUALIFIER_CPS,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].pszPolicyQualifierId))
|
|
{
|
|
// decode as an anystring
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
X509_UNICODE_ANY_STRING,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
NULL,
|
|
&cbCertName);
|
|
|
|
if (!(pCertName = (CERT_NAME_VALUE *) malloc(cbCertName)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
X509_UNICODE_ANY_STRING,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
pCertName,
|
|
&cbCertName)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pCertName->Value.cbData && NULL == *ppwszUrlString)
|
|
{
|
|
if (!(*ppwszUrlString = (LPWSTR) malloc(pCertName->Value.cbData + sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszUrlString, (LPWSTR) pCertName->Value.pbData);
|
|
}
|
|
|
|
free(pCertName);
|
|
pCertName = NULL;
|
|
}
|
|
else if (0 == strcmp(szOID_PKIX_POLICY_QUALIFIER_USERNOTICE,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].pszPolicyQualifierId))
|
|
{
|
|
// decode as user notice
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_PKIX_POLICY_QUALIFIER_USERNOTICE,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
NULL,
|
|
&cbUserNotice);
|
|
|
|
if (!(pUserNotice = (CERT_POLICY_QUALIFIER_USER_NOTICE *) malloc(cbUserNotice)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_PKIX_POLICY_QUALIFIER_USERNOTICE,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
pUserNotice,
|
|
&cbUserNotice)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
// NOTE: pUserNotice->pszDisplayText may be NULL
|
|
if (pUserNotice->pszDisplayText && NULL == *ppwszDisplayText)
|
|
{
|
|
if (!(*ppwszDisplayText = (LPWSTR) malloc((wcslen(pUserNotice->pszDisplayText) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszDisplayText, pUserNotice->pszDisplayText);
|
|
}
|
|
|
|
free(pUserNotice);
|
|
pUserNotice = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// next look for display information in the SPC Agency Info
|
|
//
|
|
|
|
else if ((pExt = CertFindExtension(SPC_SP_AGENCY_INFO_OBJID,
|
|
pCertContext->pCertInfo->cExtension,
|
|
pCertContext->pCertInfo->rgExtension)))
|
|
{
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
SPC_SP_AGENCY_INFO_STRUCT,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
NULL,
|
|
&cbInfo);
|
|
|
|
if (!(pInfo = (PSPC_SP_AGENCY_INFO) malloc(cbInfo)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
SPC_SP_AGENCY_INFO_STRUCT,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
pInfo,
|
|
&cbInfo)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(pInfo->pPolicyInformation))
|
|
{
|
|
dwRetCode = CRYPT_E_NOT_FOUND;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pInfo->pwszPolicyDisplayText)
|
|
{
|
|
if (!(*ppwszDisplayText = (LPWSTR) malloc((wcslen(pInfo->pwszPolicyDisplayText) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszDisplayText, pInfo->pwszPolicyDisplayText);
|
|
}
|
|
|
|
switch (pInfo->pPolicyInformation->dwLinkChoice)
|
|
{
|
|
case SPC_URL_LINK_CHOICE:
|
|
{
|
|
if (pInfo->pPolicyInformation->pwszUrl)
|
|
{
|
|
if (!(*ppwszUrlString = (LPWSTR) malloc((wcslen(pInfo->pPolicyInformation->pwszUrl) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszUrlString, pInfo->pPolicyInformation->pwszUrl);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SPC_FILE_LINK_CHOICE:
|
|
{
|
|
if (pInfo->pPolicyInformation->pwszFile)
|
|
{
|
|
if (!(*ppwszUrlString = (LPWSTR) malloc((wcslen(pInfo->pPolicyInformation->pwszFile) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszUrlString, pInfo->pPolicyInformation->pwszFile);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// finally, look for info in the 2.5.29.3 extension
|
|
//
|
|
|
|
else if ((pExt = CertFindExtension(szOID_CERT_POLICIES_95,
|
|
pCertContext->pCertInfo->cExtension,
|
|
pCertContext->pCertInfo->rgExtension)))
|
|
{
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES_95,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
NULL,
|
|
&cbCertPolicyInfo);
|
|
|
|
if (!(pCertPolicyInfo = (CERT_POLICIES_INFO *) malloc(cbCertPolicyInfo)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES_95,
|
|
pExt->Value.pbData,
|
|
pExt->Value.cbData,
|
|
0,
|
|
pCertPolicyInfo,
|
|
&cbCertPolicyInfo)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
// now decode the qualifiers
|
|
for (i = 0; i < pCertPolicyInfo->cPolicyInfo; i++)
|
|
{
|
|
for (j = 0; j < pCertPolicyInfo->rgPolicyInfo[i].cPolicyQualifier; j++)
|
|
{
|
|
if (0 == strcmp(szOID_CERT_POLICIES_95_QUALIFIER1,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].pszPolicyQualifierId))
|
|
{
|
|
CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES_95_QUALIFIER1,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
NULL,
|
|
&cbCertPolicy95Qualifier);
|
|
|
|
if (!(pCertPolicy95Qualifier = (CERT_POLICY95_QUALIFIER1 *) malloc(cbCertPolicy95Qualifier)))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!(CryptDecodeObject(X509_ASN_ENCODING,
|
|
szOID_CERT_POLICIES_95_QUALIFIER1,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.pbData,
|
|
pCertPolicyInfo->rgPolicyInfo[i].rgPolicyQualifier[j].Qualifier.cbData,
|
|
0,
|
|
pCertPolicy95Qualifier,
|
|
&cbCertPolicy95Qualifier)))
|
|
{
|
|
dwRetCode = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
// check to see what is available
|
|
if ((NULL != pCertPolicy95Qualifier->pszPracticesReference) && (NULL == *ppwszDisplayText))
|
|
{
|
|
if (!(*ppwszDisplayText = (LPWSTR) malloc((wcslen(pCertPolicy95Qualifier->pszPracticesReference) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszDisplayText, pCertPolicy95Qualifier->pszPracticesReference);
|
|
}
|
|
|
|
for (k = 0; k < pCertPolicy95Qualifier->cCPSURLs; k++)
|
|
{
|
|
if ((NULL != pCertPolicy95Qualifier->rgCPSURLs[k].pszURL) && (NULL == *ppwszUrlString))
|
|
{
|
|
if (!(*ppwszUrlString = (LPWSTR) malloc((wcslen(pCertPolicy95Qualifier->rgCPSURLs[k].pszURL) + 1) * sizeof(WCHAR))))
|
|
{
|
|
dwRetCode = ERROR_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
wcscpy(*ppwszUrlString, pCertPolicy95Qualifier->rgCPSURLs[k].pszURL);
|
|
}
|
|
}
|
|
|
|
free(pCertPolicy95Qualifier);
|
|
pCertPolicy95Qualifier = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there is neither display text nor an URL, then return CRYPT_E_NOT_FOUND.
|
|
//
|
|
if ((NULL == *ppwszUrlString) && (NULL == *ppwszDisplayText))
|
|
{
|
|
dwRetCode = CRYPT_E_NOT_FOUND;
|
|
}
|
|
|
|
CommonExit:
|
|
|
|
if (pInfo)
|
|
free(pInfo);
|
|
|
|
if (pCertPolicyInfo)
|
|
free(pCertPolicyInfo);
|
|
|
|
if (pUserNotice)
|
|
free(pUserNotice);
|
|
|
|
if (pCertName)
|
|
free(pCertName);
|
|
|
|
if (pCertPolicy95Qualifier)
|
|
free(pCertPolicy95Qualifier);
|
|
|
|
return dwRetCode;
|
|
|
|
ErrorExit:
|
|
if (*ppwszUrlString)
|
|
{
|
|
free(*ppwszUrlString);
|
|
*ppwszUrlString = NULL;
|
|
}
|
|
|
|
if (*ppwszDisplayText)
|
|
{
|
|
free(*ppwszDisplayText);
|
|
*ppwszDisplayText = NULL;
|
|
}
|
|
|
|
goto CommonExit;
|
|
}
|
|
|
|
|
|
BOOL IsOKToDisplayCPS(PCCERT_CONTEXT pCertContext, DWORD dwChainError)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
LPWSTR pwszUrlString = NULL;
|
|
LPWSTR pwszDisplayText = NULL;
|
|
|
|
//
|
|
// Check parameters and initialize.
|
|
//
|
|
if (NULL == pCertContext)
|
|
{
|
|
goto CommonExit;
|
|
}
|
|
|
|
//
|
|
// Get CPS info.
|
|
//
|
|
if ((0 == GetCPSInfo(pCertContext, &pwszUrlString, &pwszDisplayText)) &&
|
|
(pwszDisplayText || IsOKToFormatAsLinkW(pwszUrlString, dwChainError)))
|
|
{
|
|
fResult = TRUE;
|
|
}
|
|
|
|
CommonExit:
|
|
|
|
if (pwszUrlString)
|
|
{
|
|
free(pwszUrlString);
|
|
}
|
|
|
|
if (pwszDisplayText)
|
|
{
|
|
free(pwszDisplayText);
|
|
}
|
|
|
|
return fResult;
|
|
}
|
|
|
|
|
|
BOOL DisplayCPS(HWND hwnd, PCCERT_CONTEXT pCertContext, DWORD dwChainError, BOOL fNoCOM)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
DWORD dwRetCode = 0;
|
|
CPS_STATE_STRUCT cpsStateStruct;
|
|
|
|
//
|
|
// Initialize and check parameters.
|
|
//
|
|
memset(&cpsStateStruct, 0, sizeof(cpsStateStruct));
|
|
|
|
if (NULL == pCertContext)
|
|
{
|
|
goto Return;
|
|
}
|
|
|
|
//
|
|
// Get CPS info.
|
|
//
|
|
if (0 != (dwRetCode = GetCPSInfo(pCertContext, &cpsStateStruct.pwszURL, &cpsStateStruct.pwszDisplayText)))
|
|
{
|
|
goto Return;
|
|
}
|
|
|
|
// NOW, set up for, and launch that dialog
|
|
if ((HmodRichEdit == NULL) && (NULL == (HmodRichEdit = LoadLibraryA("RichEd20.dll"))))
|
|
{
|
|
goto Return;
|
|
}
|
|
|
|
//
|
|
// If there is an URL but no text then just invoke the browser and don't bring
|
|
// up the dialog
|
|
//
|
|
if ((cpsStateStruct.pwszDisplayText == NULL) && (cpsStateStruct.pwszURL != NULL))
|
|
{
|
|
DWORD numBytes = 0;
|
|
LPSTR pszURL = NULL;
|
|
|
|
pszURL = CertUIMkMBStr(cpsStateStruct.pwszURL);
|
|
if (pszURL == NULL)
|
|
{
|
|
goto Return;
|
|
}
|
|
|
|
CryptuiGoLink(hwnd, pszURL, fNoCOM);
|
|
free(pszURL);
|
|
}
|
|
else
|
|
{
|
|
cpsStateStruct.fNoCOM = fNoCOM;
|
|
cpsStateStruct.dwChainError = dwChainError;
|
|
DialogBoxParamU(HinstDll, (LPWSTR) MAKEINTRESOURCE(IDD_CPS_DIALOG), hwnd, CPSDlgProc, (LPARAM) &cpsStateStruct);
|
|
}
|
|
|
|
fRet = TRUE;
|
|
|
|
Return:
|
|
|
|
if (cpsStateStruct.pwszURL)
|
|
free(cpsStateStruct.pwszURL);
|
|
|
|
if (cpsStateStruct.pwszDisplayText)
|
|
free(cpsStateStruct.pwszDisplayText);
|
|
|
|
return fRet;
|
|
}
|