/***************************************************************************
                          http.c - taken from the xine project (http://www.xinehq.de)
                          and modified for use with c++
                             -------------------
    begin                : Fre Jan 23 2004
    revision             : $Revision: 1.8 $
    last modified        : $Date: 2005/07/10 13:12:14 $ by $Author: juergenk $
    copyright            : (C) 2004-2005 by Jürgen Kofler
    email                : kaffeine@gmx.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>

#include "http.h"

/*
#define LOG
*/

#define BUFSIZE                 1024
#define BUFSIZE_QUOTE          "1024"
#define DEFAULT_HTTP_PORT         80

typedef struct http_s http_t;

struct http_s {

  int              fh;

  off_t            curpos;
  off_t            contentlength;

  char             mime_type[BUFSIZE+1];

  char             buf[BUFSIZE+1];
  char             mrlbuf[BUFSIZE+1];
  char             proxybuf[BUFSIZE+1];

  char             auth[BUFSIZE+1];
  char             proxyauth[BUFSIZE+1];

  char            *user;
  char            *password;
  char            *host;
  int              port;
  char            *filename;

  char            *proxyuser;
  char            *proxypassword;
  char            *proxyhost;
  int              proxyport;

};

/*
static int host_connect_attempt (struct in_addr ia, int port) {

  int                s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  struct sockaddr_in sin;

  if (s==-1) {
    printf ("http: socket(): %s\n", strerror(errno));
    return -1;
  }

  sin.sin_family = AF_INET;
  sin.sin_addr   = ia;
  sin.sin_port   = htons(port);

  if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1
      && errno != EINPROGRESS) {
    printf ("http: connect(): %s\n", strerror(errno));
    close(s);
    return -1;
  }

  return s;
}
*/

static int http_host_connect_attempt (struct in_addr ia, int port) {

  int                s;
  struct sockaddr_in s_in;

  s=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  if (s==-1) {
    printf ("http: failed to open socket\n");
    return -1;
  }

  s_in.sin_family = AF_INET;
  s_in.sin_addr   = ia;
  s_in.sin_port   = htons(port);

  if (connect(s, (struct sockaddr *)&s_in, sizeof(s_in))==-1
      && errno != EINPROGRESS) {
    printf ("http: cannot connect to host\n");
    close(s);
    return -1;
  }

  return s;
}

static int http_host_connect (const char *host, int port) {

  struct hostent *h;
  int i;
  int s;

  h=gethostbyname(host);
  if (h==NULL) {
    printf ("http: unable to resolve >%s<\n", host);
    return -1;
  }

  for(i=0; h->h_addr_list[i]; i++) {
    struct in_addr ia;
    memcpy(&ia, h->h_addr_list[i], 4);
    s = http_host_connect_attempt (ia, port);
    if(s != -1)
      return s;
  }

  printf ("http: unable to connect to >%s<\n", host);
  return -1;
}

static int http_parse_url (char *urlbuf, char **user, char **password,
			   char** host, int *port, char **filename) {
  char   *start = NULL;
  char   *authcolon = NULL;
  char	 *at = NULL;
  char	 *portcolon = NULL;
  char   *slash = NULL;

  if (user != NULL)
    *user = NULL;

  if (password != NULL)
    *password = NULL;

  if (host != NULL)
    *host = NULL;

  if (filename != NULL)
    *filename = NULL;

  if (port != NULL)
    *port = 0;

  start = strstr(urlbuf, "://");
  if (start != NULL)
    start += 3;
  else
    start = urlbuf;

  at = strchr(start, '@');
  slash = strchr(start, '/');

  if (at != NULL && slash != NULL && at > slash)
    at = NULL;

  if (at != NULL)
  {
    authcolon = strchr(start, ':');
    if(authcolon != NULL && authcolon > at)
      authcolon = NULL;

    portcolon = strchr(at, ':');
  } else
    portcolon = strchr(start, ':');

  if (portcolon != NULL && slash != NULL && portcolon > slash)
    portcolon = NULL;

  if (at != NULL)
  {
    *at = '\0';

    if (user != NULL)
      *user = start;

    if (authcolon != NULL)
    {
      *authcolon = '\0';

      if (password != NULL)
      	*password = authcolon + 1;
    }

    if (host != NULL)
      *host = at + 1;
  } else
    if (host != NULL)
      *host = start;

  if (slash != 0)
  {
    *slash = '\0';

    if (filename != NULL)
      *filename = slash + 1;
  } else if (filename != NULL)
    *filename = urlbuf + strlen(urlbuf);

  if (portcolon != NULL)
  {
    *portcolon = '\0';

    if (port != NULL)
      *port = atoi(portcolon + 1);
  }

  return 0;
}

static int http_basicauth (const char *user, const char *password,
			   char* dest, int len) {
  static char enctable[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  char        *tmp;
  char        *sptr;
  char        *dptr;
  int          totlen;
  int          enclen;
  int          count;

  totlen = strlen (user) + 1;
  if(password != NULL)
    totlen += strlen (password);

  enclen = ((totlen + 2) / 3 ) * 4 + 1;

  if (len < enclen)
    return -1;

  tmp = malloc (sizeof(char) * (totlen + 1));
  strcpy (tmp, user);
  strcat (tmp, ":");
  if (password != NULL)
    strcat (tmp, password);

  count = strlen(tmp);
  sptr = tmp;
  dptr = dest;
  while (count >= 3) {
    dptr[0] = enctable[(sptr[0] & 0xFC) >> 2];
    dptr[1] = enctable[((sptr[0] & 0x3) << 4) | ((sptr[1] & 0xF0) >> 4)];
    dptr[2] = enctable[((sptr[1] & 0x0F) << 2) | ((sptr[2] & 0xC0) >> 6)];
    dptr[3] = enctable[sptr[2] & 0x3F];
    count -= 3;
    sptr += 3;
    dptr += 4;
  }

  if (count > 0) {
    dptr[0] = enctable[(sptr[0] & 0xFC) >> 2];
    dptr[1] = enctable[(sptr[0] & 0x3) << 4];
    dptr[2] = '=';

    if (count > 1) {
      dptr[1] = enctable[((sptr[0] & 0x3) << 4) | ((sptr[1] & 0xF0) >> 4)];
      dptr[2] = enctable[(sptr[1] & 0x0F) << 2];
    }

    dptr[3] = '=';
    dptr += 4;
  }

  dptr[0] = '\0';

  free(tmp);
  return 0;
}


static void report_error (const char *url, const char *msg) {
  printf( "http: There was an error trying to download %s: %s\n", url, msg);
}

static http_t *http_open (const char *mrl) {

  http_t   *this;
  char     *proxy;
  int       done,len,linenum;
  char      mime_type[BUFSIZE+1];

  this = malloc (sizeof (http_t));

  strncpy (this->mrlbuf, mrl, BUFSIZE);

  this->proxybuf[0] = '\0';
  proxy = getenv("http_proxy");

  if (proxy != NULL)  {
    strncpy (this->proxybuf, proxy, BUFSIZE);

    if (http_parse_url (this->proxybuf, &this->proxyuser,
			&this->proxypassword, &this->proxyhost,
			&this->proxyport, NULL)) {
      free (this);
      report_error (mrl, "url parse error");
      return NULL;
    }

    if (this->proxyport == 0)
      this->proxyport = DEFAULT_HTTP_PORT;

    if (this->proxyuser != NULL)
      if (http_basicauth (this->proxyuser, this->proxypassword,
			  this->proxyauth, BUFSIZE)) {
	free (this);
	report_error (mrl, "proxy auth error");
	return NULL;
      }
  }

  if (http_parse_url (this->mrlbuf, &this->user, &this->password,
		      &this->host, &this->port, &this->filename)) {
    free (this);
    report_error (mrl, "url parse error");
    return NULL;
  }

  if (this->port == 0)
    this->port = DEFAULT_HTTP_PORT;

  if (this->user != NULL)
    if (http_basicauth (this->user, this->password, this->auth, BUFSIZE)) {
      free (this);
      report_error (mrl, "proxy auth error");
      return NULL;
    }

#ifdef LOG
  {
    char buf[256];

    sprintf (buf, "http: opening >/%s< on host >%s<",
	     this->filename, this->host);

    if (proxy != NULL)
      sprintf(buf, "%s via proxy >%s<", buf, this->proxyhost);

    sprintf(buf, "%s\n", buf);

    printf (buf);
  }
#endif

  if (proxy != NULL)
    this->fh = http_host_connect (this->proxyhost, this->proxyport);
  else
    this->fh = http_host_connect (this->host, this->port);

  this->curpos = 0;

  if (this->fh == -1) {
    free (this);
    report_error (mrl, "host connect error");
    return NULL;
  }

  if (proxy != NULL)
    if (this->port != DEFAULT_HTTP_PORT)
      sprintf (this->buf, "GET http://%s:%d/%s HTTP/1.0\015\012",
	       this->host, this->port, this->filename);
    else
      sprintf (this->buf, "GET http://%s/%s HTTP/1.0\015\012",
	       this->host, this->filename);
  else
    sprintf (this->buf, "GET /%s HTTP/1.0\015\012", this->filename);

  if (this->port != DEFAULT_HTTP_PORT)
    sprintf (this->buf + strlen(this->buf), "Host: %s:%d\015\012",
	     this->host, this->port);
  else
    sprintf (this->buf + strlen(this->buf), "Host: %s\015\012",
	     this->host);

  if (this->proxyuser != NULL)
    sprintf (this->buf + strlen(this->buf), "Proxy-Authorization: Basic %s\015\012",
	     this->proxyauth);

  if (this->user != NULL)
    sprintf (this->buf + strlen(this->buf), "Authorization: Basic %s\015\012",
	     this->auth);

  sprintf (this->buf + strlen(this->buf), "User-Agent: xine/%s\015\012",
	   VERSION);

  strcat (this->buf, "Accept: */*\015\012");

  strcat (this->buf, "\015\012");

  if (write (this->fh, this->buf, strlen(this->buf)) != (ssize_t)strlen(this->buf)) {
    free (this);
    report_error (mrl, "couldn't send request");
    return NULL ;
  }

#ifdef LOG
  printf ("http: request sent: >%s<\n",
	  this->buf);
#endif

  /* read and parse reply */
  done = 0; len = 0; linenum = 0;
  this->contentlength = 0;

  while (!done) {

    if (read (this->fh, &this->buf[len], 1) <=0) {

      switch (errno) {
      case EAGAIN:
	printf ("http: EAGAIN\n");
	continue;
      default:
	free (this);
	report_error (mrl, "read error");
	return NULL;
      }
    }

    if (this->buf[len] == '\012') {

      this->buf[len] = '\0';
      len--;

      if (len >= 0 && this->buf[len] == '\015') {
	this->buf[len] = '\0';
	len--;
      }

      linenum++;

#ifdef LOG
      printf ("input_http: answer: >%s<\n", this->buf);
#endif

      if (linenum == 1) {
        int httpver, httpsub, httpcode;
	char httpstatus[BUFSIZE+1];

	if (sscanf(this->buf, "HTTP/%d.%d %d %[^\015\012]", &httpver, &httpsub,
		   &httpcode, httpstatus) != 4)	{
	  free (this);
	  report_error (mrl, "invalid http answer");
	  return NULL;
	}

	if (httpcode >= 300 && httpcode < 400) {
	  printf ("http: 3xx redirection not implemented: >%d %s<\n",
		  httpcode, httpstatus);
	  free (this);
	  report_error (mrl, "3xx redirection not implemented");
	  return NULL;
	}
	if (httpcode < 200 || httpcode >= 300) {
	  printf ("http: http status not 2xx: >%d %s<\n",
		  httpcode, httpstatus);
	  free (this);
	  report_error (mrl, "http status not 2xx");
 	  return NULL;
	}
      } else {
	if (this->contentlength == 0) {
	  intmax_t contentlength;

	  if ( strlen(this->buf)<(BUFSIZE-20) && sscanf(this->buf, "Content-Length: %" PRIdMAX, &contentlength) == 1) {
	    printf ("http: content length = %" PRIdMAX " bytes\n", contentlength);
	    this->contentlength = (off_t)contentlength;
	  }
        }
        if ( sscanf(this->buf, "Content-Type: %" BUFSIZE_QUOTE "s", mime_type) == 1) {
	  printf ("http: content type = '%s'\n", mime_type);
	  strcpy(this->mime_type, mime_type);
	}
	if (!strncasecmp(this->buf, "Location: ", 10)) {
      	  printf ("http: Location redirection not implemented\n");
	  free (this);
	  report_error (mrl, "location redirection not implemented");
 	  return NULL;
	}
      }

      if (len == -1)
	done = 1;
      else
	len = 0;
    } else
      len ++;
  }

  return this;
}



char *http_download (const char *url, int *file_size) {

  int     total;
  char   *buf;
  http_t *http;

#ifdef LOG
  printf ("http: attempt to download >%s<\n", url);
#endif
  http = http_open (url);

  if (!http)
    return NULL;

#ifdef LOG
  printf ("http: connect passed \n");
#endif

  buf = NULL;  total = 0;
  while (1) {
    int n;

    buf = realloc (buf, total+4096);

    n = read (http->fh, &buf[total], 4096);

    if (n < 0) {

      switch (errno) {
      case EAGAIN:
	printf ("http: EAGAIN\n");
	continue;
      default:
	printf ("http: read error\n");

	free(buf);
	*file_size = 0;
	return NULL;
      }
    } else if (n==0) {
      break;
    }

    total += n;
#ifdef LOG
    printf ("http: got %d bytes of content\n", total);
#endif
  } ;

#ifdef LOG
  printf ("http: got %d bytes of content\n%s", total, buf);
#endif

  close (http->fh);

  free (http);

  *file_size = total;
  return buf;
}


int http_peek( const char *url, int size, char *contents, char *mime_type ) {

  int     total;
/*  char   *buf; */
  http_t *http;

#ifdef LOG
  printf ("http: attempt to download %u bytes from >%s<\n", size, url);
#endif
  http = http_open (url);

  if (!http)
    return 0;

  strcpy(mime_type,http->mime_type);

#ifdef LOG
  printf ("http: connect passed \n");
#endif

  total=read (http->fh, contents, size);

  close (http->fh);
  free (http);

  return total;
}
