/*
 *   httpget.c - Get a URL.
 *
 *   Compile/link under SunOS with "gcc -O -DSunOS -o httpget httpget.c"
 *   Compile/link under Linux with "gcc -O -DUNIX -o httpget httpget.c"
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "getopt.h"

#define DEFAULT_NET_TIMEOUT 20

#ifdef SunOS
#define UNIX
#endif

#if defined(UNIX)
#include    <unistd.h>
#include    <errno.h>
#include    <sys/types.h>
#include    <sys/stat.h>

#ifdef SunOS
#include    <malloc.h>
#include    <sys/filio.h>
#else
#include    <sys/select.h>
#include    <asm/ioctls.h>
#endif

#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SockClose(x)  close(x)

#ifndef O_BINARY
#define O_BINARY 0
#endif

#endif

#if defined(_CONSOLE)
#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <winsock.h>

#define SockClose(x)    closesocket(x)
#define sleep(x)        Sleep((x)*1000L)
#define NetErrno (WSAGetLastError())
#else
#define NetErrno ((long)errno)
#endif

#if !defined(TRUE)
#define TRUE (0==0)
#endif

#if !defined(FALSE)
#define FALSE (0!=0)
#endif

#define STRNCPY(_d,_s)  strncpy(_d,_s,sizeof _d) ; _d[sizeof _d-1] = 0

#define HTTP_PORT   80

static int debug = FALSE;
static int bForce = FALSE;
static int quiet = FALSE;
static int map = FALSE ;
static int noheader = FALSE ;

typedef struct  {
    int ReadURL ;               /* TRUE if should read URL */
    int Record ;                /* record # 1..n */
    int HttpStatus ;            /* status from HTTP reply record */
#define HTTP_GET_OK 200
#define HTTP_POST_OK 302        /* 302 HTTP/1.1 302 Found */
    int VerMajor ;              /* major version number */
    int VerMinor ;              /* minor version number */
    char    StatusInfo[1024] ;  /* status information */
} HTTP_DECODE_INFO ;

static void
Error(
    char    *sWhat
){
#if defined(UNIX)
#if 1
    if (!quiet) perror(sWhat);
#else
    if (!quiet) fprintf(stderr,"%s: %s\n",sWhat,strerror(errno)) ;
#endif
#endif

#if defined(_CONSOLE)
    if (!quiet) fprintf(stderr,"%s: error %lu\n",sWhat,WSAGetLastError()) ;
#endif
    exit(9) ;
}

static int
DoHttpDecode(
    HTTP_DECODE_INFO    *p,
    char                *sHttp,
    int                 Len
){
    p->Record++ ;                   /* count record */
    if (Len == 0)  {                /* empty */
        return(1) ;             /* signal outer layer to stop reading */
    }
    else if (strncmp(sHttp,"HTTP",4) == 0)  {
        /*
         * if it looks like status info, then go ahead and
         * decode the remainder.  we think it looks like "HTTP/n.m xxx"
         * where n.m is a version, and xxx is some sort of status info.
         */
        STRNCPY(p->StatusInfo,sHttp) ;  /* save for later */
        if (sscanf(&sHttp[4],"/%d.%d %d",&p->VerMajor,&p->VerMinor,
            &p->HttpStatus) == 3)  {    /* looks good */
            if ((p->HttpStatus == HTTP_GET_OK) ||
                (p->HttpStatus == HTTP_POST_OK))  {
                p->ReadURL = TRUE ;
            }
        }
    }
    else  {
        /* add processing of other records here. */
    }
    return(0) ;                     /* keep reading */
}

static int
RecvWithTimeout(
    int     Socket,
    char    *Buffer,
    int     Len,
    long    Timeout,
    int     *bTimedOut
){
    fd_set  ReadSet ;
    int     n ;
    struct timeval Time ;

    FD_ZERO(&ReadSet) ;
    FD_SET(Socket,&ReadSet) ;
    Time.tv_sec = Timeout ;
    Time.tv_usec = 0 ;
    *bTimedOut = FALSE ;
    n = select(Socket+1,&ReadSet,NULL,NULL,&Time) ;
    if (n > 0)  {                   /* got some data */
        return(recv(Socket,Buffer,Len,0)) ;
    }
    if (n == 0)  {                  /* timeout */
        *bTimedOut = TRUE ;
    }
    return(n) ;                     /* trouble */
}

static unsigned char *
ReadPostData(
    int     map_special_chars,
    char    *sFile,
    int     *nBytes 
){
#   define POST_CHUNK_BYTES 2048
    int fd ;
    int nread ;
    int quote = TRUE ;
    int n ;
    int ch ;
    int buf_size ;
    unsigned char *buf ;
    unsigned char *pbuf ;

    *nBytes = 0 ;
    if (sFile == NULL) return NULL ;

    buf_size = POST_CHUNK_BYTES ;
    if (!(buf = (unsigned char *)malloc(buf_size))) {
        Error("no memory") ;
        return NULL ;
    } else {
        pbuf = buf ;
    }
    if (strcmp(sFile,"-") == 0) {
        fd = 0 ;
    }
    else {
        if ((fd = open(sFile, O_RDONLY | O_BINARY)) < 0)  {
            if (!quiet) {
                fprintf(stderr, "can't open POST file ") ;
                Error(sFile) ;
                free(buf) ;
                return NULL ;
            }
        }
    }
    while (1) {
        n = pbuf - buf ;
        if ((buf_size-n+3) < POST_CHUNK_BYTES) {
            buf_size += POST_CHUNK_BYTES ;
            if (!(buf = (unsigned char *)realloc(buf,buf_size))) {
                Error("no memory") ;
                free(buf) ;
                return NULL ;
            } else {
                pbuf = buf + n ;
            }
        }
        if (map_special_chars) {
            nread = 1 ;
        } else {
            nread = buf_size-(pbuf-buf) ;
        }
        if ((n=(int)read(fd,pbuf,nread)) < 0)  {
            if (!quiet) Error("read POST file") ;
            close(fd) ;
            free(buf) ;
            return NULL ;
        }
        if (n == 0) break ;
        if (quote == TRUE && map_special_chars) {
            ch = *pbuf ;
            if (ch == ' ') {
                *pbuf = '+' ;
            } else if (ch == '\n') {
                *pbuf = '&' ;
            } else if (ch == '\r') {
                n = 0 ;
            } else if (ch == '\\') {
                quote = FALSE ;
                n = 0 ;
            } else if (ch == '@') {
                ;
            } else if (ch == '=') {
                ;
            } else if (ch == '_') {
                ;
            } else if (ch == '*') {
                ;
            } else if (ch >= '-' && ch <= '9') {
                ;
            } else if (ch >= 'A' && ch <= 'Z') {
                ;
            } else if (ch >= 'a' && ch <= 'z') {
                ;
            } else {
                sprintf(pbuf, "%%%02X", ch) ;
                n = 3 ;
            }
        } else {
            quote = TRUE ;
        }
        pbuf += n ;
    }
    close(fd) ;
#ifdef ADD_TERMINATOR
    *(pbuf++) = '\r';
    *(pbuf++) = '\n';
#endif
    *nBytes = pbuf - buf ;
    return buf ;
}

static int
SetSockBlock(
    int     Socket,
    int     iBlocking
){
    u_long Nbio ;

    if (iBlocking)  {
        Nbio = 0 ;
    }
    else  {
        Nbio = 1 ;
    }
#if defined(WIN32)
    return(ioctlsocket(Socket,FIONBIO,&Nbio)) ;
#else
    return(ioctl(Socket,FIONBIO,&Nbio)) ;
#endif
}

#define WR_READ 1
#define WR_WRITE 2
#define WR_EXCEPT 4

static int
WaitReady(
    int Socket,
    int Timeout,
    int which
){
    fd_set  ReadSet ;
    int     n ;
    struct timeval Time ;
    fd_set  *pRead = NULL ;
    fd_set  *pWrite = NULL ;
    fd_set  *pExcept = NULL ;

    if (which&WR_READ)    pRead = &ReadSet ;
    if (which&WR_WRITE)   pWrite = &ReadSet ;
    if (which&WR_EXCEPT)  pExcept = &ReadSet ;

    FD_ZERO(&ReadSet) ;
    FD_SET(Socket,&ReadSet) ;
    Time.tv_sec = Timeout ;
    Time.tv_usec = 0 ;
    n = select(Socket+1,pRead,pWrite,pExcept,&Time) ;
    if (n > 0)  {                   /* got some data */
        return(0) ;
    }
    if (n == 0)  {                  /* timeout */
#if defined(WIN32)
        WSASetLastError(WSAETIMEDOUT) ;
#else
        errno = ETIMEDOUT ;
#endif
        return(1) ;
    }
    return(-1) ;
}

main(
    int argc,
    char *argv[]
){
    long        TimeoutSeconds = DEFAULT_NET_TIMEOUT ;
    int         bTimeout = FALSE ;
    int         bAddResultHdr = FALSE;
    int         bExit = 0 ;
    char        *sURL ;
    char        *sName ;
    int         Socket ;
    char        *s ;
    char        *sSocks = NULL ;
    char        *sProxy = NULL ;
    char        *sPost = NULL ;
    char        *sPostData = NULL ;
    char        *sCookie = NULL ;
    char        *sRefer = NULL ;
    int         nPost = 0 ;
    int         n ;
    char        sURLHost[256] ;
    char        sHost[256] ;
    char        sExtra[128] ;
    char        sCookieText[1024] ;
    char        sReferText[1024] ;
    char        sGenericText[1024] = "";
    int         iConnTimeout = 0 ;
    int         ConnStatus ;
    struct      sockaddr_in SockAddr ;
    struct      hostent *pHostEnt ;
    struct  {
        unsigned char vn ;
        unsigned char fc ;
        unsigned short port ;
        unsigned long addr ;
        char username[32] ;
    } SocksConnect = { 4, 1, 0, 0, "" } ;
    struct  {
        unsigned char vn ;
        unsigned char cd ;
        unsigned short port ;
        unsigned long addr ;
    } SocksReply ;
#if defined(_CONSOLE)
    WORD        wVer ;
    WSADATA     wsaData ;
#endif
    struct linger linger;
    char    sHttp[1024] ;
    int     c ;
    HTTP_DECODE_INFO    Http ;

    linger.l_onoff = 1 ;
    linger.l_linger = 5 ;

#if defined(_CONSOLE)
    wVer = MAKEWORD(1,1) ;                  // request WinSock version 1.1
    if (WSAStartup(wVer,&wsaData) != 0)  {  // if startup failed
        return(2) ;
    }
#endif

#define CMD_SWITCHES "drfqmhp:s:t:P:c:C:R:g:?"
    while ((c = getopt(argc,argv,CMD_SWITCHES)) != EOF)  {
        switch (c)  {
            case 'd':  {
                debug = 1 ;
#if defined(WIN32)
                setvbuf(stdout,NULL,_IOLBF,0) ;
                setvbuf(stderr,NULL,_IOLBF,0) ;
#endif
                break ;
            }
            case 't':  {
                iConnTimeout = atoi(optarg) ;
                break ;
            }
            case 'f':  {
                bForce = TRUE ;
                break ;
            }
            case 'q':  {
                quiet = TRUE ;
                break ;
            }
            case 'm':  {
                map = TRUE ;
                break ;
            }
            case 'h':  {
                bAddResultHdr = TRUE ;
                break ;
            }
            case 's':  {
                sSocks = optarg ;
                break ;
            }
            case 'p':  {
                sProxy = optarg ;
                break ;
            }
            case 'C':  {
                TimeoutSeconds = atoi(optarg) ;
                break ;
            }
            case 'P':  {
                sPost = optarg ;
                break ;
            }
            case 'c':  {
                sCookie = optarg ;
                break ;
            }
            case 'R':  {
                sRefer = optarg ;
                break ;
            }
            case 'r':  {
                noheader = 1 ;
                break ;
            }
            case 'g':  {
		if ( (strlen(sGenericText) + strlen(optarg) + 3) 
		     > sizeof(sGenericText)
		   )
		{
			fprintf( stderr, "ABORT: sGenericText overrun\n" );
			exit(1);
		}
		sprintf( sGenericText, "%s%s\r\n", sGenericText, optarg );
                break ;
            }
            default:  {
                goto Usage ;
            }
        }
    }
    if (argc < (optind+1))  {
Usage: ;
        fprintf(stderr,
            "Usage:\t httpget [options] URL\n"
            "options:\n"
            "   -d               - set debug mode\n"
            "   -q               - set quiet mode (no error printouts)\n"
            "   -f               - force data read on bad HTML\n"
            "   -h               - add HTTP result header to output stream\n"
            "   -s host[:port]   - use SOCKS server \"host\" (optional port)\n"
            "   -p host[:port]   - use proxy server \"host\" (optional port)\n"
            "   -C secs          - set network read timeout in seconds\n"
            "   -t secs          - set network connect timeout in seconds\n"
            "   -P file          - POST file to URL (use \"-\" for stdin)\n"
            "   -m               - map special characters in POST data (\\ quotes)\n"
            "   -c cookie        - send cookie string with request\n"
            "   -R referringURL  - send referring URL with request\n"
            "   -g generic       - include specified generic header\n"
            "where URL is of the form \"http://host[:port]/path\"\n"
            );
        return(10) ;
    }

    if (sPost != NULL) {
        if ((sPostData = ReadPostData(map,sPost,&nPost)) == NULL) {
            return(4) ;
        }
        if (debug) fprintf(stderr,"%d bytes read from POST file %s\n",nPost,sPost) ;
        bAddResultHdr = TRUE ;
    }
    memset((void *)&SockAddr,0,sizeof SockAddr) ;   /* zero sockaddr */
    SockAddr.sin_family = AF_INET ;
    SockAddr.sin_port = htons(HTTP_PORT) ;

    sURL = argv[optind] ;
    if ((s=strstr(argv[optind],"//")) != NULL)  {
        s += 2 ;                     /* skip leading stuff - better be http:// */
    }
    else  {
        s = argv[optind] ;
    }
    if ((sName=strchr(s,'/')) == NULL)  {       /* save URL "name part" */
        sName = "/" ;
    }
    STRNCPY(sHost, s);                          /* isolate URL host name */
    if ((s=strchr(sHost,'/')) != NULL)  {
        *s = 0 ;
    }
    STRNCPY(sURLHost, sHost);                   /* save URL host name/port */
    if (sProxy != NULL) {                       /* switch to proxy server? */
        STRNCPY(sHost, sProxy);
    }
    if (debug) fprintf(stderr,"Host: %s, URL Host: %s, Name: %s\n",sHost,sURLHost,sName) ;
    if ((s = strchr(sHost,':')) != NULL)  {     /* isolate target host port */
        *s++ = 0 ;
        SockAddr.sin_port = htons((u_short)atoi(s)) ;
    }
    if (isdigit(sHost[0]))  {     /* by address */
        SockAddr.sin_addr.s_addr = inet_addr(sHost) ;
    }
    else  {                         /* by name - need resolver */
        pHostEnt = gethostbyname(sHost) ;
        if (pHostEnt == NULL)  {    /* hostname lookup error */
            fprintf(stderr,"%s: unknown host\n",sHost) ;
            return(1) ;
        }
        SockAddr.sin_addr =
            *((struct in_addr *)pHostEnt->h_addr_list[0]) ;
    }
    if (sSocks != NULL)  {
        char *user ;
        SocksConnect.port = SockAddr.sin_port ;
        SocksConnect.addr = SockAddr.sin_addr.s_addr ;
        user = getenv("USER") ;
        if (!user) user = "SSL" ;
        STRNCPY(SocksConnect.username,user) ;
        SocksConnect.username[sizeof(SocksConnect.username)-1] = 0 ;
        SockAddr.sin_port = htons(1080) ;
        s = strchr(sSocks,':') ;
        if (s != NULL)  {
            *s++ = 0 ;
            SockAddr.sin_port = htons((u_short)atoi(s)) ;
        }
        if (isdigit(sSocks[0]))  {     /* by address */
            SockAddr.sin_addr.s_addr = inet_addr(sSocks) ;
        }
        else  {                         /* by name - need resolver */
            pHostEnt = gethostbyname(sSocks) ;
            if (pHostEnt == NULL)  {    /* hostname lookup error */
                fprintf(stderr,"%s: unknown host\n",sSocks) ;
                return(2) ;
            }
            SockAddr.sin_addr =
                *((struct in_addr *)pHostEnt->h_addr_list[0]) ;
        }
    }

    Socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) ;
    if (Socket != -1)  {
        if (setsockopt(Socket, SOL_SOCKET, SO_LINGER, (char*)&linger,
            sizeof(linger)) < 0)  {
            Error("setting linger") ;
        }
        if (sSocks == NULL && iConnTimeout != 0)  {
            int iLen = sizeof ConnStatus ;
            SetSockBlock(Socket,0) ;    /* non-blocking */
            ConnStatus = connect(Socket,(struct sockaddr *)&SockAddr,
                sizeof SockAddr) ;
            if (debug)  {
                fprintf(stderr,"NB connect status %lu\n",NetErrno) ;
            }
            SetSockBlock(Socket,1) ;    /* back to blocking */
            ConnStatus = WaitReady(Socket,iConnTimeout,WR_WRITE) ;
            if (ConnStatus > 0)  {
ConnectionTimeout: ;
                if (debug)  {
                    fprintf(stderr,"Connection timeout.\n") ;
                }
                ConnStatus = -1 ;
            }
        }
        else  {
            ConnStatus = connect(Socket,(struct sockaddr *)&SockAddr,
                sizeof SockAddr) ;
        }
        if (ConnStatus == 0)  {
            if (sSocks != NULL)  {
                send(Socket,(void *)&SocksConnect,(sizeof SocksConnect
                    -sizeof SocksConnect.username)+
                    strlen(SocksConnect.username)+1,0) ;
                if (iConnTimeout != 0)  {
                    if (WaitReady(Socket,iConnTimeout,WR_READ) > 0)  {
                        goto ConnectionTimeout ;
                    }
                }
                n = recv(Socket,(void *)&SocksReply,sizeof SocksReply,0) ;
                if (n == sizeof SocksReply && SocksReply.cd == 90)  {
                    if (debug) fprintf(stderr,"Connected via Socks gateway %s\n",sSocks) ;
                }
                else  {
                    fprintf(stderr,"Socks connect failed.\n") ;
                    linger.l_onoff = 1 ;
                    linger.l_linger = 0 ;
                    if (setsockopt(Socket, SOL_SOCKET, SO_LINGER, (char*)&linger,
                        sizeof(linger)) < 0)  {
                        Error("setting linger") ;
                    }
                    goto bomb ;
                }
            }

            if (sPost != NULL)  {
                sprintf(sExtra,
                    "Content-type: application/x-www-form-urlencoded\r\n"
#ifdef ADD_TERMINATOR
                    "Content-length: %d\r\n",nPost - 2);
#else
                    "Content-length: %d\r\n",nPost);
#endif
            }
            if (sCookie != NULL)  {
                sprintf(sCookieText, "Cookie: %s\r\n",sCookie);
            }
            if (sRefer != NULL)  {
                sprintf(sReferText, "Referer: %s\r\n",sRefer);
            }
            sprintf(sHttp,
                "%s %s HTTP/1.0\r\n"
                "%sUser-Agent: Mozilla/2.0 (Win95; I)\r\n"
                "Pragma: no-cache\r\n"
                "Host: %s\r\n"
                "Accept: */*\r\n"
                "%s%s%s%s\r\n%n",
                (sPost == NULL) ? "GET" : "POST",
                (sProxy == NULL) ? ((sPost == NULL) ? sName : sURL) : sURL,
                (sProxy == NULL && sSocks == NULL) ? "" : "Proxy-Connection: Keep-Alive\r\n",
                sURLHost,
                (sRefer == NULL) ? "" : sReferText,
                (sCookie == NULL) ? "" : sCookieText,
                sGenericText,
                (sPost == NULL) ? "" : sExtra,
                &n) ;
            if (debug) fprintf(stderr,"%s",sHttp) ;
            if (!noheader) {
                if (send(Socket,sHttp,n,0) != n)  {
                    if (!quiet) Error("send") ;
bomb: ;
                    shutdown(Socket,2) ;
                    SockClose(Socket) ;
                    return(3) ;
                }
            }
            /*
             * POST any form data
             */
            if (sPost != NULL)  {
                if (debug)  {
                    fflush(stderr) ;
                    write(2,sPostData,nPost);
                }
                if (debug) fprintf(stderr,"POSTING %d bytes...\n",nPost) ;
                if (send(Socket,sPostData,nPost,0) != nPost)  {
                    if (!quiet) Error("send POST data") ;
                    free(sPostData) ;
                    goto bomb ;
                }
                free(sPostData) ;
            }
            /*
             * loop through LF delimited records and process each one.
             * ignore CRs completely.  pass each record to the decoder and
             * let it tell us when we're done.
             */
            memset(&Http,0,sizeof Http) ;
            n = 0 ;
            if (!noheader)  {
                while (n < sizeof sHttp &&
                    RecvWithTimeout(Socket,&sHttp[n],1,
                    TimeoutSeconds,&bTimeout) > 0)  {
                    if (bAddResultHdr)  {
                        fprintf(stdout,"%c",sHttp[n]) ;
                    }
                    else if (debug)  {
                        fprintf(stderr,"%c",sHttp[n]) ;
                        fflush(stderr) ;
                    }
                    if (sHttp[n] == '\r')  {    /* CR - must be MicrosOffed */
                        continue ;
                    }
                    if (sHttp[n] == '\n')  {    /* another record */
                        sHttp[n] = 0 ;
                        if (DoHttpDecode(&Http,sHttp,n) != 0)  {
                            break ;             /* stop decoding */
                        }
                        n = 0 ;
                    }
                    else  {
                        n++ ;
                    }
                }
                if (!bTimeout && !Http.ReadURL)  {
                    if (!quiet) {
    			fprintf(stderr,"Bad status: %d %s\n",
                                    Http.HttpStatus,Http.StatusInfo) ;
    			fflush(stderr) ;
                    }
                    bExit = 1 ;
                }
            }
            else  {
                Http.ReadURL = TRUE ;
            }
            /*
             * now, if we are supposed to read this URL, then go ahead and
             * copy it to stdout - in binary mode.
             */
            if (bForce || (Http.ReadURL && !bTimeout))  {            
#if defined(_CONSOLE)
                _setmode(fileno(stdout),O_BINARY) ;
#endif
                while ((n=RecvWithTimeout(Socket,sHttp,sizeof sHttp,
                    TimeoutSeconds,&bTimeout)) > 0)  {
                    if ((int)fwrite(sHttp,1,n,stdout) != n)  {
                        if (!quiet) Error("stdout") ;
                        goto bomb ;
                    }
                }
            }
            if (bForce)
                printf("\n") ;
            if (n < 0)  {
               Error("reading URL") ;
            }
        }
        else  {
            Error(sURL) ;
        }
        SockClose(Socket) ;
    }
    else  {
        Error("socket") ;
    }
    if (bTimeout)  {
        bExit = 1 ;
        if (!quiet) fprintf(stderr,"Network read timeout.\n") ;
    }
    return(bExit) ;
}
