Mercurial > pidgin
annotate src/protocols/jabber/si.c @ 12116:e75ef7aa913e
[gaim-migrate @ 14416]
" This patch implements a replacement for the queuing
system from 1.x. It also obsoletes a previous patch
[#1338873] I submitted to prioritize the unseen states
in gtk conversations.
The attached envelope.png is ripped from the
msgunread.png already included in gaim. It should be
dropped in the pixmaps directory (Makefile.am is
updated accordingly in this patch).
The two separate queuing preferences from 1.x, queuing
messages while away and queuing all new messages (from
docklet), are replaced with a single 3-way preference
for conversations. The new preference is "Hide new IM
conversations". This preference can be set to never,
away and always.
When a gtk conversation is created, it may be placed in
a hidden conversation window instead of being placed
normally. This decision is based upon the preference
and possibly the away state of the account the
conversation is being created for. This *will* effect
conversations the user explicitly requests to be
created, so in these cases the caller must be sure to
present the conversation to the user, using
gaim_gtkconv_present_conversation(). This is done
already in gtkdialogs.c which handles creating
conversations requested by the user from gaim proper
(menus, double-clicking on budy in blist, etc.).
The main advantage to not queuing messages is that the
conversations exist, the message is written to the
conversation (and logged if appropriate) and the unseen
state is set on the conversation. This means no
additional features are needed to track whether there
are queued messages or not, just use the unseen state
on conversations.
Since conversations may not be visible (messages
"queued"), gaim proper needs some notification that
there are messages waiting. I opted for a menutray icon
that shows up when an im conversation has an unseen
message. Clicking this icon will focus (and show if
hidden) the first conversation with an unseen message.
This is essentially the same behavior of the docklet in
cvs right now, except that the icon is only visible
when there is a conversation with an unread message.
The api that is added is flexible enough to allow
either the docklet or the new blist menutray icon to be
visible for conversations of any/all types and for
unseen messages >= any state. Currently they are set to
only IM conversations and only unseen states >= TEXT
(system messages and no log messages will not trigger
blinking the docklet or showing the blist tray icon),
but these could be made preferences relatively easily
in the future. Other plugins could probably benefit as
well: gaim_gtk_conversations_get_first_unseen().
There is probably some limit to comment size, so I'll
stop rambling now. If anyone has more
questions/comments, catch me in #gaim, here or on
gaim-devel."
committer: Tailor Script <tailor@pidgin.im>
author | Luke Schierer <lschiere@pidgin.im> |
---|---|
date | Wed, 16 Nov 2005 18:17:01 +0000 |
parents | f9c5480ad0ce |
children | cbebda5f019c |
rev | line source |
---|---|
7395 | 1 /* |
2 * gaim - Jabber Protocol Plugin | |
3 * | |
4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or | |
9 * (at your option) any later version. | |
10 * | |
11 * This program 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 | |
14 * GNU General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program; if not, write to the Free Software | |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 * | |
20 */ | |
9030 | 21 |
22 #include "blist.h" | |
23 | |
7395 | 24 #include "internal.h" |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10112
diff
changeset
|
25 #include "cipher.h" |
7395 | 26 #include "debug.h" |
27 #include "ft.h" | |
8231
f50c059b6384
[gaim-migrate @ 8954]
Christian Hammond <chipx86@chipx86.com>
parents:
8135
diff
changeset
|
28 #include "network.h" |
7395 | 29 #include "notify.h" |
30 | |
31 #include "buddy.h" | |
8312 | 32 #include "disco.h" |
7395 | 33 #include "jabber.h" |
34 #include "iq.h" | |
35 #include "si.h" | |
36 | |
37 #include "si.h" | |
38 | |
8262 | 39 struct bytestreams_streamhost { |
40 char *jid; | |
41 char *host; | |
42 int port; | |
43 }; | |
44 | |
45 typedef struct _JabberSIXfer { | |
46 JabberStream *js; | |
47 | |
10940 | 48 gboolean accepted; |
49 | |
8262 | 50 char *stream_id; |
51 char *iq_id; | |
52 | |
53 enum { | |
54 STREAM_METHOD_UNKNOWN = 0, | |
55 STREAM_METHOD_BYTESTREAMS = 2 << 1, | |
56 STREAM_METHOD_IBB = 2 << 2, | |
57 STREAM_METHOD_UNSUPPORTED = 2 << 31 | |
58 } stream_method; | |
59 | |
60 GList *streamhosts; | |
61 GaimProxyInfo *gpi; | |
8316 | 62 |
63 char *rxqueue; | |
64 size_t rxlen; | |
8262 | 65 } JabberSIXfer; |
66 | |
67 static GaimXfer* | |
68 jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from) | |
7395 | 69 { |
70 GList *xfers; | |
71 | |
8262 | 72 if(!sid || !from) |
7395 | 73 return NULL; |
74 | |
75 for(xfers = js->file_transfers; xfers; xfers = xfers->next) { | |
76 GaimXfer *xfer = xfers->data; | |
77 JabberSIXfer *jsx = xfer->data; | |
8316 | 78 if(jsx->stream_id && xfer->who && |
79 !strcmp(jsx->stream_id, sid) && !strcmp(xfer->who, from)) | |
7395 | 80 return xfer; |
81 } | |
82 | |
83 return NULL; | |
84 } | |
85 | |
8262 | 86 |
87 static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer); | |
88 | |
89 static void jabber_si_bytestreams_connect_cb(gpointer data, gint source, GaimInputCondition cond) | |
90 { | |
7395 | 91 GaimXfer *xfer = data; |
92 JabberSIXfer *jsx = xfer->data; | |
8262 | 93 JabberIq *iq; |
94 xmlnode *query, *su; | |
95 struct bytestreams_streamhost *streamhost = jsx->streamhosts->data; | |
7395 | 96 |
8262 | 97 gaim_proxy_info_destroy(jsx->gpi); |
98 | |
99 if(source < 0) { | |
100 jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); | |
101 g_free(streamhost->jid); | |
102 g_free(streamhost->host); | |
103 g_free(streamhost); | |
104 jabber_si_bytestreams_attempt_connect(xfer); | |
105 return; | |
106 } | |
107 | |
108 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams"); | |
109 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
110 jabber_iq_set_id(iq, jsx->iq_id); | |
111 query = xmlnode_get_child(iq->node, "query"); | |
112 su = xmlnode_new_child(query, "streamhost-used"); | |
113 xmlnode_set_attrib(su, "jid", streamhost->jid); | |
114 | |
115 jabber_iq_send(iq); | |
116 | |
117 gaim_xfer_start(xfer, source, NULL, -1); | |
118 } | |
119 | |
120 static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer) | |
121 { | |
122 JabberSIXfer *jsx = xfer->data; | |
123 struct bytestreams_streamhost *streamhost; | |
124 char *dstaddr, *p; | |
125 int i; | |
126 unsigned char hashval[20]; | |
127 | |
128 if(!jsx->streamhosts) { | |
129 JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR); | |
130 xmlnode *error, *condition; | |
131 | |
132 if(jsx->iq_id) | |
133 jabber_iq_set_id(iq, jsx->iq_id); | |
134 | |
135 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
136 error = xmlnode_new_child(iq->node, "error"); | |
137 xmlnode_set_attrib(error, "code", "404"); | |
138 xmlnode_set_attrib(error, "type", "cancel"); | |
139 condition = xmlnode_new_child(error, "condition"); | |
140 xmlnode_set_attrib(condition, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas"); | |
141 xmlnode_new_child(condition, "item-not-found"); | |
142 | |
143 jabber_iq_send(iq); | |
144 | |
145 gaim_xfer_cancel_local(xfer); | |
146 | |
147 return; | |
148 } | |
149 | |
150 streamhost = jsx->streamhosts->data; | |
7395 | 151 |
8262 | 152 jsx->gpi = gaim_proxy_info_new(); |
153 gaim_proxy_info_set_type(jsx->gpi, GAIM_PROXY_SOCKS5); | |
154 gaim_proxy_info_set_host(jsx->gpi, streamhost->host); | |
155 gaim_proxy_info_set_port(jsx->gpi, streamhost->port); | |
156 | |
157 dstaddr = g_strdup_printf("%s%s%s@%s/%s", jsx->stream_id, xfer->who, jsx->js->user->node, | |
158 jsx->js->user->domain, jsx->js->user->resource); | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10112
diff
changeset
|
159 |
11183 | 160 gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
161 sizeof(hashval), hashval, NULL); |
8262 | 162 g_free(dstaddr); |
163 dstaddr = g_malloc(41); | |
164 p = dstaddr; | |
165 for(i=0; i<20; i++, p+=2) | |
166 snprintf(p, 3, "%02x", hashval[i]); | |
167 | |
168 gaim_proxy_connect_socks5(jsx->gpi, dstaddr, 0, jabber_si_bytestreams_connect_cb, xfer); | |
169 g_free(dstaddr); | |
170 } | |
171 | |
172 void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet) | |
173 { | |
174 GaimXfer *xfer; | |
175 JabberSIXfer *jsx; | |
176 xmlnode *query, *streamhost; | |
177 const char *sid, *from; | |
178 | |
179 if(!(from = xmlnode_get_attrib(packet, "from"))) | |
180 return; | |
181 | |
182 if(!(query = xmlnode_get_child(packet, "query"))) | |
183 return; | |
184 | |
185 if(!(sid = xmlnode_get_attrib(query, "sid"))) | |
186 return; | |
187 | |
188 if(!(xfer = jabber_si_xfer_find(js, sid, from))) | |
189 return; | |
190 | |
191 jsx = xfer->data; | |
10940 | 192 |
193 if(!jsx->accepted) | |
194 return; | |
195 | |
8262 | 196 if(jsx->iq_id) |
197 g_free(jsx->iq_id); | |
198 jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); | |
199 | |
200 for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; | |
201 streamhost = xmlnode_get_next_twin(streamhost)) { | |
202 const char *jid, *host, *port; | |
203 int portnum; | |
204 | |
205 if((jid = xmlnode_get_attrib(streamhost, "jid")) && | |
206 (host = xmlnode_get_attrib(streamhost, "host")) && | |
207 (port = xmlnode_get_attrib(streamhost, "port")) && | |
208 (portnum = atoi(port))) { | |
209 struct bytestreams_streamhost *sh = g_new0(struct bytestreams_streamhost, 1); | |
210 sh->jid = g_strdup(jid); | |
211 sh->host = g_strdup(host); | |
212 sh->port = portnum; | |
213 jsx->streamhosts = g_list_append(jsx->streamhosts, sh); | |
214 } | |
215 } | |
216 | |
217 jabber_si_bytestreams_attempt_connect(xfer); | |
7395 | 218 } |
219 | |
8312 | 220 static void |
8316 | 221 jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source, |
8312 | 222 GaimInputCondition cond) |
223 { | |
224 GaimXfer *xfer = data; | |
8316 | 225 JabberSIXfer *jsx = xfer->data; |
8312 | 226 int i; |
8316 | 227 char buffer[256]; |
228 int len; | |
229 char *dstaddr, *p; | |
230 unsigned char hashval[20]; | |
231 const char *host; | |
232 | |
233 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n"); | |
8312 | 234 |
8316 | 235 if(jsx->rxlen < 5) { |
236 gaim_debug_info("jabber", "reading the first 5 bytes\n"); | |
237 if((len = read(source, buffer, 5 - jsx->rxlen)) <= 0) { | |
238 gaim_input_remove(xfer->watcher); | |
239 xfer->watcher = 0; | |
240 close(source); | |
241 gaim_xfer_cancel_remote(xfer); | |
242 return; | |
243 } | |
244 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
245 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
246 jsx->rxlen += len; | |
247 return; | |
248 } else if(jsx->rxqueue[0] != 0x05 || jsx->rxqueue[1] != 0x01 || | |
249 jsx->rxqueue[3] != 0x03) { | |
250 gaim_debug_info("jabber", "invalid socks5 stuff\n"); | |
251 gaim_input_remove(xfer->watcher); | |
252 xfer->watcher = 0; | |
253 close(source); | |
254 gaim_xfer_cancel_remote(xfer); | |
255 return; | |
256 } else if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) { | |
257 gaim_debug_info("jabber", "reading umpteen more bytes\n"); | |
258 if((len = read(source, buffer, jsx->rxqueue[4] + 5 + 2 - jsx->rxlen)) <= 0) { | |
259 gaim_input_remove(xfer->watcher); | |
260 xfer->watcher = 0; | |
261 close(source); | |
262 gaim_xfer_cancel_remote(xfer); | |
263 return; | |
264 } | |
265 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
266 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
267 jsx->rxlen += len; | |
268 } | |
8312 | 269 |
8316 | 270 if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) |
271 return; | |
272 | |
273 gaim_input_remove(xfer->watcher); | |
274 xfer->watcher = 0; | |
8312 | 275 |
8316 | 276 dstaddr = g_strdup_printf("%s%s@%s/%s%s", jsx->stream_id, |
277 jsx->js->user->node, jsx->js->user->domain, | |
278 jsx->js->user->resource, xfer->who); | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10112
diff
changeset
|
279 |
11183 | 280 gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
281 sizeof(hashval), hashval, NULL); |
8316 | 282 g_free(dstaddr); |
283 dstaddr = g_malloc(41); | |
284 p = dstaddr; | |
285 for(i=0; i<20; i++, p+=2) | |
286 snprintf(p, 3, "%02x", hashval[i]); | |
287 | |
288 if(jsx->rxqueue[4] != 40 || strncmp(dstaddr, jsx->rxqueue+5, 40) || | |
289 jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) { | |
290 gaim_debug_error("jabber", "someone connected with the wrong info!\n"); | |
8312 | 291 close(source); |
292 gaim_xfer_cancel_remote(xfer); | |
293 return; | |
294 } | |
295 | |
8838 | 296 host = gaim_network_get_my_ip(jsx->js->fd); |
8316 | 297 |
298 buffer[0] = 0x05; | |
299 buffer[1] = 0x00; | |
300 buffer[2] = 0x00; | |
301 buffer[3] = 0x03; | |
302 buffer[4] = strlen(host); | |
303 memcpy(buffer + 5, host, strlen(host)); | |
304 buffer[5+strlen(host)] = 0x00; | |
305 buffer[6+strlen(host)] = 0x00; | |
306 | |
307 write(source, buffer, strlen(host)+7); | |
308 | |
309 gaim_xfer_start(xfer, source, NULL, -1); | |
310 } | |
311 | |
312 static void | |
313 jabber_si_xfer_bytestreams_send_read_cb(gpointer data, gint source, | |
314 GaimInputCondition cond) | |
315 { | |
316 GaimXfer *xfer = data; | |
317 JabberSIXfer *jsx = xfer->data; | |
318 int i; | |
319 int len; | |
320 char buffer[256]; | |
321 | |
322 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n"); | |
323 | |
324 xfer->fd = source; | |
325 | |
326 if(jsx->rxlen < 2) { | |
327 gaim_debug_info("jabber", "reading those first two bytes\n"); | |
328 if((len = read(source, buffer, 2 - jsx->rxlen)) <= 0) { | |
329 gaim_input_remove(xfer->watcher); | |
330 xfer->watcher = 0; | |
331 close(source); | |
332 gaim_xfer_cancel_remote(xfer); | |
333 return; | |
334 } | |
335 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
336 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
337 jsx->rxlen += len; | |
338 return; | |
339 } else if(jsx->rxlen - 2 < jsx->rxqueue[1]) { | |
340 gaim_debug_info("jabber", "reading the next umpteen bytes\n"); | |
341 if((len = read(source, buffer, jsx->rxqueue[1] + 2 - jsx->rxlen)) <= 0) { | |
342 gaim_input_remove(xfer->watcher); | |
343 xfer->watcher = 0; | |
344 close(source); | |
345 gaim_xfer_cancel_remote(xfer); | |
346 return; | |
347 } | |
348 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
349 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
350 jsx->rxlen += len; | |
351 } | |
352 | |
353 if(jsx->rxlen -2 < jsx->rxqueue[1]) | |
354 return; | |
355 | |
356 gaim_input_remove(xfer->watcher); | |
357 xfer->watcher = 0; | |
358 | |
359 | |
360 gaim_debug_info("jabber", "checking to make sure we're socks FIVE\n"); | |
361 | |
362 if(jsx->rxqueue[0] != 0x05) { | |
363 close(source); | |
364 gaim_xfer_cancel_remote(xfer); | |
365 return; | |
366 } | |
367 | |
368 gaim_debug_info("jabber", "going to test %hhu different methods\n", jsx->rxqueue[1]); | |
369 | |
370 for(i=0; i<jsx->rxqueue[1]; i++) { | |
371 | |
372 gaim_debug_info("jabber", "testing %hhu\n", jsx->rxqueue[i+2]); | |
373 if(jsx->rxqueue[i+2] == 0x00) { | |
374 buffer[0] = 0x05; | |
375 buffer[1] = 0x00; | |
376 write(source, buffer, 2); | |
377 xfer->watcher = gaim_input_add(source, GAIM_INPUT_READ, | |
378 jabber_si_xfer_bytestreams_send_read_again_cb, xfer); | |
379 g_free(jsx->rxqueue); | |
380 jsx->rxqueue = NULL; | |
381 jsx->rxlen = 0; | |
8312 | 382 return; |
383 } | |
384 } | |
385 | |
8316 | 386 buffer[0] = 0x05; |
387 buffer[1] = 0xFF; | |
388 write(source, buffer, 2); | |
389 close(source); | |
390 g_free(jsx->rxqueue); | |
391 jsx->rxqueue = NULL; | |
392 jsx->rxlen = 0; | |
8312 | 393 gaim_xfer_cancel_remote(xfer); |
394 } | |
395 | |
396 static void | |
8316 | 397 jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source, |
398 GaimInputCondition cond) | |
399 { | |
400 GaimXfer *xfer = data; | |
401 int acceptfd; | |
402 | |
403 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n"); | |
404 | |
405 if((acceptfd = accept(source, NULL, 0)) == -1) { | |
406 gaim_debug_warning("jabber", "accept: %s\n", strerror(errno)); | |
407 return; | |
408 } | |
409 | |
410 gaim_input_remove(xfer->watcher); | |
411 close(source); | |
412 | |
413 xfer->watcher = gaim_input_add(acceptfd, GAIM_INPUT_READ, | |
414 jabber_si_xfer_bytestreams_send_read_cb, xfer); | |
415 } | |
416 | |
417 | |
418 static void | |
8312 | 419 jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer) |
420 { | |
421 JabberSIXfer *jsx = xfer->data; | |
422 JabberIq *iq; | |
423 xmlnode *query, *streamhost; | |
424 char *jid, *port; | |
425 int fd; | |
426 | |
427 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, | |
428 "http://jabber.org/protocol/bytestreams"); | |
429 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
430 query = xmlnode_get_child(iq->node, "query"); | |
431 | |
432 xmlnode_set_attrib(query, "sid", jsx->stream_id); | |
433 | |
434 streamhost = xmlnode_new_child(query, "streamhost"); | |
435 jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); | |
436 xmlnode_set_attrib(streamhost, "jid", jid); | |
437 g_free(jid); | |
438 | |
439 if((fd = gaim_network_listen_range(0, 0)) < 0) { | |
440 /* XXX: couldn't open a port, we're fscked */ | |
441 return; | |
442 } | |
443 | |
8838 | 444 xmlnode_set_attrib(streamhost, "host", gaim_network_get_my_ip(jsx->js->fd)); |
8312 | 445 xfer->local_port = gaim_network_get_port_from_fd(fd); |
8316 | 446 port = g_strdup_printf("%hu", xfer->local_port); |
8312 | 447 xmlnode_set_attrib(streamhost, "port", port); |
448 g_free(port); | |
449 | |
450 xfer->watcher = gaim_input_add(fd, GAIM_INPUT_READ, | |
451 jabber_si_xfer_bytestreams_send_connected_cb, xfer); | |
452 | |
453 /* XXX: insert proxies here */ | |
454 | |
455 /* XXX: callback to find out which streamhost they used, or see if they | |
456 * screwed it up */ | |
457 jabber_iq_send(iq); | |
458 } | |
459 | |
460 static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet, | |
461 gpointer data) | |
462 { | |
463 GaimXfer *xfer = data; | |
464 xmlnode *si, *feature, *x, *field, *value; | |
465 | |
466 if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) { | |
467 gaim_xfer_cancel_remote(xfer); | |
468 return; | |
469 } | |
470 | |
471 if(!(feature = xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) { | |
472 gaim_xfer_cancel_remote(xfer); | |
473 return; | |
474 } | |
475 | |
476 if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) { | |
477 gaim_xfer_cancel_remote(xfer); | |
478 return; | |
479 } | |
480 | |
481 for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { | |
482 const char *var = xmlnode_get_attrib(field, "var"); | |
483 | |
484 if(var && !strcmp(var, "stream-method")) { | |
485 if((value = xmlnode_get_child(field, "value"))) { | |
486 char *val = xmlnode_get_data(value); | |
487 if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) { | |
488 jabber_si_xfer_bytestreams_send_init(xfer); | |
489 g_free(val); | |
490 return; | |
491 } | |
492 g_free(val); | |
493 } | |
494 } | |
495 } | |
496 gaim_xfer_cancel_remote(xfer); | |
497 } | |
498 | |
499 static void jabber_si_xfer_send_request(GaimXfer *xfer) | |
500 { | |
501 JabberSIXfer *jsx = xfer->data; | |
502 JabberIq *iq; | |
503 xmlnode *si, *file, *feature, *x, *field, *option, *value; | |
504 char buf[32]; | |
505 | |
506 xfer->filename = g_path_get_basename(xfer->local_filename); | |
507 | |
508 iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); | |
509 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
510 si = xmlnode_new_child(iq->node, "si"); | |
511 xmlnode_set_attrib(si, "xmlns", "http://jabber.org/protocol/si"); | |
512 jsx->stream_id = jabber_get_next_id(jsx->js); | |
513 xmlnode_set_attrib(si, "id", jsx->stream_id); | |
514 xmlnode_set_attrib(si, "profile", | |
515 "http://jabber.org/protocol/si/profile/file-transfer"); | |
516 | |
517 file = xmlnode_new_child(si, "file"); | |
518 xmlnode_set_attrib(file, "xmlns", | |
519 "http://jabber.org/protocol/si/profile/file-transfer"); | |
520 xmlnode_set_attrib(file, "name", xfer->filename); | |
11656
f9c5480ad0ce
[gaim-migrate @ 13940]
Richard Laager <rlaager@wiktel.com>
parents:
11183
diff
changeset
|
521 g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size); |
8312 | 522 xmlnode_set_attrib(file, "size", buf); |
523 /* maybe later we'll do hash and date attribs */ | |
524 | |
525 feature = xmlnode_new_child(si, "feature"); | |
526 xmlnode_set_attrib(feature, "xmlns", | |
527 "http://jabber.org/protocol/feature-neg"); | |
528 x = xmlnode_new_child(feature, "x"); | |
529 xmlnode_set_attrib(x, "xmlns", "jabber:x:data"); | |
530 xmlnode_set_attrib(x, "type", "form"); | |
531 field = xmlnode_new_child(x, "field"); | |
532 xmlnode_set_attrib(field, "var", "stream-method"); | |
533 xmlnode_set_attrib(field, "type", "list-single"); | |
534 option = xmlnode_new_child(field, "option"); | |
535 value = xmlnode_new_child(option, "value"); | |
536 xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", | |
537 -1); | |
538 /* | |
539 option = xmlnode_new_child(field, "option"); | |
540 value = xmlnode_new_child(option, "value"); | |
541 xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); | |
542 */ | |
543 | |
544 jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer); | |
545 | |
546 jabber_iq_send(iq); | |
547 } | |
548 | |
8316 | 549 static void jabber_si_xfer_free(GaimXfer *xfer) |
8312 | 550 { |
8316 | 551 JabberSIXfer *jsx = xfer->data; |
552 JabberStream *js = jsx->js; | |
553 | |
554 js->file_transfers = g_list_remove(js->file_transfers, xfer); | |
555 | |
556 g_free(jsx->stream_id); | |
557 g_free(jsx->iq_id); | |
558 /* XXX: free other stuff */ | |
559 g_free(jsx); | |
560 xfer->data = NULL; | |
561 } | |
562 | |
563 static void jabber_si_xfer_cancel_send(GaimXfer *xfer) | |
564 { | |
565 jabber_si_xfer_free(xfer); | |
8312 | 566 gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); |
567 } | |
568 | |
569 | |
8316 | 570 static void jabber_si_xfer_cancel_recv(GaimXfer *xfer) |
8312 | 571 { |
8316 | 572 jabber_si_xfer_free(xfer); |
8312 | 573 gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); |
574 } | |
575 | |
576 | |
8316 | 577 static void jabber_si_xfer_end(GaimXfer *xfer) |
578 { | |
579 jabber_si_xfer_free(xfer); | |
580 } | |
581 | |
582 | |
8312 | 583 static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who, |
584 JabberCapabilities capabilities, gpointer data) | |
585 { | |
586 GaimXfer *xfer = data; | |
587 | |
588 if(capabilities & JABBER_CAP_SI_FILE_XFER) { | |
589 jabber_si_xfer_send_request(xfer); | |
590 } else { | |
591 char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who); | |
592 gaim_notify_error(js->gc, _("File Send Failed"), | |
593 _("File Send Failed"), msg); | |
594 g_free(msg); | |
595 } | |
596 } | |
597 | |
8262 | 598 static void jabber_si_xfer_init(GaimXfer *xfer) |
599 { | |
600 JabberSIXfer *jsx = xfer->data; | |
601 JabberIq *iq; | |
8312 | 602 if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { |
603 JabberBuddy *jb; | |
604 JabberBuddyResource *jbr = NULL; | |
605 | |
606 jb = jabber_buddy_find(jsx->js, xfer->who, TRUE); | |
607 /* XXX */ | |
608 if(!jb) | |
609 return; | |
8262 | 610 |
8312 | 611 /* XXX: for now, send to the first resource available */ |
612 if(g_list_length(jb->resources) >= 1) { | |
613 char *who; | |
8316 | 614 jbr = jabber_buddy_find_resource(jb, NULL); |
8312 | 615 who = g_strdup_printf("%s/%s", xfer->who, jbr->name); |
616 g_free(xfer->who); | |
617 xfer->who = who; | |
618 jabber_disco_info_do(jsx->js, who, | |
619 jabber_si_xfer_send_disco_cb, xfer); | |
620 } else { | |
621 return; /* XXX: ick */ | |
622 } | |
623 } else { | |
624 xmlnode *si, *feature, *x, *field, *value; | |
8262 | 625 |
8312 | 626 iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT); |
627 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
628 if(jsx->iq_id) | |
629 jabber_iq_set_id(iq, jsx->iq_id); | |
630 | |
10940 | 631 jsx->accepted = TRUE; |
632 | |
8312 | 633 si = xmlnode_new_child(iq->node, "si"); |
634 xmlnode_set_attrib(si, "xmlns", "http://jabber.org/protocol/si"); | |
635 | |
636 feature = xmlnode_new_child(si, "feature"); | |
637 xmlnode_set_attrib(feature, "xmlns", "http://jabber.org/protocol/feature-neg"); | |
8262 | 638 |
8312 | 639 x = xmlnode_new_child(feature, "x"); |
640 xmlnode_set_attrib(x, "xmlns", "jabber:x:data"); | |
8343 | 641 xmlnode_set_attrib(x, "type", "submit"); |
8262 | 642 |
8312 | 643 field = xmlnode_new_child(x, "field"); |
644 xmlnode_set_attrib(field, "var", "stream-method"); | |
645 | |
646 value = xmlnode_new_child(field, "value"); | |
647 if(jsx->stream_method & STREAM_METHOD_BYTESTREAMS) | |
648 xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); | |
649 /* | |
650 else if(jsx->stream_method & STREAM_METHOD_IBB) | |
8262 | 651 xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); |
652 */ | |
653 | |
8312 | 654 jabber_iq_send(iq); |
655 } | |
8262 | 656 } |
657 | |
9466 | 658 void jabber_si_xfer_send(GaimConnection *gc, const char *who, const char *file) |
8312 | 659 { |
9030 | 660 JabberStream *js; |
661 | |
8312 | 662 GaimXfer *xfer; |
663 JabberSIXfer *jsx; | |
664 | |
9030 | 665 js = gc->proto_data; |
666 | |
9466 | 667 if(!gaim_find_buddy(gc->account, who) || !jabber_buddy_find(js, who, FALSE)) |
8312 | 668 return; |
669 | |
9466 | 670 xfer = gaim_xfer_new(gc->account, GAIM_XFER_SEND, who); |
8262 | 671 |
8312 | 672 xfer->data = jsx = g_new0(JabberSIXfer, 1); |
673 jsx->js = js; | |
8262 | 674 |
8312 | 675 gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); |
676 gaim_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send); | |
8316 | 677 gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end); |
8312 | 678 |
679 js->file_transfers = g_list_append(js->file_transfers, xfer); | |
680 | |
9466 | 681 if (file) |
682 gaim_xfer_request_accepted(xfer, file); | |
683 else | |
684 gaim_xfer_request(xfer); | |
8262 | 685 } |
686 | |
687 void jabber_si_parse(JabberStream *js, xmlnode *packet) | |
688 { | |
689 JabberSIXfer *jsx; | |
690 GaimXfer *xfer; | |
691 xmlnode *si, *file, *feature, *x, *field, *option, *value; | |
10939 | 692 const char *stream_id, *filename, *filesize_c, *profile, *from; |
8262 | 693 size_t filesize = 0; |
694 | |
695 if(!(si = xmlnode_get_child(packet, "si"))) | |
696 return; | |
697 | |
698 if(!(profile = xmlnode_get_attrib(si, "profile")) || | |
699 strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) | |
700 return; | |
701 | |
702 if(!(stream_id = xmlnode_get_attrib(si, "id"))) | |
703 return; | |
704 | |
705 if(!(file = xmlnode_get_child(si, "file"))) | |
706 return; | |
707 | |
708 if(!(filename = xmlnode_get_attrib(file, "name"))) | |
709 return; | |
710 | |
711 if((filesize_c = xmlnode_get_attrib(file, "size"))) | |
712 filesize = atoi(filesize_c); | |
713 | |
714 if(!(feature = xmlnode_get_child(si, "feature"))) | |
715 return; | |
716 | |
717 if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) | |
718 return; | |
719 | |
10939 | 720 if(!(from = xmlnode_get_attrib(packet, "from"))) |
721 return; | |
722 | |
723 /* if they've already sent us this file transfer with the same damn id | |
724 * then we're gonna ignore it, until I think of something better to do | |
725 * with it */ | |
726 if((xfer = jabber_si_xfer_find(js, stream_id, from))) | |
727 return; | |
728 | |
8262 | 729 jsx = g_new0(JabberSIXfer, 1); |
730 | |
731 for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { | |
732 const char *var = xmlnode_get_attrib(field, "var"); | |
733 if(var && !strcmp(var, "stream-method")) { | |
734 for(option = xmlnode_get_child(field, "option"); option; | |
735 option = xmlnode_get_next_twin(option)) { | |
736 if((value = xmlnode_get_child(option, "value"))) { | |
737 char *val; | |
738 if((val = xmlnode_get_data(value))) { | |
739 if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) { | |
740 jsx->stream_method |= STREAM_METHOD_BYTESTREAMS; | |
741 /* | |
742 } else if(!strcmp(val, "http://jabber.org/protocol/ibb")) { | |
743 jsx->stream_method |= STREAM_METHOD_IBB; | |
744 */ | |
745 } | |
746 g_free(val); | |
747 } | |
748 } | |
749 } | |
750 } | |
751 } | |
752 | |
753 if(jsx->stream_method == STREAM_METHOD_UNKNOWN) { | |
754 g_free(jsx); | |
755 return; | |
756 } | |
757 | |
758 jsx->js = js; | |
759 jsx->stream_id = g_strdup(stream_id); | |
760 jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); | |
761 | |
10939 | 762 xfer = gaim_xfer_new(js->gc->account, GAIM_XFER_RECEIVE, from); |
8262 | 763 xfer->data = jsx; |
764 | |
765 gaim_xfer_set_filename(xfer, filename); | |
766 if(filesize > 0) | |
767 gaim_xfer_set_size(xfer, filesize); | |
768 | |
769 gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); | |
8316 | 770 gaim_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv); |
771 gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end); | |
8262 | 772 |
773 js->file_transfers = g_list_append(js->file_transfers, xfer); | |
774 | |
775 gaim_xfer_request(xfer); | |
776 } | |
777 | |
7395 | 778 |