|
10969
|
1
|
|
|
2 /*
|
|
|
3 Meanwhile - Unofficial Lotus Sametime Community Client Library
|
|
|
4 Copyright (C) 2004 Christopher (siege) O'Brien
|
|
|
5
|
|
|
6 This library is free software; you can redistribute it and/or
|
|
|
7 modify it under the terms of the GNU Library 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 Library General Public License for more details.
|
|
|
15
|
|
|
16 You should have received a copy of the GNU Library General Public
|
|
|
17 License along with this library; if not, write to the Free
|
|
|
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
19 */
|
|
|
20
|
|
|
21 #include <glib.h>
|
|
|
22 #include <glib/glist.h>
|
|
|
23 #include <string.h>
|
|
|
24
|
|
|
25 #include "mw_channel.h"
|
|
|
26 #include "mw_debug.h"
|
|
|
27 #include "mw_error.h"
|
|
|
28 #include "mw_message.h"
|
|
|
29 #include "mw_service.h"
|
|
|
30 #include "mw_session.h"
|
|
|
31 #include "mw_srvc_im.h"
|
|
|
32 #include "mw_util.h"
|
|
|
33
|
|
|
34
|
|
|
35 #define PROTOCOL_TYPE 0x00001000
|
|
|
36 #define PROTOCOL_VER 0x00000003
|
|
|
37
|
|
|
38
|
|
|
39 /* data for the addtl blocks of channel creation */
|
|
|
40 #define mwImAddtlA_NORMAL 0x00000001
|
|
|
41
|
|
|
42 #define mwImAddtlB_NORMAL 0x00000001 /**< standard */
|
|
|
43 #define mwImAddtlB_PRECONF 0x00000019 /**< pre-conference chat */
|
|
|
44 #define mwImAddtlB_NOTESBUDDY 0x00033453 /**< notesbuddy */
|
|
|
45
|
|
|
46 #define mwImAddtlC_NORMAL 0x00000002
|
|
|
47
|
|
|
48
|
|
|
49 /* send-on-channel message type */
|
|
|
50 #define msg_MESSAGE 0x0064 /**< IM message */
|
|
|
51
|
|
|
52
|
|
|
53 /* which type of im? */
|
|
|
54 enum mwImType {
|
|
|
55 mwIm_TEXT = 0x00000001, /**< text message */
|
|
|
56 mwIm_DATA = 0x00000002, /**< status message (usually) */
|
|
|
57 };
|
|
|
58
|
|
|
59
|
|
|
60 /* which type of data im? */
|
|
|
61 enum mwImDataType {
|
|
|
62 mwImData_TYPING = 0x00000001, /**< common use typing indicator */
|
|
|
63 mwImData_SUBJECT = 0x00000003, /**< notesbuddy IM topic */
|
|
|
64 mwImData_HTML = 0x00000004, /**< notesbuddy HTML message */
|
|
|
65 mwImData_MIME = 0x00000005, /**< notesbuddy MIME message, w/image */
|
|
|
66
|
|
|
67 mwImData_INVITE = 0x0000000a, /**< Places invitation */
|
|
|
68
|
|
|
69 mwImData_MULTI_START = 0x00001388,
|
|
|
70 mwImData_MULTI_STOP = 0x00001389,
|
|
|
71 };
|
|
|
72
|
|
|
73
|
|
|
74 /** @todo might be appropriate to make a couple of hashtables to
|
|
|
75 reference conversations by channel and target */
|
|
|
76 struct mwServiceIm {
|
|
|
77 struct mwService service;
|
|
|
78
|
|
|
79 enum mwImClientType features;
|
|
|
80
|
|
|
81 struct mwImHandler *handler;
|
|
|
82 GList *convs; /**< list of struct im_convo */
|
|
|
83 };
|
|
|
84
|
|
|
85
|
|
|
86 struct mwConversation {
|
|
|
87 struct mwServiceIm *service; /**< owning service */
|
|
|
88 struct mwChannel *channel; /**< channel */
|
|
|
89 struct mwIdBlock target; /**< conversation target */
|
|
|
90
|
|
|
91 /** state of the conversation, based loosely on the state of its
|
|
|
92 underlying channel */
|
|
|
93 enum mwConversationState state;
|
|
|
94
|
|
|
95 enum mwImClientType features;
|
|
|
96
|
|
|
97 GString *multi; /**< buffer for multi-chunk message */
|
|
|
98 enum mwImSendType multi_type; /**< type of incoming multi-chunk message */
|
|
|
99
|
|
|
100 struct mw_datum client_data;
|
|
|
101 };
|
|
|
102
|
|
|
103
|
|
|
104 /** momentarily places a mwLoginInfo into a mwIdBlock */
|
|
|
105 static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) {
|
|
|
106 to->user = from->user_id;
|
|
|
107 to->community = from->community;
|
|
|
108 }
|
|
|
109
|
|
|
110
|
|
|
111 static struct mwConversation *convo_find_by_user(struct mwServiceIm *srvc,
|
|
|
112 struct mwIdBlock *to) {
|
|
|
113 GList *l;
|
|
|
114
|
|
|
115 for(l = srvc->convs; l; l = l->next) {
|
|
|
116 struct mwConversation *c = l->data;
|
|
|
117 if(mwIdBlock_equal(&c->target, to))
|
|
|
118 return c;
|
|
|
119 }
|
|
|
120
|
|
|
121 return NULL;
|
|
|
122 }
|
|
|
123
|
|
|
124
|
|
|
125 static const char *conv_state_str(enum mwConversationState state) {
|
|
|
126 switch(state) {
|
|
|
127 case mwConversation_CLOSED:
|
|
|
128 return "closed";
|
|
|
129
|
|
|
130 case mwConversation_OPEN:
|
|
|
131 return "open";
|
|
|
132
|
|
|
133 case mwConversation_PENDING:
|
|
|
134 return "pending";
|
|
|
135
|
|
|
136 case mwConversation_UNKNOWN:
|
|
|
137 default:
|
|
|
138 return "UNKNOWN";
|
|
|
139 }
|
|
|
140 }
|
|
|
141
|
|
|
142
|
|
|
143 static void convo_set_state(struct mwConversation *conv,
|
|
|
144 enum mwConversationState state) {
|
|
|
145
|
|
|
146 g_return_if_fail(conv != NULL);
|
|
|
147
|
|
|
148 if(conv->state != state) {
|
|
|
149 g_info("setting conversation (%s, %s) state: %s",
|
|
|
150 NSTR(conv->target.user), NSTR(conv->target.community),
|
|
|
151 conv_state_str(state));
|
|
|
152 conv->state = state;
|
|
|
153 }
|
|
|
154 }
|
|
|
155
|
|
|
156
|
|
|
157 struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc,
|
|
|
158 struct mwIdBlock *to) {
|
|
|
159 g_return_val_if_fail(srvc != NULL, NULL);
|
|
|
160 g_return_val_if_fail(to != NULL, NULL);
|
|
|
161
|
|
|
162 return convo_find_by_user(srvc, to);
|
|
|
163 }
|
|
|
164
|
|
|
165
|
|
|
166 struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc,
|
|
|
167 struct mwIdBlock *to) {
|
|
|
168 struct mwConversation *c;
|
|
|
169
|
|
|
170 g_return_val_if_fail(srvc != NULL, NULL);
|
|
|
171 g_return_val_if_fail(to != NULL, NULL);
|
|
|
172
|
|
|
173 c = convo_find_by_user(srvc, to);
|
|
|
174 if(! c) {
|
|
|
175 c = g_new0(struct mwConversation, 1);
|
|
|
176 c->service = srvc;
|
|
|
177 mwIdBlock_clone(&c->target, to);
|
|
|
178 c->state = mwConversation_CLOSED;
|
|
|
179 c->features = srvc->features;
|
|
|
180
|
|
|
181 srvc->convs = g_list_prepend(srvc->convs, c);
|
|
|
182 }
|
|
|
183
|
|
|
184 return c;
|
|
|
185 }
|
|
|
186
|
|
|
187
|
|
|
188 static void convo_create_chan(struct mwConversation *c) {
|
|
|
189 struct mwSession *s;
|
|
|
190 struct mwChannelSet *cs;
|
|
|
191 struct mwChannel *chan;
|
|
|
192 struct mwLoginInfo *login;
|
|
|
193 struct mwPutBuffer *b;
|
|
|
194
|
|
|
195 /* we only should be calling this if there isn't a channel already
|
|
|
196 associated with the conversation */
|
|
|
197 g_return_if_fail(c != NULL);
|
|
|
198 g_return_if_fail(mwConversation_isPending(c));
|
|
|
199 g_return_if_fail(c->channel == NULL);
|
|
|
200
|
|
|
201 s = mwService_getSession(MW_SERVICE(c->service));
|
|
|
202 cs = mwSession_getChannels(s);
|
|
|
203
|
|
|
204 chan = mwChannel_newOutgoing(cs);
|
|
|
205 mwChannel_setService(chan, MW_SERVICE(c->service));
|
|
|
206 mwChannel_setProtoType(chan, PROTOCOL_TYPE);
|
|
|
207 mwChannel_setProtoVer(chan, PROTOCOL_VER);
|
|
|
208
|
|
|
209 /* offer all known ciphers */
|
|
|
210 mwChannel_populateSupportedCipherInstances(chan);
|
|
|
211
|
|
|
212 /* set the target */
|
|
|
213 login = mwChannel_getUser(chan);
|
|
|
214 login->user_id = g_strdup(c->target.user);
|
|
|
215 login->community = g_strdup(c->target.community);
|
|
|
216
|
|
|
217 /* compose the addtl create, with optional FANCY HTML! */
|
|
|
218 b = mwPutBuffer_new();
|
|
|
219 guint32_put(b, mwImAddtlA_NORMAL);
|
|
|
220 guint32_put(b, c->features);
|
|
|
221 mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
|
|
|
222
|
|
|
223 c->channel = mwChannel_create(chan)? NULL: chan;
|
|
|
224 if(c->channel) {
|
|
|
225 mwChannel_setServiceData(c->channel, c, NULL);
|
|
|
226 }
|
|
|
227 }
|
|
|
228
|
|
|
229
|
|
|
230 void mwConversation_open(struct mwConversation *conv) {
|
|
|
231 g_return_if_fail(conv != NULL);
|
|
|
232 g_return_if_fail(mwConversation_isClosed(conv));
|
|
|
233
|
|
|
234 convo_set_state(conv, mwConversation_PENDING);
|
|
|
235 convo_create_chan(conv);
|
|
|
236 }
|
|
|
237
|
|
|
238
|
|
|
239 static void convo_opened(struct mwConversation *conv) {
|
|
|
240 struct mwImHandler *h = NULL;
|
|
|
241
|
|
|
242 g_return_if_fail(conv != NULL);
|
|
|
243 g_return_if_fail(conv->service != NULL);
|
|
|
244
|
|
|
245 convo_set_state(conv, mwConversation_OPEN);
|
|
|
246 h = conv->service->handler;
|
|
|
247
|
|
|
248 g_return_if_fail(h != NULL);
|
|
|
249
|
|
|
250 if(h->conversation_opened)
|
|
|
251 h->conversation_opened(conv);
|
|
|
252 }
|
|
|
253
|
|
|
254
|
|
|
255 static void convo_free(struct mwConversation *conv) {
|
|
|
256 struct mwServiceIm *srvc;
|
|
|
257
|
|
|
258 mwConversation_removeClientData(conv);
|
|
|
259
|
|
|
260 srvc = conv->service;
|
|
|
261 srvc->convs = g_list_remove(srvc->convs, conv);
|
|
|
262
|
|
|
263 mwIdBlock_clear(&conv->target);
|
|
|
264 g_free(conv);
|
|
|
265 }
|
|
|
266
|
|
|
267
|
|
|
268 static int send_accept(struct mwConversation *c) {
|
|
|
269
|
|
|
270 struct mwChannel *chan = c->channel;
|
|
|
271 struct mwSession *s = mwChannel_getSession(chan);
|
|
|
272 struct mwUserStatus *stat = mwSession_getUserStatus(s);
|
|
|
273
|
|
|
274 struct mwPutBuffer *b;
|
|
|
275 struct mwOpaque *o;
|
|
|
276
|
|
|
277 b = mwPutBuffer_new();
|
|
|
278 guint32_put(b, mwImAddtlA_NORMAL);
|
|
|
279 guint32_put(b, c->features);
|
|
|
280 guint32_put(b, mwImAddtlC_NORMAL);
|
|
|
281 mwUserStatus_put(b, stat);
|
|
|
282
|
|
|
283 o = mwChannel_getAddtlAccept(chan);
|
|
|
284 mwOpaque_clear(o);
|
|
|
285 mwPutBuffer_finalize(o, b);
|
|
|
286
|
|
|
287 return mwChannel_accept(chan);
|
|
|
288 }
|
|
|
289
|
|
|
290
|
|
|
291 static void recv_channelCreate(struct mwService *srvc,
|
|
|
292 struct mwChannel *chan,
|
|
|
293 struct mwMsgChannelCreate *msg) {
|
|
|
294
|
|
|
295 /* - ensure it's the right service,proto,and proto ver
|
|
|
296 - check the opaque for the right opaque junk
|
|
|
297 - if not, close channel
|
|
|
298 - compose & send a channel accept
|
|
|
299 */
|
|
|
300
|
|
|
301 struct mwServiceIm *srvc_im = (struct mwServiceIm *) srvc;
|
|
|
302 struct mwImHandler *handler;
|
|
|
303 struct mwSession *s;
|
|
|
304 struct mwUserStatus *stat;
|
|
|
305 guint32 x, y, z;
|
|
|
306 struct mwGetBuffer *b;
|
|
|
307 struct mwConversation *c = NULL;
|
|
|
308 struct mwIdBlock idb;
|
|
|
309
|
|
|
310 handler = srvc_im->handler;
|
|
|
311 s = mwChannel_getSession(chan);
|
|
|
312 stat = mwSession_getUserStatus(s);
|
|
|
313
|
|
|
314 /* ensure the appropriate service/proto/ver */
|
|
|
315 x = mwChannel_getServiceId(chan);
|
|
|
316 y = mwChannel_getProtoType(chan);
|
|
|
317 z = mwChannel_getProtoVer(chan);
|
|
|
318
|
|
|
319 if( (x != SERVICE_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) {
|
|
|
320 g_warning("unacceptable service, proto, ver:"
|
|
|
321 " 0x%08x, 0x%08x, 0x%08x", x, y, z);
|
|
|
322 mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
|
|
|
323 return;
|
|
|
324 }
|
|
|
325
|
|
|
326 /* act upon the values in the addtl block */
|
|
|
327 b = mwGetBuffer_wrap(&msg->addtl);
|
|
|
328 guint32_get(b, &x);
|
|
|
329 guint32_get(b, &y);
|
|
|
330 z = (guint) mwGetBuffer_error(b);
|
|
|
331 mwGetBuffer_free(b);
|
|
|
332
|
|
|
333 if(z /* buffer error, BOOM! */ ) {
|
|
|
334 g_warning("bad/malformed addtl in IM service");
|
|
|
335 mwChannel_destroy(chan, ERR_FAILURE, NULL);
|
|
|
336 return;
|
|
|
337
|
|
|
338 } else if(x != mwImAddtlA_NORMAL) {
|
|
|
339 g_message("unknown params: 0x%08x, 0x%08x", x, y);
|
|
|
340 mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
|
|
|
341 return;
|
|
|
342
|
|
|
343 } else if(y == mwImAddtlB_PRECONF) {
|
|
|
344 if(! handler->place_invite) {
|
|
|
345 g_info("rejecting place-invite channel");
|
|
|
346 mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
|
|
|
347 return;
|
|
|
348
|
|
|
349 } else {
|
|
|
350 g_info("accepting place-invite channel");
|
|
|
351 }
|
|
|
352
|
|
|
353 } else if(y != mwImClient_PLAIN && y != srvc_im->features) {
|
|
|
354 /** reject what we do not understand */
|
|
|
355 mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
|
|
|
356 return;
|
|
|
357
|
|
|
358 } else if(stat->status == mwStatus_BUSY) {
|
|
|
359 g_info("rejecting IM channel due to DND status");
|
|
|
360 mwChannel_destroy(chan, ERR_CLIENT_USER_DND, NULL);
|
|
|
361 return;
|
|
|
362 }
|
|
|
363
|
|
|
364 login_into_id(&idb, mwChannel_getUser(chan));
|
|
|
365
|
|
|
366 #if 0
|
|
|
367 c = convo_find_by_user(srvc_im, &idb);
|
|
|
368 #endif
|
|
|
369
|
|
|
370 if(! c) {
|
|
|
371 c = g_new0(struct mwConversation, 1);
|
|
|
372 c->service = srvc_im;
|
|
|
373 srvc_im->convs = g_list_prepend(srvc_im->convs, c);
|
|
|
374 }
|
|
|
375
|
|
|
376 #if 0
|
|
|
377 /* we're going to re-associate any existing conversations with this
|
|
|
378 new channel. That means closing any existing ones */
|
|
|
379 if(c->channel) {
|
|
|
380 g_info("closing existing IM channel 0x%08x", mwChannel_getId(c->channel));
|
|
|
381 mwConversation_close(c, ERR_SUCCESS);
|
|
|
382 }
|
|
|
383 #endif
|
|
|
384
|
|
|
385 /* set up the conversation with this channel, target, and be fancy
|
|
|
386 if the other side requested it */
|
|
|
387 c->channel = chan;
|
|
|
388 mwIdBlock_clone(&c->target, &idb);
|
|
|
389 c->features = y;
|
|
|
390 convo_set_state(c, mwConversation_PENDING);
|
|
|
391 mwChannel_setServiceData(c->channel, c, NULL);
|
|
|
392
|
|
|
393 if(send_accept(c)) {
|
|
|
394 g_warning("sending IM channel accept failed");
|
|
|
395 mwConversation_free(c);
|
|
|
396
|
|
|
397 } else {
|
|
|
398 convo_opened(c);
|
|
|
399 }
|
|
|
400 }
|
|
|
401
|
|
|
402
|
|
|
403 static void recv_channelAccept(struct mwService *srvc, struct mwChannel *chan,
|
|
|
404 struct mwMsgChannelAccept *msg) {
|
|
|
405
|
|
|
406 struct mwConversation *conv;
|
|
|
407
|
|
|
408 conv = mwChannel_getServiceData(chan);
|
|
|
409 if(! conv) {
|
|
|
410 g_warning("received channel accept for non-existant conversation");
|
|
|
411 mwChannel_destroy(chan, ERR_FAILURE, NULL);
|
|
|
412 return;
|
|
|
413 }
|
|
|
414
|
|
|
415 convo_opened(conv);
|
|
|
416 }
|
|
|
417
|
|
|
418
|
|
|
419 static void recv_channelDestroy(struct mwService *srvc, struct mwChannel *chan,
|
|
|
420 struct mwMsgChannelDestroy *msg) {
|
|
|
421
|
|
|
422 struct mwConversation *c;
|
|
|
423
|
|
|
424 c = mwChannel_getServiceData(chan);
|
|
|
425 g_return_if_fail(c != NULL);
|
|
|
426
|
|
|
427 c->channel = NULL;
|
|
|
428
|
|
|
429 if(mwChannel_isState(chan, mwChannel_ERROR)) {
|
|
|
430
|
|
|
431 /* checking for failure on the receiving end to accept html
|
|
|
432 messages. Fail-over to a non-html format on a new channel for
|
|
|
433 the convo */
|
|
|
434 if(c->features != mwImClient_PLAIN
|
|
|
435 && msg->reason == ERR_IM_NOT_REGISTERED) {
|
|
|
436
|
|
|
437 g_debug("falling back on a plaintext conversation");
|
|
|
438 c->features = mwImClient_PLAIN;
|
|
|
439 convo_create_chan(c);
|
|
|
440 return;
|
|
|
441 }
|
|
|
442 }
|
|
|
443
|
|
|
444 mwConversation_close(c, msg->reason);
|
|
|
445 }
|
|
|
446
|
|
|
447
|
|
|
448 static void convo_recv(struct mwConversation *conv, enum mwImSendType type,
|
|
|
449 gconstpointer msg) {
|
|
|
450
|
|
|
451 struct mwServiceIm *srvc;
|
|
|
452 struct mwImHandler *handler;
|
|
|
453
|
|
|
454 g_return_if_fail(conv != NULL);
|
|
|
455
|
|
|
456 srvc = conv->service;
|
|
|
457 g_return_if_fail(srvc != NULL);
|
|
|
458
|
|
|
459 handler = srvc->handler;
|
|
|
460 if(handler && handler->conversation_recv)
|
|
|
461 handler->conversation_recv(conv, type, msg);
|
|
|
462 }
|
|
|
463
|
|
|
464
|
|
|
465 static void convo_multi_start(struct mwConversation *conv) {
|
|
|
466 g_return_if_fail(conv->multi == NULL);
|
|
|
467 conv->multi = g_string_new(NULL);
|
|
|
468 }
|
|
|
469
|
|
|
470
|
|
|
471 static void convo_multi_stop(struct mwConversation *conv) {
|
|
|
472
|
|
|
473 g_return_if_fail(conv->multi != NULL);
|
|
|
474
|
|
|
475 /* actually send it */
|
|
|
476 convo_recv(conv, conv->multi_type, conv->multi->str);
|
|
|
477
|
|
|
478 /* clear up the multi buffer */
|
|
|
479 g_string_free(conv->multi, TRUE);
|
|
|
480 conv->multi = NULL;
|
|
|
481 }
|
|
|
482
|
|
|
483
|
|
|
484 static void recv_text(struct mwServiceIm *srvc, struct mwChannel *chan,
|
|
|
485 struct mwGetBuffer *b) {
|
|
|
486
|
|
|
487 struct mwConversation *c;
|
|
|
488 char *text = NULL;
|
|
|
489
|
|
|
490 mwString_get(b, &text);
|
|
|
491
|
|
|
492 if(! text) return;
|
|
|
493
|
|
|
494 c = mwChannel_getServiceData(chan);
|
|
|
495 if(c) {
|
|
|
496 if(c->multi) {
|
|
|
497 g_string_append(c->multi, text);
|
|
|
498
|
|
|
499 } else {
|
|
|
500 convo_recv(c, mwImSend_PLAIN, text);
|
|
|
501 }
|
|
|
502 }
|
|
|
503
|
|
|
504 g_free(text);
|
|
|
505 }
|
|
|
506
|
|
|
507
|
|
|
508 static void convo_invite(struct mwConversation *conv,
|
|
|
509 struct mwOpaque *o) {
|
|
|
510
|
|
|
511 struct mwServiceIm *srvc;
|
|
|
512 struct mwImHandler *handler;
|
|
|
513
|
|
|
514 struct mwGetBuffer *b;
|
|
|
515 char *title, *name, *msg;
|
|
|
516
|
|
|
517 g_info("convo_invite");
|
|
|
518
|
|
|
519 srvc = conv->service;
|
|
|
520 handler = srvc->handler;
|
|
|
521
|
|
|
522 g_return_if_fail(handler != NULL);
|
|
|
523 g_return_if_fail(handler->place_invite != NULL);
|
|
|
524
|
|
|
525 b = mwGetBuffer_wrap(o);
|
|
|
526 mwGetBuffer_advance(b, 4);
|
|
|
527 mwString_get(b, &title);
|
|
|
528 mwString_get(b, &msg);
|
|
|
529 mwGetBuffer_advance(b, 19);
|
|
|
530 mwString_get(b, &name);
|
|
|
531
|
|
|
532 if(! mwGetBuffer_error(b)) {
|
|
|
533 handler->place_invite(conv, msg, title, name);
|
|
|
534 }
|
|
|
535
|
|
|
536 mwGetBuffer_free(b);
|
|
|
537 g_free(msg);
|
|
|
538 g_free(title);
|
|
|
539 g_free(name);
|
|
|
540 }
|
|
|
541
|
|
|
542
|
|
|
543 static void recv_data(struct mwServiceIm *srvc, struct mwChannel *chan,
|
|
|
544 struct mwGetBuffer *b) {
|
|
|
545
|
|
|
546 struct mwConversation *conv;
|
|
|
547 guint32 type, subtype;
|
|
|
548 struct mwOpaque o = { 0, 0 };
|
|
|
549 char *x;
|
|
|
550
|
|
|
551 guint32_get(b, &type);
|
|
|
552 guint32_get(b, &subtype);
|
|
|
553 mwOpaque_get(b, &o);
|
|
|
554
|
|
|
555 if(mwGetBuffer_error(b)) {
|
|
|
556 mwOpaque_clear(&o);
|
|
|
557 return;
|
|
|
558 }
|
|
|
559
|
|
|
560 conv = mwChannel_getServiceData(chan);
|
|
|
561 if(! conv) return;
|
|
|
562
|
|
|
563 switch(type) {
|
|
|
564 case mwImData_TYPING:
|
|
|
565 convo_recv(conv, mwImSend_TYPING, GINT_TO_POINTER(! subtype));
|
|
|
566 break;
|
|
|
567
|
|
|
568 case mwImData_HTML:
|
|
|
569 if(o.len) {
|
|
|
570 if(conv->multi) {
|
|
|
571 g_string_append_len(conv->multi, o.data, o.len);
|
|
|
572 conv->multi_type = mwImSend_HTML;
|
|
|
573
|
|
|
574 } else {
|
|
|
575 x = g_strndup(o.data, o.len);
|
|
|
576 convo_recv(conv, mwImSend_HTML, x);
|
|
|
577 g_free(x);
|
|
|
578 }
|
|
|
579 }
|
|
|
580 break;
|
|
|
581
|
|
|
582 case mwImData_SUBJECT:
|
|
|
583 x = g_strndup(o.data, o.len);
|
|
|
584 convo_recv(conv, mwImSend_SUBJECT, x);
|
|
|
585 g_free(x);
|
|
|
586 break;
|
|
|
587
|
|
|
588 case mwImData_MIME:
|
|
|
589 if(conv->multi) {
|
|
|
590 g_string_append_len(conv->multi, o.data, o.len);
|
|
|
591 conv->multi_type = mwImSend_MIME;
|
|
|
592
|
|
|
593 } else {
|
|
|
594 x = g_strndup(o.data, o.len);
|
|
|
595 convo_recv(conv, mwImSend_MIME, x);
|
|
|
596 g_free(x);
|
|
|
597 }
|
|
|
598 break;
|
|
|
599
|
|
|
600 case mwImData_INVITE:
|
|
|
601 convo_invite(conv, &o);
|
|
|
602 break;
|
|
|
603
|
|
|
604 case mwImData_MULTI_START:
|
|
|
605 convo_multi_start(conv);
|
|
|
606 break;
|
|
|
607
|
|
|
608 case mwImData_MULTI_STOP:
|
|
|
609 convo_multi_stop(conv);
|
|
|
610 break;
|
|
|
611
|
|
|
612 default:
|
|
|
613
|
|
|
614 mw_debug_mailme(&o, "unknown data message type in IM service:"
|
|
|
615 " (0x%08x, 0x%08x)", type, subtype);
|
|
|
616 }
|
|
|
617
|
|
|
618 mwOpaque_clear(&o);
|
|
|
619 }
|
|
|
620
|
|
|
621
|
|
|
622 static void recv(struct mwService *srvc, struct mwChannel *chan,
|
|
|
623 guint16 type, struct mwOpaque *data) {
|
|
|
624
|
|
|
625 /* - ensure message type is something we want
|
|
|
626 - parse message type into either mwIMText or mwIMData
|
|
|
627 - handle
|
|
|
628 */
|
|
|
629
|
|
|
630 struct mwGetBuffer *b;
|
|
|
631 guint32 mt;
|
|
|
632
|
|
|
633 g_return_if_fail(type == msg_MESSAGE);
|
|
|
634
|
|
|
635 b = mwGetBuffer_wrap(data);
|
|
|
636 guint32_get(b, &mt);
|
|
|
637
|
|
|
638 if(mwGetBuffer_error(b)) {
|
|
|
639 g_warning("failed to parse message for IM service");
|
|
|
640 mwGetBuffer_free(b);
|
|
|
641 return;
|
|
|
642 }
|
|
|
643
|
|
|
644 switch(mt) {
|
|
|
645 case mwIm_TEXT:
|
|
|
646 recv_text((struct mwServiceIm *) srvc, chan, b);
|
|
|
647 break;
|
|
|
648
|
|
|
649 case mwIm_DATA:
|
|
|
650 recv_data((struct mwServiceIm *) srvc, chan, b);
|
|
|
651 break;
|
|
|
652
|
|
|
653 default:
|
|
|
654 g_warning("unknown message type 0x%08x for IM service", mt);
|
|
|
655 }
|
|
|
656
|
|
|
657 if(mwGetBuffer_error(b))
|
|
|
658 g_warning("failed to parse message type 0x%08x for IM service", mt);
|
|
|
659
|
|
|
660 mwGetBuffer_free(b);
|
|
|
661 }
|
|
|
662
|
|
|
663
|
|
|
664 static void clear(struct mwServiceIm *srvc) {
|
|
|
665 struct mwImHandler *h;
|
|
|
666
|
|
|
667 while(srvc->convs)
|
|
|
668 convo_free(srvc->convs->data);
|
|
|
669
|
|
|
670 h = srvc->handler;
|
|
|
671 if(h && h->clear)
|
|
|
672 h->clear(srvc);
|
|
|
673 srvc->handler = NULL;
|
|
|
674 }
|
|
|
675
|
|
|
676
|
|
|
677 static const char *name(struct mwService *srvc) {
|
|
|
678 return "Instant Messaging";
|
|
|
679 }
|
|
|
680
|
|
|
681
|
|
|
682 static const char *desc(struct mwService *srvc) {
|
|
|
683 return "IM service with Standard and NotesBuddy features";
|
|
|
684 }
|
|
|
685
|
|
|
686
|
|
|
687 static void start(struct mwService *srvc) {
|
|
|
688 mwService_started(srvc);
|
|
|
689 }
|
|
|
690
|
|
|
691
|
|
|
692 static void stop(struct mwServiceIm *srvc) {
|
|
|
693
|
|
|
694 while(srvc->convs)
|
|
|
695 mwConversation_free(srvc->convs->data);
|
|
|
696
|
|
|
697 mwService_stopped(MW_SERVICE(srvc));
|
|
|
698 }
|
|
|
699
|
|
|
700
|
|
|
701 struct mwServiceIm *mwServiceIm_new(struct mwSession *session,
|
|
|
702 struct mwImHandler *hndl) {
|
|
|
703
|
|
|
704 struct mwServiceIm *srvc_im;
|
|
|
705 struct mwService *srvc;
|
|
|
706
|
|
|
707 g_return_val_if_fail(session != NULL, NULL);
|
|
|
708 g_return_val_if_fail(hndl != NULL, NULL);
|
|
|
709
|
|
|
710 srvc_im = g_new0(struct mwServiceIm, 1);
|
|
|
711 srvc = &srvc_im->service;
|
|
|
712
|
|
|
713 mwService_init(srvc, session, SERVICE_IM);
|
|
|
714 srvc->recv_create = recv_channelCreate;
|
|
|
715 srvc->recv_accept = recv_channelAccept;
|
|
|
716 srvc->recv_destroy = recv_channelDestroy;
|
|
|
717 srvc->recv = recv;
|
|
|
718 srvc->clear = (mwService_funcClear) clear;
|
|
|
719 srvc->get_name = name;
|
|
|
720 srvc->get_desc = desc;
|
|
|
721 srvc->start = start;
|
|
|
722 srvc->stop = (mwService_funcStop) stop;
|
|
|
723
|
|
|
724 srvc_im->features = mwImClient_PLAIN;
|
|
|
725 srvc_im->handler = hndl;
|
|
|
726
|
|
|
727 return srvc_im;
|
|
|
728 }
|
|
|
729
|
|
|
730
|
|
|
731 struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc) {
|
|
|
732 g_return_val_if_fail(srvc != NULL, NULL);
|
|
|
733 return srvc->handler;
|
|
|
734 }
|
|
|
735
|
|
|
736
|
|
|
737 gboolean mwServiceIm_supports(struct mwServiceIm *srvc,
|
|
|
738 enum mwImSendType type) {
|
|
|
739
|
|
|
740 g_return_val_if_fail(srvc != NULL, FALSE);
|
|
|
741
|
|
|
742 switch(type) {
|
|
|
743 case mwImSend_PLAIN:
|
|
|
744 case mwImSend_TYPING:
|
|
|
745 return TRUE;
|
|
|
746
|
|
|
747 case mwImSend_SUBJECT:
|
|
|
748 case mwImSend_HTML:
|
|
|
749 case mwImSend_MIME:
|
|
|
750 return srvc->features == mwImClient_NOTESBUDDY;
|
|
|
751
|
|
|
752 default:
|
|
|
753 return FALSE;
|
|
|
754 }
|
|
|
755 }
|
|
|
756
|
|
|
757
|
|
|
758 void mwServiceIm_setClientType(struct mwServiceIm *srvc,
|
|
|
759 enum mwImClientType type) {
|
|
|
760
|
|
|
761 g_return_if_fail(srvc != NULL);
|
|
|
762 srvc->features = type;
|
|
|
763 }
|
|
|
764
|
|
|
765
|
|
|
766 enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc) {
|
|
|
767 g_return_val_if_fail(srvc != NULL, mwImClient_UNKNOWN);
|
|
|
768 return srvc->features;
|
|
|
769 }
|
|
|
770
|
|
|
771
|
|
|
772 static int convo_sendText(struct mwConversation *conv, const char *text) {
|
|
|
773 struct mwPutBuffer *b;
|
|
|
774 struct mwOpaque o;
|
|
|
775 int ret;
|
|
|
776
|
|
|
777 b = mwPutBuffer_new();
|
|
|
778
|
|
|
779 guint32_put(b, mwIm_TEXT);
|
|
|
780 mwString_put(b, text);
|
|
|
781
|
|
|
782 mwPutBuffer_finalize(&o, b);
|
|
|
783 ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
|
|
|
784 mwOpaque_clear(&o);
|
|
|
785
|
|
|
786 return ret;
|
|
|
787 }
|
|
|
788
|
|
|
789
|
|
|
790 static int convo_sendHtml(struct mwConversation *conv, const char *html) {
|
|
|
791 struct mwPutBuffer *b;
|
|
|
792 struct mwOpaque o;
|
|
|
793 int ret;
|
|
|
794
|
|
|
795 b = mwPutBuffer_new();
|
|
|
796
|
|
|
797 guint32_put(b, mwIm_DATA);
|
|
|
798 guint32_put(b, mwImData_HTML);
|
|
|
799 guint32_put(b, 0x00);
|
|
|
800
|
|
|
801 /* use o first as a shell of an opaque for the text */
|
|
|
802 o.len = strlen(html);
|
|
|
803 o.data = (char *) html;
|
|
|
804 mwOpaque_put(b, &o);
|
|
|
805
|
|
|
806 /* use o again as the holder of the buffer's finalized data */
|
|
|
807 mwPutBuffer_finalize(&o, b);
|
|
|
808 ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
|
|
|
809 mwOpaque_clear(&o);
|
|
|
810
|
|
|
811 return ret;
|
|
|
812 }
|
|
|
813
|
|
|
814
|
|
|
815 static int convo_sendSubject(struct mwConversation *conv,
|
|
|
816 const char *subject) {
|
|
|
817 struct mwPutBuffer *b;
|
|
|
818 struct mwOpaque o;
|
|
|
819 int ret;
|
|
|
820
|
|
|
821 b = mwPutBuffer_new();
|
|
|
822
|
|
|
823 guint32_put(b, mwIm_DATA);
|
|
|
824 guint32_put(b, mwImData_SUBJECT);
|
|
|
825 guint32_put(b, 0x00);
|
|
|
826
|
|
|
827 /* use o first as a shell of an opaque for the text */
|
|
|
828 o.len = strlen(subject);
|
|
|
829 o.data = (char *) subject;
|
|
|
830 mwOpaque_put(b, &o);
|
|
|
831
|
|
|
832 /* use o again as the holder of the buffer's finalized data */
|
|
|
833 mwPutBuffer_finalize(&o, b);
|
|
|
834 ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
|
|
|
835 mwOpaque_clear(&o);
|
|
|
836
|
|
|
837 return ret;
|
|
|
838 }
|
|
|
839
|
|
|
840
|
|
|
841 static int convo_sendTyping(struct mwConversation *conv, gboolean typing) {
|
|
|
842 struct mwPutBuffer *b;
|
|
|
843 struct mwOpaque o = { 0, NULL };
|
|
|
844 int ret;
|
|
|
845
|
|
|
846 b = mwPutBuffer_new();
|
|
|
847
|
|
|
848 guint32_put(b, mwIm_DATA);
|
|
|
849 guint32_put(b, mwImData_TYPING);
|
|
|
850 guint32_put(b, !typing);
|
|
|
851
|
|
|
852 /* not to be confusing, but we're re-using o first as an empty
|
|
|
853 opaque, and later as the contents of the finalized buffer */
|
|
|
854 mwOpaque_put(b, &o);
|
|
|
855
|
|
|
856 mwPutBuffer_finalize(&o, b);
|
|
|
857 ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
|
|
|
858 mwOpaque_clear(&o);
|
|
|
859
|
|
|
860 return ret;
|
|
|
861 }
|
|
|
862
|
|
|
863
|
|
|
864 static int convo_sendMime(struct mwConversation *conv,
|
|
|
865 const char *mime) {
|
|
|
866 struct mwPutBuffer *b;
|
|
|
867 struct mwOpaque o;
|
|
|
868 int ret;
|
|
|
869
|
|
|
870 b = mwPutBuffer_new();
|
|
|
871
|
|
|
872 guint32_put(b, mwIm_DATA);
|
|
|
873 guint32_put(b, mwImData_MIME);
|
|
|
874 guint32_put(b, 0x00);
|
|
|
875
|
|
|
876 o.len = strlen(mime);
|
|
|
877 o.data = (char *) mime;
|
|
|
878 mwOpaque_put(b, &o);
|
|
|
879
|
|
|
880 mwPutBuffer_finalize(&o, b);
|
|
|
881 ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
|
|
|
882 mwOpaque_clear(&o);
|
|
|
883
|
|
|
884 return ret;
|
|
|
885 }
|
|
|
886
|
|
|
887
|
|
|
888 int mwConversation_send(struct mwConversation *conv, enum mwImSendType type,
|
|
|
889 gconstpointer msg) {
|
|
|
890
|
|
|
891 g_return_val_if_fail(conv != NULL, -1);
|
|
|
892 g_return_val_if_fail(mwConversation_isOpen(conv), -1);
|
|
|
893 g_return_val_if_fail(conv->channel != NULL, -1);
|
|
|
894
|
|
|
895 switch(type) {
|
|
|
896 case mwImSend_PLAIN:
|
|
|
897 return convo_sendText(conv, msg);
|
|
|
898 case mwImSend_TYPING:
|
|
|
899 return convo_sendTyping(conv, GPOINTER_TO_INT(msg));
|
|
|
900 case mwImSend_SUBJECT:
|
|
|
901 return convo_sendSubject(conv, msg);
|
|
|
902 case mwImSend_HTML:
|
|
|
903 return convo_sendHtml(conv, msg);
|
|
|
904 case mwImSend_MIME:
|
|
|
905 return convo_sendMime(conv, msg);
|
|
|
906
|
|
|
907 default:
|
|
|
908 g_warning("unsupported IM Send Type, 0x%x", type);
|
|
|
909 return -1;
|
|
|
910 }
|
|
|
911 }
|
|
|
912
|
|
|
913
|
|
|
914 enum mwConversationState mwConversation_getState(struct mwConversation *conv) {
|
|
|
915 g_return_val_if_fail(conv != NULL, mwConversation_UNKNOWN);
|
|
|
916 return conv->state;
|
|
|
917 }
|
|
|
918
|
|
|
919
|
|
|
920 struct mwServiceIm *mwConversation_getService(struct mwConversation *conv) {
|
|
|
921 g_return_val_if_fail(conv != NULL, NULL);
|
|
|
922 return conv->service;
|
|
|
923 }
|
|
|
924
|
|
|
925
|
|
|
926 gboolean mwConversation_supports(struct mwConversation *conv,
|
|
|
927 enum mwImSendType type) {
|
|
|
928 g_return_val_if_fail(conv != NULL, FALSE);
|
|
|
929
|
|
|
930 switch(type) {
|
|
|
931 case mwImSend_PLAIN:
|
|
|
932 case mwImSend_TYPING:
|
|
|
933 return TRUE;
|
|
|
934
|
|
|
935 case mwImSend_SUBJECT:
|
|
|
936 case mwImSend_HTML:
|
|
|
937 case mwImSend_MIME:
|
|
|
938 return conv->features == mwImClient_NOTESBUDDY;
|
|
|
939
|
|
|
940 default:
|
|
|
941 return FALSE;
|
|
|
942 }
|
|
|
943 }
|
|
|
944
|
|
|
945
|
|
|
946 enum mwImClientType
|
|
|
947 mwConversation_getClientType(struct mwConversation *conv) {
|
|
|
948 g_return_val_if_fail(conv != NULL, mwImClient_UNKNOWN);
|
|
|
949 return conv->features;
|
|
|
950 }
|
|
|
951
|
|
|
952
|
|
|
953 struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv) {
|
|
|
954 g_return_val_if_fail(conv != NULL, NULL);
|
|
|
955 return &conv->target;
|
|
|
956 }
|
|
|
957
|
|
|
958
|
|
|
959 struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv) {
|
|
|
960 g_return_val_if_fail(conv != NULL, NULL);
|
|
|
961 g_return_val_if_fail(conv->channel != NULL, NULL);
|
|
|
962 return mwChannel_getUser(conv->channel);
|
|
|
963 }
|
|
|
964
|
|
|
965
|
|
|
966 void mwConversation_setClientData(struct mwConversation *conv,
|
|
|
967 gpointer data, GDestroyNotify clean) {
|
|
|
968 g_return_if_fail(conv != NULL);
|
|
|
969 mw_datum_set(&conv->client_data, data, clean);
|
|
|
970 }
|
|
|
971
|
|
|
972
|
|
|
973 gpointer mwConversation_getClientData(struct mwConversation *conv) {
|
|
|
974 g_return_val_if_fail(conv != NULL, NULL);
|
|
|
975 return mw_datum_get(&conv->client_data);
|
|
|
976 }
|
|
|
977
|
|
|
978
|
|
|
979 void mwConversation_removeClientData(struct mwConversation *conv) {
|
|
|
980 g_return_if_fail(conv != NULL);
|
|
|
981 mw_datum_clear(&conv->client_data);
|
|
|
982 }
|
|
|
983
|
|
|
984
|
|
|
985 void mwConversation_close(struct mwConversation *conv, guint32 reason) {
|
|
|
986 struct mwServiceIm *srvc;
|
|
|
987 struct mwImHandler *h;
|
|
|
988
|
|
|
989 g_return_if_fail(conv != NULL);
|
|
|
990
|
|
|
991 convo_set_state(conv, mwConversation_CLOSED);
|
|
|
992
|
|
|
993 srvc = conv->service;
|
|
|
994 g_return_if_fail(srvc != NULL);
|
|
|
995
|
|
|
996 h = srvc->handler;
|
|
|
997 if(h && h->conversation_closed)
|
|
|
998 h->conversation_closed(conv, reason);
|
|
|
999
|
|
|
1000 if(conv->channel) {
|
|
|
1001 mwChannel_destroy(conv->channel, reason, NULL);
|
|
|
1002 conv->channel = NULL;
|
|
|
1003 }
|
|
|
1004 }
|
|
|
1005
|
|
|
1006
|
|
|
1007 void mwConversation_free(struct mwConversation *conv) {
|
|
|
1008 g_return_if_fail(conv != NULL);
|
|
|
1009
|
|
|
1010 if(! mwConversation_isClosed(conv))
|
|
|
1011 mwConversation_close(conv, ERR_SUCCESS);
|
|
|
1012
|
|
|
1013 convo_free(conv);
|
|
|
1014 }
|
|
|
1015
|