/*******************************************************************************
**
** http.c
**
** This file is part of the ABYSS Web server project.
**
** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
*******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "abyss.h"

/*********************************************************************
** Request Parser
*********************************************************************/

void NextToken(char **p)
{
	while (1)
		switch (**p)
		{
		case '\t':
		case ' ':
			(*p)++;
			break;
		default:
			return;
		};
}

char *GetToken(char **p)
{
	char *p0=*p;

	while (1)
		switch (**p)
		{
		case '\t':
		case ' ':
		case CR:
		case LF:
		case '\0':
			if (p0==*p)
				return NULL;

			if (**p)
			{
				**p='\0';
				(*p)++;
			};
			return p0;

		default:
			(*p)++;
		};
}

char *GetMultiLine(char **p)
{
	char *p0=*p;

	while (1)
		switch (**p)
		{
		case '\0':
			return p0;

		case '\t':
		case CR:
			**p=' ';
			(*p)++;
			break;

		case ' ':
			(*p)++;
			break;

		case LF:
			**p=' ';
			(*p)++;
			if ((**p==' ')||(**p=='\t'))
				break;
			else
			{
				char *t=*p-1;

				while (*t==' ')
					*(t--)='\0';

				return p0;
			};

		default:
			(*p)++;
		};
}

bool Eol(char *p)
{
	NextToken(&p);

	if (*p==LF)
		return TRUE;

	if (*p==CR)
		if (*(p+1)==LF)
			return TRUE;

	return FALSE;
}

void NextLine(char **p)
{
	while (**p)
		if (**p!=LF)
			(*p)++;
		else
		{
			(*p)++;
			break;
		};
}




/*********************************************************************
** Request
*********************************************************************/

void RequestInit(TSession *r,TConn *c)
{
	time_t t;

	time(&t);
	r->date=*gmtime(&t);

	r->keepalive=r->cankeepalive=FALSE;
	r->query=NULL;
	r->host=NULL;
	r->from=NULL;
	r->useragent=NULL;
	r->referer=NULL;
	r->user=NULL;
	r->port=80;
	r->versionmajor=0;
	r->versionminor=9;
	r->server=c->server;
	r->conn=c;

	r->done=FALSE;

	r->chunkedwrite=r->chunkedwritemode=FALSE;

	r->requestline=NULL;

	ListInit(&r->cookies);
	ListInit(&r->ranges);
	TableInit(&r->request_headers);
	TableInit(&r->response_headers);

	r->status=0;

	StringAlloc(&(r->header));
}

void RequestFree(TSession *r)
{
	if (r->requestline)
		free(r->requestline);

	if (r->user)
		free(r->user);

	ListFree(&r->cookies);
	ListFree(&r->ranges);
	TableFree(&r->request_headers);
	TableFree(&r->response_headers);
	StringFree(&(r->header));
}

bool RequestRead(TSession *r)
{
	char *n,*t,*p;
	uint32 vmin,vmaj;
	bool ret,singleline=FALSE;

	/* Ignore CRLFs in the beginning of the request (RFC2068-P30) */
	do
	{
		if (!ConnReadLine(r->conn,&p,r->server->timeout))
		{
			/* Request Timeout */
			ResponseStatus(r,408);
			return FALSE;
		};
	} while (*p=='\0');

	/* Request Line */

	/* Jump over spaces */
	NextToken(&p);

	r->requestline=strdup(p);
	
	if (!(t=GetToken(&p)))
	{
		/* Bad request */
		ResponseStatus(r,400);
		return FALSE;
	};

	if (strcmp(t,"GET")==0)
		r->method=m_get;
	else if (strcmp(p,"PUT")==0)
		r->method=m_put;
	else if (strcmp(t,"OPTIONS")==0)
		r->method=m_options;
	else if (strcmp(p,"DELETE")==0)
		r->method=m_delete;
	else if (strcmp(t,"POST")==0)
		r->method=m_post;
	else if (strcmp(p,"TRACE")==0)
		r->method=m_trace;
	else if (strcmp(t,"HEAD")==0)
		r->method=m_head;
	else
		r->method=m_unknown;

	/* URI and Query Decoding */
	NextToken(&p);

	if (!(t=GetToken(&p)))
		return FALSE;

	r->uri=t;

	RequestUnescapeURI(r);
	
	if (t=strchr(t,'?'))
	{
		*t='\0';
		r->query=t+1;
	};

	NextToken(&p);

	/* HTTP Version Decoding */
	
	/* If no HTTP version then this is a single line request */
	if (t=GetToken(&p))
	{
		if (sscanf(t,"HTTP/%d.%d",&vmaj,&vmin)!=2)
		{
			/* Bad request */
			ResponseStatus(r,400);
			return FALSE;
		};

		r->versionmajor=vmaj;
		r->versionminor=vmin;
	}
	else
		/* If it is a single line request then we can leave */
		return TRUE;

	/* Headers decoding */
	ret= TRUE;

	for (;;)
	{
		if (!ConnReadLine(r->conn,&p,r->server->timeout))
		{
			/* Request Timeout */
			ResponseStatus(r,408);
			return FALSE;
		};

		/* We have reached the empty line so all the request was read */
		if (!*p)
			return TRUE;

		NextToken(&p);
		
		if (!(n=GetToken(&p)))
		{
			/* Bad Request */
			ResponseStatus(r,400);
			return FALSE;
		};

		/* Valid Field name ? */
		if (n[strlen(n)-1]!=':')
		{
			/* Bad Request */
			ResponseStatus(r,400);
			return FALSE;
		};

		n[strlen(n)-1]='\0';

		NextToken(&p);

		t=n;
		while (*t)
		{
			*t=tolower(*t);
			t++;
		};

		t=p;

		TableAdd(&r->request_headers,n,t);

		if (strcmp(n,"connection")==0)
		{
			/* must handle the jigsaw TE,keepalive */
			if (strcasecmp(t,"keep-alive")==0)
				r->keepalive=TRUE;
			else
				r->keepalive=FALSE;
		}
		else if (strcmp(n,"host")==0)
			r->host=t;
		else if (strcmp(n,"from")==0)
			r->from=t;
		else if (strcmp(n,"user-agent")==0)
			r->useragent=t;
		else if (strcmp(n,"referer")==0)
			r->referer=t;
		else if (strcmp(n,"range")==0)
		{
			if (strncmp(t,"bytes=",6)==0)
				if (!ListAddFromString(&(r->ranges),t+6))
				{
					/* Bad Request */
					ResponseStatus(r,400);
					return FALSE;
				};
		}
		else if (strcmp(n,"cookies")==0)
		{
			if (!ListAddFromString(&(r->cookies),t))
			{
				/* Bad Request */
				ResponseStatus(r,400);
				return FALSE;
			};
		};
	};
}

char *RequestHeaderValue(TSession *r,char *name)
{
	return (TableFind(&r->request_headers,name));
}

bool RequestUnescapeURI(TSession *r)
{
	char *x,*y,c,d;

	x=y=r->uri;

	while (1)
		switch (*x)
		{
		case '\0':
			*y='\0';
			return TRUE;

		case '%':
			x++;
			c=tolower(*x++);
			if ((c>='0') && (c<='9'))
				c-='0';
			else if ((c>='a') && (c<='f'))
				c-='a'-10;
			else
				return FALSE;

			d=tolower(*x++);
			if ((d>='0') && (d<='9'))
				d-='0';
			else if ((d>='a') && (d<='f'))
				d-='a'-10;
			else
				return FALSE;

			*y++=((c << 4) | d);
			break;

		default:
			*y++=*x++;
			break;
		};
};

bool RequestValidURI(TSession *r)
{
	char *p;

	if (!r->uri)
		return FALSE;

	if (*(r->uri)!='/')
		if (strncmp(r->uri,"http://",7)!=0)
			return FALSE;
		else
		{
			r->uri+=7;

			r->host=r->uri;

			p=strchr(r->uri,'/');

			if (!p)
			{
				r->uri="*";
				return TRUE;
			};

			r->uri=p;

			p=r->host;

			while (*p!='/')
			{
				*(p-1)=*p;
				p++;
			};

			p--;
			*p='\0';

			r->host--;
		};

	/* Host and Port Decoding */
	if (r->host)
		if (p=strchr(r->host,':'))
		{
			uint32 port=0;

			*p='\0';
			p++;
			while (isdigit(*p) && (port<65535))
			{
				port=port*10+(*p)-'0';
				p++;
			};
			
			r->port=port;

			if (*p || port==0)
				return FALSE;
		};

	if (strcmp(r->uri,"*")==0)
		return (r->method!=m_options);

	if (strchr(r->uri,'*'))
		return FALSE;

	return TRUE;
}


bool RequestValidURIPath(TSession *r)
{
	uint32 i=0;
	char *p=r->uri;

	if (*p=='/')
	{
		i=1;
		while (*p)
			if (*(p++)=='/')
			{
				if (*p=='/')
					break;
				else if ((strncmp(p,"./",2)==0) || (strcmp(p,".")==0))
					p++;
				else if ((strncmp(p,"../",2)==0) || (strcmp(p,"..")==0))
				{
					p+=2;
					i--;
					if (i==0)
						break;
				}
				/* Prevent accessing hidden files (starting with .) */
				else if (*p=='.')
					return FALSE;
				else
					if (*p)
						i++;
			};
	};

	return ((*p==0) && (i>0));
}

bool RequestAuth(TSession *r,char *credential,char *user,char *pass)
{
	char *p,*x;
	char z[80],t[80];

	if (p=RequestHeaderValue(r,"authorization"))
	{
		NextToken(&p);
		if (x=GetToken(&p))
			if (strcasecmp(x,"basic")==0)
			{
				NextToken(&p);
				sprintf(z,"%s:%s",user,pass);
				Base64Encode(z,t);

				if (strcmp(p,t)==0)
				{
					r->user=strdup(user);
					return TRUE;
				};
			};
	};

	sprintf(z,"Basic realm=\"%s\"",credential);
	ResponseAddField(r,"WWW-Authenticate",z);
	ResponseStatus(r,401);
	return FALSE;
}



/*********************************************************************
** Range
*********************************************************************/

bool RangeDecode(char *str,uint64 filesize,uint64 *start,uint64 *end)
{
	char *ss;

	*start=0;
	*end=filesize-1;

	if (*str=='-')
	{
		*start=filesize-strtol(str+1,&ss,10);
		return ((ss!=str) && (!*ss));
	};

	*start=strtol(str,&ss,10);

	if ((ss==str) || (*ss!='-'))
		return FALSE;

	str=ss+1;

	if (!*str)
		return TRUE;

	*end=strtol(str,&ss,10);

	if ((ss==str) || (*ss) || (*end<*start))
		return FALSE;

	return TRUE;
}

/*********************************************************************
** HTTP
*********************************************************************/

char *HTTPReasonByStatus(uint16 code)
{
	static struct _HTTPReasons {
		uint16 status;
		char *reason;
	} *r,reasons[] = 
	{
		{ 100,"Continue" }, 
		{ 101,"Switching Protocols" }, 
		{ 200,"OK" }, 
		{ 201,"Created" }, 
		{ 202,"Accepted" }, 
		{ 203,"Non-Authoritative Information" }, 
		{ 204,"No Content" }, 
		{ 205,"Reset Content" }, 
		{ 206,"Partial Content" }, 
		{ 300,"Multiple Choices" }, 
		{ 301,"Moved Permanently" }, 
		{ 302,"Moved Temporarily" }, 
		{ 303,"See Other" }, 
		{ 304,"Not Modified" }, 
		{ 305,"Use Proxy" }, 
		{ 400,"Bad Request" }, 
		{ 401,"Unauthorized" }, 
		{ 402,"Payment Required" }, 
		{ 403,"Forbidden" }, 
		{ 404,"Not Found" }, 
		{ 405,"Method Not Allowed" }, 
		{ 406,"Not Acceptable" }, 
		{ 407,"Proxy Authentication Required" }, 
		{ 408,"Request Timeout" }, 
		{ 409,"Conflict" }, 
		{ 410,"Gone" }, 
		{ 411,"Length Required" }, 
		{ 412,"Precondition Failed" }, 
		{ 413,"Request Entity Too Large" }, 
		{ 414,"Request-URI Too Long" }, 
		{ 415,"Unsupported Media Type" }, 
		{ 500,"Internal Server Error" }, 
		{ 501,"Not Implemented" }, 
		{ 502,"Bad Gateway" }, 
		{ 503,"Service Unavailable" }, 
		{ 504,"Gateway Timeout" }, 
		{ 505,"HTTP Version Not Supported" },
		{ 000, NULL }
	};

	r=reasons;

	while (r->status<=code)
		if (r->status==code)
			return (r->reason);
		else
			r++;

	return "No Reason";
}

int32 HTTPRead(TSession *s,char *buffer,uint32 len)
{
	return 0;
}

bool HTTPWrite(TSession *s,char *buffer,uint32 len)
{
	if (s->chunkedwrite && s->chunkedwritemode)
	{
		char t[16];

		if (ConnWrite(s->conn,t,sprintf(t,"%x"CRLF,len)))
			if (ConnWrite(s->conn,buffer,len))
				return ConnWrite(s->conn,CRLF,2);

		return FALSE;
	}
	
	return ConnWrite(s->conn,buffer,len);
}

bool HTTPWriteEnd(TSession *s)
{
	if (!s->chunkedwritemode)
		return TRUE;

	if (s->chunkedwrite)
	{
		/* May be one day trailer dumping will be added */
		s->chunkedwritemode=FALSE;
		return ConnWrite(s->conn,"0"CRLF CRLF,5);
	}

	s->keepalive=FALSE;
	return TRUE;
}

/*********************************************************************
** Response
*********************************************************************/

void ResponseError(TSession *r)
{
	char *reason=HTTPReasonByStatus(r->status);
	char z[500];

	ResponseAddField(r,"Content-type","text/html");

	ResponseWrite(r);
	
	sprintf(z,"<HTML><HEAD><TITLE>Error %d</TITLE></HEAD>"
		"<BODY><H1>Error %d</H1><P>%s</P>" SERVER_HTML_INFO 
		"</BODY></HTML>",
		r->status,r->status,reason);

	ConnWrite(r->conn,z,strlen(z));	
}

bool ResponseChunked(TSession *r)
{
	/* This is only a hope, things will be real only after a call of ResponseWrite */
	r->chunkedwrite=(r->versionmajor>=1) && (r->versionminor>=1);
	r->chunkedwritemode=TRUE;

	return TRUE;
}

void ResponseStatus(TSession *r,uint16 code)
{
	r->status=code;
}

void ResponseStatusErrno(TSession *r)
{
	uint16 code;

	switch (errno)
	{
	case EACCES:
		code=403;
		break;
	case ENOENT:
		code=404;
		break;
	default:
		code=500;
	};

	ResponseStatus(r,code);
}

bool ResponseAddField(TSession *r,char *name,char *value)
{
	return TableAdd(&r->response_headers,name,value);
}

void ResponseWrite(TSession *r)
{
	bool connclose=TRUE;
	char z[64];
	TTableItem *ti;
	uint16 i;
	char *reason;

	/* if status == 0 then this is an error */
	if (r->status==0)
		r->status=500;

	/* the request was treated */
	r->done=TRUE;

	reason=HTTPReasonByStatus(r->status);
	sprintf(z,"HTTP/1.1 %d ",r->status);
	ConnWrite(r->conn,z,strlen(z));
	ConnWrite(r->conn,reason,strlen(reason));
	ConnWrite(r->conn,CRLF,2);

	/* generation of the connection field */
	if ((r->status<400) && (r->keepalive) && (r->cankeepalive))
		connclose=FALSE;

	ResponseAddField(r,"Connection",
		(connclose?"close":"Keep-Alive"));

	if (!connclose)
	{
		sprintf(z,"timeout=%lu, max=%lu",r->server->keepalivetimeout
				,r->server->keepalivemaxconn);

		ResponseAddField(r,"Keep-Alive",z);

		if (r->chunkedwrite && r->chunkedwritemode)
		{
			if (!ResponseAddField(r,"Transfer-Encoding","chunked"))
			{
					r->chunkedwrite=FALSE;
					r->keepalive=FALSE;
			};
		};
	}
	else
	{
		r->keepalive=FALSE;
		r->chunkedwrite=FALSE;
	};
	
	/* generation of the date field */
	if ((r->status>=200) && DateToString(&r->date,z))
		ResponseAddField(r,"Date",z);

	/* Generation of the server field */
	if (r->server->advertise)
		ResponseAddField(r,"Server",SERVER_HVERSION);

	/* send all the fields */
	for (i=0;i<r->response_headers.size;i++)
	{
		ti=&r->response_headers.item[i];
		ConnWrite(r->conn,ti->name,strlen(ti->name));
		ConnWrite(r->conn,": ",2);
		ConnWrite(r->conn,ti->value,strlen(ti->value));
		ConnWrite(r->conn,CRLF,2);
	};

	ConnWrite(r->conn,CRLF,2);	
}

bool ResponseContentType(TSession *r,char *type)
{
	return ResponseAddField(r,"Content-type",type);
}

bool ResponseContentLength(TSession *r,uint64 len)
{
	char z[32];

	sprintf(z,"%lu",len);
	return ResponseAddField(r,"Content-length",z);
}


/*********************************************************************
** MIMEType
*********************************************************************/

TList _MIMETypes,_MIMEExt;
TPool _MIMEPool;

void MIMETypeInit()
{
	ListInit(&_MIMETypes);
	ListInit(&_MIMEExt);
	PoolCreate(&_MIMEPool,1024);
}

bool MIMETypeAdd(char *type,char *ext)
{
	uint16 index;

	if (ListFindString(&_MIMETypes,type,&index))
		type=_MIMETypes.item[index];
	else
		if (!(type=PoolStrdup(&_MIMEPool,type)))
			return FALSE;
	
	if (ListFindString(&_MIMEExt,ext,&index))
		_MIMETypes.item[index]=type;
	else
		if (ext=PoolStrdup(&_MIMEPool,ext))
			return (ListAdd(&_MIMETypes,type) && ListAdd(&_MIMEExt,ext));
		else
			return FALSE;
			
	return TRUE;
}

char *MIMETypeFromExt(char *ext)
{
	uint16 extindex;

	if (!ListFindString(&_MIMEExt,ext,&extindex))
		return NULL;
	else
		return _MIMETypes.item[extindex];
}

char *MIMETypeFromFileName(char *filename)
{
	char *p=filename+strlen(filename),*z=NULL;

	while ((*p!='.') && (p>=filename) && ((*p)!='/'))
		p--;

	if (*p=='.')
		z=MIMETypeFromExt(p+1);

	if (z)
		return z;
	else
		return "application/octet-stream";
}

char *MIMETypeGuessFromFile(char *filename)
{
	char *p=filename+strlen(filename),*z=NULL;
	TFile file;

	while ((*p!='.') && (p>=filename) && ((*p)!='/'))
		p--;

	if (*p=='.')
		z=MIMETypeFromExt(p+1);

	if (z)
		return z;
	
	if (FileOpen(&file,filename,O_BINARY | O_RDONLY))
	{
		uint8 buffer[80],c;
		int32 len,i,n=0;

		i=len=FileRead(&file,buffer,80);

		while (i>0)
		{
			i--;
			c=buffer[i];
			if ((c>=' ') || (isspace(c)) || (c==26))
				n++;
		};

		if (n==len)
			z="text/plain";

		FileClose(&file);
	};

	if (z)
		return z;
	else
		return "application/octet-stream";	
}

/*********************************************************************
** Date
*********************************************************************/

static char *_DateDay[7]=
{
	"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};

static char *_DateMonth[12]=
{
	"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};

static int32 _DateTimeBias=0;
static char _DateTimeBiasStr[6]="";

bool DateToString(TDate *tm,char *s)
{
	if (mktime(tm)==(time_t)(-1))
	{
		*s='\0';
		return FALSE;
	};

	sprintf(s,"%s, %02d %s %04d %02d:%02d:%02d GMT",_DateDay[tm->tm_wday],tm->tm_mday,
				_DateMonth[tm->tm_mon],tm->tm_year+1900,tm->tm_hour,tm->tm_min,tm->tm_sec);

	return TRUE;
}

bool DateToLogString(TDate *tm,char *s)
{
	time_t t;
	TDate d;

	if ((t=mktime(tm))!=(time_t)(-1))
		if (DateFromLocal(&d,t))
		{
			sprintf(s,"%02d/%s/%04d:%02d:%02d:%02d %s",d.tm_mday,_DateMonth[d.tm_mon],
				d.tm_year+1900,d.tm_hour,d.tm_min,d.tm_sec,_DateTimeBiasStr);
			
			return TRUE;
		};

	*s='\0';
	return FALSE;
}

bool DateDecode(char *s,TDate *tm)
{
	uint32 n=0;

	/* Ignore spaces, day name and spaces */
	while ((*s==' ') || (*s=='\t'))
		s++;

	while ((*s!=' ') && (*s!='\t'))
		s++;

	while ((*s==' ') || (*s=='\t'))
		s++;

	/* try to recognize the date format */
	if (sscanf(s,"%*s %d %d:%d:%d %d%*s",&tm->tm_mday,&tm->tm_hour,&tm->tm_min,
			&tm->tm_sec,&tm->tm_year)!=5)
		if (sscanf(s,"%d %n%*s %d %d:%d:%d GMT%*s",&tm->tm_mday,&n,&tm->tm_year,
			&tm->tm_hour,&tm->tm_min,&tm->tm_sec)!=5)
			if (sscanf(s,"%d-%n%*[A-Za-z]-%d %d:%d:%d GMT%*s",&tm->tm_mday,&n,&tm->tm_year,
					&tm->tm_hour,&tm->tm_min,&tm->tm_sec)!=5)
				return FALSE;

	/* s points now to the month string */
	s+=n;
	for (n=0;n<12;n++)
	{
		char *p=_DateMonth[n];

		if (tolower(*p++)==tolower(*s))
			if (*p++==tolower(s[1]))
				if (*p==tolower(s[2]))
					break;
	};

	if (n==12)
		return FALSE;

	tm->tm_mon=n;

	/* finish the work */
	if (tm->tm_year>1900)
		tm->tm_year-=1900;
    else
		if (tm->tm_year<70)
			tm->tm_year+=100;

	tm->tm_isdst=0;

	return (mktime(tm)!=(time_t)(-1));
}

int32 DateCompare(TDate *d1,TDate *d2)
{
	int32 x;

	if ((x=d1->tm_year-d2->tm_year)==0)
		if ((x=d1->tm_mon-d2->tm_mon)==0)
			if ((x=d1->tm_mday-d2->tm_mday)==0)
				if ((x=d1->tm_hour-d2->tm_hour)==0)
					if ((x=d1->tm_min-d2->tm_min)==0)
						x=d1->tm_sec-d2->tm_sec;

	return x;
}

bool DateFromGMT(TDate *d,time_t t)
{
	TDate *dx;

	if (dx=gmtime(&t))
	{
		*d=*dx;
		return TRUE;
	};

	return FALSE;
}

bool DateFromLocal(TDate *d,time_t t)
{
	return DateFromGMT(d,t+_DateTimeBias*2);
}

bool DateInit()
{
	time_t t;
	TDate gmt,local,*d;

	time(&t);
	if (DateFromGMT(&gmt,t))
		if (d=localtime(&t))
		{
			local=*d;
			_DateTimeBias=(local.tm_sec-gmt.tm_sec)+(local.tm_min-gmt.tm_min)*60
							+(local.tm_hour-gmt.tm_hour)*3600;
			sprintf(_DateTimeBiasStr,"%+03d%02d",_DateTimeBias/3600,(_DateTimeBias % 3600)/60);
			return TRUE;
		};

	return FALSE;
}

/*********************************************************************
** Base64
*********************************************************************/

void Base64Encode(char *s,char *d)
{
	/* Conversion table. */
	static char tbl[64] = {
		'A','B','C','D','E','F','G','H',
		'I','J','K','L','M','N','O','P',
		'Q','R','S','T','U','V','W','X',
		'Y','Z','a','b','c','d','e','f',
		'g','h','i','j','k','l','m','n',
		'o','p','q','r','s','t','u','v',
		'w','x','y','z','0','1','2','3',
		'4','5','6','7','8','9','+','/'
	};

	uint32 i,length=strlen(s);
	char *p=d;
	
	/* Transform the 3x8 bits to 4x6 bits, as required by base64. */
	for (i = 0; i < length; i += 3)
	{
		*p++ = tbl[s[0] >> 2];
		*p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
		*p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
		*p++ = tbl[s[2] & 0x3f];
		s += 3;
	}
	
	/* Pad the result if necessary... */
	if (i == length + 1)
		*(p - 1) = '=';
	else if (i == length + 2)
		*(p - 1) = *(p - 2) = '=';
	
	/* ...and zero-terminate it. */
	*p = '\0';
}

