Annotation of ChivanetAimPidgin/oscarprpl/src/c/odc.c, revision 1.1.1.1
1.1 snw 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.
5: *
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.
10: *
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.
15: *
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
19: */
20:
21: /* From the oscar PRPL */
22: #include "encoding.h"
23: #include "oscar.h"
24: #include "peer.h"
25:
26: /* From Purple */
27: #include "conversation.h"
28: #include "imgstore.h"
29: #include "util.h"
30:
31: #define DIRECTIM_MAX_FILESIZE 52428800
32:
33: /**
34: * Free any ODC related data and print a message to the conversation
35: * window based on conn->disconnect_reason.
36: */
37: void
38: peer_odc_close(PeerConnection *conn)
39: {
40: gchar *tmp;
41:
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
54: /*
55: * We shouldn't print a message for some disconnect_reasons.
56: * Like OSCAR_DISCONNECT_LOCAL_CLOSED.
57: */
58: tmp = NULL;
59:
60: if (tmp != NULL)
61: {
62: PurpleAccount *account;
63: PurpleConversation *conv;
64:
65: account = purple_connection_get_account(conn->od->gc);
66: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
67: purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
68: g_free(tmp);
69: }
70:
71: if (conn->frame != NULL)
72: {
73: OdcFrame *frame;
74: frame = conn->frame;
75: g_free(frame->payload.data);
76: g_free(frame);
77: }
78: }
79:
80: /**
81: * Write the given OdcFrame to a ByteStream and send it out
82: * on the established PeerConnection.
83: */
84: static void
85: peer_odc_send(PeerConnection *conn, OdcFrame *frame)
86: {
87: PurpleAccount *account;
88: const char *username;
89: size_t length;
90: ByteStream bs;
91:
92: purple_debug_info("oscar", "Outgoing ODC frame to %s with "
93: "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT "\n",
94: conn->bn, frame->type, frame->flags, frame->payload.len);
95:
96: account = purple_connection_get_account(conn->od->gc);
97: username = purple_account_get_username(account);
98: memcpy(frame->bn, username, strlen(username));
99: memcpy(frame->cookie, conn->cookie, 8);
100:
101: length = 76;
102: byte_stream_new(&bs, length + frame->payload.len);
103: byte_stream_putraw(&bs, conn->magic, 4);
104: byte_stream_put16(&bs, length);
105: byte_stream_put16(&bs, frame->type);
106: byte_stream_put16(&bs, frame->subtype);
107: byte_stream_put16(&bs, 0x0000);
108: byte_stream_putraw(&bs, frame->cookie, 8);
109: byte_stream_put16(&bs, 0x0000);
110: byte_stream_put16(&bs, 0x0000);
111: byte_stream_put16(&bs, 0x0000);
112: byte_stream_put16(&bs, 0x0000);
113: byte_stream_put32(&bs, frame->payload.len);
114: byte_stream_put16(&bs, frame->encoding);
115: byte_stream_put16(&bs, 0x0000);
116: byte_stream_put16(&bs, 0x0000);
117: byte_stream_put16(&bs, frame->flags);
118: byte_stream_put16(&bs, 0x0000);
119: byte_stream_put16(&bs, 0x0000);
120: byte_stream_putraw(&bs, frame->bn, 32);
121: byte_stream_putraw(&bs, frame->payload.data, frame->payload.len);
122:
123: peer_connection_send(conn, &bs);
124:
125: byte_stream_destroy(&bs);
126: }
127:
128: /**
129: * Send a very basic ODC frame (which contains the cookie) so that the
130: * remote user can verify that we are the person they were expecting.
131: * If we made an outgoing connection to then remote user, then we send
132: * this immediately. If the remote user connected to us, then we wait
133: * for the other person to send this to us, then we send one to them.
134: */
135: void
136: peer_odc_send_cookie(PeerConnection *conn)
137: {
138: OdcFrame frame;
139:
140: memset(&frame, 0, sizeof(OdcFrame));
141: frame.type = 0x0001;
142: frame.subtype = 0x0006;
143: frame.flags = 0x0060; /* Maybe this means "we're sending the cookie"? */
144:
145: peer_odc_send(conn, &frame);
146: }
147:
148: /**
149: * Send client-to-client typing notification over an established direct connection.
150: */
151: void
152: peer_odc_send_typing(PeerConnection *conn, PurpleTypingState typing)
153: {
154: OdcFrame frame;
155:
156: memset(&frame, 0, sizeof(OdcFrame));
157: frame.type = 0x0001;
158: frame.subtype = 0x0006;
159: if (typing == PURPLE_TYPING)
160: frame.flags = 0x0002 | 0x0008;
161: else if (typing == PURPLE_TYPED)
162: frame.flags = 0x0002 | 0x0004;
163: else
164: frame.flags = 0x0002;
165:
166: peer_odc_send(conn, &frame);
167: }
168:
169: /**
170: * Send client-to-client IM over an established direct connection.
171: * To send a direct IM, call this just like you would aim_send_im.
172: *
173: * @param conn The already-connected ODC connection.
174: * @param msg Null-terminated string to send.
175: * @param len The length of the message to send, including binary data.
176: * @param encoding See the AIM_CHARSET_* defines in oscar.h
177: * @param autoreply TRUE if this is any auto-reply.
178: */
179: void
180: peer_odc_send_im(PeerConnection *conn, const char *msg, int len, int encoding, gboolean autoreply)
181: {
182: OdcFrame frame;
183:
184: g_return_if_fail(msg != NULL);
185: g_return_if_fail(len > 0);
186:
187: memset(&frame, 0, sizeof(OdcFrame));
188: frame.type = 0x0001;
189: frame.subtype = 0x0006;
190: frame.payload.len = len;
191: frame.encoding = encoding;
192: frame.flags = autoreply;
193: byte_stream_new(&frame.payload, len);
194: byte_stream_putraw(&frame.payload, (guint8 *)msg, len);
195:
196: peer_odc_send(conn, &frame);
197:
198: g_free(frame.payload.data);
199: }
200:
201: struct embedded_data
202: {
203: size_t size;
204: const guint8 *data;
205: };
206:
207: /**
208: * This is called after a direct IM has been received in its entirety. This
209: * function is passed a long chunk of data which contains the IM with any
210: * data chunks (images) appended to it.
211: *
212: * This function rips out all the data chunks and creates an imgstore for
213: * each one. In order to do this, it first goes through the IM and takes
214: * out all the IMG tags. When doing so, it rewrites the original IMG tag
215: * with one compatible with the imgstore Purple core code. For each one, we
216: * then read in chunks of data from the end of the message and actually
217: * create the img store using the given data.
218: *
219: * For somewhat easy reference, here's a sample message
220: * (with added whitespace):
221: *
222: * <HTML><BODY BGCOLOR="#ffffff">
223: * <FONT LANG="0">
224: * This is a really stupid picture:<BR>
225: * <IMG SRC="Sample.jpg" ID="1" WIDTH="283" HEIGHT="212" DATASIZE="9894"><BR>
226: * Yeah it is<BR>
227: * Here is another one:<BR>
228: * <IMG SRC="Soap Bubbles.bmp" ID="2" WIDTH="256" HEIGHT="256" DATASIZE="65978">
229: * </FONT>
230: * </BODY></HTML>
231: * <BINARY>
232: * <DATA ID="1" SIZE="9894">datadatadatadata</DATA>
233: * <DATA ID="2" SIZE="65978">datadatadatadata</DATA>
234: * </BINARY>
235: */
236: static void
237: peer_odc_handle_payload(PeerConnection *conn, const char *msg, size_t len, int encoding, gboolean autoreply)
238: {
239: PurpleConnection *gc;
240: PurpleAccount *account;
241: const char *msgend, *binary_start, *dataend;
242: const char *tmp, *start, *end, *idstr, *src, *sizestr;
243: GData *attributes;
244: GHashTable *embedded_datas;
245: struct embedded_data *embedded_data;
246: GSList *images;
247: gchar *utf8;
248: GString *newmsg;
249: PurpleMessageFlags imflags;
250:
251: gc = conn->od->gc;
252: account = purple_connection_get_account(gc);
253:
254: dataend = msg + len;
255:
256: /*
257: * Create a hash table containing references to each embedded
258: * data chunk. The key is the "ID" and the value is an
259: * embedded_data struct.
260: */
261: embedded_datas = g_hash_table_new_full(g_direct_hash,
262: g_direct_equal, NULL, g_free);
263:
264: /*
265: * Create an index of any binary chunks. If we run into any
266: * problems while parsing the binary data section then we stop
267: * parsing it, and the local user will see broken image icons.
268: */
269: binary_start = purple_strcasestr(msg, "<binary>");
270: if (binary_start == NULL)
271: msgend = dataend;
272: else
273: {
274: msgend = binary_start;
275:
276: /* Move our pointer to immediately after the <binary> tag */
277: tmp = binary_start + 8;
278:
279: /* The embedded binary markup has a mimimum length of 29 bytes */
280: while ((tmp + 29 <= dataend) &&
281: purple_markup_find_tag("data", tmp, &start, &tmp, &attributes))
282: {
283: unsigned int id;
284: size_t size;
285:
286: /* Move the binary pointer from ">" to the start of the data */
287: tmp++;
288:
289: /* Get the ID */
290: idstr = g_datalist_get_data(&attributes, "id");
291: if (idstr == NULL)
292: {
293: g_datalist_clear(&attributes);
294: break;
295: }
296: id = atoi(idstr);
297:
298: /* Get the size */
299: sizestr = g_datalist_get_data(&attributes, "size");
300: if (sizestr == NULL)
301: {
302: g_datalist_clear(&attributes);
303: break;
304: }
305: size = atol(sizestr);
306:
307: g_datalist_clear(&attributes);
308:
309: if ((size > 0) && (tmp + size > dataend))
310: break;
311:
312: embedded_data = g_new(struct embedded_data, 1);
313: embedded_data->size = size;
314: embedded_data->data = (const guint8 *)tmp;
315: tmp += size;
316:
317: /* Skip past the closing </data> tag */
318: if (g_ascii_strncasecmp(tmp, "</data>", 7))
319: {
320: g_free(embedded_data);
321: break;
322: }
323: tmp += 7;
324:
325: g_hash_table_insert(embedded_datas,
326: GINT_TO_POINTER(id), embedded_data);
327: }
328: }
329:
330: /*
331: * Loop through the message, replacing OSCAR img tags with the
332: * equivalent Purple img tag.
333: */
334: images = NULL;
335: newmsg = g_string_new("");
336: tmp = msg;
337: while (purple_markup_find_tag("img", tmp, &start, &end, &attributes))
338: {
339: int imgid = 0;
340:
341: idstr = g_datalist_get_data(&attributes, "id");
342: src = g_datalist_get_data(&attributes, "src");
343: sizestr = g_datalist_get_data(&attributes, "datasize");
344:
345: if ((idstr != NULL) && (src != NULL) && (sizestr!= NULL))
346: {
347: unsigned int id;
348: size_t size;
349:
350: id = atoi(idstr);
351: size = atol(sizestr);
352: embedded_data = g_hash_table_lookup(embedded_datas,
353: GINT_TO_POINTER(id));
354:
355: if ((embedded_data != NULL) && (embedded_data->size == size))
356: {
357: imgid = purple_imgstore_add_with_id(g_memdup(embedded_data->data, size), size, src);
358:
359: /* Record the image number */
360: images = g_slist_append(images, GINT_TO_POINTER(imgid));
361: }
362: }
363:
364: /* Delete the attribute list */
365: g_datalist_clear(&attributes);
366:
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);
372: }
373:
374: if (imgid != 0)
375: {
376: /* Write the new image tag */
377: g_string_append_printf(newmsg, "<IMG ID=\"%d\">", imgid);
378: }
379:
380: /* Continue from the end of the tag */
381: tmp = end + 1;
382: }
383:
384: /* Append any remaining message data */
385: if (tmp <= msgend)
386: {
387: utf8 = oscar_decode_im(account, conn->bn, encoding, tmp, msgend - tmp);
388: if (utf8 != NULL) {
389: g_string_append(newmsg, utf8);
390: g_free(utf8);
391: }
392: }
393:
394: /* Display the message we received */
395: imflags = 0;
396: if (images != NULL)
397: imflags |= PURPLE_MESSAGE_IMAGES;
398: if (autoreply)
399: imflags |= PURPLE_MESSAGE_AUTO_RESP;
400: serv_got_im(gc, conn->bn, newmsg->str, imflags, time(NULL));
401: g_string_free(newmsg, TRUE);
402:
403: /* unref any images we allocated */
404: if (images)
405: {
406: GSList *l;
407: for (l = images; l != NULL; l = l->next)
408: purple_imgstore_unref_by_id(GPOINTER_TO_INT(l->data));
409: g_slist_free(images);
410: }
411:
412: /* Delete our list of pointers to embedded images */
413: g_hash_table_destroy(embedded_datas);
414: }
415:
416: /**
417: * This is a purple_input_add() watcher callback function for reading
418: * direct IM payload data. "Payload data" is always an IM and
419: * maybe some embedded images or files or something. The actual
420: * ODC frame is read using peer_connection_recv_cb(). We temporarily
421: * switch to this watcher callback ONLY to read the payload, and we
422: * switch back once we're done.
423: */
424: static void
425: peer_odc_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
426: {
427: PeerConnection *conn;
428: OdcFrame *frame;
429: ByteStream *bs;
430: gssize read;
431:
432: conn = data;
433: frame = conn->frame;
434: bs = &frame->payload;
435:
436: /* Read data into the temporary buffer until it is complete */
437: read = recv(conn->fd,
438: &bs->data[bs->offset],
439: bs->len - bs->offset,
440: 0);
441:
442: /* Check if the remote user closed the connection */
443: if (read == 0)
444: {
445: peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
446: return;
447: }
448:
449: if (read < 0)
450: {
451: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
452: /* No worries */
453: return;
454:
455: peer_connection_destroy(conn,
456: OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
457: return;
458: }
459:
460: bs->offset += read;
461: if (bs->offset < bs->len)
462: /* Waiting for more data to arrive */
463: return;
464: /* TODO: Instead of null-terminating this, it would be better if we just
465: respected the length of the buffer when parsing it. But it doesn't
466: really matter and this is easy. */
467: bs->data[bs->len] = '\0';
468:
469: /* We have a complete ODC/OFT frame! Handle it and continue reading */
470: byte_stream_rewind(bs);
471: peer_odc_handle_payload(conn, (const char *)bs->data,
472: bs->len, frame->encoding, frame->flags & 0x0001);
473: g_free(bs->data);
474: bs->data = NULL;
475: g_free(frame);
476: conn->frame = NULL;
477:
478: purple_input_remove(conn->watcher_incoming);
479: conn->watcher_incoming = purple_input_add(conn->fd,
480: PURPLE_INPUT_READ, peer_connection_recv_cb, conn);
481: }
482:
483: /**
484: * Handle an incoming OdcFrame. If there is a payload associated
485: * with this frame, then we remove the old watcher and add the
486: * ODC watcher to read in the payload.
487: */
488: void
489: peer_odc_recv_frame(PeerConnection *conn, ByteStream *bs)
490: {
491: PurpleConnection *gc;
492: OdcFrame *frame;
493:
494: gc = conn->od->gc;
495:
496: frame = g_new0(OdcFrame, 1);
497: frame->type = byte_stream_get16(bs);
498: frame->subtype = byte_stream_get16(bs);
499: byte_stream_advance(bs, 2);
500: byte_stream_getrawbuf(bs, frame->cookie, 8);
501: byte_stream_advance(bs, 8);
502: frame->payload.len = byte_stream_get32(bs);
503: frame->encoding = byte_stream_get16(bs);
504: byte_stream_advance(bs, 4);
505: frame->flags = byte_stream_get16(bs);
506: byte_stream_advance(bs, 4);
507: byte_stream_getrawbuf(bs, frame->bn, 32);
508:
509: purple_debug_info("oscar", "Incoming ODC frame from %s with "
510: "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT "\n",
511: frame->bn, frame->type, frame->flags, frame->payload.len);
512:
513: if (!conn->ready)
514: {
515: /*
516: * We need to verify the cookie so that we know we are
517: * connected to our friend and not a malicious middle man.
518: */
519:
520: PurpleAccount *account;
521: PurpleConversation *conv;
522:
523: if (conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)
524: {
525: if (memcmp(conn->cookie, frame->cookie, 8))
526: {
527: /*
528: * Oh no! The user that connected to us did not send
529: * the correct cookie! They are not our friend. Go try
530: * to accept another connection?
531: */
532: purple_debug_info("oscar", "Received an incorrect cookie. "
533: "Closing connection.\n");
534: peer_connection_destroy(conn,
535: OSCAR_DISCONNECT_INVALID_DATA, NULL);
536: g_free(frame);
537: return;
538: }
539:
540: /*
541: * Ok, we know they are legit. Now be courteous and
542: * send them our cookie. Note: This doesn't seem
543: * to be necessary, but it also doesn't seem to hurt.
544: */
545: peer_odc_send_cookie(conn);
546: }
547:
548: conn->ready = TRUE;
549:
550: /*
551: * If they connected to us then close the listener socket
552: * and send them our cookie.
553: */
554: if (conn->listenerfd != -1)
555: {
556: close(conn->listenerfd);
557: conn->listenerfd = -1;
558: }
559:
560: /* Tell the local user that we are connected */
561: account = purple_connection_get_account(gc);
562: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
563: purple_conversation_write(conv, NULL, _("Direct IM established"),
564: PURPLE_MESSAGE_SYSTEM, time(NULL));
565: }
566:
567: if ((frame->type != 0x0001) && (frame->subtype != 0x0006))
568: {
569: purple_debug_info("oscar", "Unknown ODC frame type 0x%04hx, "
570: "subtype 0x%04hx.\n", frame->type, frame->subtype);
571: g_free(frame);
572: return;
573: }
574:
575: if (frame->flags & 0x0008)
576: {
577: /* I had to leave this. It's just too funny. It reminds me of my sister. */
578: purple_debug_info("oscar", "ohmigod! %s has started typing "
579: "(DirectIM). He's going to send you a message! "
580: "*squeal*\n", conn->bn);
581: serv_got_typing(gc, conn->bn, 0, PURPLE_TYPING);
582: }
583: else if (frame->flags & 0x0004)
584: {
585: serv_got_typing(gc, conn->bn, 0, PURPLE_TYPED);
586: }
587: else
588: {
589: serv_got_typing_stopped(gc, conn->bn);
590: }
591:
592: if (frame->payload.len > 0)
593: {
594: if (frame->payload.len > DIRECTIM_MAX_FILESIZE)
595: {
596: gchar *tmp, *size1, *size2;
597: PurpleAccount *account;
598: PurpleConversation *conv;
599:
600: size1 = purple_str_size_to_units(frame->payload.len);
601: size2 = purple_str_size_to_units(DIRECTIM_MAX_FILESIZE);
602: 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);
603: g_free(size1);
604: g_free(size2);
605:
606: account = purple_connection_get_account(conn->od->gc);
607: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
608: purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
609: g_free(tmp);
610:
611: peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL);
612: g_free(frame);
613: return;
614: }
615:
616: /* We have payload data! Switch to the ODC watcher to read it. */
617: frame->payload.data = g_new(guint8, frame->payload.len + 1);
618: frame->payload.offset = 0;
619: conn->frame = frame;
620: purple_input_remove(conn->watcher_incoming);
621: conn->watcher_incoming = purple_input_add(conn->fd,
622: PURPLE_INPUT_READ, peer_odc_recv_cb, conn);
623: return;
624: }
625:
626: g_free(frame);
627: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>