#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "lib.h"
#include "rstring.h"
#include "mime.h"
#include "lprintf.h"
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#include "tcplib.h"
#include "http.h"
#include "debug.h"
int /*@printflike@*/  imsg(const char * arg_list, ...);


// Need to make this cope with 'https' and other ports... 
int http_post_xml(char *verb, char *url, char *username, char *password, char *xmlbody, char *response)
{
	int rlen;
	char nonce[BFSZ];
	char location[BFSZ];
	return http_post_any(verb,url,username,password,xmlbody,response,BFSZ-1,"text/xml",&rlen,nonce,location,NULL);
}
int http_printf(Chan *c, char * arg_list, ...)
{
	va_list arg_ptr;
	char *format;
	int len, n;
	char output[5000];
	int r;
	char tmp2[BFSZ];
	int tout = 1000*60*10;

	if (c==NULL) return TRUE;
	if (c->sk==INVALID_SOCKET) return FALSE;
	if (c->write_timeout>0) tout = c->write_timeout;

	va_start(arg_ptr, arg_list);
	format = arg_list;
	output[0] = 0;
	len = vsnprintf(output,4000, format, arg_ptr); output[4000-1] = 0;
	n = lib_strlen(output);
	if (n==0) return TRUE; /* We dont want to send an empty string */
	c->sent += n;
	r = tcp_write_timed(c,output,n,tout);
	if (r<(int) lib_strlen(output)) {
		imsg("http_printf: failed only %d sent %s\n",r,tcp_error(c,tmp2));
		tcp_close_nice(c);
		return FALSE;
	}
	//printf("http_printf: %s",output);
	if (r<=0) return FALSE;
	return TRUE;
}

int http_post_any(char *verb, char *url, char *username, char *password, char *xmlbody, char *response, int rbfsz, char *content, int *rlen, char *nonce, char *location, char *retry)
{
	char host[BFSZ];
	char rest[BFSZ];
	char *s;
	int count;
	int downloadsize = 0;
	char userpass[BFSZ];
	int code;
	char basic[BFSZ];
	int got=0;
	char bf[BFSZ];
	int ishead=TRUE;
	int isssl=FALSE;
	int worked=FALSE;
	int port=80;
	char  bugmsg[BFSZ];
	char *bug="";
	int length = lib_strlen(xmlbody);
	Chan *c=NULL;
	*rlen = 0;
	if (strstrnc(url,"https:")!=NULL) isssl = TRUE;
	if (isssl) port = 443;
	lib_url_split_host(url,host,rest);
	strcpy(location,"");
	tcp_new(&c);
	if (retry) strcpy(retry,"");
	while(TRUE) {
		imsg("http: Open (%s)\n",host);
//		printf("http: Open (%s) port %d\n",host,port);
		if (!tcp_open(c,host,port,NULL)) {
			lprintf(bugmsg,BFSZ-1,"tcp_open %s failed %s ",host,tcp_error(c,bf));
			bug = bugmsg;
			break;
		}
		if(isssl || port==443){
			imsg("http: ssl init: %s\n",url);
			if (!tcp_ssl_init(c,FALSE,NULL,response)) {
				imsg("http: ssl init failed %s\n",response);
				bug = "ssl_init failed";
				break;
			}
		}

//		if (!http_printf(c,"%s %s HTTP/1.1\r\n",verb,rest)) {bug="send http failed"; break;}
		if (!http_printf(c,"%s %s HTTP/1.1\r\n",verb,url)) {bug="send http failed"; break;}
//printf("%s %s HTTP/1.1\r\n",verb,rest);
		if (!http_printf(c,"Host: %s\r\n",host))  {bug="send host failed"; break;}
//printf("Host: %s\r\n",host);
		lprintf(userpass,BFSZ-1,"%s:%s",username,password);
		mime_encode_base64_bad(userpass, lib_strlen(userpass)+1,  basic,BFSZ-1);
		if (strlen(username)>0) {
			if (!http_printf(c,"Authorization: Basic %s\r\n",basic))  {bug="send auth failed"; break;}
		}
//		if (!http_printf(c,"Cache-Control: no-cache\r\n"))  {bug="send nocache failed"; break;}
		if (!http_printf(c,"Accept-Encoding: identity\r\n"))  {bug="send nocache failed"; break;}

		if (!http_printf(c,"Connection: Keep-Alive\r\n"))  {bug="send keepalive failed"; break;}
		if (!http_printf(c,"Content-Length: %d\r\n",length))  {bug="send content length failed"; break;}
		if (strlen(content)>0) if (!http_printf(c,"Content-Type: %s\r\n",content))  {bug="send content failed"; break;}
		if (!http_printf(c,"\r\n"))  {bug="send eol failed"; break;}
		
		// And now send the xml body
		//printf("%s",xmlbody);
		if (xmlbody) if (strlen(xmlbody)>0) if (tcp_write_timed(c,xmlbody,length,32000)!=length)  {bug="send data failed"; break;}
//		if (!http_printf(c,"\r\n"))  {bug="send eol failed"; break;} // trailing blank line maybe?> 
		worked = TRUE;
		break;
	}
	if (!worked) {
		imsg("http: xml not sent %s\n",bug);
		//printf("It failed %s %s\n",bug,tcp_error(c,response));
		ncpy(response,bug,BFSZ-1);
		tcp_free(&c);
		return FALSE;
	}

	//printf("Reading response headers\n");
	for (;ishead && tcp_readline(c,bf,BFSZ-1);) {
		//printf("RESPONSE %s\n",bf);
		if (strncmp(bf,"HTTP/",5)==0) {
			ncpy(response,bf,rbfsz-1);
			s = strchr(bf,' '); 
			if (s!=NULL) {
				code = atoi(s+1);
				ncpy(response,s+1,rbfsz-1);
				//printf("Response code %s\n",response);
			}
			if (code>300) {
//				goto failed;
			}
		}
		if (strncmp(bf, "Content-Length: ",16)==0)  downloadsize = atoi( bf+16 );
		if (strncmpnc(bf,"Retry-After: ",13)==0)  {
			if (retry) ncpy(retry,bf+13,rbfsz);
		}
		if (strncmp(bf, "Replay-Nonce: ",14)==0)  {
			ncpy(nonce,bf+14,rbfsz);
//			goto worked;
		}
		if (strncmp(bf, "Location: ",10)==0)  {
			ncpy(location,bf+10,rbfsz);
		}
		if (lib_strlen(bf)==0) ishead=0;
	}
	ncpy(response,"",rbfsz);
	//printf("Reading response body\n");
	if (downloadsize>0) { 
	 while(count = tcp_read_timed(c,bf,BFSZ-1, 180000)) {
		imsg("xml: tcp_read_timed2 got=%d %d\n",count,downloadsize);
		if (count<0) {
			lprintf(response,BFSZ-1,"200 Connection closed %d/%d",got,downloadsize);
			goto failed;
		}
		if (got+count>=rbfsz) {
			lprintf(response,BFSZ-1,"200 response too long %d %d %d",got,downloadsize,rbfsz);
			goto failed;
		}
		memcpy(response+got,bf,count);
		got += count;
		if (got>=downloadsize) break;
	 } 
	} else {
		got = tcp_read_timed(c,response,BFSZ-1, 20000);
		imsg("xml: tcp_read_timed got=%d\n",got);
	}
	response[got] = 0;
	*rlen = got;
	tcp_close_nice(c);
	tcp_free(&c);
	imsg("xml: Sent successfully, yay got back %s\n",response);
	return TRUE;
failed:
	tcp_free(&c);
	return FALSE;
}
#define OUTMAX 300000
int tcp_outadd(char *bf, int *bflen, char *add, int max)
{
	int len = strlen(add);
	if ((len+ *bflen) >= max-2) return FALSE;
	strcpy(bf+*bflen,add);
	(*bflen) += len;
	return TRUE;	
}

int http_upload(char *xurl, char *fname, Pair *params, char *reason, char *response, int rbfsz)
{
	Pair *p;
	char host[BFSZ];
	char url[BFSZ];
	char rest[BFSZ];
	char bf[BFSZ];
	int code;
	int downloadsize=0;
	int chunked=FALSE;
	int count=0;
	int got=0;
	FILE *f;
	Chan *c=NULL;
	int tout=0;
	char *out;
	int outlen=0;
	int sz=0;
	char *s;
	int ishead=TRUE;
	char *boundary="12345098273476978912043987098423740928374";
	char tmp[BFSZ];
	int do_ssl = FALSE;
	int port=80;
	char *u=url;
	strcpy(reason,"upload1"); strcpy(response,"upload");
	ncpy(url,xurl,BFSZ-1);
	tcp_new(&c);

	strcpy(rest,"");
	if (strncmp(u,"http:",5)==0) u += 5;
	if (strncmp(u,"https:",6)==0) {u += 6; do_ssl=TRUE; port=443;}
	if (strncmp(u,"//",2)==0) u += 2;
	ncpy(host,u,BFSZ-1);
	s = strstr(host,"/"); 
	if (s!=NULL) ncpy(rest,s,BFSZ-1);
	if (s!=NULL) *s = 0;

	ncpy(response,"",rbfsz-1);
	strcpy(reason,"");
	imsg("http: upload to %s %s\n",host,rest);
	if (!tcp_open(c,host,port,NULL)) {
		sprintf(reason,"tcp: open (%.80s) %d failed (%.80s)\n",host,port,tcp_error(c,tmp));
		tcp_free(&c);
		return FALSE;;
	}

	if(do_ssl){
		if (!tcp_ssl_init(c,FALSE,NULL,reason)) {
			tcp_free(&c);
			sprintf(reason,"tcp: ssl init (%.80s) %d failed (%.80s)\n",host,port,tcp_error(c,tmp));
			return FALSE;	
		}
	}
	out = calloc(1,OUTMAX); outlen = 0;  // MEMORY LEAK...

	for (p=params;p!=NULL;p=p->next) {
		lprintf(bf,BFSZ-1,"--%s\r\n",boundary); tcp_outadd(out,&outlen,bf,OUTMAX);
		lprintf(bf,BFSZ-1,"Content-Disposition: form-data; name=\"%s\"\r\n",p->var); tcp_outadd(out,&outlen,bf,OUTMAX);
		lprintf(bf,BFSZ-1,"\r\n"); tcp_outadd(out,&outlen,bf,OUTMAX);
		lprintf(bf,BFSZ-1,"%s\r\n",p->val); tcp_outadd(out,&outlen,bf,OUTMAX);
	}
	// and attach the actual file.
	f = fopen(fname,"rb");
	if (f!=NULL) {
		int nread=0;
		lprintf(bf,BFSZ-1,"--%s\r\n",boundary); tcp_outadd(out,&outlen,bf,OUTMAX);
		lprintf(bf,BFSZ-1,"Content-Disposition: form-data; name=\"fname\" filename=\"email.msg\"\r\n"); tcp_outadd(out,&outlen,bf,OUTMAX);
		lprintf(bf,BFSZ-1,"\r\n"); tcp_outadd(out,&outlen,bf,OUTMAX);
		for (;!feof(f);) {
			if (fgets(bf,BFSZ-10,f)==NULL) break;
			s = strchr(bf,'\r'); if (s!=NULL) *s = 0;
			s = strchr(bf,'\n'); if (s!=NULL) *s = 0;
			ncat(bf,"\r\n",BFSZ-1);
			nread += strlen(bf);
			if (!tcp_outadd(out,&outlen,bf,OUTMAX)) break;
		}
		fclose(f);
		imsg("http: Read %d bytes of file\n",nread);
	} else {
		lprintf(reason,BFSZ-1,"Unable to open %s %s",fname,strerror(errno));
		imsg("http: Read failed %s\n",reason);
		goto failed;
	}
	lprintf(bf,BFSZ-1,"\r\n--%s--\r\n",boundary); tcp_outadd(out,&outlen,bf,OUTMAX);

	imsg("http: POST %s HTTP/1.1\r\n",rest);
	tcp_printf(c,"POST %s HTTP/1.1\r\n",rest);
	tcp_printf(c,"HOST: %s\r\n",host);
	tcp_printf(c,"Content-Type: multipart/form-data; boundary=%s\r\n",boundary);
	tcp_printf(c,"Content-Length: %d\r\n",outlen);
	imsg("http: Content-Length: %d\r\n",outlen);
	tcp_printf(c,"\r\n");
	tcp_write(c,out,outlen);
/*	
	{
		FILE *fout;
		fout = fopen("sent.tmp","wb");
		if (fout!=NULL) {
			fprintf(fout,"POST %s HTTP/1.1\r\n",rest);
			fprintf(fout,"HOST: %s\r\n",host);
			fprintf(fout,"Content-Type: multipart/form-data; boundary=%s\r\n",boundary);
			fprintf(fout,"Content-Length: %d\r\n",outlen);
			fprintf(fout,"\r\n");
			fwrite(out,1,outlen,fout);
			fclose(fout);
		}
	}
*/
	// Now we need to read the response...

	free(out); out = NULL;  // FIX FOR MEMORY LEAK.
	for (;ishead && tcp_readline(c,bf,BFSZ-1);) {
		imsg("http: postread %s\n",bf);
		if (strncmp(bf,"HTTP/",5)==0) {
			ncpy(response,bf,rbfsz-1);
			s = strchr(bf,' '); 
			if (s!=NULL) {
				code = atoi(s+1);
				ncpy(response,s+1,rbfsz-1);
				//printf("Response code %s\n",response);
			}
		}
		if (strncmp(bf, "Content-Length: ",16)==0)  downloadsize = atoi( bf+16 );
		if (strncmp(bf, "Transfer-Encoding: chunked",26)==0) chunked = TRUE;
		if (lib_strlen(bf)==0) ishead=0;
	}

	if (downloadsize>0) { 
	got=0;
	 while(count = tcp_read_timed(c,bf,BFSZ-1, 180000)) {
		if (count<0) {
			lprintf(response,BFSZ-1,"200 Connection closed %d/%d",got,downloadsize);
			goto failed;
		}
		if (got+count>=rbfsz) {
			lprintf(response,BFSZ-1,"200 response too long %d %d %d",got,downloadsize,rbfsz);
			goto failed;
		}
		memcpy(response+got,bf,count);
		got += count;
		if (got>=downloadsize) break;
	 } 
	}
	if (chunked) { // added by chrisp, alternate http protocol used by some http servers.
		int chunk;

		for (;TRUE;) {
			if (!tcp_readline(c,bf,BFSZ-1)) break;
			chunk = hex_to_ulong(bf);
			if (chunk==0) break; 
			for (;chunk>0;) {
				int w = chunk; 
				if (w>BFSZ-1) w = BFSZ - 1;
				count = tcp_read_timed(c,bf,w, tout*1000);
				if (count<=0) goto failed;
				//if (fnew!=NULL) sz+=(int)fwrite(bf,1,count,fnew);
				memcpy(response+got,bf,count);
				got += count;

				chunk -= count; 
			}
			count = tcp_read_timed(c,bf,2, tout*1000); // read trailing crlf 
		}
	}


	response[got] = 0;

failed:
	imsg("http: Finished\n");
	tcp_free(&c);
	return TRUE;

}
