/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    ftpcat.c

Abstract:

    Windows Internet API FTP test program

    Provides the same functionality as a cut-down version of the venerable
    (console-mode) ftp program

Author:

    Richard L Firth (rfirth) 05-Jun-1995

Environment:

    Win32 user-mode console app

Revision History:

    05-Jun-1995 rfirth
        Created

--*/

#include "ftpcatp.h"

#undef tolower

#ifndef _CRTAPI1
#define _CRTAPI1
#endif

//
// macros
//

#define IS_ARG(c)   (((c) == '-') || ((c) == '/'))

//
// manifests
//

#define MAX_COMMAND_LENGTH 100

//
// external data
//

extern BOOL fQuit;

//
// data
//

BOOL Verbose = FALSE;
INTERNET_STATUS_CALLBACK PreviousCallback;
HINTERNET hCancel = NULL;
BOOL AsyncMode = FALSE;
DWORD Context = 0;
DWORD AsyncResult = 0;
DWORD AsyncError = 0;
HANDLE AsyncEvent = NULL;

#if DBG
BOOL CheckHandleLeak = FALSE;
#endif

//
// external functions
//

extern BOOL DispatchCommand(LPCTSTR, HANDLE);

//
// prototypes
//

void _CRTAPI1 main(int, char**);
void _CRTAPI1 control_c_handler(int);
void usage(void);
BOOL Prompt(LPCTSTR, LPTSTR*);

//
// functions
//

void _CRTAPI1 main(int argc, char** argv) {

    LPTSTR ptszSite = NULL;
    LPTSTR ptszUser = NULL;
    LPTSTR ptszPass = NULL;
    HINTERNET hInternet;
    HINTERNET hFtpSession;
    DWORD dwLocalAccessFlags;
    LPTSTR lpszCmd;
    DWORD lastError;
    DWORD bufLen;
    BOOL enableCallbacks = FALSE;
    DWORD flags = 0;
    DWORD accessMethod = PRE_CONFIG_INTERNET_ACCESS;
    BOOL expectingGatewayServer = FALSE;
    LPSTR gatewayServer = NULL;

    for (--argc, ++argv; argc; --argc, ++argv) {
        if (IS_ARG(**argv)) {
            switch (*++*argv) {
            case '?':
                usage();
                break;

            case 'a':
                ++*argv;
                if (**argv == 'g') {
                    accessMethod = GATEWAY_INTERNET_ACCESS;
                    if (*++*argv) {
                        gatewayServer = *argv;
                    } else {
                        expectingGatewayServer = TRUE;
                    }
                } else if ((**argv == 'l') || (**argv == 'd')) {
                    accessMethod = LOCAL_INTERNET_ACCESS;
                } else if (**argv == 'p') {
                    accessMethod = CERN_PROXY_INTERNET_ACCESS;
                } else {
                    printf("error: unrecognised access type: '%c'\n", **argv);
                    usage();
                }
                break;

            case 'c':
                enableCallbacks = TRUE;
                break;

#if DBG
            case 'l':
                CheckHandleLeak = TRUE;
                break;
#endif

            case 'p':
                flags |= INTERNET_FLAG_PASSIVE;
                break;

            case 'v':
                Verbose = TRUE;
                break;

            case 'x':
                Context = (DWORD)atoi(++*argv);
                break;

            case 'y':
                AsyncMode = TRUE;
                break;

            default:
                printf("error: unrecognized command line flag: '%c'\n", **argv);
                usage();
            }
        } else if (expectingGatewayServer) {
            gatewayServer = *argv;
            expectingGatewayServer = FALSE;
        } else if (ptszSite == NULL) {
            ptszSite = *argv;
        } else if (ptszUser == NULL) {
            ptszUser = *argv;
        } else if (ptszPass == NULL) {
            ptszPass = *argv;
        } else {
            printf("error: unrecognized command line argument: \"%s\"\n", *argv);
            usage();
        }
    }

    if (ptszSite == NULL) {
        printf("error: required server name argument missing\n");
        exit(1);
    }

    if (AsyncMode) {

        //
        // create auto-reset, initially unsignalled event
        //

        AsyncEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (AsyncEvent == NULL) {
            print_error("ftpcat", "CreateEvent()");
            exit(1);
        }
    }

    //
    // add a control-c handler
    //

    signal(SIGINT, control_c_handler);

#if DBG
    if (CheckHandleLeak) {
        printf("initial handle count = %d\n", GetProcessHandleCount());
    }
#endif

#if DBG
    if (CheckHandleLeak && Verbose) {
        printf("Initial handle count = %d\n", GetProcessHandleCount());
    }
#endif

    if (Verbose) {
        printf("calling InternetOpen()...\n");
    }
    hInternet = InternetOpen("ftpcat",
                             accessMethod,
                             gatewayServer,
                             0,
                             AsyncMode ? INTERNET_FLAG_ASYNC : 0
                             );
    if (hInternet == NULL) {
        print_error("ftpcat", "InternetOpen()");
        exit(1);
    } else if (Verbose) {
        printf("Internet handle = %x\n", hInternet);
    }

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetOpen(): handle count = %d\n", GetProcessHandleCount());
    }
#endif

    if (enableCallbacks) {

        //
        // let's have a status callback
        //
        // Note that call-backs can be set even before we have opened a handle
        // to the internet/gateway
        //

        PreviousCallback = InternetSetStatusCallback(hInternet, my_callback);
        if (PreviousCallback == INTERNET_INVALID_STATUS_CALLBACK) {
            print_error("ftpcat", "InternetSetStatusCallback()");
        } else if (Verbose) {
            printf("previous Internet callback = %x\n", PreviousCallback);
        }
    }

    if (Verbose) {
        printf("calling InternetConnect()...\n");
    }
    hFtpSession = InternetConnect(hInternet,
                                  ptszSite,
                                  0,
                                  ptszUser,
                                  ptszPass,
                                  INTERNET_SERVICE_FTP,
                                  flags,
                                  FTPCAT_CONNECT_CONTEXT
                                  );
    if ((hFtpSession == NULL) && AsyncMode) {
        if (Verbose) {
            printf("waiting for async InternetConnect()...\n");
        }
        WaitForSingleObject(AsyncEvent, INFINITE);
        hFtpSession = (HINTERNET)AsyncResult;
        SetLastError(AsyncError);
    }
    if (hFtpSession == NULL) {
        if (AsyncMode) {
            SetLastError(AsyncError);
        }
        print_error("ftpcat",
                    "%sInternetConnect()",
                    AsyncMode ? "async " : ""
                    );
        get_response();
        close_handle(hInternet);
        exit(1);
    } else if (Verbose) {
        printf("FTP session handle = %x\n", hFtpSession);
    }

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetConnect(): handle count = %d\n", GetProcessHandleCount());
    }
#endif

    printf("Connected to %s.\n", ptszSite);

    get_response();

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetGetLastResponseInfo(): handle count = %d\n", GetProcessHandleCount());
    }
#endif

    //
    // set the (top level) cancellable handle
    //

    hCancel = hFtpSession;

    while (!fQuit) {
        if (Prompt(TEXT("ftp> "), &lpszCmd)) {
            DispatchCommand(lpszCmd, hFtpSession);
        }
    }

    printf("Closing %x\n", hFtpSession);

    close_handle(hFtpSession);

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetCloseHandle(): handle count = %d\n", GetProcessHandleCount());
    }
#endif

    get_response();

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetGetLastResponseInfo(): handle count = %d\n", GetProcessHandleCount());
    }
#endif

    close_handle(hInternet);

#if DBG
    if (CheckHandleLeak) {
        printf("after InternetCloseHandle(): handle count = %d\n", GetProcessHandleCount());
    }

    if (CheckHandleLeak && Verbose) {
        printf("Final handle count = %d\n", GetProcessHandleCount());
    }
#endif

    exit(0);
}

void _CRTAPI1 control_c_handler(int sig) {

    //
    // disable signals
    //

    signal(SIGINT, SIG_IGN);

    //
    // cancel the current operation
    //

    printf("control-c handler\n");
    if (hCancel == NULL) {

        char ch;

        printf("control-c handler: no Internet operation in progress\n");
    } else {
        close_handle(hCancel);
    }

    //
    // re-enable this signal handler
    //

    signal(SIGINT, control_c_handler);
}

void usage() {
    printf("\n"
           "usage: ftpcat [-a{g{[ ]gateway server}|l|d|p}] [-c] [-d] [-p] [-v] [-x#] [-y]\n"
           "              {servername} [username] [password]\n"
           "\n"
           "where: -a = access type. Default is pre-configured:\n"
           "            g = gateway access via <gateway server>\n"
           "            l = local internet access\n"
           "            d = local internet access\n"
           "            p = CERN proxy access\n"
           "       -c = Enable call-backs\n"
           "       -p = Use Passive transfer mode\n"
           "       -v = Verbose mode. Default is off\n"
           "       -x = Set context value\n"
           "       -y = Asynchronous APIs\n"
           );
    exit(1);
}

BOOL
Prompt(
    IN LPCTSTR pszPrompt,
    OUT LPTSTR  *ppszCommand
    )
{
    static CHAR Command[MAX_COMMAND_LENGTH + sizeof(TEXT('\0'))];

#ifdef UNICODE

    static WCHAR wchBuf[MAX_COMMAND_LENGTH + sizeof(L'\0')];

#endif

    HANDLE hStdin;
    DWORD dwBytesRead;
    PTCHAR pch;

    lprintf(TEXT("%s"), pszPrompt);

    hStdin = GetStdHandle(STD_INPUT_HANDLE);

    if (!ReadFile(hStdin,
                  Command,
                  MAX_COMMAND_LENGTH * sizeof(CHAR),
                  &dwBytesRead,
                  NULL)) {
        return FALSE;
    }

    Command[dwBytesRead] = '\0';

#ifdef UNICODE

    wsprintf(wchBuf, L"%S", Command);
    *ppszCommand = wchBuf;

#else

    *ppszCommand = Command;

#endif

    pch = lstrchr(*ppszCommand, TEXT('\r'));

    if (pch) {
        *pch = TEXT('\0');
    }

    return TRUE;
}

VOID
my_callback(
    HINTERNET Handle,
    DWORD Context,
    DWORD Status,
    LPVOID Info,
    DWORD Length
    )
{
    char* type$;

    switch (Status) {
    case INTERNET_STATUS_RESOLVING_NAME:
        type$ = "RESOLVING NAME";
        break;

    case INTERNET_STATUS_NAME_RESOLVED:
        type$ = "NAME RESOLVED";
        break;

    case INTERNET_STATUS_CONNECTING_TO_SERVER:
        type$ = "CONNECTING TO SERVER";
        break;

    case INTERNET_STATUS_CONNECTED_TO_SERVER:
        type$ = "CONNECTED TO SERVER";
        break;

    case INTERNET_STATUS_SENDING_REQUEST:
        type$ = "SENDING REQUEST";
        break;

    case INTERNET_STATUS_REQUEST_SENT:
        type$ = "REQUEST SENT";
        break;

    case INTERNET_STATUS_RECEIVING_RESPONSE:
        type$ = "RECEIVING RESPONSE";
        break;

    case INTERNET_STATUS_RESPONSE_RECEIVED:
        type$ = "RESPONSE RECEIVED";
        break;

    case INTERNET_STATUS_CLOSING_CONNECTION:
        type$ = "CLOSING CONNECTION";
        break;

    case INTERNET_STATUS_CONNECTION_CLOSED:
        type$ = "CONNECTION CLOSED";
        break;

    case INTERNET_STATUS_HANDLE_CREATED:
        type$ = "HANDLE CREATED";
        hCancel = *(LPHINTERNET)Info;
        break;

    case INTERNET_STATUS_REQUEST_COMPLETE:
        type$ = "REQUEST COMPLETE";
        AsyncResult = ((LPINTERNET_ASYNC_RESULT)Info)->dwResult;
        AsyncError = ((LPINTERNET_ASYNC_RESULT)Info)->dwError;
        break;

    default:
        type$ = "???";
        break;
    }
    if (Verbose) {
        printf("callback: handle %x [context %x [%s]] %s ",
                Handle,
                Context,
                (Context == FTPCAT_CONNECT_CONTEXT) ? "Connect"
                : (Context == FTPCAT_FIND_CONTEXT) ? "Find"
                : (Context == FTPCAT_FILE_CONTEXT) ? "File"
                : (Context == FTPCAT_GET_CONTEXT) ? "Get"
                : (Context == FTPCAT_PUT_CONTEXT) ? "Put"
                : (Context == FTPCAT_COMMAND_CONTEXT) ? "Command"
                : "???",
                type$
                );
    }
    if (Info) {
        if (Status == INTERNET_STATUS_HANDLE_CREATED) {
            if (Verbose) {
                printf("%x", *(LPHINTERNET)Info);
            }
        } else if (Status == INTERNET_STATUS_REQUEST_COMPLETE) {

            //
            // nothing
            //

        } else if (Length == sizeof(DWORD)) {
            if (Verbose) {
                printf("%d", *(LPDWORD)Info);
            }
        } else if (Verbose) {
            printf(Info);
        }
    }
    if (Verbose) {
        putchar('\n');
    }
    if (Status == INTERNET_STATUS_REQUEST_COMPLETE) {
        get_response();
        if (AsyncMode) {
            SetEvent(AsyncEvent);
        } else {
            printf("error: INTERNET_STATUS_REQUEST_COMPLETE returned. Not async\n");
        }
    }
}

void close_handle(HINTERNET handle) {
    if (!InternetCloseHandle(handle)) {
        print_error("close_handle", "InternetCloseHandle(%x)", handle);
    }
}

#if DBG

DWORD GetProcessHandleCount() {

    DWORD error;
    DWORD count;
    DWORD countSize;

    countSize = sizeof(count);
    if (!InternetQueryOption(NULL,
                             INTERNET_OPTION_GET_HANDLE_COUNT,
                             &count,
                             &countSize
                             )) {
        print_error("GetProcessHandleCount", "InternetQueryOption()");
        return 0;
    }
    return count;
}

#endif
