windows-nt/Source/XPSP1/NT/base/ntsetup/win95upg/tools/ftp/ftp.c
2020-09-26 16:20:57 +08:00

1014 lines
22 KiB
C

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
TODO: cmntool.c
Abstract:
<TODO: fill in abstract>
Author:
TODO: <full name> (<alias>) <date>
Revision History:
<full name> (<alias>) <date> <comments>
--*/
#include "pch.h"
#include "resource.h"
#include <wininet.h>
typedef enum {
DOWNLOAD_CONNECTING,
DOWNLOAD_GETTING_FILE,
DOWNLOAD_DISCONNECTING
} DOWNLOADSTATE;
typedef HINTERNET (WINAPI * INTERNETOPEN) (
IN LPCSTR lpszAgent,
IN DWORD dwAccessType,
IN LPCSTR lpszProxyName,
IN LPCSTR lpszProxyBypass,
IN DWORD dwFlags
);
typedef BOOL (WINAPI * INTERNETCLOSEHANDLE) (
IN HINTERNET Handle
);
typedef HINTERNET (WINAPI * INTERNETOPENURL) (
IN HINTERNET hInternetSession,
IN LPCSTR lpszUrl,
IN LPCSTR lpszHeaders,
IN DWORD dwHeadersLength,
IN DWORD dwFlags,
IN DWORD dwContext
);
typedef BOOL (WINAPI * INTERNETREADFILE) (
IN HINTERNET hFile,
IN LPVOID lpBuffer,
IN DWORD dwNumberOfBytesToRead,
OUT LPDWORD lpNumberOfBytesRead
);
typedef BOOL (WINAPI * INTERNETCANONICALIZEURLA) (
IN LPCSTR lpszUrl,
OUT LPSTR lpszBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN DWORD dwFlags
);
typedef DWORD (WINAPI * INTERNETSETFILEPOINTER) (
IN HINTERNET hFile,
IN LONG lDistanceToMove,
IN PVOID pReserved,
IN DWORD dwMoveMethod,
IN DWORD dwContext
);
typedef BOOL (WINAPI * INTERNETHANGUP) (
IN DWORD dwConnection,
IN DWORD dwReserved
);
typedef DWORD (WINAPI * INTERNETDIALA) (
IN HWND hwndParent,
IN PCSTR lpszConnectoid,
IN DWORD dwFlags,
OUT LPDWORD lpdwConnection,
IN DWORD dwReserved
);
static HINSTANCE g_Lib;
static INTERNETOPEN g_InternetOpenA;
static INTERNETCLOSEHANDLE g_InternetCloseHandle;
static INTERNETOPENURL g_InternetOpenUrlA;
static INTERNETREADFILE g_InternetReadFile;
static INTERNETCANONICALIZEURLA g_InternetCanonicalizeUrlA;
static INTERNETSETFILEPOINTER g_InternetSetFilePointer;
static INTERNETDIALA g_InternetDialA;
static INTERNETHANGUP g_InternetHangUp;
static BOOL g_Dialed;
static DWORD g_Cxn;
HANDLE g_hHeap;
HINSTANCE g_hInst;
BOOL WINAPI MigUtil_Entry (HINSTANCE, DWORD, PVOID);
PCSTR g_AppName = TEXT("FTP Download Engine");
//PCSTR g_DirFile = TEXT("ftp://jimschm-dev/upgdir.inf");
PCSTR g_DirFile = TEXT("file://popcorn/public/jimschm/upgdir.inf");
BOOL
DownloadUpdates (
HANDLE CancelEvent, OPTIONAL
HANDLE WantToRetryEvent, OPTIONAL
HANDLE OkToRetryEvent, OPTIONAL
PCSTR *Url OPTIONAL
);
BOOL
pDownloadUpdatesWithUi (
VOID
);
BOOL
pCallEntryPoints (
DWORD Reason
)
{
HINSTANCE Instance;
//
// Simulate DllMain
//
Instance = g_hInst;
//
// Initialize the common libs
//
if (!MigUtil_Entry (Instance, Reason, NULL)) {
return FALSE;
}
//
// TODO: Add others here if needed (don't forget to prototype above)
//
return TRUE;
}
BOOL
Init (
VOID
)
{
g_hHeap = GetProcessHeap();
g_hInst = GetModuleHandle (NULL);
return pCallEntryPoints (DLL_PROCESS_ATTACH);
}
VOID
Terminate (
VOID
)
{
pCallEntryPoints (DLL_PROCESS_DETACH);
}
VOID
HelpAndExit (
VOID
)
{
//
// This routine is called whenever command line args are wrong
//
_ftprintf (
stderr,
TEXT("Command Line Syntax:\n\n")
//
// TODO: Describe command line syntax(es), indent 2 spaces
//
TEXT(" cmntool [/F:file]\n")
TEXT("\nDescription:\n\n")
//
// TODO: Describe tool, indent 2 spaces
//
TEXT(" cmntool is a stub!\n")
TEXT("\nArguments:\n\n")
//
// TODO: Describe args, indent 2 spaces, say optional if necessary
//
TEXT(" /F Specifies optional file name\n")
);
exit (1);
}
INT
__cdecl
_tmain (
INT argc,
PCTSTR argv[]
)
{
INT i;
PCTSTR FileArg;
//
// TODO: Parse command line here
//
for (i = 1 ; i < argc ; i++) {
if (argv[i][0] == TEXT('/') || argv[i][0] == TEXT('-')) {
switch (_totlower (_tcsnextc (&argv[i][1]))) {
case TEXT('f'):
//
// Sample option - /f:file
//
if (argv[i][2] == TEXT(':')) {
FileArg = &argv[i][3];
} else if (i + 1 < argc) {
FileArg = argv[++i];
} else {
HelpAndExit();
}
break;
default:
HelpAndExit();
}
} else {
//
// Parse other args that don't require / or -
//
// None
HelpAndExit();
}
}
//
// Begin processing
//
if (!Init()) {
return 0;
}
//
// TODO: Do work here
//
pDownloadUpdatesWithUi();
//
// End of processing
//
Terminate();
return 0;
}
typedef struct {
HANDLE CancelEvent;
HANDLE WantToRetryEvent;
HANDLE OkToRetryEvent;
HANDLE CloseEvent;
PCSTR Url;
} EVENTSTRUCT, *PEVENTSTRUCT;
BOOL
CALLBACK
pUiDlgProc (
HWND hdlg,
UINT msg,
WPARAM wParam,
LPARAM lParam
)
{
static PEVENTSTRUCT eventStruct;
static UINT retries;
DWORD rc;
CHAR lastUrl[256];
CHAR text[256];
switch (msg) {
case WM_INITDIALOG:
eventStruct = (PEVENTSTRUCT) lParam;
retries = 0;
lastUrl[0] = 0;
SetTimer (hdlg, 1, 100, NULL);
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDCANCEL:
ShowWindow (GetDlgItem (hdlg, IDC_MSG2), SW_HIDE);
ShowWindow (GetDlgItem (hdlg, IDC_URL), SW_HIDE);
ShowWindow (GetDlgItem (hdlg, IDC_RETRIES), SW_HIDE);
SetDlgItemTextA (hdlg, IDC_MSG1, "Stopping download...");
SetFocus (GetDlgItem (hdlg, IDC_MSG1));
EnableWindow (GetDlgItem (hdlg, IDCANCEL), FALSE);
SetEvent (eventStruct->CancelEvent);
break;
}
break;
case WM_TIMER:
//
// Check the events
//
rc = WaitForSingleObject (eventStruct->WantToRetryEvent, 0);
if (rc == WAIT_OBJECT_0) {
//
// A download failed. Try again?
//
if (StringCompareA (lastUrl, eventStruct->Url)) {
retries = 0;
}
StackStringCopy (lastUrl, eventStruct->Url);
retries++;
if (retries > 5) {
//
// Too many retries -- give up!
//
SetEvent (eventStruct->CancelEvent);
} else {
//
// Retry
//
wsprintfA (text, "on attempt %u. Retrying.", retries);
SetDlgItemText (hdlg, IDC_RETRIES, text);
if (eventStruct->Url) {
SetDlgItemText (hdlg, IDC_URL, eventStruct->Url);
}
ShowWindow (GetDlgItem (hdlg, IDC_MSG2), SW_SHOW);
ShowWindow (GetDlgItem (hdlg, IDC_URL), SW_SHOW);
ShowWindow (GetDlgItem (hdlg, IDC_RETRIES), SW_SHOW);
SetEvent (eventStruct->OkToRetryEvent);
}
}
rc = WaitForSingleObject (eventStruct->CloseEvent, 0);
if (rc == WAIT_OBJECT_0) {
EndDialog (hdlg, IDCANCEL);
}
return TRUE;
case WM_DESTROY:
KillTimer (hdlg, 1);
break;
}
return FALSE;
}
DWORD
WINAPI
pUiThread (
PVOID Arg
)
{
DialogBoxParam (
g_hInst,
(PCTSTR) IDD_STATUS,
NULL,
pUiDlgProc,
(LPARAM) Arg
);
return 0;
}
HANDLE
pCreateUiThread (
PEVENTSTRUCT EventStruct
)
{
HANDLE h;
DWORD threadId;
h = CreateThread (NULL, 0, pUiThread, EventStruct, 0, &threadId);
return h;
}
BOOL
pDownloadUpdatesWithUi (
VOID
)
{
EVENTSTRUCT es;
BOOL b = FALSE;
HANDLE h;
//
// Create the events
//
es.CancelEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
es.WantToRetryEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
es.OkToRetryEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
es.CloseEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
es.Url = NULL;
if (!es.CancelEvent || !es.WantToRetryEvent ||
!es.OkToRetryEvent || !es.CloseEvent
) {
DEBUGMSG ((DBG_ERROR, "Can't create events"));
return FALSE;
}
//
// Start the UI
//
h = pCreateUiThread (&es);
if (!h) {
DEBUGMSG ((DBG_ERROR, "Can't create UI thread"));
} else {
//
// Perform the download
//
b = DownloadUpdates (
es.CancelEvent,
es.WantToRetryEvent,
es.OkToRetryEvent,
&es.Url
);
//
// End the UI
//
SetEvent (es.CloseEvent);
WaitForSingleObject (h, INFINITE);
}
//
// Cleanup & exit
//
CloseHandle (es.CancelEvent);
CloseHandle (es.WantToRetryEvent);
CloseHandle (es.OkToRetryEvent);
CloseHandle (es.CloseEvent);
return b;
}
BOOL
pOpenWinInetSupport (
VOID
)
{
g_Lib = LoadLibrary (TEXT("wininet.dll"));
if (!g_Lib) {
return FALSE;
}
(FARPROC) g_InternetOpenA = GetProcAddress (g_Lib, "InternetOpenA");
(FARPROC) g_InternetCloseHandle = GetProcAddress (g_Lib, "InternetCloseHandle");
(FARPROC) g_InternetOpenUrlA = GetProcAddress (g_Lib, "InternetOpenUrlA");
(FARPROC) g_InternetReadFile = GetProcAddress (g_Lib, "InternetReadFile");
(FARPROC) g_InternetCanonicalizeUrlA = GetProcAddress (g_Lib, "InternetCanonicalizeUrlA");
(FARPROC) g_InternetSetFilePointer = GetProcAddress (g_Lib, "InternetSetFilePointer");
(FARPROC) g_InternetHangUp = GetProcAddress (g_Lib, "InternetHangUp");
(FARPROC) g_InternetDialA = GetProcAddress (g_Lib, "InternetDialA");
if (!g_InternetOpenA || !g_InternetOpenUrlA || !g_InternetReadFile ||
!g_InternetCloseHandle || !g_InternetCanonicalizeUrlA ||
!g_InternetSetFilePointer || !g_InternetDialA || !g_InternetHangUp
) {
return FALSE;
}
return TRUE;
}
VOID
pCloseWinInetSupport (
VOID
)
{
FreeLibrary (g_Lib);
g_Lib = NULL;
g_InternetOpenA = NULL;
g_InternetOpenUrlA = NULL;
g_InternetReadFile = NULL;
g_InternetCloseHandle = NULL;
g_InternetCanonicalizeUrlA = NULL;
g_InternetSetFilePointer = NULL;
g_InternetDialA = NULL;
g_InternetHangUp = NULL;
}
BOOL
pDownloadFile (
HINTERNET Session,
PCSTR RemoteFileUrl,
PCSTR LocalFile,
HANDLE CancelEvent
)
{
HINTERNET connection;
PBYTE buffer = NULL;
UINT size = 65536;
DWORD bytesRead;
DWORD dontCare;
HANDLE file = INVALID_HANDLE_VALUE;
BOOL b = FALSE;
//
// Establish connection to the file
//
connection = g_InternetOpenUrlA (
Session,
RemoteFileUrl,
NULL,
0,
INTERNET_FLAG_RELOAD, //INTERNET_FLAG_NO_UI
0
);
if (!connection) {
DEBUGMSGA ((DBG_ERROR "Can't connect to %s", RemoteFileUrl));
return FALSE;
}
__try {
//
// Create the local file
//
file = CreateFileA (
LocalFile,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (file == INVALID_HANDLE_VALUE) {
DEBUGMSGA ((DBG_ERROR, "Can't create %s", LocalFile));
__leave;
}
//
// Allocate a big buffer for downloading
//
buffer = MemAlloc (g_hHeap, 0, size);
if (!buffer) {
__leave;
}
//
// Download the file
//
for (;;) {
if (WAIT_OBJECT_0 == WaitForSingleObject (CancelEvent, 0)) {
DEBUGMSG ((DBG_VERBOSE, "User cancellation detected"));
__leave;
}
if (!g_InternetReadFile (connection, buffer, size, &bytesRead)) {
DEBUGMSGA ((DBG_ERROR, "Error downloading %s", RemoteFileUrl));
__leave;
}
if (!bytesRead) {
break;
}
if (!WriteFile (file, buffer, bytesRead, &dontCare, NULL)) {
DEBUGMSGA ((DBG_ERROR, "Error writing to %s", LocalFile));
__leave;
}
}
b = TRUE;
}
__finally {
g_InternetCloseHandle (connection);
CloseHandle (file);
if (!b) {
DeleteFileA (LocalFile);
}
if (buffer) {
MemFree (g_hHeap, 0, buffer);
}
}
return b;
}
BOOL
pDownloadFileWithRetry (
IN HINTERNET Session,
IN PCSTR Url,
IN PCSTR DestFile,
IN HANDLE CancelEvent, OPTIONAL
IN HANDLE WantToRetryEvent, OPTIONAL
IN HANDLE OkToRetryEvent OPTIONAL
)
/*++
Routine Description:
pDownloadFileWithRetry downloads a URL to a local file, as specified by the
caller. If CancelEvent is specified, then the caller can stop the download
by setting the event.
This function implements a retry mechanism via events. If the caller
specifies WantToRetryEvent and OkToRetryEvent, then this routine will allow
the caller an opportunity to retry a failed download.
The retry protocol is as follows:
- Caller establishes a wait on WantToRetryEvent, then calls DownloadUpdates
- Error occurs downloading one of the files
- WantToRetryEvent is set by this routine
- Caller's wait wakes up
- Caller asks user if they want to retry
- Caller sets CancelEvent or OkToRetryEvent, depending on user choice
- This routine wakes up and either retries or aborts
The caller must create all three events as auto-reset events in the
non-signaled state.
Arguments:
Session - Specifies the handle to an open internet session.
Url - Specifies the URL to download.
DestFile - Specifies the local path to download the file to.
CancelEvent - Specifies the handle to a caller-owned event. When this
event is set, the function will return FALSE and
GetLastError will return ERROR_CANCELLED.
WantToRetryEvent - Specifies the caller-owned event that is set when a
download error occurs. The caller should be waiting on
this event before calling DownloadUpdates.
OkToRetry - Specifies the caller-owned event that will be set in
response to a user's request to retry.
Return Value:
TRUE if the file was downloaded, FALSE if the user decides to cancel the
download.
--*/
{
BOOL fail;
HANDLE waitArray[2];
DWORD rc;
//
// Loop until success, user decides to cancel, or user decides
// not to retry on error
//
for (;;) {
fail = FALSE;
if (!pDownloadFile (Session, Url, DestFile, CancelEvent)) {
fail = TRUE;
if (GetLastError() != ERROR_CANCELLED &&
CancelEvent && WantToRetryEvent && OkToRetryEvent
) {
//
// We set the WantToRetryEvent. The UI thread should
// be waiting on this. The UI thread will then ask
// the user if they want to retry or cancel. If the
// user wants to retry, the UI thread will set the
// OkToRetryEvent. If the user wants to cancel, the
// UI thread will set the CancelEvent.
//
SetEvent (WantToRetryEvent);
waitArray[0] = CancelEvent;
waitArray[1] = OkToRetryEvent;
rc = WaitForMultipleObjects (2, waitArray, FALSE, INFINITE);
if (rc == WAIT_OBJECT_0 + 1) {
continue;
}
//
// We fail
//
SetLastError (ERROR_CANCELLED);
}
}
break;
}
return !fail;
}
VOID
pGoOffline (
VOID
)
{
if (g_Dialed) {
g_Dialed = FALSE;
g_InternetHangUp (g_Cxn, 0);
}
}
BOOL
pGoOnline (
HINTERNET Session
)
{
HINTERNET connection;
if (g_Dialed) {
pGoOffline();
}
//
// Check if we are online
//
connection = g_InternetOpenUrlA (
Session,
"http://www.microsoft.com/",
NULL,
0,
INTERNET_FLAG_RELOAD,
0
);
if (connection) {
DEBUGMSG ((DBG_VERBOSE, "Able to connect to www.microsoft.com"));
g_InternetCloseHandle (connection);
return TRUE;
}
//
// Unable to contact www.microsoft.com. Possibilities:
//
// - net cable unplugged
// - firewall without a proxy
// - no online connection (i.e., need to dial ISP)
// - www.microsoft.com or some part of the Internet is down
// - user has no Internet access at all
//
// Try RAS, then try connection again.
//
g_InternetDialA (NULL, NULL, INTERNET_AUTODIAL_FORCE_ONLINE, &g_Cxn, 0);
g_Dialed = TRUE;
connection = g_InternetOpenUrlA (
Session,
"http://www.microsoft.com/",
NULL,
0,
INTERNET_FLAG_RELOAD,
0
);
if (connection) {
DEBUGMSG ((DBG_VERBOSE, "Able to connect to www.microsoft.com via RAS"));
g_InternetCloseHandle (connection);
return TRUE;
}
pGoOffline();
return FALSE;
}
BOOL
DownloadUpdates (
HANDLE CancelEvent, OPTIONAL
HANDLE WantToRetryEvent, OPTIONAL
HANDLE OkToRetryEvent, OPTIONAL
PCSTR *StatusUrl OPTIONAL
)
{
HINTERNET session;
CHAR url[MAX_PATH];
DWORD size;
BOOL b = FALSE;
CHAR tempPath[MAX_TCHAR_PATH];
CHAR dirFile[MAX_TCHAR_PATH];
HINF inf;
INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
PCSTR p;
PCSTR q;
if (!pOpenWinInetSupport()) {
DEBUGMSG ((DBG_ERROR, "Can't open wininet.dll"));
return FALSE;
}
__try {
session = g_InternetOpenA (
g_AppName,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0
);
if (!session) {
DEBUGMSG ((DBG_ERROR, "InternetOpen returned NULL"));
SetLastError (ERROR_NOT_CONNECTED);
__leave;
}
if (!pGoOnline (session)) {
DEBUGMSG ((DBG_ERROR, "Can't go online"));
SetLastError (ERROR_NOT_CONNECTED);
__leave;
}
size = ARRAYSIZE(url);
if (!g_InternetCanonicalizeUrlA (g_DirFile, url, &size, 0)) {
DEBUGMSGA ((DBG_ERROR, "Can't canonicalize %s", g_DirFile));
SetLastError (ERROR_CONNECTION_ABORTED);
__leave;
}
GetTempPathA (ARRAYSIZE(tempPath), tempPath);
GetTempFileNameA (tempPath, "ftp", 0, dirFile);
if (StatusUrl) {
*StatusUrl = url;
}
if (!pDownloadFileWithRetry (
session,
url,
dirFile,
CancelEvent,
WantToRetryEvent,
OkToRetryEvent
)) {
DEBUGMSGA ((DBG_ERROR, "Can't download %s", url));
// last error set to a valid return condition
__leave;
}
inf = InfOpenInfFileA (dirFile);
if (inf == INVALID_HANDLE_VALUE) {
DEBUGMSGA ((DBG_ERROR, "Can't open %s", dirFile));
// last error set to the reason of the INF failure
__leave;
}
__try {
if (InfFindFirstLineA (inf, "Win9xUpg", NULL, &is)) {
do {
p = InfGetStringFieldA (&is, 1);
q = InfGetStringFieldA (&is, 2);
if (!p || !q) {
continue;
}
q = ExpandEnvironmentTextA (q);
size = ARRAYSIZE(url);
if (!g_InternetCanonicalizeUrlA (p, url, &size, 0)) {
DEBUGMSGA ((DBG_ERROR, "Can't canonicalize INF-specified URL: %s", p));
SetLastError (ERROR_CONNECTION_ABORTED);
__leave;
}
if (!pDownloadFileWithRetry (
session,
url,
q,
CancelEvent,
WantToRetryEvent,
OkToRetryEvent
)) {
FreeTextA (q);
DEBUGMSGA ((DBG_ERROR, "Can't download INF-specified URL: %s", url));
// last error set to a valid return code
__leave;
}
FreeTextA (q);
} while (InfFindNextLine (&is));
}
}
__finally {
InfCloseInfFile (inf);
}
}
__finally {
if (session) {
g_InternetCloseHandle (session);
}
InfCleanUpInfStruct (&is);
DeleteFileA (dirFile);
pGoOffline();
pCloseWinInetSupport();
}
return b;
}