/*
 * persist.cpp - IPersist, IPersistFile, and IPersistStream implementations for
 *               URL class.
 */


/* Headers
 **********/

#include "project.hpp"
#pragma hdrstop

#include "resource.h"

#include <mluisupp.h>

/* Global Constants
 *******************/

#pragma data_seg(DATA_SEG_READ_ONLY)

extern const UINT g_ucMaxURLLen                    = 1024;

extern const char g_cszURLPrefix[]                 = "url:";
extern const UINT g_ucbURLPrefixLen                = sizeof(g_cszURLPrefix) - 1;

extern const char g_cszURLExt[]                    = ".url";
extern const char g_cszURLDefaultFileNamePrompt[]  = "*.url";

extern const char g_cszCRLF[]                      = "\r\n";

#pragma data_seg()


/* Module Constants
 *******************/

#pragma data_seg(DATA_SEG_READ_ONLY)

// case-insensitive

PRIVATE_DATA const char s_cszInternetShortcutSection[] = "InternetShortcut";

PRIVATE_DATA const char s_cszURLKey[]              = "URL";
PRIVATE_DATA const char s_cszIconFileKey[]         = "IconFile";
PRIVATE_DATA const char s_cszIconIndexKey[]        = "IconIndex";
PRIVATE_DATA const char s_cszHotkeyKey[]           = "Hotkey";
PRIVATE_DATA const char s_cszWorkingDirectoryKey[] = "WorkingDirectory";
PRIVATE_DATA const char s_cszShowCmdKey[]          = "ShowCommand";

PRIVATE_DATA const UINT s_ucMaxIconIndexLen        = 1 + 10 + 1; // -2147483647
PRIVATE_DATA const UINT s_ucMaxHotkeyLen           = s_ucMaxIconIndexLen;
PRIVATE_DATA const UINT s_ucMaxShowCmdLen          = s_ucMaxIconIndexLen;

#pragma data_seg()


/***************************** Private Functions *****************************/


PRIVATE_CODE BOOL DeletePrivateProfileString(PCSTR pcszSection, PCSTR pcszKey,
                                             PCSTR pcszFile)
{
   ASSERT(IS_VALID_STRING_PTR(pcszSection, CSTR));
   ASSERT(IS_VALID_STRING_PTR(pcszKey, CSTR));
   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));

   return(WritePrivateProfileString(pcszSection, pcszKey, NULL, pcszFile));
}

#define SHDeleteIniString(pcszSection, pcszKey, pcszFile) \
           SHSetIniString(pcszSection, pcszKey, NULL, pcszFile)

PRIVATE_CODE HRESULT MassageURL(PSTR pszURL)
{
   HRESULT hr = E_FAIL;

   ASSERT(IS_VALID_STRING_PTR(pszURL, STR));

   TrimWhiteSpace(pszURL);

   PSTR pszBase = pszURL;
   PSTR psz;

   // Skip over any "url:" prefix.

   if (! lstrnicmp(pszBase, g_cszURLPrefix, g_ucbURLPrefixLen))
      pszBase += g_ucbURLPrefixLen;

   lstrcpy(pszURL, pszBase);
   hr = S_OK;

   TRACE_OUT(("MassageURL(): Massaged URL to %s.",
              pszURL));

   ASSERT(FAILED(hr) ||
          IS_VALID_STRING_PTR(pszURL, STR));

   return(hr);
}


PRIVATE_CODE HRESULT ReadURLFromFile(PCSTR pcszFile, PSTR *ppszURL)
{
   HRESULT hr;
   PSTR pszNewURL;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppszURL, PSTR));

   *ppszURL = NULL;

   pszNewURL = new(char[g_ucMaxURLLen]);

   if (pszNewURL)
   {
      DWORD dwcValueLen;

      dwcValueLen = SHGetIniString(s_cszInternetShortcutSection,
                                   s_cszURLKey,
                                   pszNewURL, g_ucMaxURLLen, pcszFile);

      if (dwcValueLen > 0)
      {
         hr = MassageURL(pszNewURL);

         if (hr == S_OK)
         {
            PSTR pszShorterURL;

            // (+ 1) for null terminator.

            if (ReallocateMemory(pszNewURL, lstrlen(pszNewURL) + 1,
                                 (PVOID *)&pszShorterURL))
            {
               *ppszURL = pszShorterURL;

               hr = S_OK;
            }
            else
               hr = E_OUTOFMEMORY;
         }
      }
      else
      {
         hr = S_FALSE;

         WARNING_OUT(("ReadURLFromFile: No URL found in file %s.",
                      pcszFile));
      }
   }
   else
      hr = E_OUTOFMEMORY;

   if (FAILED(hr) ||
       hr == S_FALSE)
   {
      if (pszNewURL)
      {
         delete pszNewURL;
         pszNewURL = NULL;
      }
   }

   ASSERT((hr == S_OK &&
           IS_VALID_STRING_PTR(*ppszURL, STR)) ||
          (hr != S_OK &&
           ! *ppszURL));

   return(hr);
}


PRIVATE_CODE HRESULT WriteURLToFile(PCSTR pcszFile, PCSTR pcszURL)
{
   HRESULT hr;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(! pcszURL ||
          IS_VALID_STRING_PTR(pcszURL, CSTR));

   if (AnyMeat(pcszURL))
   {
      int ncbLen;

      ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
      ASSERT(IS_VALID_STRING_PTR(pcszURL, PSTR));

      hr = (SHSetIniString(s_cszInternetShortcutSection, s_cszURLKey, pcszURL, pcszFile))
           ? S_OK
           : E_FAIL;
   }
   else
      hr = (SHDeleteIniString(s_cszInternetShortcutSection, s_cszURLKey, pcszFile))
           ? S_OK
           : E_FAIL;

   return(hr);
}


PRIVATE_CODE HRESULT ReadIconLocationFromFile(PCSTR pcszFile,
                                              PSTR *ppszIconFile, PINT pniIcon)
{
   HRESULT hr;
   char rgchNewIconFile[MAX_PATH_LEN];
   DWORD dwcValueLen;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppszIconFile, PSTR));
   ASSERT(IS_VALID_WRITE_PTR(pniIcon, INT));

   *ppszIconFile = NULL;
   *pniIcon = 0;

   dwcValueLen = SHGetIniString(s_cszInternetShortcutSection,
                                s_cszIconFileKey,
                                rgchNewIconFile,
                                sizeof(rgchNewIconFile), pcszFile);

   if (dwcValueLen > 0)
   {
      char rgchNewIconIndex[s_ucMaxIconIndexLen];

      dwcValueLen = GetPrivateProfileString(s_cszInternetShortcutSection,
                                            s_cszIconIndexKey,
                                            EMPTY_STRING, rgchNewIconIndex,
                                            sizeof(rgchNewIconIndex),
                                            pcszFile);

      if (dwcValueLen > 0)
      {
         int niIcon;

         if (StrToIntEx(rgchNewIconIndex, 0, &niIcon))
         {
            // (+ 1) for null terminator.

            *ppszIconFile = new(char[lstrlen(rgchNewIconFile) + 1]);

            if (*ppszIconFile)
            {
               lstrcpy(*ppszIconFile, rgchNewIconFile);
               *pniIcon = niIcon;

               hr = S_OK;
            }
            else
               hr = E_OUTOFMEMORY;
         }
         else
         {
            hr = S_FALSE;

            WARNING_OUT(("ReadIconLocationFromFile(): Bad icon index \"%s\" found in file %s.",
                         rgchNewIconIndex,
                         pcszFile));
         }
      }
      else
      {
         hr = S_FALSE;

         WARNING_OUT(("ReadIconLocationFromFile(): No icon index found in file %s.",
                      pcszFile));
      }
   }
   else
   {
      hr = S_FALSE;

      TRACE_OUT(("ReadIconLocationFromFile(): No icon file found in file %s.",
                 pcszFile));
   }

   ASSERT(IsValidIconIndex(hr, *ppszIconFile, MAX_PATH_LEN, *pniIcon));

   return(hr);
}


PRIVATE_CODE HRESULT WriteIconLocationToFile(PCSTR pcszFile,
                                             PCSTR pcszIconFile,
                                             int niIcon)
{
   HRESULT hr;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(! pcszIconFile ||
          IS_VALID_STRING_PTR(pcszIconFile, CSTR));
   ASSERT(IsValidIconIndex((pcszIconFile ? S_OK : S_FALSE), pcszIconFile, MAX_PATH_LEN, niIcon));

   if (AnyMeat(pcszIconFile))
   {
      char rgchIconIndexRHS[s_ucMaxIconIndexLen];
      int ncLen;

      ncLen = wsprintf(rgchIconIndexRHS, "%d", niIcon);
      ASSERT(ncLen > 0);
      ASSERT(ncLen < sizeof(rgchIconIndexRHS));
      ASSERT(ncLen == lstrlen(rgchIconIndexRHS));

      hr = (SHSetIniString(s_cszInternetShortcutSection,
                           s_cszIconFileKey, pcszIconFile,
                           pcszFile) &&
            WritePrivateProfileString(s_cszInternetShortcutSection,
                                      s_cszIconIndexKey, rgchIconIndexRHS,
                                      pcszFile))
           ? S_OK
           : E_FAIL;
   }
   else
      hr = (SHDeleteIniString(s_cszInternetShortcutSection,
                              s_cszIconFileKey, pcszFile) &&
            DeletePrivateProfileString(s_cszInternetShortcutSection,
                                       s_cszIconIndexKey, pcszFile))
           ? S_OK
           : E_FAIL;

   return(hr);
}


PRIVATE_CODE HRESULT ReadHotkeyFromFile(PCSTR pcszFile, PWORD pwHotkey)
{
   HRESULT hr = S_FALSE;
   char rgchHotkey[s_ucMaxHotkeyLen];
   DWORD dwcValueLen;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(pwHotkey, WORD));

   *pwHotkey = 0;

   dwcValueLen = GetPrivateProfileString(s_cszInternetShortcutSection,
                                         s_cszHotkeyKey, EMPTY_STRING,
                                         rgchHotkey, sizeof(rgchHotkey),
                                         pcszFile);

   if (dwcValueLen > 0)
   {
      UINT uHotkey;

      if (StrToIntEx(rgchHotkey, 0, (int *)&uHotkey))
      {
         *pwHotkey = (WORD)uHotkey;

         hr = S_OK;
      }
      else
         WARNING_OUT(("ReadHotkeyFromFile(): Bad hotkey \"%s\" found in file %s.",
                      rgchHotkey,
                      pcszFile));
   }
   else
      WARNING_OUT(("ReadHotkeyFromFile(): No hotkey found in file %s.",
                   pcszFile));

   ASSERT((hr == S_OK &&
           IsValidHotkey(*pwHotkey)) ||
          (hr == S_FALSE &&
           ! *pwHotkey));

   return(hr);
}


PRIVATE_CODE HRESULT WriteHotkeyToFile(PCSTR pcszFile, WORD wHotkey)
{
   HRESULT hr;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(! wHotkey ||
          IsValidHotkey(wHotkey));

   if (wHotkey)
   {
      char rgchHotkeyRHS[s_ucMaxHotkeyLen];
      int ncLen;

      ncLen = wsprintf(rgchHotkeyRHS, "%u", (UINT)wHotkey);
      ASSERT(ncLen > 0);
      ASSERT(ncLen < sizeof(rgchHotkeyRHS));
      ASSERT(ncLen == lstrlen(rgchHotkeyRHS));

      hr = WritePrivateProfileString(s_cszInternetShortcutSection,
                                     s_cszHotkeyKey, rgchHotkeyRHS,
                                     pcszFile)
           ? S_OK
           : E_FAIL;
   }
   else
      hr = DeletePrivateProfileString(s_cszInternetShortcutSection,
                                      s_cszHotkeyKey, pcszFile)
           ? S_OK
           : E_FAIL;

   return(hr);
}


PRIVATE_CODE HRESULT ReadWorkingDirectoryFromFile(PCSTR pcszFile,
                                                  PSTR *ppszWorkingDirectory)
{
   HRESULT hr;
   char rgchDirValue[MAX_PATH_LEN];
   DWORD dwcValueLen;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppszWorkingDirectory, PSTR));

   *ppszWorkingDirectory = NULL;

   dwcValueLen = SHGetIniString(s_cszInternetShortcutSection,
                                s_cszWorkingDirectoryKey,
                                rgchDirValue,
                                sizeof(rgchDirValue), pcszFile);

   if (dwcValueLen > 0)
   {
      char rgchFullPath[MAX_PATH_LEN];
      PSTR pszFileName;

      if (GetFullPathName(rgchDirValue, sizeof(rgchFullPath), rgchFullPath,
                          &pszFileName) > 0)
      {
         // (+ 1) for null terminator.

         *ppszWorkingDirectory = new(char[lstrlen(rgchFullPath) + 1]);

         if (*ppszWorkingDirectory)
         {
            lstrcpy(*ppszWorkingDirectory, rgchFullPath);

            hr = S_OK;
         }
         else
            hr = E_OUTOFMEMORY;
      }
      else
         hr = E_FAIL;
   }
   else
   {
      hr = S_FALSE;

      TRACE_OUT(("ReadWorkingDirectoryFromFile: No working directory found in file %s.",
                 pcszFile));
   }

   ASSERT(IsValidPathResult(hr, *ppszWorkingDirectory, MAX_PATH_LEN));

   return(hr);
}


PRIVATE_CODE HRESULT WriteWorkingDirectoryToFile(PCSTR pcszFile,
                                                 PCSTR pcszWorkingDirectory)
{
   HRESULT hr;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(! pcszWorkingDirectory ||
          IS_VALID_STRING_PTR(pcszWorkingDirectory, CSTR));

   if (AnyMeat(pcszWorkingDirectory))
      hr = (SHSetIniString(s_cszInternetShortcutSection,
                           s_cszWorkingDirectoryKey,
                           pcszWorkingDirectory, pcszFile))
           ? S_OK
           : E_FAIL;
   else
      hr = (SHDeleteIniString(s_cszInternetShortcutSection,
                              s_cszWorkingDirectoryKey, pcszFile))
           ? S_OK
           : E_FAIL;

   return(hr);
}


PRIVATE_CODE HRESULT ReadShowCmdFromFile(PCSTR pcszFile, PINT pnShowCmd)
{
   HRESULT hr;
   char rgchNewShowCmd[s_ucMaxShowCmdLen];
   DWORD dwcValueLen;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(pnShowCmd, INT));

   *pnShowCmd = g_nDefaultShowCmd;

   dwcValueLen = GetPrivateProfileString(s_cszInternetShortcutSection,
                                         s_cszShowCmdKey, EMPTY_STRING,
                                         rgchNewShowCmd,
                                         sizeof(rgchNewShowCmd), pcszFile);

   if (dwcValueLen > 0)
   {
      int nShowCmd;

      if (StrToIntEx(rgchNewShowCmd, 0, &nShowCmd))
      {
         *pnShowCmd = nShowCmd;

         hr = S_OK;
      }
      else
      {
         hr = S_FALSE;

         WARNING_OUT(("ReadShowCmdFromFile: Invalid show command \"%s\" found in file %s.",
                      rgchNewShowCmd,
                      pcszFile));
      }
   }
   else
   {
      hr = S_FALSE;

      TRACE_OUT(("ReadShowCmdFromFile: No show command found in file %s.",
                 pcszFile));
   }

   ASSERT((hr == S_OK &&
           EVAL(IsValidShowCmd(*pnShowCmd))) ||
          (hr == S_FALSE &&
           EVAL(*pnShowCmd == g_nDefaultShowCmd)));

   return(hr);
}


PRIVATE_CODE HRESULT WriteShowCmdToFile(PCSTR pcszFile, int nShowCmd)
{
   HRESULT hr;

   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
   ASSERT(IsValidShowCmd(nShowCmd));

   if (nShowCmd != g_nDefaultShowCmd)
   {
      char rgchShowCmdRHS[s_ucMaxShowCmdLen];
      int ncLen;

      ncLen = wsprintf(rgchShowCmdRHS, "%d", nShowCmd);
      ASSERT(ncLen > 0);
      ASSERT(ncLen < sizeof(rgchShowCmdRHS));
      ASSERT(ncLen == lstrlen(rgchShowCmdRHS));

      hr = (WritePrivateProfileString(s_cszInternetShortcutSection,
                                      s_cszShowCmdKey, rgchShowCmdRHS,
                                      pcszFile))
           ? S_OK
           : E_FAIL;
   }
   else
      hr = (DeletePrivateProfileString(s_cszInternetShortcutSection,
                                       s_cszShowCmdKey, pcszFile))
           ? S_OK
           : E_FAIL;

   return(hr);
}


/****************************** Public Functions *****************************/


PUBLIC_CODE HRESULT UnicodeToANSI(LPCOLESTR pcwszUnicode, PSTR *ppszANSI)
{
   HRESULT hr;
   int ncbLen;

   // FEATURE: Need OLESTR validation function to validate pcwszUnicode here.
   ASSERT(IS_VALID_WRITE_PTR(ppszANSI, PSTR));

   *ppszANSI = NULL;

   // Get length of translated string.

   ncbLen = WideCharToMultiByte(CP_ACP, 0, pcwszUnicode, -1, NULL, 0, NULL,
                                NULL);

   if (ncbLen > 0)
   {
      PSTR pszNewANSI;

      // (+ 1) for null terminator.

      pszNewANSI = new(char[ncbLen]);

      if (pszNewANSI)
      {
         // Translate string.

         if (WideCharToMultiByte(CP_ACP, 0, pcwszUnicode, -1, pszNewANSI,
                                 ncbLen, NULL, NULL) > 0)
         {
            *ppszANSI = pszNewANSI;
            hr = S_OK;
         }
         else
         {
            delete pszNewANSI;
            pszNewANSI = NULL;

            hr = E_UNEXPECTED;

            WARNING_OUT(("UnicodeToANSI(): Failed to translate Unicode string to ANSI."));
         }
      }
      else
         hr = E_OUTOFMEMORY;
   }
   else
   {
      hr = E_UNEXPECTED;

      WARNING_OUT(("UnicodeToANSI(): Failed to get length of translated ANSI string."));
   }

   ASSERT(FAILED(hr) ||
          IS_VALID_STRING_PTR(*ppszANSI, STR));

   return(hr);
}


PUBLIC_CODE HRESULT ANSIToUnicode(PCSTR pcszANSI, LPOLESTR *ppwszUnicode)
{
   HRESULT hr;
   int ncbWideLen;

   ASSERT(IS_VALID_STRING_PTR(pcszANSI, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppwszUnicode, LPOLESTR));

   *ppwszUnicode = NULL;

   // Get length of translated string.

   ncbWideLen = MultiByteToWideChar(CP_ACP, 0, pcszANSI, -1, NULL, 0);

   if (ncbWideLen > 0)
   {
      PWSTR pwszNewUnicode;

      // (+ 1) for null terminator.

      pwszNewUnicode = new(WCHAR[ncbWideLen]);

      if (pwszNewUnicode)
      {
         // Translate string.

         if (MultiByteToWideChar(CP_ACP, 0, pcszANSI, -1, pwszNewUnicode,
                                 ncbWideLen) > 0)
         {
            *ppwszUnicode = pwszNewUnicode;
            hr = S_OK;
         }
         else
         {
            delete pwszNewUnicode;
            pwszNewUnicode = NULL;

            hr = E_UNEXPECTED;

            WARNING_OUT(("ANSIToUnicode(): Failed to translate ANSI path string to Unicode."));
         }
      }
      else
         hr = E_OUTOFMEMORY;
   }
   else
   {
      hr = E_UNEXPECTED;

      WARNING_OUT(("ANSIToUnicode(): Failed to get length of translated Unicode string."));
   }

   // FEATURE: Need OLESTR validation function to validate *ppwszUnicode here.

   return(hr);
}


/********************************** Methods **********************************/


HRESULT STDMETHODCALLTYPE InternetShortcut::SaveToFile(PCSTR pcszFile,
                                                       BOOL bRemember)
{
   HRESULT hr;
   PSTR pszURL;

   DebugEntry(InternetShortcut::SaveToFile);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));

   hr = GetURL(&pszURL);

   if (SUCCEEDED(hr))
   {
      hr = WriteURLToFile(pcszFile, pszURL);

      if (pszURL)
      {
         SHFree(pszURL);
         pszURL = NULL;
      }

      if (hr == S_OK)
      {
         char rgchBuf[MAX_PATH_LEN];
         int niIcon;

         hr = GetIconLocation(rgchBuf, sizeof(rgchBuf), &niIcon);

         if (SUCCEEDED(hr))
         {
            hr = WriteIconLocationToFile(pcszFile, rgchBuf, niIcon);

            if (hr == S_OK)
            {
               WORD wHotkey;

               hr = GetHotkey(&wHotkey);

               if (SUCCEEDED(hr))
               {
                  hr = WriteHotkeyToFile(pcszFile, wHotkey);

                  if (hr == S_OK)
                  {
                     hr = GetWorkingDirectory(rgchBuf, sizeof(rgchBuf));

                     if (SUCCEEDED(hr))
                     {
                        hr = WriteWorkingDirectoryToFile(pcszFile, rgchBuf);

                        if (hr == S_OK)
                        {
                           int nShowCmd;

                           GetShowCmd(&nShowCmd);

                           hr = WriteShowCmdToFile(pcszFile, nShowCmd);

                           if (hr == S_OK)
                           {
                              /* Remember file if requested. */

                              if (bRemember)
                              {
                                 PSTR pszFileCopy;

                                 if (StringCopy(pcszFile, &pszFileCopy))
                                 {
                                    if (m_pszFile)
                                       delete m_pszFile;

                                    m_pszFile = pszFileCopy;

                                    TRACE_OUT(("InternetShortcut::SaveToFile(): Remembering file %s, as requested.",
                                               m_pszFile));
                                 }
                                 else
                                    hr = E_OUTOFMEMORY;
                              }

                              if (hr == S_OK)
                              {
                                 Dirty(FALSE);

                                 SHChangeNotify(SHCNE_UPDATEITEM,
                                                (SHCNF_PATH | SHCNF_FLUSH), pcszFile,
                                                NULL);

#ifdef DEBUG
                                 TRACE_OUT(("InternetShortcut::SaveToFile(): Internet Shortcut saved to file %s:",
                                            pcszFile));
                                 Dump();
#endif
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::SaveToFile, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::LoadFromFile(PCSTR pcszFile,
                                                         BOOL bRemember)
{
   HRESULT hr;
   PSTR pszURL;

   DebugEntry(InternetShortcut::LoadFromFile);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));

   hr = ReadURLFromFile(pcszFile, &pszURL);

   if (SUCCEEDED(hr))
   {
      hr = SetURL(pszURL, (IURL_SETURL_FL_GUESS_PROTOCOL |
                           IURL_SETURL_FL_USE_DEFAULT_PROTOCOL));

      if (pszURL)
      {
         delete pszURL;
         pszURL = NULL;
      }

      if (hr == S_OK)
      {
         PSTR pszIconFile;
         int niIcon;

         hr = ReadIconLocationFromFile(pcszFile, &pszIconFile, &niIcon);

         if (SUCCEEDED(hr))
         {
            hr = SetIconLocation(pszIconFile, niIcon);

            if (pszIconFile)
            {
               delete pszIconFile;
               pszIconFile = NULL;
            }

            if (hr == S_OK)
            {
               WORD wHotkey;

               hr = ReadHotkeyFromFile(pcszFile, &wHotkey);

               if (SUCCEEDED(hr))
               {
                  hr = SetHotkey(wHotkey);

                  if (hr == S_OK)
                  {
                     PSTR pszWorkingDirectory;

                     hr = ReadWorkingDirectoryFromFile(pcszFile,
                                                       &pszWorkingDirectory);

                     if (SUCCEEDED(hr))
                     {
                        hr = SetWorkingDirectory(pszWorkingDirectory);

                        if (pszWorkingDirectory)
                        {
                           delete pszWorkingDirectory;
                           pszWorkingDirectory = NULL;
                        }

                        if (hr == S_OK)
                        {
                           int nShowCmd;

                           hr = ReadShowCmdFromFile(pcszFile, &nShowCmd);

                           if (SUCCEEDED(hr))
                           {
                              /* Remember file if requested. */

                              if (bRemember)
                              {
                                 PSTR pszFileCopy;

                                 if (StringCopy(pcszFile, &pszFileCopy))
                                 {
                                    if (m_pszFile)
                                       delete m_pszFile;

                                    m_pszFile = pszFileCopy;

                                    TRACE_OUT(("InternetShortcut::LoadFromFile(): Remembering file %s, as requested.",
                                               m_pszFile));
                                 }
                                 else
                                    hr = E_OUTOFMEMORY;
                              }

                              if (SUCCEEDED(hr))
                              {
                                 SetShowCmd(nShowCmd);

                                 Dirty(FALSE);

                                 hr = S_OK;

#ifdef DEBUG
                                 TRACE_OUT(("InternetShortcut::LoadFromFile(): Internet Shortcut loaded from file %s:",
                                            pcszFile));
                                 Dump();
#endif
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::LoadFromFile, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::GetCurFile(PSTR pszFile,
                                                       UINT ucbLen)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::GetCurFile);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszFile, STR, ucbLen));

   if (m_pszFile)
   {
      lstrcpyn(pszFile, m_pszFile, ucbLen);

      TRACE_OUT(("InternetShortcut::GetCurFile(): Current file name is %s.",
                 pszFile));

      hr = S_OK;
   }
   else
      hr = S_FALSE;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRING_PTR(pszFile, STR) &&
          EVAL((UINT)lstrlen(pszFile) < ucbLen));
   ASSERT(hr == S_OK ||
          hr == S_FALSE);

   DebugExitHRESULT(InternetShortcut::GetCurFile, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::Dirty(BOOL bDirty)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::Dirty);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   if (bDirty)
   {
      if (IS_FLAG_CLEAR(m_dwFlags, INTSHCUT_FL_DIRTY)) {
         TRACE_OUT(("InternetShortcut::Dirty(): Now dirty."));
      }

      SET_FLAG(m_dwFlags, INTSHCUT_FL_DIRTY);
   }
   else
   {
      if (IS_FLAG_SET(m_dwFlags, INTSHCUT_FL_DIRTY)) {
         TRACE_OUT(("InternetShortcut::Dirty(): Now clean."));
      }

      CLEAR_FLAG(m_dwFlags, INTSHCUT_FL_DIRTY);
   }

   hr = S_OK;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(hr == S_OK);

   DebugExitVOID(InternetShortcut::Dirty);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::GetClassID(PCLSID pclsid)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::GetClassID);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRUCT_PTR(pclsid, CCLSID));

   *pclsid = CLSID_InternetShortcut;
   hr = S_OK;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(FAILED(hr) ||
          IS_VALID_STRUCT_PTR(pclsid, CCLSID));

   DebugExitHRESULT(InternetShortcut::GetClassID, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::IsDirty(void)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::IsDirty);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   if (IS_FLAG_SET(m_dwFlags, INTSHCUT_FL_DIRTY))
      hr = S_OK;
   else
      hr = S_FALSE;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::IsDirty, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::Save(LPCOLESTR pcwszFile,
                                                 BOOL bRemember)
{
   HRESULT hr;
   PSTR pszFile;

   DebugEntry(InternetShortcut::Save);

   // bRemember may be any value.

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   // FEATURE: Need OLESTR validation function to validate pcwszFile here.

   if (pcwszFile)
   {
      hr = UnicodeToANSI(pcwszFile, &pszFile);

      if (hr == S_OK)
      {
         hr = SaveToFile(pszFile, bRemember);

         delete pszFile;
         pszFile = NULL;
      }
   }
   else if (m_pszFile)
      // Ignore bRemember.
      hr = SaveToFile(m_pszFile, FALSE);
   else
      hr = E_FAIL;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::Save, hr);

   return(hr);
}


#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */

HRESULT STDMETHODCALLTYPE InternetShortcut::SaveCompleted(LPCOLESTR pcwszFile)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::SaveCompleted);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   // FEATURE: Need OLESTR validation function to validate pcwszFile here.

   hr = S_OK;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::SaveCompleted, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::Load(LPCOLESTR pcwszFile,
                                                 DWORD dwMode)
{
   HRESULT hr;
   PSTR pszFile;

   DebugEntry(InternetShortcut::Load);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   // FEATURE: Need OLESTR validation function to validate pcwszFile here.
   // FEATURE: Validate dwMode here.

   // FEAUTRE: Implement dwMode flag support.

   hr = UnicodeToANSI(pcwszFile, &pszFile);

   if (hr == S_OK)
   {
      hr = LoadFromFile(pszFile, TRUE);

      delete pszFile;
      pszFile = NULL;
   }

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::Load, hr);

   return(hr);
}

#pragma warning(default:4100) /* "unreferenced formal parameter" warning */


HRESULT STDMETHODCALLTYPE InternetShortcut::GetCurFile(LPOLESTR *ppwszFile)
{
   HRESULT hr;
   LPOLESTR pwszTempFile;

   DebugEntry(InternetShortcut::GetCurFile);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_WRITE_PTR(ppwszFile, LPOLESTR));

   if (m_pszFile)
   {
      hr = ANSIToUnicode(m_pszFile, &pwszTempFile);

      if (hr == S_OK) {
         TRACE_OUT(("InternetShortcut::GetCurFile(): Current file name is %s.",
                    m_pszFile));
      }
   }
   else
   {
      hr = ANSIToUnicode(g_cszURLDefaultFileNamePrompt, &pwszTempFile);

      if (hr == S_OK)
      {
         hr = S_FALSE;

         TRACE_OUT(("InternetShortcut::GetCurFile(): No current file name.  Returning default file name prompt %s.",
                    g_cszURLDefaultFileNamePrompt));
      }
   }

   if (SUCCEEDED(hr))
   {
      // We should really call OleGetMalloc() to get the process IMalloc here.
      // Use SHAlloc() here instead to avoid loading ole32.dll.
      // SHAlloc() / SHFree() turn in to IMalloc::Alloc() and IMalloc::Free()
      // once ole32.dll is loaded.

      // N.b., lstrlenW() returns the length of the given string in characters,
      // not bytes.

      // (+ 1) for null terminator.
      *ppwszFile = (LPOLESTR)SHAlloc((lstrlenW(pwszTempFile) + 1) *
                                     sizeof(*pwszTempFile));

      if (*ppwszFile)
         lstrcpyW(*ppwszFile, pwszTempFile);
      else
         hr = E_OUTOFMEMORY;

      delete pwszTempFile;
      pwszTempFile = NULL;
   }

   // FEATURE: Need OLESTR validation function to validate *ppwszFile here.

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::GetCurFile, hr);

   return(hr);
}


#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */

HRESULT STDMETHODCALLTYPE InternetShortcut::Load(PIStream pistr)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::Load);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_INTERFACE_PTR(pistr, IStream));

   hr = E_NOTIMPL;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::Load, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::Save(PIStream pistr,
                                                 BOOL bClearDirty)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::Save);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_INTERFACE_PTR(pistr, IStream));

   // APPCOMPAT: Yes, this is an awful hack, but that's what we get when
   // no one implements a needed interface and we need to get a product
   // shipped.  (Actually, the hack isn't that bad, as it's what happens in
   // TransferFileContents, except we're writing to a stream and not memory).
   
   const static TCHAR s_cszNewLine[] = TEXT("\r\n");
   const static TCHAR s_cszPrefix[] = TEXT("[InternetShortcut]\r\nURL=");
   LPTSTR pszBuf;
   DWORD cb;

   pszBuf = (LPTSTR)LocalAlloc(LPTR, lstrlen(m_pszURL) + lstrlen(s_cszPrefix) + lstrlen(s_cszNewLine) + 1);

   if (pszBuf)
   {
       wsprintf(pszBuf, TEXT("%s%s%s"), s_cszPrefix, m_pszURL ? m_pszURL : TEXT("") , s_cszNewLine);
      
       hr = pistr->Write(pszBuf, lstrlen(pszBuf), &cb);

       LocalFree(pszBuf);
   }
   else
   {
       hr = E_OUTOFMEMORY;
   }
   
   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::Save, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::GetSizeMax(PULARGE_INTEGER pcbSize)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::GetSizeMax);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_WRITE_PTR(pcbSize, ULARGE_INTEGER));

   hr = E_NOTIMPL;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitHRESULT(InternetShortcut::GetSizeMax, hr);

   return(hr);
}

#pragma warning(default:4100) /* "unreferenced formal parameter" warning */


DWORD STDMETHODCALLTYPE InternetShortcut::GetFileContentsSize(void)
{
   DWORD dwcbLen;

   DebugEntry(InternetShortcut::GetFileContentsSize);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   // Section length.

   // (- 1) for each null terminator.

   HRESULT hr = CreateURLFileContents(m_pszURL, NULL);

   // REARCHITECT: (DavidDi 3/29/95) We need to save more than just the URL string
   // here, i.e., icon file and index, working directory, and show command.

   dwcbLen = SUCCEEDED(hr) ? hr : 0;

   dwcbLen++;       // + 1 for final null terminator

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   DebugExitDWORD(InternetShortcut::GetFileContentsSize, dwcbLen);

   return(dwcbLen);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::TransferUniformResourceLocator(
                                                            PFORMATETC pfmtetc,
                                                            PSTGMEDIUM pstgmed)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::TransferUniformResourceLocator);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRUCT_PTR(pfmtetc, CFORMATETC));
   ASSERT(IS_VALID_WRITE_PTR(pstgmed, STGMEDIUM));

   ASSERT(pfmtetc->dwAspect == DVASPECT_CONTENT);
   ASSERT(pfmtetc->lindex == -1);

   ZeroMemory(pstgmed, sizeof(*pstgmed));

   if (IS_FLAG_SET(pfmtetc->tymed, TYMED_HGLOBAL))
   {
      if (m_pszURL)
      {
         HGLOBAL hgURL;

         hr = E_OUTOFMEMORY;

         // (+ 1) for null terminator.
         hgURL = GlobalAlloc(0, lstrlen(m_pszURL) + 1);

         if (hgURL)
         {
            PSTR pszURL;

            pszURL = (PSTR)GlobalLock(hgURL);

            if (EVAL(pszURL))
            {
               lstrcpy(pszURL, m_pszURL);

               pstgmed->tymed = TYMED_HGLOBAL;
               pstgmed->hGlobal = hgURL;
               ASSERT(! pstgmed->pUnkForRelease);

               hr = S_OK;

               GlobalUnlock(hgURL);
               pszURL = NULL;
            }

            if (hr != S_OK)
            {
               GlobalFree(hgURL);
               hgURL = NULL;
            }
         }
      }
      else
         hr = DV_E_FORMATETC;
   }
   else
      hr = DV_E_TYMED;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT((hr == S_OK &&
           IS_VALID_STRUCT_PTR(pstgmed, CSTGMEDIUM)) ||
          (FAILED(hr) &&
           (EVAL(pstgmed->tymed == TYMED_NULL) &&
            EVAL(! pstgmed->hGlobal) &&
            EVAL(! pstgmed->pUnkForRelease))));

   DebugExitHRESULT(InternetShortcut::TransferUniformResourceLocator, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::TransferText(PFORMATETC pfmtetc,
                                                         PSTGMEDIUM pstgmed)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::TransferText);

   // Assume InternetShortcut::TransferUniformResourceLocator() will perform
   // input and output validation.

   hr = TransferUniformResourceLocator(pfmtetc, pstgmed);

   DebugExitHRESULT(InternetShortcut::TransferText, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::TransferFileGroupDescriptor(
                                                            PFORMATETC pfmtetc,
                                                            PSTGMEDIUM pstgmed)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::TransferFileGroupDescriptor);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRUCT_PTR(pfmtetc, CFORMATETC));
   ASSERT(IS_VALID_WRITE_PTR(pstgmed, STGMEDIUM));

   ASSERT(pfmtetc->dwAspect == DVASPECT_CONTENT);
   ASSERT(pfmtetc->lindex == -1);

   pstgmed->tymed = TYMED_NULL;
   pstgmed->hGlobal = NULL;
   pstgmed->pUnkForRelease = NULL;

   if (IS_FLAG_SET(pfmtetc->tymed, TYMED_HGLOBAL))
   {
      HGLOBAL hgFileGroupDesc;

      hr = E_OUTOFMEMORY;

      hgFileGroupDesc = GlobalAlloc(GMEM_ZEROINIT,
                                    sizeof(FILEGROUPDESCRIPTOR));

      if (hgFileGroupDesc)
      {
         PFILEGROUPDESCRIPTOR pfgd;

         pfgd = (PFILEGROUPDESCRIPTOR)GlobalLock(hgFileGroupDesc);

         if (EVAL(pfgd))
         {
            PFILEDESCRIPTOR pfd = &(pfgd->fgd[0]);

            // Do we already have a file name to use?

            if (m_pszFile)
            {
               lstrcpyn(pfd->cFileName, ExtractFileName(m_pszFile),
                        SIZECHARS(pfd->cFileName));

               hr = S_OK;
            }
            else
            {
               if (EVAL(MLLoadStringA(
                                   IDS_NEW_INTERNET_SHORTCUT, pfd->cFileName,
                                   sizeof(pfd->cFileName))))
                  hr = S_OK;
            }

            if (hr == S_OK)
            {
               pfd->dwFlags = (FD_FILESIZE |
                               FD_LINKUI);
               pfd->nFileSizeHigh = 0;
               pfd->nFileSizeLow = GetFileContentsSize();

               pfgd->cItems = 1;

               pstgmed->tymed = TYMED_HGLOBAL;
               pstgmed->hGlobal = hgFileGroupDesc;
               ASSERT(! pstgmed->pUnkForRelease);
            }

            GlobalUnlock(hgFileGroupDesc);
            pfgd = NULL;
         }

         if (hr != S_OK)
         {
            GlobalFree(hgFileGroupDesc);
            hgFileGroupDesc = NULL;
         }
      }
   }
   else
      hr = DV_E_TYMED;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT((hr == S_OK &&
           IS_VALID_STRUCT_PTR(pstgmed, CSTGMEDIUM)) ||
          (FAILED(hr) &&
           (EVAL(pstgmed->tymed == TYMED_NULL) &&
            EVAL(! pstgmed->hGlobal) &&
            EVAL(! pstgmed->pUnkForRelease))));

   DebugExitHRESULT(InternetShortcut::TransferFileGroupDescriptor, hr);

   return(hr);
}


HRESULT STDMETHODCALLTYPE InternetShortcut::TransferFileContents(
                                                            PFORMATETC pfmtetc,
                                                            PSTGMEDIUM pstgmed)
{
   HRESULT hr;

   DebugEntry(InternetShortcut::TransferFileContents);

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT(IS_VALID_STRUCT_PTR(pfmtetc, CFORMATETC));
   ASSERT(IS_VALID_WRITE_PTR(pstgmed, STGMEDIUM));

   ASSERT(pfmtetc->dwAspect == DVASPECT_CONTENT);
   ASSERT(! pfmtetc->lindex);

   pstgmed->tymed = TYMED_NULL;
   pstgmed->hGlobal = NULL;
   pstgmed->pUnkForRelease = NULL;

   if (IS_FLAG_SET(pfmtetc->tymed, TYMED_HGLOBAL))
   {
      HGLOBAL hgFileContents;
      hr = CreateURLFileContents(m_pszURL, (LPSTR *)&hgFileContents);

      if (SUCCEEDED(hr))
      {
         // Note some apps don't pay attention to the nFileSizeLow
         // field; fortunately, CreateURLFileContents adds a final
         // null terminator to prevent trailing garbage.

         pstgmed->tymed = TYMED_HGLOBAL;
         pstgmed->hGlobal = hgFileContents;
         ASSERT(! pstgmed->pUnkForRelease);

         hr = S_OK;
      }
      else
         hr = E_OUTOFMEMORY;
   }
   else
      hr = DV_E_TYMED;

   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
   ASSERT((hr == S_OK &&
           IS_VALID_STRUCT_PTR(pstgmed, CSTGMEDIUM)) ||
          (FAILED(hr) &&
           (EVAL(pstgmed->tymed == TYMED_NULL) &&
            EVAL(! pstgmed->hGlobal) &&
            EVAL(! pstgmed->pUnkForRelease))));

   DebugExitHRESULT(InternetShortcut::TransferFileContents, hr);

   return(hr);
}


#ifdef DEBUG

void STDMETHODCALLTYPE InternetShortcut::Dump(void)
{
   ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));

   PLAIN_TRACE_OUT(("%sm_dwFlags = %#08lx",
                    INDENT_STRING,
                    m_dwFlags));
   PLAIN_TRACE_OUT(("%sm_pszFile = \"%s\"",
                    INDENT_STRING,
                    CHECK_STRING(m_pszFile)));
   PLAIN_TRACE_OUT(("%sm_pszURL = \"%s\"",
                    INDENT_STRING,
                    CHECK_STRING(m_pszURL)));
   PLAIN_TRACE_OUT(("%sm_pszIconFile = \"%s\"",
                    INDENT_STRING,
                    CHECK_STRING(m_pszIconFile)));
   PLAIN_TRACE_OUT(("%sm_niIcon = %d",
                    INDENT_STRING,
                    m_niIcon));
   PLAIN_TRACE_OUT(("%sm_wHotkey = %#04x",
                    INDENT_STRING,
                    (UINT)m_wHotkey));
   PLAIN_TRACE_OUT(("%sm_pszWorkingDirectory = \"%s\"",
                    INDENT_STRING,
                    CHECK_STRING(m_pszWorkingDirectory)));
   PLAIN_TRACE_OUT(("%sm_nShowCmd = %d",
                    INDENT_STRING,
                    m_nShowCmd));

   return;
}

#endif