msg_parser.c
512dcd98
 /*
  * $Id$
  *
  * sip msg. header proxy parser 
  *
  */
 
888ca09d
 #include <string.h>
512dcd98
 
888ca09d
 #include "msg_parser.h"
512dcd98
 #include "parser_f.h"
 #include "dprint.h"
 
 
 
888ca09d
 #define DEBUG
 
 
 
512dcd98
 /* 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;
038e5c9e
 	char* end;
512dcd98
 	
 	/* grammar:
 		request  =  method SP uri SP version CRLF
 		response =  version SP status  SP reason  CRLF
 		(version = "SIP/2.0")
 	*/
 	
 
038e5c9e
 	end=buffer+len;
512dcd98
 	/* see if it's a reply (status) */
 	tmp=eat_token(buffer, len);
038e5c9e
 	if ((tmp==buffer)||(tmp>=end)){
efeaaf53
 		LOG(L_INFO, "ERROR:parse_first_line: empty  or bad first line\n");
512dcd98
 		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;
038e5c9e
 	if ((second==tmp)||(tmp>=end)){
512dcd98
 		goto error;
 	}
 	*tmp=0; /* mark the end of the token */
 	fl->u.request.method=buffer;
 	
 	/* next element */
 	tmp=eat_token(second, len-offset);
038e5c9e
 	if (tmp>=end){
 		goto error;
 	}
512dcd98
 	offset+=tmp-second;
 	third=eat_space(tmp, len-offset);
 	offset+=third-tmp;
038e5c9e
 	if ((third==tmp)||(tmp>=end)){
512dcd98
 		goto error;
 	}
 	*tmp=0; /* mark the end of the token */
 	fl->u.request.uri=second;
038e5c9e
 	/*  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;
512dcd98
 	}
038e5c9e
 	nl=eat_line(tmp,len-offset);
 	if (nl>=end){ /* no crlf in packet or only 1 line > invalid */
512dcd98
 		goto error;
 	}
 	*tmp=0;
 	fl->u.request.version=third;
 	
 	return nl;
 
 error:
efeaaf53
 	LOG(L_INFO, "ERROR:parse_first_line: bad %s first line\n", 
512dcd98
 		(fl->type==SIP_REPLY)?"reply(status)":"request");
 error1:
 	fl->type=SIP_INVALID;
efeaaf53
 	LOG(L_INFO, "ERROR: at line 0 char %d\n", offset);
512dcd98
 	/* skip  line */
 	nl=eat_line(buffer,len);
 	return nl;
 }
 
 
 /* returns integer field name type */
 int field_name(char *s)
 {
 	if ((strcmp(s, "Via")==0 )||(strcmp(s,"v")==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;
 	char* nl;
 	char* body;
 	int offset;
 
 	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-1))|| (*tmp!=':')){
 		hdr_f->type=HDR_ERROR;
 		goto error;
 	}
 	*tmp=0;
 	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;
efeaaf53
 		LOG(L_INFO, "ERROR: het_hdr_field: field body too  long\n");
512dcd98
 		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)){
efeaaf53
 			LOG(L_INFO, 
 					"ERROR: hostport: trailing chars in port number: %s(%x)\n",
512dcd98
 					invalid, invalid);
 			/* report error? */
 		}
 	}
 	return *host;
 }
 
 
 
 /* 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;
038e5c9e
 				tmp++;
512dcd98
 				/*the rest is comment? */
038e5c9e
 				if (tmp-buffer<len){
512dcd98
 					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++;
038e5c9e
 				if (tmp-buffer>=len) goto error;
512dcd98
 				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;
038e5c9e
 				tmp++;
 				if (tmp-buffer<len){
512dcd98
 					/* eat space and ',' */
038e5c9e
 					for(;((tmp-buffer)<len)&&
512dcd98
 						(*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;
 }
 
888ca09d
 
 
 /* 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;
 	int r;
 
 	
 	/* 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:
efeaaf53
 			DBG("parse_msg: invalid message\n");
888ca09d
 			goto error;
 			break;
 		case SIP_REQUEST:
efeaaf53
 			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);
888ca09d
 			break;
 		case SIP_REPLY:
efeaaf53
 			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);
888ca09d
 			break;
 		default:
efeaaf53
 			DBG("unknown type %d\n",fl.type);
888ca09d
 	}
 	
 	/*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:
efeaaf53
 				LOG(L_INFO,"ERROR: bad header  field\n");
888ca09d
 				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
efeaaf53
 						DBG("first via: <%s>\n", first_via);
888ca09d
 				#endif
 						bar=parse_via_body(first_via, strlen(first_via), &vb1);
 						if (vb1.error!=VIA_PARSE_OK){
efeaaf53
 							LOG(L_INFO, "ERROR: parsing via body: %s\n",
 									first_via);
888ca09d
 							goto error;
 						}
e60a9728
 						
 						vb1.size=bar-first_via+first_via-vb1.hdr; 
888ca09d
 						
 						/* compact via */
 						if (vb1.next) {
 							second_via=vb1.next;
 							/* not interested in the rest of the header */
 							goto skip;
e60a9728
 						}else{
 						/*  add 1 (we don't see the trailing lf which
 						 *  was zeroed by get_hfr_field */
 							vb1.size+=1;
888ca09d
 						}
 				}else if (second_via==0){
 							second_via=hf.body;
 							vb2.hdr=hf.name;
 							goto skip;
 				}
 				break;
 		}
 	#ifdef DEBUG
efeaaf53
 		DBG("header field type %d, name=<%s>, body=<%s>\n",
888ca09d
 			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){
efeaaf53
 			LOG(L_INFO, "ERROR: parsing via2 body: %s\n", second_via);
888ca09d
 			goto error;
 		}
e60a9728
 		vb2.size=tmp-second_via; 
 		if (vb2.next==0) vb2.size+=1; /* +1 from trailing lf */
888ca09d
 		if (vb2.hdr) vb2.size+=second_via-vb2.hdr;
 	}
 	
 
 #ifdef DEBUG
 	/* dump parsed data */
efeaaf53
 	DBG(" first  via: <%s/%s/%s> <%s:%d>",
888ca09d
 			vb1.name, vb1.version, vb1.transport, vb1.host, vb1.port);
efeaaf53
 	if (vb1.params)  DBG(";<%s>", vb1.params);
 	if (vb1.comment) DBG(" <%s>", vb1.comment);
 	DBG ("\n");
888ca09d
 	if (second_via){
efeaaf53
 		DBG(" second via: <%s/%s/%s> <%s:%d>",
888ca09d
 				vb2.name, vb2.version, vb2.transport, vb2.host, vb2.port);
efeaaf53
 		if (vb2.params)  DBG(";<%s>", vb2.params);
 		if (vb2.comment) DBG(" <%s>", vb2.comment);
 		DBG("\n");
888ca09d
 	}
 #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
efeaaf53
 	DBG("exiting parse_msg\n");
888ca09d
 #endif
 
 	return 0;
 	
 error:
 	return -1;
 }