/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    get_sock.c

Abstract:

    This does a get using raw sockets

History:

    09-Nov-1995     Created
    15-Feb-1996     Added authentication support

--*/

#include <windows.h>
#include <winsock.h>

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "httpauth.h"

// globals

#define HD_AUTHENTICATE         "WWW-Authenticate:"
#define HD_LENGTH               "Content-Length:"
#define HD_CONNECTION           "Connection:"

// list of Authentication methods to support

char      achAuth[256];


// Helper functions

void SkipWhite(PSTR *ppS )
{
    PSTR pS = *ppS;

    while ( *pS && isspace(*pS) )
        ++pS;

    *ppS = pS;
}


void SkipNonWhite(PSTR *ppS )
{
    PSTR pS = *ppS;

    while ( *pS && !isspace(*pS) )
        ++pS;

    *ppS = pS;
}



BOOL
HttpGetSocket(
    char * Verb,
    char * Server,
    char * URL,
    BOOL   DisplayHeaders,
    DWORD  ClientDataSize,
    PSTR   pchUserName,
    PSTR   pchPassword,
    PSTR   pszStore,
    PSTR   pszPref
    )
/*++

 Routine Description:

    Issue a command to a HTTP server using authentication

 Arguments:

    Verb                HTTP command : GET / HEAD / ...
    Server              server name
    URL                 URL to send to the server
    DisplayHeaders      TRUE to display headers as received
    ClientDataSize      number of bytes to send in the request
    pchUserName         user name for authentication
    pchPassword         password for authentication
    pszStore            file name where to dump reply from server

 Return Value:

    Returns TRUE is successful; otherwise FALSE is returned.

--*/
{
    char               ReceiveBuffer[8*1024];
    int                Error;
    BYTE               Request[1024];
    int                RequestSize;
    char *             AcceptTypes[2] = {"*/*", NULL};
    SOCKET             Socket = INVALID_SOCKET;
    WSADATA            WsaData;
    struct sockaddr_in Address;
    struct sockaddr_in AddressC;
    struct hostent *   HostEntry;
    char               Headers[] = 
                           "HTTP/1.0\r\n"
                           "User-Agent: AuthClient\r\n"
                           "Accept: */*\r\n";
    char               CrLf[] = "\r\n";
    BYTE               ClientData[64*1024];
    BOOL               fKeepAlive = FALSE;
    int                cRec;
    DWORD              cLen;
    BOOL               fInHeader;
    PSTR               pchAuthData;
    BOOL               fServerKeepAlive = FALSE;
    BOOL               fNeedMoreData;
    BOOL               fNeedAuthenticate;
    PSTR               pH;
    PSTR               pN;
    BOOL               fStatusLine;
    int                Status = -1;
    DWORD              cToRead;
    PSTR               paAuth = achAuth;
    BOOL               fSt = FALSE;
    int                hnd = EOF;

    Error = WSAStartup (0x101, &WsaData);

    if (Error == SOCKET_ERROR) 
    {
        fprintf(stderr, "Error in WSAStartup = %d\n", GetLastError());
        return FALSE;
    }

    if ( !InitAuthorizationHeader() )
    {
        fprintf(stderr, "Cannot initialize security module\n" );
        return FALSE;
    }

    memset( achAuth, '\0', sizeof(achAuth) );
    pchAuthData = NULL;
    fNeedAuthenticate = FALSE;

    AddressC.sin_addr.s_addr = (unsigned long)-1;

    //
    // Connect to the server
    //

    if  ( pszStore != NULL )
        if ( (hnd = _open( pszStore, _O_BINARY | _O_CREAT | _O_TRUNC | _O_RDWR, S_IREAD|S_IWRITE )) == EOF )
        {
            fprintf( stderr, "Can't create file %s\n", pszStore );
            return FALSE;
        }

again:

    if ( Socket == INVALID_SOCKET )
    {
        Socket = socket(AF_INET, SOCK_STREAM, 0);

        if (Socket == INVALID_SOCKET) 
        {
            fprintf(stderr, "Error creating socket = %d\n", GetLastError());
            fSt = FALSE;
            goto ex;
        }

        Address.sin_family = AF_INET;
        Address.sin_port = 0;
        Address.sin_addr.s_addr = INADDR_ANY;

        Error = 
        bind(
            Socket,
            (struct sockaddr *) &Address,
            sizeof(Address));

        if (Error) 
        {
            fprintf(stderr, "Error in bind = %d\n", GetLastError());
            fSt = FALSE;
            goto ex;
        }

        if ( AddressC.sin_addr.s_addr == -1 )
        {
            AddressC.sin_family = AF_INET;
            AddressC.sin_port = htons(80);
            AddressC.sin_addr.s_addr = inet_addr(Server);

            if (AddressC.sin_addr.s_addr == -1) 
            {
                //
                // Must be a server name
                //

                HostEntry = gethostbyname(Server);

                if (HostEntry == NULL) 
                {
                    printf("unable to resolve %s\n", Server);
                    fSt = FALSE;
                    goto ex;
                } else 
                {
                    AddressC.sin_addr.s_addr = *((unsigned long *) HostEntry->h_addr);
                }
            }
        }

        Error =
        connect(
            Socket,
            (struct sockaddr *) &AddressC,
            sizeof(AddressC));

        if (Error) 
        {
            fprintf(stderr, "Error connecting to %s = %d\n", Server, GetLastError());
            fSt = FALSE;
            goto ex;
        }
    }

    //
    // Send the client request
    //

    strcpy(Request, Verb);
    strcat(Request, " ");
    strcat(Request, URL);
    strcat(Request, " ");
    strcat(Request, Headers);
    if (ClientDataSize) 
    {
        sprintf(ClientData, "Content-Length: %d\r\n", ClientDataSize);
        strcat(Request, ClientData);
    }
    if ( fKeepAlive )
    {
        strcat(Request, "Connection: Keep-Alive\r\n" );
    }

    if ( !AddAuthorizationHeader( Request + strlen(Request), achAuth, pchAuthData, pchUserName, pchPassword, &fNeedMoreData ) )
    {
        printf( "Authentication failed\n" );
        fSt = FALSE;
        goto ex;
    }

    strcat(Request, CrLf);

    RequestSize = strlen(Request);

    Error =
    send(
        Socket,
        Request,
        RequestSize,
        0);

    if (Error != RequestSize) 
    {
        printf("Error in client send = %d, %d\n", Error, GetLastError());
        fSt = FALSE;
        goto ex;
    }

    if (ClientDataSize) 
    {
        memset( ClientData, ' ', ClientDataSize );

        //
        // Send the client data
        //

        Error =
        send(
            Socket,
            ClientData,
            ClientDataSize,
            0);

        if ( (DWORD)Error != ClientDataSize) 
        {
            printf("Error in client send = %d, %d\n", Error, GetLastError());
            fSt = FALSE;
            goto ex;
        }
    }

    // parse status & header

    cLen = (DWORD)-1;
    fInHeader = TRUE;
    fServerKeepAlive = FALSE;
    fNeedAuthenticate = FALSE;

    for ( pH = ReceiveBuffer, fStatusLine = TRUE ; fInHeader ; )
    {
        cRec = recv( Socket, pH, ReceiveBuffer+sizeof(ReceiveBuffer)-pH, 0 );
        if ( cRec <= 0 )
        {
            closesocket( Socket );
            Socket = INVALID_SOCKET;
            break;
        }
        pH[ cRec ] = '\0';

        // Iterate on header fields

        while ( pN = strstr(pH, "\r\n" ) )
        {
            *pN = '\0';

            if ( DisplayHeaders )
            {
                printf( "%s\n", pH );
            }

            if ( fStatusLine )
            {
                // This is the status line, decode status

                SkipNonWhite( &pH );
                SkipWhite( &pH );
                Status = atoi( pH );
                if ( Status == 401 )
                {
                    fNeedAuthenticate = TRUE;
                }
                fStatusLine = FALSE;
            }
            else if ( pN == pH )    // end of header fields
            {
                if ( hnd != EOF )
                    write( hnd, pH+2, ReceiveBuffer+cRec-pH-2 );

                cLen -= ( ReceiveBuffer+cRec-pH-2 );
                fInHeader = FALSE;
                break;
            }
            else if ( !strnicmp( pH, HD_AUTHENTICATE, sizeof(HD_AUTHENTICATE)-1 ) )
            {
                SkipNonWhite( &pH );
                SkipWhite( &pH );

                // check if we are already in the authentication sequence

                if ( !IsInAuthorizationSequence() )
                {
                    // append to list of supported authentication methods

                    strcpy( paAuth, pH );
                    paAuth += strlen( pH ) + 1;
                }
                else
                {
                    // store pointer to authenticate blob

                    SkipNonWhite( &pH );
                    SkipWhite( &pH );
                    pchAuthData = pH;
                }
            }
            else if ( !strnicmp( pH, HD_LENGTH, sizeof(HD_LENGTH)-1 ) )
            {
                // get content length

                SkipNonWhite( &pH );
                SkipWhite( &pH );
                cLen = atoi( pH );
            }
            else if ( !strnicmp( pH, HD_CONNECTION, sizeof(HD_CONNECTION)-1 ) )
            {
                // check for keep-alive flag

                SkipNonWhite( &pH );
                SkipWhite( &pH );
                if ( !strnicmp( pH, "Keep-Alive", sizeof("Keep-Alive")-1 ) )
                    fServerKeepAlive = TRUE;
            }

            pH = pN + 2;
        }
    }

    // add final delimiter to list of supported authentication methods

    if ( !IsInAuthorizationSequence() && fNeedAuthenticate )
    {
        *paAuth = '\0';

        // Insure specified methods are supported localy

        if ( !ValidateAuthenticationMethods( achAuth, pszPref ) )
        {
            // None of the server specified authentication methods
            // are supported localy.

            SetLastError( ERROR_ACCESS_DENIED );

            fprintf( stderr, "No supported authentication method\n" );

            fSt = FALSE;
            goto ex;
        }
    }

    // read message body

    if ( Socket != INVALID_SOCKET )
    {
        for ( ; cLen ; )
        {
            if ( (cToRead = sizeof(ReceiveBuffer)) > cLen )
                cToRead = cLen;
            cRec = recv( Socket, ReceiveBuffer, cToRead, 0 );

            if ( cRec <= 0 )
            {
                closesocket( Socket );
                Socket = INVALID_SOCKET;
                break;
            }
            if ( hnd != EOF )
                write( hnd, ReceiveBuffer, cRec );

            cLen -= cRec;
        }
    }

    if ( !fServerKeepAlive )
    {
        if ( IsInAuthorizationSequence() )
        {
            fprintf( stderr, "Authentication rejected by server\n" );

            fNeedAuthenticate = FALSE;  // authentication failed
        }

        closesocket( Socket );
        Socket = INVALID_SOCKET;
    }

    if ( fNeedAuthenticate )
    {
        fKeepAlive = TRUE;
        goto again;
    }

    if ( Socket != INVALID_SOCKET )
        closesocket(Socket);

    fSt = Status == 200;

ex:
    TerminateAuthorizationHeader();

    if ( hnd != EOF )
        close( hnd );

    return fSt;
}
