2 * Purple's oscar protocol plugin
3 * This file is the legal property of its developers.
4 * Please see the AUTHORS file distributed alongside this file.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 /* From the oscar PRPL */
27 #include "conversation.h"
28 #include "image-store.h"
31 #define DIRECTIM_MAX_FILESIZE 52428800
34 * Free any ODC related data and print a message to the conversation
35 * window based on conn->disconnect_reason.
38 peer_odc_close(PeerConnection
*conn
)
42 if (conn
->disconnect_reason
== OSCAR_DISCONNECT_REMOTE_CLOSED
)
43 tmp
= g_strdup(_("The remote user has closed the connection."));
44 else if (conn
->disconnect_reason
== OSCAR_DISCONNECT_REMOTE_REFUSED
)
45 tmp
= g_strdup(_("The remote user has declined your request."));
46 else if (conn
->disconnect_reason
== OSCAR_DISCONNECT_LOST_CONNECTION
)
47 tmp
= g_strdup_printf(_("Lost connection with the remote user:<br>%s"),
49 else if (conn
->disconnect_reason
== OSCAR_DISCONNECT_INVALID_DATA
)
50 tmp
= g_strdup(_("Received invalid data on connection with remote user."));
51 else if (conn
->disconnect_reason
== OSCAR_DISCONNECT_COULD_NOT_CONNECT
)
52 tmp
= g_strdup(_("Unable to establish a connection with the remote user."));
55 * We shouldn't print a message for some disconnect_reasons.
56 * Like OSCAR_DISCONNECT_LOCAL_CLOSED.
62 PurpleAccount
*account
;
63 PurpleIMConversation
*im
;
65 account
= purple_connection_get_account(conn
->od
->gc
);
66 im
= purple_im_conversation_new(account
, conn
->bn
);
67 purple_conversation_write_system_message(
68 PURPLE_CONVERSATION(im
), tmp
, 0);
72 if (conn
->frame
!= NULL
)
76 g_free(frame
->payload
.data
);
82 * Write the given OdcFrame to a ByteStream and send it out
83 * on the established PeerConnection.
86 peer_odc_send(PeerConnection
*conn
, OdcFrame
*frame
)
88 PurpleAccount
*account
;
93 purple_debug_info("oscar", "Outgoing ODC frame to %s with "
94 "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT
"\n",
95 conn
->bn
, frame
->type
, frame
->flags
, frame
->payload
.len
);
97 account
= purple_connection_get_account(conn
->od
->gc
);
98 username
= purple_account_get_username(account
);
99 memcpy(frame
->bn
, username
, strlen(username
));
100 memcpy(frame
->cookie
, conn
->cookie
, 8);
103 byte_stream_new(&bs
, length
+ frame
->payload
.len
);
104 byte_stream_putraw(&bs
, conn
->magic
, 4);
105 byte_stream_put16(&bs
, length
);
106 byte_stream_put16(&bs
, frame
->type
);
107 byte_stream_put16(&bs
, frame
->subtype
);
108 byte_stream_put16(&bs
, 0x0000);
109 byte_stream_putraw(&bs
, frame
->cookie
, 8);
110 byte_stream_put16(&bs
, 0x0000);
111 byte_stream_put16(&bs
, 0x0000);
112 byte_stream_put16(&bs
, 0x0000);
113 byte_stream_put16(&bs
, 0x0000);
114 byte_stream_put32(&bs
, frame
->payload
.len
);
115 byte_stream_put16(&bs
, frame
->encoding
);
116 byte_stream_put16(&bs
, 0x0000);
117 byte_stream_put16(&bs
, 0x0000);
118 byte_stream_put16(&bs
, frame
->flags
);
119 byte_stream_put16(&bs
, 0x0000);
120 byte_stream_put16(&bs
, 0x0000);
121 byte_stream_putraw(&bs
, frame
->bn
, 32);
122 byte_stream_putraw(&bs
, frame
->payload
.data
, frame
->payload
.len
);
124 peer_connection_send(conn
, &bs
);
126 byte_stream_destroy(&bs
);
130 * Send a very basic ODC frame (which contains the cookie) so that the
131 * remote user can verify that we are the person they were expecting.
132 * If we made an outgoing connection to then remote user, then we send
133 * this immediately. If the remote user connected to us, then we wait
134 * for the other person to send this to us, then we send one to them.
137 peer_odc_send_cookie(PeerConnection
*conn
)
141 memset(&frame
, 0, sizeof(OdcFrame
));
143 frame
.subtype
= 0x0006;
144 frame
.flags
= 0x0060; /* Maybe this means "we're sending the cookie"? */
146 peer_odc_send(conn
, &frame
);
150 * Send client-to-client typing notification over an established direct connection.
153 peer_odc_send_typing(PeerConnection
*conn
, PurpleIMTypingState typing
)
157 memset(&frame
, 0, sizeof(OdcFrame
));
159 frame
.subtype
= 0x0006;
160 if (typing
== PURPLE_IM_TYPING
)
161 frame
.flags
= 0x0002 | 0x0008;
162 else if (typing
== PURPLE_IM_TYPED
)
163 frame
.flags
= 0x0002 | 0x0004;
165 frame
.flags
= 0x0002;
167 peer_odc_send(conn
, &frame
);
171 * Send client-to-client IM over an established direct connection.
172 * To send a direct IM, call this just like you would aim_send_im.
174 * @param conn The already-connected ODC connection.
175 * @param msg Null-terminated string to send.
176 * @param len The length of the message to send, including binary data.
177 * @param encoding See the AIM_CHARSET_* defines in oscar.h
178 * @param autoreply TRUE if this is any auto-reply.
181 peer_odc_send_im(PeerConnection
*conn
, const char *msg
, int len
, int encoding
, gboolean autoreply
)
185 g_return_if_fail(msg
!= NULL
);
186 g_return_if_fail(len
> 0);
188 memset(&frame
, 0, sizeof(OdcFrame
));
190 frame
.subtype
= 0x0006;
191 frame
.payload
.len
= len
;
192 frame
.encoding
= encoding
;
193 frame
.flags
= autoreply
;
194 byte_stream_new(&frame
.payload
, len
);
195 byte_stream_putraw(&frame
.payload
, (guint8
*)msg
, len
);
197 peer_odc_send(conn
, &frame
);
199 g_free(frame
.payload
.data
);
209 * This is called after a direct IM has been received in its entirety. This
210 * function is passed a long chunk of data which contains the IM with any
211 * data chunks (images) appended to it.
213 * This function rips out all the data chunks and creates a PurpleImage (?) for
214 * each one. In order to do this, it first goes through the IM and takes
215 * out all the IMG tags. When doing so, it rewrites the original IMG tag
216 * with one compatible with the PurpleImage (?) code. For each one, we
217 * then read in chunks of data from the end of the message and actually
218 * create the img store using the given data.
220 * For somewhat easy reference, here's a sample message
221 * (with added whitespace):
223 * <HTML><BODY BGCOLOR="#ffffff">
225 * This is a really stupid picture:<BR>
226 * <IMG SRC="Sample.jpg" ID="1" WIDTH="283" HEIGHT="212" DATASIZE="9894"><BR>
228 * Here is another one:<BR>
229 * <IMG SRC="Soap Bubbles.bmp" ID="2" WIDTH="256" HEIGHT="256" DATASIZE="65978">
233 * <DATA ID="1" SIZE="9894">datadatadatadata</DATA>
234 * <DATA ID="2" SIZE="65978">datadatadatadata</DATA>
238 peer_odc_handle_payload(PeerConnection
*conn
, const char *msg
, size_t len
, int encoding
, gboolean autoreply
)
240 PurpleConnection
*gc
;
241 PurpleAccount
*account
;
242 const char *msgend
, *binary_start
, *dataend
;
243 const char *tmp
, *start
, *end
, *idstr
, *src
, *sizestr
;
245 GHashTable
*embedded_datas
;
246 struct embedded_data
*embedded_data
;
247 gboolean any_images
= FALSE
;
250 PurpleMessageFlags imflags
;
253 account
= purple_connection_get_account(gc
);
258 * Create a hash table containing references to each embedded
259 * data chunk. The key is the "ID" and the value is an
260 * embedded_data struct.
262 embedded_datas
= g_hash_table_new_full(g_direct_hash
,
263 g_direct_equal
, NULL
, g_free
);
266 * Create an index of any binary chunks. If we run into any
267 * problems while parsing the binary data section then we stop
268 * parsing it, and the local user will see broken image icons.
270 binary_start
= purple_strcasestr(msg
, "<binary>");
271 if (binary_start
== NULL
)
275 msgend
= binary_start
;
277 /* Move our pointer to immediately after the <binary> tag */
278 tmp
= binary_start
+ 8;
280 /* The embedded binary markup has a mimimum length of 29 bytes */
281 while ((tmp
+ 29 <= dataend
) &&
282 purple_markup_find_tag("data", tmp
, &start
, &tmp
, &attributes
))
287 /* Move the binary pointer from ">" to the start of the data */
291 idstr
= g_datalist_get_data(&attributes
, "id");
294 g_datalist_clear(&attributes
);
300 sizestr
= g_datalist_get_data(&attributes
, "size");
303 g_datalist_clear(&attributes
);
306 size
= atol(sizestr
);
308 g_datalist_clear(&attributes
);
310 if ((size
> 0) && (tmp
+ size
> dataend
))
313 embedded_data
= g_new(struct embedded_data
, 1);
314 embedded_data
->size
= size
;
315 embedded_data
->data
= (const guint8
*)tmp
;
318 /* Skip past the closing </data> tag */
319 if (g_ascii_strncasecmp(tmp
, "</data>", 7))
321 g_free(embedded_data
);
326 g_hash_table_insert(embedded_datas
,
327 GINT_TO_POINTER(id
), embedded_data
);
332 * Loop through the message, replacing OSCAR img tags with the
333 * equivalent Purple img tag.
335 newmsg
= g_string_new("");
337 while (purple_markup_find_tag("img", tmp
, &start
, &end
, &attributes
))
339 PurpleImage
*image
= NULL
;
341 idstr
= g_datalist_get_data(&attributes
, "id");
342 src
= g_datalist_get_data(&attributes
, "src");
343 sizestr
= g_datalist_get_data(&attributes
, "datasize");
345 if ((idstr
!= NULL
) && (src
!= NULL
) && (sizestr
!= NULL
))
351 size
= atol(sizestr
);
352 embedded_data
= g_hash_table_lookup(embedded_datas
,
353 GINT_TO_POINTER(id
));
355 if ((embedded_data
!= NULL
) && (embedded_data
->size
== size
))
357 image
= purple_image_new_from_data(
360 purple_image_set_friendly_filename(image
, src
);
364 /* Delete the attribute list */
365 g_datalist_clear(&attributes
);
367 /* Append the message up to the tag */
368 utf8
= oscar_decode_im(account
, conn
->bn
, encoding
, tmp
, start
- tmp
);
370 g_string_append(newmsg
, utf8
);
378 img_id
= purple_image_store_add_temporary(image
);
379 g_object_unref(image
);
382 /* Write the new image tag */
383 g_string_append_printf(newmsg
, "<img src=\""
384 PURPLE_IMAGE_STORE_PROTOCOL
"%u\">", img_id
);
387 /* Continue from the end of the tag */
391 /* Append any remaining message data */
394 utf8
= oscar_decode_im(account
, conn
->bn
, encoding
, tmp
, msgend
- tmp
);
396 g_string_append(newmsg
, utf8
);
401 /* Display the message we received */
404 imflags
|= PURPLE_MESSAGE_IMAGES
;
406 imflags
|= PURPLE_MESSAGE_AUTO_RESP
;
407 purple_serv_got_im(gc
, conn
->bn
, newmsg
->str
, imflags
, time(NULL
));
408 g_string_free(newmsg
, TRUE
);
410 /* Delete our list of pointers to embedded images */
411 g_hash_table_destroy(embedded_datas
);
415 * This is a purple_input_add() watcher callback function for reading
416 * direct IM payload data. "Payload data" is always an IM and
417 * maybe some embedded images or files or something. The actual
418 * ODC frame is read using peer_connection_recv_cb(). We temporarily
419 * switch to this watcher callback ONLY to read the payload, and we
420 * switch back once we're done.
423 peer_odc_recv_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
425 PeerConnection
*conn
;
432 bs
= &frame
->payload
;
434 /* Read data into the temporary buffer until it is complete */
435 read
= recv(conn
->fd
,
436 &bs
->data
[bs
->offset
],
437 bs
->len
- bs
->offset
,
440 /* Check if the remote user closed the connection */
443 peer_connection_destroy(conn
, OSCAR_DISCONNECT_REMOTE_CLOSED
, NULL
);
449 if ((errno
== EAGAIN
) || (errno
== EWOULDBLOCK
))
453 peer_connection_destroy(conn
,
454 OSCAR_DISCONNECT_LOST_CONNECTION
, g_strerror(errno
));
459 if (bs
->offset
< bs
->len
)
460 /* Waiting for more data to arrive */
462 /* TODO: Instead of null-terminating this, it would be better if we just
463 respected the length of the buffer when parsing it. But it doesn't
464 really matter and this is easy. */
465 bs
->data
[bs
->len
] = '\0';
467 /* We have a complete ODC/OFT frame! Handle it and continue reading */
468 byte_stream_rewind(bs
);
469 peer_odc_handle_payload(conn
, (const char *)bs
->data
,
470 bs
->len
, frame
->encoding
, frame
->flags
& 0x0001);
476 purple_input_remove(conn
->watcher_incoming
);
477 conn
->watcher_incoming
= purple_input_add(conn
->fd
,
478 PURPLE_INPUT_READ
, peer_connection_recv_cb
, conn
);
482 * Handle an incoming OdcFrame. If there is a payload associated
483 * with this frame, then we remove the old watcher and add the
484 * ODC watcher to read in the payload.
487 peer_odc_recv_frame(PeerConnection
*conn
, ByteStream
*bs
)
489 PurpleConnection
*gc
;
494 frame
= g_new0(OdcFrame
, 1);
495 frame
->type
= byte_stream_get16(bs
);
496 frame
->subtype
= byte_stream_get16(bs
);
497 byte_stream_advance(bs
, 2);
498 byte_stream_getrawbuf(bs
, frame
->cookie
, 8);
499 byte_stream_advance(bs
, 8);
500 frame
->payload
.len
= byte_stream_get32(bs
);
501 frame
->encoding
= byte_stream_get16(bs
);
502 byte_stream_advance(bs
, 4);
503 frame
->flags
= byte_stream_get16(bs
);
504 byte_stream_advance(bs
, 4);
505 byte_stream_getrawbuf(bs
, frame
->bn
, 32);
507 purple_debug_info("oscar", "Incoming ODC frame from %s with "
508 "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT
"\n",
509 frame
->bn
, frame
->type
, frame
->flags
, frame
->payload
.len
);
514 * We need to verify the cookie so that we know we are
515 * connected to our friend and not a malicious middle man.
518 PurpleAccount
*account
;
519 PurpleIMConversation
*im
;
521 if (conn
->flags
& PEER_CONNECTION_FLAG_IS_INCOMING
)
523 if (memcmp(conn
->cookie
, frame
->cookie
, 8))
526 * Oh no! The user that connected to us did not send
527 * the correct cookie! They are not our friend. Go try
528 * to accept another connection?
530 purple_debug_info("oscar", "Received an incorrect cookie. "
531 "Closing connection.\n");
532 peer_connection_destroy(conn
,
533 OSCAR_DISCONNECT_INVALID_DATA
, NULL
);
539 * Ok, we know they are legit. Now be courteous and
540 * send them our cookie. Note: This doesn't seem
541 * to be necessary, but it also doesn't seem to hurt.
543 peer_odc_send_cookie(conn
);
549 * If they connected to us then close the listener socket
550 * and send them our cookie.
552 if (conn
->listenerfd
!= -1)
554 close(conn
->listenerfd
);
555 conn
->listenerfd
= -1;
558 /* Tell the local user that we are connected */
559 account
= purple_connection_get_account(gc
);
560 im
= purple_im_conversation_new(account
, conn
->bn
);
561 purple_conversation_write_system_message(
562 PURPLE_CONVERSATION(im
), _("Direct IM established"), 0);
565 if ((frame
->type
!= 0x0001) && (frame
->subtype
!= 0x0006))
567 purple_debug_info("oscar", "Unknown ODC frame type 0x%04hx, "
568 "subtype 0x%04hx.\n", frame
->type
, frame
->subtype
);
573 if (frame
->flags
& 0x0008)
575 /* I had to leave this. It's just too funny. It reminds me of my sister. */
576 purple_debug_info("oscar", "ohmigod! %s has started typing "
577 "(DirectIM). He's going to send you a message! "
578 "*squeal*\n", conn
->bn
);
579 purple_serv_got_typing(gc
, conn
->bn
, 0, PURPLE_IM_TYPING
);
581 else if (frame
->flags
& 0x0004)
583 purple_serv_got_typing(gc
, conn
->bn
, 0, PURPLE_IM_TYPED
);
587 purple_serv_got_typing_stopped(gc
, conn
->bn
);
590 if (frame
->payload
.len
> 0)
592 if (frame
->payload
.len
> DIRECTIM_MAX_FILESIZE
)
594 gchar
*tmp
, *size1
, *size2
;
595 PurpleAccount
*account
;
596 PurpleIMConversation
*im
;
598 size1
= g_format_size(frame
->payload
.len
);
599 size2
= g_format_size(DIRECTIM_MAX_FILESIZE
);
600 tmp
= g_strdup_printf(_("%s tried to send you a %s file, but we only allow files up to %s over Direct IM. Try using file transfer instead.\n"), conn
->bn
, size1
, size2
);
604 account
= purple_connection_get_account(conn
->od
->gc
);
605 im
= purple_im_conversation_new(account
, conn
->bn
);
606 purple_conversation_write_system_message(
607 PURPLE_CONVERSATION(im
), tmp
, 0);
610 peer_connection_destroy(conn
, OSCAR_DISCONNECT_LOCAL_CLOSED
, NULL
);
615 /* We have payload data! Switch to the ODC watcher to read it. */
616 frame
->payload
.data
= g_new(guint8
, frame
->payload
.len
+ 1);
617 frame
->payload
.offset
= 0;
619 purple_input_remove(conn
->watcher_incoming
);
620 conn
->watcher_incoming
= purple_input_add(conn
->fd
,
621 PURPLE_INPUT_READ
, peer_odc_recv_cb
, conn
);