Eliminate a potential (if unlikely) memory leak: Free a
[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* broadcast      = xmlGetProp( root, BAD_CAST "broadcast" );
127         xmlChar* osrf_xid       = xmlGetProp( root, BAD_CAST "osrf_xid" );
128
129         if( osrf_xid ) {
130                 message_set_osrf_xid( new_msg, (char*) osrf_xid);
131                 xmlFree(osrf_xid);
132         }
133
134         if( router_from ) {
135                 new_msg->sender     = strdup((const char*)router_from);
136         } else {
137                 if( sender ) {
138                         new_msg->sender = strdup((const char*)sender);
139                         xmlFree(sender);
140                 }
141         }
142
143         if( recipient ) {
144                 new_msg->recipient  = strdup((const char*)recipient);
145                 xmlFree(recipient);
146         }
147
148         if(subject){
149                 new_msg->subject    = strdup((const char*)subject);
150                 xmlFree(subject);
151         }
152
153         if(thread) {
154                 new_msg->thread     = strdup((const char*)thread);
155                 xmlFree(thread);
156         }
157
158         if(router_from) {
159                 new_msg->router_from = strdup((const char*)router_from);
160                 xmlFree(router_from);
161         }
162
163         if(router_to) {
164                 new_msg->router_to  = strdup((const char*)router_to);
165                 xmlFree(router_to);
166         }
167
168         if(router_class) {
169                 new_msg->router_class = strdup((const char*)router_class);
170                 xmlFree(router_class);
171         }
172
173         if(broadcast) {
174                 if(strcmp((const char*) broadcast,"0") )
175                         new_msg->broadcast = 1;
176                 xmlFree(broadcast);
177         }
178
179         /* Within the message element, find the child nodes for "thread", "subject", and */
180         /* "body".  Extract their textual content into the corresponding members. */
181         xmlNodePtr search_node = root->children;
182         while( search_node != NULL ) {
183
184                 if( ! strcmp( (const char*) search_node->name, "thread" ) ) {
185                         if( search_node->children && search_node->children->content )
186                                 new_msg->thread = strdup( (const char*) search_node->children->content );
187                 }
188
189                 if( ! strcmp( (const char*) search_node->name, "subject" ) ) {
190                         if( search_node->children && search_node->children->content )
191                                 new_msg->subject = strdup( (const char*) search_node->children->content );
192                 }
193
194                 if( ! strcmp( (const char*) search_node->name, "body" ) ) {
195                         if( search_node->children && search_node->children->content )
196                                 new_msg->body = strdup((const char*) search_node->children->content );
197                 }
198
199                 search_node = search_node->next;
200         }
201
202         if( new_msg->thread == NULL )
203                 new_msg->thread = strdup("");
204         if( new_msg->subject == NULL )
205                 new_msg->subject = strdup("");
206         if( new_msg->body == NULL )
207                 new_msg->body = strdup("");
208
209         /* Convert the XML document back into a string, and store it. */
210         new_msg->msg_xml = xmlDocToString(msg_doc, 0);
211         xmlFreeDoc(msg_doc);
212         xmlCleanupParser();
213
214         return new_msg;
215 }
216
217 /**
218         @brief Populate the osrf_xid (an OSRF extension) of a transport_message.
219         @param msg Pointer to the transport_message.
220         @param osrf_xid Value of the "osrf_xid" attribute of a message stanza.
221
222         If @a osrf_xid is NULL, populate with an empty string.
223
224         See also message_set_router_info().
225 */
226 void message_set_osrf_xid( transport_message* msg, const char* osrf_xid ) {
227         if( msg ) {
228                 if( msg->osrf_xid ) free( msg->osrf_xid );
229                 msg->osrf_xid = strdup( osrf_xid ? osrf_xid : "" );
230         }
231 }
232
233 /**
234         @brief Populate some OSRF extensions to XMPP in a transport_message.
235         @param msg Pointer to the transport_message to be populated.
236         @param router_from Value of "router_from" attribute in message stanza
237         @param router_to Value of "router_to" attribute in message stanza
238         @param router_class Value of "router_class" attribute in message stanza
239         @param router_command Value of "router_command" attribute in message stanza
240         @param broadcast_enabled Value of "broadcast" attribute in message stanza
241
242         If any of the pointer pararmeters is NULL (other than @a msg), populate the
243         corresponding member with an empty string.
244
245         See also osrf_set_xid().
246 */
247 void message_set_router_info( transport_message* msg, const char* router_from,
248                 const char* router_to, const char* router_class, const char* router_command,
249                 int broadcast_enabled ) {
250
251         if( msg ) {
252
253                 /* free old values, if any */
254                 if( msg->router_from    ) free( msg->router_from );
255                 if( msg->router_to      ) free( msg->router_to );
256                 if( msg->router_class   ) free( msg->router_class );
257                 if( msg->router_command ) free( msg->router_command );
258
259                 /* install new values */
260                 msg->router_from     = strdup( router_from     ? router_from     : "" );
261                 msg->router_to       = strdup( router_to       ? router_to       : "" );
262                 msg->router_class    = strdup( router_class    ? router_class    : "" );
263                 msg->router_command  = strdup( router_command  ? router_command  : "" );
264                 msg->broadcast = broadcast_enabled;
265
266                 if( msg->router_from == NULL || msg->router_to == NULL ||
267                                 msg->router_class == NULL || msg->router_command == NULL )
268                         osrfLogError(OSRF_LOG_MARK,  "message_set_router_info(): Out of Memory" );
269         }
270 }
271
272
273 /**
274         @brief Free a transport_message and all the memory it owns.
275         @param msg Pointer to the transport_message to be destroyed.
276         @return 1 if successful, or 0 upon error.  The only error condition is if @a msg is NULL.
277 */
278 int message_free( transport_message* msg ){
279         if( msg == NULL ) { return 0; }
280
281         free(msg->body);
282         free(msg->thread);
283         free(msg->subject);
284         free(msg->recipient);
285         free(msg->sender);
286         free(msg->router_from);
287         free(msg->router_to);
288         free(msg->router_class);
289         free(msg->router_command);
290         free(msg->osrf_xid);
291         if( msg->error_type != NULL ) free(msg->error_type);
292         if( msg->msg_xml != NULL ) free(msg->msg_xml);
293         free(msg);
294         return 1;
295 }
296
297
298 /**
299         @brief Build a &lt;message&gt; element and store it as a string in the msg_xml member.
300         @param msg Pointer to a transport_message.
301         @return 1 if successful, or 0 if not.  The only error condition is if @a msg is NULL.
302
303         If msg_xml is already populated, keep it, and return immediately.
304
305         The contents of the &lt;message&gt; element come from various members of the
306         transport_message.  Store the resulting string as the msg_xml member.
307
308         To build the XML we first build a DOM structure, and then export it to a string.  That
309         way we can let the XML library worry about replacing certain characters with
310         character entity references -- not a trivial task when UTF-8 characters may be present.
311 */
312 int message_prepare_xml( transport_message* msg ) {
313
314         if( !msg ) return 0;
315         if( msg->msg_xml ) return 1;   /* already done */
316
317         xmlNodePtr  message_node;
318         xmlNodePtr  body_node;
319         xmlNodePtr  thread_node;
320         xmlNodePtr  subject_node;
321         xmlNodePtr  error_node;
322
323         xmlDocPtr   doc;
324
325         xmlKeepBlanksDefault(0);
326
327         doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
328         message_node = xmlDocGetRootElement(doc);
329
330         if( msg->is_error ) {
331                 error_node = xmlNewChild(message_node, NULL, BAD_CAST "error" , NULL );
332                 xmlAddChild( message_node, error_node );
333                 xmlNewProp( error_node, BAD_CAST "type", BAD_CAST msg->error_type );
334                 char code_buf[16];
335                 osrf_clearbuf( code_buf, sizeof(code_buf));
336                 sprintf(code_buf, "%d", msg->error_code );
337                 xmlNewProp( error_node, BAD_CAST "code", BAD_CAST code_buf  );
338         }
339
340         /* set from and to */
341         xmlNewProp( message_node, BAD_CAST "to", BAD_CAST msg->recipient );
342         xmlNewProp( message_node, BAD_CAST "from", BAD_CAST msg->sender );
343         xmlNewProp( message_node, BAD_CAST "router_from", BAD_CAST msg->router_from );
344         xmlNewProp( message_node, BAD_CAST "router_to", BAD_CAST msg->router_to );
345         xmlNewProp( message_node, BAD_CAST "router_class", BAD_CAST msg->router_class );
346         xmlNewProp( message_node, BAD_CAST "router_command", BAD_CAST msg->router_command );
347         xmlNewProp( message_node, BAD_CAST "osrf_xid", BAD_CAST msg->osrf_xid );
348
349         if( msg->broadcast )
350                 xmlNewProp( message_node, BAD_CAST "broadcast", BAD_CAST "1" );
351
352         /* Now add nodes where appropriate */
353         char* body      = msg->body;
354         char* subject   = msg->subject;
355         char* thread    = msg->thread;
356
357         if( thread && *thread ) {
358                 thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", NULL );
359                 xmlNodePtr txt = xmlNewText((xmlChar*) thread);
360                 xmlAddChild(thread_node, txt);
361                 xmlAddChild(message_node, thread_node);
362         }
363
364         if( subject && *subject ) {
365                 subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", NULL );
366                 xmlNodePtr txt = xmlNewText((xmlChar*) subject);
367                 xmlAddChild(subject_node, txt);
368                 xmlAddChild( message_node, subject_node );
369         }
370
371         if( body && *body ) {
372                 body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", NULL);
373                 xmlNodePtr txt = xmlNewText((xmlChar*) body);
374                 xmlAddChild(body_node, txt);
375                 xmlAddChild( message_node, body_node );
376         }
377
378         // Export the xmlDoc to a string
379         xmlBufferPtr xmlbuf = xmlBufferCreate();
380         xmlNodeDump( xmlbuf, doc, xmlDocGetRootElement(doc), 0, 0);
381         msg->msg_xml = strdup((const char*) (xmlBufferContent(xmlbuf)));
382
383         xmlBufferFree(xmlbuf);
384         xmlFreeDoc( doc );
385         xmlCleanupParser();
386
387         return 1;
388 }
389
390
391 /**
392         @brief Extract the username from a Jabber ID.
393         @param jid Pointer to the Jabber ID.
394         @param buf Pointer to a receiving buffer supplied by the caller.
395         @param size Maximum number of characters to copy, not including the terminal nul.
396
397         A jabber ID is of the form "username@domain/resource", where the resource is optional.
398         Here we copy the username portion into the supplied buffer, plus a terminal nul.  If there
399         is no "@" character, leave the buffer as an empty string.
400 */
401 void jid_get_username( const char* jid, char buf[], int size ) {
402
403         if( jid == NULL || buf == NULL || size <= 0 ) { return; }
404
405         buf[ 0 ] = '\0';
406
407         /* find the @ and return whatever is in front of it */
408         int len = strlen( jid );
409         int i;
410         for( i = 0; i != len; i++ ) {
411                 if( jid[i] == '@' ) {
412                         if(i > size)  i = size;
413                         memcpy( buf, jid, i );
414                         buf[i] = '\0';
415                         return;
416                 }
417         }
418 }
419
420
421 /**
422         @brief Extract the resource from a Jabber ID.
423         @param jid Pointer to the Jabber ID.
424         @param buf Pointer to a receiving buffer supplied by the caller.
425         @param size Maximum number of characters to copy, not including the terminal nul.
426
427         A jabber ID is of the form "username@domain/resource", where the resource is optional.
428         Here we copy the resource portion, if present into the supplied buffer, plus a terminal nul.
429         If there is no resource, leave the buffer as an empty string.
430 */
431 void jid_get_resource( const char* jid, char buf[], int size)  {
432         if( jid == NULL || buf == NULL || size <= 0 ) { return; }
433
434         // Find the last slash, if any
435         const char* start = strrchr( jid, '/' );
436         if( start ) {
437
438                 // Copy the text beyond the slash, up to a maximum size
439                 size_t len = strlen( ++start );
440                 if( len > size ) len = size;
441                 memcpy( buf, start, len );
442                 buf[ len ] = '\0';
443         }
444         else
445                 buf[ 0 ] = '\0';
446 }
447
448 /**
449         @brief Extract the domain from a Jabber ID.
450         @param jid Pointer to the Jabber ID.
451         @param buf Pointer to a receiving buffer supplied by the caller.
452         @param size Maximum number of characters to copy, not including the terminal nul.
453
454         A jabber ID is of the form "username@domain/resource", where the resource is optional.
455         Here we copy the domain portion into the supplied buffer, plus a terminal nul.  If the
456         Jabber ID is ill-formed, the results may be ill-formed or empty.
457 */
458 void jid_get_domain( const char* jid, char buf[], int size ) {
459
460         if(jid == NULL) return;
461
462         int len = strlen(jid);
463         int i;
464         int index1 = 0;
465         int index2 = 0;
466
467         for( i = 0; i!= len; i++ ) {
468                 if(jid[i] == '@')
469                         index1 = i + 1;
470                 else if(jid[i] == '/' && index1 != 0)
471                         index2 = i;
472         }
473
474         if( index1 > 0 && index2 > 0 && index2 > index1 ) {
475                 int dlen = index2 - index1;
476                 if(dlen > size) dlen = size;
477                 memcpy( buf, jid + index1, dlen );
478                 buf[dlen] = '\0';
479         }
480         else
481                 buf[ 0 ] = '\0';
482 }
483
484 /**
485         @brief Turn a transport_message into an error message.
486         @param msg Pointer to the transport_message.
487         @param type Pointer to a short string denoting the type of error.
488         @param err_code The error code.
489
490         The @a type and @a err_code parameters correspond to the "type" and "code" attributes of
491         a Jabber error element.
492 */
493 void set_msg_error( transport_message* msg, const char* type, int err_code ) {
494
495         if( !msg ) return;
496
497         if( type != NULL && *type ) {
498                 if( msg->error_type )
499                         free( msg->error_type );
500                 msg->error_type = safe_malloc( strlen(type)+1 );
501                 strcpy( msg->error_type, type );
502                 msg->error_code = err_code;
503         }
504         msg->is_error = 1;
505 }