source: protocols/jabber/message.c @ 64b8c9f

Last change on this file since 64b8c9f was fa8f57b, checked in by dequis <dx@…>, at 2015-11-21T03:01:05Z

jabber: Implement carbons (XEP-0280)

"Message carbons" (XEP-0280) is a server feature to get copies of
outgoing messages sent from other clients connected to the same account.
It's not widely supported by most public XMPP servers (easier if you
host your own), but this will probably change in the next few years.

This is enabled by default if the server supports it. It can also be
disabled with the "carbons" account setting.

Loosely based on a patch by kormat from trac ticket 1021. (Thanks!)
I moved stuff around, simplified things, fixed a few bugs, and used the
new self-messages feature.

  • Property mode set to 100644
File size: 7.6 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Handling of message(s) (tags), etc                       *
5*                                                                           *
6*  Copyright 2006-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25
26static xt_status jabber_pkt_message_normal(struct xt_node *node, gpointer data, gboolean carbons_sent)
27{
28        struct im_connection *ic = data;
29        char *from = xt_find_attr(node, carbons_sent ? "to" : "from");
30        char *type = xt_find_attr(node, "type");
31        char *id = xt_find_attr(node, "id");
32        struct xt_node *body = xt_find_node(node->children, "body"), *c;
33        struct xt_node *request = xt_find_node(node->children, "request");
34        struct jabber_buddy *bud = NULL;
35        char *s, *room = NULL, *reason = NULL;
36
37        if (!from) {
38                return XT_HANDLED; /* Consider this packet corrupted. */
39        }
40
41        if (request && id && g_strcmp0(type, "groupchat") != 0 && !carbons_sent) {
42                /* Send a message receipt (XEP-0184), looking like this:
43                 * <message from='...' id='...' to='...'>
44                 *  <received xmlns='urn:xmpp:receipts' id='richard2-4.1.247'/>
45                 * </message>
46                 *
47                 * MUC messages are excluded, since receipts aren't supposed to be sent over MUCs
48                 * (XEP-0184 section 5.3) and replying to those may result in 'forbidden' errors.
49                 */
50                struct xt_node *received, *receipt;
51
52                received = xt_new_node("received", NULL, NULL);
53                xt_add_attr(received, "xmlns", XMLNS_RECEIPTS);
54                xt_add_attr(received, "id", id);
55                receipt = jabber_make_packet("message", NULL, from, received);
56
57                jabber_write_packet(ic, receipt);
58                xt_free_node(receipt);
59        }
60
61        bud = jabber_buddy_by_jid(ic, from, GET_BUDDY_EXACT);
62
63        if (type && strcmp(type, "error") == 0) {
64                /* Handle type=error packet. */
65        } else if (type && from && strcmp(type, "groupchat") == 0) {
66                jabber_chat_pkt_message(ic, bud, node);
67        } else { /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */
68                GString *fullmsg = g_string_new("");
69
70                for (c = node->children; (c = xt_find_node(c, "x")); c = c->next) {
71                        char *ns = xt_find_attr(c, "xmlns");
72                        struct xt_node *inv;
73
74                        if (ns && strcmp(ns, XMLNS_MUC_USER) == 0 &&
75                            (inv = xt_find_node(c->children, "invite"))) {
76                                /* This is an invitation. Set some vars which
77                                   will be passed to imcb_chat_invite() below. */
78                                room = from;
79                                if ((from = xt_find_attr(inv, "from")) == NULL) {
80                                        from = room;
81                                }
82                                if ((inv = xt_find_node(inv->children, "reason")) && inv->text_len > 0) {
83                                        reason = inv->text;
84                                }
85                        }
86                }
87
88                if ((s = strchr(from, '/'))) {
89                        if (bud) {
90                                bud->last_msg = time(NULL);
91                                from = bud->ext_jid ? bud->ext_jid : bud->bare_jid;
92                        } else {
93                                *s = 0; /* We need to generate a bare JID now. */
94                        }
95                }
96
97                if (type && strcmp(type, "headline") == 0) {
98                        if ((c = xt_find_node(node->children, "subject")) && c->text_len > 0) {
99                                g_string_append_printf(fullmsg, "Headline: %s\n", c->text);
100                        }
101
102                        /* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */
103                        for (c = node->children; c; c = c->next) {
104                                struct xt_node *url;
105
106                                if ((url = xt_find_node(c->children, "url")) && url->text_len > 0) {
107                                        g_string_append_printf(fullmsg, "URL: %s\n", url->text);
108                                }
109                        }
110                } else if ((c = xt_find_node(node->children, "subject")) && c->text_len > 0 &&
111                           (!bud || !(bud->flags & JBFLAG_HIDE_SUBJECT))) {
112                        g_string_append_printf(fullmsg, "<< \002BitlBee\002 - Message with subject: %s >>\n", c->text);
113                        if (bud) {
114                                bud->flags |= JBFLAG_HIDE_SUBJECT;
115                        }
116                } else if (bud && !c) {
117                        /* Yeah, possibly we're hiding changes to this field now. But nobody uses
118                           this for anything useful anyway, except GMail when people reply to an
119                           e-mail via chat, repeating the same subject all the time. I don't want
120                           to have to remember full subject strings for everyone. */
121                        bud->flags &= ~JBFLAG_HIDE_SUBJECT;
122                }
123
124                if (body && body->text_len > 0) { /* Could be just a typing notification. */
125                        fullmsg = g_string_append(fullmsg, body->text);
126                }
127
128                if (fullmsg->len > 0) {
129                        imcb_buddy_msg(ic, from, fullmsg->str,
130                                       carbons_sent ? OPT_SELFMESSAGE : 0, jabber_get_timestamp(node));
131                }
132                if (room) {
133                        imcb_chat_invite(ic, room, from, reason);
134                }
135
136                g_string_free(fullmsg, TRUE);
137
138                /* Handling of incoming typing notifications. */
139                if (bud == NULL || carbons_sent) {
140                        /* Can't handle these for unknown buddies.
141                           And ignore them if it's just carbons */
142                } else if (xt_find_node(node->children, "composing")) {
143                        bud->flags |= JBFLAG_DOES_XEP85;
144                        imcb_buddy_typing(ic, from, OPT_TYPING);
145                }
146                else if (xt_find_node(node->children, "active")) {
147                        bud->flags |= JBFLAG_DOES_XEP85;
148
149                        /* No need to send a "stopped typing" signal when there's a message. */
150                        if (body == NULL) {
151                                imcb_buddy_typing(ic, from, 0);
152                        }
153                } else if (xt_find_node(node->children, "paused")) {
154                        bud->flags |= JBFLAG_DOES_XEP85;
155                        imcb_buddy_typing(ic, from, OPT_THINKING);
156                }
157
158                if (s) {
159                        *s = '/'; /* And convert it back to a full JID. */
160                }
161        }
162
163        return XT_HANDLED;
164}
165
166static xt_status jabber_carbons_message(struct xt_node *node, gpointer data)
167{
168        struct im_connection *ic = data;
169        struct xt_node *wrap, *fwd, *msg;
170        gboolean carbons_sent;
171
172        if ((wrap = xt_find_node(node->children, "received"))) {
173                carbons_sent = FALSE;
174        } else if ((wrap = xt_find_node(node->children, "sent"))) {
175                carbons_sent = TRUE;
176        }
177
178        if (wrap == NULL || g_strcmp0(xt_find_attr(wrap, "xmlns"), XMLNS_CARBONS) != 0) {
179                return XT_NEXT;
180        }
181
182        if (!(fwd = xt_find_node(wrap->children, "forwarded")) ||
183             (g_strcmp0(xt_find_attr(fwd, "xmlns"), XMLNS_FORWARDING) != 0) ||
184            !(msg = xt_find_node(fwd->children, "message"))) {
185                imcb_log(ic, "Error: Invalid carbons message received");
186                return XT_ABORT;
187        }
188
189        return jabber_pkt_message_normal(msg, data, carbons_sent);
190}
191
192xt_status jabber_pkt_message(struct xt_node *node, gpointer data)
193{
194        struct im_connection *ic = data;
195        struct jabber_data *jd = ic->proto_data;
196        char *from = xt_find_attr(node, "from");
197
198        if (jabber_compare_jid(jd->me, from)) {    /* Probably a Carbons message */
199                xt_status st = jabber_carbons_message(node, data);
200                if (st == XT_HANDLED || st == XT_ABORT) {
201                        return st;
202                }
203        }
204        return jabber_pkt_message_normal(node, data, FALSE);
205}
Note: See TracBrowser for help on using the repository browser.