// HelperFuncs.cpp : Useful functions
#include "stdafx.h"
#include <time.h>
#include "HelperFuncs.h"
#include "Monitoring.h"
#include "nsconst.h"
#include <wininet.h>
#include <commd5.h>
#include <atlstr.h>

using namespace ATL;

LPWSTR GetVersionString(void);
BOOL PPEscapeUrl(LPCTSTR lpszStringIn,
                 LPTSTR lpszStringOut,
                 DWORD* pdwStrLen,
                 DWORD dwMaxLength,
                 DWORD dwFlags);

LPSTR
CopyHelperA(
    LPSTR   pszDest,
    LPCSTR  pszSrc,
    LPCSTR  pszBufEnd
    )
{
    while( (*pszDest = *pszSrc) && (pszDest <= pszBufEnd))
    {
        pszDest++;
        pszSrc++;
    }
    return( pszDest );
}

LPWSTR
CopyHelperW(
    LPWSTR  pszDest,
    LPCWSTR pszSrc,
    LPCWSTR pszBufEnd
    )
{
    while( (*pszDest = *pszSrc) && (pszDest <= pszBufEnd))
    {
        pszDest++;
        pszSrc++;
    }
    return( pszDest );
}

LPWSTR
CopyNHelperW(
    LPWSTR  pszDest,
    LPCWSTR pszSrc,
    ULONG   ulCount,
    LPCWSTR pszBufEnd
    )
{
    ULONG ulCur = 0;
    while( (*pszDest = *pszSrc) && (pszDest <= pszBufEnd))
    {
        pszDest++;
        pszSrc++;
        if(++ulCur == ulCount) break;
    }

    return pszDest;
}

BSTR
FormatNormalLogoTag(
    LPCWSTR pszLoginServerURL,
    ULONG   ulSiteId,
    LPCWSTR pszReturnURL,
    ULONG   ulTimeWindow,
    BOOL    bForceLogin,
    ULONG   ulCurrentCryptVersion,
    time_t  tCurrentTime,
    LPCWSTR pszCoBrand,
    LPCWSTR pszImageURL,
    LPCWSTR pszNameSpace,
    int     nKPP,
    PM_LOGOTYPE nLogoType,
    USHORT  lang,
    ULONG   ulSecureLevel,
    CRegistryConfig* pCRC,
    BOOL    fRedirToSelf
    )
/*
The old sprintf for reference:
            _snwprintf(text, 2048, L"<A HREF=\"%s?id=%d&ru=%s&tw=%d&fs=%s&kv=%d&ct=%u%s%s\">%s</A>",
                       url, crc->getSiteId(), returnUrl, TimeWindow, ForceLogin ? L"1" : L"0",
                       crc->getCurrentCryptVersion(), ct, CBT?L"&cb=":L"", CBT?CBT:L"", iurl);
*/
{
    WCHAR   text[2048];
    LPWSTR  pszCurrent = text;
    LPCWSTR pszBufEnd = &(text[2047]);

    //  logotag specific format
    pszCurrent = CopyHelperW(pszCurrent, L"<A HREF=\"", pszBufEnd);

    //  call the common formatting function
    //  it is the same for AuthURL and LogoTag
    pszCurrent = FormatAuthURLParameters(pszLoginServerURL,
                                         ulSiteId,
                                         pszReturnURL,
                                         ulTimeWindow,
                                         bForceLogin,
                                         ulCurrentCryptVersion,
                                         tCurrentTime,
                                         pszCoBrand,
                                         pszNameSpace,
                                         nKPP,
                                         pszCurrent,
                                         pszBufEnd - pszCurrent,
                                         lang,
                                         ulSecureLevel,
                                         pCRC,
                                         fRedirToSelf &&
                                            nLogoType == PM_LOGOTYPE_SIGNIN);

    //  more logotag specific formatting
    if(nLogoType == PM_LOGOTYPE_SIGNIN)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&C=1", pszBufEnd);
    }


    pszCurrent = CopyHelperW(pszCurrent, L"\">", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszImageURL, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"</A>", pszBufEnd);

    return ALLOC_AND_GIVEAWAY_BSTR(text);
}

BSTR
FormatUpdateLogoTag(
    LPCWSTR pszLoginServerURL,
    ULONG   ulSiteId,
    LPCWSTR pszReturnURL,
    ULONG   ulTimeWindow,
    BOOL    bForceLogin,
    ULONG   ulCurrentKeyVersion,
    time_t  tCurrentTime,
    LPCWSTR pszCoBrand,
    int     nKPP,
    LPCWSTR pszUpdateServerURL,
    BOOL    bSecure,
    LPCWSTR pszProfileUpdate,
    PM_LOGOTYPE nLogoType,
    ULONG   ulSecureLevel,
    CRegistryConfig* pCRC
)
/*
The old sprintf for reference:
_snwprintf(text, 2048,
                   L"<A HREF=\"%s?id=%d&ru=%s&tw=%d&fs=%s&kv=%d&ct=%u%s%s\">%.*s?id=%d&ct=%u&sec=%s&ru=%s&up=%s%s</A>",
                   url, crc->getSiteId(), returnUrl, TimeWindow, ForceLogin ? L"1" : L"0",
                   crc->getCurrentCryptVersion(), ct, CBT?L"&cb=":L"", CBT?CBT:L"",
           (ins-iurl), iurl, crc->getSiteId(), ct, (bSecure ? L"true" : L"false"),returnUrl,
           newCH, ins+2);
*/
{
    WCHAR   text[2048];
    WCHAR   temp[32];
    WCHAR   siteid[32];
    WCHAR   curtime[32];
    LPWSTR  pszCurrent = text;
    LPCWSTR pszBufEnd = &(text[2047]);
    LPWSTR  pszFirstHalfEnd;

    pszCurrent = CopyHelperW(pszCurrent, L"<A HREF=\"", pszBufEnd);
    LPWSTR signStart1 = pszCurrent;
    pszCurrent = CopyHelperW(pszCurrent, pszLoginServerURL, pszBufEnd);

    if(wcschr(text, L'?') == NULL)
        pszCurrent = CopyHelperW(pszCurrent, L"?id=", pszBufEnd);
    else
        pszCurrent = CopyHelperW(pszCurrent, L"&id=", pszBufEnd);

    _ultow(ulSiteId, siteid, 10);
    pszCurrent = CopyHelperW(pszCurrent, siteid, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&ru=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszReturnURL, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&tw=", pszBufEnd);

    _ultow(ulTimeWindow, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&fs=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, bForceLogin ? L"1" : L"0", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&kv=", pszBufEnd);

    _ultow(ulCurrentKeyVersion, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&ct=", pszBufEnd);

    _ultow(tCurrentTime, curtime, 10);
    pszCurrent = CopyHelperW(pszCurrent, curtime, pszBufEnd);
    if(pszCoBrand)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&cb=", pszBufEnd);
        pszCurrent = CopyHelperW(pszCurrent, pszCoBrand, pszBufEnd);
    }

    if(nKPP != -1)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&kpp=", pszBufEnd);

        _ultow(nKPP, temp, 10);
        pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    }

    if(ulSecureLevel != 0)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&seclog=", pszBufEnd);

        _ultow(ulSecureLevel, temp, 10);
        pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    }

    pszCurrent = CopyHelperW(pszCurrent, L"&ver=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, T2W(GetVersionString()), pszBufEnd);

    if(nLogoType == PM_LOGOTYPE_SIGNIN)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&C=1", pszBufEnd);
    }
    
    SignQueryString(pCRC, ulCurrentKeyVersion, signStart1, pszCurrent, pszBufEnd);
    
    pszCurrent = CopyHelperW(pszCurrent, L"\">", pszBufEnd);

    pszFirstHalfEnd = pszUpdateServerURL ? (wcsstr(pszUpdateServerURL, L"$1")) : NULL;

    pszCurrent = CopyNHelperW(pszCurrent, pszUpdateServerURL, (ULONG)(pszFirstHalfEnd - pszUpdateServerURL), pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"?id=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, siteid, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&ct=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, curtime, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&sec=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, bSecure ? L"true" : L"false", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&ru=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszReturnURL, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&up=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszProfileUpdate, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszFirstHalfEnd + 2, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"</A>", pszBufEnd);

    return ALLOC_AND_GIVEAWAY_BSTR(text);
}

HRESULT SignQueryString(
    CRegistryConfig* pCRC,
    ULONG   ulCurrentKeyVersion,
    LPWSTR  pszBufStart,
    LPWSTR& pszCurrent,
    LPCWSTR pszBufEnd
    )
{
    HRESULT hr = S_OK;
    if(pCRC)
    {
       CComBSTR signature;
       LPWSTR   signStart = wcschr(pszBufStart, L'?');

       // if found before pszCurrent
       if(signStart && signStart < pszCurrent)
       {
           ++signStart;;
       }
       HRESULT hr = PartnerHash(pCRC, ulCurrentKeyVersion, signStart, pszCurrent - signStart, &signature);

       if (hr == S_OK && signature != NULL)
       {
           pszCurrent = CopyHelperW(pszCurrent, L"&tpf=", pszBufEnd);
           pszCurrent = CopyHelperW(pszCurrent, signature, pszBufEnd);
       }
       
       if (!signature && g_pAlert)
           g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_URLSIGNATURE_NOTCREATED,
                        0, NULL);
    }  
    else if(g_pAlert)
        g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_URLSIGNATURE_NOTCREATED,
                        0, NULL);

   return hr;
}


HRESULT PartnerHash(
    CRegistryConfig* pCRC,
    ULONG   ulCurrentKeyVersion,
    LPCWSTR tobeSigned,
    ULONG   nChars,
    BSTR*   pbstrHash)
{
    // MD5 hash the url and query strings + the key of the 
    // 
    if(!pCRC || !pbstrHash) return E_INVALIDARG;

    CCoCrypt* crypt = pCRC->getCrypt(ulCurrentKeyVersion, NULL);
    DWORD keyLen = 0;
    unsigned char* key = NULL;
    CComBSTR bstrHash;
    HRESULT  hr = S_OK;
    BOOL bSigned = FALSE;
    
    if (crypt && (key = crypt->getKeyMaterial(&keyLen)))
    {
       CBinHex  BinHex;
       CComBSTR binHexedKey;
       //encode the key
       hr = BinHex.ToBase64ASCII((BYTE*)key, keyLen, 0, NULL, &binHexedKey);
       if(hr == S_OK)
       {
          // W2A conversion here -- we sign ascii version
          CStringA tobeHashed(tobeSigned, nChars);
          
          tobeHashed += (LPCSTR)(BSTR)binHexedKey;

          CComBSTR bstrOrg;
          bstrOrg.Attach(::SysAllocStringByteLen((LPCSTR)tobeHashed, tobeHashed.GetLength()));
          
          if(bstrOrg != NULL)
          {
             CComPtr<IMD5>  md5;
 
             HRESULT hr = GetGlobalCOMmd5(&md5);
             if(hr == S_OK)
             {
                hr = md5->MD5Hash(bstrOrg, &bstrHash);
 
                if( hr == S_OK && bstrHash != NULL)
                {
                  *pbstrHash = bstrHash.Detach();
                  bSigned = TRUE;
                }
                else
                {
                   *pbstrHash = NULL;
                   bstrHash.Empty(); // so we can use value to determin if things are hashed
                }
             }
         }
      }
    }
    else
    {
        if (g_pAlert )
        {
            g_pAlert->report(PassportAlertInterface::ERROR_TYPE, PM_CURRENTKEY_NOTDEFINED,
                          0, NULL);
        }
    }

    if (!bSigned && g_pAlert)
        g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_URLSIGNATURE_NOTCREATED,
                     0, NULL);
    return hr;
}  


BSTR
FormatAuthURL(
    LPCWSTR pszLoginServerURL,
    ULONG   ulSiteId,
    LPCWSTR pszReturnURL,
    ULONG   ulTimeWindow,
    BOOL    bForceLogin,
    ULONG   ulCurrentKeyVersion,
    time_t  tCurrentTime,
    LPCWSTR pszCoBrand,
    LPCWSTR pszNameSpace,
    int     nKPP,
    USHORT  lang,
    ULONG   ulSecureLevel,
    CRegistryConfig* pCRC,
    BOOL    fRedirToSelf
    )
/*
The old sprintf for reference:
  _snwprintf(text, 2048, L"%s?id=%d&ru=%s&tw=%d&fs=%d&kv=%d&ct=%u%s%s",
             url, crc->getSiteId(), returnUrl, TimeWindow, ForceLogin ? 1 : 0,
             crc->getCurrentCryptVersion(), ct ,CBT?L"&cb=":L"", CBT?CBT:L"");
*/
{
    WCHAR   text[2048] = L"";

    FormatAuthURLParameters(pszLoginServerURL,
                            ulSiteId,
                            pszReturnURL,
                            ulTimeWindow,
                            bForceLogin,
                            ulCurrentKeyVersion,
                            tCurrentTime,
                            pszCoBrand,
                            pszNameSpace,
                            nKPP,
                            text,
                            sizeof(text)/sizeof(WCHAR),
                            lang,
                            ulSecureLevel,
                            pCRC,
                            fRedirToSelf);
    return ALLOC_AND_GIVEAWAY_BSTR(text);
}

//
//  consolidate the code in FormatAuthUrl and NormalLogoTag
//
PWSTR
FormatAuthURLParameters(
    LPCWSTR pszLoginServerURL,
    ULONG   ulSiteId,
    LPCWSTR pszReturnURL,
    ULONG   ulTimeWindow,
    BOOL    bForceLogin,
    ULONG   ulCurrentKeyVersion,
    time_t  tCurrentTime,
    LPCWSTR pszCoBrand,
    LPCWSTR pszNameSpace,
    int     nKPP,
    PWSTR   pszBufStart,
    ULONG   cBufLen,        //  length of buffer in WCHAR
    USHORT  lang,
    ULONG   ulSecureLevel,
    CRegistryConfig* pCRC,
    BOOL    fRedirectToSelf //  if true, this is URL for self redirect
                            //  otherwise the redirect is to the login server
    )
{
    WCHAR   temp[32];
    LPWSTR  pszCurrent = pszBufStart, pszLoginStart, pszSignURLStart = NULL;
    LPCWSTR pszBufEnd = pszBufStart + cBufLen - 1;
    HRESULT hr = S_OK;

    //  helper BSTR ...
    CComBSTR bstrHelper(cBufLen);
    if (fRedirectToSelf)
    {
        //
        //  new authUrl is the return URL + indication a challenge - msppchlg=1 - has to be
        //  done + the rest of the qs parameters as they are in the original
        //  protocol
        //
        DWORD   cchLen = cBufLen;

        if(!InternetCanonicalizeUrl(pszReturnURL,
                                    pszCurrent,
                                    &cchLen,
                                    ICU_DECODE | ICU_NO_ENCODE))
        {
            //  this should not fail ...
            _ASSERT(FALSE);
            return  NULL;
        }

        //  require at least 50 chars
        if (cchLen > cBufLen - 50 )
        {
            _ASSERT(FALSE);
            return  NULL;
        }
        PWSTR psz = pszCurrent;
        while(*psz && *psz != L'?') psz++;
        //  see if URL already contains '?'
        //  if so, the sequence will start with '&'
        if (*psz)
            pszCurrent[cchLen] = L'&';
        else
            pszCurrent[cchLen] = L'?';
        pszCurrent += cchLen + 1;

        // indicate challange
        pszCurrent = CopyHelperW(pszCurrent, PPSITE_CHALLENGE, pszBufEnd);

        // login server ....
        pszCurrent = CopyHelperW(pszCurrent, L"&", pszBufEnd);
        pszCurrent = CopyHelperW(pszCurrent, PPLOGIN_PARAM, pszBufEnd);

        //
        //  remember the start of the login URL
        pszLoginStart = pszCurrent;
        //  use the temp buffer for the rest
        pszCurrent = (BSTR)bstrHelper;
        pszSignURLStart = pszCurrent;
        pszBufEnd = pszCurrent + bstrHelper.Length() - 1;
        //
        //  format loginserverUrl and qs params in a separate buffer, so
        //  they can be escaped ...
        pszCurrent = CopyHelperW(pszCurrent, pszLoginServerURL, pszBufEnd);

        //  start sequence
        if (wcschr(pszLoginServerURL, L'?'))
        {
            //  login server already contains qs
            pszCurrent = CopyHelperW(pszCurrent, L"&", pszBufEnd);
        }
        else
        {
            //  start qs sequence
            pszCurrent = CopyHelperW(pszCurrent, L"?", pszBufEnd);
        }
        //  put lcid first ....
        _ultow(lang, temp, 10);
        pszCurrent = CopyHelperW(pszCurrent, L"lcid=", pszBufEnd);
        pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
        pszCurrent = CopyHelperW(pszCurrent, L"&id=", pszBufEnd);
        //  common code will fill in id and the rest ....
    }
    else
    {
        //  redirect directly to a login server
        pszSignURLStart = pszCurrent;
        pszCurrent = CopyHelperW(pszCurrent, pszLoginServerURL, pszBufEnd);
        //  start sequence
        while(*pszLoginServerURL && *pszLoginServerURL != L'?') pszLoginServerURL++;
        if (*pszLoginServerURL)
            pszCurrent = CopyHelperW(pszCurrent, L"&id=", pszBufEnd);
        else
            pszCurrent = CopyHelperW(pszCurrent, L"?id=", pszBufEnd);
    }


    _ultow(ulSiteId, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);

    // keep the ru, so I don't have to reconstruct
    pszCurrent = CopyHelperW(pszCurrent, L"&ru=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, pszReturnURL, pszBufEnd);

    pszCurrent = CopyHelperW(pszCurrent, L"&tw=", pszBufEnd);

    _ultow(ulTimeWindow, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&fs=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, bForceLogin ? L"1" : L"0", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&kv=", pszBufEnd);

    _ultow(ulCurrentKeyVersion, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, L"&ct=", pszBufEnd);

    _ultow(tCurrentTime, temp, 10);
    pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    if(pszCoBrand)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&cb=", pszBufEnd);
        pszCurrent = CopyHelperW(pszCurrent, pszCoBrand, pszBufEnd);
    }

    if(pszNameSpace)
    {
        if (!_wcsicmp(pszNameSpace, L"email"))
        {
            // namespace == email -> ems=1
            pszCurrent = CopyHelperW(pszCurrent, L"&ems=1", pszBufEnd);
        }
        else
        {
            // regular namespace logic
            pszCurrent = CopyHelperW(pszCurrent, L"&ns=", pszBufEnd);
            pszCurrent = CopyHelperW(pszCurrent, pszNameSpace, pszBufEnd);
        }
    }
    else
    {
        // namespace == null : default to email
        pszCurrent = CopyHelperW(pszCurrent, L"&ems=1", pszBufEnd);
    }

    if(nKPP != -1)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&kpp=", pszBufEnd);

        _ultow(nKPP, temp, 10);
        pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    }

    if(ulSecureLevel != 0)
    {
        pszCurrent = CopyHelperW(pszCurrent, L"&seclog=", pszBufEnd);

        _ultow(ulSecureLevel, temp, 10);
        pszCurrent = CopyHelperW(pszCurrent, temp, pszBufEnd);
    }

    pszCurrent = CopyHelperW(pszCurrent, L"&ver=", pszBufEnd);
    pszCurrent = CopyHelperW(pszCurrent, T2W(GetVersionString()), pszBufEnd);

    // MD5 hash the url and query strings + the key of the 
    // 
    SignQueryString(pCRC, ulCurrentKeyVersion, pszSignURLStart, pszCurrent, pszBufEnd);
#if 0      
    if(pCRC)
    {
       CCoCrypt* crypt = pCRC->getCrypt(ulCurrentKeyVersion, NULL);
       DWORD keyLen = 0;
       DWORD wKeyLen = 0;
       unsigned char* key = NULL;
       CComBSTR bstrHash;
       HRESULT  hr = S_OK;
    
       if (crypt && (key = crypt->getKeyMaterial(&keyLen)))
       {
          CBinHex  BinHex;
          CComBSTR binHexedKey;
          //encode the key
          hr = BinHex.ToBase64((BYTE*)key, keyLen, 0, NULL, &binHexedKey);
          if(hr == S_OK)
          {
             wKeyLen = ::SysStringLen(binHexedKey);
         
             // construct the whole BSTR for hashing
             // re-use the pcurrent buffer
             if(pszCurrent + wKeyLen<= pszBufEnd) // key version has wchar
             {
               memcpy(pszCurrent, binHexedKey, wKeyLen * sizeof(WCHAR));
               *(pszCurrent + wKeyLen) = 0;  // put an end
               
               LPWSTR   signStart = wcschr(pszBufStart, L'?');

               // if found before pszCurrent
               if(signStart && signStart < pszCurrent)
               {
                  ++signStart;;

                  // W2A conversion here -- we sign ascii version
                  CStringA tobeSigned(signStart);

                  CComBSTR bstrOrg;
                  bstrOrg.Attach(::SysAllocStringByteLen((LPCSTR)tobeSigned, tobeSigned.GetLength()));
                  
                  if(bstrOrg != NULL)
                  {
                     CComPtr<IMD5>  md5;

                     HRESULT hr = GetGlobalCOMmd5(&md5);
                     if(hr == S_OK)
                     {
                        hr = md5->MD5Hash(bstrOrg, &bstrHash);

                        if( hr == S_OK && bstrHash != NULL)
                        {
                            pszCurrent = CopyHelperW(pszCurrent, L"&tpf=", pszBufEnd);
                            pszCurrent = CopyHelperW(pszCurrent, bstrHash, pszBufEnd);
                        }
                        else
                            bstrHash.Empty(); // so we can use value to determin if things are hashed
                     }
                  }
               }
            }
         }
       }
       else
       {
           if (g_pAlert )
           {
               g_pAlert->report(PassportAlertInterface::ERROR_TYPE, PM_CURRENTKEY_NOTDEFINED,
                             0, NULL);
           }
       }

       if (!bstrHash && g_pAlert)
           g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_URLSIGNATURE_NOTCREATED,
                        0, NULL);
    }  
    else if(g_pAlert)
        g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_URLSIGNATURE_NOTCREATED,
                        0, NULL);
      
#endif
    *pszCurrent = L'\0';
    if (fRedirectToSelf)
    {
        //  escape and put back in the original buffer.
        //  adjust the length first
        cBufLen -= (ULONG) (pszLoginStart - pszBufStart);
        if (!PPEscapeUrl((BSTR)bstrHelper,
                         pszLoginStart,
                         &cBufLen,
                         cBufLen,
                         0))
#if 0
        if(!InternetCanonicalizeUrl((BSTR)bstrHelper,
                                    pszBufStart,
                                    &cBufLen,
                                    ICU_ENCODE_PERCENT))
#endif
        {
            _ASSERT(FALSE);
            //  cut the return
            pszCurrent = pszLoginStart;
        }
        else
        {
            pszCurrent = pszLoginStart + cBufLen;
//            pszCurrent = pszBufStart + cBufLen;
        }

    }
    return  pszCurrent;
}

BOOL
GetQueryParam(LPSTR queryString, LPSTR param, BSTR* p)
{
    LPSTR aLoc, aEnd;
    int aLen, i;

    //  Find the first occurrence of the param in the queryString.
    aLoc = strstr(queryString, param);
    while(aLoc != NULL)
    {
        //  If the string was found at the beginning of the string, or was
        //  preceded by a '&' then we've found the correct param.  Otherwise
        //  we tail-matched some other query string param and should look again.

        if(aLoc == queryString ||
            *(aLoc - 1) == '&')
        {
            aLoc += strlen(param);
            aEnd = strchr(aLoc, '&');

            if(aEnd)
                aLen = aEnd - aLoc;
            else
                aLen = strlen(aLoc);

            BSTR aVal = ALLOC_BSTR_LEN(NULL, aLen);
            for (i = 0; i < aLen; i++)
                aVal[i] = aLoc[i];
            *p = aVal;
            GIVEAWAY_BSTR(aVal);
            return TRUE;
        }

        aLoc = strstr(aLoc + 1, param);
    }

    return FALSE;
}

BOOL
GetQueryData(
    LPSTR   queryString,
    BSTR*   a,
    BSTR*   p,
    BSTR*   f)
{
    //  This one is optional, don't error out if it isn't there.
    GetQueryParam(queryString, "f=", f);

    if(!GetQueryParam(queryString, "t=", a))
        return FALSE;

    // OK if we have ticket w/o profile.
    GetQueryParam(queryString, "p=", p);

    return TRUE;
}

#define ToHexDigit(x) (('0' <= x && x <= '9') ? (x - '0') : (tolower(x) - 'a' + 10))

BOOL
GetCookie(
    LPSTR   pszCookieHeader,
    LPSTR   pszCookieName,
    BSTR*   pbstrCookieVal
    )
{
    LPSTR nLoc;
    LPSTR nEnd;
    int   nLen, src, dst;

    if(pbstrCookieVal == NULL)
        return FALSE;

    *pbstrCookieVal = NULL;

    if ((nLoc = strstr(pszCookieHeader, pszCookieName)) == NULL)
    {
        *pbstrCookieVal = NULL;
        return FALSE;
    }

    // ticket

    nLoc = strchr(nLoc, '=');
    if(!nLoc)
        return FALSE;

    nLoc++;

    nEnd = strchr(nLoc,';');

    if (nEnd)
        nLen = nEnd - nLoc;
    else
        nLen = strlen(nLoc);

    BSTR nVal = ALLOC_BSTR_LEN(NULL, nLen);
    if(!nVal)
        return FALSE;

    for (src = 0, dst = 0; src < nLen;)
    {
        //handle any url encoded gunk
        if(nLoc[src] == '%')
        {
            nVal[dst++] = (ToHexDigit(nLoc[src+1]) << 4) + ToHexDigit(nLoc[src+2]);
            src+=3;
        }
        else
        {
            nVal[dst++] = nLoc[src++];
        }
    }
    nVal[dst] = 0;

    GIVEAWAY_BSTR(nVal);
    *pbstrCookieVal = nVal;

    return TRUE;
}


BOOL
BuildCookieHeaders(
    LPCSTR  pszTicket,
    LPCSTR  pszProfile,
    LPCSTR  pszConsent,
    LPCSTR  pszSecure,
    LPCSTR  pszTicketDomain,
    LPCSTR  pszTicketPath,
    LPCSTR  pszConsentDomain,
    LPCSTR  pszConsentPath,
    LPCSTR  pszSecureDomain,
    LPCSTR  pszSecurePath,
    BOOL    bSave,
    LPSTR   pszBuf,
    LPDWORD pdwBufLen
    )
/*
Here is the old code for reference:

    if (domain)
    {
        *bufSize = _snprintf(pCookieHeader, *bufSize,
                            "Set-Cookie: MSPAuth=%s; path=/; domain=%s; %s\r\n"
                            "Set-Cookie: MSPProf=%s; path=/; domain=%s; %s\r\n",
                            W2A(a), domain,
                            persist ? "expires=Mon 1-Jan-2035 12:00:00 GMT;" : "",
                            W2A(p), domain,
                            persist ? "expires=Mon 1-Jan-2035 12:00:00 GMT;" : "");
    }
    else
    {
        *bufSize = _snprintf(pCookieHeader, *bufSize,
                            "Set-Cookie: MSPAuth=%s; path=/; %s\r\n"
                            "Set-Cookie: MSPProf=%s; path=/; %s\r\n",
                            W2A(a),
                            persist ? "expires=Mon 1-Jan-2035 12:00:00 GMT;" : "",
                            W2A(p),
                            persist ? "expires=Mon 1-Jan-2035 12:00:00 GMT;" : "");
    }

*/
{
    LPSTR   pszCurrent = pszBuf;
    LPCSTR  pszBufEnd = pszBuf + *pdwBufLen - 1;

    pszCurrent = CopyHelperA(pszCurrent, "Set-Cookie: MSPAuth=", pszBufEnd);
    pszCurrent = CopyHelperA(pszCurrent, pszTicket, pszBufEnd);
    if(pszTicketPath)
    {
        pszCurrent = CopyHelperA(pszCurrent, "; path=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszTicketPath, pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
    }
    else
        pszCurrent = CopyHelperA(pszCurrent, "; path=/; ", pszBufEnd);

    if(pszTicketDomain)
    {
        pszCurrent = CopyHelperA(pszCurrent, "domain=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszTicketDomain, pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
    }

    if(bSave)
    {
        pszCurrent = CopyHelperA(pszCurrent, "expires=Mon 1-Jan-2035 12:00:00 GMT;", pszBufEnd);
    }

    pszCurrent = CopyHelperA(pszCurrent, "\r\n", pszBufEnd);

    if(pszProfile)
    {
        pszCurrent = CopyHelperA(pszCurrent, "Set-Cookie: MSPProf=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszProfile, pszBufEnd);

        if(pszTicketPath)
        {
            pszCurrent = CopyHelperA(pszCurrent, "; path=", pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, pszTicketPath, pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
        }
        else
            pszCurrent = CopyHelperA(pszCurrent, "; path=/; ", pszBufEnd);

        if(pszTicketDomain)
        {
            pszCurrent = CopyHelperA(pszCurrent, "domain=", pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, pszTicketDomain, pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
        }

        if(bSave)
        {
            pszCurrent = CopyHelperA(pszCurrent, "expires=Mon 1-Jan-2035 12:00:00 GMT;", pszBufEnd);
        }

        pszCurrent = CopyHelperA(pszCurrent, "\r\n", pszBufEnd);

    }

    if(pszSecure)
    {
        pszCurrent = CopyHelperA(pszCurrent, "Set-Cookie: MSPSecAuth=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszSecure, pszBufEnd);
        if(pszSecurePath)
        {
            pszCurrent = CopyHelperA(pszCurrent, "; path=", pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, pszSecurePath, pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
        }
        else
            pszCurrent = CopyHelperA(pszCurrent, "; path=/; ", pszBufEnd);

        if(pszSecureDomain)
        {
            pszCurrent = CopyHelperA(pszCurrent, "domain=", pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, pszSecureDomain, pszBufEnd);
            pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
        }

        pszCurrent = CopyHelperA(pszCurrent, "secure\r\n", pszBufEnd);
    }

    //  Set MSPConsent cookie
    pszCurrent = CopyHelperA(pszCurrent, "Set-Cookie: MSPConsent=", pszBufEnd);
    if(pszConsent)
    {
        pszCurrent = CopyHelperA(pszCurrent, pszConsent, pszBufEnd);
    }

    if(pszConsentPath)
    {
        pszCurrent = CopyHelperA(pszCurrent, "; path=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszConsentPath, pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
    }
    else
        pszCurrent = CopyHelperA(pszCurrent, "; path=/; ", pszBufEnd);

    if(pszConsentDomain)
    {
        pszCurrent = CopyHelperA(pszCurrent, "domain=", pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, pszConsentDomain, pszBufEnd);
        pszCurrent = CopyHelperA(pszCurrent, "; ", pszBufEnd);
    }

    if(pszConsent)
        {
        if(bSave)
        {
            pszCurrent = CopyHelperA(pszCurrent, "expires=Mon 1-Jan-2035 12:00:00 GMT;", pszBufEnd);
        }
    }
    else
    {
        pszCurrent = CopyHelperA(pszCurrent, "expires=Tue 1-Jan-1980 12:00:00 GMT;", pszBufEnd);
    }

    pszCurrent = CopyHelperA(pszCurrent, "\r\n", pszBufEnd);


    //  finally put the Auth-Info header
    pszCurrent = CopyHelperA(pszCurrent,
            "Authentication-Info: tname=MSPAuth,tname=MSPProf,tname=MSPConsent\r\n",
            pszBufEnd);
    *(pszCurrent++) = '\0';

    *pdwBufLen = pszCurrent - pszBuf;

    return TRUE;
}


HRESULT
DecryptTicketAndProfile(
    BSTR                bstrTicket,
    BSTR                bstrProfile,
    BOOL                bCheckConsent,
    BSTR                bstrConsent,
    CRegistryConfig*    pRegistryConfig,
    IPassportTicket*    piTicket,
    IPassportProfile*   piProfile)
{
    HRESULT             hr;
    BSTR                ret = NULL;
    CCoCrypt*           crypt = NULL;
    time_t              tValidUntil;
    time_t              tNow = time(NULL);
    int                 kv;
    int                 nMemberIdHighT, nMemberIdLowT;
    VARIANT             vMemberIdHighP, vMemberIdLowP;
    CComPtr<IPassportTicket2>   spTicket2;

    if (!g_config->isValid()) // Guarantees config is non-null
    {
        AtlReportError(CLSID_FastAuth, PP_E_NOT_CONFIGUREDSTR,
                       IID_IPassportFastAuth, PP_E_NOT_CONFIGURED);
        hr = PP_E_NOT_CONFIGURED;
        goto Cleanup;
    }

    // Make sure we have both ticket and profile first.
    if (bstrTicket == NULL || SysStringLen(bstrTicket) == 0)
    {
      /* this is no longer true for passport 2.0 -- weijiang -- 02/24/01
       it's ok to have only ticket
        //  Did we get profile w/o ticket?  This is a BAD error!
        //  It's ok to get a ticket w/o a profile however
        if(bstrProfile != NULL && SysStringLen(bstrProfile) != 0)
            if (g_pAlert)
                g_pAlert->report(PassportAlertInterface::ERROR_TYPE, PM_PROFILE_WO_TICKET,
                                 0, NULL, SysStringByteLen(bstrProfile), (LPVOID)bstrProfile);
      */
        hr = E_INVALIDARG;
        goto Cleanup;
    }

    // Get key version and crypt object.
    kv = CCoCrypt::getKeyVersion(bstrTicket);
    crypt = pRegistryConfig->getCrypt(kv, &tValidUntil);

    if (crypt == NULL)
    {
        if (g_pAlert )
            g_pAlert->report(PassportAlertInterface::ERROR_TYPE, PM_INVALID_KEY,
                             0, NULL, SysStringByteLen(bstrTicket), (LPVOID)bstrTicket);
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    // Is the key still valid?
    if(tValidUntil && tValidUntil < tNow)
    {
        DWORD dwTimes[2] = { tValidUntil, tNow };
        TCHAR *pszStrings[1];
        TCHAR value[34];   // the _itot only takes upto 33 chars
        pszStrings[0] = _itot(pRegistryConfig->getSiteId(), value, 10);

        if(g_pAlert)
            g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_KEY_EXPIRED,
                             1, (LPCTSTR*)pszStrings, sizeof(DWORD) << 1, (LPVOID)dwTimes);
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    // Decrypt the ticket and set it into the ticket object.
    if(crypt->Decrypt(bstrTicket, SysStringByteLen(bstrTicket), &ret)==FALSE)
    {
        if(g_pAlert)
            g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_INVALID_TICKET_C,
                             0, NULL, SysStringByteLen(bstrTicket), (LPVOID)bstrTicket);
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    TAKEOVER_BSTR(ret);
    piTicket->put_unencryptedTicket(ret);
    piTicket->QueryInterface(_uuidof(IPassportTicket2), (void**)&spTicket2);
    _ASSERT(spTicket2);
    FREE_BSTR(ret);
    ret = NULL;

    // Decrypt the profile and set it into the profile object.
    if(bstrProfile && SysStringLen(bstrProfile) != 0)
    {
       if(crypt->Decrypt(bstrProfile, SysStringByteLen(bstrProfile), &ret) == FALSE)
       {
           if(g_pAlert)
               g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_INVALID_PROFILE_C,
                             0, NULL, SysStringByteLen(bstrProfile), (LPVOID)bstrProfile);
          piProfile->put_unencryptedProfile(NULL);
       }
       else
       {

          TAKEOVER_BSTR(ret);
          piProfile->put_unencryptedProfile(ret);

          //
          //  Member id in profile MUST match member id in ticket.
          //

          piTicket->get_MemberIdHigh(&nMemberIdHighT);
          piTicket->get_MemberIdLow(&nMemberIdLowT);

          VariantInit(&vMemberIdHighP);
          VariantInit(&vMemberIdLowP);

          // these could be missing for mobile case
          HRESULT hr1 = piProfile->get_Attribute(L"memberidhigh", &vMemberIdHighP);
          HRESULT hr2 = piProfile->get_Attribute(L"memberidlow", &vMemberIdLowP);

          // these could be missing for mobile case
          if(hr1 == S_OK && hr2 == S_OK && 
             (nMemberIdHighT != vMemberIdHighP.lVal ||
              nMemberIdLowT  != vMemberIdLowP.lVal))
          {
              piProfile->put_unencryptedProfile(NULL);
          }
       }
    }
    else
       piProfile->put_unencryptedProfile(NULL);

    //
    // consent stuff
    if(bstrConsent)
    {
       FREE_BSTR(ret);
       ret = NULL;

       if(crypt->Decrypt(bstrConsent, SysStringByteLen(bstrConsent), &ret) == FALSE)
       {
           if(g_pAlert)
               g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_INVALID_CONSENT,
                             0, NULL, SysStringByteLen(bstrProfile), (LPVOID)bstrProfile);
            // we can continue
       }
       else
       {
          TAKEOVER_BSTR(ret);
          spTicket2->SetTertiaryConsent(ret);  // we ignore return value here
       }
    }
       
    //  If the caller wants us to check consent, then do it.  If we don't have
    //  consent, then set the profile back to NULL.
    if(bCheckConsent)
    {
       NeedConsentEnum  needConsent = NeedConsent_Undefined;      
       spTicket2->needConsent(NULL, &needConsent);
       switch(needConsent)
       {
       case NeedConsent_Yes:
          piProfile->put_unencryptedProfile(NULL);
          break;
         
       case NeedConsent_Undefined:  // mean 1.X ticket
          {
          CComVariant vFlags;
          // mobile case, flags may not exist
          if(S_OK == piProfile->get_Attribute(L"flags", &vFlags) && 
            (V_I4(&vFlags)& k_ulFlagsConsentCookieNeeded)) 
          {
             piProfile->put_unencryptedProfile(NULL);
          }
          }
          break;
          
       case NeedConsent_No:
          break;
          
       default:
         _ASSERT(0); // should not be here
         break;

       }
    }

    hr = S_OK;

Cleanup:

    if (ret) FREE_BSTR(ret);

    if(g_pPerf)
    {
        switch(hr)
        {
        case PP_E_INVALID_TICKET:
        case E_INVALIDARG:
            g_pPerf->incrementCounter(PM_INVALIDREQUESTS_TOTAL);
            g_pPerf->incrementCounter(PM_INVALIDREQUESTS_SEC);
            break;

        default:
            g_pPerf->incrementCounter(PM_VALIDREQUESTS_TOTAL);
            g_pPerf->incrementCounter(PM_VALIDREQUESTS_SEC);
            break;
        }

        g_pPerf->incrementCounter(PM_REQUESTS_TOTAL);
        g_pPerf->incrementCounter(PM_REQUESTS_SEC);
    }
    else
    {
        _ASSERT(g_pPerf);
    }

    return hr;
}


HRESULT
DoSecureCheck(
    BSTR                bstrSecure,
    CRegistryConfig*    pRegistryConfig,
    IPassportTicket*    piTicket
    )
{
    HRESULT hr;
    BSTR                ret = NULL;
    CCoCrypt*           crypt = NULL;
    time_t              tValidUntil;
    time_t              tNow = time(NULL);
    int                 kv;

    if (!g_config->isValid()) // Guarantees config is non-null
    {
        AtlReportError(CLSID_FastAuth, PP_E_NOT_CONFIGUREDSTR,
                       IID_IPassportFastAuth, PP_E_NOT_CONFIGURED);
        hr = PP_E_NOT_CONFIGURED;
        goto Cleanup;
    }

    // Make sure we have both ticket and profile first.
    if (bstrSecure == NULL || SysStringLen(bstrSecure) == 0)
    {
        hr = E_INVALIDARG;
        goto Cleanup;
    }

    // Get key version and crypt object.
    kv = CCoCrypt::getKeyVersion(bstrSecure);
    crypt = pRegistryConfig->getCrypt(kv, &tValidUntil);

    if (crypt == NULL)
    {
        if (g_pAlert)
            g_pAlert->report(PassportAlertInterface::ERROR_TYPE, PM_INVALID_KEY,
                             0, NULL, sizeof(DWORD), (LPVOID)&kv);
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    // Is the key still valid?
    if(tValidUntil && tValidUntil < tNow)
    {
        DWORD dwTimes[2] = { tValidUntil, tNow };
        TCHAR *pszStrings[1];
        TCHAR value[34];   // the _itot only takes upto 33 chars
        pszStrings[0] = _itot(pRegistryConfig->getSiteId(), value, 10);

        if(g_pAlert)
            g_pAlert->report(PassportAlertInterface::WARNING_TYPE, PM_KEY_EXPIRED,
                             1, (LPCTSTR*)pszStrings, sizeof(DWORD) << 1, (LPVOID)dwTimes);
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    // Decrypt the ticket and set it into the ticket object.
    if(crypt->Decrypt(bstrSecure, SysStringByteLen(bstrSecure), &ret)==FALSE)
    {
        hr = PP_E_INVALID_TICKET;
        goto Cleanup;
    }

    TAKEOVER_BSTR(ret);
    piTicket->DoSecureCheck(ret);
    FREE_BSTR(ret);
    ret = NULL;

    hr = S_OK;

Cleanup:

    return hr;
}


LPSTR
GetServerVariableECB(
    EXTENSION_CONTROL_BLOCK*    pECB,
    LPSTR                       pszHeader
    )
{
    DWORD   dwSize = 0;
    LPSTR   lpBuf;

    pECB->GetServerVariable(pECB->ConnID, pszHeader, NULL, &dwSize);
    if(GetLastError() != ERROR_INSUFFICIENT_BUFFER || dwSize == 0)
    {
        lpBuf = NULL;
        goto Cleanup;
    }

    lpBuf = new CHAR[dwSize];
    if(!lpBuf)
        goto Cleanup;

    if(!pECB->GetServerVariable(pECB->ConnID, pszHeader, lpBuf, &dwSize))
    {
        delete [] lpBuf;
        lpBuf = NULL;
    }

Cleanup:

    return lpBuf;
}

LPSTR
GetServerVariablePFC(
    PHTTP_FILTER_CONTEXT    pPFC,
    LPSTR                   pszHeader
    )
{
    DWORD   dwSize;
    LPSTR   lpBuf;
    CHAR    cDummy;

    dwSize = 1;
    pPFC->GetServerVariable(pPFC, pszHeader, &cDummy, &dwSize);

    lpBuf = new CHAR[dwSize];
    if(!lpBuf)
        goto Cleanup;

    if(!pPFC->GetServerVariable(pPFC, pszHeader, lpBuf, &dwSize))
    {
        delete [] lpBuf;
        lpBuf = NULL;
    }

Cleanup:

    return lpBuf;
}

LONG
HexToNum(
    WCHAR c
    )
{
    return ((c >= L'0' && c <= L'9') ? (c - L'0') : ((c >= 'A' && c <= 'F') ? (c - L'A' + 10) : -1));
}

LONG
FromHex(
    LPCWSTR     pszHexString
    )
{
    LONG    lResult = 0;
    LONG    lCurrent;
    LPWSTR  pszCurrent;

    for(pszCurrent = const_cast<LPWSTR>(pszHexString); *pszCurrent; pszCurrent++)
    {
        if((lCurrent = HexToNum(towupper(*pszCurrent))) == -1)
            break;  // illegal character, we're done

        lResult = (lResult << 4) + lCurrent;
    }

    return lResult;
}


//
//  helpers for (un)escaping URLs
//  The code is stolen without modifications from atlutil.h in VC7
//  for future versions, these helpers should be removed and the ATL funcs
//  called directly
//
BOOL PPUnescapeUrl(PCWSTR  lpszStringIn,
                   PWSTR   lpszStringOut,
                   LPDWORD pdwStrLen,
                   DWORD   dwMaxLength)
{
    int nValue = 0;
    TCHAR ch;
    DWORD dwLen = 0;
    BOOL bRet = TRUE;
    while ((ch = *lpszStringIn) != 0)
    {
        if (dwLen == dwMaxLength)
            bRet = FALSE;

        if (bRet)
        {
            if (ch == '%')
            {
                ch = *(++lpszStringIn);
                //currently assuming 2 hex values after '%'
                //as per the RFC 2396 document
                nValue = 16*HexToNum(ch);
                nValue+= HexToNum(*(++lpszStringIn));
                *lpszStringOut++ = (TCHAR) nValue;
            }
            else //non-escape character
            {
                if (bRet)
                    *lpszStringOut++ = ch;
            }
        }
        dwLen++;
        lpszStringIn++;
    }

    if (bRet)
        *lpszStringOut = '\0';

    if (pdwStrLen)
        *pdwStrLen = dwLen;

    return TRUE;
}

inline BOOL PPIsUnsafeUrlChar(TCHAR chIn) throw();

BOOL PPEscapeUrl(LPCTSTR lpszStringIn,
                 LPTSTR lpszStringOut,
                 DWORD* pdwStrLen,
                 DWORD dwMaxLength,
                 DWORD dwFlags)
{
    TCHAR ch;
    DWORD dwLen = 0;
    BOOL bRet = TRUE;
    BOOL bSchemeFile = FALSE;
    DWORD dwColonPos = 0;
    DWORD dwFlagsInternal = dwFlags;
    while((ch = *lpszStringIn++) != '\0')
    {
        //if we are at the maximum length, set bRet to FALSE
        //this ensures no more data is written to lpszStringOut, but
        //the length of the string is still updated, so the user
        //knows how much space to allocate
        if (dwLen == dwMaxLength)
        {
            bRet = FALSE;
        }

#if 0
        //  we want % to be encoded!!!
        if (ch == '%')
        {
            //decode the escaped sequence
            ch = (TCHAR)(16*HexToNum(*lpszStringIn++));
            ch = (TCHAR)(ch+HexToNum(*lpszStringIn++));
        }
#endif
        //if we are encoding and it is an unsafe character
        if (PPIsUnsafeUrlChar(ch))
        {
            {
                //if there is not enough space for the escape sequence
                if (dwLen >= (dwMaxLength-3))
                {
                        bRet = FALSE;
                }
                if (bRet)
                {
                        //output the percent, followed by the hex value of the character
                        *lpszStringOut++ = '%';
                        _stprintf(lpszStringOut, _T("%.2X"), (unsigned char)(ch));
                        lpszStringOut+= 2;
                }
                dwLen += 2;
            }
        }
        else //safe character
        {
            if (bRet)
                *lpszStringOut++ = ch;
        }
        dwLen++;
    }

    if (bRet)
        *lpszStringOut = L'\0';
    *pdwStrLen = dwLen;
    return  bRet;
}

//Determine if the character is unsafe under the URI RFC document
inline BOOL PPIsUnsafeUrlChar(TCHAR chIn) throw()
{
        unsigned char ch = (unsigned char)chIn;
        switch(ch)
        {
                case ';': case '\\': case '?': case '@': case '&':
                case '=': case '+': case '$': case ',': case ' ':
                case '<': case '>': case '#': case '%': case '\"':
                case '{': case '}': case '|':
                case '^': case '[': case ']': case '`':
                        return TRUE;
                default:
                {
                        if (ch < 32 || ch > 126)
                                return TRUE;
                        return FALSE;
                }
        }
}