windows-nt/Source/XPSP1/NT/base/ntsetup/oobe/msobdl/download.cpp
2020-09-26 16:20:57 +08:00

1643 lines
44 KiB
C++

/*****************************************************************************\
MAIN.CPP
Microsoft Confidential
Copyright (c) Microsoft Corporation 1998
All rights reserved
\*****************************************************************************/
#include <windows.h>
#include "pch.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <shellapi.h>
#include <shlobj.h>
#include <intshcut.h>
#include <wininet.h>
//#include "icwdl.h"
#include "msobdl.h"
// 12/4/96 jmazner Normandy #12193
// path to icwconn1.exe registry key from HKEY_LOCAL_MACHINE
#define ICWCONN1PATHKEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\CONNWIZ.EXE"
#define PATHKEYNAME "Path"
#include <winreg.h>
// Cabbing up.
extern HRESULT HandleCab(LPSTR pszPath);
extern void CleanupCabHandler();
// all global data is static shared, read-only
// (written only during DLL-load)
HANDLE g_hDLLHeap; // private Win32 heap
HINSTANCE g_hInst; // our DLL hInstance
HWND g_hWndMain; // hwnd of icwconn1 parent window
#define DllExport extern "C" __declspec(dllexport)
#define MAX_RES_LEN 255 // max length of string resources
#define SMALL_BUF_LEN 48 // convenient size for small text buffers
LPSTR LoadSz(UINT idString, LPSTR lpszBuf,UINT cbBuf);
///////////////////////////////////////////////////////////
// DLL module information
//
/*
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD dwReason,
void* lpReserved )
{
return TRUE;
}*/
#if 0
// MSOBDL is not currently a COM object so there is no need to implement these.
//
//////////////////////////////////////////////////////////
// DllCanUnloadNow
//
STDAPI DllCanUnloadNow()
{
return S_OK ;
}
//////////////////////////////////////////////////////////
// Server Registration
//
STDAPI DllRegisterServer()
{
return S_OK ;
}
//////////////////////////////////////////////////////////
// Unregistration
//
STDAPI DllUnregisterServer()
{
return S_OK ;
}
#endif // 0
LPSTR LoadSz(UINT idString, LPSTR lpszBuf,UINT cbBuf);
//+---------------------------------------------------------------------------
//
// Function: MyGetTempPath()
//
// Synopsis: Gets the path to temporary directory
// - Use GetTempFileName to get a file name
// and strips off the filename portion to get the temp path
//
// Arguments: [uiLength - Length of buffer to contain the temp path]
// [szPath - Buffer in which temp path will be returned]
//
// Returns: Length of temp path if successful
// 0 otherwise
//
// History: 7/6/96 VetriV Created
// 8/23/96 VetriV Delete the temp file
// 12/4/96 jmazner Modified to serve as a wrapper of sorts;
// if TMP or TEMP don't exist, setEnv our own
// vars that point to conn1's installed path
// (Normandy #12193)
//
//----------------------------------------------------------------------------
DWORD MyGetTempPath(UINT uiLength, LPSTR szPath)
{
CHAR szEnvVarName[SMALL_BUF_LEN + 1] = "\0unitialized szEnvVarName\0";
DWORD dwFileAttr = 0;
lstrcpynA( szPath, "\0unitialized szPath\0", 20 );
// is the TMP variable set?
LoadSz(IDS_TMPVAR, szEnvVarName,sizeof(szEnvVarName));
if( GetEnvironmentVariableA( szEnvVarName, szPath, uiLength ) )
{
// 1/7/96 jmazner Normandy #12193
// verify validity of directory name
dwFileAttr = GetFileAttributesA(szPath);
// if there was any error, this directory isn't valid.
if( 0xFFFFFFFF != dwFileAttr )
{
if( FILE_ATTRIBUTE_DIRECTORY & dwFileAttr )
{
return( lstrlenA(szPath) );
}
}
}
lstrcpynA( szEnvVarName, "\0unitialized again\0", 19 );
// if not, is the TEMP variable set?
LoadSz(IDS_TEMPVAR, szEnvVarName,sizeof(szEnvVarName));
if( GetEnvironmentVariableA( szEnvVarName, szPath, uiLength ) )
{
// 1/7/96 jmazner Normandy #12193
// verify validity of directory name
dwFileAttr = GetFileAttributesA(szPath);
if( 0xFFFFFFFF != dwFileAttr )
{
if( FILE_ATTRIBUTE_DIRECTORY & dwFileAttr )
{
return( lstrlenA(szPath) );
}
}
}
// neither one is set, so let's use the path to the installed icwconn1.exe
// from the registry SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\ICWCONN1.EXE\Path
HKEY hkey = NULL;
if ((RegOpenKeyExA(HKEY_LOCAL_MACHINE, ICWCONN1PATHKEY, 0, KEY_QUERY_VALUE, &hkey)) == ERROR_SUCCESS)
RegQueryValueExA(hkey, PATHKEYNAME, NULL, NULL, (BYTE *)szPath, (DWORD *)&uiLength);
if (hkey)
{
RegCloseKey(hkey);
}
//The path variable is supposed to have a semicolon at the end of it.
// if it's there, remove it.
if( ';' == szPath[uiLength - 2] )
szPath[uiLength - 2] = '\0';
// go ahead and set the TEMP variable for future reference
// (only effects currently running process)
if( szEnvVarName[0] )
{
SetEnvironmentVariableA( szEnvVarName, szPath );
}
else
{
lstrcpynA( szPath, "\0unitialized again\0", 19 );
return( 0 );
}
return( uiLength );
}
extern "C" BOOL _stdcall DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lbv)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
// Need to use OLE/Com later
if (FAILED(CoInitialize(NULL)))
return(FALSE);
//
// ChrisK Olympus 6373 6/13/97
// Disable thread attach calls in order to avoid race condition
// on Win95 golden
//
DisableThreadLibraryCalls(hInstance);
g_hInst = hInstance;
g_hDLLHeap = HeapCreate(0, 0, 0);
MyAssert(g_hDLLHeap);
if (g_hDLLHeap == NULL)
return FALSE;
break;
case DLL_PROCESS_DETACH:
CoUninitialize();
HeapDestroy(g_hDLLHeap);
// Cleanup the cabbing stuff.
CleanupCabHandler();
break;
}
return TRUE;
}
LPSTR MyStrDup(LPSTR pszIn)
{
int len;
LPSTR pszOut;
MyAssert(pszIn);
len = lstrlenA(pszIn);
if(!(pszOut = (LPSTR)MyAlloc((len+1) * sizeof(CHAR))))
{
MyAssert(FALSE);
return NULL;
}
lstrcpyA(pszOut, pszIn);
pszOut[len] = 0;
return pszOut;
}
int MyAssertProc(LPSTR pszFile, int nLine, LPSTR pszExpr)
{
CHAR szBuf[512];
wsprintfA(szBuf, "Assert failed at line %d in file %s. (%s)\r\n", nLine, pszFile, pszExpr);
MyDbgSz((szBuf));
return 0;
}
void _cdecl MyDprintf(LPCSTR pcsz, ...)
{
va_list argp;
CHAR szBuf[1024];
if ((NULL == pcsz) || ('\0' == pcsz[0]))
return;
va_start(argp, pcsz);
wvsprintfA(szBuf, pcsz, argp);
MyDbgSz((szBuf));
va_end(argp);
} // Dprintf()
// ############################################################################
// operator new
//
// This function allocate memory for C++ classes
//
// Created 3/18/96, Chris Kauffman
// ############################################################################
void * MyBaseClass::operator new( size_t cb )
{
return MyAlloc(cb);
}
// ############################################################################
// operator delete
//
// This function frees memory for C++ classes
//
// Created 3/18/96, Chris Kauffman
// ############################################################################
void MyBaseClass::operator delete( void * p )
{
MyFree( p );
}
void CDownLoad::AddToFileList(CFileInfo* pfi)
{
CFileInfo **ppfi;
// must add at tail
for(ppfi=&m_pfiHead; *ppfi; ppfi = &((*ppfi)->m_pfiNext))
;
*ppfi = pfi;
}
CDownLoad::CDownLoad(LPSTR psz)
{
CHAR szUserAgent[128];
OSVERSIONINFO osVer;
LPSTR pszOS = "";
memset(this, 0, sizeof(CDownLoad));
if(psz)
m_pszURL = MyStrDup(psz);
memset(&osVer, 0, sizeof(osVer));
osVer.dwOSVersionInfoSize = sizeof(osVer);
GetVersionEx(&osVer);
switch(osVer.dwPlatformId)
{
case VER_PLATFORM_WIN32_WINDOWS:
pszOS = "Windows95";
break;
case VER_PLATFORM_WIN32_NT:
pszOS = "WindowsNT";
break;
}
wsprintfA(szUserAgent, USERAGENT_FMT, pszOS, osVer.dwMajorVersion,
osVer.dwMinorVersion, GetSystemDefaultLangID());
m_hSession = InternetOpenA(szUserAgent, 0, NULL, NULL, 0);
CHAR szBuf[MAX_PATH+1];
if (GetWindowsDirectoryA(szBuf, MAX_PATH) == 0)
{
szBuf[MAX_PATH] = 0;
m_pszWindowsDir = MyStrDup(szBuf);
m_dwWindowsDirLen = lstrlenA(m_pszWindowsDir);
}
GetSystemDirectoryA(szBuf, MAX_PATH);
szBuf[MAX_PATH] = 0;
m_pszSystemDir = MyStrDup(szBuf);
m_dwSystemDirLen = lstrlenA(m_pszSystemDir);
MyGetTempPath(MAX_PATH, szBuf);
szBuf[MAX_PATH] = 0;
m_pszTempDir = MyStrDup(szBuf);
if (m_pszTempDir != NULL)
{
m_dwTempDirLen = lstrlenA(m_pszTempDir);
if(m_dwTempDirLen > 0 && m_pszTempDir[m_dwTempDirLen-1]=='\\')
{
m_pszTempDir[m_dwTempDirLen-1]=0;
m_dwTempDirLen--;
}
}
// form the ICW98 dir. It is basically the CWD
m_dwICW98DirLen = 0;
m_pszICW98Dir = (LPSTR)MyAlloc((MAX_PATH +1) * sizeof(CHAR));
if (m_pszICW98Dir != NULL)
{
if (0 < GetSystemDirectoryA(m_pszICW98Dir, MAX_PATH))
{
lstrcatA(m_pszICW98Dir, "\\OOBE");
}
m_dwICW98DirLen = lstrlenA(m_pszICW98Dir);
}
LPSTR pszCmdLine = GetCommandLineA();
LPSTR pszTemp = NULL, pszTemp2 = NULL;
lstrcpynA(szBuf, pszCmdLine, MAX_PATH);
szBuf[MAX_PATH] = 0;
pszTemp = strtok(szBuf, " \t\r\n");
if (NULL != pszTemp)
{
pszTemp2 = strchr(pszTemp, TEXT('\\'));
if(!pszTemp2)
pszTemp2 = strrchr(pszTemp, TEXT('/'));
}
if(pszTemp2)
{
*pszTemp2 = 0;
m_pszSignupDir = MyStrDup(pszTemp);
}
else
{
MyAssert(FALSE);
GetCurrentDirectoryA(MAX_PATH, szBuf);
szBuf[MAX_PATH] = 0;
m_pszSignupDir = MyStrDup(szBuf);
}
m_dwSignupDirLen = lstrlenA(m_pszSignupDir);
m_dwReadLength = 0;
}
CDownLoad::~CDownLoad(void)
{
MyDprintf("ICWDL: CDownLoad::~CDownLoad called\n", this);
CFileInfo *pfi, *pfiNext;
for(pfi=m_pfiHead; pfi; pfi=pfiNext)
{
pfiNext = pfi->m_pfiNext;
delete pfi;
}
if(m_pszWindowsDir)
MyFree(m_pszWindowsDir);
if(m_pszSystemDir)
MyFree(m_pszSystemDir);
if(m_pszTempDir)
MyFree(m_pszTempDir);
if(m_pszICW98Dir)
MyFree(m_pszICW98Dir);
if(m_pszSignupDir)
MyFree(m_pszSignupDir);
if(m_pszURL)
MyFree(m_pszURL);
if(m_pszBoundary)
MyFree(m_pszBoundary);
if(m_hSession)
InternetSessionCloseHandle(m_hSession);
MyAssert(!m_hRequest);
//
// 5/23/97 jmazner Olympus #4652
// Make sure that any waiting threads are freed up.
//
if( m_hCancelSemaphore )
{
ReleaseSemaphore( m_hCancelSemaphore, 1, NULL );
CloseHandle( m_hCancelSemaphore );
m_hCancelSemaphore = NULL;
}
}
// perform a file name substitution
LPSTR CDownLoad::FileToPath(LPSTR pszFile)
{
CHAR szBuf[MAX_PATH+1];
for(long j=0; *pszFile; pszFile++)
{
if(j>=MAX_PATH)
return NULL;
if(*pszFile=='%')
{
pszFile++;
LPSTR pszTemp = strchr(pszFile, '%');
if(!pszTemp)
return NULL;
*pszTemp = 0;
if(lstrcmpiA(pszFile, SIGNUP)==0)
{
lstrcpyA(szBuf+j, m_pszSignupDir);
j+= m_dwSignupDirLen;
}
else if(lstrcmpiA(pszFile, WINDOWS)==0)
{
lstrcpyA(szBuf+j, m_pszWindowsDir);
j+= m_dwWindowsDirLen;
}
else if(lstrcmpiA(pszFile, SYSTEM)==0)
{
lstrcpyA(szBuf+j, m_pszSystemDir);
j+= m_dwSystemDirLen;
}
else if(lstrcmpiA(pszFile, TEMP)==0)
{
lstrcpyA(szBuf+j, m_pszTempDir);
j+= m_dwTempDirLen;
}
else if(lstrcmpiA(pszFile, ICW98DIR)==0 && m_pszICW98Dir != NULL)
{
lstrcpyA(szBuf+j, m_pszICW98Dir);
j+= m_dwICW98DirLen;
}
else
return NULL;
pszFile=pszTemp;
}
else
szBuf[j++] = *pszFile;
}
szBuf[j] = 0;
return MyStrDup(szBuf);
}
// Chops input up into CRLF-delimited chunks
// Modifies input
LPSTR GetNextLine(LPSTR pszIn)
{
LPSTR pszNext;
while(*pszIn)
{
pszNext = strchr(pszIn, '\r');
if(!pszNext)
return NULL;
else if(pszNext[1]=='\n')
{
pszNext[0] = pszNext[1] = 0;
return pszNext+2;
}
else
pszIn = pszNext+1;
}
return NULL;
}
// Modifies input. Output is *in-place*
LPSTR FindHeaderParam(LPSTR pszIn, LPSTR pszLook)
{
LPSTR pszEnd = pszIn + lstrlenA(pszIn);
BOOL fFound = FALSE;
LPSTR pszToken = NULL;
while(pszIn<pszEnd)
{
pszToken=strtok(pszIn, " \t;=");
if(fFound || !pszToken)
break;
pszIn = pszToken+lstrlenA(pszToken)+1;
if(lstrcmpiA(pszToken, pszLook)==0)
fFound = TRUE;
}
if(fFound && pszToken)
{
if(pszToken[0]=='"')
pszToken++;
int iLen = lstrlenA(pszToken);
if(pszToken[iLen-1]=='"')
pszToken[iLen-1]=0;
return pszToken;
}
return NULL;
}
// Modifies input!!
LPSTR ParseHeaders(LPSTR pszIn, LPSTR* ppszBoundary, LPSTR* ppszFilename, BOOL* pfInline)
{
LPSTR pszNext=NULL, pszCurr=NULL, pszToken=NULL, pszToken2=NULL, pszTemp=NULL;
// int iLen; ChrisK
if(pfInline) *pfInline = FALSE;
if(ppszFilename) *ppszFilename = NULL;
if(ppszBoundary) *ppszBoundary = NULL;
for(pszCurr=pszIn; pszCurr; pszCurr=pszNext)
{
// terminate current line with null & get ptr to next
pszNext = GetNextLine(pszCurr);
// if we have a blank line, done with headers--exit loop
if(*pszCurr==0)
{
pszCurr = pszNext;
break;
}
if(!(pszToken = strtok(pszCurr, " \t:;")))
continue;
pszCurr = pszToken+lstrlenA(pszToken)+1;
if(lstrcmpiA(pszToken, MULTIPART_MIXED)==0)
{
if(ppszBoundary)
{
pszTemp = FindHeaderParam(pszCurr, BOUNDARY);
if(pszTemp)
{
int iLen = lstrlenA(pszTemp);
*ppszBoundary = (LPSTR)MyAlloc((iLen+2+1) * sizeof(CHAR));
if (*ppszBoundary != NULL)
{
(*ppszBoundary)[0] = (*ppszBoundary)[1] = '-';
lstrcpyA(*ppszBoundary+2, pszTemp);
}
}
}
}
else if(lstrcmpiA(pszToken, CONTENT_DISPOSITION)==0)
{
if(!(pszToken2 = strtok(pszCurr, " \t:;")))
continue;
pszCurr = pszToken2+lstrlenA(pszToken2)+1;
if(lstrcmpiA(pszToken2, INLINE)==0)
{
if(pfInline)
*pfInline = TRUE;
}
else if(lstrcmpiA(pszToken2, ATTACHMENT)!=0)
continue;
if(ppszFilename)
{
pszTemp = FindHeaderParam(pszCurr, FILENAME);
if(pszTemp)
*ppszFilename = MyStrDup(pszTemp);
}
}
}
return pszCurr;
}
BOOL g_ForceOnlineAttempted = FALSE;
HRESULT CDownLoad::Execute(void)
{
CHAR szBuf[256];
DWORD dwLen;
HRESULT hr = ERROR_GEN_FAILURE;
if(!m_hSession || !m_pszURL)
return ERROR_INVALID_PARAMETER;
m_hRequest = InternetOpenUrlA(m_hSession, m_pszURL, NULL, 0,
(INTERNET_FLAG_RELOAD|INTERNET_FLAG_DONT_CACHE), (DWORD_PTR)this);
if(!m_hRequest)
{
if (!m_hSession)
return GetLastError();
else
{
HRESULT hRes = InternetGetLastError(m_hSession);
if (hRes == INTERNET_STATE_DISCONNECTED)
{
DWORD dwConnectedFlags = 0;
InternetGetConnectedStateEx(&dwConnectedFlags,
NULL,
0,
0);
if(dwConnectedFlags & INTERNET_CONNECTION_OFFLINE)
{
if(g_ForceOnlineAttempted)
{
g_ForceOnlineAttempted = FALSE;
hRes = INTERNET_CONNECTION_OFFLINE;
}
else
{
//ack! the user is offline. not good. let's put them back online.
INTERNET_CONNECTED_INFO ci;
memset(&ci, 0, sizeof(ci));
ci.dwConnectedState = INTERNET_STATE_CONNECTED;
InternetSetOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
g_ForceOnlineAttempted = TRUE;
//now that we've reset the state let's recurse the call.
//if we fail again, then we'll tell the user they need
//to disable the Offline themseleve
return Execute();
}
}
}
return hRes;
}
}
dwLen = sizeof(szBuf);
if(HttpQueryInfoA(m_hRequest, HTTP_QUERY_CONTENT_LENGTH, szBuf, &dwLen, NULL))
{
m_dwContentLength = atol(szBuf);
}
else
{
m_dwContentLength = 0;
}
dwLen = sizeof(szBuf);
if(HttpQueryInfoA(m_hRequest, HTTP_QUERY_CONTENT_TYPE, szBuf, &dwLen, NULL))
{
ParseHeaders(szBuf, &m_pszBoundary, NULL, NULL);
if(m_pszBoundary)
m_dwBoundaryLen = lstrlenA(m_pszBoundary);
else
goto ExecuteExit; // Chrisk, you have to clean up before exiting
hr = ProcessRequest();
}
ExecuteExit:
if (m_hRequest)
InternetRequestCloseHandle(m_hRequest);
m_hRequest = NULL;
return hr;
}
//+----------------------------------------------------------------------------
//
// Function: ShowProgress
//
// Synopsis: update running total & call progress callback
//
// Arguments: dwRead - additional number of bytes read
//
// Returns: none
//
// History: ArulM Created
// 8/896 ChrisK Ported from \\TRANGO
//
//-----------------------------------------------------------------------------
void CDownLoad::ShowProgress(DWORD dwRead)
{
int prc;
m_dwReadLength += dwRead; // running total bytes read
MyAssert(m_dwReadLength <= m_dwContentLength);
if (m_lpfnCB)
{
if (m_dwContentLength)
{
prc = (int)((DWORD)100 * m_dwReadLength / m_dwContentLength);
}
else
{
prc = 0;
}
//
// 5/27/97 jmazner Olympus #4579
// need to pass in a valid pointer to a CDialingDlg!
//
(m_lpfnCB)(m_hRequest, m_lpCDialingDlg,CALLBACK_TYPE_PROGRESS,(LPVOID)&prc,sizeof(prc));
}
}
//+----------------------------------------------------------------------------
//
// Function: FillBuffer
//
// Synopsis: takes a buffer that is partially-filled and reads until it is
// full or until we've reached the end.
//
// Arguments: Buffer pointer, buffer size, count of valid data bytes
//
// Returns: total number of bytes in buf
//
// History: ArulM Created
// 8/8/96 ChrisK Ported from \\TRANGO
//
//-----------------------------------------------------------------------------
DWORD CDownLoad::FillBuffer(LPBYTE pbBuf, DWORD dwLen, DWORD dwRead)
{
DWORD dwTemp;
while(dwRead < dwLen)
{
dwTemp = 0;
if(!InternetReadFile(m_hRequest, pbBuf+dwRead, (dwLen-dwRead), &dwTemp))
break;
if(!dwTemp)
break;
ShowProgress(dwTemp);
dwRead += dwTemp;
}
if(dwLen-dwRead)
memset(pbBuf+dwRead, 0, (size_t)(dwLen-dwRead));
return dwRead;
}
//+----------------------------------------------------------------------------
//
// Function: MoveAndFillBuffer
//
// Synopsis: move remaining contents of buffer from middle of buffer back to
// the beginning & refill buffer.
//
// Arguements: Buffer pointer, Buffer size, count of *total* valid data bytes
// Pointer to start of data to be moved (everything before is nuked)
//
// Returns: total number of bytes in buffer
//
// History: ArulM Created
// 8/8/96 ChrisK Ported from \\TRANGO
//
//-----------------------------------------------------------------------------
DWORD CDownLoad::MoveAndFillBuffer(LPBYTE pbBuf, DWORD dwLen, DWORD dwValid, LPBYTE pbNewStart)
{
MyAssert(pbNewStart >= pbBuf);
MyAssert(pbBuf+dwValid >= pbNewStart);
dwValid -= (DWORD)(pbNewStart-pbBuf);
if(dwValid)
memmove(pbBuf, pbNewStart, (size_t)dwValid);
return FillBuffer(pbBuf, dwLen, dwValid);
}
//+----------------------------------------------------------------------------
//
// Function: HandlwDLFile
//
// Synopsis: Handle filename:
// (1) get full path after macro substituition. (2) Free
// pszFile string.
// (3) save the file path & inline/attach info internally for
// later handling
// (4) Create file on disk & return HANDLE
//
// Aruguments: pszFile - filename
// fInLine - value of inline/attached header from the MIME mutli-part
//
// Returns: phFile - handle of file created
// return - ERROR_SUCCESS == success
//
// History: ArulM Created
// 8/8/96 ChrisK Ported from \\TRANGO
//
//-----------------------------------------------------------------------------
HRESULT CDownLoad::HandleDLFile(LPSTR pszFile, BOOL fInline, LPHANDLE phFile)
{
CHAR szdrive[_MAX_DRIVE];
CHAR szPathName[_MAX_PATH]; // This will be the dir we need to create
CHAR szdir[_MAX_DIR];
CHAR szfname[_MAX_FNAME];
CHAR szext[_MAX_EXT];
MyAssert(phFile);
*phFile = INVALID_HANDLE_VALUE;
LPSTR pszPath = FileToPath(pszFile);
MyFree(pszFile);
if(!pszPath)
return ERROR_INVALID_DATA;
// Split the provided path to get at the drive and path portion
_splitpath( pszPath, szdrive, szdir, szfname, szext );
wsprintfA (szPathName, "%s%s", szdrive, szdir);
// Create the Directory
CreateDirectoryA(szPathName, NULL);
// create the file
*phFile = CreateFileA(pszPath,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(*phFile == INVALID_HANDLE_VALUE)
return GetLastError();
CFileInfo* pfi = new CFileInfo(pszPath, fInline);
if(!pfi)
return GetLastError();
AddToFileList(pfi);
return ERROR_SUCCESS;
}
/*******************************************************************
*
* NAME: LoadSz
*
* SYNOPSIS: Loads specified string resource into buffer
*
* EXIT: returns a pointer to the passed-in buffer
*
* NOTES: If this function fails (most likely due to low
* memory), the returned buffer will have a leading NULL
* so it is generally safe to use this without checking for
* failure.
*
********************************************************************/
LPSTR LoadSz(UINT idString, LPSTR lpszBuf,UINT cbBuf)
{
// Clear the buffer and load the string
if ( lpszBuf )
{
*lpszBuf = '\0';
LoadStringA( g_hInst, idString, lpszBuf, cbBuf );
}
return lpszBuf;
}
HRESULT CDownLoad::ProcessRequest(void)
{
LPBYTE pbData = NULL, pBoundary = NULL;
DWORD dwLen = 0;
HFIND hFindBoundary = NULL;
LPSTR pszDLFileName = NULL;
HANDLE hOutFile = INVALID_HANDLE_VALUE;
HRESULT hr = E_FAIL;
MyAssert(m_hRequest && m_pszBoundary);
MyAssert(m_pszBoundary[0]=='\r' && m_pszBoundary[1]=='\n');
MyAssert(m_pszBoundary[2]=='-' && m_pszBoundary[3]=='-');
// Buf Size must be greater than larget possible block of headers
// also must be greater than the OVERLAP, which must be greater
// than max size of MIME part boundary (70?)
MyAssert(DEFAULT_DATABUF_SIZE > OVERLAP_LEN);
MyAssert(OVERLAP_LEN > 80);
// init buffer & find-pattern
if(! (pbData = (LPBYTE)MyAlloc(DEFAULT_DATABUF_SIZE+SLOP)))
{
hr = E_OUTOFMEMORY;
goto error;
}
hFindBoundary = SetFindPattern(m_pszBoundary);
// find first boundary. If not in first blob, we have too much
// white-space. Discard & try again (everything before the first
// boundary is discardable)
for(pBoundary=NULL; !pBoundary; )
{
if(!(dwLen = FillBuffer(pbData, DEFAULT_DATABUF_SIZE, 0)))
goto iNetError;
pBoundary = (LPBYTE)Find(hFindBoundary, (LPSTR)pbData, dwLen);
}
for(;;)
{
MyAssert(pBoundary && pbData && dwLen);
MyAssert(pBoundary>=pbData && (pBoundary+m_dwBoundaryLen)<=(pbData+dwLen));
// move remaining data to front of buffer & refill.
if(!(dwLen = MoveAndFillBuffer(pbData, DEFAULT_DATABUF_SIZE, dwLen, pBoundary+m_dwBoundaryLen)))
goto iNetError;
pBoundary = NULL;
// look for trailing -- after boundary to indicate end of last part
if(pbData[0]=='-' && pbData[1]=='-')
break;
// skip leading CRLF (alway one after boundary)
MyAssert(pbData[0]=='\r' && pbData[1]=='\n');
// reads headers and skips everything until doubleCRLF. assumes all
// headers fit in the single buffer. Pass in pbData+2 to skip
// leading CRLF. Return value is ptr to first byte past the dbl crlf
LPSTR pszFile = NULL;
BOOL fInline = FALSE;
LPBYTE pbNext = (LPBYTE)ParseHeaders((LPSTR)pbData+2, NULL, &pszFile, &fInline);
if(!pszFile || !pbNext)
{
hr = ERROR_INVALID_DATA;
goto error;
}
//
// Make a copy of the file name - will be used
// for displaying error message
//
pszDLFileName = (LPSTR) MyAlloc((lstrlenA(pszFile) + 1) * sizeof(CHAR));
lstrcpyA(pszDLFileName, pszFile);
// Handle filename: (1) get full path after macro substituition.
// (2) Free pszFile string. (3) save the file path & inline/attach info
// internally for later handling (4) Create file on disk &return HANDLE
if(hr = HandleDLFile(pszFile, fInline, &hOutFile))
goto error;
// move remaining data (after headers) to front of buffer & refill.
dwLen = MoveAndFillBuffer(pbData, DEFAULT_DATABUF_SIZE, dwLen, pbNext);
pBoundary = NULL;
MyAssert(dwLen);
while(dwLen)
{
DWORD dwWriteLen = 0;
DWORD dwTemp = 0;
// look for boundary. careful of boundary cut across
// blocks. Overlapping blocks by 100 bytes to cover this case.
if(pBoundary = (LPBYTE)Find(hFindBoundary, (LPSTR)pbData, dwLen))
dwWriteLen = (DWORD)(pBoundary - pbData);
else if(dwLen > OVERLAP_LEN)
dwWriteLen = dwLen-OVERLAP_LEN;
else
dwWriteLen = dwLen;
MyAssert(dwWriteLen <= dwLen);
MyAssert(hOutFile != INVALID_HANDLE_VALUE);
if(dwWriteLen)
{
dwTemp = 0;
if(!WriteFile(hOutFile, pbData, dwWriteLen, &dwTemp, NULL)
|| dwTemp!=dwWriteLen)
{
hr = GetLastError();
//
// If we are out of diskspace, get the drive letter
// and display an out of diskspace message
//
goto error;
}
}
if(pBoundary)
break;
// move remaining data (after last byte written) to front of buffer & refill
dwLen = MoveAndFillBuffer(pbData, DEFAULT_DATABUF_SIZE, dwLen, pbData+dwWriteLen);
}
// *truncate* file & close
MyAssert(hOutFile != INVALID_HANDLE_VALUE);
SetEndOfFile(hOutFile);
// close file
CloseHandle(hOutFile);
hOutFile = INVALID_HANDLE_VALUE;
if (NULL != pszDLFileName)
{
MyFree(pszDLFileName);
pszDLFileName = NULL;
}
if(!pBoundary)
{
MyAssert(dwLen==0); // can only get here on dwLen==0 or found boundary
goto iNetError;
}
// at start of loop we'll discard everything upto and including the boundary
// if we loop back with pBoundary==NULL, we'll GPF
}
return ERROR_SUCCESS;
iNetError:
hr = InternetGetLastError(m_hSession);
if(!hr)
hr = ERROR_INVALID_DATA;
// goto error;
// fall through
error:
if(pbData) MyFree(pbData);
if(hFindBoundary) FreeFindPattern(hFindBoundary);
if (NULL != pszDLFileName)
{
MyFree(pszDLFileName);
pszDLFileName = NULL;
}
if (hOutFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hOutFile);
hOutFile = INVALID_HANDLE_VALUE;
}
return hr;
}
HRESULT HandleExe(LPSTR pszPath, HANDLE hCancelSemaphore)
{
MyAssert( hCancelSemaphore );
struct _STARTUPINFOA si;
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
if(!CreateProcessA(pszPath, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
return GetLastError();
else
{
HANDLE lpHandles[2] = {hCancelSemaphore, pi.hProcess};
DWORD dwRet = 0xDEAF;
MyDprintf("ICWDL: HandleExe about to wait....\n");
//
// 5/23/97 jmazner Olympus #4652
// sit here and wait until either
// 1) the process we launched terminates, or
// 2) the user tells us to cancel
//
dwRet = WaitForMultipleObjects( 2, lpHandles, FALSE, INFINITE );
MyDprintf("ICWDL: ....HandleExe done waiting -- %s was signalled\n",
(0==(dwRet - WAIT_OBJECT_0))?"hCancelSemaphore":"pi.hProcess");
// should we try to kill the process here??
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return NO_ERROR;
}
}
HRESULT HandleReg(LPSTR pszPath, HANDLE hCancelSemaphore)
{
struct _STARTUPINFOA si;
PROCESS_INFORMATION pi;
CHAR szCmd[MAX_PATH + 1];
MyAssert( pszPath );
MyAssert( hCancelSemaphore );
// 11/20/96 jmazner Normandy #5272
// Wrap quotes around pszPath in a directory or file name includes a space.
lstrcpyA(szCmd, REGEDIT_CMD);
if( '\"' != pszPath[0] )
{
// add 2 for two quotes
MyAssert( (lstrlenA(REGEDIT_CMD) + lstrlenA(pszPath)) < MAX_PATH );
lstrcatA(szCmd, "\"");
lstrcatA(szCmd, pszPath);
int i = lstrlenA(szCmd);
szCmd[i] = '\"';
szCmd[i+1] = '\0';
}
else
{
MyAssert( (lstrlenA(REGEDIT_CMD) + lstrlenA(pszPath)) < MAX_PATH );
lstrcatA(szCmd, pszPath);
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
if(!CreateProcessA(NULL, szCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
return GetLastError();
else
{
// HRESULT hr = (WaitAndKillRegeditWindow(10) ? NO_ERROR : E_FAIL);
HANDLE lpHandles[2] = {hCancelSemaphore, pi.hProcess};
DWORD dwRet = 0xDEAF;
MyDprintf("ICWDL: HandleReg about to wait....\n");
//
// 5/23/97 jmazner Olympus #4652
// sit here and wait until either
// 1) the process we launched terminates, or
// 2) the user tells us to cancel
//
dwRet = WaitForMultipleObjects( 2, lpHandles, FALSE, INFINITE );
MyDprintf("ICWDL: ....HandleReg done waiting -- %s was signalled\n",
(0==(dwRet - WAIT_OBJECT_0))?"hCancelSemaphore":"pi.hProcess");
// should we try to kill the process here??
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return ERROR_SUCCESS;
}
}
HRESULT HandleInf(LPSTR pszPath, HANDLE hCancelSemaphore)
{
CHAR szCmd[MAX_PATH + 1];
MyAssert( pszPath );
MyAssert( hCancelSemaphore );
// add 2 for two quotes,
// subtract 70 for approximate length of string in sprintf
MyAssert( (lstrlenA(pszPath) - 70 + 2) < MAX_PATH );
// 11/20/96 jmazner Normandy #5272
// wrap pszPath in quotes in case it includes a space
if( '\"' != pszPath[0] )
{
wsprintfA(szCmd, "rundll setupx.dll, InstallHinfSection DefaultInstall 128 \"%s", pszPath);
int i = lstrlenA(szCmd);
szCmd[i] = '\"';
szCmd[i+1] = '\0';
}
else
{
wsprintfA(szCmd, "rundll setupx.dll, InstallHinfSection DefaultInstall 128 %s", pszPath);
}
struct _STARTUPINFOA si;
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
if(!CreateProcessA(NULL, szCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
return GetLastError();
else
{
HANDLE lpHandles[2] = {hCancelSemaphore, pi.hProcess};
DWORD dwRet = 0xDEAF;
MyDprintf("ICWDL: HandleInf about to wait....\n");
//
// 5/23/97 jmazner Olympus #4652
// sit here and wait until either
// 1) the process we launched terminates, or
// 2) the user tells us to cancel
//
dwRet = WaitForMultipleObjects( 2, lpHandles, FALSE, INFINITE );
MyDprintf("ICWDL: ....HandleInf done waiting -- %s was signalled\n",
(0==(dwRet - WAIT_OBJECT_0))?"hCancelSemaphore":"pi.hProcess");
// should we try to kill the process here??
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return NO_ERROR;
}
}
#define STR_BSTR 0
#define STR_OLESTR 1
#ifdef UNICODE
#define BSTRFROMANSI(x) (BSTR)(x)
#define OLESTRFROMANSI(x) (LPCOLESTR)(x)
#else
#define BSTRFROMANSI(x) (BSTR)MakeWideStrFromAnsi((LPSTR)(x), STR_BSTR)
#define OLESTRFROMANSI(x) (LPCOLESTR)MakeWideStrFromAnsi((LPSTR)(x), STR_OLESTR)
#endif
#define TO_ASCII(x) (CHAR)((unsigned char)x + 0x30)
// Get the URL location from the .URL file, and send it to the progress dude
HRESULT CDownLoad::HandleURL(LPSTR pszPath)
{
MyAssert( pszPath );
// Data for CALLBACK_TYPE_URL is a wide string.
LPWSTR pszURL;
// Create a IUniformResourceLocator object
IUniformResourceLocator * pURL;
if (SUCCEEDED(CoCreateInstance(CLSID_InternetShortcut,
NULL,
CLSCTX_INPROC_SERVER,
IID_IUniformResourceLocator,
(LPVOID*)&pURL)))
{
// Get a persist file interface
IPersistFile *ppf;
if (SUCCEEDED(pURL->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf)))
{
// Attempt to connect the storage of the IURL to the .URL file we
// downloaded
if (SUCCEEDED(ppf->Load(OLESTRFROMANSI(pszPath), STGM_READ)))
{
// OK, have the URL object give us the location
if (SUCCEEDED(pURL->GetURL(&pszURL)) && pszURL)
{
// Notify the callback about the URL location
(m_lpfnCB)(m_hRequest, m_lpCDialingDlg, CALLBACK_TYPE_URL, (LPVOID)pszURL, lstrlenW(pszURL));
// Free the allocated URL, since the callback made a copy of it
IMalloc* pMalloc;
HRESULT hres = SHGetMalloc(&pMalloc);
if (SUCCEEDED(hres))
{
pMalloc->Free(pszURL);
pMalloc->Release();
}
}
}
// Release the persist file interface
ppf->Release();
}
// release the URL object
pURL->Release();
}
return(NO_ERROR);
}
#define PHONEBOOK_LIBRARY "ICWPHBK.DLL"
#define PHBK_LOADAPI "PhoneBookLoad"
#define PHBK_MERGEAPI "PhoneBookMergeChanges"
#define PHBK_UNLOADAPI "PhoneBookUnload"
#define PHONEBOOK_SUFFIX ".PHB"
typedef HRESULT (CALLBACK* PFNPHONEBOOKLOAD)(LPCSTR pszISPCode, DWORD *pdwPhoneID);
typedef HRESULT (CALLBACK* PFNPHONEBOOKMERGE)(DWORD dwPhoneID, LPSTR pszFileName);
typedef HRESULT (CALLBACK *PFNPHONEBOOKUNLOAD) (DWORD dwPhoneID);
HRESULT HandleChg(LPSTR pszPath)
{
CHAR szPhoneBookPath[MAX_PATH+1];
CHAR *p;
LPSTR szFilePart;
HRESULT hr = ERROR_FILE_NOT_FOUND;
HINSTANCE hPHBKDLL = NULL;
FARPROC fp;
DWORD dwPhoneBook;
lstrcpyA(szPhoneBookPath, pszPath);
if (lstrlenA(szPhoneBookPath) > 4)
{
p = &(szPhoneBookPath[lstrlenA(szPhoneBookPath)-4]);
} else {
hr = ERROR_INVALID_PARAMETER;
goto HandleChgExit;
}
lstrcpyA(p, PHONEBOOK_SUFFIX);
while (*p != '\\' && p > &szPhoneBookPath[0])
p--;
p++;
if(!SearchPathA(NULL, p,NULL,MAX_PATH,szPhoneBookPath,&szFilePart))
{
hr = GetLastError();
goto HandleChgExit;
}
hPHBKDLL = LoadLibraryA(PHONEBOOK_LIBRARY);
if (!hPHBKDLL)
{
hr = GetLastError();
goto HandleChgExit;
}
fp = GetProcAddress(hPHBKDLL, PHBK_LOADAPI);
if (!fp)
{
hr = GetLastError();
goto HandleChgExit;
}
hr = ((PFNPHONEBOOKLOAD)fp)(pszPath, &dwPhoneBook);
if(hr != ERROR_SUCCESS)
goto HandleChgExit;
fp = GetProcAddress(hPHBKDLL, PHBK_MERGEAPI);
if (!fp)
{
hr = GetLastError();
goto HandleChgExit;
}
hr = ((PFNPHONEBOOKMERGE)fp)(dwPhoneBook, pszPath);
fp = GetProcAddress(hPHBKDLL, PHBK_UNLOADAPI);
if (!fp)
{
hr = GetLastError();
goto HandleChgExit;
}
((PFNPHONEBOOKUNLOAD)fp)(dwPhoneBook);
HandleChgExit:
return hr;
}
HRESULT HandleOthers(LPSTR pszPath)
{
DWORD_PTR dwErr;
CHAR szCmd[MAX_PATH + 1];
MyAssert( pszPath );
// 11/20/96 jmazner Normandy #5272
// Wrap quotes around pszPath in case it includes a space.
// add 2 for two quotes
MyAssert( (lstrlenA(pszPath) + 2) < MAX_PATH );
if( '\"' != pszPath[0] )
{
lstrcpyA(szCmd, "\"");
lstrcatA(szCmd, pszPath);
int i = lstrlenA(szCmd);
szCmd[i] = '\"';
szCmd[i+1] = '\0';
}
else
{
lstrcpyA(szCmd, pszPath);
}
if((dwErr=(DWORD_PTR)ShellExecuteA(NULL, NULL, szCmd, NULL, NULL, SW_SHOWNORMAL)) < 32)
return (DWORD)dwErr;
else
return ERROR_SUCCESS;
}
LPSTR GetExtension(LPSTR pszPath)
{
LPSTR pszRet = strrchr(pszPath, '.');
if(pszRet)
return pszRet+1;
else
return NULL;
}
// Normandy 12093 - ChrisK 12/3/96
// return the error code for the first error that occurs while processing a file,
// but don't stop processing files.
//
HRESULT CDownLoad::Process(void)
{
HRESULT hr;
HRESULT hrProcess = ERROR_SUCCESS;
LPSTR pszExt;
CFileInfo *pfi;
for(pfi=m_pfiHead; pfi; pfi=pfi->m_pfiNext)
{
// Normandy 12093 - ChrisK 12/3/96
hr = ERROR_SUCCESS;
if(pfi->m_fInline)
{
pszExt = GetExtension(pfi->m_pszPath);
if(!pszExt)
continue;
if (lstrcmpiA(pszExt, EXT_CAB)==0)
hr = HandleCab(pfi->m_pszPath);
else if(lstrcmpiA(pszExt, EXT_EXE)==0)
hr = HandleExe(pfi->m_pszPath, m_hCancelSemaphore);
else if(lstrcmpiA(pszExt, EXT_REG)==0)
hr = HandleReg(pfi->m_pszPath, m_hCancelSemaphore);
else if(lstrcmpiA(pszExt, EXT_CHG)==0)
hr = HandleChg(pfi->m_pszPath);
else if(lstrcmpiA(pszExt, EXT_INF)==0)
hr = HandleInf(pfi->m_pszPath, m_hCancelSemaphore);
else if(lstrcmpiA(pszExt, EXT_URL)==0)
hr = HandleURL(pfi->m_pszPath);
else
hr = HandleOthers(pfi->m_pszPath);
// Normandy 12093 - ChrisK 12/3/96
if ((ERROR_SUCCESS == hrProcess) && (ERROR_SUCCESS != hr))
hrProcess = hr;
}
}
// Normandy 12093 - ChrisK 12/3/96
return hrProcess;
}
HRESULT CDownLoad::SetStatusCallback (INTERNET_STATUS_CALLBACK lpfnCB)
{
HRESULT hr;
hr = ERROR_SUCCESS;
if (!lpfnCB)
{
hr = ERROR_INVALID_PARAMETER;
} else {
m_lpfnCB = lpfnCB;
}
return hr;
}
#ifdef DEBUG
extern "C" HRESULT WINAPI DLTest(LPSTR pszURL)
{
CDownLoad* pdl = new CDownLoad(pszURL);
HRESULT hr = pdl->Execute();
if(hr) goto done;
hr = pdl->Process();
done:
delete pdl;
return hr;
}
#endif //DEBUG
HRESULT WINAPI DownLoadInit(LPWSTR wszURL, DWORD FAR *lpCDialingDlg, DWORD_PTR FAR *pdwDownLoad, HWND hWndMain)
{
g_hWndMain = hWndMain;
HRESULT hr = ERROR_NOT_ENOUGH_MEMORY;
int _convert;
LPSTR lpa = NULL;
CDownLoad* pdl;
_convert = WideCharToMultiByte(CP_ACP, 0, wszURL, -1, lpa, 0, NULL, NULL);
if (_convert == 0) goto DownLoadInitExit;
lpa = new char[_convert];
if (lpa == NULL) goto DownLoadInitExit;
if (WideCharToMultiByte(CP_ACP, 0, wszURL, -1, lpa, _convert, NULL, NULL) == 0) goto DownLoadInitExit;
pdl = new CDownLoad(lpa);
if (!pdl) goto DownLoadInitExit;
*pdwDownLoad = (DWORD_PTR)pdl;
//
// 5/27/97 jmazner Olympus #4579
//
pdl->m_lpCDialingDlg = (DWORD_PTR)lpCDialingDlg;
hr = ERROR_SUCCESS;
//
// 5/23/97 jmazner Olympus #4652
// create a semaphore in non-signaled state. If we ever get a downLoadCancel, we
// should signal the semaphore, and any waiting threads should notice that and bail out.
//
pdl->m_hCancelSemaphore = CreateSemaphoreA( NULL, 0, 1, "ICWDL DownloadCancel Semaphore" );
if( !pdl->m_hCancelSemaphore || (ERROR_ALREADY_EXISTS == GetLastError()) )
{
MyDprintf("ICWDL: Unable to create CancelSemaphore!!\n");
hr = ERROR_ALREADY_EXISTS;
}
DownLoadInitExit:
if (lpa != NULL)
{
delete [] lpa;
}
return hr;
}
HRESULT WINAPI DownLoadCancel(DWORD_PTR dwDownLoad)
{
MyDprintf("ICWDL: DownLoadCancel called\n");
if (dwDownLoad)
{
MyDprintf("ICWDL: DownLoadCancel releasing m_hCancelSemaphore\n");
MyAssert( ((CDownLoad*)dwDownLoad)->m_hCancelSemaphore );
ReleaseSemaphore( ((CDownLoad*)dwDownLoad)->m_hCancelSemaphore, 1, NULL );
((CDownLoad*)dwDownLoad)->Cancel();
return ERROR_SUCCESS;
}
else
{
return ERROR_INVALID_PARAMETER;
}
}
HRESULT WINAPI DownLoadExecute(DWORD_PTR dwDownLoad)
{
if (dwDownLoad)
{
return ((CDownLoad*)dwDownLoad)->Execute();
}
else
{
return ERROR_INVALID_PARAMETER;
}
}
HRESULT WINAPI DownLoadClose(DWORD_PTR dwDownLoad)
{
MyDprintf("ICWDL: DownLoadClose called \n");
if (dwDownLoad)
{
// be good and cancel any downloads that are in progress
((CDownLoad*)dwDownLoad)->Cancel();
delete ((CDownLoad*)dwDownLoad);
return ERROR_SUCCESS;
}
else
{
return ERROR_INVALID_PARAMETER;
}
}
HRESULT WINAPI DownLoadSetStatusCallback
(
DWORD_PTR dwDownLoad,
INTERNET_STATUS_CALLBACK lpfnCB
)
{
if (dwDownLoad)
{
return ((CDownLoad*)dwDownLoad)->SetStatusCallback(lpfnCB);
}
else
{
return ERROR_INVALID_PARAMETER;
}
}
HRESULT WINAPI DownLoadProcess(DWORD_PTR dwDownLoad)
{
MyDprintf("ICWDL: DownLoadProcess\n");
if (dwDownLoad)
{
return ((CDownLoad*)dwDownLoad)->Process();
}
else
{
return ERROR_INVALID_PARAMETER;
}
}