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