rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / oscar / odc.c
blob0f2ddd2aa5d66c477c95f719fa4e4f9edc5cb831
1 /*
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 */
22 #include "encoding.h"
23 #include "oscar.h"
24 #include "peer.h"
26 /* From Purple */
27 #include "conversation.h"
28 #include "image-store.h"
29 #include "util.h"
31 #define DIRECTIM_MAX_FILESIZE 52428800
33 /**
34 * Free any ODC related data and print a message to the conversation
35 * window based on conn->disconnect_reason.
37 void
38 peer_odc_close(PeerConnection *conn)
40 gchar *tmp;
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"),
48 conn->error_message);
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."));
53 else
55 * We shouldn't print a message for some disconnect_reasons.
56 * Like OSCAR_DISCONNECT_LOCAL_CLOSED.
58 tmp = NULL;
60 if (tmp != NULL)
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);
69 g_free(tmp);
72 if (conn->frame != NULL)
74 OdcFrame *frame;
75 frame = conn->frame;
76 g_free(frame->payload.data);
77 g_free(frame);
81 /**
82 * Write the given OdcFrame to a ByteStream and send it out
83 * on the established PeerConnection.
85 static void
86 peer_odc_send(PeerConnection *conn, OdcFrame *frame)
88 PurpleAccount *account;
89 const char *username;
90 size_t length;
91 ByteStream bs;
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);
102 length = 76;
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.
136 void
137 peer_odc_send_cookie(PeerConnection *conn)
139 OdcFrame frame;
141 memset(&frame, 0, sizeof(OdcFrame));
142 frame.type = 0x0001;
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.
152 void
153 peer_odc_send_typing(PeerConnection *conn, PurpleIMTypingState typing)
155 OdcFrame frame;
157 memset(&frame, 0, sizeof(OdcFrame));
158 frame.type = 0x0001;
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;
164 else
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.
180 void
181 peer_odc_send_im(PeerConnection *conn, const char *msg, int len, int encoding, gboolean autoreply)
183 OdcFrame frame;
185 g_return_if_fail(msg != NULL);
186 g_return_if_fail(len > 0);
188 memset(&frame, 0, sizeof(OdcFrame));
189 frame.type = 0x0001;
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);
202 struct embedded_data
204 size_t size;
205 const guint8 *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">
224 * <FONT LANG="0">
225 * This is a really stupid picture:<BR>
226 * <IMG SRC="Sample.jpg" ID="1" WIDTH="283" HEIGHT="212" DATASIZE="9894"><BR>
227 * Yeah it is<BR>
228 * Here is another one:<BR>
229 * <IMG SRC="Soap Bubbles.bmp" ID="2" WIDTH="256" HEIGHT="256" DATASIZE="65978">
230 * </FONT>
231 * </BODY></HTML>
232 * <BINARY>
233 * <DATA ID="1" SIZE="9894">datadatadatadata</DATA>
234 * <DATA ID="2" SIZE="65978">datadatadatadata</DATA>
235 * </BINARY>
237 static void
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;
244 GData *attributes;
245 GHashTable *embedded_datas;
246 struct embedded_data *embedded_data;
247 gboolean any_images = FALSE;
248 gchar *utf8;
249 GString *newmsg;
250 PurpleMessageFlags imflags;
252 gc = conn->od->gc;
253 account = purple_connection_get_account(gc);
255 dataend = msg + len;
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)
272 msgend = dataend;
273 else
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))
284 unsigned int id;
285 size_t size;
287 /* Move the binary pointer from ">" to the start of the data */
288 tmp++;
290 /* Get the ID */
291 idstr = g_datalist_get_data(&attributes, "id");
292 if (idstr == NULL)
294 g_datalist_clear(&attributes);
295 break;
297 id = atoi(idstr);
299 /* Get the size */
300 sizestr = g_datalist_get_data(&attributes, "size");
301 if (sizestr == NULL)
303 g_datalist_clear(&attributes);
304 break;
306 size = atol(sizestr);
308 g_datalist_clear(&attributes);
310 if ((size > 0) && (tmp + size > dataend))
311 break;
313 embedded_data = g_new(struct embedded_data, 1);
314 embedded_data->size = size;
315 embedded_data->data = (const guint8 *)tmp;
316 tmp += size;
318 /* Skip past the closing </data> tag */
319 if (g_ascii_strncasecmp(tmp, "</data>", 7))
321 g_free(embedded_data);
322 break;
324 tmp += 7;
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("");
336 tmp = msg;
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))
347 unsigned int id;
348 size_t size;
350 id = atoi(idstr);
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(
358 embedded_data->data,
359 size);
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);
369 if (utf8 != NULL) {
370 g_string_append(newmsg, utf8);
371 g_free(utf8);
374 if (image)
376 guint img_id;
378 img_id = purple_image_store_add_temporary(image);
379 g_object_unref(image);
380 any_images = TRUE;
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 */
388 tmp = end + 1;
391 /* Append any remaining message data */
392 if (tmp <= msgend)
394 utf8 = oscar_decode_im(account, conn->bn, encoding, tmp, msgend - tmp);
395 if (utf8 != NULL) {
396 g_string_append(newmsg, utf8);
397 g_free(utf8);
401 /* Display the message we received */
402 imflags = 0;
403 if (any_images)
404 imflags |= PURPLE_MESSAGE_IMAGES;
405 if (autoreply)
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.
422 static void
423 peer_odc_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
425 PeerConnection *conn;
426 OdcFrame *frame;
427 ByteStream *bs;
428 gssize read;
430 conn = data;
431 frame = conn->frame;
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 */
441 if (read == 0)
443 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
444 return;
447 if (read < 0)
449 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
450 /* No worries */
451 return;
453 peer_connection_destroy(conn,
454 OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
455 return;
458 bs->offset += read;
459 if (bs->offset < bs->len)
460 /* Waiting for more data to arrive */
461 return;
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);
471 g_free(bs->data);
472 bs->data = NULL;
473 g_free(frame);
474 conn->frame = NULL;
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.
486 void
487 peer_odc_recv_frame(PeerConnection *conn, ByteStream *bs)
489 PurpleConnection *gc;
490 OdcFrame *frame;
492 gc = conn->od->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);
511 if (!conn->ready)
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);
534 g_free(frame);
535 return;
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);
546 conn->ready = TRUE;
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);
569 g_free(frame);
570 return;
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);
585 else
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);
601 g_free(size1);
602 g_free(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);
608 g_free(tmp);
610 peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL);
611 g_free(frame);
612 return;
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;
618 conn->frame = frame;
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);
622 return;
625 g_free(frame);