/*
 *   HTGET
 *
 *   Get a document via HTTP
 *
 *   This program was compiled under GNU C v4.3.2 (DJGPP v2.03)
 *   You will require the WATT32 libraries. A copy is included in the
 *   distribution. For the sources of WATTCP, search on the Watt32 project's
 *   homepage.
 *
 *   Please send bug fixes, ports and enhancements to the current maintainer
 *   for incorporation in newer versions: <mateusz(#)viste-family.net>
 *
 *   Copyright (C) 1996-1998 Ken Yap
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU/GPL, a copy of which is included with this
 *   distribution.
 *
 *   THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 *   WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 *   MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <io.h>
#include <tcp.h>

#ifdef __GNUC__
#define write _write
#define close _close
#endif

#if (defined(__SMALL__) && !defined(DOS386)) || defined(__LARGE__)
  #define SAVE_SPACE
#else
  #define perror perror_s  /* prevent duplicate symbols */
#endif

#define WINDOWSIZE  (16*1024)
#define BUFFERSIZE  2048
#define PROGRAM     "HTGET"
#define VERSION     "v1.06"
#define AUTHOR      "Copyright (C) 1996-1998 Ken Yap, 2009 Mateusz Viste"

// #define HTTPVER  "HTTP/1.[01]"   /* Apache doesn't like this */
#define HTTPVER     "HTTP/1.0"
#define HTTPVER10   "HTTP/1.0"
#define HTTPVER11   "HTTP/1.1"
#define strn(s)     s, sizeof(s)-1

char       *buffer;
tcp_Socket *sock;

struct tm *mtime;
int        output;
int        headeronly      = 0;
int        debugMode       = 0;
int        verboseMode     = 0;
int        ifModifiedSince = 0;
char      *outputfile      = NULL;
char      *userPass        = NULL;
char      *dayname         = "SunMonTueWedThuFriSat";
char      *monthname       = "JanFebMarAprMayJunJulAugSepOctNovDec";
char       proxy_host[80]  = { 0 };
int        proxy_port      = 0;
FILE      *of;
void     (*prev_hook) (const char*, const char*);

void base64encode (char *in, char *out)
{
  int    c1, c2, c3;
  int    len = 0;
  static char basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\
                           "abcdefghijklmnopqrstuvwxyz0123456789+/";
  while (*in)
  {
    c1 = *in++;
    if (*in == '\0')
       c2 = c3 = 0;
    else
    {
      c2 = *in++;
      if (*in == '\0')
           c3 = 0;
      else c3 = *in++;
    }
    *out++ = basis_64 [c1 >> 2];
    *out++ = basis_64 [((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)];
    len += 2;
    if (c2 == 0 && c3 == 0)
    {
      *out++ = '=';
      *out++ = '=';
      len += 2;
    }
    else if (c3 == 0)
    {
      *out++ = basis_64 [((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
      *out++ = '=';
      len += 2;
    }
    else
    {
      *out++ = basis_64 [((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
      *out++ = basis_64 [c3 & 0x3F];
      len += 2;
    }
  }
  *out = '\0';
}

/*
 * WATTCP's sock_gets() doesn't do the right thing.
 * That's because sock isn't set to ASCII mode.
 */
int sock_getline (void *sock, char *buf, int len)
{
  int  i;
  char ch;

  for (i = 0, --len; i <= len && sock_read(sock,&ch,1) > 0; )
  {
    if (ch == '\n')
       break;
    if (ch != '\r')
    {
      *buf++ = ch;
      ++i;
    }
  }
  *buf = '\0';
  return (i);
}

long header (const char *path)
{
  int   i, len, response;
  long  contentlength;
  char *s;

  contentlength = LONG_MAX;     /* largest int, in case no CL header */
  if ((len = sock_getline(sock, buffer, BUFFERSIZE)) <= 0)
  {
    puts ("EOF from server");
    return (-1L);
  }
  if (strncmp(s = buffer,strn(HTTPVER10)) && /* no HTTP/1.[01]? */
      strncmp(s,strn(HTTPVER11)))
  {
    puts ("Not a HTTP/1.[01] server");
    return (-1L);
  }

  s += sizeof(HTTPVER10)-1;
  if ((i = strspn(s, " \t")) <= 0)        /* no whitespace? */
  {
    puts ("Malformed HTTP/1.[01] line");
    return (-1L);
  }
  s += i;
  response = 500;
  sscanf (s, "%3d", &response);
  if (response == 401)
  {
    printf ("%s: authorisation failed!\n", path);
    return (-1L);
  }
  else if (response != 200 && response != 301 &&
           response != 302 && response != 304)
  {
    printf ("%s: %s\n", path, s);
    contentlength = -1L;
  }

  if (headeronly)
  {
    write (output, buffer, len);
    write (output, "\r\n", 2);
  }

  /* eat up the other header lines here */
  while ((len = sock_getline(sock, buffer, BUFFERSIZE)) > 0)
  {
    if (headeronly)
    {
      write (output, buffer, len);
      write (output, "\r\n", 2);
    }
    if (!strnicmp(s=buffer,strn("Content-Length:")))
    {
      s += sizeof("Content-Length:")-1;
      contentlength = atol(s);
    }
    else if (!strnicmp(buffer, strn("Location:")))
    {
      if (response == 301 || response == 302)
         printf ("At %s\n", buffer);
    }
    else if (strchr(" \t", buffer[0]))
            printf ("Warning: continuation line encountered\n");
  }
  return (response == 304 ? 0L : contentlength);
}


int htget (const char *host, int port, const char *path)
{
  struct in_addr a,b;
  DWORD  hostaddr;
  int    status = 0;
  int    connected = 0;
  int    completed = 0;
  int    i, use_proxy = proxy_host[0] != 0;
  long   length, contentlength = 0L;
  const  char *name;
  char  *buf = buffer;
  char   url[512];

  if (proxy_host[0] && use_proxy)
       name = proxy_host;
  else name = host;

  if ((hostaddr = lookup_host(name,NULL)) == 0)
  {
    printf (dom_strerror(dom_errno));
    return (1);
  }

#ifndef SAVE_SPACE
  a.s_addr = intel (my_ip_addr);      /* A-side is me   */
  inet_aton (host, &b);               /* B-side is host */

  if (inet_netof(a) == inet_netof(b)) /* on same network */
     use_proxy = 0;
#endif

#if 0   /* test */
  {
    struct in_addr na, nb;

    na.s_addr = inet_netof (a);
    nb.s_addr = inet_netof (b);

    printf ("A [%s]: netof: %s\n", inet_ntoa(a), inet_ntoa(na));
    printf ("B [%s]: netof: %s\n", inet_ntoa(b), inet_ntoa(nb));

    na.s_addr = htonl (inet_lnaof(a));
    nb.s_addr = htonl (inet_lnaof(b));

    printf ("A [%s]: lnaof: %s\n", inet_ntoa(a), inet_ntoa(na));
    printf ("B [%s]: lnaof: %s\n", inet_ntoa(b), inet_ntoa(nb));

    printf ("use_proxy = %d\n", use_proxy);
  }
#endif

  if (use_proxy && proxy_port && proxy_host[0])
     port = proxy_port;

  if (debugMode)
     printf ("%s:%d%s\n", host, port, path);

  if (!tcp_open(sock, 0, hostaddr, port, NULL))
  {
    printf ("Cannot connect to `%s'\n", name);
    return (1);
  }

  sock_setbuf (sock, malloc(WINDOWSIZE), WINDOWSIZE);

  sock_wait_established (sock, sock_delay, NULL, &status);
  connected = 1;
  completed = 1;
  sock_tick (sock, &status);      /* in case they sent reset */

  if (verboseMode)
     puts ("Sending HTTP GET/HEAD request");

  if (proxy_host[0])
       sprintf (url, "http://%s%s", host, path);
  else strcpy  (url, path);

  buf += sprintf (buf, "%s %s " HTTPVER "\r\n"
                  "Host: %s\r\n"
                  "User-Agent: " PROGRAM "-DOS/" VERSION "\r\n",
                  headeronly ? "HEAD" : "GET", url, host);
  if (userPass)
  {
    char pass [100];

    base64encode (userPass, pass);
    if (debugMode)
       printf ("%s => %s\n", userPass, pass);

    buf += sprintf (buf, "Authorization: Basic %s\r\n", pass);
  }

  if (ifModifiedSince)
  {
    char *mod = buf;
    buf += sprintf (buf,
                    "If-Modified-Since: %.3s, %02d %.3s %04d %02d:%02d:%02d GMT\r\n",
                    dayname + 3*mtime->tm_wday,
                    mtime->tm_mday,
                    monthname + 3*mtime->tm_mon,
                    mtime->tm_year + 1900,
                    mtime->tm_hour, mtime->tm_min, mtime->tm_sec);
    if (debugMode || verboseMode)
       puts (mod);
  }

  buf += sprintf (buf, "\r\n");
  sock_fastwrite (sock, buffer, buf-buffer);

  if ((contentlength = header(path)) >= 0L && !headeronly)
  {
    /* We wait until the last moment to open the output file.
     * If any specified so that we won't overwrite the file
     * in case of error in contacting server.
     */
    if (outputfile)
    {
      if ((of = fopen(outputfile, "wb")) == NULL)
      {
        perror (outputfile);
        goto close_up;
      }
      output = fileno (of);
    }

    length = 0L;
    while ((i = sock_read(sock,buffer,BUFFERSIZE)) > 0)
    {
      write (output, buffer, i);
      length += i;
      if (verboseMode)
      {
        printf ("Got %lu bytes\r", length);
        fflush (stdout);
      }
    }

    if (contentlength != LONG_MAX && length != contentlength)
       printf ("Warning, actual length = %ld, content length = %ld\n",
               length, contentlength);
  }

close_up:
  sock_close (sock);
  sock_wait_closed (sock, sock_delay, NULL, &status);

sock_err:
  if (status == -1)
     printf ("`%s' %s\n", name, sockerr(sock));
  if (!connected)
     puts ("Could not get connected");

  return (!completed || contentlength < 0L);
}

void version (void)
{
  puts (PROGRAM " " VERSION " " AUTHOR "\n"
        "\n"
        PROGRAM " comes with ABSOLUTELY NO WARRANTY.\n"
        "This is free software, and you are welcome to redistribute it\n"
        "under certain conditions; see the license file for details.");
}

void usage (void)
{
  version();
  puts (
    "\n"
    "Usage: " PROGRAM " [-hvVm] [-p ident:passwd] [-o file] URL\n"
    "\t -h option to get header only\n"
    "\t -V show version info\n"
    "\t -v show verbose messages\n"
    "\t -d enable debug mode\n"
    "\t -m fetch only if newer than file in -o\n"
    "\t -p ident:password  send credentials\n"
    "\t -o write HTML output to file");
}

/*--------------------------------------------------------------*/

static void Exit (char *str)
{
  puts (str);
  exit (1);
}

void cnf_hook (const char *name, const char *value)
{
  if (!strcmp(name,"HTTP.PROXY"))
  {
    if (sscanf(value,"%[^:]:%d",proxy_host,&proxy_port) != 2)
       Exit ("Config error: syntax is HTTP_PROXY=<host>:<port>");
  }
  else if (prev_hook)
         (*prev_hook) (name, value);
}

void set_cnf_hook (void)
{
  prev_hook = usr_init;
  usr_init  = cnf_hook;
}

int main (int argc, char **argv)
{
  char *host, *path, *s;
  int   port = 80;
  int   ch, status;

  while ((ch = getopt(argc, argv, "?hVvdmp:o:")) != EOF)
     switch (ch)
     {
       case 'V': version();
                 puts (wattcpVersion());
                 return (0);

       case 'v': verboseMode = 1;
                 break;

       case 'd': debugMode = 1;
                 break;

       case 'h': headeronly = 1;
                 break;

       case 'm': ifModifiedSince = 1;
                 break;

       case 'o': outputfile = optarg;
                 break;

       case 'p': userPass = optarg;
                 break;

       default : usage();
                 return (1);
     }

  argc -= optind;
  argv += optind;

  if (argc <= 0)
  {
    usage();
    return (1);
  }

  buffer = malloc (BUFFERSIZE);
  sock   = malloc (sizeof(*sock));
  if (!buffer || !sock)
  {
    puts ("No memory");
    return (-1);
  }

  if (!strnicmp(argv[0], strn("http://")))
     argv[0] += sizeof("http://") - 1;
 
  if ((path = strchr(argv[0],'/')) == NULL) /* separate out the path */
  {
    host = argv[0];
    path = "/";      /* top directory */
  }
  else
  {
    if ((host = calloc(path-argv[0]+1,1)) == NULL)
    {
      printf (PROGRAM ": Out of memory\n");
      return (1);
    }
    strncat (host, argv[0], path-argv[0]);
  }


  /* do we have a port number? */
  if ((s = strchr(host,':')) != NULL)
  {
    *s++ = '\0';
    port = atoi(s);
  }

  if (ifModifiedSince)
  {
    struct stat statbuf;

    /* allow only if no -h and -o file specified and file exists */
    if (headeronly || outputfile == 0 || stat(outputfile,&statbuf) < 0)
        ifModifiedSince = 0;
    else if (verboseMode)
    {
      mtime = gmtime(&statbuf.st_mtime);
      printf ("%s last modified %s", outputfile,asctime(mtime));
    }
  }
  if (debugMode)
  {
    tcp_set_debug_state (1);
    dbug_init();
  }
  tzset();
  set_cnf_hook();
  sock_init();

  output = fileno(stdout);
  status = htget (host, port, path);
  close (output);
  return (status);
}

