/*
 * Presence Agent, PIDF document support
 *
 * $Id: pidf.c,v 1.13.2.1 2005/07/20 17:11:52 andrei Exp $
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of ser, a free SIP server.
 *
 * ser is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * For a license to use the ser software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * ser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <string.h>
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>

#include "../../dprint.h"
#include "paerrno.h"
#include "common.h"
#include "presentity.h"
#include "pidf.h"
#include "ptime.h"
#include "pa_mod.h"

#define CRLF "\r\n"
#define CRLF_L (sizeof(CRLF) - 1)

#define PUBLIC_ID "//IETF//DTD RFCxxxx PIDF 1.0//EN"
#define PUBLIC_ID_L (sizeof(PUBLIC_ID) - 1)

#define MIME_TYPE "application/pidf+xml"
#define MIME_TYPE_L (sizeof(MIME_TYPE) - 1)

#define XML_VERSION "<?xml version=\"1.0\"?>"
#define XML_VERSION_L (sizeof(XML_VERSION) - 1)

#define TUPLE_ETAG "</tuple>"
#define TUPLE_ETAG_L (sizeof(TUPLE_ETAG) - 1)

#define PIDF_DTD "pidf.dtd"
#define PIDF_DTD_L (sizeof(XPDIF_DTD) - 1)

#define DOCTYPE "<!DOCTYPE presence PUBLIC \"" PUBLIC_ID "\" \"" PIDF_DTD "\">"
#define DOCTYPE_L (sizeof(DOCTYPE) - 1)

#define PRESENCE_START "<presence entity=\"sip:"
#define PRESENCE_START_L (sizeof(PRESENCE_START) - 1)

#define PRESENCE_END "\">"
#define PRESENCE_END_L (sizeof(PRESENCE_END) - 1)

#define PRESENCE_ETAG "</presence>"
#define PRESENCE_ETAG_L (sizeof(PRESENCE_ETAG) - 1)

#define TUPLE_START "<tuple id=\"9r28r49\">"
#define TUPLE_START_L (sizeof(TUPLE_START) - 1)

#define TUPLE_END "\">"
#define TUPLE_END_L (sizeof(TUPLE_END) - 1)

#define CONTACT_START "  <contact"
#define CONTACT_START_L (sizeof(CONTACT_START) - 1)

#define PRIORITY_START "  priority=\""
#define PRIORITY_START_L (sizeof(PRIORITY_START) - 1)

#define PRIORITY_END "\""
#define PRIORITY_END_L (sizeof(PRIORITY_END) - 1)

#define CONTACT_END ">"
#define CONTACT_END_L (sizeof(CONTACT_END) - 1)

#define CONTACT_ETAG "</contact>"
#define CONTACT_ETAG_L (sizeof(CONTACT_ETAG) - 1)

#define STATUS_STAG "  <status>"
#define STATUS_STAG_L (sizeof(STATUS_STAG) - 1)

#define STATUS_ETAG "  </status>"
#define STATUS_ETAG_L (sizeof(STATUS_ETAG) - 1)

#define BASIC_OPEN "    <basic>open</basic>\r\n"
#define BASIC_OPEN_L (sizeof(BASIC_OPEN) - 1)

#define BASIC_CLOSED "    <basic>closed</basic>\r\n"
#define BASIC_CLOSED_L (sizeof(BASIC_CLOSED) - 1)

#define LOCATION_STAG "    <geopriv><location-info><civilAddress>"
#define LOCATION_STAG_L (sizeof(LOCATION_STAG) - 1)

#define LOCATION_ETAG "    </civilAddress></location-info></geopriv>"
#define LOCATION_ETAG_L (sizeof(LOCATION_ETAG) - 1)

#define LOC_STAG "      <loc>"
#define LOC_STAG_L (sizeof(LOC_STAG) - 1)

#define LOC_ETAG "</loc>"
#define LOC_ETAG_L (sizeof(LOC_ETAG) - 1)

#define SITE_STAG "      <site>"
#define SITE_STAG_L (sizeof(SITE_STAG) - 1)

#define SITE_ETAG "</site>"
#define SITE_ETAG_L (sizeof(SITE_ETAG) - 1)

#define FLOOR_STAG "      <floor>"
#define FLOOR_STAG_L (sizeof(FLOOR_STAG) - 1)

#define FLOOR_ETAG "</floor>"
#define FLOOR_ETAG_L (sizeof(FLOOR_ETAG) - 1)

#define ROOM_STAG "      <room>"
#define ROOM_STAG_L (sizeof(ROOM_STAG) - 1)

#define ROOM_ETAG "</room>"
#define ROOM_ETAG_L (sizeof(ROOM_ETAG) - 1)

#define X_STAG "      <x>"
#define X_STAG_L (sizeof(X_STAG) - 1)

#define X_ETAG "</x>"
#define X_ETAG_L (sizeof(X_ETAG) - 1)

#define Y_STAG "      <y>"
#define Y_STAG_L (sizeof(Y_STAG) - 1)

#define Y_ETAG "</y>"
#define Y_ETAG_L (sizeof(Y_ETAG) - 1)

#define RADIUS_STAG "      <radius>"
#define RADIUS_STAG_L (sizeof(RADIUS_STAG) - 1)

#define RADIUS_ETAG "</radius>"
#define RADIUS_ETAG_L (sizeof(RADIUS_ETAG) - 1)

#define PRESCAPS_STAG "  <prescaps>"
#define PRESCAPS_STAG_L (sizeof(PRESCAPS_STAG) - 1)

#define PRESCAPS_ETAG "  </prescaps>"
#define PRESCAPS_ETAG_L (sizeof(PRESCAPS_ETAG) - 1)

const char *prescap_names[] = {
     "audio",
     "video",
     "text",
     "application"
};

/*
 * Create start of pidf document
 */
int start_pidf_doc(str* _b, int _l)
{
	if ((XML_VERSION_L + 
	     CRLF_L +
	     DOCTYPE_L + 
	     CRLF_L
	    ) > _l) {
		paerrno = PA_SMALL_BUFFER;
		LOG(L_ERR, "start_pidf_doc(): Buffer too small\n");
		return -1;
	}

	str_append(_b, XML_VERSION CRLF DOCTYPE CRLF,
		   XML_VERSION_L + CRLF_L + DOCTYPE_L + CRLF_L);
	return 0;
}


/*
 * Add a presentity information
 */
int pidf_add_presentity(str* _b, int _l, str* _uri)
{
	if (_l < PRESENCE_START_L + _uri->len + PRESENCE_END_L + CRLF_L) {
		paerrno = PA_SMALL_BUFFER;
		LOG(L_ERR, "pidf_add_presentity(): Buffer too small\n");
		return -1;
	}

	str_append(_b, PRESENCE_START, PRESENCE_START_L);
	str_append(_b, _uri->s, _uri->len);
	str_append(_b, PRESENCE_END CRLF, 
		   PRESENCE_END_L + CRLF_L);
	return 0;
}


/*
 * Create start of pidf tuple
 */
int pidf_start_tuple(str* _b, str *id, int _l)
{
	if ((TUPLE_START_L + 
	     id->len +
	     TUPLE_END_L +
	     CRLF_L
	    ) > _l) {
		paerrno = PA_SMALL_BUFFER;
		LOG(L_ERR, "start_pidf_tuple(): Buffer too small: _l=%d\n", _l);
		return -1;
	}

	str_append(_b, TUPLE_START, TUPLE_START_L);
	str_append(_b, id->s, id->len);
	str_append(_b, TUPLE_END CRLF, TUPLE_END_L + CRLF_L);
	return 0;
}

/*
 * Add a contact address with given status and priority
 */
int pidf_add_contact(str* _b, int _l, str* _addr, double priority)
{
	char priority_s[32];
	int priority_len = 0;

	if (_addr->len) {
		priority_len = sprintf(priority_s, "%f", priority);
		str_append(_b, CONTACT_START, CONTACT_START_L);
		if (pa_pidf_priority) {
			str_append(_b, PRIORITY_START, PRIORITY_START_L);
			str_append(_b, priority_s, priority_len);
			str_append(_b, PRIORITY_END, PRIORITY_END_L);
		}
		str_append(_b, CONTACT_END, CONTACT_END_L);
		str_append(_b, _addr->s, _addr->len);
		str_append(_b, CONTACT_ETAG CRLF , 
			   CONTACT_ETAG_L + CRLF_L);
	}
	return 0;
}

int pidf_start_status(str *_b, int _l, pidf_status_t _st)
{
	int len = 0;
	char* basic;

	switch(_st) {
	case PIDF_ST_OPEN:   basic = BASIC_OPEN;   len = BASIC_OPEN_L;   break;
	case PIDF_ST_CLOSED: basic = BASIC_CLOSED; len = BASIC_CLOSED_L; break;
	default:              basic = BASIC_CLOSED; len = BASIC_CLOSED_L; break; /* Makes gcc happy */
	}

	str_append(_b, STATUS_STAG CRLF, STATUS_STAG_L + CRLF_L);
	str_append(_b, basic, len);
	return 0;
}

int pidf_end_status(str *_b, int _l)
{
	str_append(_b, STATUS_ETAG CRLF, STATUS_ETAG_L + CRLF_L);
	return 0;
}


/*
 * Add location information
 */
int pidf_add_location(str* _b, int _l, str *_loc, str *_site, str *_floor, str *_room, double _x, double _y, double _radius,
		      enum prescaps prescaps)
{
	str_append(_b, LOCATION_STAG, LOCATION_STAG_L);

	if (_loc->len) {
		str_append(_b, LOC_STAG, LOC_STAG_L);
		str_append(_b, _loc->s, _loc->len);
		str_append(_b, LOC_ETAG CRLF, LOC_ETAG_L + CRLF_L);
	}
	if (_site->len) {
		str_append(_b, SITE_STAG, SITE_STAG_L);
		str_append(_b, _site->s, _site->len);
		str_append(_b, SITE_ETAG CRLF, SITE_ETAG_L + CRLF_L);
	}
	if (_floor->len) {
		str_append(_b, FLOOR_STAG, FLOOR_STAG_L);
		str_append(_b, _floor->s, _floor->len);
		str_append(_b, FLOOR_ETAG CRLF, FLOOR_ETAG_L + CRLF_L);
	}
	if (_room->len) {
		str_append(_b, ROOM_STAG, ROOM_STAG_L);
		str_append(_b, _room->s, _room->len);
		str_append(_b, ROOM_ETAG CRLF, ROOM_ETAG_L + CRLF_L);
	}

	if (_x) {
		char buf[128];
		int len = sprintf(buf, "%g", _x);
		str_append(_b, X_STAG, X_STAG_L);
		str_append(_b, buf, len);
		str_append(_b, X_ETAG CRLF, X_ETAG_L + CRLF_L);
	}
	if (_y) {
		char buf[128];
		int len = sprintf(buf, "%g", _y);
		str_append(_b, Y_STAG, Y_STAG_L);
		str_append(_b, buf, len);
		str_append(_b, Y_ETAG CRLF, Y_ETAG_L + CRLF_L);
	}
	if (_radius) {
		char buf[128];
		int len = sprintf(buf, "%g", _radius);
		str_append(_b, RADIUS_STAG, RADIUS_STAG_L);
		str_append(_b, buf, len);
		str_append(_b, RADIUS_ETAG CRLF, RADIUS_ETAG_L + CRLF_L);
	}

	str_append(_b, LOCATION_ETAG CRLF, CRLF_L + LOCATION_ETAG_L);
	if (prescaps) {
	     int i;
	     str_append(_b, PRESCAPS_STAG CRLF, PRESCAPS_STAG_L + CRLF_L);
	     for (i = 0; i < 4; i++) {
		  const char *prescap_name = prescap_names[i];
		  char prescap[128];
		  int prescap_l = sprintf(prescap, "      <%s>%s</%s>%s",
					  prescap_name, ((prescaps & (1 << i)) ? "true" : "false"), prescap_name, CRLF);
		  str_append(_b, prescap, prescap_l);
	     }
	     str_append(_b, PRESCAPS_ETAG CRLF, PRESCAPS_ETAG_L + CRLF_L);
	}
	str_append(_b, STATUS_ETAG CRLF, STATUS_ETAG_L + CRLF_L);
	return 0;
}

/*
 * Create start of pidf tuple
 */
int pidf_end_tuple(str* _b, int _l)
{
	if ((TUPLE_ETAG_L + 
	     CRLF_L
	    ) > _l) {
		paerrno = PA_SMALL_BUFFER;
		LOG(L_ERR, "pidf_end_tuple(): Buffer too small\n");
		return -1;
	}

	str_append(_b, TUPLE_ETAG CRLF,
		   TUPLE_ETAG_L + CRLF_L);
	return 0;
}

/*
 * End the document
 */
int end_pidf_doc(str* _b, int _l)
{
	if (_l < (PRESENCE_ETAG_L + CRLF_L)) {
		paerrno = PA_SMALL_BUFFER;
		LOG(L_ERR, "end_pidf_doc(): Buffer too small\n");
		return -1;
	}

	str_append(_b, PRESENCE_ETAG CRLF, PRESENCE_ETAG_L + CRLF_L);
	return 0;
}



xmlDocPtr event_body_parse(char *event_body)
{
	return xmlParseMemory(event_body, strlen(event_body));
}

/*
 * apply procedure f to each xmlNodePtr in doc matched by xpath
 */
void xpath_map(xmlDocPtr doc, char *xpath, void (*f)(xmlNodePtr, void *), void *data)
{
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	xmlNodeSetPtr nodeset;
	int i;

	context = xmlXPathNewContext(doc);
	result = xmlXPathEvalExpression((xmlChar*)xpath, context);
	if(!result || xmlXPathNodeSetIsEmpty(result->nodesetval)){
		fprintf(stderr, "xpath_map: no result for xpath=%s\n", xpath);
		return;
	}
	nodeset = result->nodesetval;
	for (i=0; i < nodeset->nodeNr; i++) {
		xmlNodePtr node = nodeset->nodeTab[i];
		printf("name[%d]: %s\n", i, node->name);
		f(node, data);
	}
	xmlXPathFreeContext(context);
}

xmlNodePtr xpath_get_node(xmlDocPtr doc, char *xpath)
{
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	xmlNodeSetPtr nodeset;
	xmlNodePtr node;

	context = xmlXPathNewContext(doc);
	result = xmlXPathEvalExpression((xmlChar*)xpath, context);
	if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
		fprintf(stderr, "xpath_get_node: no result for xpath=%s\n", xpath);
		return NULL;
	}
	nodeset = result->nodesetval;
	node = nodeset->nodeTab[0];
	xmlXPathFreeContext(context);
	return node;
}

xmlAttrPtr xmlNodeGetAttrByName(xmlNodePtr node, const char *name)
{
	xmlAttrPtr attr = node->properties;
	while (attr) {
		if (xmlStrcasecmp(attr->name, (xmlChar*)name) == 0)
			return attr;
		attr = attr->next;
	}
	return NULL;
}

char *xmlNodeGetAttrContentByName(xmlNodePtr node, const char *name)
{
     xmlAttrPtr attr = xmlNodeGetAttrByName(node, name);
     if (attr)
	  return (char*)xmlNodeGetContent(attr->children);
     else
	  return NULL;
}

xmlNodePtr xmlNodeGetChildByName(xmlNodePtr node, const char *name)
{
	xmlNodePtr cur = node->children;
	while (cur) {
		if (xmlStrcasecmp(cur->name, (xmlChar*)name) == 0)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

xmlNodePtr xmlNodeGetNodeByName(xmlNodePtr node, const char *name, const char *ns)
{
	xmlNodePtr cur = node;
	while (cur) {
		xmlNodePtr match = NULL;
		if (xmlStrcasecmp(cur->name, (xmlChar*)name) == 0) {
			if (!ns || (cur->ns && 
						xmlStrcasecmp(cur->ns->prefix, (xmlChar*)ns) == 0))
				return cur;
		}
		match = xmlNodeGetNodeByName(cur->children, name, ns);
		if (match)
			return match;
		cur = cur->next;
	}
	return NULL;
}

char *xmlNodeGetNodeContentByName(xmlNodePtr root, const char *name, const char *ns)
{
	xmlNodePtr node = xmlNodeGetNodeByName(root, name, ns);
	if (node)
		return (char*)xmlNodeGetContent(node->children);
	else
		return NULL;
}

xmlNodePtr xmlDocGetNodeByName(xmlDocPtr doc, const char *name, const char *ns)
{
	xmlNodePtr cur = doc->children;
	return xmlNodeGetNodeByName(cur, name, ns);
}

char *xmlDocGetNodeContentByName(xmlDocPtr doc, const char *name, const char *ns)
{
	xmlNodePtr node = xmlDocGetNodeByName(doc, name, ns);
	if (node)
		return (char*)xmlNodeGetContent(node->children);
	else
		return NULL;
}


void xmlNodeMapByName(xmlNodePtr node, const char *name, const char *ns, 
		      void (f)(xmlNodePtr, void*), void *data)
{
	xmlNodePtr cur = node;
	if (!f)
		return;
	while (cur) {
		if (xmlStrcasecmp(cur->name, (xmlChar*)name) == 0) {
			if (!ns || (cur->ns &&
						xmlStrcasecmp(cur->ns->prefix, (xmlChar*)ns) == 0))
				f(cur, data);
		}
		/* visit children */
		xmlNodeMapByName(cur->children, name, ns, f, data);

		cur = cur->next;
	}
}

void xmlDocMapByName(xmlDocPtr doc, const char *name, const char *ns,
			   void (f)(xmlNodePtr, void*), void *data )
{
	xmlNodePtr cur = doc->children;
	xmlNodeMapByName(cur, name, ns, f, data);
}

int parse_pidf(char *pidf_body, str *contact_str, str *basic_str, str *status_str, 
	       str *location_str, str *site_str, str *floor_str, str *room_str,
	       double *xp, double *yp, double *radiusp,
	       str *packet_loss_str, double *priorityp, time_t *expiresp,
	       int *prescapsp)
{
     int flags = 0;
     xmlDocPtr doc = NULL;
     xmlNodePtr presenceNode = NULL;
     xmlNodePtr prescapsNode = NULL;
     char *presence = NULL;
     char *sipuri = NULL;
     char *contact = NULL;
     char *basic = NULL;
     char *status = NULL;
     char *location = NULL;
     char *site = NULL;
     char *floor = NULL;
     char *room = NULL;
     char *x = NULL;
     char *y = NULL;
     char *radius = NULL;
     char *packet_loss = NULL;
     char *priority_str = NULL;
     char *expires_str = NULL;
     int prescaps = 0;

     doc = event_body_parse(pidf_body);
     if (!doc) {
	  return flags;
     }

     presenceNode = xmlDocGetNodeByName(doc, "presence", NULL);
     presence = xmlDocGetNodeContentByName(doc, "presence", NULL);
     contact = xmlDocGetNodeContentByName(doc, "contact", NULL);
     basic = xmlDocGetNodeContentByName(doc, "basic", NULL);
     status = xmlDocGetNodeContentByName(doc, "status", NULL);
     location = xmlDocGetNodeContentByName(doc, "loc", NULL);
     site = xmlDocGetNodeContentByName(doc, "site", NULL);
     floor = xmlDocGetNodeContentByName(doc, "floor", NULL);
     room = xmlDocGetNodeContentByName(doc, "room", NULL);
     x = xmlDocGetNodeContentByName(doc, "x", NULL);
     y = xmlDocGetNodeContentByName(doc, "y", NULL);
     radius = xmlDocGetNodeContentByName(doc, "radius", NULL);
     packet_loss = xmlDocGetNodeContentByName(doc, "packet-loss", NULL);
     priority_str = xmlDocGetNodeContentByName(doc, "priority", NULL);
     expires_str = xmlDocGetNodeContentByName(doc, "expires", NULL);
     prescapsNode = xmlDocGetNodeByName(doc, "prescaps", NULL);
		
     if (presenceNode)
	  sipuri = xmlNodeGetAttrContentByName(presenceNode, "entity");

     LOG(L_INFO, "parse_pidf: sipuri=%p:%s contact=%p:%s basic=%p:%s location=%p:%s\n",
	 sipuri, sipuri, contact, contact, basic, basic, location, location);
     LOG(L_INFO, "parse_pidf: site=%p:%s floor=%p:%s room=%p:%s\n",
	 site, site, floor, floor, room, room);
     LOG(L_INFO, "parse_pidf: x=%p:%s y=%p:%s radius=%p:%s\n",
	 x, x, y, y, radius, radius);
     if (packet_loss)
	  LOG(L_INFO, "packet_loss=%p:%s\n", packet_loss, packet_loss);

     if (contact_str && contact) {
	  contact_str->len = strlen(contact);
	  contact_str->s = strdup(contact);
	  flags |= PARSE_PIDF_CONTACT;
     }
     if (basic_str && basic) {
	  basic_str->len = strlen(basic);
	  basic_str->s = strdup(basic);
	  flags |= PARSE_PIDF_BASIC;
     }
     if (status_str && status) {
	  status_str->len = strlen(status);
	  status_str->s = strdup(status);
	  flags |= PARSE_PIDF_STATUS;
     }
     if (location_str && location) {
	  location_str->len = strlen(location);
	  location_str->s = strdup(location);
	  flags |= PARSE_PIDF_LOC;
     }
     if (site_str && site) {
	  site_str->len = strlen(site);
	  site_str->s = strdup(site);
	  flags |= PARSE_PIDF_SITE;
     }
     if (floor_str && floor) {
	  floor_str->len = strlen(floor);
	  floor_str->s = strdup(floor);
	  flags |= PARSE_PIDF_FLOOR;
     }
     if (room_str && room) {
	  room_str->len = strlen(room);
	  room_str->s = strdup(room);
	  flags |= PARSE_PIDF_ROOM;
     }
     if (xp && x) {
	  *xp = strtod(x, NULL);
	  flags |= PARSE_PIDF_X;
     }
     if (yp && y) {
	  *yp = strtod(y, NULL);
	  flags |= PARSE_PIDF_Y;
     }
     if (radiusp && radius) {
	  *radiusp = strtod(radius, NULL);
	  flags |= PARSE_PIDF_RADIUS;
     }
     if (packet_loss_str && packet_loss) {
	  packet_loss_str->len = strlen(packet_loss);
	  packet_loss_str->s = strdup(packet_loss);
	  flags |= PARSE_PIDF_PACKET_LOSS;
     }
     if (expiresp && expires_str) {
       *expiresp = act_time + strtod(expires_str, NULL);
       flags |= PARSE_PIDF_EXPIRES;
     }
     if (priorityp && priority_str) {
	  *priorityp = strtod(priority_str, NULL);
	  flags |= PARSE_PIDF_PRIORITY;
     }
     if (prescapsNode) {
	  int i;
	  for (i = 0; i < 4; i++) {
	       const char *prescap_name = prescap_names[i];
	       xmlNodePtr prescap_node = xmlNodeGetNodeByName(prescapsNode, prescap_name, NULL);
	       const char *prescap_str = xmlNodeGetNodeContentByName(prescapsNode, prescap_name, NULL);
	       if (prescap_str && (strcasecmp(prescap_str, "true") == 0))
		    prescaps |= (1 << i);
	       LOG(L_INFO, "parse_pidf: prescap=%s node=%p value=%s\n", prescap_name, prescap_node, prescap_str);
	  }
	  LOG(L_INFO, "parse_pidf: prescaps=%x\n", prescaps);
     }
     if (prescapsp) {
	  *prescapsp = prescaps;
	  flags |= PARSE_PIDF_PRESCAPS;
     }
     return flags;
}


syntax highlighted by Code2HTML, v. 0.9.1