//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1994
//
//  File:       persist.cxx
//
//  Contents:   Implmentation of Office9 Thicket Save API
//
//----------------------------------------------------------------------------
#include "priv.h"

//#include "headers.hxx"
//#include "formkrnl.hxx"
#include <platform.h>
#include <mlang.h>
#include "resource.h"
#include "impexp.h"
#include "reload.h"
//#include <siterc.h>
#include "packager.h"
#include "iehelpid.h"
#include "thicket.h"
#include "apithk.h"

#include <mluisupp.h>
#include <mshtmcid.h>

#define NUM_OLE_CMDS                          1

#define SAVEAS_OK                             0x00000001
#define SAVEAS_NEVER_ASK_AGAIN                0x00000002

#define CODEPAGE_UNICODE                      0x000004B0
#define CODEPAGE_UTF8                         0x0000FDE9
#define UNICODE_TEXT                          TEXT("Unicode")

#define REGSTR_VAL_SAVEDIRECTORY              TEXT("Save Directory")
#define REGKEY_SAVEAS_WARNING_RESTRICTION     TEXT("SOFTWARE\\Microsoft\\Internet Explorer\\Main")
#define REGVALUE_SAVEAS_WARNING               TEXT("NoSaveAsPOSTWarning")

#define WM_WORKER_THREAD_COMPLETED            WM_USER + 1000

#define MAX_ENCODING_DESC_LEN                 1024


const static DWORD aSaveAsHelpIDs[] =
{
    IDC_SAVE_CHARSET,   IDH_CHAR_SET_SAVE_AS,
    0,                  0
};

INT_PTR CALLBACK SaveAsWarningDlgProc(HWND hDlg, UINT msg, WPARAM wParam,
                                      LPARAM lParam);

HRESULT SaveToThicket( HWND hwnd, LPCTSTR pszFileName, IHTMLDocument2 *pDoc,
                       UINT codepageSrc, UINT codepageDst,
                       UINT iPackageStyle );

HRESULT
FormsGetFileName(
        HWND hwndOwner,
        LPTSTR pstrFile,
        int cchFile,
        LPARAM lCustData,
        DWORD *pnFilterIndex,
        BOOL bForceHTMLOnly);
HRESULT
GetFileNameFromURL( LPWSTR pwszURL, LPTSTR pszFile, DWORD cchFile);

void ReportThicketError( HWND hwnd, HRESULT hr );

#define DOWNLOAD_PROGRESS  0x9001
#define DOWNLOAD_COMPLETE  0x9002
#define THICKET_TIMER      0x9003
#define THICKET_INTERVAL   1000

#define MDLGMSG(psz, x)         TraceMsg(0, "shd TR-MODELESS::%s %x", psz, x)

static DWORD s_dwInetComVerMS = 0;
static DWORD s_dwInetComVerLS = 0;

struct ThicketCPInfo
{
    UINT    cpSrc;
    UINT    cpDst;
    LPWSTR lpwstrDocCharSet;
};

class CThicketUI
{
public:
    CThicketUI(void) :
        _hDlg(NULL),
        _hWndProg(NULL),
        _iErrorDL(0),
        _hrDL(E_FAIL),
#ifndef NO_MARSHALLING
        _pstmDoc(NULL),
#else
        _pDoc(NULL),
#endif
        _pszFileName(NULL),
        _dwDLMax(0),
        _codepageSrc(0),
        _codepageDst(0),
        _iPackageStyle(PACKAGE_THICKET),
        _fCancel(FALSE) {};

    ~CThicketUI(void) 
    { 
#ifndef NO_MARSHALLING
        SAFERELEASE(_pstmDoc);
#endif
        SAFELOCALFREE(_pszFileName); 
    };

    // CThicketUI methods
    HRESULT SaveDocument( HWND hWnd, LPCTSTR pszFileName, IHTMLDocument2 *pDoc,
                          UINT codepageSrc, UINT codepageDst,
                          UINT iPackageStyle );

protected:
    static BOOL_PTR ThicketUIDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
    static DWORD WINAPI ThicketUIThreadProc( LPVOID );

    BOOL DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

    HWND                _hDlg;
    HWND                _hWndProg;
    int                 _iErrorDL;
    HRESULT             _hrDL;
#ifndef UNIX
    IStream             *_pstmDoc;      // marshalled IHTMLDocument2
#else
    IHTMLDocument2      *_pDoc;
#endif
    LPTSTR              _pszFileName;
    DWORD               _dwDLMax;
    UINT                _codepageSrc;
    UINT                _codepageDst;
    BOOL                _fThreadStarted;
    UINT                _iPackageStyle;
    BOOL                _fCancel;
};

HRESULT
CThicketUI::SaveDocument( HWND hWnd, LPCTSTR pszFileName, IHTMLDocument2 *pDoc,
                          UINT codepageSrc, UINT codepageDst,
                          UINT iPackageStyle)
{
    _pszFileName = StrDup(pszFileName);
    _codepageSrc = codepageSrc;
    _codepageDst = codepageDst;
    _iPackageStyle = iPackageStyle;

#ifndef NO_MARSHALLING
    // We don't do anything with pDoc until we're on the worker thread,
    // so marshall it.
    _hrDL = CoMarshalInterThreadInterfaceInStream(IID_IHTMLDocument2, pDoc, &_pstmDoc);

    if (SUCCEEDED(_hrDL))
#else
    _pDoc = pDoc;
#endif
    {
        // Needs to be modal cuz we're going to work with pDoc on the worker thread
        // so we don't want the user to navigate away from it, close the window, etc.
        DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_SAVETHICKET),
                         hWnd, CThicketUI::ThicketUIDlgProc, (LPARAM)this);

     //   HWND hwnd = MLCreateDialogParamWrap(MLGetHinst(), MAKEINTRESOURCE(IDD_SAVETHICKET),
     //                                 NULL, CThicketUI::ThicketUIDlgProc, (LPARAM)this);
     //   if (!hwnd)
     //       _hrDL = E_FAIL;
    }

    return _hrDL;
}

BOOL_PTR
CThicketUI::ThicketUIDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    BOOL        fRet = FALSE;
    CThicketUI* ptui = NULL;

    if (msg == WM_INITDIALOG)
    {
        ptui = (CThicketUI*)lParam;
    }
    else
        ptui = (CThicketUI*)GetWindowLongPtr(hDlg, DWLP_USER);

    if (ptui)
    {
        fRet = ptui->DlgProc(hDlg, msg, wParam, lParam);

        if (msg == WM_DESTROY || msg == WM_WORKER_THREAD_COMPLETED)
        {
            SetWindowLongPtr(hDlg, DWLP_USER, lParam);
            fRet = TRUE;
        }
    }

    return fRet;
}

BOOL
CThicketUI::DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    BOOL fRet = TRUE;

    switch (msg)
    {
    case WM_INITDIALOG:
        _hDlg = hDlg;
        _hWndProg = GetDlgItem(hDlg, IDC_THICKETPROGRESS);
        SetWindowLongPtr(hDlg, DWLP_USER, lParam);

        _hrDL = S_FALSE;

#ifndef NO_MARSHALLING
        //_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThicketUIThreadProc, this, 0, &_idThread);
        if (!(_fThreadStarted = SHQueueUserWorkItem(ThicketUIThreadProc,
                                                    this,
                                                    0,
                                                    (DWORD_PTR)NULL,
                                                    (DWORD_PTR *)NULL,
                                                    "shdocvw.dll",
                                                    TPS_LONGEXECTIME)))
            _hrDL = E_FAIL;
#else
        ThicketUIThreadProc((LPVOID)this);
#endif

        if (FAILED(_hrDL))
             EndDialog(hDlg, 0);
        else
        {
            ShowWindow(hDlg, SW_SHOWNORMAL);
            Animate_OpenEx(GetDlgItem(hDlg, IDD_ANIMATE), HINST_THISDLL, IDA_DOWNLOAD);
            ShowWindow(GetDlgItem(hDlg, IDD_DOWNLOADICON), SW_HIDE);
        }
        fRet = FALSE;
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDCANCEL:
            _fCancel = TRUE;
            // and wait for the worker thread to quit, polling at WM_TIMER
            break;

        default:
            break;
        }
        break;

    case WM_WORKER_THREAD_COMPLETED:
        _hrDL = (DWORD) wParam;
        EndDialog(hDlg,0);
        break;

    //case WM_CLOSE:
    //    KillTimer( hDlg, THICKET_TIMER );
    //    _fCancel = TRUE;
    //    while( _hrDL == S_FALSE );
    //    break;

    case WM_DESTROY:
        _fCancel = TRUE;
        while( _hrDL == S_FALSE )
        {
            Sleep(0);
        }
        break;

    default:
        fRet = FALSE;
    }

    return fRet;
}

DWORD WINAPI CThicketUI::ThicketUIThreadProc( LPVOID ppv )
{
    HRESULT hr = S_OK;
    CThicketUI* ptui = (CThicketUI *)ppv;

    ASSERT(ptui);

    hr = CoInitialize(NULL);
    if (SUCCEEDED(hr))
    {
        IHTMLDocument2 *pDoc = NULL;

#ifndef NO_MARSHALLING
        hr = CoGetInterfaceAndReleaseStream( ptui->_pstmDoc, IID_IHTMLDocument2,(LPVOID*)&pDoc);

        // CoGetInterfaceAndReleaseStream always releases the stream
        ptui->_pstmDoc = NULL;
#else
        pDoc = ptui->_pDoc;
        pDoc->AddRef();
#endif

        if (SUCCEEDED(hr))
        {
            CThicketProgress    tprog( ptui->_hDlg );
            CDocumentPackager   docPkgr(ptui->_iPackageStyle);

            hr = S_FALSE;

            hr = docPkgr.PackageDocument( pDoc, ptui->_pszFileName,
                          &ptui->_fCancel, &tprog,
                          0, 100,
                          ptui->_codepageDst );

            pDoc->Release(); // release marshalled interface
        }

        CoUninitialize();
    }

    PostMessage(ptui->_hDlg, WM_WORKER_THREAD_COMPLETED, hr, 0);

    return 0;
}


//+------------------------------------------------------------------------
//
//
//
//-------------------------------------------------------------------------
HRESULT
SaveToThicket( HWND hWnd, LPCTSTR pszFileName, IHTMLDocument2 *pDoc,
               UINT codepageSrc, UINT codepageDst, UINT iPackageStyle )
{
    HRESULT     hr;
    CThicketUI* ptui;

#ifdef OLD_THICKET
    LPTSTR      lpszURL;

    lpszURL = bstrDocURL;

    const   DWORD       dwMaxPathLen        = 24;

    URL_COMPONENTS urlComp;
    TCHAR   rgchUrlPath[MAX_PATH];
    TCHAR   rgchCanonicalUrl[MAX_URL_STRING];
    DWORD   dwLen;

    dwLen = ARRAYSIZE(rgchCanonicalUrl);

    hr = UrlCanonicalize( lpszURL, rgchCanonicalUrl, &dwLen, 0);
    if (FAILED(hr))
        return E_FAIL;

    ZeroMemory(&urlComp, sizeof(urlComp));

    urlComp.dwStructSize = sizeof(urlComp);
    urlComp.lpszUrlPath = rgchUrlPath;
    urlComp.dwUrlPathLength = ARRAYSIZE(rgchUrlPath);

    hr = InternetCrackUrl(rgchCanonicalUrl, lstrlen(rgchCanonicalUrl), ICU_DECODE, &urlComp);
    if (FAILED(hr))
        return E_FAIL;

    // Since this is not a snap-shot, saving the doc over itself is a no-op.
    // This means we can avoid some nasty issues with the save-over, safe-save,
    // et al, by short circuiting the save here.
    if ( StrCmpI(pszFileName, rgchUrlPath) == 0 )
    {
        if (PathFileExists(pszFileName))
            return S_OK;
        else
            return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    }

#endif //OLD_THICKET

    ptui = new CThicketUI;
    if (ptui)
    {
        hr = ptui->SaveDocument( hWnd, pszFileName, pDoc, codepageSrc, codepageDst, iPackageStyle );
        delete ptui;
    }
    else
        hr = E_OUTOFMEMORY;

    return hr;
}

//+------------------------------------------------------------------------
//
//
//
//-------------------------------------------------------------------------

void SaveBrowserFile( HWND hwnd, LPUNKNOWN punk )
{
    HRESULT         hr;
    TCHAR           szFileDst[MAX_PATH];
    DWORD           iFilter = 1;
    IHTMLDocument2  *pDoc;
    BSTR            bstrURL = NULL;
    ThicketCPInfo   tcpi;
    BSTR            bstrCharSet = NULL;
    BSTR            bstrTitle = NULL;
    BSTR            bstrMime = NULL;
    IOleCommandTarget *pOleCommandTarget = NULL;
    WCHAR          *pwzExt = NULL;
    OLECMD          pCmd[NUM_OLE_CMDS];
    ULONG           nCmds = NUM_OLE_CMDS;
    BOOL            bForceHTMLOnly = FALSE;

    static const WCHAR *wzImage = L" Image";
    

    hr = punk->QueryInterface(IID_IHTMLDocument2, (void**)&pDoc);
    if (FAILED(hr))
        goto Cleanup;

    if (FAILED(pDoc->get_URL( &bstrURL )))
        goto Cleanup;

    hr = pDoc->get_charset( &bstrCharSet );
    if (FAILED(hr))
        goto Cleanup;

    tcpi.cpSrc = CP_ACP;
    tcpi.lpwstrDocCharSet = bstrCharSet;

    // If it is an image file, then bring up trident to do the save.
    // APPCOMPAT: This is a crappy way to do this. We are hard-coding the
    // image types, so we know to put up the "Save as image" dialog.
    // We originally tried looking at the MIME type, but Trident returns
    // inconsistent MIME types to us (ex. under some platforms we get
    // "JPG Image" and under others we get "JPG File"!).

    ASSERT(bstrURL);

    pwzExt = bstrURL + lstrlenW(bstrURL);

    while (pwzExt > bstrURL && *pwzExt != L'.')
    {
        pwzExt--;
    }

    hr = pDoc->QueryInterface(IID_IOleCommandTarget,
                              (void **)&pOleCommandTarget);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    if (pwzExt > bstrURL) {

        // Found a "dot". Now pwzExt points to what we think is the extension

        if (!StrCmpIW(pwzExt, L".JPG") ||
            !StrCmpIW(pwzExt, L".GIF") ||
            !StrCmpIW(pwzExt, L".BMP") ||
            !StrCmpIW(pwzExt, L".XBM") ||
            !StrCmpIW(pwzExt, L".ART") ||
            !StrCmpIW(pwzExt, L".PNG") ||
            !StrCmpIW(pwzExt, L".WMF") ||
            !StrCmpIW(pwzExt, L".TIFF") ||
            !StrCmpIW(pwzExt, L".JPEG"))
        {
            hr = pOleCommandTarget->Exec(&CGID_MSHTML, IDM_SAVEPICTURE, 0,
                                         NULL, NULL);

            // FEATURE: Handle a failed HR here. It is very unlikely that
            // this will fail, yet regular save-as code (that follows)
            // will succeed. We always exit out of here, so we will
            // never get two UI dialogs thrown at the user. We should
            // come up with a good scheme to propagate an error dialog
            // to the user. Possible scenario: low disk space causing
            // a fail-out.

            goto Cleanup;
        }
    }

    // IE5 RAID #54672: Save-as has problems saving pages generated by POSTs
    // This code is to detect if the page was generated by POST data and
    // warn the user that saving may not work.

    pCmd[0].cmdID = SHDVID_PAGEFROMPOSTDATA;
    hr = pOleCommandTarget->QueryStatus(&CGID_ShellDocView, nCmds, pCmd, NULL);

    if (FAILED(hr))
    {
        goto Cleanup;
    }

    if (pCmd[0].cmdf & OLECMDF_LATCHED)
    {
        HKEY         hkeySaveAs = 0;
        DWORD        dwValue = 0;
        DWORD        dwSize = 0;
        INT_PTR      iFlags = 0;

        bForceHTMLOnly = TRUE;

        if (RegOpenKeyEx(HKEY_CURRENT_USER,
                         REGKEY_SAVEAS_WARNING_RESTRICTION, 0,
                         KEY_READ, &hkeySaveAs) == ERROR_SUCCESS)
        {
            dwSize = sizeof(DWORD);

            if (RegQueryValueEx(hkeySaveAs, REGVALUE_SAVEAS_WARNING, NULL,
                                NULL, (LPBYTE)&dwValue, &dwSize) == ERROR_SUCCESS)
            {

                if (dwValue)
                {
                    // restriction set, don't show dialog
                    RegCloseKey(hkeySaveAs);
                    goto Continue;
                }
            }

            RegCloseKey(hkeySaveAs);
        }

        iFlags = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(DLG_SAVEAS_WARNING),
                                hwnd, SaveAsWarningDlgProc, (LPARAM)0);

        if (!(iFlags & SAVEAS_OK))
        {
            goto Cleanup;
        }
        
        if (iFlags & SAVEAS_NEVER_ASK_AGAIN)
        {
            HKEY                 hkey = 0;
            DWORD                dwNeverAsk = 1;

            if (RegOpenKeyEx(HKEY_CURRENT_USER,
                             REGKEY_SAVEAS_WARNING_RESTRICTION, 0,
                             KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
            {

                RegSetValueEx(hkey, REGVALUE_SAVEAS_WARNING, 0, REG_DWORD,
                              (CONST BYTE *)&dwNeverAsk,
                              sizeof(dwNeverAsk));
                RegCloseKey(hkey);
            }
        }
    }
    
Continue:

    // Suggest a file name
    
    szFileDst[0] = 0;

    // Our favorite candidate is the title,  fall back on the file name.
    hr = pDoc->get_title(&bstrTitle);
    if (SUCCEEDED(hr) && lstrlenW(bstrTitle))
    {
        StrCpyN(szFileDst, bstrTitle, ARRAYSIZE(szFileDst));
    }
    else
        hr = GetFileNameFromURL(bstrURL, szFileDst, ARRAYSIZE(szFileDst));

    if (FAILED(hr))
        goto Cleanup;

    PathCleanupSpec(NULL, szFileDst);

    hr = FormsGetFileName(hwnd, szFileDst, ARRAYSIZE(szFileDst),
                          (LONG_PTR)&tcpi, &iFilter, bForceHTMLOnly);

    if (hr==S_OK)
        hr = SaveToThicket( hwnd, szFileDst, pDoc, tcpi.cpSrc, tcpi.cpDst, iFilter);

Cleanup:

    if (FAILED(hr))
        ReportThicketError(hwnd, hr);

    if (pOleCommandTarget)
        pOleCommandTarget->Release();

    if (pDoc)
        pDoc->Release();

    if (bstrURL)
        SysFreeString(bstrURL);

    if (bstrCharSet)
        SysFreeString(bstrCharSet);

    if (bstrTitle)
        SysFreeString(bstrTitle);

    return;
}

void ReportThicketError( HWND hwnd, HRESULT hr )
{
    LPTSTR lpstrMsg = NULL;

    switch (hr)
    {
    case HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY):
    case E_OUTOFMEMORY:
        lpstrMsg = MAKEINTRESOURCE(IDS_THICKETERRMEM);
        break;

    case E_ACCESSDENIED:
    case STG_E_ACCESSDENIED:
        lpstrMsg = MAKEINTRESOURCE(IDS_THICKETERRACC);
        break;

    case HRESULT_FROM_WIN32(ERROR_DISK_FULL):
    case STG_E_MEDIUMFULL:
        lpstrMsg = MAKEINTRESOURCE(IDS_THICKETERRFULL);
        break;

    case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
        lpstrMsg = MAKEINTRESOURCE(IDS_THICKETERRFNF);
        break;

    case E_ABORT:
        // Ray says we don't want a canceled message box.
        //lpstrMsg = MAKEINTRESOURCE(IDS_THICKETABORT);
        break;

    case E_FAIL:
    default:
        lpstrMsg = MAKEINTRESOURCE(IDS_THICKETERRMISC);
        break;
    }

    if ( lpstrMsg )
    {
        MLShellMessageBox(
                        hwnd,
                        lpstrMsg,
                        MAKEINTRESOURCE(IDS_THICKETERRTITLE),
                        MB_OK | MB_ICONERROR);
    }
}

//+--------------------------------------------------------------------------
//
//  File:       file.cxx
//
//  Contents:   Import/export dialog helpers
//
//  History:    16-May-95   RobBear     Taken from formtool
//
//---------------------------------------------------------------------------

const CHAR c_szNT4ResourceLocale[]      = ".DEFAULT\\Control Panel\\International";
const CHAR c_szWin9xResourceLocale[]    = ".Default\\Control Panel\\desktop\\ResourceLocale";
const CHAR c_szLocale[]                 = "Locale";

LANGID
MLGetShellLanguage()
{
    LANGID  lidShell = 0;

    // FEATURE: this fn is copied from shlwapi. there really should be a
    // shlwapi export. if MLGetUILanguage has any merit, then
    // MLGetShellLanguage has merit as well.

    if (IsOS(OS_WIN2000ORGREATER))
    {
        static LANGID (CALLBACK* pfnGetUserDefaultUILanguage)(void) = NULL;

        if (pfnGetUserDefaultUILanguage == NULL)
        {
            HMODULE hmod = GetModuleHandle(TEXT("KERNEL32"));

            if (hmod)
                pfnGetUserDefaultUILanguage = (LANGID (CALLBACK*)(void))GetProcAddress(hmod, "GetUserDefaultUILanguage");
        }
        if (pfnGetUserDefaultUILanguage)
            lidShell = pfnGetUserDefaultUILanguage();
    }
    else
    {
        CHAR szLangID[12];
        DWORD cb, dwRet;

        cb = sizeof(szLangID) - 2*sizeof(szLangID[0]);  // avoid 2 byte buffer overrun
        if (IsOS(OS_NT))
            dwRet = SHGetValueA(HKEY_USERS, c_szNT4ResourceLocale, c_szLocale, NULL, szLangID + 2, &cb);
        else
            dwRet = SHGetValueA(HKEY_USERS, c_szWin9xResourceLocale, NULL, NULL, szLangID + 2, &cb);

        if (ERROR_SUCCESS == dwRet)
        {
            // IE uses a string rep of the hex value
            szLangID[0] = '0';
            szLangID[1] = 'x';
            StrToIntExA(szLangID, STIF_SUPPORT_HEX, (LPINT)&lidShell);
        }
    }

    return lidShell;
}

/*
 *  Stolen from Trident's src\core\cdutil\file.cxx
 */

// Hook procedure for open file dialog.

UINT_PTR APIENTRY SaveOFNHookProc(HWND hdlg,
                                  UINT uiMsg,
                                  WPARAM wParam,
                                  LPARAM lParam)
{
    ULONG i, iCurSel;
    BOOL  bFoundEncoding = FALSE;
    WCHAR wzEncoding[MAX_ENCODING_DESC_LEN];

    switch (uiMsg)
    {
        // Populate the dropdown.
        case WM_INITDIALOG:
        {
            HRESULT hr;
            LPOPENFILENAME pofn = (LPOPENFILENAME)lParam;
            ThicketCPInfo *ptcpi = (ThicketCPInfo *)pofn->lCustData;
            IMultiLanguage2 *pMultiLanguage = NULL;
            IEnumCodePage  *pEnumCodePage = NULL;
            //UINT            codepageDefault = ptcpi->cp;
            MIMECSETINFO    csetInfo;
            LANGID          langid;

#ifdef UNIX
            SetWindowLongPtr(hdlg, DWLP_USER, (LONG_PTR)ptcpi);
#endif /* UNIX */

            hr = CoCreateInstance(
                    CLSID_CMultiLanguage,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_IMultiLanguage2,
                    (void**)&pMultiLanguage);
            if (hr)
                break;

            hr = pMultiLanguage->GetCharsetInfo(ptcpi->lpwstrDocCharSet,&csetInfo);
            if (hr)
                break;

#ifndef UNIX
            // the shell combobox where this stuff shows up
            // doesn't know how to fontlink... so we have
            // to stay in the shell's codepage
            langid = MLGetShellLanguage();
#else
            langid = GetSystemDefaultLangID();
#endif /* UNIX */
            if (pMultiLanguage->EnumCodePages( MIMECONTF_SAVABLE_BROWSER | MIMECONTF_VALID,
                                               langid,
                                               &pEnumCodePage) == S_OK)
            {
                MIMECPINFO cpInfo;
                ULONG      ccpInfo;
                UINT       cpDefault;

                if (pMultiLanguage->GetCodePageInfo(csetInfo.uiInternetEncoding, langid, &cpInfo) == S_OK &&
                    !(cpInfo.dwFlags & MIMECONTF_SAVABLE_BROWSER))
                {
                    // If the codepage selected is not savable (eg JP_AUTO),
                    // use the family codepage.
                    cpDefault = cpInfo.uiFamilyCodePage;
                }
                else
                    cpDefault = csetInfo.uiInternetEncoding;

                ptcpi->cpSrc = csetInfo.uiInternetEncoding;

                if (cpDefault == CODEPAGE_UNICODE &&
                    pofn->nFilterIndex == PACKAGE_MHTML) {
                    cpDefault = CODEPAGE_UTF8;
                }

                for (i = 0; pEnumCodePage->Next(1, &cpInfo, &ccpInfo) == S_OK; ++i)
                {
                    TCHAR *lpszDesc;
                    INT_PTR iIdx;

                    if (cpInfo.uiCodePage == CODEPAGE_UNICODE &&
                        pofn->nFilterIndex == PACKAGE_MHTML) {
                        i--;
                        continue;
                    }

                    if (cpDefault == cpInfo.uiCodePage)
                    {
                       StrCpyNW(wzEncoding, cpInfo.wszDescription,
                                lstrlen(cpInfo.wszDescription) + 1);
                       bFoundEncoding = TRUE;
                    }

                    lpszDesc = cpInfo.wszDescription;

                    iIdx = SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                              CB_ADDSTRING, 0,
                                              (LPARAM)lpszDesc);
                    SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                       CB_SETITEMDATA, iIdx,
                                       (LPARAM)cpInfo.uiCodePage);
                }

                if (bFoundEncoding)
                {
                    INT_PTR iIndex = 0;

                    iIndex = SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                CB_FINDSTRINGEXACT, -1,
                                                (LPARAM)wzEncoding);

                    SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_SETCURSEL,
                                       (WPARAM)iIndex, 0);
                }
                else
                {
                    // No encoding found! Bad error. Recover by selecting
                    // the first one.

                    SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_SETCURSEL,
                                       0, 0);
                }

            }
            SAFERELEASE(pEnumCodePage);
            SAFERELEASE(pMultiLanguage);
            break;
        }

#ifdef UNIX
        case WM_COMMAND:
        {
          switch (GET_WM_COMMAND_ID(wParam,lParam))
          {
            case IDOK:
            {
                 ThicketCPInfo *ptcpi = (ThicketCPInfo *)GetWindowLongPtr(hdlg,DWLP_USER);

                 ptcpi->cpDst = CP_ACP;
                 iCurSel = (int) SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETCURSEL, 0, 0);
                 ptcpi->cpDst =
                 (UINT)SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                 (WPARAM)iCurSel, (LPARAM)0);

                 // To spare us from re-instantiating MLANG, we'll set the src and dest
                 // to CP_ACP if no change is indicated.
                 if (ptcpi->cpDst == ptcpi->cpSrc)
                    ptcpi->cpDst = ptcpi->cpSrc = CP_ACP;
           }
           break;
         }
        }
        break;
#endif /* UNIX */

        case WM_NOTIFY:
        {
            LPOFNOTIFY phdr = (LPOFNOTIFY)lParam;

            switch (phdr->hdr.code)
            {
                case CDN_FILEOK:
                {
                    LPOPENFILENAME pofn = (LPOPENFILENAME)phdr->lpOFN;
                    ThicketCPInfo *ptcpi = (ThicketCPInfo *)pofn->lCustData;

                    iCurSel = (int) SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETCURSEL, 0, 0);
                    ptcpi->cpDst = //*(UINT *)phdr->lpOFN->lCustData =
                        (UINT)SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                                             (WPARAM)iCurSel, (LPARAM)0);
                }

                // HACK! This case is implemented to implement a hack for
                // IE5 RAID #60672. MIMEOLE cannot save UNICODE encoding,
                // so when the user selects MHTML saves, we should remove
                // this option.  This code should be removed when MIMEOLE
                // fixes their bug (targeted for NT5 RTM). Contact SBailey
                // for the status of this.

                case CDN_TYPECHANGE:
                {
                    LPOPENFILENAME pofn = (LPOPENFILENAME)phdr->lpOFN;
                    ThicketCPInfo *ptcpi = (ThicketCPInfo *)pofn->lCustData;
                    UINT uiCPSel, uiCP;
                    int iType = pofn->nFilterIndex;
                    UINT iCount;
                    int iCurSel;
                    int iSet = -1;

                    if (iType == PACKAGE_MHTML)
                    {
                        iCurSel = (int)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_GETCURSEL, 0, 0);

                        uiCPSel = (UINT)SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                                             (WPARAM)iCurSel, (LPARAM)0);
                         
                        // If you selected unicode, make it look like you
                        // really selected UTF-8

                        if (uiCPSel == CODEPAGE_UNICODE)
                        {
                            uiCPSel = CODEPAGE_UTF8;
                        }

                        i = (int) SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                     CB_FINDSTRINGEXACT,
                                                     (WPARAM)0,
                                                     (LPARAM)UNICODE_TEXT);
                        if (i != CB_ERR)
                        {
                            SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                               CB_DELETESTRING, i, (LPARAM)0);
                        }

                        iCount = (int)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                         CB_GETCOUNT, 0, 0);

                        // Set selected item back

                        for (i = 0; i < iCount; i++)
                        {
                            uiCP = (UINT)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                                                            (WPARAM)i, (LPARAM)0);
                            if (uiCP == uiCPSel)
                            {
                                iSet = i;
                            }
                        }

                        if (iSet != 0xffffffff)
                        {
                            SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_SETCURSEL,
                                               (WPARAM)iSet, (LPARAM)0);
                        }
                    }
                    else
                    {
                        // Store current selection

                        iCurSel = (int)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_GETCURSEL, 0, 0);

                        uiCPSel = (UINT)SendDlgItemMessage (hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                                             (WPARAM)iCurSel, (LPARAM)0);

                        // Add unicode back in, if it was removed

                        i = (int) SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                     CB_FINDSTRINGEXACT,
                                                     (WPARAM)0,
                                                     (LPARAM)UNICODE_TEXT);

                        if (i == CB_ERR) {
                            // Unicode does not exist, add it back in
                            i = (int) SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                         CB_ADDSTRING, 0,
                                                         (LPARAM)UNICODE_TEXT);
    
                            SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                               CB_SETITEMDATA, i,
                                               (LPARAM)CODEPAGE_UNICODE);

    
                            // Make sure the same encoding selected before is
                            // still selected.
                            iCount = (int)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET,
                                                             CB_GETCOUNT, 0, 0);
                            for (i = 0; i < iCount; i++)
                            {
                                uiCP = (UINT)SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_GETITEMDATA,
                                                                (WPARAM)i, (LPARAM)0);
                                if (uiCP == uiCPSel)
                                {
                                    iSet = i;
                                }
                            }
    
                            if (iCurSel != 0xffffffff)
                            {
                                SendDlgItemMessage(hdlg, IDC_SAVE_CHARSET, CB_SETCURSEL,
                                                   (WPARAM)iSet, (LPARAM)0);
                            }
                        }

                    }

                }
                break;
            }
        }
        break;

        case WM_HELP:
        {
            SHWinHelpOnDemandWrap((HWND)((LPHELPINFO) lParam)->hItemHandle,
                          c_szHelpFile,
                          HELP_WM_HELP,
                          (DWORD_PTR)aSaveAsHelpIDs);
        }
        break;

        case WM_CONTEXTMENU:
        {
            SHWinHelpOnDemandWrap((HWND) wParam,
                          c_szHelpFile, 
                          HELP_CONTEXTMENU,
                          (DWORD_PTR)aSaveAsHelpIDs);
        }
        break;
    }
    return (FALSE);
}

//
// Protect the naive users from themselves, if somebody enters a filename
// of microsoft.com when saving http://www.microsoft.com we don't want
// to save a .COM file since this will be interpreted as an executable.
// bad things will happen
//
void CleanUpFilename(LPTSTR pszFile, int iPackageStyle)
{
    //
    // If we find .COM as the file extension replace it with the file extension
    // of the filetype they are saving the file as
    //
    LPTSTR pszExt = PathFindExtension(pszFile);

    ASSERT(pszExt);
    if (StrCmpI(pszExt, TEXT(".COM")) == 0) // REVIEW any other file types???
    {
        //
        // Map the package style to a default extension. NOTE this relies on 
        // the fact that the filter index maps to the PACKAGE style enum
        // (as does the rest of the thicket code).
        //
        switch (iPackageStyle)
        {
        case PACKAGE_THICKET:
        case PACKAGE_HTML:
            StrCatBuff(pszFile, TEXT(".htm"), MAX_PATH); 
            break;

        case PACKAGE_MHTML:
            StrCatBuff(pszFile, TEXT(".mht"), MAX_PATH); 
            break;

        case PACKAGE_TEXT:
            StrCatBuff(pszFile, TEXT(".txt"), MAX_PATH); 
            break;

        default:
            ASSERT(FALSE);  // Unknown package type
            break;
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   FormsGetFileName
//
//  Synopsis:   Gets a file name using either the GetOpenFileName or
//              GetSaveFileName functions.
//
//  Arguments:  [fSaveFile]   -- TRUE means use GetSaveFileName
//                               FALSE means use GetOpenFileName
//
//              [idFilterRes] -- The string resource specifying text in the
//                                  dialog box.  It must have the
//                                  following format:
//                            Note: the string has to be _one_ contiguous string.
//                                  The example is broken up to make it fit
//                                  on-screen. The verical bar ("pipe") characters
//                                  are changed to '\0'-s on the fly.
//                                  This allows the strings to be localized
//                                  using Espresso.
//
//          IDS_FILENAMERESOURCE, "Save Dialog As|         // the title
//                                 odg|                    // default extension
//                                 Forms3 Dialog (*.odg)|  // pairs of filter strings
//                                 *.odg|
//                                 Any File (*.*)|
//                                 *.*|"
//
//              [pstrFile]    -- Buffer for file name.
//              [cchFile]     -- Size of buffer in characters.
//
//  Modifies:   [pstrFile]
//
//----------------------------------------------------------------------------
#ifdef _MAC
extern "C" {
char * __cdecl _p2cstr(unsigned char *);
}
#endif

#define CHAR_DOT                TEXT('.')
#define CHAR_DOT_REPLACEMENT    TEXT('_')

void ReplaceDotsInFileName(LPTSTR pszFileName)
{
    ASSERT(pszFileName);

    while (*pszFileName)
    {
        if (*pszFileName == CHAR_DOT)
        {
            *pszFileName = CHAR_DOT_REPLACEMENT;
        }
        pszFileName++;
    }
}


HRESULT
FormsGetFileName(
        HWND hwndOwner,
        LPTSTR pstrFile,
        int cchFile,
        LPARAM lCustData,
        DWORD *pnFilterIndex,
        BOOL bForceHTMLOnly)
{
    HRESULT         hr  = S_OK;
    BOOL            fOK;
    DWORD           dwCommDlgErr;
    LPTSTR          pstr;
    OPENFILENAME    ofn;
    TCHAR           achBuffer[4096];    //  Max. size of a string resource
    TCHAR *         cp;
    TCHAR *         pstrExt;
    int             cbBuffer;
    TCHAR           achPath[MAX_PATH];
    DWORD           dwType = REG_SZ;
    DWORD           cbData = MAX_PATH * sizeof(TCHAR);
    int             idFilterRes;


    // Initialize ofn struct
    memset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = hwndOwner;
    ofn.Flags           =   OFN_FILEMUSTEXIST   |
                            OFN_PATHMUSTEXIST   |
                            OFN_OVERWRITEPROMPT |
                            OFN_HIDEREADONLY    |
#ifndef UNIX
                            OFN_NOCHANGEDIR     |
                            OFN_EXPLORER;
#else
                            OFN_NOCHANGEDIR;
#endif /* UNIX */

    ofn.lpfnHook        = NULL;
    ofn.nMaxFile        = cchFile;
    ofn.lCustData       = lCustData;
    ofn.lpstrFile       = pstrFile;
#ifndef NO_IME
    // We add an extra control to the save file dialog.

    if (lCustData)
    {
        ofn.Flags |= OFN_ENABLETEMPLATE | OFN_ENABLEHOOK;
        ofn.lpfnHook = SaveOFNHookProc;
        ofn.lpTemplateName = MAKEINTRESOURCE(IDD_ADDTOSAVE_DIALOG);
        ofn.hInstance = g_hinst;
    }

#endif

    //
    // Find the extension and set the filter index based on what the
    // extension is.  After these loops pstrExt will either be NULL if
    // we didn't find an extension, or will point to the extension starting
    // at the '.'

    pstrExt = pstrFile;
    while (*pstrExt)
        pstrExt++;
    while ( pstrExt >= pstrFile )
    {
        if( *pstrExt == TEXT('.') )
            break;
        pstrExt--;
    }
    if( pstrExt < pstrFile )
        pstrExt = NULL;

    // Load the filter spec.
    // FEATURE: Convert table to stringtable for localization

    if ( SHRestricted2W( REST_NoBrowserSaveWebComplete, NULL, 0 ) )
        idFilterRes = IDS_NOTHICKET_SAVE;
    else if ( s_dwInetComVerMS != 0xFFFFFFFF )
    {
#ifndef UNIX
        if (s_dwInetComVerMS == 0)
        {
            TCHAR szPath[MAX_PATH];

            GetSystemDirectory( szPath, MAX_PATH );
            StrCatBuff( szPath, TEXT("\\INETCOMM.DLL"), MAX_PATH );
            if (FAILED(GetVersionFromFile(szPath, &s_dwInetComVerMS, &s_dwInetComVerLS)))
                s_dwInetComVerMS = 0xFFFFFFFF;
        }

        if (s_dwInetComVerMS >= 0x50000 && s_dwInetComVerMS != 0xFFFFFFFF)
            idFilterRes = IDS_THICKET_SAVE;
        else
            idFilterRes = IDS_NOMHTML_SAVE;
#else
        // on UNIX we don't have inetcomm.dll if oe is not installed
        {
           HINSTANCE hInetComm = NULL;

           if ((hInetComm = LoadLibrary(TEXT("INETCOMM.DLL"))))
           {
              idFilterRes = IDS_THICKET_SAVE;
              FreeLibrary(hInetComm);
           }
           else
              idFilterRes = IDS_NOMHTML_SAVE;
        }
#endif
    }
    else
        idFilterRes = IDS_THICKET_SAVE;

    cbBuffer = MLLoadShellLangString(idFilterRes, achBuffer, ARRAYSIZE(achBuffer));
    ASSERT(cbBuffer > 0);
    if ( ! cbBuffer )
        return E_FAIL;

    ofn.lpstrTitle = achBuffer;

    for ( cp = achBuffer; *cp; cp++ )
    {
        if ( *cp == TEXT('|') )
        {
            *cp = TEXT('\0');
        }
    }

    ASSERT(ofn.lpstrTitle);

    // Default extension is second string.
    pstr = (LPTSTR) ofn.lpstrTitle;
    while (*pstr++)
    {
    }

    // N.B. (johnv) Here we assume that filter index one corresponds with the default
    //  extension, otherwise we would have to introduce a default filter index into
    //  the resource string.
    ofn.nFilterIndex    = ((pnFilterIndex)? *pnFilterIndex : 1);
    ofn.lpstrDefExt     = pstr;

    // Filter is third string.
    while(*pstr++)
    {
    }

    ofn.lpstrFilter = pstr;

    // Try to match the extension with an entry in the filter list
    // If we match, remove the extension from the incoming path string,
    //   set the default extension to the one we found, and appropriately
    //   set the filter index.

    if (pstrExt && !bForceHTMLOnly)
    {
        // N.B. (johnv) We are searching more than we need to.

        int    iIndex = 0;
        const TCHAR* pSearch = ofn.lpstrFilter;

        while( pSearch )
        {
            if( StrStr( pSearch, pstrExt ) )
            {
                ofn.nFilterIndex = (iIndex / 2) + 1;
                ofn.lpstrDefExt = pstrExt + 1;

                // Remove the extension from the file name we pass in
                *pstrExt = TEXT('\0');

                break;
            }
            pSearch += lstrlen(pSearch);
            if( pSearch[1] == 0 )
                break;

            pSearch++;
            iIndex++;
        }
    }

    // Suggest HTML Only as default save-type

    if (bForceHTMLOnly)
    {
        // NOTE: These are hard-coded indices based on shdoclc.rc's
        // IDS_THICKET_SAVE, IDS_NOMHTML_SAVE, IDS_NOTHICKET_SAVE ordering.
        // This saves us the perf hit of doing string comparisons to find
        // HTML only

        switch (idFilterRes)
        {
            case IDS_NOTHICKET_SAVE:
                ofn.nFilterIndex = 1;
                break;

            case IDS_NOMHTML_SAVE:
                ofn.nFilterIndex = 2;
                break;

            default:
                ASSERT(idFilterRes == IDS_THICKET_SAVE);
                ofn.nFilterIndex = 3;
                break;
        }
    }

    if ( SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_MAIN, REGSTR_VAL_SAVEDIRECTORY,
                    &dwType, achPath, &cbData) != ERROR_SUCCESS ||
         !PathFileExists(achPath))
    {
        SHGetSpecialFolderPath(hwndOwner, achPath, CSIDL_PERSONAL, FALSE);
    }

    ofn.lpstrInitialDir = achPath;

    // We don't want to suggest dots in the filename
    ReplaceDotsInFileName(pstrFile);

    // Now, at last, we're ready to call the save file dialog
    fOK = GetSaveFileName(&ofn);

    // if working with the abbreviated format list, adjust the index
    if (idFilterRes == IDS_NOTHICKET_SAVE)
        ofn.nFilterIndex += 2;
    else if ( idFilterRes == IDS_NOMHTML_SAVE && ofn.nFilterIndex > 1 )
        ofn.nFilterIndex += 1;

    if (fOK)
    {
        //
        // Protect the naive users from themselves, if somebody enters a filename
        // of microsoft.com when saving http://www.microsoft.com we don't want
        // to save a .COM file since this will be interpreted as an executable.
        // bad things will happen
        //
        CleanUpFilename(pstrFile, ofn.nFilterIndex);

        TCHAR *lpszFileName;

        StrCpyN( achPath, pstrFile, ARRAYSIZE(achPath) );

        lpszFileName = PathFindFileName( achPath );
        *lpszFileName = 0;

        SHSetValue(HKEY_CURRENT_USER, REGSTR_PATH_MAIN, REGSTR_VAL_SAVEDIRECTORY,
                   REG_SZ, achPath, (lstrlen(achPath) * sizeof(TCHAR)));

        if (pnFilterIndex)
            *pnFilterIndex = ofn.nFilterIndex;

        if (ofn.nFilterIndex != PACKAGE_MHTML)
        {
            // we can only do this if we're not packaging MHTML
            // because MHTML requires that we tag with the explicit
            // charset, even if it was the default. unlike thicket
            // which inherits the charset from the original document,
            // MHTML must be explicitly tagged or else some system
            // charset tags will sneak in.

            ThicketCPInfo * ptcpi = (ThicketCPInfo *)lCustData;

            // To spare us from re-instantiating MLANG, we'll set the src and dest
            // to CP_ACP if no change is indicated.
            if (ptcpi->cpDst == ptcpi->cpSrc)
               ptcpi->cpDst = ptcpi->cpSrc = CP_ACP;
        }
    }
    else
    {
#ifndef WINCE
        dwCommDlgErr = CommDlgExtendedError();
        if (dwCommDlgErr)
        {
            hr = HRESULT_FROM_WIN32(dwCommDlgErr);
        }
        else
        {
            hr = S_FALSE;
        }
#else // WINCE
        hr = E_FAIL;
#endif // WINCE
    }

    return hr;
}

HRESULT
GetFileNameFromURL( LPWSTR pwszURL, LPTSTR pszFile, DWORD cchFile)
{
    HRESULT         hr = S_OK;
    PARSEDURLW      puw = {0};
    int             cchUrl;

    cchUrl = SysStringLen(pwszURL);

    if (cchUrl)
    {
        puw.cbSize = sizeof(PARSEDURLW);
        if (SUCCEEDED(ParseURLW(pwszURL, &puw)))
        {
            OLECHAR *pwchBookMark;
            DWORD   dwSize;
            INTERNET_CACHE_ENTRY_INFOW      ceiT;
            LPINTERNET_CACHE_ENTRY_INFOW    pcei = NULL;

            // Temporarily, null out the '#' in the url
            pwchBookMark = StrRChrW(puw.pszSuffix, NULL,'#');
            if (pwchBookMark)
            {
                *pwchBookMark = 0;
            }

            dwSize = sizeof(INTERNET_CACHE_ENTRY_INFO);
            if ( !GetUrlCacheEntryInfoW( pwszURL, &ceiT, &dwSize ) &&
                 GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
                 (pcei = (LPINTERNET_CACHE_ENTRY_INFOW)new BYTE[dwSize]) != NULL &&
                 GetUrlCacheEntryInfoW( pwszURL, pcei, &dwSize ) )
            {
                StrCpyN(pszFile, PathFindFileName(pcei->lpszLocalFileName), cchFile);
                PathUndecorate(pszFile);
            }

            if(pcei)
                delete[] pcei;

            if (pwchBookMark)
                *pwchBookMark = '#';

            if ( !pszFile[0] )
            {
                OLECHAR *pwchQuery;
                TCHAR   szFileT[MAX_PATH];

                // Temporarily, null out the '?' in the url
                pwchQuery = StrRChrW(puw.pszSuffix, NULL,'?');
                if (pwchQuery)
                {
                    *pwchQuery = 0;
                }

                // IE5 bug 15055 - http://my.excite.com/?uid=B56E4E2D34DF3FED.save_uid
                // fails to save because we were passing "my.excite.com/" as the file
                // name to the file dialog. It doesn't like this.
                if (!pwchQuery || (pwchQuery[-1] != '/' && pwchQuery[-1] != '\\'))
                {
                    dwSize = ARRAYSIZE(szFileT);

                    StrCpyN(szFileT, PathFindFileName(puw.pszSuffix), dwSize);

                    if ( !InternetCanonicalizeUrl( szFileT, pszFile, &dwSize, ICU_DECODE | ICU_NO_ENCODE) )
                        StrCpyN(pszFile, szFileT, cchFile);

                    pszFile[cchFile - 1] = 0;
                }

                if (pwchQuery)
                    *pwchQuery = '?';
            }
        }
    }

    if (!pszFile[0])
    {
        MLLoadString(IDS_UNTITLED, pszFile, cchFile);
    }

    return hr;
}

INT_PTR CALLBACK SaveAsWarningDlgProc(HWND hDlg, UINT msg, WPARAM wParam,
                                      LPARAM lParam)
{
    BOOL        fRet = FALSE;
    HRESULT     hr = S_OK;
    int         iFlags = 0;
    INT_PTR     bChecked = 0;

    switch (msg)
    {
        case WM_INITDIALOG:
            MessageBeep(MB_ICONEXCLAMATION);
            fRet = TRUE;

        case WM_COMMAND:
            bChecked = SendDlgItemMessage(hDlg, IDC_SAVEAS_WARNING_CB,
                                                  BM_GETCHECK, 0, 0 );
            iFlags = (bChecked) ? (SAVEAS_NEVER_ASK_AGAIN) : (0);

            switch (LOWORD(wParam))
            {
                case IDYES:
                    iFlags |= SAVEAS_OK;
                    // fall through

                case IDNO:
                    EndDialog(hDlg, iFlags);
                    fRet = TRUE;
                    break;
            }
    
        default:
            fRet = FALSE;
    }

    return fRet;
}