summaryrefslogtreecommitdiff
path: root/libpurple/protocols/oscar/odc.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/oscar/odc.c')
-rw-r--r--libpurple/protocols/oscar/odc.c601
1 files changed, 601 insertions, 0 deletions
diff --git a/libpurple/protocols/oscar/odc.c b/libpurple/protocols/oscar/odc.c
new file mode 100644
index 0000000000..160cfb418c
--- /dev/null
+++ b/libpurple/protocols/oscar/odc.c
@@ -0,0 +1,601 @@
+/*
+ * Gaim's oscar protocol plugin
+ * This file is the legal property of its developers.
+ * Please see the AUTHORS file distributed alongside this file.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/* From the oscar PRPL */
+#include "oscar.h"
+#include "peer.h"
+
+/* From Gaim */
+#include "conversation.h"
+#include "imgstore.h"
+#include "util.h"
+
+/**
+ * Free any ODC related data and print a message to the conversation
+ * window based on conn->disconnect_reason.
+ */
+void
+peer_odc_close(PeerConnection *conn)
+{
+ gchar *tmp;
+
+ if (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED)
+ tmp = g_strdup(_("The remote user has closed the connection."));
+ else if (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED)
+ tmp = g_strdup(_("The remote user has declined your request."));
+ else if (conn->disconnect_reason == OSCAR_DISCONNECT_LOST_CONNECTION)
+ tmp = g_strdup_printf(_("Lost connection with the remote user:<br>%s"),
+ conn->error_message);
+ else if (conn->disconnect_reason == OSCAR_DISCONNECT_INVALID_DATA)
+ tmp = g_strdup(_("Received invalid data on connection with remote user."));
+ else if (conn->disconnect_reason == OSCAR_DISCONNECT_COULD_NOT_CONNECT)
+ tmp = g_strdup(_("Could not establish a connection with the remote user."));
+ else
+ /*
+ * We shouldn't print a message for some disconnect_reasons.
+ * Like OSCAR_DISCONNECT_LOCAL_CLOSED.
+ */
+ tmp = NULL;
+
+ if (tmp != NULL)
+ {
+ GaimAccount *account;
+ GaimConversation *conv;
+
+ account = gaim_connection_get_account(conn->od->gc);
+ conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn);
+ gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL));
+ g_free(tmp);
+ }
+
+ if (conn->frame != NULL)
+ {
+ OdcFrame *frame;
+ frame = conn->frame;
+ g_free(frame->payload.data);
+ g_free(frame);
+ }
+}
+
+/**
+ * Write the given OdcFrame to a ByteStream and send it out
+ * on the established PeerConnection.
+ */
+static void
+peer_odc_send(PeerConnection *conn, OdcFrame *frame)
+{
+ GaimAccount *account;
+ const char *username;
+ size_t length;
+ ByteStream bs;
+
+ gaim_debug_info("oscar", "Outgoing ODC frame to %s with "
+ "type=0x%04x, flags=0x%04x, payload length=%u\n",
+ conn->sn, frame->type, frame->flags, frame->payload.len);
+
+ account = gaim_connection_get_account(conn->od->gc);
+ username = gaim_account_get_username(account);
+ memcpy(frame->sn, username, strlen(username));
+ memcpy(frame->cookie, conn->cookie, 8);
+
+ length = 76;
+ byte_stream_new(&bs, length + frame->payload.len);
+ byte_stream_putraw(&bs, conn->magic, 4);
+ byte_stream_put16(&bs, length);
+ byte_stream_put16(&bs, frame->type);
+ byte_stream_put16(&bs, frame->subtype);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_putraw(&bs, frame->cookie, 8);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put32(&bs, frame->payload.len);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, frame->encoding);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, frame->flags);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_put16(&bs, 0x0000);
+ byte_stream_putraw(&bs, frame->sn, 32);
+ byte_stream_putraw(&bs, frame->payload.data, frame->payload.len);
+
+ peer_connection_send(conn, &bs);
+
+ g_free(bs.data);
+}
+
+/**
+ * Send a very basic ODC frame (which contains the cookie) so that the
+ * remote user can verify that we are the person they were expecting.
+ * If we made an outgoing connection to then remote user, then we send
+ * this immediately. If the remote user connected to us, then we wait
+ * for the other person to send this to us, then we send one to them.
+ */
+void
+peer_odc_send_cookie(PeerConnection *conn)
+{
+ OdcFrame frame;
+
+ memset(&frame, 0, sizeof(OdcFrame));
+ frame.type = 0x0001;
+ frame.subtype = 0x0006;
+ frame.flags = 0x0060; /* Maybe this means "we're sending the cookie"? */
+
+ peer_odc_send(conn, &frame);
+}
+
+/**
+ * Send client-to-client typing notification over an established direct connection.
+ */
+void
+peer_odc_send_typing(PeerConnection *conn, GaimTypingState typing)
+{
+ OdcFrame frame;
+
+ memset(&frame, 0, sizeof(OdcFrame));
+ frame.type = 0x0001;
+ frame.subtype = 0x0006;
+ if (typing == GAIM_TYPING)
+ frame.flags = 0x0002 | 0x0008;
+ else if (typing == GAIM_TYPED)
+ frame.flags = 0x0002 | 0x0004;
+ else
+ frame.flags = 0x0002;
+
+ peer_odc_send(conn, &frame);
+}
+
+/**
+ * Send client-to-client IM over an established direct connection.
+ * To send a direct IM, call this just like you would aim_send_im.
+ *
+ * @param conn The already-connected ODC connection.
+ * @param msg Null-terminated string to send.
+ * @param len The length of the message to send, including binary data.
+ * @param encoding See the AIM_CHARSET_* defines in oscar.h
+ * @param autoreply TRUE if this is any auto-reply.
+ */
+void
+peer_odc_send_im(PeerConnection *conn, const char *msg, int len, int encoding, gboolean autoreply)
+{
+ OdcFrame frame;
+
+ g_return_if_fail(msg != NULL);
+ g_return_if_fail(len > 0);
+
+ memset(&frame, 0, sizeof(OdcFrame));
+ frame.type = 0x0001;
+ frame.subtype = 0x0006;
+ frame.payload.len = len;
+ frame.encoding = encoding;
+ frame.flags = autoreply;
+ byte_stream_new(&frame.payload, len);
+ byte_stream_putraw(&frame.payload, (guint8 *)msg, len);
+
+ peer_odc_send(conn, &frame);
+
+ g_free(frame.payload.data);
+}
+
+struct embedded_data
+{
+ size_t size;
+ const guint8 *data;
+};
+
+/**
+ * This is called after a direct IM has been received in its entirety. This
+ * function is passed a long chunk of data which contains the IM with any
+ * data chunks (images) appended to it.
+ *
+ * This function rips out all the data chunks and creates an imgstore for
+ * each one. In order to do this, it first goes through the IM and takes
+ * out all the IMG tags. When doing so, it rewrites the original IMG tag
+ * with one compatible with the imgstore Gaim core code. For each one, we
+ * then read in chunks of data from the end of the message and actually
+ * create the img store using the given data.
+ *
+ * For somewhat easy reference, here's a sample message
+ * (with added whitespace):
+ *
+ * <HTML><BODY BGCOLOR="#ffffff">
+ * <FONT LANG="0">
+ * This is a really stupid picture:<BR>
+ * <IMG SRC="Sample.jpg" ID="1" WIDTH="283" HEIGHT="212" DATASIZE="9894"><BR>
+ * Yeah it is<BR>
+ * Here is another one:<BR>
+ * <IMG SRC="Soap Bubbles.bmp" ID="2" WIDTH="256" HEIGHT="256" DATASIZE="65978">
+ * </FONT>
+ * </BODY></HTML>
+ * <BINARY>
+ * <DATA ID="1" SIZE="9894">datadatadatadata</DATA>
+ * <DATA ID="2" SIZE="65978">datadatadatadata</DATA>
+ * </BINARY>
+ */
+static void
+peer_odc_handle_payload(PeerConnection *conn, const char *msg, size_t len, int encoding, gboolean autoreply)
+{
+ GaimConnection *gc;
+ GaimAccount *account;
+ const char *msgend, *binary_start, *dataend;
+ const char *tmp, *start, *end, *idstr, *src, *sizestr;
+ GData *attributes;
+ GHashTable *embedded_datas;
+ struct embedded_data *embedded_data;
+ GSList *images;
+ gchar *utf8;
+ GString *newmsg;
+ GaimMessageFlags imflags;
+
+ gc = conn->od->gc;
+ account = gaim_connection_get_account(gc);
+
+ dataend = msg + len;
+
+ /*
+ * Create a hash table containing references to each embedded
+ * data chunk. The key is the "ID" and the value is an
+ * embedded_data struct.
+ */
+ embedded_datas = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, g_free);
+
+ /*
+ * Create an index of any binary chunks. If we run into any
+ * problems while parsing the binary data section then we stop
+ * parsing it, and the local user will see broken image icons.
+ */
+ /* TODO: Use a length argument when looking for the <binary> tag! */
+ binary_start = gaim_strcasestr(msg, "<binary>");
+ if (binary_start == NULL)
+ msgend = dataend;
+ else
+ {
+ msgend = binary_start;
+
+ /* Move our pointer to immediately after the <binary> tag */
+ tmp = binary_start + 8;
+
+ /* The embedded binary markup has a mimimum length of 29 bytes */
+ /* TODO: Use a length argument when looking for the <data> tag! */
+ while ((tmp + 29 <= dataend) &&
+ gaim_markup_find_tag("data", tmp, &start, &tmp, &attributes))
+ {
+ unsigned int id;
+ size_t size;
+
+ /* Move the binary pointer from ">" to the start of the data */
+ tmp++;
+
+ /* Get the ID */
+ idstr = g_datalist_get_data(&attributes, "id");
+ if (idstr == NULL)
+ {
+ g_datalist_clear(&attributes);
+ break;
+ }
+ id = atoi(idstr);
+
+ /* Get the size */
+ sizestr = g_datalist_get_data(&attributes, "size");
+ if (sizestr == NULL)
+ {
+ g_datalist_clear(&attributes);
+ break;
+ }
+ size = atol(sizestr);
+
+ g_datalist_clear(&attributes);
+
+ if ((size > 0) && (tmp + size > dataend))
+ break;
+
+ embedded_data = g_new(struct embedded_data, 1);
+ embedded_data->size = size;
+ embedded_data->data = (const guint8 *)tmp;
+ tmp += size;
+
+ /* Skip past the closing </data> tag */
+ if (strncasecmp(tmp, "</data>", 7))
+ {
+ g_free(embedded_data);
+ break;
+ }
+ tmp += 7;
+
+ g_hash_table_insert(embedded_datas,
+ GINT_TO_POINTER(id), embedded_data);
+ }
+ }
+
+ /*
+ * Loop through the message, replacing OSCAR img tags with the
+ * equivalent Gaim img tag.
+ */
+ images = NULL;
+ newmsg = g_string_new("");
+ tmp = msg;
+ while (gaim_markup_find_tag("img", tmp, &start, &end, &attributes))
+ {
+ int imgid = 0;
+
+ idstr = g_datalist_get_data(&attributes, "id");
+ src = g_datalist_get_data(&attributes, "src");
+ sizestr = g_datalist_get_data(&attributes, "datasize");
+
+ if ((idstr != NULL) && (src != NULL) && (sizestr!= NULL))
+ {
+ unsigned int id;
+ size_t size;
+
+ id = atoi(idstr);
+ size = atol(sizestr);
+ embedded_data = g_hash_table_lookup(embedded_datas,
+ GINT_TO_POINTER(id));
+
+ if ((embedded_data != NULL) && (embedded_data->size == size))
+ {
+ imgid = gaim_imgstore_add(embedded_data->data, size, src);
+
+ /* Record the image number */
+ images = g_slist_append(images, GINT_TO_POINTER(imgid));
+ }
+ }
+
+ /* Delete the attribute list */
+ g_datalist_clear(&attributes);
+
+ /* Append the message up to the tag */
+ utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
+ encoding, 0x0000, tmp, start - tmp);
+ if (utf8 != NULL) {
+ g_string_append(newmsg, utf8);
+ g_free(utf8);
+ }
+
+ if (imgid != 0)
+ {
+ /* Write the new image tag */
+ g_string_append_printf(newmsg, "<IMG ID=\"%d\">", imgid);
+ }
+
+ /* Continue from the end of the tag */
+ tmp = end + 1;
+ }
+
+ /* Append any remaining message data */
+ if (tmp <= msgend)
+ {
+ utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
+ encoding, 0x0000, tmp, msgend - tmp);
+ if (utf8 != NULL) {
+ g_string_append(newmsg, utf8);
+ g_free(utf8);
+ }
+ }
+
+ /* Send the message */
+ imflags = 0;
+ if (images != NULL)
+ imflags |= GAIM_MESSAGE_IMAGES;
+ if (autoreply)
+ imflags |= GAIM_MESSAGE_AUTO_RESP;
+ serv_got_im(gc, conn->sn, newmsg->str, imflags, time(NULL));
+ g_string_free(newmsg, TRUE);
+
+ /* unref any images we allocated */
+ if (images)
+ {
+ GSList *l;
+ for (l = images; l != NULL; l = l->next)
+ gaim_imgstore_unref(GPOINTER_TO_INT(l->data));
+ g_slist_free(images);
+ }
+
+ /* Delete our list of pointers to embedded images */
+ g_hash_table_destroy(embedded_datas);
+}
+
+/**
+ * This is a gaim_input_add() watcher callback function for reading
+ * direct IM payload data. "Payload data" is always an IM and
+ * maybe some embedded images or files or something. The actual
+ * ODC frame is read using peer_connection_recv_cb(). We temporarily
+ * switch to this watcher callback ONLY to read the payload, and we
+ * switch back once we're done.
+ */
+static void
+peer_odc_recv_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+ PeerConnection *conn;
+ OdcFrame *frame;
+ ByteStream *bs;
+ ssize_t read;
+
+ conn = data;
+ frame = conn->frame;
+ bs = &frame->payload;
+
+ /* Read data into the temporary buffer until it is complete */
+ read = recv(conn->fd,
+ &bs->data[bs->offset],
+ bs->len - bs->offset,
+ 0);
+
+ /* Check if the remote user closed the connection */
+ if (read == 0)
+ {
+ peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
+ return;
+ }
+
+ if (read == -1)
+ {
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+ /* No worries */
+ return;
+
+ peer_connection_destroy(conn,
+ OSCAR_DISCONNECT_LOST_CONNECTION, strerror(errno));
+ return;
+ }
+
+ bs->offset += read;
+ if (bs->offset < bs->len)
+ /* Waiting for more data to arrive */
+ return;
+
+ /* We have a complete ODC/OFT frame! Handle it and continue reading */
+ byte_stream_rewind(bs);
+ peer_odc_handle_payload(conn, (const char *)bs->data,
+ bs->len, frame->encoding, frame->flags & 0x0001);
+ g_free(bs->data);
+ bs->data = NULL;
+ g_free(frame);
+ conn->frame = NULL;
+
+ gaim_input_remove(conn->watcher_incoming);
+ conn->watcher_incoming = gaim_input_add(conn->fd,
+ GAIM_INPUT_READ, peer_connection_recv_cb, conn);
+}
+
+/**
+ * Handle an incoming OdcFrame. If there is a payload associated
+ * with this frame, then we remove the old watcher and add the
+ * ODC watcher to read in the payload.
+ */
+void
+peer_odc_recv_frame(PeerConnection *conn, ByteStream *bs)
+{
+ GaimConnection *gc;
+ OdcFrame *frame;
+
+ gc = conn->od->gc;
+
+ frame = g_new0(OdcFrame, 1);
+ frame->type = byte_stream_get16(bs);
+ frame->subtype = byte_stream_get16(bs);
+ byte_stream_advance(bs, 2);
+ byte_stream_getrawbuf(bs, frame->cookie, 8);
+ byte_stream_advance(bs, 8);
+ frame->payload.len = byte_stream_get32(bs);
+ frame->encoding = byte_stream_get16(bs);
+ byte_stream_advance(bs, 4);
+ frame->flags = byte_stream_get16(bs);
+ byte_stream_advance(bs, 4);
+ byte_stream_getrawbuf(bs, frame->sn, 32);
+
+ gaim_debug_info("oscar", "Incoming ODC frame from %s with "
+ "type=0x%04x, flags=0x%04x, payload length=%u\n",
+ frame->sn, frame->type, frame->flags, frame->payload.len);
+
+ if (!conn->ready)
+ {
+ /*
+ * We need to verify the cookie so that we know we are
+ * connected to our friend and not a malicious middle man.
+ */
+
+ GaimAccount *account;
+ GaimConversation *conv;
+
+ if (conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)
+ {
+ if (memcmp(conn->cookie, frame->cookie, 8))
+ {
+ /*
+ * Oh no! The user that connected to us did not send
+ * the correct cookie! They are not our friend. Go try
+ * to accept another connection?
+ */
+ gaim_debug_info("oscar", "Received an incorrect cookie. "
+ "Closing connection.\n");
+ peer_connection_destroy(conn,
+ OSCAR_DISCONNECT_INVALID_DATA, NULL);
+ g_free(frame);
+ return;
+ }
+
+ /*
+ * Ok, we know they are legit. Now be courteous and
+ * send them our cookie. Note: This doesn't seem
+ * to be necessary, but it also doesn't seem to hurt.
+ */
+ peer_odc_send_cookie(conn);
+ }
+
+ conn->ready = TRUE;
+
+ /*
+ * If they connected to us then close the listener socket
+ * and send them our cookie.
+ */
+ if (conn->listenerfd != -1)
+ {
+ close(conn->listenerfd);
+ conn->listenerfd = -1;
+ }
+
+ /* Tell the local user that we are connected */
+ account = gaim_connection_get_account(gc);
+ conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn);
+ gaim_conversation_write(conv, NULL, _("Direct IM established"),
+ GAIM_MESSAGE_SYSTEM, time(NULL));
+ }
+
+ if ((frame->type != 0x0001) && (frame->subtype != 0x0006))
+ {
+ gaim_debug_info("oscar", "Unknown ODC frame type 0x%04hx, "
+ "subtype 0x%04hx.\n", frame->type, frame->subtype);
+ return;
+ }
+
+ if (frame->flags & 0x0008)
+ {
+ /* I had to leave this. It's just too funny. It reminds me of my sister. */
+ gaim_debug_info("oscar", "ohmigod! %s has started typing "
+ "(DirectIM). He's going to send you a message! "
+ "*squeal*\n", conn->sn);
+ serv_got_typing(gc, conn->sn, 0, GAIM_TYPING);
+ }
+ else if (frame->flags & 0x0004)
+ {
+ serv_got_typing(gc, conn->sn, 0, GAIM_TYPED);
+ }
+ else
+ {
+ serv_got_typing_stopped(gc, conn->sn);
+ }
+
+ if (frame->payload.len > 0)
+ {
+ /* We have payload data! Switch to the ODC watcher to read it. */
+ frame->payload.data = g_new(guint8, frame->payload.len);
+ frame->payload.offset = 0;
+ conn->frame = frame;
+ gaim_input_remove(conn->watcher_incoming);
+ conn->watcher_incoming = gaim_input_add(conn->fd,
+ GAIM_INPUT_READ, peer_odc_recv_cb, conn);
+ return;
+ }
+
+ g_free(frame);
+}