comparison libpurple/protocols/bonjour/jabber.c @ 18761:316be7e715c6

Update the Bonjour prpl to use libxml explicitly instead of the xml_node stuff. This allows us to deal with partial reads. I also fixed issues related to starting conversations with iChat and a couple other things. Fixes #2022,#1652
author Daniel Atallah <daniel.atallah@gmail.com>
date Tue, 31 Jul 2007 23:23:25 +0000
parents b981d3c39d0b
children 6e3cd5e80420
comparison
equal deleted inserted replaced
18760:bf47d0401a96 18761:316be7e715c6
40 #include "debug.h" 40 #include "debug.h"
41 #include "notify.h" 41 #include "notify.h"
42 #include "util.h" 42 #include "util.h"
43 43
44 #include "jabber.h" 44 #include "jabber.h"
45 #include "parser.h"
45 #include "bonjour.h" 46 #include "bonjour.h"
46 #include "buddy.h" 47 #include "buddy.h"
47 48
48 #define STREAM_END "</stream:stream>" 49 #define STREAM_END "</stream:stream>"
49 /* TODO: specify version='1.0' and send stream features */ 50 /* TODO: specify version='1.0' and send stream features */
107 108
108 return "1"; 109 return "1";
109 } 110 }
110 111
111 static void 112 static void
112 _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb) 113 _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleBuddy *pb)
113 { 114 {
114 xmlnode *body_node, *html_node, *events_node; 115 xmlnode *body_node, *html_node, *events_node;
116 PurpleConnection *gc = pb->account->gc;
115 char *body, *html_body = NULL; 117 char *body, *html_body = NULL;
116 const char *ichat_balloon_color = NULL; 118 const char *ichat_balloon_color = NULL;
117 const char *ichat_text_color = NULL; 119 const char *ichat_text_color = NULL;
118 const char *font_face = NULL; 120 const char *font_face = NULL;
119 const char *font_size = NULL; 121 const char *font_size = NULL;
184 } 186 }
185 187
186 /* TODO: Should we do something with "composing_event" here? */ 188 /* TODO: Should we do something with "composing_event" here? */
187 189
188 /* Send the message to the UI */ 190 /* Send the message to the UI */
189 serv_got_im(connection, pb->name, body, 0, time(NULL)); 191 serv_got_im(gc, pb->name, body, 0, time(NULL));
190 192
191 g_free(body); 193 g_free(body);
192 g_free(html_body); 194 g_free(html_body);
193 } 195 }
194 196
216 if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0)) 218 if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
217 *(cbba->pb) = pb; 219 *(cbba->pb) = pb;
218 } 220 }
219 } 221 }
220 222
221 static gint
222 _read_data(gint socket, char **message)
223 {
224 GString *data = g_string_new("");
225 char partial_data[512];
226 gint total_message_length = 0;
227 gint partial_message_length = 0;
228
229 /* Read chunks of 512 bytes till the end of the data */
230 while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0)
231 {
232 g_string_append_len(data, partial_data, partial_message_length);
233 total_message_length += partial_message_length;
234 }
235
236 if (partial_message_length == -1)
237 {
238 if (errno != EAGAIN)
239 purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
240 if (total_message_length == 0) {
241 return -1;
242 }
243 }
244
245 *message = g_string_free(data, FALSE);
246 if (total_message_length != 0)
247 purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length);
248
249 return total_message_length;
250 }
251
252 static void 223 static void
253 _send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond) 224 _send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
254 { 225 {
255 PurpleBuddy *pb = data; 226 PurpleBuddy *pb = data;
256 BonjourBuddy *bb = pb->proto_data; 227 BonjourBuddy *bb = pb->proto_data;
301 BonjourJabberConversation *bconv = bb->conversation; 272 BonjourJabberConversation *bconv = bb->conversation;
302 273
303 /* If we're not ready to actually send, append it to the buffer */ 274 /* If we're not ready to actually send, append it to the buffer */
304 if (bconv->tx_handler != -1 275 if (bconv->tx_handler != -1
305 || bconv->connect_data != NULL 276 || bconv->connect_data != NULL
306 || !bconv->stream_started 277 || !bconv->sent_stream_start
278 || !bconv->recv_stream_start
307 || purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { 279 || purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
308 ret = -1; 280 ret = -1;
309 errno = EAGAIN; 281 errno = EAGAIN;
310 } else { 282 } else {
311 ret = send(bconv->socket, message, len, 0); 283 ret = send(bconv->socket, message, len, 0);
339 } 311 }
340 312
341 return ret; 313 return ret;
342 } 314 }
343 315
316 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {
317 if (!strcmp(packet->name, "message"))
318 _jabber_parse_and_write_message_to_ui(packet, pb);
319 else
320 purple_debug_warning("bonjour", "Unknown packet: %s\n",
321 packet->name);
322 }
323
324
344 static void 325 static void
345 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition) 326 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
346 { 327 {
347 char *message = NULL;
348 gint message_length;
349 PurpleBuddy *pb = data; 328 PurpleBuddy *pb = data;
350 PurpleAccount *account = pb->account; 329 gint len, message_length;
351 BonjourBuddy *bb = pb->proto_data; 330 static char message[4096];
352 gboolean closed_conversation = FALSE; 331
332 /*TODO: use a static buffer */
353 333
354 /* Read the data from the socket */ 334 /* Read the data from the socket */
355 if ((message_length = _read_data(socket, &message)) == -1) { 335 if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
356 /* There have been an error reading from the socket */ 336 /* There have been an error reading from the socket */
357 if (errno != EAGAIN) { 337 if (errno != EAGAIN) {
338 BonjourBuddy *bb = pb->proto_data;
339
340 purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
341
358 bonjour_jabber_close_conversation(bb->conversation); 342 bonjour_jabber_close_conversation(bb->conversation);
359 bb->conversation = NULL; 343 bb->conversation = NULL;
360 344
361 /* I guess we really don't need to notify the user. 345 /* I guess we really don't need to notify the user.
362 * If they try to send another message it'll reconnect */ 346 * If they try to send another message it'll reconnect */
363 } 347 }
364 return; 348 return;
365 } else if (message_length == 0) { /* The other end has closed the socket */ 349 } else if (len == 0) { /* The other end has closed the socket */
366 closed_conversation = TRUE; 350 purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name);
351 bonjour_jabber_stream_ended(pb);
352 return;
367 } else { 353 } else {
354 message_length = len;
368 message[message_length] = '\0'; 355 message[message_length] = '\0';
369 356
370 while (g_ascii_iscntrl(message[message_length - 1])) { 357 while (message_length > 0 && g_ascii_iscntrl(message[message_length - 1])) {
371 message[message_length - 1] = '\0'; 358 message[message_length - 1] = '\0';
372 message_length--; 359 message_length--;
373 } 360 }
374 } 361 }
375 362
376 /* 363 purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);
377 * Check that this is not the end of the conversation. This is 364
378 * using a magic string, but xmlnode won't play nice when just 365 bonjour_parser_process(pb, message, message_length);
379 * parsing an end tag 366 }
380 */ 367
381 if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) { 368 void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
382 PurpleConversation *conv; 369 BonjourBuddy *bb = pb->proto_data;
383 370 PurpleConversation *conv;
384 /* Close the socket, clear the watcher and free memory */ 371
385 bonjour_jabber_close_conversation(bb->conversation); 372 purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);
386 bb->conversation = NULL; 373
387 374 /* Close the socket, clear the watcher and free memory */
388 /* Inform the user that the conversation has been closed */ 375 bonjour_jabber_close_conversation(bb->conversation);
389 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account); 376 bb->conversation = NULL;
390 if (conv != NULL) { 377
391 char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); 378 /* Inform the user that the conversation has been closed */
392 purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); 379 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
393 g_free(tmp); 380 if (conv != NULL) {
394 } 381 char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
395 } else { 382 purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
396 xmlnode *message_node; 383 g_free(tmp);
397 384 }
398 /* Parse the message into an XMLnode for analysis */ 385 }
399 message_node = xmlnode_from_str(message, strlen(message)); 386
400 387 void bonjour_jabber_stream_started(PurpleBuddy *pb) {
401 if (message_node != NULL) { 388 BonjourBuddy *bb = pb->proto_data;
402 /* Parse the message to get the data and send to the ui */ 389 BonjourJabberConversation *bconv = bb->conversation;
403 _jabber_parse_and_write_message_to_ui(message_node, account->gc, pb); 390
404 xmlnode_free(message_node); 391 /* If the stream has been completely started, we can start doing stuff */
405 } else { 392 if (bconv->sent_stream_start && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
406 /* TODO: Deal with receiving only a partial message */ 393 /* Watch for when we can write the buffered messages */
407 } 394 bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
408 } 395 _send_data_write_cb, pb);
409 396 /* We can probably write the data right now. */
410 g_free(message); 397 _send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
398 }
399
411 } 400 }
412 401
413 struct _stream_start_data { 402 struct _stream_start_data {
414 char *msg; 403 char *msg;
415 PurpleInputFunction tx_handler_cb;
416 }; 404 };
405
417 406
418 static void 407 static void
419 _start_stream(gpointer data, gint source, PurpleInputCondition condition) 408 _start_stream(gpointer data, gint source, PurpleInputCondition condition)
420 { 409 {
421 PurpleBuddy *pb = data; 410 PurpleBuddy *pb = data;
422 BonjourBuddy *bb = pb->proto_data; 411 BonjourBuddy *bb = pb->proto_data;
423 struct _stream_start_data *ss = bb->conversation->stream_data; 412 BonjourJabberConversation *bconv = bb->conversation;
413 struct _stream_start_data *ss = bconv->stream_data;
424 int len, ret; 414 int len, ret;
425 415
426 len = strlen(ss->msg); 416 len = strlen(ss->msg);
427 417
428 /* Start Stream */ 418 /* Start Stream */
441 if (conv != NULL) 431 if (conv != NULL)
442 purple_conversation_write(conv, NULL, 432 purple_conversation_write(conv, NULL,
443 _("Unable to send the message, the conversation couldn't be started."), 433 _("Unable to send the message, the conversation couldn't be started."),
444 PURPLE_MESSAGE_SYSTEM, time(NULL)); 434 PURPLE_MESSAGE_SYSTEM, time(NULL));
445 435
446 bonjour_jabber_close_conversation(bb->conversation); 436 bonjour_jabber_close_conversation(bconv);
447 bb->conversation = NULL; 437 bb->conversation = NULL;
448 438
449 return; 439 return;
450 } 440 }
451 441
455 g_free(ss->msg); 445 g_free(ss->msg);
456 ss->msg = tmp; 446 ss->msg = tmp;
457 return; 447 return;
458 } 448 }
459 449
460 /* Stream started; process the send buffer if there is one*/
461 purple_input_remove(bb->conversation->tx_handler);
462 bb->conversation->tx_handler= -1;
463
464 bb->conversation->stream_started = TRUE;
465
466 g_free(ss->msg); 450 g_free(ss->msg);
467 g_free(ss); 451 g_free(ss);
468 bb->conversation->stream_data = NULL; 452 bconv->stream_data = NULL;
469 453
470 if (ss->tx_handler_cb) { 454 /* Stream started; process the send buffer if there is one */
471 bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE, 455 purple_input_remove(bconv->tx_handler);
472 ss->tx_handler_cb, pb); 456 bconv->tx_handler= -1;
473 /* We can probably write the data now. */ 457 bconv->sent_stream_start = TRUE;
474 (ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE); 458
475 } 459 bonjour_jabber_stream_started(pb);
460
461 }
462
463 static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
464 {
465 int ret, len;
466 char *stream_start;
467 BonjourBuddy *bb = pb->proto_data;
468
469 stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
470 purple_buddy_get_name(pb));
471 len = strlen(stream_start);
472
473 /* Start the stream */
474 ret = send(client_socket, stream_start, len, 0);
475
476 if (ret == -1 && errno == EAGAIN)
477 ret = 0;
478 else if (ret <= 0) {
479 const char *err = strerror(errno);
480
481 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
482 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
483
484 close(client_socket);
485 g_free(stream_start);
486
487 return FALSE;
488 }
489
490 /* This is unlikely to happen */
491 if (ret < len) {
492 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
493 ss->msg = g_strdup(stream_start + ret);
494 bb->conversation->stream_data = ss;
495 /* Finish sending the stream start */
496 bb->conversation->tx_handler = purple_input_add(client_socket,
497 PURPLE_INPUT_WRITE, _start_stream, pb);
498 } else
499 bb->conversation->sent_stream_start = TRUE;
500
501 g_free(stream_start);
502
503 /* setup the parser fresh for each stream */
504 bonjour_parser_setup(bb->conversation);
505
506 bb->conversation->socket = client_socket;
507 bb->conversation->rx_handler = purple_input_add(client_socket,
508 PURPLE_INPUT_READ, _client_socket_handler, pb);
509
510 return TRUE;
476 } 511 }
477 512
478 static void 513 static void
479 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition) 514 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
480 { 515 {
496 531
497 fcntl(client_socket, F_SETFL, O_NONBLOCK); 532 fcntl(client_socket, F_SETFL, O_NONBLOCK);
498 533
499 /* Look for the buddy that has opened the conversation and fill information */ 534 /* Look for the buddy that has opened the conversation and fill information */
500 address_text = inet_ntoa(their_addr.sin_addr); 535 address_text = inet_ntoa(their_addr.sin_addr);
536 purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text);
501 cbba = g_new0(struct _check_buddy_by_address_t, 1); 537 cbba = g_new0(struct _check_buddy_by_address_t, 1);
502 cbba->address = address_text; 538 cbba->address = address_text;
503 cbba->pb = &pb; 539 cbba->pb = &pb;
504 cbba->bj = data; 540 cbba->bj = data;
505 g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba); 541 g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
513 bb = pb->proto_data; 549 bb = pb->proto_data;
514 550
515 /* Check if the conversation has been previously started */ 551 /* Check if the conversation has been previously started */
516 if (bb->conversation == NULL) 552 if (bb->conversation == NULL)
517 { 553 {
518 int ret, len; 554 bb->conversation = bonjour_jabber_conv_new();
519 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), 555
520 purple_buddy_get_name(pb)); 556 if (!bonjour_jabber_stream_init(pb, client_socket)) {
521
522 len = strlen(stream_start);
523
524 /* Start the stream */
525 ret = send(client_socket, stream_start, len, 0);
526
527 if (ret == -1 && errno == EAGAIN)
528 ret = 0;
529 else if (ret <= 0) {
530 const char *err = strerror(errno);
531
532 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
533 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
534
535 close(client_socket); 557 close(client_socket);
536 g_free(stream_start);
537
538 return; 558 return;
539 } 559 }
540 560
541 bb->conversation = bonjour_jabber_conv_new();
542 bb->conversation->socket = client_socket;
543 bb->conversation->rx_handler = purple_input_add(client_socket,
544 PURPLE_INPUT_READ, _client_socket_handler, pb);
545
546 /* This is unlikely to happen */
547 if (ret < len) {
548 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
549 ss->msg = g_strdup(stream_start + ret);
550 ss->tx_handler_cb = NULL; /* We have nothing to write yet */
551 bb->conversation->stream_data = ss;
552 /* Finish sending the stream start */
553 bb->conversation->tx_handler = purple_input_add(client_socket,
554 PURPLE_INPUT_WRITE, _start_stream, pb);
555 } else {
556 bb->conversation->stream_started = TRUE;
557 }
558
559 g_free(stream_start);
560 } else { 561 } else {
562 purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
561 close(client_socket); 563 close(client_socket);
562 } 564 }
563 } 565 }
564 566
565 gint 567 gint
637 static void 639 static void
638 _connected_to_buddy(gpointer data, gint source, const gchar *error) 640 _connected_to_buddy(gpointer data, gint source, const gchar *error)
639 { 641 {
640 PurpleBuddy *pb = data; 642 PurpleBuddy *pb = data;
641 BonjourBuddy *bb = pb->proto_data; 643 BonjourBuddy *bb = pb->proto_data;
642 int len, ret;
643 char *stream_start;
644 644
645 bb->conversation->connect_data = NULL; 645 bb->conversation->connect_data = NULL;
646 646
647 if (source < 0) { 647 if (source < 0) {
648 PurpleConversation *conv; 648 PurpleConversation *conv;
659 bonjour_jabber_close_conversation(bb->conversation); 659 bonjour_jabber_close_conversation(bb->conversation);
660 bb->conversation = NULL; 660 bb->conversation = NULL;
661 return; 661 return;
662 } 662 }
663 663
664 stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb)); 664 if (!bonjour_jabber_stream_init(pb, source)) {
665 len = strlen(stream_start);
666
667 /* Start the stream and send queued messages */
668 ret = send(source, stream_start, len, 0);
669
670 if (ret == -1 && errno == EAGAIN)
671 ret = 0;
672 else if (ret <= 0) {
673 const char *err = strerror(errno); 665 const char *err = strerror(errno);
674 PurpleConversation *conv; 666 PurpleConversation *conv;
675 667
676 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", 668 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
677 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)"); 669 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
683 PURPLE_MESSAGE_SYSTEM, time(NULL)); 675 PURPLE_MESSAGE_SYSTEM, time(NULL));
684 676
685 close(source); 677 close(source);
686 bonjour_jabber_close_conversation(bb->conversation); 678 bonjour_jabber_close_conversation(bb->conversation);
687 bb->conversation = NULL; 679 bb->conversation = NULL;
688 680 return;
689 g_free(stream_start); 681 }
690
691 return;
692 }
693
694 bb->conversation->socket = source;
695 bb->conversation->rx_handler = purple_input_add(source,
696 PURPLE_INPUT_READ, _client_socket_handler, pb);
697
698 /* This is unlikely to happen */
699 if (ret < len) {
700 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
701 ss->msg = g_strdup(stream_start + ret);
702 ss->tx_handler_cb = _send_data_write_cb;
703 bb->conversation->stream_data = ss;
704 /* Finish sending the stream start */
705 bb->conversation->tx_handler = purple_input_add(source,
706 PURPLE_INPUT_WRITE, _start_stream, pb);
707 }
708 /* Process the send buffer */
709 else {
710 bb->conversation->stream_started = TRUE;
711 /* Watch for when we can write the buffered messages */
712 bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
713 _send_data_write_cb, pb);
714 /* We can probably write the data now. */
715 _send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
716 }
717
718 g_free(stream_start);
719 } 682 }
720 683
721 int 684 int
722 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) 685 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
723 { 686 {
807 if (bconv != NULL) 770 if (bconv != NULL)
808 { 771 {
809 /* Close the socket and remove the watcher */ 772 /* Close the socket and remove the watcher */
810 if (bconv->socket >= 0) { 773 if (bconv->socket >= 0) {
811 /* Send the end of the stream to the other end of the conversation */ 774 /* Send the end of the stream to the other end of the conversation */
812 if (bconv->stream_started) 775 if (bconv->sent_stream_start)
813 send(bconv->socket, STREAM_END, strlen(STREAM_END), 0); 776 send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
814 /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ 777 /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
815 close(bconv->socket); 778 close(bconv->socket);
816 } 779 }
817 if (bconv->rx_handler != -1) 780 if (bconv->rx_handler != -1)
826 if (bconv->stream_data != NULL) { 789 if (bconv->stream_data != NULL) {
827 struct _stream_start_data *ss = bconv->stream_data; 790 struct _stream_start_data *ss = bconv->stream_data;
828 g_free(ss->msg); 791 g_free(ss->msg);
829 g_free(ss); 792 g_free(ss);
830 } 793 }
794
795 if (bconv->context != NULL)
796 bonjour_parser_setup(bconv);
797
831 g_free(bconv); 798 g_free(bconv);
832 } 799 }
833 } 800 }
834 801
835 void 802 void