/*++

Copyright (c) 1994 Microsoft Corporation

Module Name:

    thttp.c

Abstract:

    Simple test program for the HTTP API.

Author:

    Keith Moore (keithmo) 16-Nov-1994

Revision History:

--*/

#include <windows.h>
#include <wininet.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>

//
// macros
//

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

//
//  Private constants.
//

#define DEFAULT_CONTEXT 1
#define OPEN_CONTEXT    2
#define CONNECT_CONTEXT 3
#define REQUEST_CONTEXT 4

#define LOAD_ENTRY( hMod, Name )  \
   (p##Name = (pfn##Name) GetProcAddress( (hMod), #Name ))

//
//  Private types.
//

typedef struct _QUERY_LEVEL
{
    DWORD   QueryType;
    CHAR  * QueryName;

} QUERY_LEVEL;

#define MK_QUERY(x) { HTTP_QUERY_ ## x, #x }

typedef
INTERNETAPI
HINTERNET
(WINAPI *
pfnInternetOpenA)(
    IN LPCSTR lpszAgent,
    IN DWORD dwAccessType,
    IN LPCSTR lpszProxy OPTIONAL,
    IN LPCSTR lpszProxyBypass OPTIONAL,
    IN DWORD dwFlags
    );

typedef
INTERNETAPI
INTERNET_STATUS_CALLBACK
(WINAPI *
pfnInternetSetStatusCallback)(
    IN HINTERNET hInternet,
    IN INTERNET_STATUS_CALLBACK lpfnInternetCallback
    );

typedef
INTERNETAPI
HINTERNET
(WINAPI *
pfnInternetConnectA)(
    IN HINTERNET hInternet,
    IN LPCSTR lpszServerName,
    IN INTERNET_PORT nServerPort,
    IN LPCSTR lpszUserName OPTIONAL,
    IN LPCSTR lpszPassword OPTIONAL,
    IN DWORD dwService,
    IN DWORD dwFlags,
    IN DWORD dwContext
    );

typedef
INTERNETAPI
HINTERNET
(WINAPI *
pfnHttpOpenRequestA)(
    IN HINTERNET hConnect,
    IN LPCSTR lpszVerb,
    IN LPCSTR lpszObjectName,
    IN LPCSTR lpszVersion,
    IN LPCSTR lpszReferrer OPTIONAL,
    IN LPCSTR FAR * lplpszAcceptTypes OPTIONAL,
    IN DWORD dwFlags,
    IN DWORD dwContext
    );

typedef
INTERNETAPI
BOOL
(WINAPI *
pfnHttpAddRequestHeadersA)(
    IN HINTERNET hRequest,
    IN LPCSTR lpszHeaders,
    IN DWORD dwHeadersLength,
    IN DWORD dwModifiers
    );

typedef
INTERNETAPI
BOOL
(WINAPI *
pfnHttpSendRequestA)(
    IN HINTERNET hRequest,
    IN LPCSTR lpszHeaders OPTIONAL,
    IN DWORD dwHeadersLength,
    IN LPVOID lpOptional OPTIONAL,
    IN DWORD dwOptionalLength
    );

typedef
INTERNETAPI
BOOL
(WINAPI *
pfnHttpQueryInfoA)(
    IN HINTERNET hRequest,
    IN DWORD dwInfoLevel,
    IN OUT LPVOID lpBuffer OPTIONAL,
    IN OUT LPDWORD lpdwBufferLength,
    IN OUT LPDWORD lpdwIndex OPTIONAL
    );

typedef
INTERNETAPI
BOOL
(WINAPI *
pfnInternetCloseHandle)(
    IN HINTERNET hInternet
    );

typedef
INTERNETAPI
BOOL
(WINAPI *
pfnInternetReadFile)(
    IN HINTERNET hFile,
    IN LPVOID lpBuffer,
    IN DWORD dwNumberOfBytesToRead,
    OUT LPDWORD lpdwNumberOfBytesRead
    );

//
//  Private globals.
//

CHAR MoreHeaders[] = "Pragma: This is garbage!\r\n";

HMODULE hWininet;

LPTSTR AcceptTypes[] =
    {
        "*/*",
         NULL
    };

QUERY_LEVEL QueryLevels[] =
    {
        MK_QUERY( STATUS_CODE ),
        MK_QUERY( STATUS_TEXT ),
        MK_QUERY( VERSION ),
        MK_QUERY( MIME_VERSION ),

        MK_QUERY( CONTENT_TYPE ),
        MK_QUERY( CONTENT_TRANSFER_ENCODING ),
        MK_QUERY( CONTENT_ID     ),
        MK_QUERY( CONTENT_DESCRIPTION ),
        MK_QUERY( CONTENT_LENGTH ),
        MK_QUERY( CONTENT_LANGUAGE ),
        MK_QUERY( ALLOW ),
        MK_QUERY( PUBLIC ),
        MK_QUERY( DATE ),
        MK_QUERY( EXPIRES ),
        MK_QUERY( LAST_MODIFIED ),
        MK_QUERY( MESSAGE_ID ),
        MK_QUERY( URI ),
        MK_QUERY( DERIVED_FROM ),
        MK_QUERY( COST ),
        MK_QUERY( LINK ),
        MK_QUERY( PRAGMA ),
        MK_QUERY( CONNECTION ),
        MK_QUERY( RAW_HEADERS_CRLF )
    };
#define NUM_LEVELS (sizeof(QueryLevels) / sizeof(QueryLevels[0]))

BOOL Verbose = FALSE;
BOOL Quiet   = FALSE;   // Don't print failed headers and content
BOOL Recurse = FALSE;   // Follow links
BOOL Cache   = FALSE;   // Don't allow caching (i.e., force reload)
BOOL Stats   = FALSE;   // Print stats
BOOL Logs   = FALSE;    // Print log
BOOL LargeBuf= TRUE;   // Use 8k reads rather then 512 byte
BOOL KeepAlive = FALSE;
DWORD AccessType = PRE_CONFIG_INTERNET_ACCESS;
BOOL EnableCallbacks = FALSE;
BOOL UserSuppliedContext = FALSE;

INTERNET_STATUS_CALLBACK PreviousCallback;

DWORD cLevel     = 0;   // Current recurse level
DWORD cMaxLevel  = 10;  // Max Recurse level
DWORD cbReceived = 0;
DWORD cmsecStart = 0;
DWORD cFiles     = 0;
DWORD cIterations= 1;   // Total iterations to perform request

LPSTR GatewayServer = NULL;

INTERNET_PORT nServerPort = 0;

DWORD LogError = ERROR_SUCCESS;

HANDLE AsyncEvent = NULL;
BOOL AsyncMode = FALSE;
DWORD AsyncResult;
DWORD AsyncError;
DWORD Context = 0;


pfnInternetOpenA              pInternetOpenA;
pfnInternetSetStatusCallback  pInternetSetStatusCallback;
pfnInternetConnectA           pInternetConnectA;
pfnHttpOpenRequestA           pHttpOpenRequestA;
pfnHttpAddRequestHeadersA     pHttpAddRequestHeadersA;
pfnHttpSendRequestA           pHttpSendRequestA;
pfnHttpQueryInfoA             pHttpQueryInfoA;
pfnInternetCloseHandle        pInternetCloseHandle;
pfnInternetReadFile           pInternetReadFile;


//
//  Private prototypes.
//

void usage(void);

DWORD
DoTest(
    LPSTR Host,
    LPSTR Verb,
    LPSTR Object
    );

BOOL
add_headers(
    HINTERNET hHttpRequest,
    LPSTR lpszHeaders,
    DWORD dwHeadersLength
    );

void my_callback(HINTERNET, DWORD, DWORD, LPVOID, DWORD);

VOID
FindLink(
    LPSTR   Host,
    LPSTR   Verb,
    CHAR *  buf,
    DWORD   len,
    CHAR *  pchLink,
    BOOL *  pfCopyingLink,
    CHAR *  pchReferer
    );

DWORD ReadHtml(HINTERNET hInternet, LPVOID buf, DWORD len, LPDWORD pRead);

BOOL
LoadWininet(
    VOID
    );

//
//  Public functions.
//


int
__cdecl
main(
    int   argc,
    char * argv[]
    )
{
    LPSTR host = NULL;
    LPSTR verb = NULL;
    LPSTR object = NULL;

    if ( !LoadWininet() )
    {
        printf(" Unable to load wininet.dll, error %d\n", GetLastError() );
        return GetLastError();
    }

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

            case 'c':
                EnableCallbacks = TRUE;
                break;

            case 'C':
                Cache = TRUE;
                break;

            case 'G':
                printf("'G' flag is not supported at this time\n");
                GatewayServer = ++*argv;
                //AccessType = GATEWAY_INTERNET_ACCESS;
                break;

            case 'i':

                if ( isdigit( argv[0][1] ))
                {
                    cIterations = atoi( ++*argv );

                    while ( isdigit( *(*argv)++ ))
                        ;
                }
                break;

            case 'k':
                KeepAlive = TRUE;
                break;

            case 'l':
                LargeBuf = TRUE;
                break;

            case 'L':
                AccessType = LOCAL_INTERNET_ACCESS;
                break;

            case 'p':
                object = ++*argv;
                break;

            case 'P':

                if ( isdigit( argv[0][1] ))
                {
                    nServerPort = (INTERNET_PORT)atoi( ++*argv );

                    while ( isdigit( *(*argv)++ ))
                        ;
                }
                break;

            case 'q':
                Quiet = TRUE;
                break;

            case 'r':
                Recurse = TRUE;

                if ( isdigit( argv[0][1] ))
                {
                    cMaxLevel = atoi( ++*argv );

                    while ( isdigit( *(*argv)++ ))
                        ;
                }
                break;

            case 's':
                Stats = TRUE;
                cmsecStart = GetTickCount();
                break;

            case 'v':
                Verbose = TRUE;
                break;

            case 'x':
                ++*argv;
                if (!**argv) {
                    Context = DEFAULT_CONTEXT;
                } else {
                    Context = (DWORD)strtoul(*argv, NULL, 0);
                    UserSuppliedContext = TRUE;
                }
                break;

            case 'y':
                AsyncMode = TRUE;
                break;

            case 'z':
                Logs = TRUE;
                cmsecStart = GetTickCount();
                break;

            default:
                printf("error: unrecognized command line flag: '%c'\n", **argv);
                usage();
            }
        } else if (!host) {
            host = *argv;
        } else if (!verb) {
            verb = *argv;
        } else if (!object) {
            object = *argv;
        } else {
            printf("error: unrecognized command line argument: \"%s\"\n", *argv);
            usage();
        }
    }

    if (!verb) {
        verb = "GET";
    }

    if (!object) {
        object = "\r\n";
    }

    if (!(host && verb && object)) {
        printf("error: missing command-line argument\n");
        usage();
    }

    if (AsyncMode) {

        //
        // create an auto-reset event
        //

        AsyncEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    }

    //
    //  Make stdout "binary" so we can retrieve GIFs, JPEGs, etc.
    //

    _setmode( _fileno( stdout ), _O_BINARY );

    //
    //  Perform some tests.
    //

    while ( cIterations-- )
    {
        DWORD Error;

        Error = DoTest(host, verb, object );

        if( Error != ERROR_SUCCESS ) {
            LogError = Error;
        }
    }

    if ( Stats )
    {
        DWORD csecTotal = (GetTickCount() - cmsecStart) / 1000;
        DWORD cMin      = csecTotal / 60;
        DWORD cSec      = csecTotal % 60;

        fprintf( stderr,
                 "=====================================\n"
                 "Total data bytes received: %ld\n"
                 "Total files retrieved:     %ld\n"
                 "Total time:                %d:%d\n"
                 "=====================================\n",
                 cbReceived,
                 cFiles,
                 cMin,
                 cSec );
    }

    if ( Logs )
    {
        DWORD csecTotal = (GetTickCount() - cmsecStart) ;
        SYSTEMTIME SystemTime;

        GetLocalTime( &SystemTime );

        fprintf( stderr,
                "LOG: [%02u/%02u %02u:%02u:%02u] "
                 "%-10s %-32s %4s %8d %8d\n",
                    SystemTime.wMonth,
                    SystemTime.wDay,
                    SystemTime.wHour,
                    SystemTime.wMinute,
                    SystemTime.wSecond,
                        GatewayServer,
                        host,
                        object,
                        LogError,
                        csecTotal );
    }


    return 0;

}   // main

void usage() {
    printf("usage: thttp [-c] [-C] [-l] [-L] [-k] [-p<path>] [-q] [-r] [-s] [-v] [-P]\n"
                "        [-x$] [-y] [-z] [-G<servername>] <host> [<verb>] [<object>]\n"
           "\n"
           "where: -c    = Enable call-backs\n"
           "       -C    = Enable caching\n"
           "       -i[n] = Iterate n times\n"
           "       -l    = Large network buffer\n"
           "       -L    = Force local access (i.e., do not use gateway)\n"
           "       -k    = Use Keep-Alive\n"
           "       -p    = path (e.g. if path starts with '/')\n"
           "       -q    = Quiet mode, no failed headers, no content\n"
           "       -r[n] = Recurse into links, n = max recurse level\n"
           "       -s    = Print network statistics\n"
           "       -v    = Verbose mode\n"
           "       -G    = specific gateway server\n"
           "       -P[n] = Use port n; default = 80\n"
           "       -x    = Context value. $ is number string (binary, hex, decimal)\n"
           "       -y    = Async mode\n"
           "       -z    = print log\n"
           "Verb defaults to \"GET\"\n"
           "Object defaults to \"\\r\\n\"\n"
           );
    exit(1);
}

BOOL
LoadWininet(
    VOID
    )
{
    if ( !(hWininet = LoadLibrary( "wininet.dll" )) )
    {
        printf("Failed to load wininet.dll\n" );
        return FALSE;
    }

    if ( !LOAD_ENTRY( hWininet, InternetOpenA ) ||
         !LOAD_ENTRY( hWininet, InternetSetStatusCallback ) ||
         !LOAD_ENTRY( hWininet, InternetConnectA ) ||
         !LOAD_ENTRY( hWininet, HttpOpenRequestA ) ||
         !LOAD_ENTRY( hWininet, HttpAddRequestHeadersA ) ||
         !LOAD_ENTRY( hWininet, HttpSendRequestA ) ||
         !LOAD_ENTRY( hWininet, HttpQueryInfoA ) ||
         !LOAD_ENTRY( hWininet, InternetCloseHandle ) ||
         !LOAD_ENTRY( hWininet, InternetReadFile ) )
    {
        return FALSE;
    }

    return TRUE;
}

DWORD
DoTest(
    LPSTR Host,
    LPSTR Verb,
    LPSTR Object
    )
{
    DWORD Error = ERROR_SUCCESS;
    HINTERNET InternetHandle = NULL;
    HINTERNET InternetConnectHandle = NULL;
    HINTERNET hhttp = NULL;
    DWORD     len;
    int       i;
    CHAR      buf[8192];
    CHAR      bufLink[512];
    BOOL      fCopyingLink = FALSE;

    *bufLink = '\0';

    //
    // open internet.
    //

    if (Verbose) {
        printf("calling InternetOpen()...\n");
    }

    InternetHandle = pInternetOpenA(
                        "THTTP: HTTP API Test Application", // lpszCallerName
                        AccessType,                         // dwAccessType
                        GatewayServer,                      // lpszProxyName
                        INTERNET_INVALID_PORT_NUMBER,       // nProxyPort
                        AsyncMode ? INTERNET_FLAG_ASYNC : 0 // dwFlags (async)
                        );
    if (InternetHandle == NULL) {
        if (AsyncMode) {
            Error = GetLastError();
            if (Error == ERROR_IO_PENDING) {
                if (Verbose) {
                    fprintf(stderr, "error: InternetOpen() is async (spanish inquisition mode)\n");
                    printf("waiting for async InternetOpen()...\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                if (AsyncResult == 0) {
                    fprintf(stderr, "error: async InternetOpen() returns %d\n",
                        AsyncError);
                    goto Cleanup;
                } else {
                    InternetHandle = (HINTERNET)AsyncResult;
                }
            } else {
                fprintf(stderr, "error: async InternetOpen() returns %d\n", Error);
                goto Cleanup;
            }
        } else {
            fprintf( stderr,
                     "InternetOpen() failed, error %d\n",
                        Error = GetLastError() );

            goto Cleanup;
        }
    }

    if (Verbose) {
        printf("InternetOpen() returns %x\n", InternetHandle);
    }

    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 = pInternetSetStatusCallback(InternetHandle, my_callback);
        if (Verbose) {
            printf("previous Internet callback = %x\n", PreviousCallback);
        }
    }


    //
    // Call internet connect to connect to the http server.
    //

    if (Verbose) {
        printf("calling InternetConnect()...\n");
    }

    InternetConnectHandle = pInternetConnectA(
                                InternetHandle,         // hInternetSession
                                Host,                   // lpszServerName
                                nServerPort,            // nServerPort
                                NULL,                   // lpszUserName
                                NULL,                   // lpszPassword
                                INTERNET_SERVICE_HTTP,  // dwService
                                0,                      // dwFlags
                                UserSuppliedContext ? Context : CONNECT_CONTEXT
                                );


    if( InternetConnectHandle == NULL )
    {
        if (AsyncMode) {
            Error = GetLastError();
            if (Error == ERROR_IO_PENDING) {
                if (Verbose) {
                    fprintf(stderr, "error: InternetConnect() is async (spanish inquisition mode)\n");
                    printf("waiting for async InternetConnect()...\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                if (AsyncResult == 0) {
                    fprintf(stderr, "error: async InternetConnect() returns %d\n",
                        AsyncError);
                    goto Cleanup;
                } else {
                    InternetConnectHandle = (HINTERNET)AsyncResult;
                }
            } else {
                fprintf(stderr, "error: async InternetConnect() returns %d\n", Error);
                goto Cleanup;
            }
        } else {
            fprintf( stderr,
                     "InternetConnect() failed, error %d\n",
                        Error = GetLastError() );

            goto Cleanup;
        }
    }

    if (Verbose) {
        printf("InternetConnect() returns %x\n", InternetConnectHandle);
    }

    //
    //  Open a request handle.
    //

    if (Verbose) {
        printf("calling HttpOpenRequest()...\n");
    }

    hhttp = pHttpOpenRequestA(
                InternetConnectHandle,      // hHttpSession
                Verb,                       // lpszVerb
                Object,                     // lpszObjectName
                NULL,                       // lpszVersion
                NULL,                       // lpszReferer
                AcceptTypes,                // lplpszAcceptTypes
                (Cache ? 0 :
                         INTERNET_FLAG_RELOAD),
                UserSuppliedContext ? Context : REQUEST_CONTEXT
                );

    if( hhttp == NULL )
    {
        if (AsyncMode) {
            Error = GetLastError();
            if (Error == ERROR_IO_PENDING) {
                if (Verbose) {
                    fprintf(stderr, "error: HttpOpenRequest() is async (spanish inquisition mode)\n");
                    printf("waiting for async HttpOpenRequest()...\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                if (AsyncResult == 0) {
                    fprintf(stderr, "error: async HttpOpenRequest() returns %d\n",
                        AsyncError);
                    goto Cleanup;
                } else {
                    hhttp = (HINTERNET)AsyncResult;
                }
            } else {
                fprintf(stderr, "error: async HttpOpenRequest() returns %d\n", Error);
                goto Cleanup;
            }
        } else {
            fprintf( stderr,
                     "HttpOpenRequest() failed, error %d\n",
                        Error = GetLastError() );

            goto Cleanup;
        }
    }

    if (Verbose) {
        printf("HttpOpenRequest() returns %x\n", hhttp);
    }

    //
    // add keep-alive header if requested
    //

    if (KeepAlive) {
        if (!add_headers(hhttp, "Connection: Keep-Alive\r\n", (DWORD)-1)) {
            fprintf(stderr, "HttpAddRequestHeaders() returns %d\n", GetLastError());
        }
    }

    //
    //  Add additional request headers.
    //

    if( !add_headers(
            hhttp,
            "Pragma: bite-me\r\n",
            (DWORD)-1L ) )
    {
        fprintf( stderr,
                 "HttpAddRequestHeaders() failed, error %d\n",
                 GetLastError() );
    }

    if( !add_headers(
            hhttp,
            "Pragma: bite-me-again\r\n",
            (DWORD)-1L ) )
    {
        fprintf( stderr,
                 "HttpAddRequestHeaders() failed, error %d\n",
                 GetLastError() );
    }

    if( !add_headers(
            hhttp,
            "Pragma: bite-me-a-third-time\r\n",
            (DWORD)-1L ) )
    {
        fprintf( stderr,
                 "HttpAddRequestHeaders() failed, error %d\n",
                 GetLastError() );
    }

    //
    //  Send the request.
    //

    if (Verbose) {
        printf("calling HttpSendRequest()...\n");
    }

    if( !pHttpSendRequestA(
            hhttp,          // hHttpRequest
            MoreHeaders,    // lpszHeaders
            (DWORD)-1L,     // dwHeadersLength
            NULL,           // lpOptional
            0 ) )           // dwOptionalLength
    {
        if (AsyncMode) {
            Error = GetLastError();
            if (Error == ERROR_IO_PENDING) {
                if (Verbose) {
                    printf("HttpSendRequest() waiting for async completion\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                Error = AsyncError;
                if (!AsyncResult) {
                    printf("error: ASYNC HttpSendRequest() returns FALSE\n");
                    if (Error == ERROR_SUCCESS) {
                        printf("error: ASYNC HttpSendRequest() (FALSE) returns ERROR_SUCCESS!\n");
                    } else {
                        printf("ASYNC HttpSendRequest() returns %d\n", Error);
                    }
                } else if (Verbose) {
                    printf("ASYNC HttpSendRequest() success\n");
                }
            } else {
                printf("error: ASYNC HttpSendRequest() returns %d\n", Error);
            }
        } else {
            fprintf( stderr,
                     "HttpSendRequest() failed, error %d\n",
                        Error = GetLastError() );
        }
    } else if (AsyncMode) {

        //
        // we expect async HttpSendRequest() to always return FALSE w/ error
        // or ERROR_IO_PENDING
        //

        printf("ASYNC HttpSendRequest() returns TRUE\n");
    // } else {

        //
        // Error is still ERROR_SUCCESS from initialization
        //

    }

    if (Error == ERROR_SUCCESS) {

        //
        //  Process the queries.
        //

        if ( Quiet )
        {
            len = sizeof(buf);

            //
            //  Only look for failures to retrieve if we're in quiet mode
            //

            if ( !pHttpQueryInfoA(
                    hhttp,
                    HTTP_QUERY_STATUS_CODE,
                    buf,
                    &len,
                    NULL ))
            {
                fprintf( stderr,
                         "HttpQueryInfo( HTTP_QUERY_STATUS_CODE ) failed, error %d\n",
                         GetLastError() );
            }

            if ( *buf != '2' )
            {
                Error = atoi(buf);
                goto PrintAllHeaders;
            }

            cFiles++;
        }
        else
        {
PrintAllHeaders:

            if( !Logs ) {
                for( i = 0 ; i < NUM_LEVELS ; i++ )
                {
                    len = sizeof(buf);

                    if( !pHttpQueryInfoA(
                            hhttp,
                            QueryLevels[i].QueryType,
                            buf,
                            &len,
                            NULL ) )
                    {
                        if ( QueryLevels[i].QueryType == HTTP_QUERY_STATUS_CODE &&
                             *buf == '2' )
                        {
                            cFiles++;
                        }

                        if ( !Quiet && GetLastError() != ERROR_HTTP_HEADER_NOT_FOUND )
                        {
                            fprintf( stderr,
                                     "HttpQueryInfo( %s ) failed, error %d\n",
                                     QueryLevels[i].QueryName,
                                     GetLastError() );
                        }
                    }
                    else
                    {
                        fprintf( stderr,
                                 "%s = %s\n",
                                 QueryLevels[i].QueryName,
                                 buf );
                    }
                }
            }
        }

        //
        //  Read the data.
        //

        for( ; ; )
        {
            len = LargeBuf ? sizeof(buf) : 512;

            Error = ReadHtml(hhttp, buf, len, &len);
            if (Error != ERROR_SUCCESS) {
                fprintf( stderr,
                         "InternetReadFile() failed, error %d\n",
                            Error = GetLastError() );

                break;
            }

            cbReceived += len;

            if( len == 0 )
            {
                if ( !Quiet )
                {
                    fprintf( stderr,
                             "EOF\n" );
                }

                break;
            }

            if ( !Quiet )
            {
                fwrite( buf, 1, (size_t)len, stdout );
            }

            if ( Recurse && cLevel < cMaxLevel )
            {
                CHAR ContentType[50];
                DWORD cbContentType = sizeof( ContentType );

                //
                //  Only look for links if the content type is text/html
                //

                if( pHttpQueryInfoA(
                        hhttp,
                        HTTP_QUERY_CONTENT_TYPE,
                        ContentType,
                        &cbContentType,
                        NULL ) &&
                    !_stricmp( ContentType,
                              "text/html" ))
                {
                    FindLink( Host,
                              Verb,
                              buf,
                              len,
                              bufLink,
                              &fCopyingLink,
                              Object );
                }
            }
        }

        //
        //  Perform an extraneous read.
        //

        len = sizeof(buf);

        Error = ReadHtml(hhttp, buf, len, &len);
        if (Error != ERROR_SUCCESS) {
            fprintf( stderr,
                     "InternetReadFile() failed, error %d\n",
                      Error = GetLastError() );
        }
        else
        if( len != 0 )
        {
            fprintf( stderr,
                     "BOGUS EXTRANEOUS READ: %d\n",
                     len );
        }
    }

Cleanup:

    //
    //  Close handles.
    //

    if( hhttp != NULL )
    {
        if( !pInternetCloseHandle( hhttp ) )
        {
            fprintf( stderr,
                     "InternetCloseHandle() failed, error %d\n",
                     GetLastError() );
        }
    }

    if( InternetConnectHandle != NULL )
    {
        if( !pInternetCloseHandle( InternetConnectHandle ) )
        {
            fprintf( stderr,
                     "InternetCloseHandle() failed, error %d\n",
                     GetLastError() );
        }
    }

    if( InternetHandle != NULL )
    {
        if( !pInternetCloseHandle( InternetHandle ) )
        {
            fprintf( stderr,
                     "InternetCloseHandle() failed, error %d\n",
                     GetLastError() );
        }
    }

    cLevel--;
    return( Error );
}   // DoTest

BOOL
add_headers(
    HINTERNET hHttpRequest,
    LPSTR lpszHeaders,
    DWORD dwHeadersLength
    )
{
    BOOL ok;

    ok = pHttpAddRequestHeadersA(hHttpRequest, lpszHeaders, dwHeadersLength, 0);
    if (AsyncMode) {
        if (!ok) {

            DWORD err;

            err = GetLastError();
            if (err == ERROR_IO_PENDING) {
                if (Verbose) {
                    printf("warning: HttpAddRequestHeaders() is async - unexpected\n");
                    printf("waiting for async HttpAddRequestHeaders()...\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                ok = (BOOL)AsyncResult;
                if (!ok) {
                    printf("error: async HttpAddRequestHeaders() returns %d\n",
                        AsyncError);
                }
            } else {
                printf("error: async HttpAddRequestHeaders() returns %d\n", err);
            }
        }
    }
    return ok;
}

VOID
my_callback(
    HINTERNET hInternet,
    DWORD Context,
    DWORD Status,
    LPVOID Info,
    DWORD Length
    )
{
    char* type$;
    BOOL unknown = FALSE;

    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_REQUEST_COMPLETE:
        type$ = "REQUEST COMPLETE";
        if (AsyncMode) {
            AsyncResult = ((LPINTERNET_ASYNC_RESULT)Info)->dwResult;
            AsyncError = ((LPINTERNET_ASYNC_RESULT)Info)->dwError;
            SetEvent(AsyncEvent);
        } else {
            printf("error: REQUEST_COMPLETE not expected - not async\n");
        }
        break;

    default:
        type$ = "???";
        unknown = TRUE;
        break;
    }
    if (Verbose) {
        printf("callback: handle %x [context %x [%s]] %s ",
                hInternet,
                Context,
                UserSuppliedContext             ? "User"
                : (Context == DEFAULT_CONTEXT)  ? "Default"
                : (Context == OPEN_CONTEXT)     ? "Open"
                : (Context == CONNECT_CONTEXT)  ? "Connect"
                : (Context == REQUEST_CONTEXT)  ? "Request"
                : "???",
                type$
                );
        if (Info && !unknown) {
            if (Status == INTERNET_STATUS_REQUEST_COMPLETE) {
                if (Verbose) {
                    printf("dwResult = %x, dwError = %d\n",
                            ((LPINTERNET_ASYNC_RESULT)Info)->dwResult,
                            ((LPINTERNET_ASYNC_RESULT)Info)->dwError
                            );
                }
            } else {
                printf(Info);
            }
        }
        putchar('\n');
    }
}

VOID
FindLink(
    LPSTR   Host,
    LPSTR   Verb,
    CHAR *  buf,
    DWORD   len,
    CHAR *  pchLink,
    BOOL *  pfCopyingLink,
    CHAR *  pchReferer
    )
{
    DWORD Error;
    CHAR * pchEnd = buf + len;
    CHAR * pch = buf;
    DWORD  cchLink = strlen( pchLink );

    while ( TRUE )
    {
        if ( *pfCopyingLink )
        {
FindEOT:
            //
            //  Look for end of href
            //

            while ( pch < pchEnd )
            {
                if ( *pch == '"' )
                    goto FoundEOT;

                pchLink[cchLink++] = *pch;

                pch++;
            }

            //
            //  Used up all of the buffer and we didn't find the end of the tag,
            //  get some more data
            //

            pchLink[cchLink] = '\0';

            return;

FoundEOT:
            pchLink[cchLink] = '\0';
            *pfCopyingLink = FALSE;

            //
            //  We only traverse URLs of the form '/dir/bar/doc.htm'
            //

            if ( pchLink[0] != '/' )
            {
                CHAR * pchLastSlash;
                CHAR   achTemp[512];

                //
                //  If it's relative, use the referer to make it absolute
                //
                //  Note we don't process /dir/bar/doc.htm#GoHere tags
                //

                if ( (pchLastSlash = strrchr( pchReferer, '/' )) &&
                     strncmp( pchLink, "ftp:", 4 )               &&
                     strncmp( pchLink, "http:", 5 )              &&
                     strncmp( pchLink, "gopher:", 7 )            &&
                     strncmp( pchLink, "mailto:", 7 )            &&
                     !strchr( pchLink, '#' ))
                {
                    *(pchLastSlash + 1) = '\0';
                    strcpy( achTemp, pchReferer );
                    strcat( achTemp, pchLink );
                    strcpy( pchLink, achTemp );
                }
                else
                {
                    fprintf( stderr,
                             "Ignoring %s\n",
                             pchLink );
                    return;
                }
            }

            fprintf( stderr,
                     "Traversing %s\n",
                     pchLink );

            cLevel++;

            Error = DoTest(
                            Host,
                            Verb,
                            pchLink );

            if( Error != ERROR_SUCCESS ) {
                LogError = Error;
            }

        }
        else
        {
            *pchLink = '\0';

            //
            //  Scan for the beginning of an href tag
            //

            while ( pch < pchEnd )
            {
                if ( *pch == '<' )
                {
                    //
                    //  Look for "<A HREF="", note we aren't flexible about spacing
                    //

                    if ( !_strnicmp( pch, "<A HREF=\"", 9 ) ||
                         !_strnicmp( pch, "<IMG SRC=\"", 10 ))
                    {
                        pch += (toupper(pch[1]) == 'A' ? 9 : 10);
                        *pfCopyingLink = TRUE;
                        cchLink = 0;
                        goto FindEOT;
                    }
                }

                pch++;
            }

            //
            //  No tag found, return
            //

            return;
        }
    }
}

DWORD ReadHtml(HINTERNET hInternet, LPVOID buf, DWORD len, LPDWORD pRead) {

    DWORD error = ERROR_SUCCESS;

    if (!pInternetReadFile(hInternet, buf, len, pRead)) {
        if (AsyncMode) {
            error = GetLastError();
            if (error == ERROR_IO_PENDING) {
                if (Verbose) {
                    printf("ASYNC InternetReadFile() waiting for async completion\n");
                }
                WaitForSingleObject(AsyncEvent, INFINITE);
                error = AsyncError;
                if (!AsyncResult) {
                    printf("error: ASYNC InternetReadFile() returns FALSE\n");
                    if (error == ERROR_SUCCESS) {
                        printf("error: ASYNC InternetReadFile() (FALSE) returns ERROR_SUCCESS!\n");
                    } else {
                        printf("ASYNC InternetReadFile() returns %d\n", error);
                    }
                } else if (Verbose) {
                    printf("ASYNC InternetReadFile() success\n");

                    //
                    // error should be ERROR_SUCCESS from callback
                    //

                    if (error != ERROR_SUCCESS) {
                        printf("error: async error = %d. Expected ERROR_SUCCESS\n", error);
                    }
                }
            } else {
                printf("error: ASYNC InternetReadFile() returns %d\n", error);
            }
        } else {
            error = GetLastError();
            printf("error: SYNC InternetReadFile() returns %d\n", error);
        }
    } else if (AsyncMode) {

        //
        // we expect async InternetReadFile() to always return FALSE w/ error
        // or ERROR_IO_PENDING
        //

        if (Verbose) {
            printf("ASYNC InternetReadFile() returns TRUE\n");
        }
    } else {

        //
        // error is still ERROR_SUCCESS from initialization
        //

        if (Verbose) {
            printf("SYNC InternetReadFile() returns TRUE\n");
        }
    }
    return error;
}