/*
** dohttp.c
**
** 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 "network.h"
#include "weblib.h"

#define	DO_GZIP		0x001
#define	DO_REDIRECT	0x002
#define	DO_CHUNKED	0x004
#define	GOT_SIZE	0x008
#define	GOT_DATE	0x010
#define	KEEP_CONN	0x020
#define	CHECK_END	0x040
#define	RETRY_AUTH	0x080
#define	RETRY_AUTH2	0x100
#define	RETRY_PR_AUTH	0x200

extern jmp_buf	redjmp;

typedef struct htParms
{
    int             kind;
    int             hterr;
    int             flags;
}               htParms;


do_cookie(urlB * u, char * in)
{
    int version = 0, newdomain;
    int flags = 0;
    unsigned long expire = 0;
    lstr name, value, path = {0, 0}, domain = {0, 0};
    char *ptr, c;
    cookieB *cookieP = NULL;
    pathB *pathP;
    domainB *domainP;

    if (*in == '2')
    {
	in++;
	version = 1;
    }
    if (*in == ':')
	in++;

    for(;;)
    {
	while (isspace(*in))
	    in++;

	for (ptr = in; c = *ptr; ++ptr)
	    if (c == '=' || c == ';' || c == ',' || c == ' ')
		break;
	if (!c)
	    break;
	name.txt = in;
	name.len = ptr - in;
	*ptr++ = '\0';
	if (c == '=')
	{
	    for (in = ptr; c = *ptr; ++ptr)
		if (c == ';')
		    break;
	    value.txt = in;
	    value.len = ptr - in;
	    *ptr++ = '\0';
	    in = ptr;
	} else
	{
	    value.txt = "";
	    value.len = 0;
	}
	if (name.len == 4 && !strnicmp(name.txt, "path", 4))
	{
	    path.txt = value.txt;
	    path.len = value.len;
	    flags |= P_COOK_PATH;
	} else
	if (name.len == 6 && !strnicmp(name.txt, "secure", 6))
	{
	    flags |= P_COOK_SECURE;
	} else
	if (name.len == 6 && !strnicmp(name.txt, "domain", 6))
	{
	    domain.txt = value.txt;
	    domain.len = value.len;
	    flags |= P_COOK_DOMAIN;
	} else
	if (name.len == 7 && !strnicmp(name.txt, "expires", 7))
	{
	    expire = make_unix_time(value.txt);
	} else
	if (name.len == 7 && !strnicmp(name.txt, "max-age", 7))
	{
	    sscanf(value.txt,"%ld",&expire);
	    if (expire)
		expire += time(0L);
	    else
		expire = 1;
	} else
	if (name.len == 7 && !strnicmp(name.txt, "version", 7))
	{
	    version=atoi(value.txt);
	} else
	{
	    cookieB *new = NewCookie(&name, &value);
	    if (!new)
		return ENOMEM;
	    new->next = cookieP;
	    cookieP = new;
	}
	if (!c)
	    break;
    }
    if (!path.txt)
    {
	path.txt = u->path->name.txt;
	path.len = u->path->name.len;
    }
    if (!domain.txt)
    {
	domain.txt=u->host->name.txt;
	domain.len=u->host->name.len;
    }
    /* The cookie path must be a prefix of the request path */
    if (strncmp(path.txt, u->path->name.txt, path.len))
	goto cleanup;
    /* An explicitly set domain must begin with a dot */
    if ((flags & P_COOK_DOMAIN) && domain.txt[0] != '.')
	goto cleanup;
    /* A cookie domain must have an embedded dot */
    if (domain.txt[0] == '.')
    {
	ptr = strchr(domain.txt+1, '.');
	if (!ptr || !ptr[1])
	    goto cleanup;
    }
    /* The cookie domain must be the immediate parent of the request host */
    {
	char *d1, *d2;
	d1 = strchr(domain.txt, '.');
	d2 = strchr(u->host->name.txt, '.');
	if (stricmp(d1, d2))
	    goto cleanup;
    }
    if (cookieFlag == Cprompt ||
	(cookieFlag == Cserver && stricmp(domain.txt,u->host->name.txt)))
    {
	char form[256], *p1, *p2;
	cookieB *p;
	long i;

	i = sprintf(in, "Got Cookie from %s for //%s%s",
	    u->host->name.txt, domain.txt, path.txt);
	ptr = in+i;
	
	for (p=cookieP; p; p=p->next)
	{
	    i = sprintf(ptr, "; %s=%s", p->name, p->value);
	    ptr += i;
	}
	strcpy(form,"[1][");
	for (i = 1, p1 = form + 4, p2 = in; i < 150 && *p2; i++, p2++)
	{
	    if (!(i % 30))
		*p1++ = '|';
	    if (*p2 == '|')
		*p1++ = '!';
	    else
		*p1++ = *p2;
	}
	strcpy(p1, "][Accept|Reject]");
	i = form_alert(1, form) >> 16;
	/* All that work wasted. Throw this one away... */
	if (i == 2)
	{
cleanup:    for (;cookieP;cookieP = p)
	    {
		p = cookieP->next;
		FreeCookieB(cookieP);
	    }
	    return 0;
	}
    }
    domainP = GetDomainB(&domain, u->host, &newdomain);
    if (!domainP)
	goto cleanup;
    pathP = GetPathB(&path, domainP);
    if (!pathP)
	goto cleanup;
    /* If expiring right away, delete all cookie info on this path */
    if (expire && expire < time(0L))
    {
	cookieB *c1, *c2;
	for (c1=pathP->cookies;c1;c1=c2)
	{
	    c2 = c1->next;
	    FreeCookieB(c1);
	}
	pathP->cookies = NULL;
	goto cleanup;
    }
    pathP->expire = expire;
    pathP->version = version;
    pathP->flags = flags;

    /* We don't just append; look for matching names and replace their
     * values ... 
     */
    AddCookies(cookieP, pathP);
    /* If this cookie is for a domain, see if it needs to be added to
     * any other hosts.
     */
    if (newdomain && domain.txt[0] == '.')
	AddNewDomain(domainP);
}

int
do_auth(urlB *u, htParms *hP, char *in, int is_proxy)
{
    char *ptr, *user, *pass, c;
    domainB *domainP;
    pathB *pathP;
    authB *auth;
    hostB *host;
    int method = 0, newdomain;
    long i;
    lstr realm = { 0, 0 }, path, nonce, opaque;

    for(;;)
    {
	while (isspace(*in))
	    in++;

	for (ptr = in; c = *ptr; ++ptr)
	    if (c == '=' || c == ';' || c == ',' || c == ' ')
		break;
	if (!c)
	    break;

	if (!strnicmp(in, "basic", 5))
	{
	    in += 5;
	} else
	if (!strnicmp(in, "digest", 6))
	{
	    method = 1;
	    in += 6;
	} else
	if (!strnicmp(in, "realm", 5))
	{
	    for (in = ++ptr; c = *in; ++in)
		if (c == '"')
		    break;
	    if (!c)
		break;
	    for (ptr = ++in; c = *ptr; ++ptr)
		if (c == '"')
		    break;
	    if (!c)
		break;
	    realm.txt = in;
	    realm.len = ptr - in;
	    *ptr++ = '\0';
	    in = ptr;
	}
    }

    if (is_proxy)
    {
	host = proxyHosts[u->request].host;
	path.txt = "/";
	path.len = 1;
    } else
    {
	host = u->host;
	path = u->path->name;
	ptr = strrchr(path.txt, '/');
	path.len = ptr - path.txt;
    }

    domainP = GetDomainB(&host->name, host, &newdomain);
    if (!domainP)
	return 0;
    pathP = GetPathB(&path, domainP);
    if (!pathP)
	return 0;
    auth = GetAuthB(&realm, pathP);
    if (!auth)
	return 0;
    /* This might be a retry for a realm we already have auth info for, if
     * the current path has multiple auth realms. If so, return the current
     * realm info right away, without prompting for new info. But if we've
     * already tried this once, forget it, we have to prompt.
     */
    if (auth->user && !(hP->flags & RETRY_AUTH2))
    {
	hP->flags |= RETRY_AUTH2;
	return 1;
    }
    sprintf(in, "%s on %s", realm.txt, host->name.txt);
    {
	char form[256], *p1, *p2;

	strcpy(form,"[1][Authentication required for|");
	for (i = 1, p1 = form + 32, p2 = in; i < 120 && *p2; i++, p2++)
	{
	    if (!(i % 30))
		*p1++ = '|';
	    if (*p2 == '|')
		*p1++ = '!';
	    else
		*p1++ = *p2;
	}
	strcpy(p1, "][OK|Cancel]");
	i = form_alert(1, form) >> 16;
	if (i == 2)
	    return 0;
    }
    i = respond(R_PROMPT, REQUEST_NAME, (long) &user);
    if (i < 1)
	return 0;
    if (auth->user)
	free(auth->user);
    auth->user = malloc(strlen(user)+1);
    if (!auth->user)
    {
cleanup:
	pathP->auth = pathP->auth->next;
	FreeAuthB(auth);
	return 0;
    }
    strcpy(auth->user, user);
    i = respond(R_PROMPT, REQUEST_PASSWORD, (long) &pass);
    if (i < 1)
	goto cleanup;
    if (auth->pass)
	free(auth->pass);
    auth->pass = malloc(strlen(pass)+1);
    if (!auth->pass)
	goto cleanup;
    strcpy(auth->pass, pass);
    return 1;
}

long 
htHead(urlB * u, sockB * conn, htParms * hP)
{
    long            e;
    int		    oldflags = hP->flags;

    hP->flags &= ~(RETRY_AUTH|RETRY_PR_AUTH);

    while ((e = readline(0, Conn, gbuf, READ_SIZE)) > 1 && gbuf[0])
    {
	LOG(("%s", gbuf));
	gbuf[strlen(gbuf) - 1] = '\0';
	if ((hP->flags & DO_REDIRECT) && !strnicmp("Location:", gbuf, 9))
	{
	    char           *new;
	    for (new = gbuf + 9; *new == ' ' || *new == '\t'; new++)
		;
	    closeConn(Conn);
	    if (hP->kind == POST)
	    {
		/* 1.1 spec requires manual POST redirection */
		if (hP->hterr == 301 || hP->hterr == 302)
		{
		    char            a[200], *p1, *p2;
		    strcpy(a, "[2][POST got redirected!");
		    for (e = 0, p1 = a + 24, p2 = new; e < 120 && *p2; e++, p2++)
		    {
			if (!(e % 30))
			    *p1++ = '|';
			if (*p2 == '|')
			    *p1++ = '!';
			else
			    *p1++ = *p2;
		    }
		    strcpy(p1, "][GET this|Re-POST|Cancel]");
		    e = form_alert(1, a) >> 16;
		    if (e == 1)
			hP->kind = GET;
		    else
		    if (e != 2)
			continue;
		} else
		if (hP->hterr == 303)
		{
		    hP->kind = GET;
		}
	    }
	    /* Must turn URL into absolute form before giving it to CAB */
	    u = processUrl(new);
	    if (!u)
		return 0;
	    new = gbuf;
	    new += sprintf(gbuf, "%s://%s", urltypes[u->request].name,
		u->host->name.txt);
	    if (u->port != urltypes[u->request].port)
		new += sprintf(new, ":%d", u->port);
	    strcpy(new, u->path->name.txt);
	    if (!respond(R_REDIR, (long) gbuf, (long) &Args.Afile))
	    {
		Args.Aurl = gbuf;
		LOG(("Redirecting to %s\n", gbuf));
		longjmp(redjmp, hP->kind+1);
	    }
	    return 0;
	} else
	if (!strnicmp("content-encoding: ", gbuf, 18))
	{
	    hP->flags |= DO_GZIP;
	} else
	if (!strnicmp("content-length: ", gbuf, 16))
	{
	    sscanf(gbuf + 16, "%ld", &u->path->size);
	    hP->flags |= GOT_SIZE;
	} else
	if (!strnicmp("last-modified: ", gbuf, 15))
	{
	    u->path->time = make_unix_time(gbuf + 15);
	    hP->flags |= GOT_DATE;
	} else
	if (!strnicmp("content-type: ", gbuf, 14))
	{
	    e = strlen(gbuf + 14);
	    if (u->path->type && stricmp(gbuf + 14, u->path->type))
	    {
		free(u->path->type);
		u->path->type = NULL;
	    }
	    if (!u->path->type)
	    {
		u->path->type = malloc(e + 1);
		if (u->path->type)
		    strcpy(u->path->type, gbuf + 14);
	    }
	    e = 0;
	} else
	if (!strnicmp("transfer-encoding: ", gbuf, 19))
	{
	    if (strnicmp(gbuf + 19, "chunked", 7))
		return -1;
	    hP->flags |= DO_CHUNKED;
	} else
	if (!strnicmp("connection: ", gbuf, 12))
	{
	    if (!strnicmp(gbuf + 12, "keep-alive", 10))
		hP->flags |= KEEP_CONN;
	    else
		hP->flags &= ~KEEP_CONN;
	} else
	if (!strnicmp("etag: ", gbuf, 6))
	{
	    if (u->path->etag && strcmp(gbuf+6, u->path->etag))
		free(u->path->etag);
	    u->path->etag = malloc(strlen(gbuf+6)+1);
	    if (u->path->etag)
		strcpy(u->path->etag, gbuf+6);
	}
	if (!strnicmp("set-cookie", gbuf, 10))
	{
	    if (cookieFlag != Creject)
		do_cookie(u, gbuf+10);
	} else
	if (!strnicmp("www-authenticate: ", gbuf, 18))
	{
	    if (oldflags & RETRY_AUTH)
		form_alert(1, "[3][Authentication failed!][Continue]");
	    if (do_auth(u, hP, gbuf+18, PARSE_HOST))
		hP->flags |= RETRY_AUTH;
	} else
	if (!strnicmp("proxy-authenticate: ", gbuf, 21))
	{
	    if (oldflags & RETRY_PR_AUTH)
		form_alert(1, "[3][Proxy Authentication failed!][Continue]");
	    if (do_auth(u, hP, gbuf+21, PARSE_PROXY))
		hP->flags |= RETRY_PR_AUTH;
	}
    }
    if (!e && hP->kind == HEAD)
    {
	*Args.Asize = u->path->size;
	*Args.Atime = u->path->time;
	if (u->path->type)
	    strcpy(Args.Atype, u->path->type);
    }
    if (e < 0)
    {
	LOG(("Fatal error reading headers, %d\n", e));
    }
    return e;
}

char *
formatAuth(hostB *host, char *rpath, char *ptr, int is_proxy)
{
    int i;
    domainH *dh;
    pathB *path;

    for (dh = host->domains; dh; dh=dh->next)
    {
	for (path = dh->domain->paths; path; path=path->next)
	{
	    char ibuf[128], obuf[256];
	    if (!path->auth)
		continue;
	    if (strncmp(path->name.txt, rpath, path->name.len))
		continue;
	    sprintf(ibuf, "%s:%s", path->auth->user, path->auth->pass);
	    base64(ibuf, obuf);
	    i = sprintf(ptr, "%sAuthorization: Basic %s\n",
		is_proxy ? "Proxy-" : "",  obuf);
	    ptr += i;
	}
    }
    return ptr;
}

char *
formatDomain(urlB * u)
{
    char *ptr;
    domainH *dh;
    pathB *path;
    cookieB *cook;
    int i, j=0;

    ptr = gbuf+256;
    *ptr='\0';
    for (dh = u->host->domains; dh; dh=dh->next)
    {
	for (path = dh->domain->paths; path; path=path->next)
	{
	    if (!path->cookies)
		continue;
	    if ((path->flags & P_COOK_SECURE) && !Conn->ssl_con)
		continue;
	    if (strncmp(path->name.txt, u->path->name.txt, path->name.len))
		continue;
	    if (!j)
	    {
		strcpy(ptr, "Cookie:");
		ptr += 7;
		j = 1;
		if (path->version)
		{
		    i = sprintf(ptr, " $Version=\"%d\"", path->version);
		    ptr += i;
		    j = 2;
		}
	    }
	    for (cook = path->cookies; cook; cook=cook->next)
	    {
		if (j > 1)
		    *ptr++ = ';';
		else
		    j = 2;
		i = sprintf(ptr, " %s=%s", cook->name, cook->value);
		ptr += i;
		if (path->version)
		{
		    if (path->flags & P_COOK_PATH)
		    {
			i = sprintf(ptr, "; $path=%s", path->name.txt);
			ptr += i;
		    }
		    if (path->flags & P_COOK_DOMAIN)
		    {
			i = sprintf(ptr, "; $domain=%s",
				dh->domain->name.txt);
			ptr += i;
		    }
		}
	    }
	}
    }
    if (j)
    {
	*ptr++='\n';
	*ptr='\0';	
    }
    ptr = formatAuth(u->host, u->path->name.txt, ptr, 0);
    if (u->flags & U_USE_PROXY)
	formatAuth(proxyHosts[u->request].host, "/", ptr, 1);

    return gbuf+256;
}

long 
dohttp(urlB * u, int kind)
{
    hostB          *h = u->host;
    int             e;
    char           *ptr;
    long            bcount = 0, rem;
    long            sz = 0;
    int             primitive = 0;
    htParms         hP;

    memset(&hP, 0, sizeof(hP));
    hP.kind = kind;

reauth:
    ptr = formatDomain(u);

retry:
    LOG((">>\n"));
    fprintf(Conn->sp, "%s ", reqs[kind]);
    if (u->flags & U_USE_PROXY)
    {
	fprintf(Conn->sp, "%s://", urltypes[u->request].name);
	/* Check user/pw info for ftp */
	if (u->request == FTP)
	{
	    if (u->user.txt)
		fprintf(Conn->sp, "%*s", u->user.len, u->user.txt);
	    if (u->pass.txt)
		fprintf(Conn->sp, ":%*s", u->pass.len, u->pass.txt);
	    if (u->user.txt || u->pass.txt)
		fputc('@', Conn->sp);
	}
	fputs(u->host->name.txt, Conn->sp);
	if (u->port != urltypes[u->request].port)
	    fprintf(Conn->sp, ":%d", u->port);
    }
    fprintf(Conn->sp, "%s HTTP/1.1\n", u->path->name.txt);
    fprintf(Conn->sp, "User-Agent: %s\nAccept: *\n", agent);
    fprintf(Conn->sp, "Host: %s\n", h->name.txt);
    fputs(ptr, Conn->sp);
    if (gzip)
	fprintf(Conn->sp, "Accept-Encoding: gzip, compress, x-gzip, x-compress\n");
    if (*browser->reloadflag)
	fprintf(Conn->sp, "Pragma: no-cache\nCache-Control: no-cache\n");
    if (kind == POST)
    {
	fprintf(Conn->sp, "Content-type: %s\nContent-length: %ld\n\n%s\n",
		Args.Atype, strlen(Args.Acont), Args.Acont);
    } else
    if (kind == GETNEW)
    {
	if (u->path->etag)
	    fprintf(Conn->sp, "If-None-Match: %s\n", u->path->etag);
	if (*Args.Atime > 42)
	{
	    struct tm      *tm;
	    if (u->path->time > *Args.Atime)
		*Args.Atime = u->path->time;
	    tm = gmtime((time_t *) Args.Atime);
	    fprintf(Conn->sp, "If-Modified-Since: %s, %02d %s %d %02d:%02d:%02d GMT\n",
		days[tm->tm_wday], tm->tm_mday, monthtab[tm->tm_mon],
		1900 + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec);
	}
    }
    fputc('\n', Conn->sp);

    if (debug)
    {
	if (stdout->_cnt + Conn->sp->_cnt > stdout->_bsiz)
	    fflush(stdout);
	memcpy(stdout->_ptr, Conn->sp->_base, Conn->sp->_cnt);
	stdout->_cnt += Conn->sp->_cnt;
	stdout->_ptr += Conn->sp->_cnt;
    }
    e = netFlush(Conn);
    if (e == EOF)
    {
	e = errno;
bye:	if (e || !(hP.flags & KEEP_CONN))
	    closeConn(Conn);
	return e;
    }
again:
    respond(R_STATUS, STATUS_WAITING_RESPONSE, 0);
    if ((e = readline(1, Conn, gbuf, READ_SIZE)) < 0)
    {
	LOG(("Fatal error %d reading first response line\n", e));
	if (!primitive)
	{
	    LOG(("Retrying connection...\n"));
	    closeConn(Conn);
	    e = openConn(u);
	    if (e < 0)
		return errno;
	    primitive = 1;
	    goto retry;
	}
	goto bye;
    } else
    {
	LOG(("<<\n%s", gbuf));
	primitive = 0;
	/* Should really check for "HTTP/1." but NCSA HTTP/1.0 server
	 * gets confused by our HTTP/1.1 request, and just sends "HTTP"
	 * without the "/1.0" on its reply.
	 */
	if (strnicmp("HTTP", gbuf, 4))
	{
	    /* Eeek! Server is HTTP/0.9 */
	    primitive = 1;
	} else
	{
	    char           *tmp;
	    tmp = strchr(gbuf, ' ');
	    if (!tmp)
	    {
		LOG(("Malformed response line\n"));
		e = EPLFMT;
		goto bye;
	    }
	    ++tmp;
	    if (*tmp == '2')
	    {
		hP.hterr = 0;
	    } else
	    {
		hP.hterr = atoi(tmp);
	    }
	    if (*tmp == '3')
	    {
		/* If it's not a Not-Modified response, it's a redirect */
		if (hP.hterr != 304)
		    hP.flags |= DO_REDIRECT;
	    }
	}
	if (primitive && kind == HEAD)
	{
	    e = EBADRQ;
	    goto bye;
	}
    }
    if (!primitive)
    {
	e = htHead(u, Conn, &hP);
	if (e < 0)
	{
	    LOG(("Fatal error reading headers, %d\n", e));
	    goto bye;
	}
	if ((hP.flags & DO_REDIRECT) || hP.hterr == 304)
	    goto bye;
	if (hP.hterr == 100)	/* "Continue" */
	{
	    hP.hterr = 0;
	    goto again;
	}
	if (hP.flags & (RETRY_AUTH|RETRY_PR_AUTH))
	{
	    if (!(hP.flags & KEEP_CONN))
	    {
		closeConn(Conn);
		e = openConn(u);
		if (e < 0)
		    return errno;
	    }
	    goto reauth;
	}
    }
    if (kind == HEAD)
    {
	e = 0;
	goto bye;
    }
    fp = fopen(Args.Afile, "w+b");
    if (!fp)
    {
	e = errno;
	LOG(("Couldn't open file %s, err %d\n", Args.Afile, e));
	goto bye;
    }
    if (hP.flags & DO_GZIP)
    {
	int             fds[2], pid, out;
	Fpipe(fds);
	out = Fdup(1);
	Fforce(1, fileno(fp));
	Fforce(0, fds[0]);
	pid = Pexec(100, gzip, "\002-d", NULL);
	if (pid < 0)
	    hP.flags ^= DO_GZIP;
	else
	    Fforce(fileno(fp), fds[1]);
	Fclose(fds[0]);
	Fclose(fds[1]);
	Fforce(1, out);
    }
    if (hP.hterr >= 300)
	fprintf(fp, "<html><head><title>Error %d </title></head>\n", hP.hterr);

    rem = -2;
    if (hP.flags & GOT_SIZE)
    {
	respond(R_STATUS, STATUS_DATALENGTH, u->path->size);
	rem = u->path->size;
    }
    if ((hP.flags & (DO_CHUNKED | GOT_SIZE | KEEP_CONN)) == KEEP_CONN)
	hP.flags |= CHECK_END;

    do
    {
	if (hP.flags & DO_CHUNKED)
	{
	    e = readline(1, Conn, gbuf, READ_SIZE);
	    if (e < 0)
		goto err;
	    LOG(("%s", gbuf));
	    sscanf(gbuf, "%x", &rem);
	    if (rem == 0)
		break;
	}
	while (1)
	{
	    int             read_size = READ_SIZE;
	    if (rem != -2 && read_size > rem)
		read_size = rem;
	    sz = netRead(Conn, gbuf, read_size);
	    LOG(("netRead %ld\n", sz));
	    if (sz == -1 || sz > read_size)
	    {
	err:	e = errno;
		fclose(fp);
		closeConn(Conn);
		if (hP.flags & DO_GZIP)
		    Pwait();
		return e;
	    }
	    if (sz < 1)
		break;
	    read_size = sz;
	    sz = fwrite(gbuf, 1, read_size, fp);
	    if (sz != read_size)
		goto err;
	    LOG(("write %ld\n", sz));
	    bcount += sz;
	    respond(R_STATUS, STATUS_RECEIVING_DATA, bcount);
	    if (rem != -2)
	    {
		rem -= sz;
		if (rem < 1)
		    break;
	    }
	    if (hP.flags & CHECK_END)
	    {
		for (ptr = gbuf + sz - 12; *ptr;)
		    if (*ptr++ == '<')
			break;
		if (!strnicmp(ptr, "/html>", 6))
		    break;
	    }
	}
	if (hP.flags & DO_CHUNKED)
	{
	    e = readline(1, Conn, gbuf, READ_SIZE);
	    if (e < 0)
		goto err;
	    LOG(("%s", gbuf));
	    /* My ISP's proxy server mangles chunked transfers, inserting
	     * extra bytes. If we didn't get a plain EOL here, then we need
	     * to write extra bytes to the output file.
	     */
	    if (e > 1)
		fwrite(gbuf, 1, e-1, fp);
	}
    } while (hP.flags & DO_CHUNKED);
    if (hP.flags & DO_CHUNKED)
    {
	e = htHead(u, Conn, &hP);
	u->path->size = bcount;
    }
    if (!(hP.flags & KEEP_CONN))
	closeConn(Conn);
    fclose(fp);
    if (hP.flags & GOT_DATE)
    {
	struct utimbuf  ut;
	ut.actime = u->path->time;
	/* Workaround TOS limitation - only even datestamps are usable */
	if (ut.actime & 1)
	    ut.actime++;
	ut.modtime = ut.actime;
	utime(Args.Afile, &ut);
    }
    if (hP.flags & DO_GZIP)
	Pwait();
    switch (hP.hterr)
    {
    case 0:
	e = 0;
	break;
    case 400:
    case 405:
    case 406:
    case 408:
	e = EBADRQ;
	break;
    case 401:
    case 402:
    case 403:
    case 407:
	e = EACCES;
	break;
    case 404:
    case 410:
	e = EPATH;
	break;
    case 409:
	e = ELOCKED;
	break;
    case 411:
	e = EBADARG;
	break;
    case 412:
	e = ENOENT;
	break;
    case 413:
	e = ERANGE;
	break;
    case 414:
	e = ENAMETOOLONG;
	break;
    case 415:
	e = EUKMEDIA;
	break;
    case 500:
	e = EINTERNAL;
	break;
    case 501:
	e = EINVAL;
	break;
    case 502:
	e = EBADRQ;
	break;
    case 503:
	e = EDRVNR;
	break;
    case 504:
	e = ETIMEDOUT;
	break;
    case 505:
	e = EINVAL;
	break;
    default:
	e = EBADRQ;
	break;
    }
    LOG(("Return %d\n", e));
    return e;
}
