#include "a.h"

static char*
haveheader(char *buf, int n)
{
	int i;

	for(i=0; i<n; i++){
		if(buf[i] == '\n'){
			if(i+2 < n && buf[i+1] == '\r' && buf[i+2] == '\n')
				return buf+i+3;
			if(i+1 < n && buf[i+1] == '\n')
				return buf+i+2;
		}
	}
	return 0;
}

static int
parseheader(char *buf, int n, HTTPHeader *hdr)
{
	int nline;
	char *data, *ebuf, *p, *q, *next;

	memset(hdr, 0, sizeof *hdr);
	ebuf = buf+n;
	data = haveheader(buf, n);
	if(data == nil)
		return -1;

	data[-1] = 0;
	if(data[-2] == '\r')
		data[-2] = 0;
	if(chattyhttp > 1){
		fprint(2, "--HTTP Response Header:\n");
		fprint(2, "%s\n", buf);
		fprint(2, "--\n");
	}
	nline = 0;
	for(p=buf; *p; p=next, nline++){
		q = strchr(p, '\n');
		if(q){
			next = q+1;
			*q = 0;
			if(q > p && q[-1] == '\r')
				q[-1] = 0;
		}else
			next = p+strlen(p);
		if(nline == 0){
			if(memcmp(p, "HTTP/", 5) != 0){
				werrstr("invalid HTTP version: %.10s", p);
				return -1;
			}
			q = strchr(p, ' ');
			if(q == nil){
				werrstr("invalid HTTP version");
				return -1;
			}
			*q++ = 0;
			strncpy(hdr->proto, p, sizeof hdr->proto);
			hdr->proto[sizeof hdr->proto-1] = 0;
			while(*q == ' ')
				q++;
			if(*q < '0' || '9' < *q){
				werrstr("invalid HTTP response code");
				return -1;
			}
			p = q;
			q = strchr(p, ' ');
			if(q == nil)
				q = p+strlen(p);
			else
				*q++ = 0;
			hdr->code = strtol(p, &p, 10);
			if(*p != 0)
				return -1;
			while(*q == ' ')
				q++;
			strncpy(hdr->codedesc, q, sizeof hdr->codedesc);
			hdr->codedesc[sizeof hdr->codedesc-1] = 0;
			continue;
		}
		q = strchr(p, ':');
		if(q == nil)
			continue;
		*q++ = 0;
		while(*q != 0 && (*q == ' ' || *q == '\t'))
			q++;
		if(cistrcmp(p, "Content-Type") == 0){
			strncpy(hdr->contenttype, q, sizeof hdr->contenttype);
			hdr->contenttype[sizeof hdr->contenttype-1] = 0;
			continue;
		}
		if(cistrcmp(p, "Content-Length") == 0 && '0' <= *q && *q <= '9'){
			hdr->contentlength = strtoll(q, 0, 10);
			continue;
		}
	}
	if(nline < 1){
		werrstr("no header");
		return -1;
	}

	memmove(buf, data, ebuf - data);
	return ebuf - data;
}

static char*
genhttp(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int wfd, int rfd, vlong rtotal)
{
	int n, m, total, want;
	char buf[8192], *data;
	Pfd *fd;

	if(chattyhttp > 1){
		fprint(2, "--HTTP Request:\n");
		fprint(2, "%s", req);
		fprint(2, "--\n");
	}
	fd = proto->connect(host);
	if(fd == nil){
		if(chattyhttp > 0)
			fprint(2, "connect %s: %r\n", host);
		return nil;
	}

	n = strlen(req);
	if(proto->write(fd, req, n) != n){
		if(chattyhttp > 0)
			fprint(2, "write %s: %r\n", host);
		proto->close(fd);
		return nil;
	}

	if(rfd >= 0){
		while(rtotal > 0){
			m = sizeof buf;
			if(m > rtotal)
				m = rtotal;
			if((n = read(rfd, buf, m)) <= 0){
				fprint(2, "read: missing data\n");
				proto->close(fd);
				return nil;
			}
			if(proto->write(fd, buf, n) != n){
				fprint(2, "write data: %r\n");
				proto->close(fd);
				return nil;
			}
			rtotal -= n;
		}
	}

	total = 0;
	while(!haveheader(buf, total)){
		n = proto->read(fd, buf+total, sizeof buf-total);
		if(n <= 0){
			if(chattyhttp > 0)
				fprint(2, "read missing header\n");
			proto->close(fd);
			return nil;
		}
		total += n;
	}

	n = parseheader(buf, total, hdr);
	if(n < 0){
		fprint(2, "failed response parse: %r\n");
		proto->close(fd);
		return nil;
	}
	if(hdr->contentlength >= MaxResponse){
		werrstr("response too long");
		proto->close(fd);
		return nil;
	}
	if(hdr->contentlength >= 0 && n > hdr->contentlength)
		n = hdr->contentlength;
	want = sizeof buf;
	data = nil;
	total = 0;
	goto didread;

	while(want > 0 && (n = proto->read(fd, buf, want)) > 0){
	didread:
		if(wfd >= 0){
			if(writen(wfd, buf, n) < 0){
				proto->close(fd);
				werrstr("write error");
				return nil;
			}
		}else{
			data = erealloc(data, total+n);
			memmove(data+total, buf, n);
		}
		total += n;
		if(total > MaxResponse){
			proto->close(fd);
			werrstr("response too long");
			return nil;
		}
		if(hdr->contentlength >= 0 && total + want > hdr->contentlength)
			want = hdr->contentlength - total;
	}
	proto->close(fd);

	if(hdr->contentlength >= 0 && total != hdr->contentlength){
		werrstr("got wrong content size %d %d", total, hdr->contentlength);
		return nil;
	}
	hdr->contentlength = total;
	if(wfd >= 0)
		return (void*)1;
	else{
		data = erealloc(data, total+1);
		data[total] = 0;
	}
	return data;
}

char*
httpreq(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int rfd, vlong rlength)
{
	return genhttp(proto, host, req, hdr, -1, rfd, rlength);
}

int
httptofile(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int fd)
{
	if(fd < 0){
		werrstr("bad fd");
		return -1;
	}
	if(genhttp(proto, host, req, hdr, fd, -1, 0) == nil)
		return -1;
	return 0;
}