/*
 * $Id$
 *
 * sip msg. header proxy parser 
 *
 */

#include <string.h>
#include <stdlib.h>

#include "msg_parser.h"
#include "parser_f.h"
#include "error.h"
#include "dprint.h"

#ifdef DEBUG_DMALLOC
#include <dmalloc.h>
#endif



#define DEBUG



/* parses the first line, returns pointer to  next line  & fills fl;
   also  modifies buffer (to avoid extra copy ops) */
char* parse_first_line(char* buffer, unsigned int len, struct msg_start * fl)
{
	
	char *tmp;
	char* second;
	char* third;
	char* nl;
	int offset;
	char* end;
	
	/* grammar:
		request  =  method SP uri SP version CRLF
		response =  version SP status  SP reason  CRLF
		(version = "SIP/2.0")
	*/
	

	end=buffer+len;
	/* see if it's a reply (status) */
	tmp=eat_token(buffer, len);
	if ((tmp==buffer)||(tmp>=end)){
		LOG(L_INFO, "ERROR:parse_first_line: empty  or bad first line\n");
		goto error1;
	}
	if ((strlen(SIP_VERSION)==(tmp-buffer)) &&
		(memcmp(buffer,SIP_VERSION,tmp-buffer)==0)){
		
		fl->type=SIP_REPLY;
	}else{
		fl->type=SIP_REQUEST;
	}
	
	offset=tmp-buffer;
	second=eat_space(tmp, len-offset);
	offset+=second-tmp;
	if ((second==tmp)||(tmp>=end)){
		goto error;
	}
	*tmp=0; /* mark the end of the token */
	fl->u.request.method=buffer;
	
	/* next element */
	tmp=eat_token(second, len-offset);
	if (tmp>=end){
		goto error;
	}
	offset+=tmp-second;
	third=eat_space(tmp, len-offset);
	offset+=third-tmp;
	if ((third==tmp)||(tmp>=end)){
		goto error;
	}
	*tmp=0; /* mark the end of the token */
	fl->u.request.uri=second;
	/*  last part: for a request it must be the version, for a reply
	 *  it can contain almost anything, including spaces, so we don't care
	 *  about it*/
	if (fl->type==SIP_REQUEST){
		tmp=eat_token(third,len-offset);
		offset+=tmp-third;
		if ((tmp==third)||(tmp>=end)){
			goto error;
		}
		if (! is_empty(tmp, len-offset)){
			goto error;
		}
	}else{
		tmp=eat_token2(third,len-offset,'\r'); /* find end of line 
												  ('\n' or '\r') */
		if (tmp>=end){ /* no crlf in packet => invalid */
			goto error;
		}
		offset+=tmp-third;
	}
	nl=eat_line(tmp,len-offset);
	if (nl>=end){ /* no crlf in packet or only 1 line > invalid */
		goto error;
	}
	*tmp=0;
	fl->u.request.version=third;
	
	return nl;

error:
	LOG(L_INFO, "ERROR:parse_first_line: bad %s first line\n", 
		(fl->type==SIP_REPLY)?"reply(status)":"request");
error1:
	fl->type=SIP_INVALID;
	LOG(L_INFO, "ERROR: at line 0 char %d\n", offset);
	/* skip  line */
	nl=eat_line(buffer,len);
	return nl;
}


/* returns integer field name type */
int field_name(char *s)
{
	int l;
	l=strlen(s);
	if (l<1) return HDR_OTHER;
	else if ((l==1) && ((*s=='v')||(*s=='V')))
		return HDR_VIA;
	else if (strcasecmp(s, "Via")==0)
		return  HDR_VIA;
/*	if ((strcmp(s, "To")==0)||(strcmp(s,"t")==0))
		return HDR_TO;*/
	return HDR_OTHER;
}




/* returns pointer to next header line, and fill hdr_f */
char* get_hdr_field(char *buffer, unsigned int len, struct hdr_field*  hdr_f)
{
	/* grammar (rfc822):
		field = field-name ":" field-body CRLF
		field-body = text [ CRLF SP field-body ]
	   (CRLF in the field body must be removed)
	*/

	char* tmp, *tmp2;
	char* nl;
	char* body;
	int offset;

	
	/* init content to the empty string */
	hdr_f->name="";
	hdr_f->body="";
	
	if ((*buffer=='\n')||(*buffer=='\r')){
		/* double crlf */
		tmp=eat_line(buffer,len);
		hdr_f->type=HDR_EOH;
		return tmp;
	}
	
	tmp=eat_token2(buffer, len, ':');
	if ((tmp==buffer) || (tmp-buffer==len) ||
		(is_empty(buffer, tmp-buffer))|| (*tmp!=':')){
		hdr_f->type=HDR_ERROR;
		goto error;
	}
	*tmp=0;
	/* take care of possible spaces (e.g: "Via  :") */
	tmp2=eat_token(buffer, tmp-buffer);
	/* in the worst case tmp2=buffer+tmp-buffer=tmp */
	*tmp2=0;
	if (tmp2<tmp){
		tmp2++;
		/* catch things like: "Via foo bar:" */
		tmp2=eat_space(tmp2, tmp-tmp2);
		if (tmp2!=tmp){
			hdr_f->type=HDR_ERROR;
			goto error;
		}
	}

	hdr_f->type=field_name(buffer);
	body= ++tmp;
	hdr_f->name=buffer;
	offset=tmp-buffer;
	/* get all the lines in this field  body */
	do{
		nl=eat_line(tmp, len-offset);
		offset+=nl-tmp;
		tmp=nl;
	
	}while( (*tmp==' ' ||  *tmp=='\t') && (offset<len) );
	if (offset==len){
		hdr_f->type=HDR_ERROR;
		LOG(L_INFO, "ERROR: get_hdr_field: field body too  long\n");
		goto error;
	}
	*(tmp-1)=0; /* should be an LF */
	hdr_f->body=body;
error:
	return tmp;
}



char* parse_hostport(char* buf, char** host, short int* port)
{
	char *tmp;
	char *invalid;
	
	*host=buf;
	for(tmp=buf;(*tmp)&&(*tmp!=':');tmp++);
	if (*tmp==0){
		*port=0;
	}else{
		*tmp=0;
		invalid=0;
		*port=strtol(tmp+1, &invalid, 10);
		if ((invalid!=0)&&(*invalid)){
			LOG(L_INFO, 
					"ERROR: hostport: trailing chars in port number: %s(%x)\n",
					invalid, invalid);
			/* report error? */
		}
	}
	return *host;
}



/* buf= pointer to begining of uri (sip:x@foo.bar:5060;a=b?h=i)
   len= len of uri
returns: fills uri & returns <0 on error or 0 if ok */
int parse_uri(char *buf, int len, struct sip_uri* uri)
{
	char* next, *end;
	char *user, *passwd, *host, *port, *params, *headers;
	int host_len, port_len, params_len, headers_len;
	int ret;
	

	ret=0;
	end=buf+len;
	memset(uri, 0, sizeof(struct sip_uri)); /* zero it all, just to be sure */
	/* look for "sip:"*/;
	next=memchr(buf, ':',  len);
	if ((next==0)||(strncmp(buf,"sip",next-buf)!=0)){
		LOG(L_DBG, "ERROR: parse_uri: bad sip uri\n");
		ret=E_UNSPEC;
		goto error;
	}
	buf=next+1; /* next char after ':' */
	if (buf>end){
		LOG(L_DBG, "ERROR: parse_uri: uri too short\n");
		ret=E_UNSPEC;
		goto error;
	}
	/*look for '@' */
	next=memchr(buf,'@', end-buf);
	if (next==0){
		/* no '@' found, => no userinfo */
		uri->user=0;
		uri->passwd=0;
		host=buf;
	}else{
		/* found it */
		user=buf;
		/* try to find passwd */
		passwd=memchr(user,':', next-user);
		if (passwd==0){
			/* no ':' found => no password */
			uri->passwd=0;
			uri->user=(char*)malloc(next-user+1);
			if (uri->user==0){
				LOG(L_ERR,"ERROR:parse_uri: memory allocation failure\n");
				ret=E_OUT_OF_MEM;
				goto error;
			}
			memcpy(uri->user,user, next-user);
			uri->user[next-user]=0; /* null terminate it, usefull for easy printing*/
		}else{
			uri->user=(char*)malloc(passwd-user+1);
			if (uri->user==0){
				LOG(L_ERR,"ERROR:parse_uri: memory allocation failure\n");
				ret=E_OUT_OF_MEM;
				goto error;
			}
			memcpy(uri->user,user, passwd-user);
			uri->user[passwd-user]=0;
			passwd++; /*skip ':' */
			uri->passwd=(char*)malloc(next-passwd+1);
			if (uri->passwd==0){
				LOG(L_ERR,"ERROR:parse_uri: memory allocation failure\n");
				ret=E_OUT_OF_MEM;
				goto error;
			}
			memcpy(uri->passwd,passwd, next-passwd);
			uri->passwd[next-passwd]=0;
		}
		host=next+1; /* skip '@' */
	}
	/* try to find the rest */
	if(host>=end){
		LOG(L_DBG, "ERROR: parse_uri: missing hostport\n");
		ret=E_UNSPEC;
		goto error;
	}
	headers=memchr(host,'?',end-host);
	params=memchr(host,';',end-host);
	port=memchr(host,':',end-host);
	host_len=(port)?port-host:(params)?params-host:(headers)?headers-host:end-host;
	/* get host */
	uri->host=malloc(host_len+1);
	if (uri->host==0){
		LOG(L_ERR, "ERROR: parse_uri: memory allocation error\n");
		ret=E_OUT_OF_MEM;
		goto error;
	}
	memcpy(uri->host,host, host_len);
	uri->host[host_len]=0;
	/* get port*/
	if ((port)&&(port+1<end)){
		port++;
		if ( ((params) &&(params<port))||((headers) &&(headers<port)) ){
			/* error -> invalid uri we found ';' or '?' before ':' */
			LOG(L_DBG, "ERROR: parse_uri: malformed sip uri\n");
			ret=E_UNSPEC;
			goto error;
		}
		port_len=(params)?params-port:(headers)?headers-port:end-port;
		uri->port=malloc(port_len+1);
		if (uri->port==0){
			LOG(L_ERR, "ERROR: parse_uri: memory allocation error\n");
			ret=E_OUT_OF_MEM;
			goto error;
		}
		memcpy(uri->port, port, port_len);
		uri->port[port_len]=0;
	}else uri->port=0;
	/* get params */
	if ((params)&&(params+1<end)){
		params++;
		if ((headers) && (headers<params)){
			/* error -> invalid uri we found '?' or '?' before ';' */
			LOG(L_DBG, "ERROR: parse_uri: malformed sip uri\n");
			ret=E_UNSPEC;
			goto error;
		}
		params_len=(headers)?headers-params:end-params;
		uri->params=malloc(params_len+1);
		if (uri->params==0){
			LOG(L_ERR, "ERROR: parse_uri: memory allocation error\n");
			ret=E_OUT_OF_MEM;
			goto error;
		}
		memcpy(uri->params, params, params_len);
		uri->params[params_len]=0;
	}else uri->params=0;
	/*get headers */
	if ((headers)&&(headers+1<end)){
		headers++;
		headers_len=end-headers;
		uri->headers=malloc(headers_len+1);
		if(uri->headers==0){
			LOG(L_ERR, "ERROR: parse_uri: memory allocation error\n");
			ret=E_OUT_OF_MEM;
			goto error;
		}
		memcpy(uri->headers, headers, headers_len);
		uri->headers[headers_len]=0;
	}else uri->headers=0;
	
	return ret;
error:
	return ret;
}


			

			
			
	
		
	
	



/* parses a via body, returns next via (for compact vias) & fills vb,
 * the buffer should be null terminated! */
char* parse_via_body(char* buffer,unsigned int len, struct via_body * vb)
{
	/* format: sent-proto sent-by  *(";" params) [comment] 

	           sent-proto = name"/"version"/"transport
		   sent-by    = host [":" port]
		   
	*/

	char* tmp;
	char *name,*version, *transport, *comment, *params, *hostport;
	char * next_via;
	char * host;
	short int port;
	int offset;
	

	name=version=transport=comment=params=hostport=next_via=host=0;
	name=eat_space(buffer, len);
	if (name-buffer==len) goto error;
	offset=name-buffer;
	tmp=name;

	version=eat_token2(tmp,len-offset,'/');
	if (version+1-buffer>=len) goto error;
	*version=0;
	version++;
	offset+=version-tmp;
	
	transport=eat_token2(tmp,len-offset,'/');
	if (transport+1-buffer>=len) goto error;
	*transport=0;
	transport++;
	offset+=transport-tmp;
	
	tmp=eat_token(transport,len-offset);
	if (tmp+1-buffer>=len) goto error;
	*tmp=0;
	tmp++;
	offset+=tmp-transport;
	
	hostport=eat_space(tmp,len-offset);
	if (hostport+1-buffer>=len) goto error;
	offset+=hostport-tmp;

	/* find end of hostport */
 	for(tmp=hostport; (tmp-buffer)<len &&
			(*tmp!=' ')&&(*tmp!=';')&&(*tmp!=','); tmp++);
	if (tmp-buffer<len){
		switch (*tmp){
			case ' ':
				*tmp=0;
				tmp++;
				/*the rest is comment? */
				if (tmp-buffer<len){
					comment=tmp;
					/* eat the comment */
					for(;((tmp-buffer)<len)&&
						(*tmp!=',');tmp++);
					/* mark end of compact via (also end of comment)*/
					if (tmp-buffer<len){
						*tmp=0;
					}else break;
					/* eat space & ',' */
					for(tmp=tmp+1;((tmp-buffer)<len)&&
						(*tmp==' '|| *tmp==',');tmp++);
					
				}
				break;

			case ';':
				*tmp=0;
				tmp++;
				if (tmp-buffer>=len) goto error;
				params=tmp;
				/* eat till end, first space  or ',' */
				for(;((tmp-buffer)<len)&&
					(*tmp!=' '&& *tmp!=',');tmp++);
				if (tmp-buffer==len)  break;
				if (*tmp==' '){
					/* eat comment */
					*tmp=0;
					tmp++;
					comment=tmp;
					for(;((tmp-buffer)<len)&&
						(*tmp!=',');tmp++);
					if (tmp-buffer==len)  break;
				}
				/* mark end of via*/
				*tmp=0;
				/* eat space & ',' */
				for(tmp=tmp+1;((tmp-buffer)<len)&&
					(*tmp==' '|| *tmp==',');tmp++);
				break;

			case ',':
				*tmp=0;
				tmp++;
				if (tmp-buffer<len){
					/* eat space and ',' */
					for(;((tmp-buffer)<len)&&
						(*tmp==' '|| *tmp==',');
					   tmp++);
				}
		}
	}
	/* if we are not at the end of the body => we found another compact via */
	if (tmp-buffer<len) next_via=tmp;
	
	/* parse hostport */
	parse_hostport(hostport, &host, &port);
	vb->name=name;
	vb->version=version;
	vb->transport=transport;
	vb->host=host;
	vb->port=port;
	vb->params=params;
	vb->comment=comment;
	vb->next=next_via;
	vb->error=VIA_PARSE_OK;

	
	/* tmp points to end of body or to next via (if compact)*/
	
	return tmp;

error:
	vb->error=VIA_PARSE_ERROR;
	return tmp;
}



/* returns 0 if ok, -1 for errors */
int parse_msg(char* buf, unsigned int len, struct sip_msg* msg)
{

	char *tmp, *bar;
	char* rest;
	char* first_via;
	char* second_via;
	struct msg_start fl;
	struct hdr_field hf;
	struct via_body vb1, vb2;
	int offset;

	
	/* init vb1 & vb2 to the null string */
	vb1.error=VIA_PARSE_ERROR;
	vb1.hdr=vb1.name=vb1.version=vb1.transport=vb1.host=0;
	vb1.params=vb1.comment=0;
	vb1.next=0;
	vb1.size=0;
	memcpy(&vb2, &vb1, sizeof(struct via_body));

	/* eat crlf from the beginning */
	for (tmp=buf; (*tmp=='\n' || *tmp=='\r')&&
			tmp-buf < len ; tmp++);
	offset=tmp-buf;
	rest=parse_first_line(tmp, len-offset, &fl);
	offset+=rest-tmp;
	tmp=rest;
	switch(fl.type){
		case SIP_INVALID:
			DBG("parse_msg: invalid message\n");
			goto error;
			break;
		case SIP_REQUEST:
			DBG("SIP Request:\n");
			DBG(" method:  <%s>\n",fl.u.request.method);
			DBG(" uri:     <%s>\n",fl.u.request.uri);
			DBG(" version: <%s>\n",fl.u.request.version);
			break;
		case SIP_REPLY:
			DBG("SIP Reply  (status):\n");
			DBG(" version: <%s>\n",fl.u.reply.version);
			DBG(" status:  <%s>\n",fl.u.reply.status);
			DBG(" reason:  <%s>\n",fl.u.reply.reason);
			break;
		default:
			DBG("unknown type %d\n",fl.type);
	}
	
	/*find first Via: */
	hf.type=HDR_ERROR;
	first_via=0;
	second_via=0;
	do{
		rest=get_hdr_field(tmp, len-offset, &hf);
		offset+=rest-tmp;
		switch (hf.type){
			case HDR_ERROR:
				LOG(L_INFO,"ERROR: bad header  field\n");
				goto  error;
			case HDR_EOH: 
				goto skip;
			case HDR_VIA:
				if (first_via==0){
						first_via=hf.body;
						vb1.hdr=hf.name;
						/* replace cr/lf with space in first via */
						for (bar=first_via;(first_via) && (*bar);bar++)
							if ((*bar=='\r')||(*bar=='\n'))	*bar=' ';
				#ifdef DEBUG
						DBG("first via: <%s>\n", first_via);
				#endif
						bar=parse_via_body(first_via, strlen(first_via), &vb1);
						if (vb1.error!=VIA_PARSE_OK){
							LOG(L_INFO, "ERROR: parsing via body: %s\n",
									first_via);
							goto error;
						}
						
						vb1.size=bar-first_via+first_via-vb1.hdr; 
						
						/* compact via */
						if (vb1.next) {
							second_via=vb1.next;
							/* not interested in the rest of the header */
							goto skip;
						}else{
						/*  add 1 (we don't see the trailing lf which
						 *  was zeroed by get_hfr_field) */
							vb1.size+=1;
						}
						if (fl.type!=SIP_REPLY) goto skip; /* we are interested in the 2nd via 
															   only in replies */
				}else if (second_via==0){
							second_via=hf.body;
							vb2.hdr=hf.name;
							goto skip;
				}
				break;
		}
	#ifdef DEBUG
		DBG("header field type %d, name=<%s>, body=<%s>\n",
			hf.type, hf.name, hf.body);
	#endif
		tmp=rest;
	}while(hf.type!=HDR_EOH && rest-buf < len);

skip:
	/* replace cr/lf with space in the second via */
	for (tmp=second_via;(second_via) && (*tmp);tmp++)
		if ((*tmp=='\r')||(*tmp=='\n'))	*tmp=' ';

	if (second_via) {
		tmp=parse_via_body(second_via, strlen(second_via), &vb2);
		if (vb2.error!=VIA_PARSE_OK){
			LOG(L_INFO, "ERROR: parsing via2 body: %s\n", second_via);
			goto error;
		}
		vb2.size=tmp-second_via; 
		if (vb2.next==0) vb2.size+=1; /* +1 from trailing lf */
		if (vb2.hdr) vb2.size+=second_via-vb2.hdr;
	}
	

#ifdef DEBUG
	/* dump parsed data */
	if (first_via){
		DBG(" first  via: <%s/%s/%s> <%s:%d>",
				vb1.name, vb1.version, vb1.transport, vb1.host, vb1.port);
		if (vb1.params)  DBG(";<%s>", vb1.params);
		if (vb1.comment) DBG(" <%s>", vb1.comment);
		DBG ("\n");
	}
	if (second_via){
		DBG(" second via: <%s/%s/%s> <%s:%d>",
				vb2.name, vb2.version, vb2.transport, vb2.host, vb2.port);
		if (vb2.params)  DBG(";<%s>", vb2.params);
		if (vb2.comment) DBG(" <%s>", vb2.comment);
		DBG("\n");
	}
#endif
	
	/* copy data into msg */
	memcpy(&(msg->first_line), &fl, sizeof(struct msg_start));
	memcpy(&(msg->via1), &vb1, sizeof(struct via_body));
	memcpy(&(msg->via2), &vb2, sizeof(struct via_body));

#ifdef DEBUG
	DBG("exiting parse_msg\n");
#endif

	return 0;
	
error:
	return -1;
}