//
//  Command line HTTP client application
//


extern "C" {

#include <windows.h>
#include <winsock.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wsipx.h>
#include <nspapi.h>
#include <svcguid.h>
#include <inetcom.h>
#include <svcloc.h>

} // extern "C"

//
//  Converts a value between zero and fifteen to the appropriate hex digit
//
#define HEXDIGIT( nDigit )                              \
    (TCHAR)((nDigit) > 9 ?                              \
          (nDigit) - 10 + 'A'                           \
          : (nDigit) + '0')

//
//  Converts a single hex digit to its decimal equivalent
//
#define TOHEX( ch )                                     \
    ((ch) > '9' ?                                       \
        (ch) >= 'a' ?                                   \
            (ch) - 'a' + 10 :                           \
            (ch) - 'A' + 10                             \
        : (ch) - '0')

#define W3SVC_GUID { 0x585908c0, 0x6305, 0x11ce, { 0xae,0x00,0,0xaa,0,0x4a,0x38,0xb9 } }

CHAR * SkipWhite( CHAR * pch );
#define ISWHITE( ch )       ((ch) == ' ' || (ch) == '\t' || (ch) == '\r')

DWORD PrintHeaders( BOOL * pfAllHeaders );
BOOL ReadRequest( VOID );
BOOL SendRequest( VOID );
BOOL ReadReply( VOID );
void PrintUsage( void );
BOOL FillAddr( PSOCKADDR psin,
               BOOL bConnect);
CHAR * SkipComments( CHAR * pch );
CHAR * BuildNextRequest( VOID );
BOOL AddAuthorizeHeaders( CHAR * pch );
VOID DumpAuthBuffer( VOID * pbuff, char * pchSrc );


#define HTTPCMD_VERSION         "1.1"
#define HTTP_REQ_TERM           "$END"

char      buff[65535];
int       cbRequest;
char *    pchCurrentReq;           // Current location of request
SOCKET    sock     = INVALID_SOCKET;
int       portno   =   80;         // HTTP port number
char *    pchServer;               // Server to make the request to
char * pchInputFile;          // Input file to read from
char *    pchUserName = NULL;      // Username to user for authentication
char *    pchPassword = NULL;      // Password to use for authentication
BOOL      fKeepConnection = FALSE; // Pass "Pragma: Keep-connection" ?
BOOL      fEchoSends = FALSE;      // Print send requests to stdout?
BOOL      fDumpAuth = FALSE;       // Dump raw authorization data
BOOL      fDontEchoHeaders = FALSE;// Don't print received headers
BOOL      fHexTranslation = FALSE; // Simulate for encryption filters
BOOL      fReadHeaders = FALSE;    // On translated reply, have we read HTTP headers?
BOOL      fUseSPX = FALSE;         // Use SPX rather then TCP
BOOL      fProxyRequest = FALSE;   // Is this a proxy request?
DWORD     cbContentLength = 0xffffffff; // Number of bytes in server reply
char      achAuth[256];            // Authentication schemes to support
char *    pchAuth = achAuth;       // Current position in achAuth
char      bufftmp[65535];          // Temporary buffer
#if TCP_AUTHENT
TCP_AUTHENT * pTcpAuth = NULL;     // NT Authentication class
#endif
char      achAuthData[512];        // Server response authentication blob
BOOL      fLoggedOn = FALSE;       // Has the server accepted our request?

//
//  Default field names for authorization
//

char * pchAuthSendHeader = "Authorization: ";
char * pchAuthRecvHeader = "WWW-Authenticate: ";


main( int argc, char * argv[], char * envp[] )
{
    CHAR * pch;

    if ( argc < 3 )
    {
        PrintUsage();
        return 0;
    }

    pchServer = argv[1];
    pchInputFile = argv[2];
    memset( achAuth, 0, sizeof( achAuth ));

    for ( int i = 4; i < (argc + 1); i++ )
    {
        switch ( *(pch = (argv[i-1] + 1) ))
        {
        case 'k':
        case 'K':
            fKeepConnection = TRUE;
            break;

        case 'e':
        case 'E':
            fEchoSends = TRUE;
            break;

        case 'd':
        case 'D':
            fDumpAuth = TRUE;
            break;

        //
        //  -u:username:password
        //

        case 'u':
        case 'U':
            if ( *(++pch) != ':' )
            {
                PrintUsage();
                return 0;
            }

            pch++;

            pchUserName = pch;

            if ( pch = strchr( pch, ':' ) )
            {
                *pch = '\0';
                pchPassword = pch+1;
            }
            else
            {
                pchPassword = "";
            }
            break;

        //
        //  Authentication schemes
        //
        //  -a:NTLM -a:Basic
        //

        case 'a':
        case 'A':
            if ( *(++pch) != ':' )
            {
                PrintUsage();
                return 0;
            }

            pch++;

            strcpy( pchAuth, pch );
            pchAuth += strlen( pch ) + 1;
            break;

        //
        // translation schemes
        //

        case 't':
        case 'T':
            //
            //  Assume hex conversion for time being
            //

            fHexTranslation = TRUE;
            break;

        case 'H':
        case 'h':
            fDontEchoHeaders = TRUE;
            break;

        case 'S':
        case 's':
            fUseSPX = TRUE;
            break;

        case 'P':
        case 'p':
            pchAuthSendHeader = "Proxy-Authorization: ";
            pchAuthRecvHeader = "Proxy-Authenticate: ";
            break;
        }
    }

    if ( !ReadRequest() )
        return -1;

    while ( TRUE )
    {
        if ( !SendRequest() ||
             !ReadReply()     )
        {
            break;
        }
    }

    return 0;
}

//
//  ReadRequest - Reads stdin for an HTTP request
//

BOOL ReadRequest( void )
{
    int    i;
    char * pch;
    FILE *stream;

    cbRequest = 0;

    if( (stream = fopen( pchInputFile, "r" )) != NULL )
        cbRequest = fread( buff, 1, sizeof(buff), stream );

    if ( !cbRequest )
    {
        fprintf( stderr, "failed to read anything from %s, quitting\n", pchInputFile);
        return FALSE;
    }

    buff[cbRequest] = '\0';

    pchCurrentReq = SkipComments( buff );
    return TRUE;
}

//
//  SendRequest - Sends the request to the specified server
//

BOOL SendRequest( VOID )
{
    WSADATA     WSAData;
    int         status;
    SOCKADDR dest_sin;

    if ( sock == INVALID_SOCKET )
    {
        if ((status = WSAStartup(MAKEWORD(1,1), &WSAData)) != 0)
        {
            fprintf( stderr, "WSAStartup failed, status = %d\n", status);
            return FALSE;
        }

        if ( fUseSPX )
            sock = socket( AF_IPX, SOCK_STREAM, NSPROTO_SPXII);
        else
            sock = socket( AF_INET, SOCK_STREAM, 0);

        if (sock == INVALID_SOCKET)
        {
            fprintf( stderr, "socket() failed, error %u\n", GetLastError());
            return FALSE;
        }

        //
        //    Retrieve the IP address and TCP Port number
        //    Global variable szBuff contains the remote host name.
        //

        if (!FillAddr( &dest_sin, TRUE))
        {
           closesocket( sock );
           return FALSE;
        }

        if ( connect( sock, (PSOCKADDR) &dest_sin, sizeof( dest_sin)) < 0)
        {
           fprintf( stderr, "connect() failed, error %d", WSAGetLastError());
           closesocket( sock );
           return FALSE;
        }
    }

    CHAR * pch = BuildNextRequest();

    if ( !pch )
        return FALSE;

    int cb = strlen( pch );

    if ( fEchoSends )
    {
        printf( pch );
        printf("==========================End=of=Request=====================================\n");
    }

    if ( send( sock, pch, cb, 0 ) != cb )
    {
        closesocket( sock );
        fprintf( stderr, "send() failed, error %d", WSAGetLastError());
        return FALSE;
    }

    return TRUE;
}

//
//  ReadReply - Read's the server's reply and outputs it to stdout
//

BOOL ReadReply( VOID )
{
    int  cb;
    int  cbTotal = 0;
    BOOL fReadAny = FALSE;
    CHAR * pchAuthField;

    while ( (cb = recv( sock, bufftmp, sizeof(buff), 0 )) > 0 )
    {
        DWORD cbHeaders = 0;
        CHAR * pch;
        CHAR * pchtmp;

        fReadAny = TRUE;
        bufftmp[cb] = '\0';

        //
        //  Are we expecting a translated reply?
        //

        if ( fHexTranslation )
        {
            BOOL fAllHeaders;

            //
            //  Print out the special HTTP headers
            //

            if ( !fReadHeaders )
            {
                //
                //  We currently assume we'll always get all of the
                //  HEX-HTTP headers
                //

                cbHeaders = PrintHeaders( &fAllHeaders );
            }

            //
            //  Now translate the data in place
            //

            pch = bufftmp + cbHeaders;
            pchtmp = bufftmp;

            while ( *pch )
            {
                *pchtmp = TOHEX( *pch ) * 16 + TOHEX( pch[1]);
                pchtmp++;
                pch += 2;
            }

            *(pchtmp) = '\0';
            cb = pchtmp - bufftmp;
        }

        //
        //  Print out the regular HTTP headers if we haven't already
        //

        if ( !fReadHeaders )
        {
            cbHeaders = PrintHeaders( &fReadHeaders );
        }

        cbTotal += cb - cbHeaders;
        fwrite( bufftmp + cbHeaders, 1, cb - cbHeaders, stdout );

        //
        //  Get the content length field
        //

        if ( pch = strstr( bufftmp, "Content-length:" ))
        {
            pch = strchr( pch, ':' );
            pch++;
            while ( *pch == ' ' )
                pch++;

            cbContentLength = (DWORD) atoi( pch );
        }

        //
        //  Did we successfully get the response?
        //

        if ( !fLoggedOn && strstr( bufftmp, " 200 OK\r\n" ))
        {
            fLoggedOn = TRUE;
        }

        //
        //  Were we denied access from a new response?
        //

        if ( fLoggedOn && strstr( bufftmp, " 401 Access Denied" ))
        {
            fLoggedOn = FALSE;
        }

        //
        //  Need to grab the first WWW-Authenticate headers and grab the server
        //  authentication response blob
        //

        pch = bufftmp;

        while ( pch = strstr( pch, pchAuthRecvHeader ) )
        {
            //
            // Skip the field name
            //
            CHAR * pchtmp = strchr( pch, ':');
            pchtmp++;

            //
            //  Get the scheme name (and ignore it)
            //

            int i = 0;
            while ( *pchtmp == ' ' ) pchtmp++;

            while ( *pchtmp != ' ' &&
                    *pchtmp )
            {
                //achScheme[i++] = *pchtmp;
                pchtmp++;
            }

            //
            //  Note we copy a bunch of extra stuff here, uuencode stops
            //  after the first non-printable character
            //

            strncpy( achAuthData, pchtmp, sizeof( achAuthData ) );
            achAuthData[sizeof(achAuthData)-1] = '\0';

            //
            //  We only handle one authentication scheme
            //

            goto Exit;

        }

        if ( cbTotal >= cbContentLength )
            break;
    }

Exit:

    if ( !fReadAny )
        return FALSE;

    if ( fEchoSends )
        printf("\n==========================End=of=Reply=====================================\n");
    return TRUE;
}

CHAR * BuildNextRequest( VOID )
{
    //
    //  Are we done?
    //

    if ( !pchCurrentReq || !*pchCurrentReq )
        return NULL;

    //
    //  Expect a large chunk of data unless we here otherwise
    //

    cbContentLength = (DWORD) -1;

    //
    //  If this isn't the first request, skip the HTTP terminator
    //

    if ( !strncmp( pchCurrentReq, HTTP_REQ_TERM, sizeof( HTTP_REQ_TERM ) - 1))
    {
        pchCurrentReq += sizeof(HTTP_REQ_TERM) ;
    }

    pchCurrentReq = SkipComments( pchCurrentReq );

    //
    //  Look for the request terminator or end of file
    //

    CHAR * pchEnd = strstr( pchCurrentReq, HTTP_REQ_TERM );
    UINT cb;
    char ch;

    if ( !pchEnd )
    {
        cb = strlen( pchCurrentReq );
    }
    else
    {
        ch = *pchEnd;
        *pchEnd = '\0';
        cb = pchEnd - pchCurrentReq;
    }

    //
    //  Watch for the case where the last request ends in $END
    //

    if ( !cb )
        return FALSE;

    CHAR * pch = pchCurrentReq;

    //
    //  Copy the first line
    //

    pch = strchr( pch, '\n' );
    strncpy( bufftmp, pchCurrentReq, pch - pchCurrentReq + 1);
    bufftmp[pch - pchCurrentReq + 1] = '\0';
    pch++;

    //
    //  Add any optional lines (authentication, pragmas etc)
    //

    if ( fKeepConnection )
    {
        strcat( bufftmp, "Connection: keep-alive\r\n");
    }

    if ( !fLoggedOn )
    {
        if ( !AddAuthorizeHeaders( bufftmp ) )
            return NULL;
    }

    //
    //  Append the rest of the request
    //

    strcat( bufftmp, pch );

    //
    //  Do any translations on the outgoing data
    //

#define HEX_HTTP_HEADER      "GET * HEX-HTTP/1.0\r\n\r\n"

    if ( fHexTranslation )
    {
        DWORD cbHdr = strlen( bufftmp );

        bufftmp[cbHdr*2] = '\0';

        while ( cbHdr > 0 )
        {
            CHAR ch = bufftmp[cbHdr-1];

            bufftmp[(cbHdr-1)*2 + 1] = HEXDIGIT( ch % 16 );

            ch /= 16;

            bufftmp[(cbHdr-1)*2] = HEXDIGIT( ch % 16 );

            cbHdr--;
        }

        //
        //  Copy the new HTTP header
        //

        memmove( bufftmp + sizeof(HEX_HTTP_HEADER) - 1,
                 bufftmp,
                 strlen(bufftmp) );

        memcpy( bufftmp,
                HEX_HTTP_HEADER,
                sizeof(HEX_HTTP_HEADER) - 1 );
    }


    //
    //  Set up for the next request
    //

    if ( pchEnd )
        *pchEnd = ch;

    pchCurrentReq = pchEnd;

    return bufftmp;
}


BOOL AddAuthorizeHeaders( CHAR * pch )
{
#ifdef TCP_AUTH
    CHAR * pchSchemes = achAuth;
    BUFFER buff;
    CHAR   achUserAndPass[256];
    DWORD  cbOut;
    BOOL   fNeedMoreData;

    while ( *pchSchemes )
    {
        if ( !_stricmp( pchSchemes, "Basic" ))
        {
            strcpy( achUserAndPass, pchUserName );
            strcat( achUserAndPass, ":" );
            strcat( achUserAndPass, pchPassword );

            uuencode( (BYTE *) achUserAndPass,
                      strlen( achUserAndPass ),
                      &buff );
            strcat( pch, pchAuthSendHeader );
            strcat( pch, "Basic " );
            strcat( pch, achUserAndPass ),
            strcat( pch, "\r\n");
        }
        else
        {
            if ( !pTcpAuth )
            {
                pTcpAuth = new TCP_AUTHENT( TCPAUTH_CLIENT | TCPAUTH_UUENCODE );
            }
            else
            {
                if ( fDumpAuth )
                    DumpAuthBuffer( achAuthData, "Server" );
            }

            //
            //  Assume this is an NT authentication scheme
            //

            if ( !pTcpAuth->Converse( (void *) achAuthData,
                                      0,
                                      &buff,
                                      &cbOut,
                                      &fNeedMoreData,
                                      pchSchemes,
                                      pchUserName,
                                      pchPassword ))
            {
                printf("SSP conversation failed, error %x, scheme %s\n",
                        GetLastError(),
                        pchSchemes );
                return FALSE;
            }

            if ( fDumpAuth )
            {
                DumpAuthBuffer( buff.QueryPtr(), "Client" );
            }

            strcat( pch, pchAuthSendHeader );
            strcat( pch, pchSchemes );
            strcat( pch, " " );
            strcat( pch, (CHAR *) buff.QueryPtr() );
            strcat( pch, "\r\n" );
        }

        pchSchemes += strlen(pchSchemes) + 1;
    }
#endif
    return TRUE;
}

//
//  PrintUsage - Usage information
//

void PrintUsage( void )
{
    printf("\n\nhttpcmd version %s - Command line HTTP client\n", HTTPCMD_VERSION );
    printf("usage: httpcmd server input.file [options] \n\n");
    printf("Where input.file contains a full HTTP request\n");
    printf("The server's reply is sent to stdout");
    printf("Options:\n"
           "    -k  - Send Pragma: Keep-connection, input.file contains multiple requests\n"
           "          seperated by " HTTP_REQ_TERM " on a blank line\n"
           "\n"
           "    -u:username:password - Use the specified user name and password for\n"
           "                           authentication purposes\n"
           "\n"
           "    -a:<authentication scheme>, can be \"Basic\", \"NTLM\" or \"MS-KERBEROS\"\n"
           "          Only one NT authentication scheme is currently supported\n"
           "\n"
           "    -e  - Echo send requests to stdout.  Shows what's being requested\n"
           "\n"
           "    -t  - Do HEX-HTTP for filter testing\n"
           "\n"
           "    -h  - Don't echo HTTP headers\n"
           "\n");

}

BOOL FillAddr( PSOCKADDR psin,
               BOOL bConnect)
{
   DWORD dwSize;
   char szTemp[200];
   int protocols[2];

   BYTE buffer[2048];
   DWORD bufferSize;
   char aliasBuffer[512];
   DWORD aliasBufferSize;
   int count;
   PCSADDR_INFO csaddrInfo;
   GUID TcpGuid = SVCID_HOSTNAME;
   GUID SPXGuid = SVCID_NETWARE(12005);
   GUID *pGuid;
   bufferSize = sizeof(buffer);
   aliasBufferSize = sizeof(aliasBuffer);
   LPINET_SERVER_INFO pInetServerInfo;
   DWORD err;

       /*
       *   If we are setting up for a listen() call (bConnect = FALSE),
       *   fill servent with our address.
       */
       if (!bConnect) {

         /*
          *   Retrieve my ip address.  Assuming the hosts file in
          *   in %systemroot%/system/drivers/etc/hosts contains my computer name.
          */

         dwSize = sizeof(szTemp);
         GetComputerName(szTemp, &dwSize);
      }
      else
      {
         strcpy( szTemp, pchServer );
      }

   if ( fUseSPX ) {
       strcat(szTemp, "_HTTP");
       protocols[0] = NSPROTO_SPXII;
       pGuid = &SPXGuid;
   }
   else  {
       protocols[0] = IPPROTO_TCP;
       pGuid = &TcpGuid;
   }

   protocols[1] = 0;

   count = GetAddressByName(
               NS_DEFAULT,
               pGuid,
               szTemp,
               protocols,
               0,
               NULL,
               buffer,
               &bufferSize,
               aliasBuffer,
               &aliasBufferSize
               );

   if (count <= 0) {
       fprintf( stderr,
               "GetAddressByName for machine %s failed with error %d.",
               szTemp,
               GetLastError());
   				
      return FALSE;
   }

   csaddrInfo = (PCSADDR_INFO)buffer;
   memcpy((char FAR *)psin,
           (char FAR *)(csaddrInfo->RemoteAddr.lpSockaddr),
           csaddrInfo->RemoteAddr.iSockaddrLength);

   if ( !fUseSPX ) {
       ((PSOCKADDR_IN) psin)->sin_port = htons(portno);
   }

   return TRUE;
}


//
//  Skip comments.  Comments (marked with "//") must be the first thing
//  in the file and must be at the beginning of the line
//

CHAR * SkipComments( CHAR * pch )
{
    while ( TRUE )
    {
        if ( *pch == '/' )
        {
            pch = strchr( pch, '\n' );
            pch++;
        }
        else
        {
            break;
        }
    }

    return pch;
 }

#if TCP_AUTH
 VOID DumpAuthBuffer( VOID * pbuff, char * pchSrc )
 {
#define NUM_CHARS 16
    BUFFER buftmp;
    DWORD  cb;
    DWORD i, limit;
    CHAR TextBuffer[NUM_CHARS + 1];
    LPBYTE BufferPtr;

    if ( !pbuff )
    {
        printf("No Authorization return buffer\n");
        return;
    }

    TCP_REQUIRE( uudecode( (char *) pbuff,
                           &buftmp,
                           &cb ));

    BufferPtr = (LPBYTE) buftmp.QueryPtr();

    printf("%s: Authorization data - %d bytes:\n", pchSrc, cb );


    //
    // Hex dump of the bytes
    //
    limit = ((cb - 1) / NUM_CHARS + 1) * NUM_CHARS;

    for (i = 0; i < limit; i++) {

        if (i < cb) {

            printf("%02x ", BufferPtr[i]);

            if (BufferPtr[i] < 31 ) {
                TextBuffer[i % NUM_CHARS] = '.';
            } else if (BufferPtr[i] == '\0') {
                TextBuffer[i % NUM_CHARS] = ' ';
            } else {
                TextBuffer[i % NUM_CHARS] = (CHAR) BufferPtr[i];
            }

        } else {

            printf("   ");
            TextBuffer[i % NUM_CHARS] = ' ';

        }

        if ((i + 1) % NUM_CHARS == 0) {
            TextBuffer[NUM_CHARS] = 0;
            printf("  %s\n", TextBuffer);
        }

    }
 }
#endif

DWORD PrintHeaders( BOOL * pfAllHeaders )
{
    CHAR * pch;
    DWORD  cbHeaders;

    //
    //  Find the end of the headers
    //

    pch = bufftmp;
    while ( pch = strchr( pch, '\n' ))
    {
        if ( *(pch = SkipWhite( pch + 1)) == '\n' )
            break;
    }

    if ( pch )
    {
        pch++;
        cbHeaders = pch - bufftmp;
        *pfAllHeaders = TRUE;
    }
    else
    {
        //
        //  We don't have all of the headers yet
        //

        cbHeaders = strlen( bufftmp );
        *pfAllHeaders = FALSE;
    }

    if ( !fDontEchoHeaders )
        fwrite( bufftmp, 1, cbHeaders, stdout );

    return cbHeaders;
}

CHAR * SkipWhite( CHAR * pch )
{
    while ( ISWHITE( *pch ) )
    {
        pch++;
    }

    return pch;
}
