1 #include <opensrf/transport_message.h>
4 @file transport_message.c
5 @brief Collection of routines for managing transport_messages.
7 These routines are largely concerned with the conversion of XML to transport_messages,
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.
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.
26 The calling code is responsible for freeing the transport_message by calling message_free().
28 transport_message* message_init( const char* body, const char* subject,
29 const char* thread, const char* recipient, const char* sender ) {
31 transport_message* msg = safe_malloc( sizeof(transport_message) );
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 = ""; }
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);
45 if( msg->body == NULL || msg->thread == NULL ||
46 msg->subject == NULL || msg->recipient == NULL ||
47 msg->sender == NULL ) {
49 osrfLogError(OSRF_LOG_MARK, "message_init(): Out of Memory" );
53 free( msg->recipient );
59 msg->router_from = NULL;
60 msg->router_to = NULL;
61 msg->router_class = NULL;
62 msg->router_command = NULL;
65 msg->error_type = NULL;
76 @brief Translate an XML string into a transport_message.
77 @param msg_xml Pointer to a <message> element as passed by Jabber.
78 @return Pointer to a newly created transport_message.
80 Do @em not populate the following members:
86 The calling code is responsible for freeing the transport_message by calling message_free().
88 transport_message* new_message_from_xml( const char* msg_xml ) {
90 if( msg_xml == NULL || *msg_xml == '\0' )
93 transport_message* new_msg = safe_malloc( sizeof(transport_message) );
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;
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);
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" );
131 message_set_osrf_xid( new_msg, (char*) osrf_xid);
136 new_msg->sender = strdup((const char*)router_from);
139 new_msg->sender = strdup((const char*)sender);
145 new_msg->recipient = strdup((const char*)recipient);
150 new_msg->subject = strdup((const char*)subject);
155 new_msg->thread = strdup((const char*)thread);
160 new_msg->router_from = strdup((const char*)router_from);
161 xmlFree(router_from);
165 new_msg->router_to = strdup((const char*)router_to);
170 new_msg->router_class = strdup((const char*)router_class);
171 xmlFree(router_class);
175 new_msg->router_command = strdup((const char*)router_command);
176 xmlFree(router_command);
180 if(strcmp((const char*) broadcast,"0") )
181 new_msg->broadcast = 1;
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 ) {
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 );
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 );
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" );
209 message_set_osrf_xid( new_msg, (char*) osrf_xid);
214 new_msg->sender = strdup((const char*)router_from);
217 new_msg->sender = strdup((const char*)sender);
223 new_msg->router_from = strdup((const char*)router_from);
224 xmlFree(router_from);
228 new_msg->router_to = strdup((const char*)router_to);
233 new_msg->router_class = strdup((const char*)router_class);
234 xmlFree(router_class);
238 new_msg->router_command = strdup((const char*)router_command);
239 xmlFree(router_command);
243 if(strcmp((const char*) broadcast,"0") )
244 new_msg->broadcast = 1;
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 );
254 search_node = search_node->next;
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("");
264 /* Convert the XML document back into a string, and store it. */
265 new_msg->msg_xml = xmlDocToString(msg_doc, 0);
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.
277 If @a osrf_xid is NULL, populate with an empty string.
279 See also message_set_router_info().
281 void message_set_osrf_xid( transport_message* msg, const char* osrf_xid ) {
283 if( msg->osrf_xid ) free( msg->osrf_xid );
284 msg->osrf_xid = strdup( osrf_xid ? osrf_xid : "" );
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
297 If any of the pointer pararmeters is NULL (other than @a msg), populate the
298 corresponding member with an empty string.
300 See also osrf_set_xid().
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 ) {
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 );
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;
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" );
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.
333 int message_free( transport_message* msg ){
334 if( msg == NULL ) { return 0; }
339 free(msg->recipient);
341 free(msg->router_from);
342 free(msg->router_to);
343 free(msg->router_class);
344 free(msg->router_command);
346 if( msg->error_type != NULL ) free(msg->error_type);
347 if( msg->msg_xml != NULL ) free(msg->msg_xml);
354 @brief Build a <message> 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.
358 If msg_xml is already populated, keep it, and return immediately.
360 The contents of the <message> element come from various members of the
361 transport_message. Store the resulting string as the msg_xml member.
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.
367 int message_prepare_xml( transport_message* msg ) {
370 if( msg->msg_xml ) return 1; /* already done */
372 xmlNodePtr message_node;
373 xmlNodePtr body_node;
374 xmlNodePtr thread_node;
375 xmlNodePtr opensrf_node;
376 xmlNodePtr subject_node;
377 xmlNodePtr error_node;
381 xmlKeepBlanksDefault(0);
383 doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
384 message_node = xmlDocGetRootElement(doc);
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 );
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 );
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 );
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 );
408 xmlAddChild(message_node, opensrf_node);
410 if( msg->broadcast ) {
411 xmlNewProp( opensrf_node, BAD_CAST "broadcast", BAD_CAST "1" );
414 /* Now add nodes where appropriate */
415 char* body = msg->body;
416 char* subject = msg->subject;
417 char* thread = msg->thread;
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);
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 );
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 );
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)));
445 xmlBufferFree(xmlbuf);
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.
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.
463 void jid_get_username( const char* jid, char buf[], int size ) {
465 if( jid == NULL || buf == NULL || size <= 0 ) { return; }
469 /* find the @ and return whatever is in front of it */
470 int len = strlen( jid );
472 for( i = 0; i != len; i++ ) {
473 if( jid[i] == '@' ) {
474 if(i > size) i = size;
475 memcpy( buf, jid, i );
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.
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.
493 void jid_get_resource( const char* jid, char buf[], int size) {
494 if( jid == NULL || buf == NULL || size <= 0 ) { return; }
496 // Find the last slash, if any
497 const char* start = strrchr( jid, '/' );
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 );
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.
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.
520 void jid_get_domain( const char* jid, char buf[], int size ) {
522 if(jid == NULL) return;
524 int len = strlen(jid);
529 for( i = 0; i!= len; i++ ) {
532 else if(jid[i] == '/' && index1 != 0)
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 );
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.
552 The @a type and @a err_code parameters correspond to the "type" and "code" attributes of
553 a Jabber error element.
555 void set_msg_error( transport_message* msg, const char* type, int err_code ) {
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;