/* geturl.c - command line browser
   by tz@execpc.com

   version 0.9

   added proxy SSL support.

   based on upget 0.8
 */

#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <arpa/nameser.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <resolv.h>

#ifdef SOCKS
void SOCKSinit(char *c);

#endif

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif

#define XBUFSIZ 65536
#define TBUFSIZ 4096

char mimetab[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

void usage()
{
  fprintf(stderr, "usage: geturl URL [options] \n"
          "\t-a string               Use this for User-Agent\n"
          "\t-c formdata             POST content from formdata\n"
          "\t-g formdata             GET with appended form data\n"
          "\t                        (for formdata, use - for stdin)\n"
          "\t-d                      debug (print sent text to stderr)\n"
          "\t-i interval             Repeat at an interval\n"
          "\t-l                      for ftp, get a directory\n"
          "\t-t seconds              set timeout (default=60)\n"
          "\t-u                      Auth with uid from ~/.upgetauth\n"
          "\t-z                      strip HTTP header\n");
  exit(-1);
}

static char contbuf[TBUFSIZ];
static char xferbuf[XBUFSIZ];
static char headbuf[4096];
static char proxy[4096];

int debugflag = 0;

int main(int argc, char *argv[])
{
  char authstr[256], user[256];
  char protocol[16], site[64], path[1024], reqtype[16];
  char agent[128], formtype[64];
  char *ip, *op, *ap;
  int c, n, argp;
  int net;
  int siteport;
  int timeout;
  int headers = 1;
  struct timeval tv;

  int putsiz;
  struct sockaddr_in sin;
  unsigned long temp;
  struct hostent *host;
  struct servent *serv;

  int formflag = 0;
  int listflag = 0;
  int interval = 0;
  FILE *ff = NULL;
  FILE *fp = NULL;

  int netd = (-1);
  char *a, *p;
  int val[6];

  fd_set fds;

  if (argc == 1)
    usage();

  /* set sane defaults */

  strcpy(protocol, "http");
  proxy[0] = 0;
  strcpy(reqtype, "GET");
  strcpy(path, "/index.html");
  strcpy(agent, "Mozilla/3.0 (X11; U; Compatible Upget)");
  strcpy(formtype, "x-www-form-urlencoded");
  siteport = 80;
  authstr[0] = 0;
  user[0] = 0;
  timeout = 30;                 /* in seconds */

  /* */
  /* parse URL - get protocol */
  /* */

  /* extract protocol string */
  ap = argv[1];

  if ((ip = strstr(ap, "://")) != NULL) {
    siteport = 0;
    ap = ip + 3;
    strncpy(protocol, argv[1], 15);
    op = strstr(protocol, "://");
    *op = 0;
  }
  strcpy(proxy, protocol);
  strcat(proxy, "_proxy");
  if (NULL != (op = getenv(proxy))) {
    strcpy(proxy, op);

    if (!strncmp(protocol, "http", 4))
      strcpy(path, argv[1]);
    else {
      op = ap;
      op = strchr(ap, '/');
      strcpy(path, op);
    }
    ap = proxy;
    if ((ip = strstr(ap, "://")) != NULL) {
      ap = ip + 3;
      strncpy(protocol, proxy, 15);
      op = strstr(protocol, "://");
      *op = 0;
    }
  } else
    proxy[0] = 0;
  if (!strncmp(ap, "//", 2))
    ap++, ap++;

  /* extract site name (or IP:port) and path */
  strncpy(site, ap, 63);
  if ((ip = strstr(ap, "/")) != NULL) {
    if (!proxy[0])
      strcpy(path, ip);
    op = strstr(site, "/");
    *op = 0;

  }
  /* look for explicit port number */
  if ((ip = strstr(site, ":")) != NULL) {
    *ip++ = 0;
    siteport = atoi(ip);
  }
  /* */
  /* parse command line options */
  /* */

  argp = 2;
  while (argp < argc) {

    if (argv[argp][0] == '-')
      switch (argv[argp++][1]) {
      case 'a':
        /* Some sites aren't cooperative with non-netscape */
        strcpy(agent, argv[argp++]);
        break;
        /* form content - type */
      case 'C':
        strcpy(formtype, argv[argp++]);
        break;
        /* form content - file */
      case 'c':
        formflag = 2;
        if (!strcmp(argv[argp], "-"))
          ff = stdin;
        else
          ff = fopen(argv[argp], "r");

        if (ff == NULL) {
          fprintf(stderr, "Could not open form data file\n");
          exit(-2);
        }
        strcpy(reqtype, "POST");
        argp++;
        break;
      case 'd':
        debugflag = 1;
        fprintf(stderr, "proto=%s, site=%s, path=%s, port=%s\n",
                protocol, site, path, ip);
        break;
        /* whole form */
      case 'f':
        formflag = 1;
        if (!strcmp(argv[argp], "-"))
          ff = stdin;
        else
          ff = fopen(argv[argp], "r");

        if (ff == NULL) {
          fprintf(stderr, "Could not open form file\n");
          exit(-2);
        }
        argp++;
        break;
      case 'g':
        formflag = 3;
        if (!strcmp(argv[argp], "-"))
          ff = stdin;
        else
          ff = fopen(argv[argp], "r");

        if (ff == NULL) {
          fprintf(stderr, "Could not open form data file\n");
          exit(-2);
        }
        strcpy(reqtype, "GET");
        argp++;
        break;
      case 'i':
        /* get repeat interval */
        interval = atoi(argv[argp++]);
        break;
      case 'l':
        listflag = 1;
        break;
      case 'r':
        /* copy request type */
        strcpy(reqtype, argv[argp++]);
        break;
      case 't':
        /* get timeout */
        timeout = atoi(argv[argp++]);
        break;
      case 'u':
        /* create authorization string */

        ip = getenv("HOME");
        sprintf(authstr, "%s/.upgetauth", ip);
        fp = fopen(authstr, "r");
        for (;;) {
          fscanf(fp, "%s %s", xferbuf, user);
          if (feof(fp)) {
            fprintf(stderr, "Cannot find authorization\n");
            exit(-1);
          }
          if (!strcmp(xferbuf, site))
            break;
        }
        fclose(fp);

        /* convert into MIME bin64 */
        ip = user;
        op = contbuf;
        c = strlen(ip);

        for (temp = 0; temp < c; temp += 3) {
          *op++ = mimetab[*ip >> 2];
          *op++ = mimetab[((*ip << 4) & 0x30) | ((ip[1] >> 4) & 0x0f)];
          *op++ = mimetab[((ip[1] << 2) & 0x3c) | ((ip[2] >> 6) & 0x03)];
          *op++ = mimetab[ip[2] & 0x3f];
          ip += 3;
        }

        if (temp >= c + 1)
          op[-1] = '=';
        if (temp == c + 2)
          op[-2] = '=';
        *op = '\0';

        /* create full auth string */
        strcpy(authstr, "Authorization: Basic ");
        strcat(authstr, contbuf);
        strcat(authstr, "\r\n");

        break;
      case 'z':
        headers = 0;
        break;
      default:
        usage();
    } else
      usage();
  }

  /* */
  /* compose HTTP or FTP request */
  /* */

  contbuf[0] = 0;
  if (!strncmp(protocol, "http", 4)) {
    if (formflag == 3) {
      putsiz = fread(&contbuf[1], 1, XBUFSIZ, ff);
      contbuf[putsiz + 1] = 0;
      contbuf[0] = '?';
      fclose(ff);
      ff = NULL;
    }
    sprintf(headbuf,
            "%s %s%s HTTP/1.0\r\n"
            "Host: %s\r\n"
            "User-Agent: %s\r\n"
            "Accept: */*\r\n"
            "%s",
            reqtype, path, contbuf, site, agent,
            authstr);
  } else if (!strncmp(protocol, "nntp", 4)) {
#if 0
    op = getenv("NNTPSERVER");
#endif
    op = strchr(&path[1], '/');
    *op++ = 0;;
    if (user[0] == 0) {
      sprintf(headbuf,
              "GROUP %s\r\n"
              "ARTICLE %s\r\n"
              "QUIT\r\n",
              &path[1], op);
    } else {
      strcpy(authstr, user);
      ip = strchr(authstr, ':');
      *ip++ = 0;
      sprintf(headbuf,
              "AUTHINFO USER %s\r\n"
              "AUTHINFO PASS %s\r\n"
              "GROUP %s\r\n"
              "ARTICLE %s\r\n"
              "QUIT\r\n",
              authstr, ip, &path[1], op);
    }
  } else if (!strncmp(protocol, "file", 4)) {
    fp = fopen(path, "r");
    if (fp == NULL)
      exit(-1);
    putsiz = 1;
    while (putsiz) {
      putsiz = fread(xferbuf, 1, XBUFSIZ, fp);
      fwrite(xferbuf, 1, putsiz, stdout);
    }
    fclose(fp);
    exit(0);
  } else if (!strncmp(protocol, "ftp", 3)) {
    op = path;
    op++;

    if (listflag) {
      strcpy(contbuf, "CWD ");
      strcat(contbuf, op);
      strcat(contbuf, "\r\nLIST");
    } else {
      strcpy(contbuf, "RETR ");
      strcat(contbuf, op);
    }

    if (user[0] == 0) {
      sprintf(headbuf,
      /* get user from user later */
              "USER anonymous\r\n"
              "PASS geturl@nowhere.xxx\r\n"
              "TYPE I\r\n"
              "PASV\r\n"
              "%s\r\n"
              "QUIT\r\n",
              contbuf);
    } else {
      strcpy(authstr, user);
      ip = strchr(authstr, ':');
      *ip++ = 0;
      sprintf(headbuf,
      /* get user from user later */
              "USER %s\r\n"
              "PASS %s\r\n"
              "TYPE I\r\n"
              "PASV\r\n"
              "%s\r\n"
              "QUIT\r\n",
              authstr, ip, contbuf);
    }

  } else if (!strncmp(protocol, "gopher", 6)) {
    strcpy(headbuf, &path[1]);
  } else if (!strncmp(protocol, "finger", 6)) {
    strcpy(headbuf, &path[1]);
  } else if (!strncmp(protocol, "whois", 5)) {
    strcpy(headbuf, &path[1]);
  } else
    headbuf[0] = 0;

  /* append any form data to the request */
  putsiz = 0;
  if (formflag == 1) {
    putsiz = fread(contbuf, 1, XBUFSIZ, ff);
    fclose(ff);
    ff = NULL;
  }
  if (formflag == 2) {
    fseek(ff, 0, SEEK_END);
    putsiz = ftell(ff);
    rewind(ff);
    /* need to use better sizing */
    sprintf(&headbuf[strlen(headbuf)],
            "Content-type: application/%s\n"
            "Content-length: %d\r\n\r\n", formtype, putsiz);
  } else
    /* final linefeed */
    strcat(headbuf, "\r\n");

  /* */
  /* lookup site and port for IP connection */
  /* */

  if (debugflag)
    fprintf(stderr, "%s", headbuf);

  memset((char *) &sin, 0, sizeof(sin));
  temp = inet_addr(site);

#ifdef SOCKS
  SOCKSinit(argv[0]);
#endif

  if (temp != INADDR_NONE)
    memcpy(&sin.sin_addr, &temp, sizeof(temp));
  else {
    host = gethostbyname(site);
    if (host == NULL)
      return (-1);
    memcpy((char *) &sin.sin_addr, host->h_addr, host->h_length);
  }
  if (debugflag)
    fprintf(stderr, "Address: %ld.%ld.%ld.%ld\n",
            (htonl(sin.sin_addr.s_addr) >> 24) & 0xff,
            (htonl(sin.sin_addr.s_addr) >> 16) & 0xff,
            (htonl(sin.sin_addr.s_addr) >> 8) & 0xff,
            htonl(sin.sin_addr.s_addr) & 0xff
      );

  if (siteport)
    sin.sin_port = htons(siteport);
  else {
    serv = getservbyname(protocol, "tcp");
    if (serv == NULL)
      return (-2);
    sin.sin_port = serv->s_port;
  }
  if (debugflag)
    fprintf(stderr, "Port: %d\n", htons(sin.sin_port));

  sin.sin_family = AF_INET;     /* host->h_addrtype; */

  /* interval loop */
  do {

    net = socket(AF_INET, SOCK_STREAM, 0);
    if (net < 0)
      return net;

    /* */
    /* connect to remote site and do transaction */
    /* */

    if ((c = connect(net, (struct sockaddr *) &sin, sizeof(sin))) < 0) {
      if (!interval)
        return c;
      else {
        sleep(interval);
        continue;
      }
    }
    /* write request string; dump any response to stdout */
    /* write the request to the remote site */

    write(net, headbuf, strlen(headbuf));
    if (ff != NULL) {
      putsiz = 1;
      while (putsiz) {
        putsiz = fread(contbuf, 1, XBUFSIZ, ff);
        if (!putsiz)
          break;
        write(net, contbuf, putsiz);
      }
      rewind(ff);
    }
    /* */
    /* FTP protocol handler */
    /* */

    if (!strncmp(protocol, "ftp", 3)) {

      netd = socket(AF_INET, SOCK_STREAM, 0);
      if (netd < 0)
        return -1;

      c = 0;
      /* find entire PASV response line */
      while (!(ip = strstr(xferbuf, "\n227")) || !strchr(&ip[5], '\n')) {

        /* really should add select() calls */

        temp = read(net, &xferbuf[c], XBUFSIZ - c);
        xferbuf[c + temp] = 0;
        if (debugflag)
          fprintf(stderr, "%s", &xferbuf[c]);
        c += temp;
      }

      if (!ip)
        return -1;

      ip += 5;

      a = (char *) &sin.sin_addr;
      p = (char *) &sin.sin_port;

      while (*ip++ != '(');     /* find start of pasv ip/port message */

      /* extract ip and port into sin structure */
      sscanf(ip, "%d,%d,%d,%d,%d,%d",
             &val[0], &val[1], &val[2], &val[3], &val[4], &val[5]);
      a[0] = val[0], a[1] = val[1], a[2] = val[2];
      a[3] = val[3], p[0] = val[4], p[1] = val[5];

      /* connect to data port */
      if ((c = (connect(netd, (struct sockaddr *) &sin, sizeof(sin)) < 0)))
        return c;

      if (debugflag)
        fprintf(stderr, "FTP data connection opened\n");

      /* swap data and control */
      net = netd;
    }
    /* FTP data and everything else */

    c = XBUFSIZ;
    ip = NULL;
    while (c > 0) {

      FD_ZERO(&fds);
      FD_SET(net, &fds);
      tv.tv_sec = timeout;
      tv.tv_usec = 0;
      if (!select(net + 1, &fds, NULL, NULL, &tv))
        break;
      c = read(net, xferbuf, XBUFSIZ);
      if (!headers && ip == NULL && !strncmp(protocol, "http", 4)) {
        n = c;
        xferbuf[n] = 0;
        while (c >= 0 && (ip = strstr(xferbuf, "\r\n\r\n")) == NULL
               && (ip = strstr(xferbuf, "\n\n")) == NULL) {
          c = read(net, &xferbuf[n], XBUFSIZ - n);
          n += c;
          xferbuf[n] = 0;
        }
        if (ip != NULL) {
          if (*ip == '\n')
            ip += 2;
          else
            ip += 4;
          n -= ip - xferbuf;
          memcpy(xferbuf, ip, n);
          c = n;
          if (!c)
            c = read(net, xferbuf, XBUFSIZ);
        } else
          break;
      }
      if (c != write(1, xferbuf, c))
        break;                  /* did someone close the pipe */
    }

    close(net);
    if (netd > 0)
      close(netd);
    if (interval > 0)
      sleep(interval);

  } while (interval);

  /* end of interval loop */
  return 0;
}
