LP#1703411: Move OpenSRF XMPP attrs to subelement
[OpenSRF.git] / src / libopensrf / transport_message.c
1 #include <opensrf/transport_message.h>
2
3 /**
4         @file transport_message.c
5         @brief Collection of routines for managing transport_messages.
6
7         These routines are largely concerned with the conversion of XML to transport_messages,
8         and vice versa.
9 */
10
11 /**
12         @brief Allocate and initialize a new transport_message to be send via Jabber.
13         @param body Content of the message.
14         @param subject Subject of the message.
15         @param thread Thread of the message.
16         @param recipient The address of the recipient.
17         @param sender The address of the sender.
18         @return A pointer to a newly-allocated transport_message.
19
20         This function doesn't populate everything.  Typically there are subsequent calls to
21         some combination of message_set_router_info(), message_set_osrf_xid(), and
22         set_msg_error() to populate various additional bits and pieces.  Before sending the
23         message anywhere, we call message_prepare_xml() to translate the message into XML,
24         specifically a message stanza for a Jabber XML stream.
25
26         The calling code is responsible for freeing the transport_message by calling message_free().
27 */
28 transport_message* message_init( const char* body, const char* subject,
29                 const char* thread, const char* recipient, const char* sender ) {
30
31         transport_message* msg = safe_malloc( sizeof(transport_message) );
32
33         if( body        == NULL ) { body       = ""; }
34         if( thread      == NULL ) { thread     = ""; }
35         if( subject     == NULL ) { subject    = ""; }
36         if( sender      == NULL ) { sender     = ""; }
37         if( recipient   == NULL ) { recipient  = ""; }
38
39         msg->body       = strdup(body);
40         msg->thread     = strdup(thread);
41         msg->subject    = strdup(subject);
42         msg->recipient  = strdup(recipient);
43         msg->sender     = strdup(sender);
44
45         if( msg->body        == NULL || msg->thread    == NULL  ||
46                         msg->subject == NULL || msg->recipient == NULL  ||
47                         msg->sender  == NULL ) {
48
49                 osrfLogError(OSRF_LOG_MARK, "message_init(): Out of Memory" );
50                 free( msg->body );
51                 free( msg->thread );
52                 free( msg->subject );
53                 free( msg->recipient );
54                 free( msg->sender );
55                 free( msg );
56                 return NULL;
57         }
58
59         msg->router_from    = NULL;
60         msg->router_to      = NULL;
61         msg->router_class   = NULL;
62         msg->router_command = NULL;
63         msg->osrf_xid       = NULL;
64         msg->is_error       = 0;
65         msg->error_type     = NULL;
66         msg->error_code     = 0;
67         msg->broadcast      = 0;
68         msg->msg_xml        = NULL;
69         msg->next           = NULL;
70
71         return msg;
72 }
73
74
75 /**
76         @brief Translate an XML string into a transport_message.
77         @param msg_xml Pointer to a &lt;message&gt; element as passed by Jabber.
78         @return Pointer to a newly created transport_message.
79
80         Do @em not populate the following members:
81         - router_command
82         - is_error
83         - error_type
84         - error_code.
85
86         The calling code is responsible for freeing the transport_message by calling message_free().
87 */
88 transport_message* new_message_from_xml( const char* msg_xml ) {
89
90         if( msg_xml == NULL || *msg_xml == '\0' )
91                 return NULL;
92
93         transport_message* new_msg = safe_malloc( sizeof(transport_message) );
94
95         new_msg->body           = NULL;
96         new_msg->subject        = NULL;
97         new_msg->thread         = NULL;
98         new_msg->recipient      = NULL;
99         new_msg->sender         = NULL;
100         new_msg->router_from    = NULL;
101         new_msg->router_to      = NULL;
102         new_msg->router_class   = NULL;
103         new_msg->router_command = NULL;
104         new_msg->osrf_xid       = NULL;
105         new_msg->is_error       = 0;
106         new_msg->error_type     = NULL;
107         new_msg->error_code     = 0;
108         new_msg->broadcast      = 0;
109         new_msg->msg_xml        = NULL;
110         new_msg->next           = NULL;
111
112         /* Parse the XML document and grab the root */
113         xmlKeepBlanksDefault(0);
114         xmlDocPtr msg_doc = xmlReadDoc( BAD_CAST msg_xml, NULL, NULL, 0 );
115         xmlNodePtr root = xmlDocGetRootElement(msg_doc);
116
117         /* Get various attributes of the root, */
118         /* and use them to populate the corresponding members */
119         xmlChar* sender         = xmlGetProp( root, BAD_CAST "from");
120         xmlChar* recipient      = xmlGetProp( root, BAD_CAST "to");
121         xmlChar* subject        = xmlGetProp( root, BAD_CAST "subject");
122         xmlChar* thread         = xmlGetProp( root, BAD_CAST "thread" );
123         xmlChar* router_from    = xmlGetProp( root, BAD_CAST "router_from" );
124         xmlChar* router_to      = xmlGetProp( root, BAD_CAST "router_to" );
125         xmlChar* router_class   = xmlGetProp( root, BAD_CAST "router_class" );
126         xmlChar* router_command = xmlGetProp( root, BAD_CAST "router_command" );
127         xmlChar* broadcast      = xmlGetProp( root, BAD_CAST "broadcast" );
128         xmlChar* osrf_xid       = xmlGetProp( root, BAD_CAST "osrf_xid" );
129
130         if( osrf_xid ) {
131                 message_set_osrf_xid( new_msg, (char*) osrf_xid);
132                 xmlFree(osrf_xid);
133         }
134
135         if( router_from ) {
136                 new_msg->sender     = strdup((const char*)router_from);
137         } else {
138                 if( sender ) {
139                         new_msg->sender = strdup((const char*)sender);
140                         xmlFree(sender);
141                 }
142         }
143
144         if( recipient ) {
145                 new_msg->recipient  = strdup((const char*)recipient);
146                 xmlFree(recipient);
147         }
148
149         if(subject){
150                 new_msg->subject    = strdup((const char*)subject);
151                 xmlFree(subject);
152         }
153
154         if(thread) {
155                 new_msg->thread     = strdup((const char*)thread);
156                 xmlFree(thread);
157         }
158
159         if(router_from) {
160                 new_msg->router_from = strdup((const char*)router_from);
161                 xmlFree(router_from);
162         }
163
164         if(router_to) {
165                 new_msg->router_to  = strdup((const char*)router_to);
166                 xmlFree(router_to);
167         }
168
169         if(router_class) {
170                 new_msg->router_class = strdup((const char*)router_class);
171                 xmlFree(router_class);
172         }
173
174         if(router_command) {
175                 new_msg->router_command = strdup((const char*)router_command);
176                 xmlFree(router_command);
177         }
178
179         if(broadcast) {
180                 if(strcmp((const char*) broadcast,"0") )
181                         new_msg->broadcast = 1;
182                 xmlFree(broadcast);
183         }
184
185         /* Within the message element, find the child nodes for "thread", "subject", and */
186         /* "body".  Extract their textual content into the corresponding members. */
187         xmlNodePtr search_node = root->children;
188         while( search_node != NULL ) {
189
190                 if( ! strcmp( (const char*) search_node->name, "thread" ) ) {
191                         if( search_node->children && search_node->children->content )
192                                 new_msg->thread = strdup( (const char*) search_node->children->content );
193                 }
194
195                 if( ! strcmp( (const char*) search_node->name, "subject" ) ) {
196                         if( search_node->children && search_node->children->content )
197                                 new_msg->subject = strdup( (const char*) search_node->children->content );
198                 }
199
200                 if( ! strcmp( (const char*) search_node->name, "opensrf" ) ) {
201                         router_from    = xmlGetProp( search_node, BAD_CAST "router_from" );
202                         router_to      = xmlGetProp( search_node, BAD_CAST "router_to" );
203                         router_class   = xmlGetProp( search_node, BAD_CAST "router_class" );
204                         router_command = xmlGetProp( search_node, BAD_CAST "router_command" );
205                         broadcast      = xmlGetProp( search_node, BAD_CAST "broadcast" );
206                         osrf_xid       = xmlGetProp( search_node, BAD_CAST "osrf_xid" );
207
208                         if( osrf_xid ) {
209                                 message_set_osrf_xid( new_msg, (char*) osrf_xid);
210                                 xmlFree(osrf_xid);
211                         }
212
213                         if( router_from ) {
214                                 new_msg->sender     = strdup((const char*)router_from);
215                         } else {
216                                 if( sender ) {
217                                         new_msg->sender = strdup((const char*)sender);
218                                         xmlFree(sender);
219                                 }
220                         }
221
222                         if(router_from) {
223                                 new_msg->router_from = strdup((const char*)router_from);
224                                 xmlFree(router_from);
225                         }
226
227                         if(router_to) {
228                                 new_msg->router_to  = strdup((const char*)router_to);
229                                 xmlFree(router_to);
230                         }
231
232                         if(router_class) {
233                                 new_msg->router_class = strdup((const char*)router_class);
234                                 xmlFree(router_class);
235                         }
236
237                         if(router_command) {
238                                 new_msg->router_command = strdup((const char*)router_command);
239                                 xmlFree(router_command);
240                         }
241
242                         if(broadcast) {
243                                 if(strcmp((const char*) broadcast,"0") )
244                                         new_msg->broadcast = 1;
245                                 xmlFree(broadcast);
246                         }
247                 }
248
249                 if( ! strcmp( (const char*) search_node->name, "body" ) ) {
250                         if( search_node->children && search_node->children->content )
251                                 new_msg->body = strdup((const char*) search_node->children->content );
252                 }
253
254                 search_node = search_node->next;
255         }
256
257         if( new_msg->thread == NULL )
258                 new_msg->thread = strdup("");
259         if( new_msg->subject == NULL )
260                 new_msg->subject = strdup("");
261         if( new_msg->body == NULL )
262                 new_msg->body = strdup("");
263
264         /* Convert the XML document back into a string, and store it. */
265         new_msg->msg_xml = xmlDocToString(msg_doc, 0);
266         xmlFreeDoc(msg_doc);
267         xmlCleanupParser();
268
269         return new_msg;
270 }
271
272 /**
273         @brief Populate the osrf_xid (an OSRF extension) of a transport_message.
274         @param msg Pointer to the transport_message.
275         @param osrf_xid Value of the "osrf_xid" attribute of a message stanza.
276
277         If @a osrf_xid is NULL, populate with an empty string.
278
279         See also message_set_router_info().
280 */
281 void message_set_osrf_xid( transport_message* msg, const char* osrf_xid ) {
282         if( msg ) {
283                 if( msg->osrf_xid ) free( msg->osrf_xid );
284                 msg->osrf_xid = strdup( osrf_xid ? osrf_xid : "" );
285         }
286 }
287
288 /**
289         @brief Populate some OSRF extensions to XMPP in a transport_message.
290         @param msg Pointer to the transport_message to be populated.
291         @param router_from Value of "router_from" attribute in message stanza
292         @param router_to Value of "router_to" attribute in message stanza
293         @param router_class Value of "router_class" attribute in message stanza
294         @param router_command Value of "router_command" attribute in message stanza
295         @param broadcast_enabled Value of "broadcast" attribute in message stanza
296
297         If any of the pointer pararmeters is NULL (other than @a msg), populate the
298         corresponding member with an empty string.
299
300         See also osrf_set_xid().
301 */
302 void message_set_router_info( transport_message* msg, const char* router_from,
303                 const char* router_to, const char* router_class, const char* router_command,
304                 int broadcast_enabled ) {
305
306         if( msg ) {
307
308                 /* free old values, if any */
309                 if( msg->router_from    ) free( msg->router_from );
310                 if( msg->router_to      ) free( msg->router_to );
311                 if( msg->router_class   ) free( msg->router_class );
312                 if( msg->router_command ) free( msg->router_command );
313
314                 /* install new values */
315                 msg->router_from     = strdup( router_from     ? router_from     : "" );
316                 msg->router_to       = strdup( router_to       ? router_to       : "" );
317                 msg->router_class    = strdup( router_class    ? router_class    : "" );
318                 msg->router_command  = strdup( router_command  ? router_command  : "" );
319                 msg->broadcast = broadcast_enabled;
320
321                 if( msg->router_from == NULL || msg->router_to == NULL ||
322                                 msg->router_class == NULL || msg->router_command == NULL )
323                         osrfLogError(OSRF_LOG_MARK,  "message_set_router_info(): Out of Memory" );
324         }
325 }
326
327
328 /**
329         @brief Free a transport_message and all the memory it owns.
330         @param msg Pointer to the transport_message to be destroyed.
331         @return 1 if successful, or 0 upon error.  The only error condition is if @a msg is NULL.
332 */
333 int message_free( transport_message* msg ){
334         if( msg == NULL ) { return 0; }
335
336         free(msg->body);
337         free(msg->thread);
338         free(msg->subject);
339         free(msg->recipient);
340         free(msg->sender);
341         free(msg->router_from);
342         free(msg->router_to);
343         free(msg->router_class);
344         free(msg->router_command);
345         free(msg->osrf_xid);
346         if( msg->error_type != NULL ) free(msg->error_type);
347         if( msg->msg_xml != NULL ) free(msg->msg_xml);
348         free(msg);
349         return 1;
350 }
351
352
353 /**
354         @brief Build a &lt;message&gt; element and store it as a string in the msg_xml member.
355         @param msg Pointer to a transport_message.
356         @return 1 if successful, or 0 if not.  The only error condition is if @a msg is NULL.
357
358         If msg_xml is already populated, keep it, and return immediately.
359
360         The contents of the &lt;message&gt; element come from various members of the
361         transport_message.  Store the resulting string as the msg_xml member.
362
363         To build the XML we first build a DOM structure, and then export it to a string.  That
364         way we can let the XML library worry about replacing certain characters with
365         character entity references -- not a trivial task when UTF-8 characters may be present.
366 */
367 int message_prepare_xml( transport_message* msg ) {
368
369         if( !msg ) return 0;
370         if( msg->msg_xml ) return 1;   /* already done */
371
372         xmlNodePtr  message_node;
373         xmlNodePtr  body_node;
374         xmlNodePtr  thread_node;
375         xmlNodePtr  opensrf_node;
376         xmlNodePtr  subject_node;
377         xmlNodePtr  error_node;
378
379         xmlDocPtr   doc;
380
381         xmlKeepBlanksDefault(0);
382
383         doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
384         message_node = xmlDocGetRootElement(doc);
385
386         if( msg->is_error ) {
387                 error_node = xmlNewChild(message_node, NULL, BAD_CAST "error" , NULL );
388                 xmlAddChild( message_node, error_node );
389                 xmlNewProp( error_node, BAD_CAST "type", BAD_CAST msg->error_type );
390                 char code_buf[16];
391                 osrf_clearbuf( code_buf, sizeof(code_buf));
392                 sprintf(code_buf, "%d", msg->error_code );
393                 xmlNewProp( error_node, BAD_CAST "code", BAD_CAST code_buf  );
394         }
395
396         /* set from and to */
397         xmlNewProp( message_node, BAD_CAST "to", BAD_CAST msg->recipient );
398         xmlNewProp( message_node, BAD_CAST "from", BAD_CAST msg->sender );
399
400         /* set from and to on a new node, also */
401         opensrf_node = xmlNewChild(message_node, NULL, (xmlChar*) "opensrf", NULL );
402         xmlNewProp( opensrf_node, BAD_CAST "router_from", BAD_CAST msg->router_from );
403         xmlNewProp( opensrf_node, BAD_CAST "router_to", BAD_CAST msg->router_to );
404         xmlNewProp( opensrf_node, BAD_CAST "router_class", BAD_CAST msg->router_class );
405         xmlNewProp( opensrf_node, BAD_CAST "router_command", BAD_CAST msg->router_command );
406         xmlNewProp( opensrf_node, BAD_CAST "osrf_xid", BAD_CAST msg->osrf_xid );
407
408         xmlAddChild(message_node, opensrf_node);
409
410         if( msg->broadcast ) {
411                 xmlNewProp( opensrf_node, BAD_CAST "broadcast", BAD_CAST "1" );
412         }
413
414         /* Now add nodes where appropriate */
415         char* body      = msg->body;
416         char* subject   = msg->subject;
417         char* thread    = msg->thread;
418
419         if( thread && *thread ) {
420                 thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", NULL );
421                 xmlNodePtr txt = xmlNewText((xmlChar*) thread);
422                 xmlAddChild(thread_node, txt);
423                 xmlAddChild(message_node, thread_node);
424         }
425
426         if( subject && *subject ) {
427                 subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", NULL );
428                 xmlNodePtr txt = xmlNewText((xmlChar*) subject);
429                 xmlAddChild(subject_node, txt);
430                 xmlAddChild( message_node, subject_node );
431         }
432
433         if( body && *body ) {
434                 body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", NULL);
435                 xmlNodePtr txt = xmlNewText((xmlChar*) body);
436                 xmlAddChild(body_node, txt);
437                 xmlAddChild( message_node, body_node );
438         }
439
440         // Export the xmlDoc to a string
441         xmlBufferPtr xmlbuf = xmlBufferCreate();
442         xmlNodeDump( xmlbuf, doc, xmlDocGetRootElement(doc), 0, 0);
443         msg->msg_xml = strdup((const char*) (xmlBufferContent(xmlbuf)));
444
445         xmlBufferFree(xmlbuf);
446         xmlFreeDoc( doc );
447         xmlCleanupParser();
448
449         return 1;
450 }
451
452
453 /**
454         @brief Extract the username from a Jabber ID.
455         @param jid Pointer to the Jabber ID.
456         @param buf Pointer to a receiving buffer supplied by the caller.
457         @param size Maximum number of characters to copy, not including the terminal nul.
458
459         A jabber ID is of the form "username@domain/resource", where the resource is optional.
460         Here we copy the username portion into the supplied buffer, plus a terminal nul.  If there
461         is no "@" character, leave the buffer as an empty string.
462 */
463 void jid_get_username( const char* jid, char buf[], int size ) {
464
465         if( jid == NULL || buf == NULL || size <= 0 ) { return; }
466
467         buf[ 0 ] = '\0';
468
469         /* find the @ and return whatever is in front of it */
470         int len = strlen( jid );
471         int i;
472         for( i = 0; i != len; i++ ) {
473                 if( jid[i] == '@' ) {
474                         if(i > size)  i = size;
475                         memcpy( buf, jid, i );
476                         buf[i] = '\0';
477                         return;
478                 }
479         }
480 }
481
482
483 /**
484         @brief Extract the resource from a Jabber ID.
485         @param jid Pointer to the Jabber ID.
486         @param buf Pointer to a receiving buffer supplied by the caller.
487         @param size Maximum number of characters to copy, not including the terminal nul.
488
489         A jabber ID is of the form "username@domain/resource", where the resource is optional.
490         Here we copy the resource portion, if present into the supplied buffer, plus a terminal nul.
491         If there is no resource, leave the buffer as an empty string.
492 */
493 void jid_get_resource( const char* jid, char buf[], int size)  {
494         if( jid == NULL || buf == NULL || size <= 0 ) { return; }
495
496         // Find the last slash, if any
497         const char* start = strrchr( jid, '/' );
498         if( start ) {
499
500                 // Copy the text beyond the slash, up to a maximum size
501                 size_t len = strlen( ++start );
502                 if( len > size ) len = size;
503                 memcpy( buf, start, len );
504                 buf[ len ] = '\0';
505         }
506         else
507                 buf[ 0 ] = '\0';
508 }
509
510 /**
511         @brief Extract the domain from a Jabber ID.
512         @param jid Pointer to the Jabber ID.
513         @param buf Pointer to a receiving buffer supplied by the caller.
514         @param size Maximum number of characters to copy, not including the terminal nul.
515
516         A jabber ID is of the form "username@domain/resource", where the resource is optional.
517         Here we copy the domain portion into the supplied buffer, plus a terminal nul.  If the
518         Jabber ID is ill-formed, the results may be ill-formed or empty.
519 */
520 void jid_get_domain( const char* jid, char buf[], int size ) {
521
522         if(jid == NULL) return;
523
524         int len = strlen(jid);
525         int i;
526         int index1 = 0;
527         int index2 = 0;
528
529         for( i = 0; i!= len; i++ ) {
530                 if(jid[i] == '@')
531                         index1 = i + 1;
532                 else if(jid[i] == '/' && index1 != 0)
533                         index2 = i;
534         }
535
536         if( index1 > 0 && index2 > 0 && index2 > index1 ) {
537                 int dlen = index2 - index1;
538                 if(dlen > size) dlen = size;
539                 memcpy( buf, jid + index1, dlen );
540                 buf[dlen] = '\0';
541         }
542         else
543                 buf[ 0 ] = '\0';
544 }
545
546 /**
547         @brief Turn a transport_message into an error message.
548         @param msg Pointer to the transport_message.
549         @param type Pointer to a short string denoting the type of error.
550         @param err_code The error code.
551
552         The @a type and @a err_code parameters correspond to the "type" and "code" attributes of
553         a Jabber error element.
554 */
555 void set_msg_error( transport_message* msg, const char* type, int err_code ) {
556
557         if( !msg ) return;
558
559         if( type != NULL && *type ) {
560                 if( msg->error_type )
561                         free( msg->error_type );
562                 msg->error_type = safe_malloc( strlen(type)+1 );
563                 strcpy( msg->error_type, type );
564                 msg->error_code = err_code;
565         }
566         msg->is_error = 1;
567 }