/* httpget -- use the http protocol to download files */

/** Ideas:
 *  Need to escape characters in restp: [% ] + ctrl + highctrl
 *  Should this be able to get more than one url at a time?
 *    If so, how should argument order be handled?
 *  Should this support server push?
 *    If so, pretend we are Mozilla.
 *  This could support If-Modified-Since, Head, and POST
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>		/* struct sockaddr_in */
#include <netdb.h>		/* struct hostent */

char *Progname = "sockmsg";

char *Proxy=0;
char *HeaderFileName=0, *OutFileName=0;
int F_headonly=0;
int F_verbose=1;
int F_stat=1;
int F_saveheader=0;
int F_progress=0;  /* autodetect */

int Sock;

void usage()
{
  fprintf(stderr,"Usage:  %s [options] URL\n"
"options:\n"
"  -o outputfile   save document to file instead of sending to stdout\n"
"  -q              don't print anything\n"
"  -s		   print file transfer progress stats (sometime default\n"
"  -t              print transfer time statistics when done (default)\n"
"  -v              increase verboseness to improve interactivity\n"
"  -H		   only get header\n"
"  -h [outputfile] save header as well as body; in a separate file if given\n"
"  -p proxy        proxy host in URL format or host:port format\n"
	  ,Progname);
  exit(1);
}

/* pass restp as 0 if you don't need it */
void parseurl(char*url, char**hostp, unsigned short *portp,char **restp)
{
  char *s,*t;
  int i;

  /* take care of protocol type */
  if (strncmp(url,"http://",6)){
    fprintf(stderr,"Url '%s' not accepted.  Only protocol 'http' supported.\n", url);
    exit(1);
  } else
    url += 7;

  /* find end of host:port */
  s = strchr(url,'/');
  if (!s) 
    s = url+strlen(url);
  /* is there a port? */
  t = strchr(url,':');
  if (t && t<s) {
    *portp = atoi(t+1);
    i = t - url;
  } else {
    i = s - url;
    *portp = 80;
  }

  /* copy the hostname */
  *hostp = malloc(i+1);
  strncpy(*hostp, url,i);
  (*hostp)[i]=0;

  /* return the remainder */
  if (restp) 
    *restp = s;			/* this should look for things to escape */
}

#define BufLen 1024
char wbuf[BufLen];
int wbuflen=0;


int safeflushsome()
{
  int i;

  if (!wbuflen) return 0;	/* nothing to write */
  i = write(Sock,wbuf,wbuflen);
  if (i<0){
    perror("Write failed");
    exit(1);
  }
  if (F_verbose>3)
    write(2,wbuf,wbuflen);
  if (i<wbuflen){
    memmove(wbuf,wbuf+i,wbuflen-i);
    wbuflen -= i;
  } else
    wbuflen=0;
  return wbuflen;
}

void safeflush()
{
  while (safeflushsome())
    if (F_verbose)
      write(1,".",1);
}

void safewrite(char* str)
{
  int l,i;

  l=strlen(str);
  while (l){
    if (wbuflen+l<BufLen) {	/* str will fit in wbuf */
      memcpy(wbuf+wbuflen,str,l);
      wbuflen += l;
      l=0;
    } else if (wbuflen) {	/* wbuf is not empty and str can fill it */
      if (wbuflen<BufLen){	/* if wbuf not full, fill it */
	i = BufLen - wbuflen;	
	/* ASSERT: i<=l since wbuflen+l>=BufLen */
	memcpy(wbuf+wbuflen,str,i);
	str += i;
	wbuflen+=i;
	l -= i;
      }
      safeflushsome();
    } else {	     /* str is too big and wbuf is empty--write in place */
      /* ASSERT: l>BufLen && wbuflen==0 */
      while (l>BufLen){
	i = write(Sock,str,l);	/* write as much as possible */
	if (i<0){
	  perror("Write error");
	  exit(1);
	}
	str += i;
	l -= i;
	if (l && F_verbose) write(1,".",1);
      }
    }
  }
}

char rbuf[BufLen+1];
char *rbufs=rbuf, *rbufe=rbuf;

int justifyrbuf()
{
  int i=0;

  if (rbufe<=rbufs)
    rbufe=rbufs=rbuf;
  else {
    memmove(rbuf,rbufs,i=rbufe-rbufs);
    rbufs = rbuf;
    rbufe = rbuf + i;
  }
  return i;
}

char *safereadline()
{
  char *s,*r;
  int i;

  while(1){
    if (rbufs<rbufe){		/* rbuf not empty */
      for (s=rbufs; s<rbufe; s++){
	if (*s=='\r' || *s=='\n')
	  break;
      }
      if (*s=='\n' || (*s=='\r' && s<rbufe-1)){
	/* found a complete line */
	r = rbufs;
	if (*s=='\n'){
	  rbufs = s+1;
	  *s=0;
	} else if (s[1]=='\n'){
	  /* ASSERT: *s == '\r' */
	  *s = s[1] = 0;
	  rbufs = s+2;
	}
	return r;
      }
      /* else, there isn't a complete line in the buffer */
      if (rbufs==rbuf && rbufe==rbuf+BufLen){
	/* buffer is full, and no complete line */
	/* this line is too long--pretend it is complete */
	rbuf[BufLen]=0;		/* mark the end */
	rbufs=rbufe=rbuf;	/* mark as emtpy */
	return rbuf;		/* return the whole thing */
      }
    }
    if (rbufs>=rbufe)		/* buffer is empty */
      rbufs = rbufe = rbuf;
    if (rbufs!=rbuf) {		/* move buffer to front */
      memmove(rbuf,rbufs,i=rbufe-rbufs);
      rbufs = rbuf;
      rbufe = rbuf + i;
    }
    i=read(Sock,rbufe,BufLen-(rbuf-rbufe)); /* fill the buffer */
    if (i<0){
      perror("Read error");
      exit(1);
    }
    rbufe += i;
    if (i==0){			/* nothing read--socket dead? */
      if (rbufe>=rbuf+BufLen)	/* mark end of buffer */
	rbuf[BufLen]=0;
      else
	*rbufe=0;
      if (rbufe>rbufs){		/* return what we have */
	rbufe=rbufs;
	return rbufs;
      } else
	return 0;		/* return NULL */
    }
  }
}

int
main(int argc,char *argv[])
{
  unsigned int a,b,c,d;
  struct sockaddr_in address;
  char *host,*url=0, *geturl;
  char *s;
  unsigned short port;
  int i,j,filesize=0,datasize=0,dot=1024;
  int exitval=0;
  long starttime;
  FILE *out=0,*headerout=0;

/* verify and parse command line options */
  if (argv && argv[0]) Progname = argv[0];

  { char **ap;
    for (ap=argv+1; *ap; ap++){
      if (**ap=='-'){
	switch(ap[0][1]){
	 case 'o':		/* output file */
	  if (!*++ap || **ap=='-') usage();
	  OutFileName= *ap;
	  break;
	 case 'H':              /* header only */
	  F_headonly++;
	  F_saveheader++;
	  break;
	 case 'h':		/* save header */
	  F_saveheader++;
	  if (ap[1] && (ap[1][0]!='-' || !ap[1][1]))
	    HeaderFileName = *++ap;
	  break;
	 case 'p':		/* proxy */
	  if (!*++ap || **ap=='-') usage();
	  Proxy = *ap;
	  break;
	 case 'q':		/* quiet */
	  F_verbose = F_stat = 0;
	  F_progress = -1;
	  break;
	 case 's':		/* progress stats */
	  F_progress++;
	  break;
	 case 't':		/* transfer statistics */
	  F_stat++;
	  break;
	 case 'v':		/* verbose level */
	  F_verbose++;
	  break;
	}
      } else 
	url = *ap;
    }
  }
  if (!url) usage();

  if (Proxy){
    if (!strncmp(Proxy,"http://",7))
      parseurl(Proxy,&host,&port,0);
    else {
      char *s;
      int i;
      s = strchr(Proxy,':');
      if (s) {
	i = s-Proxy;
	host = malloc(i+1);
	strncpy(host,Proxy,i);
	host[i]=0;
	port = atoi(s+1);
      } else {
	host = Proxy;		/* oops, not malloc'ed here, inconsistant */
	port = 80;
      }
    }
    geturl = url;
  } else
    parseurl(url,&host,&port,&geturl);
  if (F_verbose>2)
    fprintf(stderr,"Connecting to %s:%u to get %s\n",host,port,geturl);

/* build the address */
  memset(&address,0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(port);

  if (4==sscanf(host,"%d.%d.%d.%d",&a,&b,&c,&d)){
    /* hostname given as ip? */
    long l = htonl((a<<24)+(b<<16)+(c<<8)+d);
    memcpy(&address.sin_addr,&l,sizeof(l));
  } else if (*host>='0' && *host<='9'){
    /* must be ip addr in long format */
    long l = htonl(atol(host));
    if (!l) usage();  /* bad host address format */
    memcpy(&address.sin_addr,&l,sizeof(l));
  } else {
    /* hostname */
    struct hostent *hp;
    if (F_verbose>1)
      fprintf(stderr,"Resolving hostname...");
    hp = gethostbyname(host);
    if (!hp) {
      fprintf(stderr,"I've never heard host '%s'.\n",host);
      exit(1);
    }
    memcpy(&address.sin_addr,hp->h_addr_list[0],hp->h_length);
    if (F_verbose>1)
      fprintf(stderr,"\r                      \r");
  }    

/* open the socket */
  Sock = socket(AF_INET, SOCK_STREAM, 0);
  if (Sock<0){
    perror("socket failed");
    exit(3);
  }
  if (F_verbose>1)
    fprintf(stderr,"Connecting...");
  if ( connect(Sock,(struct sockaddr*)&address,sizeof(address))<0){
    perror("Connect failed");
    exit(4);
  }
  if (F_verbose>1)
    fprintf(stderr,"\rWaiting...   ");

/* send the request header */
  if (F_headonly)
    safewrite("HEAD ");
  else
    safewrite("GET ");
  safewrite(geturl);
  safewrite(" HTTP/1.0\nAccept: */*\r\n");
  /* any other headers we want to send? */
  safewrite("\r\n");
  safeflush();

/* open output file or make arrangements... */
  if (OutFileName){
    out = fopen(OutFileName,"w");
    if (!out){
      perror(OutFileName);
    }
  } else {
    out = stdout;
  }

/* open header output file or make arrangements... */
  if (F_saveheader){
    if (!HeaderFileName)
      headerout = out;
    else if (!strcmp(HeaderFileName,"-")) {
      if (out==stdout)
        headerout = stderr;
      else
        headerout = stdout;
    } else {
      headerout = fopen(OutFileName,"w");
      if (!headerout){
	perror(HeaderFileName);
      }
    }
  }

  /* If progress is autodetect, check to make sure it won't interfere */
  if (F_progress==0 && isatty(2) && (out!=stdout || !isatty(1)))
    F_progress=1;
    
  starttime=time(0);
/* wait for the header */
/** Example header:
 * HTTP/1.0 200 Document follows
 * MIME-Version: 1.0
 * Server: CERN/3.0pre6
 * Date: Friday, 23-Jun-95 17:26:59 GMT
 * Content-Type: text/html
 * Content-Length: 1672
 * Last-Modified: Saturday, 10-Jun-95 04:15:51 GMT
 */
  s = safereadline();
  if (!s || strncmp(s,"HTTP/",5))
    fprintf(stderr,"No header.\n");
  else {
    /* should check version & return condition here */
    char *s1, *s2;
    s1 = strchr(s,' ');
    if (s1)
      s2=strchr(++s1,' ');
    if (s1) {
      if (!s2) s2= s1+strlen(s1);
      if (s2-s1!=3 || 0!=strncmp(s1,"200",3)){
        char c;
        exitval=4;
        if (F_verbose) {
	  c = *s2; *s2 = 0;
	  fprintf(stderr,"Header return val '%s'\n",s1);
	  *s2 = c;
	}
      }
    }
    /****/
    if (headerout)
      fprintf(headerout,"%s\n",s);
    while ((s=safereadline()) && *s){
      if (headerout)
	fprintf(headerout,"%s\n",s);
      if (!strncasecmp(s,"Content-Length:",15)){
	filesize = atoi(s+15);
      }
    }
    if (headerout)
      fprintf(headerout,"%s\n",s);
  }

/* wait for the body */
  i=justifyrbuf();
  do {
    datasize += i;
    if (F_progress)
      if (filesize)
	fprintf(stderr,"\r%2.0f%% %d/%d ",100.0*datasize/filesize,datasize,filesize);
      else if ((F_progress>1 || datasize>dot)) {
	write(1,".",1);
	dot += 1024;
      }
    if (i){
      j = fwrite(rbuf,1,i,out);
      if (j!=i) fprintf(stderr,"\noops! read!=write (%d!=%d)\n",i,j);
    }
    i = read(Sock,rbuf,BufLen);
  } while (i>0);
  if (F_verbose)
    fprintf(stderr,"\r                                                \r");
  if (i<0) {
    perror("Read error");
    exit(1);
  }
   
/* print the statistics */
  if (F_stat){
    starttime = time(0) - starttime;
    if (filesize)
      fprintf(stderr,"%d/%d",datasize,filesize);
    else
      fprintf(stderr,"%d",datasize);
    fprintf(stderr," bytes transfered in ");
    if (!starttime)
      fprintf(stderr,"0 seconds!\n");
    else 
      fprintf(stderr,"%ld seconds (%5f k/sec).\n",starttime,((double)datasize/1024)/starttime);
  }
  if (out && out!=stdout) fclose(out);

  shutdown(Sock,2);
  close(Sock);

  exit(exitval);
}
