/* netfetch.c
 * ==========================================================================
 *    Name: NetFetch
 * Purpose: Simple HTTP fetcher
 *  Author: by David Thomas,  1996-8
 * Version: 1.30 (24 May 1998)
 * ==========================================================================
 */

#undef BLOCKING			/* When defined, BLOCKING compiles a version
				   of NetFetch which utilises the 'TaskWindow
				   sleep' feature of the Internet 5 and
				   FreeNet 2 stacks.  This gives much
				   smoother fetches.			   */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef __riscos
#  define __time_t		/* ### Fudge! (Stop time_t's clashing)     */
#else
#  define BSD_COMP		/* Ensure BSD compliance on SunOS, Solaris */
#endif

#include "sys/types.h"
#include "sys/socket.h"
#include "sys/ioctl.h"
#ifndef BLOCKING
#  include "sys/time.h"		/* For timeval(), timerclear()		   */
#endif
#include "netinet/in.h"
#include "errno.h"
#include "netdb.h"

#ifdef __riscos
#  include "unixlib.h"
#  include "socklib.h"
#  include "inetlib.h"
#  ifdef BLOCKING
#    define FIOSLEEPTW	0x80046679
#  endif
#else
#  define socketread(sk, bf, sz)	read(sk, bf, sz)
#  define socketwrite(sk, bf, sz)	write(sk, bf, sz)
#  define socketclose(sk)		close(sk)
#  define socketioctl(sk, fl, pt)	ioctl(sk, fl, pt)
#  define xperror(s)			perror(s)
#endif

#include "netfetch.h"

#define UNUSED(x) {x=x;}


char buffer[16384], line[256], uri[256], saveas[256];
unsigned int lineptr, flags, stage, total;
int content_length = -1;
time_t starttime;
FILE *wr, *list;

int main(int argc, char *argv[])
{
  int thisarg, found;
  char *in, *out, *proxy;

  if (argc < 2)
    show_syntax(argv[0]);

  flags = 0;				/* no flags set yet */
  in = out = proxy = NULL;
  thisarg = 1;				/* first parameter */
  while (thisarg < argc) {
    if (*argv[thisarg] == '-') {
      if (strcmp("-list", argv[thisarg]) == 0) {
	flags |= FLAG_LIST;
	in = argv[++thisarg];
      }
      if (strcmp("-proxy", argv[thisarg]) == 0) {
	flags |= FLAG_PROXY;
	proxy = argv[++thisarg];
      }
    } else {
      if (in == NULL) {
	in = argv[thisarg];
      } else {
	if (out == NULL)
	  out = argv[thisarg];
      }
    }
    thisarg++;
  }

  printf("NetFetch 1.30 - Simple HTTP fetcher -  David Thomas\n\n");

  if (flags & FLAG_LIST) {
    if ((list = fopen(in, "r")) != NULL) {
      while (feof(list) == 0) {
	if ((found = fscanf(list, "%s %s\n", uri, saveas)) != EOF) {
	  if (found < 2)
	    printf("Error: No destination filename given for '%s'.\n", uri);
	  else
	    fetch(uri, saveas, proxy);
	}
      }
      fclose(list);
    } else {
      printf("Error: Couldn't open URI list file.\n");
    }
  } else {
    if (out != NULL)
      fetch(in, out, proxy);
    else
      printf("Error: No destination filename given for '%s'.\n", in);
  }

  exit(1);
}

void show_syntax(char *progname)
{
  printf("Syntax: %s [switches] [<location> <filename>]\n", progname);
  exit(0);
}


int fetch(char *uri, char *saveas, char *proxy)
{
  int outsk, on = 1;
  char scheme[6], credentials[32], hostname[64], path[256];
  int port;
  char rqscheme[6], rqcredentials[32], rqhostname[64], rqpath[256];
  int rqport;
  struct sockaddr_in server;
  struct hostent *hp;
#ifndef BLOCKING
  fd_set fd, fd2;
  struct timeval timeout;
#endif

  printf("Fetching '%s'\n", uri);

  if (ae_uri_decode(uri, rqscheme, rqcredentials, rqhostname, &rqport, rqpath) == 0)
    return(0);

  printf("Finding hostname...");
  if (flags & FLAG_PROXY) {
    /* Using a proxy */
    if (ae_uri_decode(proxy, scheme, credentials, hostname, &port, path) == 0)
      return(0);
    strncpy(path, uri, 255);
  } else {
    /* Connecting directly to host */
    if (ae_uri_decode(uri, scheme, credentials, hostname, &port, path) == 0)
      return(0);
  }

  if ((hp = gethostbyname(hostname)) == NULL) {
    printf("\nError: gethostbyname() failed for '%s'.\n", hostname);
    return(0);
  }

  if ((outsk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    xperror("\nError: socket() failed");
    return(0);
  }

#ifdef __riscos
#  ifdef BLOCKING
  socketioctl(outsk, FIOSLEEPTW, &on);	/* sleeping */
#  endif
#endif

  printf("\rConnecting to '%s'...", hostname);
  bzero((char *) &server, sizeof(server));
  bcopy(hp->h_addr, (char *) &server.sin_addr, hp->h_length);
  server.sin_family = hp->h_addrtype;
  server.sin_port = htons(port);
  if (connect(outsk, (struct sockaddr *) &server, sizeof(server)) < 0) {
    xperror("\nError: connect() failed");
    return(0);
  }

  printf("\rConnected to '%s'... \n", hostname);

#ifndef BLOCKING
  socketioctl(outsk, FIONBIO, &on);	/* non-blocking */

  FD_ZERO(&fd);
  FD_SET(outsk, &fd);

  timerclear(&timeout);
#endif

  stage = STAGE_SENDHEADER;

  wr = fopen(saveas, "wb");

  while (stage != STAGE_INACTIVE) {

#ifndef BLOCKING
    memcpy(&fd2, &fd, sizeof(fd));
    if (select(FD_SETSIZE, NULL, &fd2, NULL, &timeout)) {
#endif
      if (stage == STAGE_SENDHEADER)
	sendheader(outsk, path, rqhostname);
#ifndef BLOCKING
    }

    memcpy(&fd2, &fd, sizeof(fd));
    if (select(FD_SETSIZE, &fd2, NULL, NULL, &timeout)) {
#endif
      if (stage == STAGE_WAITHEADER)
	waitheader(outsk);
      if (stage == STAGE_READHEADER)
	readheader(outsk);
      if (stage == STAGE_PARSEHEADER)
	parseheader(outsk);
      if (stage == STAGE_RECVENTITY)
	receiveentity(outsk, saveas);
#ifndef BLOCKING
    }
#endif

  }

  fclose(wr);

#ifndef BLOCKING
  FD_CLR(outsk, &fd);
#endif

  socketclose(outsk);

  return(1);
}


void sendheader(int sk, char *obj, char *host)
{
  char temp[256];

  printf("Requesting '%s'...\n\n", obj);

  sprintf(temp, "\
GET %s HTTP/1.0\n\
Host: %s\n\
Connection: Close\n\
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\n\
User-Agent: Mozilla/4.0 (compatible; Feck!; Arse!; Gerls!)\n\
\n\
", obj, host);
  socketwrite(sk, temp, strlen(temp));

  stage = STAGE_WAITHEADER;
}

void waitheader(int sk)
{
  int bytes;

  socketioctl(sk, FIONREAD, &bytes);
  if (bytes == 0)
    return;

  flags |= FLAG_FIRSTLINE;
  lineptr = 0;
  total = 0;
  stage = STAGE_READHEADER;
}

void readheader(int sk)
{
  char c;

  do {
    if (socketread(sk, &c, 1) == -1) {
      if (errno != EWOULDBLOCK) {
	xperror("\nError: socketread() failed");
	stage = STAGE_INACTIVE;
      }
    } else {
      if ((c == 10) || (c == 13)) {
	if (c == 10) {
	  line[lineptr] = '\0';	/* terminate the header line */
	  stage = STAGE_PARSEHEADER;
	}
      } else {
	line[lineptr++] = c;
      }
    }
  } while (c != 10);
}

void parseheader(int sk)
{
  char *c;
  UNUSED(sk);

  if (strcmp(line, "") == 0) {
    printf("\nReceiving object...\n\n");
    starttime = time(NULL);
    stage = STAGE_RECVENTITY;
  } else {
    lineptr = 0;
    printf(": %s\n",line);
    if (flags & FLAG_FIRSTLINE) {
      if (line[9] == '2') {
	/* 2xx success header */
	flags &= ~FLAG_FIRSTLINE;	/* clear the flag */
	stage = STAGE_READHEADER;
      } else {
	printf("Server did not return an object. :-(\n");
	stage = STAGE_INACTIVE;
      }
    } else {
      for (c = line; *c != ':'; c++)
	*c = (char) tolower((int) *c);
      if (strncmp(line, "content-length: ", 16) == 0)
	content_length = atoi(line + 16);
      stage = STAGE_READHEADER;
    }
  }
}

void receiveentity(int sk, char *saveas)
{
  int bytes;
  UNUSED(saveas);

  if ((bytes = socketread(sk, buffer, sizeof(buffer))) == -1) {
    if (errno != EWOULDBLOCK) {
      xperror("\nError: socketread() failed");
      stage = STAGE_INACTIVE;
    }
    return;
  }

  if (bytes) {
    fwrite(buffer, 1, bytes, wr);		/* ### disc error check */
    total += bytes;
    if (content_length == -1)
      printf("\r: %d bytes received.", total);
    else
      printf("\r: %d bytes received - %d%% at %d bytes/sec", total, (int) (total * 100 / content_length), (int) (total / (time(NULL) - starttime + 1)));
  } else {
    printf("\n\nObject received. :-)\n");
    stage = STAGE_INACTIVE;
  }
}

/* The following is taken from AppEngine's uri.c : */

int ae_uri_decode(char *uri, char *scheme, char *credentials, char *hostname, int *port, char *path)
{
  char *p, *q, *r;

  p = uri;

  /* Scheme */
  q = scheme;
  while ((*p >= 'a') && (*p <= 'z'))
    *q++ = *p++;
  *q++ = '\0';

  if ((*p++ != ':') || (*p++ != '/') || (*p++ != '/')) {
    printf("Error: Malformed URI\n");
    return(0);
  }

  /* Credentials, Host and Port */
  q = hostname;
  while ((*p > ' ') && (*p != '/'))
    *q++ = *p++;
  *q++ = '\0';
  r = strchr(hostname, '@');	/* credentials in the hostname string? */
  if (r != NULL) {
    *r = '\0';
    strcpy(credentials, hostname);
    strcpy(hostname, r+1);
  } else {
    *credentials = '\0';
  }
  /* Port specifier */
  r = strchr(hostname, ':');
  if (r != NULL) {
    *r++ = '\0';
    if ((*r >= '0') && (*r <= '9'))
      *port = atoi(r);
    else
      *port = 80;		/* Default port (### for HTTP only!) */
  } else {
    *port = 80;			/* Default port (### for HTTP only!) */
  }

  /* Path specifier */
  if (*p > ' ') {
    q = path;
    while (*p > ' ')
      *q++ = *p++;
    *q++ = '\0';
  } else {
    path = "/";			/* Default */
  }

  return(1);
}
