lib/presence/pidf.c
1c24f8eb
 /* 
299073a5
  * PIDF parser
  *
  * $Id$
  * 
1c24f8eb
  * Copyright (C) 2005 iptelorg GmbH
  *
  * 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 <presence/pidf.h>
 #include <cds/dstring.h>
 #include <cds/memory.h>
 #include <cds/logger.h>
17d8cc2b
 #include <cds/list.h>
 #include <presence/xml_utils.h>
 #include <string.h>
 
 /* ------------------------------ PIDF document creation ------------------------------ */
1c24f8eb
 
2cbeb3df
 static void doc_add_tuple_note(dstring_t *buf, presence_note_t *n)
 {
 	DEBUG_LOG("doc_add_tuple_note()\n");
 	
 	dstr_append_zt(buf, "\t\t<note");
 	if (n->lang.len > 0) {
 		dstr_append_zt(buf, " lang=\"");
 		dstr_append_str(buf, &n->lang);
 		dstr_append_zt(buf, "\"");
 	}
 	dstr_append_zt(buf, ">");
 	dstr_append_str(buf, &n->value);	
 	dstr_append_zt(buf, "</note>\r\n");
 }
 
69cdb5f3
 static inline void doc_add_extension(dstring_t *buf, extension_element_t *ex)
 {
 	dstr_append_str(buf, &ex->element);
 	dstr_append_zt(buf, "\r\n");
 }
2cbeb3df
 
1c24f8eb
 static void doc_add_tuple(dstring_t *buf, presentity_info_t *p, presence_tuple_info_t *t)
 {
2cbeb3df
 	presence_note_t *n;
69cdb5f3
 	extension_element_t *e;
17d8cc2b
 	char tmp[32];
2cbeb3df
 	
1c24f8eb
 	DEBUG_LOG("doc_add_tuple()\n");
 	
 	dstr_append_zt(buf, "\t<tuple id=\"");
17d8cc2b
 	dstr_append_str(buf, &t->id);
6e2ace47
 	dstr_append_zt(buf, "\">\r\n");
1c24f8eb
 	
69cdb5f3
 	dstr_append_zt(buf, "\t\t<status>\r\n");
 	if (t->status.basic != presence_tuple_undefined_status) {
701973de
 		/* do not add unknown status it is not mandatory in PIDF */
69cdb5f3
 		dstr_append_zt(buf, "\t\t\t<basic>");
 		dstr_append_str(buf, tuple_status2str(t->status.basic));
 		dstr_append_zt(buf, "</basic>\r\n");
 	}
 	/* add extension status elements */
 	e = t->status.first_unknown_element;
 	while (e) {
 		doc_add_extension(buf, e);
 		e = e->next;
 	}
 	dstr_append_zt(buf, "\t\t</status>\r\n");
 	
 	/* add extension elements */
 	e = t->first_unknown_element;
 	while (e) {
 		doc_add_extension(buf, e);
 		e = e->next;
701973de
 	}
8e4e4fcb
 
69cdb5f3
 	if (!is_str_empty(&t->contact)) {
 		dstr_append_zt(buf, "\t\t<contact priority=\"");
 		sprintf(tmp, "%1.2f", t->priority);
 		dstr_append_zt(buf, tmp);
 		dstr_append_zt(buf, "\">");
 		dstr_append_str(buf, &t->contact);
 		dstr_append_zt(buf, "</contact>\r\n");
 	}
1c24f8eb
 
2cbeb3df
 	n = t->first_note;
 	while (n) {
 		doc_add_tuple_note(buf, n);
 		n = n->next;
 	}
 	
6e2ace47
 	dstr_append_zt(buf, "\t</tuple>\r\n");
1c24f8eb
 }
 
04985995
 static void doc_add_empty_tuple(dstring_t *buf)
 {
e36217b1
 	/* "empty" tuple is needed in PIDF by Microsoft Windows Messenger v. 5.1 and linphone 1.2) */
2cbeb3df
 	DEBUG_LOG("doc_add_empty_tuple()\n");
04985995
 	
 	dstr_append_zt(buf, "\t<tuple id=\"none\">\r\n");
 	dstr_append_zt(buf, "\t\t<status><basic>closed</basic></status>\r\n");
 
 	dstr_append_zt(buf, "\t</tuple>\r\n");
 }
 
17d8cc2b
 static void doc_add_note(dstring_t *buf, presentity_info_t *p, presence_note_t *n)
 {
2cbeb3df
 	DEBUG_LOG("doc_add_note()\n");
17d8cc2b
 	
 	dstr_append_zt(buf, "\t<note");
 	if (n->lang.len > 0) {
 		dstr_append_zt(buf, " lang=\"");
 		dstr_append_str(buf, &n->lang);
 		dstr_append_zt(buf, "\"");
 	}
 	dstr_append_zt(buf, ">");
 	dstr_append_str(buf, &n->value);	
 	dstr_append_zt(buf, "</note>\r\n");
 }
 
8e4e4fcb
 static void dstr_put_pres_uri(dstring_t *buf, str_t *uri)
 {
 	char *c;
 	int len = 0;
 	
 	if (!uri) return;
 	
 	c = str_strchr(uri, ':');
 	if (c) {
 		len = uri->len - (c - uri->s) - 1;
 		if (len > 0) c++;
 	}
 	else {
 		c = uri->s;
 		len = uri->len;
 	}
 	if (len > 0) {
 		dstr_append_zt(buf, "pres:");
 		dstr_append(buf, c, len);
 	}
 }
 
 static void doc_add_presentity(dstring_t *buf, presentity_info_t *p, int use_cpim_pidf_ns)
1c24f8eb
 {
 	presence_tuple_info_t *t;
17d8cc2b
 	presence_note_t *n;
69cdb5f3
 	extension_element_t *e;
1c24f8eb
 
17d8cc2b
 	DEBUG_LOG("doc_add_presentity()\n");
8e4e4fcb
 	if (use_cpim_pidf_ns)
 		dstr_append_zt(buf, "<presence xmlns=\"urn:ietf:params:xml:ns:cpim-pidf\" entity=\"");
 	else 
 		dstr_append_zt(buf, "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"");
 	/* !!! there SHOULD be pres URI of presentity !!! */
701973de
 	dstr_put_pres_uri(buf, &p->uri);
04985995
 	/* dstr_append_str(buf, &p->presentity); */ /* only for test !!! */
6e2ace47
 	dstr_append_zt(buf, "\">\r\n");
1c24f8eb
 	
de0394c4
 	DEBUG_LOG("adding tuples\n");
1c24f8eb
 	t = p->first_tuple;
e36217b1
 	if (!t) doc_add_empty_tuple(buf); /* correction for some strange clients :-) */
1c24f8eb
 	while (t) {
 		doc_add_tuple(buf, p, t);
 		t = t->next;
 	}
17d8cc2b
 	
de0394c4
 	DEBUG_LOG("adding notes\n");
17d8cc2b
 	n = p->first_note;
 	while (n) {
 		doc_add_note(buf, p, n);
 		n = n->next;
 	}
de0394c4
 	
69cdb5f3
 	/* add extension elements */
 	DEBUG_LOG("adding extension elements\n");
 	e = p->first_unknown_element;
 	while (e) {
 		doc_add_extension(buf, e);
 		e = e->next;
de0394c4
 	}
17d8cc2b
 
04985995
 	dstr_append_zt(buf, "</presence>\r\n");
1c24f8eb
 }
 
8e4e4fcb
 int create_pidf_document_ex(presentity_info_t *p, str_t *dst, str_t *dst_content_type, int use_cpim_pidf_ns)
1c24f8eb
 {
 	dstring_t buf;
c0ce5c29
 	int err;
1c24f8eb
 	
 	if (!dst) return -1;
 	
 	str_clear(dst);
 	if (dst_content_type) str_clear(dst_content_type);
 
 	if (!p) return -1;
 	
e36217b1
 	if (dst_content_type) {
 		if (use_cpim_pidf_ns)
c0ce5c29
 			err = str_dup_zt(dst_content_type, "application/cpim-pidf+xml");
e36217b1
 		else
c0ce5c29
 			err = str_dup_zt(dst_content_type, "application/pidf+xml;charset=\"UTF-8\"");
 		if (err < 0) return -1;
e36217b1
 	}
 	
04985995
 /*	if (!p->first_tuple) return 0;*/	/* no tuples => nothing to say */ 
1c24f8eb
 	
 	dstr_init(&buf, 2048);
 	
6e2ace47
 	dstr_append_zt(&buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
8e4e4fcb
 	doc_add_presentity(&buf, p, use_cpim_pidf_ns);
1c24f8eb
 	
c0ce5c29
 	err = dstr_get_str(&buf, dst);
1c24f8eb
 	dstr_destroy(&buf);
 	
c0ce5c29
 	if (err != 0) {
 		str_free_content(dst);
 		if (dst_content_type) str_free_content(dst_content_type);
 	}
 	
 	return err;
1c24f8eb
 }
 
8e4e4fcb
 int create_pidf_document(presentity_info_t *p, str_t *dst, str_t *dst_content_type)
 {
 	return create_pidf_document_ex(p, dst, dst_content_type, 0);
 }
 
17d8cc2b
 /* ------------------------------ PIDF document parsing ------------------------------ */
 
 static char *pidf_ns = "urn:ietf:params:xml:ns:pidf";
de0394c4
 /* static char *rpid_ns = "urn:ietf:params:xml:ns:pidf:rpid"; */
69cdb5f3
 /* static char *data_model_ns = "urn:ietf:params:xml:ns:pidf:data-model"; */
17d8cc2b
 
 static int read_note(xmlNode *node, presence_note_t **dst)
 {
 	const char *note = NULL;
 	const char *lang = NULL;
 
 	note = get_node_value(node);
 	lang = get_attr_value(find_attr(node->properties, "lang"));
 
 	*dst = create_presence_note_zt(note, lang);
 	if (!dst) return -1;
 	
 	return 0;
 }
 
69cdb5f3
 static int get_whole_node_content(xmlNode *n, str_t *dst, xmlDocPtr doc)
 {
 	int res = 0;
 
 	str_clear(dst);
 	if (n) {
 		n = xmlCopyNode(n, 1); /* this inserts namespaces into element correctly */
 		if (!n) {
 			ERROR_LOG("can't duplicate XML node\n");
 			return -1;
 		}
 	}
 	if (n) {
 		xmlBufferPtr buf;
 		buf = xmlBufferCreate();
 		if (buf == NULL) {
 			ERROR_LOG("Error creating the xml buffer\n");
 			return -1;
 		}
 		if (xmlNodeDump(buf, doc, n, 0, 0) < 0) res = -1;
 		if ((res == 0) && (buf->use > 0)) {
 			str_t s;
 			s.s = (char *)buf->content;
 			s.len = buf->use;
 			res = str_dup(dst, &s);
 		}
 		xmlBufferFree(buf);
 		xmlFreeNode(n); /* was duplicated due to namespaces! */
 	}
 	return res;
 }
 
 static int read_extension(xmlNode *ex, extension_element_t **dst, xmlDocPtr doc)
 {
 	extension_element_t *e;
 	/* xmlNode *n; */
 
 	if (!dst) return -1;
 	*dst = NULL;
 	
 	e = (extension_element_t*)cds_malloc(sizeof(extension_element_t));
 	if (!e) return -1;
 	
 	memset(e, 0, sizeof(*e));
 	*dst = e;
17d8cc2b
 
69cdb5f3
 	/* do not care about internals - take whole element ! */
 	if (get_whole_node_content(ex, &e->element, doc) != 0) {
 		cds_free(e);
 		*dst = NULL;
 		return -1;
 	}
 	
 	return 0;
 }
 
 static int read_tuple(xmlNode *tuple, presence_tuple_info_t **dst, int ignore_ns, xmlDocPtr doc)
17d8cc2b
 {
 	str_t contact, id;
69cdb5f3
 	basic_tuple_status_t status;
 	xmlNode *n, *status_node;
17d8cc2b
 	double priority = 0;
 	const char *s;
 	int res = 0;
 	presence_note_t *note;
4526254b
 	char *ns = ignore_ns ? NULL: pidf_ns;
69cdb5f3
 	extension_element_t *ex;
17d8cc2b
 
 	*dst = NULL;
 
 	DEBUG_LOG("read_tuple()\n");
 	/* process contact (only one node) */
4526254b
 	n = find_node(tuple, "contact", ns);
17d8cc2b
 	if (!n) {
2c82fd67
 		/* ERROR_LOG("contact not found\n"); */
e36217b1
 		str_clear(&contact);
 		/* return -1; */
 	}
 	else {
 		s = get_attr_value(find_attr(n->properties, "priority"));
 		if (s) priority = atof(s);
 		s = get_node_value(n);
 		contact.s = (char *)s;
 		if (s) contact.len = strlen(s);
 		else contact.len = 0;
 		if (contact.len < 1) {
 			ERROR_LOG("empty contact using default\n");
 			/* return -1; */
 		}	
17d8cc2b
 	}
 	
 	/* process status (only one node) */
69cdb5f3
 	status_node = find_node(tuple, "status", ns);
 	if (!status_node) {
17d8cc2b
 		ERROR_LOG("status not found\n");
 		return -1;
 	}
69cdb5f3
 	n = find_node(status_node, "basic", ns);
17d8cc2b
 	if (!n) {
7382e329
 		ERROR_LOG("basic status not found - using \'closed\'\n");
 		/* return -1; */
 		s = "closed";
17d8cc2b
 	}
7382e329
 	else s = get_node_value(n);
17d8cc2b
 	if (!s) {
 		ERROR_LOG("basic status without value\n");
 		return -1;
 	}
 
 	/* translate status */
 	status = presence_tuple_closed; /* default value */
8191f4d5
 	if (strcasecmp(s, "open") == 0) status = presence_tuple_open;
 	if (strcasecmp(s, "closed") == 0) status = presence_tuple_closed;
17d8cc2b
 	/* FIXME: handle not standardized variants too (add note to basic status) */
 	
 	/* get ID from tuple node attribute? */
 	id.s = (char *)get_attr_value(find_attr(tuple->properties, "id"));
 	if (id.s) id.len = strlen(id.s);
 	else id.len = 0;
 	
 	*dst = create_tuple_info(&contact, &id, status);
 	if (!(*dst)) return -1;
 
 	(*dst)->priority = priority;
 
69cdb5f3
 	/* handle nested elements */
17d8cc2b
 	n = tuple->children;
 	while (n) {
 		if (n->type == XML_ELEMENT_NODE) {
4526254b
 			if (cmp_node(n, "note", ns) >= 0) {
17d8cc2b
 				res = read_note(n, &note);
 				if ((res == 0) && note) {
 					DOUBLE_LINKED_LIST_ADD((*dst)->first_note, 
 							(*dst)->last_note, note);
 				}
 				else break;
 			}
69cdb5f3
 			else if (cmp_node(n, "contact", ns) >= 0) {
 				/* skip, already processed */
 			}
 			else if (cmp_node(n, "status", ns) >= 0) {
 				/* skip, already processed */
 			}	
 			else if (cmp_node(n, "timestamp", ns) >= 0) {
 				/* FIXME: process */
 			}
 			else { /* PIDF extensions - only from non-PIDF namespace? */
 				res = read_extension(n, &ex, doc);
 				if ((res == 0) && ex) 
 					DOUBLE_LINKED_LIST_ADD((*dst)->first_unknown_element, 
 							(*dst)->last_unknown_element, ex);
 			}
17d8cc2b
 		}
 		n = n->next;
 	}
69cdb5f3
 	
 	/* handle nested elements in status */
 	if (status_node) n = status_node->children;
 	else n = NULL;
 	while (n) {
 		if (n->type == XML_ELEMENT_NODE) {
 			if (cmp_node(n, "basic", ns) >= 0) {
 				/* skip, already processed */
 			}
 			else { /* PIDF extensions - only from non-PIDF namespace? */
 				res = read_extension(n, &ex, doc);
 				if ((res == 0) && ex) 
 					DOUBLE_LINKED_LIST_ADD((*dst)->status.first_unknown_element, 
 							(*dst)->status.last_unknown_element, ex);
 			}
de0394c4
 		}
69cdb5f3
 		n = n->next;
de0394c4
 	}
 
69cdb5f3
 	return res;
de0394c4
 }
 
 static int read_presentity(xmlNode *root, presentity_info_t **dst, int ignore_ns, xmlDocPtr doc)
17d8cc2b
 {
 	xmlNode *n;
 	str_t entity;
 	presence_tuple_info_t *t;
 	presence_note_t *note;
 	int res = 0;
4526254b
 	char *ns = ignore_ns ? NULL: pidf_ns;
69cdb5f3
 	extension_element_t *ex;
17d8cc2b
 	
299073a5
 	/* TRACE_LOG("read_presentity(ns=%s)\n", ns ? ns : ""); */
4526254b
 	if (cmp_node(root, "presence", ns) < 0) {
17d8cc2b
 		ERROR_LOG("document is not presence \n");
 		return -1;
 	}
 
 	entity = zt2str((char*)get_attr_value(find_attr(root->properties, "entity")));
 	*dst = create_presentity_info(&entity);
 	if (!(*dst)) return -1; /* memory */
 
 	n = root->children;
 	while (n) {
 		if (n->type == XML_ELEMENT_NODE) {
4526254b
 			if (cmp_node(n, "tuple", ns) >= 0) {
69cdb5f3
 				res = read_tuple(n, &t, ignore_ns, doc);
17d8cc2b
 				if ((res == 0) && t) add_tuple_info(*dst, t);
 				else break;
 			}
69cdb5f3
 			else if (cmp_node(n, "note", ns) >= 0) {
 					res = read_note(n, &note);
 					if ((res == 0) && note) {
 						DOUBLE_LINKED_LIST_ADD((*dst)->first_note, 
 								(*dst)->last_note, note);
 					}
 					else break;
17d8cc2b
 				}
69cdb5f3
 			else { /* PIDF extensions - only from non-PIDF namespace? */
 				res = read_extension(n, &ex, doc);
 				if ((res == 0) && ex) 
 					DOUBLE_LINKED_LIST_ADD((*dst)->first_unknown_element, 
 							(*dst)->last_unknown_element, ex);
de0394c4
 				/*if (res != 0) break; ignore errors there */
 			}
 			
17d8cc2b
 		}
 		n = n->next;
 	}
 
 	return res;
 }
 
8e4e4fcb
 /* ignore ns added for cpim-pidf+xml, draft version 07 (differs only in ns) */
 int parse_pidf_document_ex(presentity_info_t **dst, const char *data, int data_len, int ignore_ns)
17d8cc2b
 {
 	int res = 0;
 	xmlDocPtr doc;
 	
 	if (!dst) return -1;
 	if ((!data) || (data_len < 1)) return -2;
 
 	*dst = NULL;
 	doc = xmlReadMemory(data, data_len, NULL, NULL, xml_parser_flags);
 	if (doc == NULL) {
 		ERROR_LOG("can't parse document\n");
 		return -1;
 	}
69cdb5f3
 
de0394c4
 	res = read_presentity(xmlDocGetRootElement(doc), dst, ignore_ns, doc);
17d8cc2b
 	if (res != 0) {
 		/* may be set => must be freed */
 		if (*dst) free_presentity_info(*dst);
 		*dst = NULL;
 	}
 
 	xmlFreeDoc(doc);
 	return res;
 }
 
8e4e4fcb
 /* libxml2 must be initialized before calling this function ! */
 int parse_pidf_document(presentity_info_t **dst, const char *data, int data_len)
 {
 	return parse_pidf_document_ex(dst, data, data_len, 0);
 }
 
 /* --------------- CPIM_PIDF document creation/parsing ---------------- */
 
 int parse_cpim_pidf_document(presentity_info_t **dst, const char *data, int data_len)
 {
 	return parse_pidf_document_ex(dst, data, data_len, 1);
 }
 
 int create_cpim_pidf_document(presentity_info_t *p, str_t *dst, str_t *dst_content_type)
 {
 	return create_pidf_document_ex(p, dst, dst_content_type, 1);
 }