//---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1996
//
//  File:      util.cxx
//
//  Contents:  Some misc helper functions
//
//  History:
//----------------------------------------------------------------------------
#include "ldapc.hxx"
#pragma hdrstop

#if 0
// ADsGetSearchPreference code
//
// Must explicitly include here since adshlp.h
// is not #included.
//
#ifdef __cplusplus
extern "C" {
#endif

HRESULT WINAPI
ADsGetSearchPreference(
    ADS_SEARCH_HANDLE hSearchHandle,
    LPWSTR lpszPathName,
    PADS_SEARCHPREF_INFO *ppSearchPrefs,
    PDWORD pdwNumPrefs
    );

#ifdef __cplusplus
}
#endif


HRESULT
UTF8ToUnicodeString(
    LPCSTR   pUTF8,
    LPWSTR *ppUnicode
    );
#endif

//
// The following table needs to be sorted
//
SEARCHENTRY g_aSyntaxSearchTable[] =
{
  { TEXT("AccessPointDN"),   LDAPTYPE_ACCESSPOINTDN }, // in NTDS, not in LDAP
  { TEXT("AttributeTypeDescription"), LDAPTYPE_ATTRIBUTETYPEDESCRIPTION },
  { TEXT("Audio"),           LDAPTYPE_AUDIO },
  { TEXT("Binary"),          LDAPTYPE_OCTETSTRING },
  { TEXT("BitString"),       LDAPTYPE_BITSTRING },
  { TEXT("Boolean"),         LDAPTYPE_BOOLEAN },
  { TEXT("CaseExactString") ,LDAPTYPE_CASEEXACTSTRING },
  { TEXT("CaseIgnoreString"),LDAPTYPE_CASEIGNORESTRING },  // in NTDS, not in LDAP RFC
  { TEXT("Certificate"),     LDAPTYPE_CERTIFICATE },
  { TEXT("CertificateList"), LDAPTYPE_CERTIFICATELIST },
  { TEXT("CertificatePair"), LDAPTYPE_CERTIFICATEPAIR },
  { TEXT("Country"),         LDAPTYPE_COUNTRYSTRING },
  { TEXT("DataQualitySyntax"),LDAPTYPE_DATAQUALITYSYNTAX },
  { TEXT("DeliveryMethod"),  LDAPTYPE_DELIVERYMETHOD },
  { TEXT("DirectoryString"), LDAPTYPE_DIRECTORYSTRING },
  { TEXT("DN"),              LDAPTYPE_DN },
  { TEXT("DSAQualitySyntax"),LDAPTYPE_DSAQUALITYSYNTAX },
  { TEXT("EnhancedGuide"),   LDAPTYPE_ENHANCEDGUIDE },
  { TEXT("FacsimileTelephoneNumber"),   LDAPTYPE_FACSIMILETELEPHONENUMBER },
  { TEXT("Fax"),             LDAPTYPE_FAX },
  { TEXT("GeneralizedTime"), LDAPTYPE_GENERALIZEDTIME },
  { TEXT("Guide"),           LDAPTYPE_GUIDE },
  { TEXT("IA5String"),       LDAPTYPE_IA5STRING },
  { TEXT("INTEGER"),         LDAPTYPE_INTEGER },
  { TEXT("INTEGER8"),        LDAPTYPE_INTEGER8 }, // in NTDS, not in LDAP RFC
  { TEXT("JPEG"),            LDAPTYPE_JPEG },
  { TEXT("MailPreference"),  LDAPTYPE_MAILPREFERENCE },
  { TEXT("NameAndOptionalUID"), LDAPTYPE_NAMEANDOPTIONALUID },
  { TEXT("NumericString"),   LDAPTYPE_NUMERICSTRING },
  { TEXT("ObjectClassDescription"), LDAPTYPE_OBJECTCLASSDESCRIPTION },
  { TEXT("ObjectSecurityDescriptor"), LDAPTYPE_SECURITY_DESCRIPTOR},
  { TEXT("OctetString"),     LDAPTYPE_OCTETSTRING }, // in NTDS, not in LDAP RFC
  { TEXT("OID"),             LDAPTYPE_OID },
  { TEXT("ORAddress"),       LDAPTYPE_ORADDRESS },
  { TEXT("ORName"),          LDAPTYPE_ORNAME },  // in NTDS, not in LDAP RFC
  { TEXT("OtherMailbox"),    LDAPTYPE_OTHERMAILBOX },
  { TEXT("Password"),        LDAPTYPE_PASSWORD },
  { TEXT("PostalAddress"),   LDAPTYPE_POSTALADDRESS },
  { TEXT("PresentationAddress"), LDAPTYPE_PRESENTATIONADDRESS },
  { TEXT("PrintableString"), LDAPTYPE_PRINTABLESTRING },
  { TEXT("TelephoneNumber"), LDAPTYPE_TELEPHONENUMBER },
  { TEXT("TeletexTerminalIdentifier"), LDAPTYPE_TELETEXTERMINALIDENTIFIER },
  { TEXT("TelexNumber"),     LDAPTYPE_TELEXNUMBER },
  // 
  // Allegedly, "Time" started out as a bug in the schema (the correct description
  // is "GeneralizedTime").  However, we never delete items from the schema, so it
  // is still present in the current Whistler schema
  // (4/27/2000), so we'll keep in support for it.
  //
  { TEXT("Time"),            LDAPTYPE_GENERALIZEDTIME },
  { TEXT("UTCTIME"),         LDAPTYPE_UTCTIME }
};

DWORD g_nSyntaxSearchTableSize = ARRAY_SIZE(g_aSyntaxSearchTable );


//
// The following table needs to be sorted (lexicographically) on the first field
//

SEARCHENTRY g_aOidSyntaxSearchTable[] = {
  // the type is ORName a type of string -> mapped to string.
  { TEXT("1.2.840.113556.1.4.1221"),           LDAPTYPE_CASEIGNORESTRING },
  // the type is Undefined syntax in the server, so we are defaulting.
  { TEXT("1.2.840.113556.1.4.1222"),           LDAPTYPE_OCTETSTRING},
  { TEXT("1.2.840.113556.1.4.1362"),           LDAPTYPE_CASEEXACTSTRING},
  { TEXT("1.2.840.113556.1.4.903"),            LDAPTYPE_DNWITHBINARY},
  { TEXT("1.2.840.113556.1.4.904"),            LDAPTYPE_DNWITHSTRING},
  { TEXT("1.2.840.113556.1.4.905"),            LDAPTYPE_CASEIGNORESTRING },
  { TEXT("1.2.840.113556.1.4.906"),            LDAPTYPE_INTEGER8 },
  { TEXT("1.2.840.113556.1.4.907"),            LDAPTYPE_SECURITY_DESCRIPTOR },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.10"),     LDAPTYPE_CERTIFICATEPAIR },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.11"),     LDAPTYPE_COUNTRYSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.12"),     LDAPTYPE_DN },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.13"),     LDAPTYPE_DATAQUALITYSYNTAX },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.14"),     LDAPTYPE_DELIVERYMETHOD },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.15"),     LDAPTYPE_DIRECTORYSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.19"),     LDAPTYPE_DSAQUALITYSYNTAX },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.2"),      LDAPTYPE_ACCESSPOINTDN },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.21"),     LDAPTYPE_ENHANCEDGUIDE },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.22"),     LDAPTYPE_FACSIMILETELEPHONENUMBER },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.23"),     LDAPTYPE_FAX },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.24"),     LDAPTYPE_GENERALIZEDTIME },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.25"),     LDAPTYPE_GUIDE },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.26"),     LDAPTYPE_IA5STRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.27"),     LDAPTYPE_INTEGER },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.28"),     LDAPTYPE_JPEG },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.3"),      LDAPTYPE_ATTRIBUTETYPEDESCRIPTION },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.32"),     LDAPTYPE_MAILPREFERENCE },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.33"),     LDAPTYPE_ORADDRESS },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.34"),     LDAPTYPE_NAMEANDOPTIONALUID },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.36"),     LDAPTYPE_NUMERICSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.37"),     LDAPTYPE_OBJECTCLASSDESCRIPTION },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.38"),     LDAPTYPE_OID },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.39"),     LDAPTYPE_OTHERMAILBOX },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.4"),      LDAPTYPE_AUDIO },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.40"),     LDAPTYPE_OCTETSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.41"),     LDAPTYPE_POSTALADDRESS },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.43"),     LDAPTYPE_PRESENTATIONADDRESS },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.44"),     LDAPTYPE_PRINTABLESTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.5"),      LDAPTYPE_OCTETSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.50"),     LDAPTYPE_TELEPHONENUMBER },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.51"),     LDAPTYPE_TELETEXTERMINALIDENTIFIER },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.52"),     LDAPTYPE_TELEXNUMBER },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.53"),     LDAPTYPE_UTCTIME },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.6"),      LDAPTYPE_BITSTRING },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.7"),      LDAPTYPE_BOOLEAN },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.8"),      LDAPTYPE_CERTIFICATE },
  { TEXT("1.3.6.1.4.1.1466.115.121.1.9"),      LDAPTYPE_CERTIFICATELIST },
};

DWORD g_nOidSyntaxSearchTableSize = ARRAY_SIZE(g_aOidSyntaxSearchTable );

DWORD
GetSyntaxOfAttribute(
    LPWSTR pszAttrName,
    SCHEMAINFO *pSchemaInfo
)
{
    LPWSTR pszTemp = NULL;

    // Support for range attributes; for eg., objectClass=Range=0-1 We should
    // ignore everything after ';' inclusive.
    //

    if ((pszTemp = wcschr(pszAttrName, L';')) != NULL ) {
        *pszTemp = L'\0';
    }

    DWORD dwEntry = FindEntryInSearchTable( pszAttrName, pSchemaInfo->aPropertiesSearchTable, pSchemaInfo->nNumOfProperties * 2 );

    //
    // Put back the ; if we had replaced it.
    //

    if (pszTemp)
        *pszTemp = L';';

    if ( dwEntry != -1 )
    {
        DWORD dwSyntax = FindEntryInSearchTable( pSchemaInfo->aProperties[dwEntry].pszSyntax, g_aSyntaxSearchTable, ARRAY_SIZE(g_aSyntaxSearchTable) );

        if ( dwSyntax != -1 )
            return dwSyntax;
    }

    return LDAPTYPE_UNKNOWN;
}


DWORD
LdapGetSyntaxIdFromName(
    LPWSTR  pszSyntax
)
{
    DWORD dwSyntaxId;

    dwSyntaxId = FindEntryInSearchTable(
                      pszSyntax,
                      g_aSyntaxSearchTable,
                      g_nSyntaxSearchTableSize );

    if ( dwSyntaxId == -1 ) {

        //
        // We need to look at the OID table before defaulting
        //
        dwSyntaxId = FindEntryInSearchTable(
                         pszSyntax,
                         g_aOidSyntaxSearchTable,
                         g_nOidSyntaxSearchTableSize
                         );
    }

    if (dwSyntaxId == -1 ) {
        dwSyntaxId = LDAPTYPE_UNKNOWN;
    }

    return dwSyntaxId;
}


HRESULT
UnMarshallLDAPToLDAPSynID(
    LPWSTR  pszAttrName,
    ADS_LDP *ld,
    LDAPMessage *entry,
    DWORD dwSyntax,
    LDAPOBJECTARRAY *pldapObjectArray
)
{
    HRESULT hr = S_OK;
    DWORD dwStatus = 0;
    int nNumberOfValues;

    switch ( dwSyntax ) {

        // The cases below are binary data
        case LDAPTYPE_OCTETSTRING:
        case LDAPTYPE_SECURITY_DESCRIPTOR:
        case LDAPTYPE_CERTIFICATE:
        case LDAPTYPE_CERTIFICATELIST:
        case LDAPTYPE_CERTIFICATEPAIR:
        case LDAPTYPE_PASSWORD:
        case LDAPTYPE_TELETEXTERMINALIDENTIFIER:
        case LDAPTYPE_AUDIO:
        case LDAPTYPE_JPEG:
        case LDAPTYPE_FAX:
        case LDAPTYPE_UNKNOWN:
        {
            struct berval **bValues = NULL;

            hr = LdapGetValuesLen( ld, entry, pszAttrName,
                                   &bValues, &nNumberOfValues );
            BAIL_ON_FAILURE(hr);

            pldapObjectArray->fIsString = FALSE;
            pldapObjectArray->dwCount = nNumberOfValues;
            pldapObjectArray->pLdapObjects = (PLDAPOBJECT) bValues;

            break;
        }


        // otherwise it is a string
        default:
        {
            TCHAR **strValues = NULL;
            hr  = LdapGetValues( ld, entry, pszAttrName,
                                 &strValues, &nNumberOfValues );
            BAIL_ON_FAILURE(hr);

            pldapObjectArray->fIsString = TRUE;
            pldapObjectArray->dwCount = nNumberOfValues;
            pldapObjectArray->pLdapObjects = (PLDAPOBJECT) strValues;

            break;
        }
    }

error:

    RRETURN(hr);
}

#if 0
// ADsGetSearchPreference code

HRESULT WINAPI
ADsGetSearchPreference(
    ADS_SEARCH_HANDLE hSearchHandle,
    LPWSTR lpszPathName,
    PADS_SEARCHPREF_INFO *ppSearchPrefs,
    PDWORD pdwNumPrefs
    )
{
    HRESULT hr = S_OK;
    OBJECTINFO ObjectInfo;
    POBJECTINFO pObjectInfo = &ObjectInfo;

    PLDAP_SEARCHINFO     phSearchInfo= (PLDAP_SEARCHINFO) hSearchHandle;
    PLDAP_SEARCH_PREF    pSearchPref    = NULL;
    PADS_SEARCHPREF_INFO pADsSearchPref = NULL;
    PBYTE                pbExtra        = NULL;

    DWORD dwNumberPrefs = 0;
    DWORD dwNumberExtraBytes = 0;

    //
    // sanity check
    //
    if (!lpszPathName || !ppSearchPrefs || !phSearchInfo || !pdwNumPrefs)
        RRETURN(E_INVALIDARG);

    *ppSearchPrefs = NULL;
    *pdwNumPrefs = 0;

    //
    // Make sure we're being called on an LDAP path
    //
    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    hr = ADsObject(lpszPathName, pObjectInfo);
    BAIL_ON_FAILURE(hr);

    if (_tcscmp(pObjectInfo->ProviderName, szProviderName) != 0) {
        BAIL_ON_FAILURE(hr = E_NOTIMPL);
    }

    //
    // allocate space for the ADS_SEARCHPREF_INFO array we're
    // going to build
    //
    pSearchPref = &(phSearchInfo->_SearchPref);
    
    hr = CalcSpaceForSearchPrefs(pSearchPref, &dwNumberPrefs, &dwNumberExtraBytes);
    BAIL_ON_FAILURE(hr);

    // no search prefs were set
    if (dwNumberPrefs == 0) {
        *ppSearchPrefs = NULL;
        FreeObjectInfo(pObjectInfo);
        RRETURN(S_OK);
    }
    

    pADsSearchPref = (PADS_SEARCHPREF_INFO) AllocADsMem( (dwNumberPrefs * sizeof(ADS_SEARCHPREF_INFO)) + dwNumberExtraBytes);
    if (!pADsSearchPref)
        BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);

    pbExtra = ((PBYTE)pADsSearchPref) + (dwNumberPrefs * sizeof(ADS_SEARCHPREF_INFO));
    

    //
    // construct the array of search prefs
    //
    hr = ConstructSearchPrefArray(pSearchPref, pADsSearchPref, pbExtra);
    BAIL_ON_FAILURE(hr);

    *ppSearchPrefs = pADsSearchPref;
    *pdwNumPrefs   = dwNumberPrefs;

    FreeObjectInfo(pObjectInfo);

    RRETURN(hr);
    
error:

    if (pADsSearchPref)
        FreeADsMem(pADsSearchPref);

    if (pObjectInfo) {
        FreeObjectInfo(pObjectInfo);
    }


    RRETURN(hr);
}


HRESULT
ConstructSearchPrefArray(
    PLDAP_SEARCH_PREF    pPrefs,
    PADS_SEARCHPREF_INFO pADsSearchPref,
    PBYTE                pbExtraBytes
    )
{
    HRESULT hr = S_OK;
    BOOL fDefault;

    LPSTR pUTF8 = NULL;
    LPWSTR pUnicode = NULL;

    if (!pPrefs || !pADsSearchPref || !pbExtraBytes)
        RRETURN(E_INVALIDARG);


    // ADS_SEARCHPREF_ASYNCHRONOUS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ASYNCHRONOUS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_ASYNCHRONOUS;
        pADsSearchPref->vValue.dwType  = ADSTYPE_BOOLEAN;
        pADsSearchPref->vValue.Boolean = pPrefs->_fAsynchronous;
        pADsSearchPref++;
    }
    
    // ADS_SEARCHPREF_DEREF_ALIASES
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_DEREF_ALIASES,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_DEREF_ALIASES;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_dwDerefAliases;
        pADsSearchPref++;
    }
    
    // ADS_SEARCHPREF_SIZE_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SIZE_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_SIZE_LIMIT;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_dwSizeLimit;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_TIME_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TIME_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_TIME_LIMIT;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_dwTimeLimit;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_ATTRIBTYPES_ONLY
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ATTRIBTYPES_ONLY,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_ATTRIBTYPES_ONLY;
        pADsSearchPref->vValue.dwType  = ADSTYPE_BOOLEAN;
        pADsSearchPref->vValue.Boolean = pPrefs->_fAttrsOnly;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_SEARCH_SCOPE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SEARCH_SCOPE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        switch(pPrefs->_dwSearchScope) {
        case LDAP_SCOPE_SUBTREE:
            pADsSearchPref->vValue.Integer = ADS_SCOPE_SUBTREE;
            break;

        case LDAP_SCOPE_ONELEVEL:
            pADsSearchPref->vValue.Integer = ADS_SCOPE_ONELEVEL;
            break;
        
        case LDAP_SCOPE_BASE:
            pADsSearchPref->vValue.Integer = ADS_SCOPE_BASE;
            break;
        }

        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_TIMEOUT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TIMEOUT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_TIMEOUT;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_timeout.tv_sec;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_PAGESIZE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_PAGESIZE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_dwPageSize;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_PAGED_TIME_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_PAGED_TIME_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_PAGED_TIME_LIMIT;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        pADsSearchPref->vValue.Integer = pPrefs->_dwPagedTimeLimit;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_CHASE_REFERRALS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_CHASE_REFERRALS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_CHASE_REFERRALS;
        pADsSearchPref->vValue.dwType  = ADSTYPE_INTEGER;
        switch(pPrefs->_dwChaseReferrals) {
        case (DWORD) (DWORD_PTR)LDAP_OPT_OFF:
            pADsSearchPref->vValue.Integer = ADS_CHASE_REFERRALS_NEVER;
            break;

        case LDAP_CHASE_SUBORDINATE_REFERRALS:
            pADsSearchPref->vValue.Integer = ADS_CHASE_REFERRALS_SUBORDINATE;
            break;
        
        case LDAP_CHASE_EXTERNAL_REFERRALS:
            pADsSearchPref->vValue.Integer = ADS_CHASE_REFERRALS_EXTERNAL;
            break;

        case (DWORD) (DWORD_PTR) LDAP_OPT_ON:
            pADsSearchPref->vValue.Integer = ADS_CHASE_REFERRALS_ALWAYS;
            break;

        }

        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_CACHE_RESULTS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_CACHE_RESULTS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_CACHE_RESULTS;
        pADsSearchPref->vValue.dwType  = ADSTYPE_BOOLEAN;
        pADsSearchPref->vValue.Boolean = pPrefs->_fCacheResults;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_TOMBSTONE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TOMBSTONE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_TOMBSTONE;
        pADsSearchPref->vValue.dwType  = ADSTYPE_BOOLEAN;
        pADsSearchPref->vValue.Boolean = pPrefs->_fTombStone;
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_DIRSYNC
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_DIRSYNC,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_DIRSYNC;
        pADsSearchPref->vValue.dwType  = ADSTYPE_PROV_SPECIFIC;
        pADsSearchPref->vValue.ProviderSpecific.dwLength = 0;
        pADsSearchPref->vValue.ProviderSpecific.lpValue = NULL;

        if (pPrefs->_pProvSpecific && pPrefs->_pProvSpecific->lpValue) {
            memcpy(pbExtraBytes, pPrefs->_pProvSpecific->lpValue, pPrefs->_pProvSpecific->dwLength);

            pADsSearchPref->vValue.ProviderSpecific.lpValue  = pbExtraBytes;
            pADsSearchPref->vValue.ProviderSpecific.dwLength = pPrefs->_pProvSpecific->dwLength;
            
            pbExtraBytes += pPrefs->_pProvSpecific->dwLength;
        }
        
        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_VLV
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_VLV,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_VLV;
        pADsSearchPref->vValue.dwType  = ADSTYPE_PROV_SPECIFIC;
        pADsSearchPref->vValue.ProviderSpecific.dwLength = sizeof(ADS_VLV);
        pADsSearchPref->vValue.ProviderSpecific.lpValue = pbExtraBytes;

        PADS_VLV pADsVLV = (PADS_VLV) pbExtraBytes;
        pbExtraBytes += sizeof(ADS_VLV);

        pADsVLV->dwBeforeCount  = pPrefs->_pVLVInfo->ldvlv_before_count;
        pADsVLV->dwAfterCount   = pPrefs->_pVLVInfo->ldvlv_after_count;
        pADsVLV->dwOffset       = pPrefs->_pVLVInfo->ldvlv_offset;
        pADsVLV->dwContentCount = pPrefs->_pVLVInfo->ldvlv_count;
        pADsVLV->pszTarget  = NULL;
        pADsVLV->lpContextID = NULL;
        pADsVLV->dwContextIDLength = 0;

        if (pPrefs->_pVLVInfo->ldvlv_attrvalue && pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_val) {
            // As stored, the attribute is a non-terminated UTF-8 string.
            // We need to return a NULL-terminated Unicode string.
            // We do this by constructing a NULL-terminated UTF-8 string, and then
            // converting that to a Unicode string.

            pUTF8 = (PCHAR) AllocADsMem(pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_len + 1);
            if (!pUTF8)
                BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);

            memcpy(pUTF8,
                   pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_val,
                   pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_len);

            pUTF8[pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_len] = '\0';
                   
            hr = UTF8ToUnicodeString(pUTF8, &pUnicode);
            BAIL_ON_FAILURE(hr);
            
            memcpy(pbExtraBytes,
                   pUnicode,
                   (wcslen(pUnicode)+1) * sizeof(WCHAR));

            pADsVLV->pszTarget = (LPWSTR) pbExtraBytes;
            pbExtraBytes += (wcslen(pUnicode)+1) * sizeof(WCHAR);
        }

        if (pPrefs->_pVLVInfo->ldvlv_context && pPrefs->_pVLVInfo->ldvlv_context->bv_val) {
            memcpy(pbExtraBytes,
                   pPrefs->_pVLVInfo->ldvlv_context->bv_val,
                   pPrefs->_pVLVInfo->ldvlv_context->bv_len);

            pADsVLV->lpContextID = pbExtraBytes;
            pADsVLV->dwContextIDLength = pPrefs->_pVLVInfo->ldvlv_context->bv_len;
            pbExtraBytes += pPrefs->_pVLVInfo->ldvlv_context->bv_len;
        }

        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_SORT_ON
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SORT_ON,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_SORT_ON;
        pADsSearchPref->vValue.dwType  = ADSTYPE_PROV_SPECIFIC;
        pADsSearchPref->vValue.ProviderSpecific.dwLength = sizeof(ADS_SORTKEY) * pPrefs->_nSortKeys;
        pADsSearchPref->vValue.ProviderSpecific.lpValue  = pbExtraBytes;

        PADS_SORTKEY pSortKeys = (PADS_SORTKEY) pbExtraBytes;
        pbExtraBytes += (sizeof(ADS_SORTKEY) * pPrefs->_nSortKeys);

        DWORD i;
        for (i=0; i < pPrefs->_nSortKeys; i++) {
            pSortKeys[i].fReverseorder = pPrefs->_pSortKeys[i].sk_reverseorder;
            pSortKeys[i].pszReserved   = pPrefs->_pSortKeys[i].sk_matchruleoid;
            pSortKeys[i].pszAttrType   = (LPWSTR) pbExtraBytes;
            memcpy(pbExtraBytes,
                   pPrefs->_pSortKeys[i].sk_attrtype,
                   (wcslen(pPrefs->_pSortKeys[i].sk_attrtype)+1) * sizeof(WCHAR)
                   );
            pbExtraBytes += (wcslen(pPrefs->_pSortKeys[i].sk_attrtype)+1) * sizeof(WCHAR);
        }

        pADsSearchPref++;
    }

    // ADS_SEARCHPREF_ATTRIBUTE_QUERY
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ATTRIBUTE_QUERY,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        pADsSearchPref->dwSearchPref = ADS_SEARCHPREF_ATTRIBUTE_QUERY;
        pADsSearchPref->vValue.dwType  = ADSTYPE_CASE_IGNORE_STRING;
        pADsSearchPref->vValue.CaseIgnoreString = pbExtraBytes;
        
        // copy SourceAttribute
        DWORD dwLen = (wcslen(pPrefs->_pAttribScoped) + 1) * sizeof(WCHAR);
        
        memcpy(pbExtraBytes,
               pPrefs->_pAttribScoped,
               dwLen);
        pbExtraBytes += dwLen ;

        pADsSearchPref++;
    }
    
    
error:

    if (pUTF8)
        FreeADsMem(pUTF8);

    if (pUnicode)
        FreeADsMem(pUnicode);
        

    RRETURN(hr);
}


HRESULT
CalcSpaceForSearchPrefs(
    PLDAP_SEARCH_PREF pPrefs,
    PDWORD            pdwNumberPrefs,
    PDWORD            pdwNumberExtraBytes
    )
{
    if (!pPrefs || !pdwNumberPrefs || !pdwNumberExtraBytes)
        RRETURN(E_INVALIDARG);

    *pdwNumberPrefs = 0;
    *pdwNumberExtraBytes = 0;

    //
    // Calculate the number of ADS_SEARCHPREF_INFOs required
    // for search prefs that do _not_ require extra space.
    // (Currently, only _SORT_ON, _DIRSYNC, _VLV, _ATTRIBUTE_QUERY
    // require extra space).
    //
    // A ADS_SEARCHPREF_INFO is "required" if the corresponding
    // search pref is not set to its default value, as determined
    // by IsSearchPrefSetToDefault.
    //
    
    BOOL fDefault;
    HRESULT hr = S_OK;

    // ADS_SEARCHPREF_ASYNCHRONOUS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ASYNCHRONOUS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;
    
    // ADS_SEARCHPREF_DEREF_ALIASES
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_DEREF_ALIASES,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_SIZE_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SIZE_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_TIME_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TIME_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_ATTRIBTYPES_ONLY
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ATTRIBTYPES_ONLY,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_SEARCH_SCOPE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SEARCH_SCOPE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_TIMEOUT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TIMEOUT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;
        
    // ADS_SEARCHPREF_PAGESIZE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_PAGESIZE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;
        
    // ADS_SEARCHPREF_PAGED_TIME_LIMIT
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_PAGED_TIME_LIMIT,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;
        
    // ADS_SEARCHPREF_CHASE_REFERRALS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_CHASE_REFERRALS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_CACHE_RESULTS
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_CACHE_RESULTS,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;

    // ADS_SEARCHPREF_TOMBSTONE
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_TOMBSTONE,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault)
        (*pdwNumberPrefs)++;


    //
    // _VLV, _DIRSYNC, _ATTRIBUTE_QUERY, and _SORT_ON require extra space in addition
    // to the ADS_SEARCHPREF_INFO structure.
    //

    // ADS_SEARCHPREF_DIRSYNC
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_DIRSYNC,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        (*pdwNumberPrefs)++;

        if (pPrefs->_pProvSpecific && pPrefs->_pProvSpecific->dwLength > 0) {
            *pdwNumberExtraBytes += pPrefs->_pProvSpecific->dwLength;
        }
    }

    // ADS_SEARCHPREF_VLV
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_VLV,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        (*pdwNumberPrefs)++;

        *pdwNumberExtraBytes += sizeof(ADS_VLV);

        if (pPrefs->_pVLVInfo->ldvlv_context) {
            *pdwNumberExtraBytes += pPrefs->_pVLVInfo->ldvlv_context->bv_len;
        }

        if (pPrefs->_pVLVInfo->ldvlv_attrvalue) {
            // As stored, the string is a UTF-8 string that is not NULL-terminated.
            // We need to calculate the size of a NULL-terminated Unicode string.

            int cch = MultiByteToWideChar(CP_UTF8,
                                          0,
                                          pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_val,
                                          pPrefs->_pVLVInfo->ldvlv_attrvalue->bv_len,
                                          NULL,
                                          0);
            if (!cch)
                BAIL_ON_FAILURE(hr = E_FAIL);
            
            // add one WCHAR for NULL terminator
            *pdwNumberExtraBytes += ((cch*sizeof(WCHAR)) + sizeof(WCHAR));
        }
    }

    // ADS_SEARCHPREF_SORT_ON
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_SORT_ON,
                                                  pPrefs,
                                                  &fDefault));
    if (!fDefault) {
        (*pdwNumberPrefs)++;

        *pdwNumberExtraBytes += (sizeof(ADS_SORTKEY) * pPrefs->_nSortKeys);

        DWORD i;
        for (i=0; i<pPrefs->_nSortKeys; i++) {
            *pdwNumberExtraBytes += ((wcslen(pPrefs->_pSortKeys[i].sk_attrtype)+1) * sizeof(WCHAR));
        }
    }

    // ADS_SEARCHPREF_ATTRIBUTE_QUERY
    BAIL_ON_FAILURE(hr = IsSearchPrefSetToDefault(ADS_SEARCHPREF_ATTRIBUTE_QUERY,
                                                  pPrefs,
                                                  &fDefault));

    if (!fDefault) {
        (*pdwNumberPrefs)++;

        *pdwNumberExtraBytes += ((wcslen(pPrefs->_pAttribScoped) + 1) * sizeof(WCHAR));

    }



error:
    RRETURN(hr);

}


//
// This function tests whether a given search pref is set
// to it's default value.
//
// Important: This function considers the "default value"
// to be the value currently set in CLDAPGenObject::InitSearchPrefs.
// If you ever change those defaults, update this function.
//
HRESULT
IsSearchPrefSetToDefault(
    ADS_SEARCHPREF_ENUM pref,
    PLDAP_SEARCH_PREF   pPrefs,
    PBOOL               pfDefault
    )
{

    if (!pPrefs || !pfDefault)
        RRETURN(E_INVALIDARG);

    *pfDefault = TRUE;

    switch(pref) {

    case ADS_SEARCHPREF_ASYNCHRONOUS:
        // default: not async
        if (pPrefs->_fAsynchronous)
            *pfDefault = FALSE;
        break;
    
    case ADS_SEARCHPREF_DEREF_ALIASES:
        // default: do not deref
        if (pPrefs->_dwDerefAliases)
            *pfDefault = FALSE;
        break;
    
    case ADS_SEARCHPREF_SIZE_LIMIT:
        // default: no size limit
        if (pPrefs->_dwSizeLimit)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_TIME_LIMIT:
        // default: no time limit
        if (pPrefs->_dwTimeLimit)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_ATTRIBTYPES_ONLY:
        // default: not attribtypes only
        if (pPrefs->_fAttrsOnly)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_SEARCH_SCOPE:
        // default: LDAP_SCOPE_SUBTREE
        if (pPrefs->_dwSearchScope != LDAP_SCOPE_SUBTREE)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_TIMEOUT:
        // default: no timeout
        if  (pPrefs->_timeout.tv_sec || pPrefs->_timeout.tv_usec)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_PAGESIZE:
        // default: no pagesize
        if (pPrefs->_dwPageSize)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_PAGED_TIME_LIMIT:
        // default: no paged time limit
        if (pPrefs->_dwPagedTimeLimit)
            *pfDefault = FALSE;
       break;

    case ADS_SEARCHPREF_CHASE_REFERRALS:
        // default: ADS_CHASE_REFERRALS_EXTERNAL
        if (pPrefs->_dwChaseReferrals != ADS_CHASE_REFERRALS_EXTERNAL)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_SORT_ON:
        // default: no sorting
        if (pPrefs->_pSortKeys)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_CACHE_RESULTS:
        // default: cache results
        if (!pPrefs->_fCacheResults)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_DIRSYNC:
        // default: not a dirsync search
        if (pPrefs->_fDirSync)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_TOMBSTONE:
        // default: don't include tombstones
        if (pPrefs->_fTombStone)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_VLV:
        // default: not a VLV search
        if (pPrefs->_pVLVInfo)
            *pfDefault = FALSE;
        break;

    case ADS_SEARCHPREF_ATTRIBUTE_QUERY:
        // default: not an attribute-scoped query search
        if (pPrefs->_pAttribScoped)
            *pfDefault = FALSE;
        break;

    default:
        RRETURN(E_INVALIDARG);
    }

    RRETURN(S_OK);
}
#endif