/*
** weblib.c for assorted programs
**
** Copyright (C) 1995-1996, Andrew 'Dancer' Vesperman. All rights reserved.
** Copyright (C) 1997, Howard Chu
**
** This file can be redistributed under the terms of the GNU General
** Public Licence.
*/

#include <unistd.h>
#include "module.h"

#include <osbind.h>
#include <mintbind.h>
#include <aesbind.h>
#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/time.h>
#include <utime.h>

#include "hostinfo.h"
#include "weblib.h"

char            gbuf[READ_SIZE + 256];
int             ifd, ofd, ap_id;
extern short    _global[];

FILE           *fp;

jmp_buf         myjmp, redjmp;

struct Args     Args;
CookieFlag cookieFlag;

char           *debug;
urlB            proxyHosts[RQ_PROXIES];

protoT         *protosw[] = {
			     doftp, dohttp, dowais, dogopher, donntp, dofail,
			     domail, donews, dohttp, donews};

char           *gzip;

#if DEBUG_PARENT
static int      didreopen;
#endif
static int      didreply;

long 
doftp(urlB * u)
{
    return 0;
}
long 
dowais(urlB * u)
{
    return 0;
}
long 
dogopher(urlB * u)
{
    return 0;
}
long 
donntp(urlB * u)
{
    return 0;
}
long 
dofail(urlB * u)
{
    return -1;
}
long 
donews(urlB * u)
{
    return 0;
}

/* ----------------------------------------------------------------- **
** get_url - Fetch URL and write it as a HTML file                   **
**           Returns 0 if OK, else `errno'                           **
** ----------------------------------------------------------------- */

long            ___CDECL
get_url(char *url, char *filename)
{
    return _get_url(url, GET, filename, NULL, NULL);
}

/* ----------------------------------------------------------------- **
** post    - Post FORM, fetch result and write it as a HTML file     **
**           Returns 0 if OK, else `errno'                           **
** ----------------------------------------------------------------- */

long            ___CDECL
post(char *url, char *content, char *enctype, char *filename)
{
    return _get_url(url, POST, filename, content, enctype);
}


/* ----------------------------------------------------------------- **
** get_url_info - Retrieve infos for an URL                          **
**                Returns 0 if OK, else `errno'                      **
** ----------------------------------------------------------------- */

long            ___CDECL
get_url_info(char *url, long *timep, long *sizep, char *type)
{
    return _get_url(url, HEAD, sizep, timep, type);
}

long            ___CDECL
mailto(char *url, char *subject, char *filename)
{
    return _get_url(url, MAIL, filename, subject, NULL);
}

long            ___CDECL
get_if_newer(char *url, char *filename, long *time)
{
    return _get_url(url, GETNEW, filename, time, NULL);
}

long            ___CDECL
_get_url(char *Url, int kind, void *ptr1, void *ptr2, void *ptr3)
{
    long           *l;
    short          *buf = (short *) l;
    int             i, t;

    Args.Aurl = Url;
    Args.Afile = ptr1;
    Args.Asubj = ptr2;
    Args.Atype = ptr3;

#if DEBUG_PARENT
    if (debug)
    {
	if (!didreopen)
	{
	    freopen("CMTWO.LOG", "w", stderr);
	    didreopen = 1;
	}
	fprintf(stderr, "\nclk %ld req %d, url %lx, p1 %lx, p2 %lx, p3 %lx\n",
		time(0L), kind, Url, ptr1, ptr2, ptr3);
	fflush(stderr);
	fprintf(stderr, "url: %s\n", Url);
	fflush(stderr);
	switch (kind)
	{
	case HEAD:
	    fprintf(stderr, "time %ld, size %ld, type %s\n",
		    *Args.Atime, *Args.Asize, Args.Atype);
	    break;
	case GET:
	    fprintf(stderr, "file %s\n", Args.Afile);
	    break;
	case POST:
	    fprintf(stderr, "file %s, enc %s, body %s\n",
		    Args.Afile, Args.Atype, Args.Acont);
	    break;
	case GETNEW:
	    fprintf(stderr, "file %s, time %ld\n",
		    Args.Afile, *Args.Atime);
	    break;
	case MAIL:
	    fprintf(stderr, "file %s, subject %s\n",
		    Args.Afile, Args.Asubj);
	    break;
	}
	fflush(stderr);
    }
#endif

    Fputchar(ofd, kind, 0);

    for (t = 0;;)
    {
	l = (long *) browser->aes_events(30000);
	if ((long) l == -1)
	{
	    didreply = 1;
	    kill(child, SIGINT);
	    return 0;
	}
	if (!l)
	{
	    ++t;
	    browser->msg_status(STATUS_WAITINGFORDATA, t);
	    continue;
	}
	buf = (short *) l;
	if (buf[0] != 3)
	{
	    browser->aes_messages((int *) buf);
	    continue;
	}
	t = 0;
	switch (buf[1])
	{
	case R_ERROR:
	    if (l[1])
	    {
		if (l[1] > 0)
		    l[1] = -l[1];
		browser->msg_error(l[1]);
	    }
#if DEBUG_PARENT
	    if (debug)
		fprintf(stderr, "\nclk %ld return %ld\n",
			time(0L), l[1]);
#endif
	    return l[1];
	case R_STATUS:
	    browser->msg_status(l[1], l[2]);
	    break;
	case R_ALERT:
	    i = browser->alert_box(l[1], l[2]);
	    break;
	case R_CLEAR:
	    i = browser->clear_cache(l[1]);
	    break;
	case R_REDIR:
	    i = browser->new_url((char *) l[1], (char **) l[2]);
	    break;
	case R_PROMPT:
	    i = browser->ask_user(l[1], (char **) l[2]);
	    break;
	case R_ONLINE:
	    browser->online(l[1]);
	    break;
	}
	switch (buf[1])
	{
	case R_ALERT:
	case R_CLEAR:
	case R_REDIR:
	case R_PROMPT:
	    Fputchar(ofd, i, 0);
	}
    }
    return EBADREQ;
}

int 
respond(int how, long l1, long l2)
{
    long            l[4];
    short          *buf = (short *) l;

    buf[0] = 3;
    buf[1] = how;
    l[1] = l1;
    l[2] = l2;
    appl_write(ap_id, 16, buf);
    switch (how)
    {
    case R_ERROR:
    case R_STATUS:
    case R_ONLINE:
	return 0;
    case R_ALERT:
    case R_CLEAR:
    case R_REDIR:
    case R_PROMPT:
	return Fgetchar(ifd, 0);
    }
}

int
no_proxy(urlB * u)
{
    int             len, dlen = 0, port;
    char           *domain, *ptr, *prev;

    /*
     * If the CAB option no_proxy is not defined then we will always use a
     * proxy 
     */
    if (!browser->proxy->no_proxy || !browser->proxy->no_proxy[0])
	return 0;

    /*
     * no_proxy is defined. Step through the list and try to match it against
     * the supplied site. If we find a match then we do not proxy requests
     * for this site 
     */
    LOG(("searching no_proxy list\n"));
    domain = strchr(u->host->name.txt, '.');
    if (domain)
	dlen = u->host->name.len - (domain - u->host->name.txt);

    for (ptr = browser->proxy->no_proxy; *ptr;)
    {
	for (prev = ptr; *ptr; ++ptr)
	{
	    if (*ptr == ',' || *ptr == ';' || *ptr == ':')
		break;
	}
	len = ptr - prev;
	port = 0;
	if (*ptr == ':')
	{
	    port = atoi(++ptr);
	    for (; *ptr; ++ptr)
		if (*ptr == ',' || *ptr == ';')
		    break;
	}
	if (*ptr)
	    ++ptr;
	/* Do a domain comparison */
	if (*prev == '.')
	{
	    if (len != dlen)
		continue;
	    if (strnicmp(domain, prev, len))
		continue;
	} else			/* compare full hostname */
	{
	    if (len != u->host->name.len)
		continue;
	    if (strnicmp(u->host->name.txt, prev, len))
		continue;
	}
	if (!port || port == u->port)
	{
	    LOG(("Do not proxy for this site: %s\n", u->host->name.txt));
	    return 1;
	}
    }
    LOG(("Access Proxy for this site: %s\n", u->host->name.txt));
    return 0;
}

static urlB     theUrl;

urlB           *
processUrl(char *url)
{
    urlB           *u = &theUrl;
    int             req;

    if (parseUrl(url, u, PARSE_HOST))
	return NULL;
    u->flags &= ~U_USE_PROXY;

    req = u->request;
    if (req >= RQ_PROXIES)
	return u;

    if (req > NO_PROXY)
    {				/* These guys (MAIL, NEWS) require a server
				 * entry. */
	if (!proxies[req] || !proxies[req][0])
	    return NULL;
    } else
    {
	if (!proxies[req] || !proxies[req][0] || no_proxy(u))
	    return u;
    }

    /*
     * Have we already initialized this proxy service? Has CAB's notion of
     * the proxy server changed since the last time we set it up? 
     */
    if (!proxyHosts[req].port ||
	(proxyHosts[req].user.txt &&
	 stricmp(proxies[req], proxyHosts[req].user.txt)))
    {
	if (proxyHosts[req].user.txt)
	    free(proxyHosts[req].user.txt);

	memset(&proxyHosts[req], 0, sizeof(urlB));
	if (parseUrl(proxies[req], &proxyHosts[req], PARSE_PROXY))
	    return NULL;
	proxyHosts[req].user.txt = malloc(strlen(proxies[req]) + 1);
	if (proxyHosts[req].user.txt)
	    strcpy(proxyHosts[req].user.txt, proxies[req]);
	resolveHost(proxyHosts[req].host);
	/* If the proxy string didn't specify a port, use the default... */
	if (!proxyHosts[req].port)
	    proxyHosts[req].port = urltypes[req].port;
    }
    u->flags |= U_USE_PROXY;
    return u;
}

long
readline(int zeroit, sockB * sock, char *buf, int limit)
{
    char           *ptr = buf;
    long            b, c = 0;
    static long     bcount;

    if (zeroit)
	bcount = 0;

    while (++c < limit)
    {
	b = netGetChar(sock);
	if (b < 0 || b == 0xff1a)
	{
	    if (b == 0xff1a)
		b = EOF;
	    LOG(("netGetChar got %d\n", b));
	    return b;
	}
	if (b == '\r')
	    continue;
	*ptr++ = b;
	if (b == '\n')
	    break;
    }
    bcount += c - 1;
    if (zeroit || ptr - buf == 1)
	respond(R_STATUS, STATUS_RECEIVING_DATA, bcount);
    *ptr = '\0';
    return ptr - buf;
}

const char     *monthtab[12] = {"Jan", "Feb", "Mar",
				"Apr", "May", "Jun",
				"Jul", "Aug", "Sep",
				"Oct", "Nov", "Dec"};
const char     *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

long
make_unix_time(char *s)
{
    struct tm       time;
    int             i, ysub = 1900, fmt = 0;
    char            day[128];
    char            month[20];
    char           *n;

    if (s[3] != ' ')
    {
	fmt = 1;
	if (s[3] != ',')
	    ysub = 0;
    }
    for (n = s; *n; ++n)
	if (*n == '-' || *n == ':')
	    *n = ' ';

    time.tm_mon = 0;
    if (fmt)
	sscanf(s, "%s %d %s %d %d %d %d GMT",
	       day, &time.tm_mday, month, &time.tm_year,
	       &time.tm_hour, &time.tm_min, &time.tm_sec);
    else
	sscanf(s, "%s %s  %d %d %d %d %d",
	       day, month, &time.tm_mday,
	       &time.tm_hour, &time.tm_min, &time.tm_sec, &time.tm_year);
    if (time.tm_year > 100)
	time.tm_year -= ysub;

    for (i = 0; i < 12; i++)
	if (!stricmp(month, monthtab[i]))
	{
	    time.tm_mon = i;
	    break;
	}
    time.tm_isdst = 0;		/* daylight saving is never in effect in GMT */
    return mktime(&time);
}

const char     *reqs[] = {"HEAD", "GET", "POST", "GET", "", "", "MAIL"};

long            ___CDECL
GetUrl(int kind)
{
    urlB           *u;
    int i;

    if (!Args.Aurl)
	return EBADREQ;

    LOG(("\n%s %s\n", reqs[kind], Args.Aurl));

    respond(R_STATUS, STATUS_DATALENGTH, -1);

    if (kind != HEAD)
    {
	LOG(("Filename %s\n", Args.Afile));
    }
    respond(R_STATUS, STATUS_RESOLVING_HOST, 0);
    u = processUrl(Args.Aurl);
    if (!u)
	return EBADREQ;

    i = setjmp(redjmp);
    if (i)
	kind = i-1;

    if (debug)
    {
	urlB           *nu = u;

	printf("URL Parsed.\n");
	printf("REQ : %d(%s)\n", u->request,
	       u->request >= 0 ? urltypes[u->request].name : "unknown");
	if (u->flags & U_USE_PROXY)
	    nu = &proxyHosts[u->request];
	printf("PATH: %s\n", u->path ? u->path->name.txt : "");
	printf("SITE: %s\n", nu->host ? nu->host->name.txt : "");
	printf("PORT: %d\n", nu->port);
    }
    if (kind == HEAD)
    {
	if (!Args.Asize)
	    return EBADREQ;
	*Args.Asize = -1;
	/* Got info already, just return it */
	if (u->path->size || u->path->time || u->path->type)
	{
	    *Args.Atime = u->path->time;
	    *Args.Asize = u->path->size;
	    if (u->path->type && Args.Atype)
		strcpy(Args.Atype, u->path->type);
	    return 0;
	}
    }
    respond(R_STATUS, STATUS_CONNECTING_HOST, 0);
    if (openConn(u) < 0)
	return errno;

    LOG(("Connection opened\n"));

    respond(R_STATUS, STATUS_SENDING_REQUEST, 0);

    if (kind == MAIL)
	return domail(u);

    if (kind == POST && u->request != HTTP && u->request != HTTPS)
	return EBADREQ;

    /* All the basic stuff goes thru an HTTP proxy, when proxies are used */
    if ((u->flags & U_USE_PROXY) && u->request < NO_PROXY)
	return dohttp(u, kind);

    return protosw[u->request] (u, kind);
}

static char nib2b64[0x40+1] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void
base64(char *in, char *out)
{
    unsigned long bits;
    unsigned char c;
    int done = 0;

    for (;c = *in;)
    {
	bits = c;
	c = * ++in;
	bits <<= 8;
	bits |= c;
	bits <<= 8;
	if (!c)
	{
	   done = 2;
	} else
	{
	    c = * ++in;
	    bits |= c;
	    if (!c)
		done = 1;
	}
	in++;
	out[3] = nib2b64[bits & 0x3f];
	bits >>= 6;
	out[2] = nib2b64[bits & 0x3f];
	bits >>= 6;
	out[1] = nib2b64[bits & 0x3f];
	bits >>= 6;
	out[0] = nib2b64[bits & 0x3f];
	if (done)
	    out[3] = '=';
	if (done == 2)
	    out[2] = '=';
	out += 4;
	if (done)
	    break;
    }
    *out = '\0';
}

void 
handler(int sig)
{
    LOG(("\nClk %ld Got signal %d\n\n", time(0L), sig));

    if (sig == SIGTERM)
    {
	if (debug)
	{
	    fclose(stdout);
#if DEBUG_PARENT
	    fclose(stderr);
#endif
	}
	appl_exit();
	Pterm0();
    }
    if (fp)
    {
	fclose(fp);
	fp = NULL;
    }
    closeConn(Conn);
    memset(&Args, 0, sizeof(Args));
    longjmp(myjmp, EINTR);
}

void
readconf()
{
    int i;
    char *ptr;
	
    fp = fopen("cabovl.cnf", "r");
    if (!fp)
	fp = fopen("modules\\cabovl.cnf", "r");
    if (!fp)
	return;
    while (fgets(gbuf, sizeof(gbuf), fp))
    {
	if (gbuf[0] == '#')
	    continue;
	ptr = strchr(gbuf, '\n');
	if (ptr)
	    *ptr = '\0';
	ptr = strchr(gbuf, '=');
	if (!ptr)
	    continue;
	*ptr++ = '\0';
	if (ptr - gbuf == 5 && !stricmp(gbuf, "gzip"))
	{
	    gzip = malloc(strlen(ptr));
	    if (gzip)
		strcpy(gzip, ptr);
	} else
	if (ptr - gbuf == 8 && !stricmp(gbuf, "cookies"))
	{
	    if (!stricmp(ptr, "reject"))
		cookieFlag = Creject;
	    else
	    if (!stricmp(ptr, "prompt"))
		cookieFlag = Cprompt;
	    else
	    if (!stricmp(ptr, "server"))
		cookieFlag = Cserver;
	    else
	    if (!stricmp(ptr, "accept"))
		cookieFlag = Caccept;
	} else
	if (ptr - gbuf == 9 && !stricmp(gbuf, "mailfrom"))
	{
	    strcpy(username, ptr);
	} else
	if (ptr - gbuf == 9 && !stricmp(gbuf, "maxhosts"))
	{
	    i = atoi(ptr);
	    if (i < MIN_HOSTS)
		i = MIN_HOSTS;
	    hostX.max = i;
	} else
	if (ptr - gbuf == 9 && !stricmp(gbuf, "maxnodes"))
	{
	    i = atoi(ptr);
	    if (i < MIN_NODES)
		i = MIN_NODES;
	    nodeX.max = i;
	}
	
    }
    fclose(fp);
}

void
myloop()
{
    long            i;
    int             id;

    Fclose(ofd);
    id = appl_init();
    if (_global[1] == -1)
	menu_register(id, "  CAB+MiNTNet");

    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    debug = getenv("CMDEBUG");
    if (debug)
	freopen("cmdebug.log", "w", stdout);

    readconf();

    netInit();

    if (setjmp(myjmp) && !didreply)
	respond(R_ERROR, 0, 0);
    didreply = 0;

    for (;;)
    {
	i = Fgetchar(ifd, 0);
	if (i < 0 || i == 0xff1a)
	    break;
	fp = NULL;
	id = i;
	i = GetUrl(id);
	respond(R_ERROR, i, 0);
    }
    if (debug)
    {
	fclose(stdout);
#if DEBUG_PARENT
	fclose(stderr);
#endif
    }
    appl_exit();
}
