]> crepu.dev Git - config.git/blob - djavu-asus/emacs/elpa/jabber-20230715.456/jabber-disco.el
059904f5f3ffd6e20cd47af7b29781fd1d76f437
[config.git] / djavu-asus / emacs / elpa / jabber-20230715.456 / jabber-disco.el
1 ;; jabber-disco.el - service discovery functions -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
4 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
5
6 ;; This file is a part of jabber.el.
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
19 ;; along with this program; if not, write to the Free Software
20 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22 (require 'jabber-iq)
23 (require 'jabber-xml)
24 (require 'jabber-menu)
25
26 ;;; Respond to disco requests
27
28 ;;;###autoload
29 (eval-after-load "jabber-core"
30 '(add-to-list 'jabber-presence-chain #'jabber-process-caps))
31
32 (defvar jabber-caps-cache (make-hash-table :test 'equal))
33
34 (defconst jabber-caps-hash-names
35 (if (fboundp 'secure-hash)
36 '(("sha-1" . sha1)
37 ("sha-224" . sha224)
38 ("sha-256" . sha256)
39 ("sha-384" . sha384)
40 ("sha-512" . sha512))
41 ;; `secure-hash' was introduced in Emacs 24. For Emacs 23, fall
42 ;; back to the `sha1' function, handled specially in
43 ;; `jabber-caps--secure-hash'.
44 '(("sha-1" . sha1)))
45 "Hash function name map.
46 Maps names defined in http://www.iana.org/assignments/hash-function-text-names
47 to symbols accepted by `secure-hash'.
48
49 XEP-0115 currently recommends SHA-1, but let's be future-proof.")
50
51 (defun jabber-caps-get-cached (jid)
52 "Get disco info from Entity Capabilities cache.
53 JID should be a string containing a full JID.
54 Return (IDENTITIES FEATURES), or nil if not in cache."
55 (let* ((symbol (jabber-jid-symbol jid))
56 (resource (or (jabber-jid-resource jid) ""))
57 (resource-plist (cdr (assoc resource (get symbol 'resources))))
58 (key (plist-get resource-plist 'caps)))
59 (when key
60 (let ((cache-entry (gethash key jabber-caps-cache)))
61 (when (and (consp cache-entry) (not (floatp (car cache-entry))))
62 cache-entry)))))
63
64 ;;;###autoload
65 (defun jabber-process-caps (jc xml-data)
66 "Look for entity capabilities in presence stanzas.
67
68 JC is the Jabber connection.
69 XML-DATA is the parsed tree data from the stream (stanzas)
70 obtained from `xml-parse-region'."
71 (let* ((from (jabber-xml-get-attribute xml-data 'from))
72 (type (jabber-xml-get-attribute xml-data 'type))
73 (c (jabber-xml-path xml-data '(("http://jabber.org/protocol/caps" . "c")))))
74 (when (and (null type) c)
75 (jabber-xml-let-attributes
76 (ext hash node ver) c
77 (cond
78 (hash
79 ;; If the <c/> element has a hash attribute, it follows the
80 ;; "modern" version of XEP-0115.
81 (jabber-process-caps-modern jc from hash node ver))
82 (t
83 ;; No hash attribute. Use legacy version of XEP-0115.
84 ;; TODO: do something clever here.
85 ))))))
86
87 (defun jabber-process-caps-modern (jc jid hash node ver)
88 (when (assoc hash jabber-caps-hash-names)
89 ;; We support the hash function used.
90 (let* ((key (cons hash ver))
91 (cache-entry (gethash key jabber-caps-cache)))
92 ;; Remember the hash in the JID symbol.
93 (let* ((symbol (jabber-jid-symbol jid))
94 (resource (or (jabber-jid-resource jid) ""))
95 (resource-entry (assoc resource (get symbol 'resources)))
96 (new-resource-plist (plist-put (cdr resource-entry) 'caps key)))
97 (if resource-entry
98 (setf (cdr resource-entry) new-resource-plist)
99 (push (cons resource new-resource-plist) (get symbol 'resources))))
100
101 (cl-flet ((request-disco-info
102 ()
103 (jabber-send-iq
104 jc jid
105 "get"
106 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
107 (node . ,(concat node "#" ver))))
108 #'jabber-process-caps-info-result (list hash node ver)
109 #'jabber-process-caps-info-error (list hash node ver))))
110 (cond
111 ((and (consp cache-entry)
112 (floatp (car cache-entry)))
113 ;; We have a record of asking someone about this hash.
114 (if (< (- (float-time) (car cache-entry)) 10.0)
115 ;; We asked someone about this hash less than 10 seconds ago.
116 ;; Let's add the new JID to the entry, just in case that
117 ;; doesn't work out.
118 (cl-pushnew jid (cdr cache-entry) :test #'string=)
119 ;; We asked someone about it more than 10 seconds ago.
120 ;; They're probably not going to answer. Let's ask
121 ;; this contact about it instead.
122 (setf (car cache-entry) (float-time))
123 (request-disco-info)))
124 ((null cache-entry)
125 ;; We know nothing about this hash. Let's note the
126 ;; fact that we tried to get information about it.
127 (puthash key (list (float-time)) jabber-caps-cache)
128 (request-disco-info))
129 (t
130 ;; We already know what this hash represents, so we
131 ;; can cache info for this contact.
132 (puthash (cons jid nil) cache-entry jabber-disco-info-cache)))))))
133
134 (defun jabber-process-caps-info-result (jc xml-data closure-data)
135 (cl-destructuring-bind (hash node ver) closure-data
136 (let* ((key (cons hash ver))
137 (query (jabber-iq-query xml-data))
138 (verification-string (jabber-caps-ver-string query hash)))
139 (if (string= ver verification-string)
140 ;; The hash is correct; save info.
141 (puthash key (jabber-disco-parse-info xml-data) jabber-caps-cache)
142 ;; The hash is incorrect.
143 (jabber-caps-try-next jc hash node ver)))))
144
145 (defun jabber-process-caps-info-error (jc xml-data closure-data)
146 (cl-destructuring-bind (hash node ver) closure-data
147 (jabber-caps-try-next jc hash node ver)))
148
149 (defun jabber-caps-try-next (jc hash node ver)
150 (let* ((key (cons hash ver))
151 (cache-entry (gethash key jabber-caps-cache)))
152 (when (floatp (car-safe cache-entry))
153 (let ((next-jid (pop (cdr cache-entry))))
154 ;; Do we know someone else we could ask about this hash?
155 (if next-jid
156 (progn
157 (setf (car cache-entry) (float-time))
158 (jabber-send-iq
159 jc next-jid
160 "get"
161 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
162 (node . ,(concat node "#" ver))))
163 #'jabber-process-caps-info-result (list hash node ver)
164 #'jabber-process-caps-info-error (list hash node ver)))
165 ;; No, forget about it for now.
166 (remhash key jabber-caps-cache))))))
167
168 (defun jabber-caps-ver-string (query hash)
169 ;; XEP-0115, section 5.1
170 ;; 1. Initialize an empty string S.
171 (with-temp-buffer
172 (let* ((identities (jabber-xml-get-children query 'identity))
173 (disco-features (mapcar (lambda (f) (jabber-xml-get-attribute f 'var))
174 (jabber-xml-get-children query 'feature)))
175 (maybe-forms (jabber-xml-get-children query 'x))
176 (forms (cl-remove-if-not
177 (lambda (x)
178 ;; Keep elements that are forms and have a FORM_TYPE,
179 ;; according to XEP-0128.
180 (and (string= (jabber-xml-get-xmlns x) "jabber:x:data")
181 (jabber-xdata-formtype x)))
182 maybe-forms)))
183 ;; 2. Sort the service discovery identities [15] by category
184 ;; and then by type and then by xml:lang (if it exists),
185 ;; formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
186 ;; [NAME]. [16] Note that each slash is included even if the
187 ;; LANG or NAME is not included (in accordance with XEP-0030,
188 ;; the category and type MUST be included.
189 (setq identities (sort identities #'jabber-caps-identity-<))
190 ;; 3. For each identity, append the 'category/type/lang/name' to
191 ;; S, followed by the '<' character.
192 (dolist (identity identities)
193 (jabber-xml-let-attributes (category type xml:lang name) identity
194 ;; Use `concat' here instead of passing everything to
195 ;; `insert', since `concat' tolerates nil values.
196 (insert (concat category "/" type "/" xml:lang "/" name "<"))))
197 ;; 4. Sort the supported service discovery features. [17]
198 (setq disco-features (sort disco-features #'string<))
199 ;; 5. For each feature, append the feature to S, followed by the
200 ;; '<' character.
201 (dolist (f disco-features)
202 (insert f "<"))
203 ;; 6. If the service discovery information response includes
204 ;; XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
205 ;; by the XML character data of the <value/> element).
206 (setq forms (sort forms (lambda (a b)
207 (string< (jabber-xdata-formtype a)
208 (jabber-xdata-formtype b)))))
209 ;; 7. For each extended service discovery information form:
210 (dolist (form forms)
211 ;; Append the XML character data of the FORM_TYPE field's
212 ;; <value/> element, followed by the '<' character.
213 (insert (jabber-xdata-formtype form) "<")
214 ;; Sort the fields by the value of the "var" attribute.
215 (let ((fields (sort (jabber-xml-get-children form 'field)
216 (lambda (a b)
217 (string< (jabber-xml-get-attribute a 'var)
218 (jabber-xml-get-attribute b 'var))))))
219 (dolist (field fields)
220 ;; For each field other than FORM_TYPE:
221 (unless (string= (jabber-xml-get-attribute field 'var) "FORM_TYPE")
222 ;; Append the value of the "var" attribute, followed by the '<' character.
223 (insert (jabber-xml-get-attribute field 'var) "<")
224 ;; Sort values by the XML character data of the <value/> element.
225 (let ((values (sort (mapcar (lambda (value)
226 (car (jabber-xml-node-children value)))
227 (jabber-xml-get-children field 'value))
228 #'string<)))
229 ;; For each <value/> element, append the XML character
230 ;; data, followed by the '<' character.
231 (dolist (value values)
232 (insert value "<"))))))))
233
234 ;; 8. Ensure that S is encoded according to the UTF-8 encoding
235 ;; (RFC 3269 [18]).
236 (let ((s (encode-coding-string (buffer-string) 'utf-8 t))
237 (algorithm (cdr (assoc hash jabber-caps-hash-names))))
238 ;; 9. Compute the verification string by hashing S using the
239 ;; algorithm specified in the 'hash' attribute (e.g., SHA-1 as
240 ;; defined in RFC 3174 [19]). The hashed data MUST be generated
241 ;; with binary output and encoded using Base64 as specified in
242 ;; Section 4 of RFC 4648 [20] (note: the Base64 output MUST NOT
243 ;; include whitespace and MUST set padding bits to zero). [21]
244 (base64-encode-string (jabber-caps--secure-hash algorithm s) t))))
245
246 (defun jabber-caps--secure-hash (algorithm string)
247 (cond
248 ;; `secure-hash' was introduced in Emacs 24
249 ((fboundp 'secure-hash)
250 (secure-hash algorithm string nil nil t))
251 ((eq algorithm 'sha1)
252 ;; For SHA-1, we can use the `sha1' function.
253 (sha1 string nil nil t))
254 (t
255 (error "Cannot use hash algorithm %s!" algorithm))))
256
257 (defun jabber-caps-identity-< (a b)
258 (let ((a-category (jabber-xml-get-attribute a 'category))
259 (b-category (jabber-xml-get-attribute b 'category)))
260 (or (string< a-category b-category)
261 (and (string= a-category b-category)
262 (let ((a-type (jabber-xml-get-attribute a 'type))
263 (b-type (jabber-xml-get-attribute b 'type)))
264 (or (string< a-type b-type)
265 (and (string= a-type b-type)
266 (let ((a-xml:lang (jabber-xml-get-attribute a 'xml:lang))
267 (b-xml:lang (jabber-xml-get-attribute b 'xml:lang)))
268 (string< a-xml:lang b-xml:lang)))))))))
269
270 (defvar jabber-caps-default-hash-function "sha-1"
271 "Hash function to use when sending caps in presence stanzas.
272 The value should be a key in `jabber-caps-hash-names'.")
273
274 (defvar jabber-caps-current-hash nil
275 "The current disco hash we're sending out in presence stanzas.")
276
277 (defconst jabber-caps-node "http://emacs-jabber.sourceforge.net")
278
279 ;;;###autoload
280 (defun jabber-disco-advertise-feature (feature)
281 (unless (member feature jabber-advertised-features)
282 (push feature jabber-advertised-features)
283 (when jabber-caps-current-hash
284 (jabber-caps-recalculate-hash)
285 ;; If we're already connected, we need to send updated presence
286 ;; for the new feature.
287 (mapc #'jabber-send-current-presence jabber-connections))))
288
289 (defun jabber-caps-recalculate-hash ()
290 "Update `jabber-caps-current-hash' for feature list change.
291 Also update `jabber-disco-info-nodes', so we return results for
292 the right node."
293 (let* ((old-hash jabber-caps-current-hash)
294 (old-node (and old-hash (concat jabber-caps-node "#" old-hash)))
295 (new-hash
296 (jabber-caps-ver-string `(query () ,@(jabber-disco-return-client-info))
297 jabber-caps-default-hash-function))
298 (new-node (concat jabber-caps-node "#" new-hash)))
299 (when old-node
300 (let ((old-entry (assoc old-node jabber-disco-info-nodes)))
301 (when old-entry
302 (setq jabber-disco-info-nodes (delq old-entry jabber-disco-info-nodes)))))
303 (push (list new-node #'jabber-disco-return-client-info nil)
304 jabber-disco-info-nodes)
305 (setq jabber-caps-current-hash new-hash)))
306
307 ;;;###autoload
308 (defun jabber-caps-presence-element (_jc)
309 (unless jabber-caps-current-hash
310 (jabber-caps-recalculate-hash))
311
312 (list
313 `(c ((xmlns . "http://jabber.org/protocol/caps")
314 (hash . ,jabber-caps-default-hash-function)
315 (node . ,jabber-caps-node)
316 (ver . ,jabber-caps-current-hash)))))
317
318 ;;;###autoload
319 (eval-after-load "jabber-presence"
320 '(add-to-list 'jabber-presence-element-functions #'jabber-caps-presence-element))
321
322 (defvar jabber-advertised-features
323 (list "http://jabber.org/protocol/disco#info")
324 "Features advertised on service discovery requests.
325
326 Don't add your feature to this list directly. Instead, call
327 `jabber-disco-advertise-feature'.")
328
329 (defvar jabber-disco-items-nodes
330 (list
331 (list "" nil nil))
332 "Alist of node names and information about returning disco item data.
333 Key is node name as a string, or \"\" for no node specified. Value is
334 a list of two items.
335
336 First item is data to return. If it is a function, that function is
337 called and its return value is used; if it is a list, that list is
338 used. The list should be the XML data to be returned inside the
339 <query/> element, like this:
340
341 \((item ((name . \"Name of first item\")
342 (jid . \"first.item\")
343 (node . \"node\"))))
344
345 Second item is access control function. That function is passed the
346 JID, and returns non-nil if access is granted. If the second item is
347 nil, access is always granted.")
348
349 (defvar jabber-disco-info-nodes
350 (list
351 (list "" #'jabber-disco-return-client-info nil))
352 "Alist of node names and information returning disco info data.
353 Key is node name as a string, or \"\" for no node specified. Value is
354 a list of two items.
355
356 First item is data to return. If it is a function, that function is
357 called and its return value is used; if it is a list, that list is
358 used. The list should be the XML data to be returned inside the
359 <query/> element, like this:
360
361 \((identity ((category . \"client\")
362 (type . \"pc\")
363 (name . \"Jabber client\")))
364 (feature ((var . \"some-feature\"))))
365
366 Second item is access control function. That function is passed the
367 JID, and returns non-nil if access is granted. If the second item is
368 nil, access is always granted.")
369
370 (add-to-list 'jabber-iq-get-xmlns-alist
371 (cons "http://jabber.org/protocol/disco#info" 'jabber-return-disco-info))
372 (add-to-list 'jabber-iq-get-xmlns-alist
373 (cons "http://jabber.org/protocol/disco#items" 'jabber-return-disco-info))
374 (defun jabber-return-disco-info (jc xml-data)
375 "Respond to a service discovery request.
376 See XEP-0030.
377
378 JC is the Jabber connection.
379 XML-DATA is the parsed tree data from the stream (stanzas)
380 obtained from `xml-parse-region'."
381 (let* ((to (jabber-xml-get-attribute xml-data 'from))
382 (id (jabber-xml-get-attribute xml-data 'id))
383 (xmlns (jabber-iq-xmlns xml-data))
384 (which-alist (eval (cdr (assoc xmlns
385 (list
386 (cons "http://jabber.org/protocol/disco#info" 'jabber-disco-info-nodes)
387 (cons "http://jabber.org/protocol/disco#items" 'jabber-disco-items-nodes))))))
388 (node (or
389 (jabber-xml-get-attribute (jabber-iq-query xml-data) 'node)
390 ""))
391 (return-list (cdr (assoc node which-alist)))
392 (func (nth 0 return-list))
393 (access-control (nth 1 return-list)))
394 (if return-list
395 (if (and (functionp access-control)
396 (not (funcall access-control jc to)))
397 (jabber-signal-error "Cancel" 'not-allowed)
398 ;; Access control passed
399 (let ((result (if (functionp func)
400 (funcall func jc xml-data)
401 func)))
402 (jabber-send-iq jc to "result"
403 `(query ((xmlns . ,xmlns)
404 ,@(when node
405 (list (cons 'node node))))
406 ,@result)
407 nil nil nil nil id)))
408
409 ;; No such node
410 (jabber-signal-error "Cancel" 'item-not-found))))
411
412 (defun jabber-disco-return-client-info (&optional jc xml-data)
413 `(
414 ;; If running under a window system, this is
415 ;; a GUI client. If not, it is a console client.
416 (identity ((category . "client")
417 (name . "Emacs Jabber client")
418 (type . ,(if (memq window-system
419 '(x w32 mac ns))
420 "pc"
421 "console"))))
422 ,@(mapcar
423 #'(lambda (featurename)
424 `(feature ((var . ,featurename))))
425 jabber-advertised-features)))
426
427 (add-to-list 'jabber-jid-info-menu
428 (cons "Send items disco query" 'jabber-get-disco-items))
429 (defun jabber-get-disco-items (jc to &optional node)
430 "Send a service discovery request for items.
431
432 JC is the Jabber connection."
433 (interactive (list (jabber-read-account)
434 (jabber-read-jid-completing "Send items disco request to: " nil nil nil 'full t)
435 (jabber-read-node "Node (or leave empty): ")))
436 (jabber-send-iq jc to
437 "get"
438 (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#items"))
439 (if (> (length node) 0)
440 (list (cons 'node node)))))
441 #'jabber-process-data #'jabber-process-disco-items
442 #'jabber-process-data "Item discovery failed"))
443
444 (add-to-list 'jabber-jid-info-menu
445 (cons "Send info disco query" 'jabber-get-disco-info))
446 (defun jabber-get-disco-info (jc to &optional node)
447 "Send a service discovery request for info.
448
449 JC is the Jabber connection."
450 (interactive (list (jabber-read-account)
451 (jabber-read-jid-completing "Send info disco request to: " nil nil nil 'full t)
452 (jabber-read-node "Node (or leave empty): ")))
453 (jabber-send-iq jc to
454 "get"
455 (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#info"))
456 (if (> (length node) 0)
457 (list (cons 'node node)))))
458 #'jabber-process-data #'jabber-process-disco-info
459 #'jabber-process-data "Info discovery failed"))
460
461 (defun jabber-process-disco-info (jc xml-data)
462 "Handle results from info disco requests.
463
464 JC is the Jabber connection.
465 XML-DATA is the parsed tree data from the stream (stanzas)
466 obtained from `xml-parse-region'."
467
468 (let ((beginning (point)))
469 (dolist (x (jabber-xml-node-children (jabber-iq-query xml-data)))
470 (cond
471 ((eq (jabber-xml-node-name x) 'identity)
472 (let ((name (jabber-xml-get-attribute x 'name))
473 (category (jabber-xml-get-attribute x 'category))
474 (type (jabber-xml-get-attribute x 'type)))
475 (insert (jabber-propertize (if name
476 name
477 "Unnamed")
478 'face 'jabber-title-medium)
479 "\n\nCategory:\t" category "\n")
480 (if type
481 (insert "Type:\t\t" type "\n"))
482 (insert "\n")))
483 ((eq (jabber-xml-node-name x) 'feature)
484 (let ((var (jabber-xml-get-attribute x 'var)))
485 (insert "Feature:\t" var "\n")))))
486 (put-text-property beginning (point)
487 'jabber-jid (jabber-xml-get-attribute xml-data 'from))
488 (put-text-property beginning (point)
489 'jabber-account jc)))
490
491 (defun jabber-process-disco-items (jc xml-data)
492 "Handle results from items disco requests.
493
494 JC is the Jabber connection.
495 XML-DATA is the parsed tree data from the stream (stanzas)
496 obtained from `xml-parse-region'."
497
498 (let ((items (jabber-xml-get-children (jabber-iq-query xml-data) 'item)))
499 (if items
500 (dolist (item items)
501 (let ((jid (jabber-xml-get-attribute item 'jid))
502 (name (jabber-xml-get-attribute item 'name))
503 (node (jabber-xml-get-attribute item 'node)))
504 (insert
505 (jabber-propertize
506 (concat
507 (jabber-propertize
508 (concat jid "\n" (if node (format "Node: %s\n" node)))
509 'face 'jabber-title-medium)
510 name "\n\n")
511 'jabber-jid jid
512 'jabber-account jc
513 'jabber-node node))))
514 (insert "No items found.\n"))))
515
516 ;; Keys are ("jid" . "node"), where "node" is nil if appropriate.
517 ;; Values are (identities features), where each identity is ["name"
518 ;; "category" "type"], and each feature is a string.
519 (defvar jabber-disco-info-cache (make-hash-table :test 'equal))
520
521 ;; Keys are ("jid" . "node"). Values are (items), where each
522 ;; item is ["name" "jid" "node"] (some values may be nil).
523 (defvar jabber-disco-items-cache (make-hash-table :test 'equal))
524
525 (defun jabber-disco-get-info (jc jid node callback closure-data &optional force)
526 "Get disco info for JID and NODE, using connection JC.
527 Call CALLBACK with JC and CLOSURE-DATA as first and second
528 arguments and result as third argument when result is available.
529 On success, result is (IDENTITIES FEATURES), where each identity is [\"name\"
530 \"category\" \"type\"], and each feature is a string.
531 On error, result is the error node, recognizable by (eq (car result) 'error).
532
533 If CALLBACK is nil, just fetch data. If FORCE is non-nil,
534 invalidate cache and get fresh data."
535 (when force
536 (remhash (cons jid node) jabber-disco-info-cache))
537 (let ((result (unless force (jabber-disco-get-info-immediately jid node))))
538 (if result
539 (and callback (run-with-timer 0 nil callback jc closure-data result))
540 (jabber-send-iq jc jid
541 "get"
542 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
543 ,@(when node `((node . ,node)))))
544 #'jabber-disco-got-info (cons callback closure-data)
545 (lambda (jc xml-data callback-data)
546 (when (car callback-data)
547 (funcall (car callback-data) jc (cdr callback-data) (jabber-iq-error xml-data))))
548 (cons callback closure-data)))))
549
550 (defun jabber-disco-got-info (jc xml-data callback-data)
551 (let ((jid (jabber-xml-get-attribute xml-data 'from))
552 (node (jabber-xml-get-attribute (jabber-iq-query xml-data)
553 'node))
554 (result (jabber-disco-parse-info xml-data)))
555 (puthash (cons jid node) result jabber-disco-info-cache)
556 (when (car callback-data)
557 (funcall (car callback-data) jc (cdr callback-data) result))))
558
559 (defun jabber-disco-parse-info (xml-data)
560 "Extract data from an <iq/> stanza containing a disco#info result.
561 See `jabber-disco-get-info' for a description of the return value.
562
563 XML-DATA is the parsed tree data from the stream (stanzas)
564 obtained from `xml-parse-region'."
565 (list
566 (mapcar
567 #'(lambda (id)
568 (vector (jabber-xml-get-attribute id 'name)
569 (jabber-xml-get-attribute id 'category)
570 (jabber-xml-get-attribute id 'type)))
571 (jabber-xml-get-children (jabber-iq-query xml-data) 'identity))
572 (mapcar
573 #'(lambda (feature)
574 (jabber-xml-get-attribute feature 'var))
575 (jabber-xml-get-children (jabber-iq-query xml-data) 'feature))))
576
577 (defun jabber-disco-get-info-immediately (jid node)
578 "Get cached disco info for JID and NODE.
579 Return nil if no info available.
580
581 Fill the cache with `jabber-disco-get-info'."
582 (or
583 ;; Check "normal" cache...
584 (gethash (cons jid node) jabber-disco-info-cache)
585 ;; And then check Entity Capabilities.
586 (and (null node) (jabber-caps-get-cached jid))))
587
588 (defun jabber-disco-get-items (jc jid node callback closure-data &optional force)
589 "Get disco items for JID and NODE, using connection JC.
590 Call CALLBACK with JC and CLOSURE-DATA as first and second
591 arguments and items result as third argument when result is
592 available.
593 On success, result is a list of items, where each
594 item is [\"name\" \"jid\" \"node\"] (some values may be nil).
595 On error, result is the error node, recognizable by (eq (car result) 'error).
596
597 If CALLBACK is nil, just fetch data. If FORCE is non-nil,
598 invalidate cache and get fresh data."
599 (when force
600 (remhash (cons jid node) jabber-disco-items-cache))
601 (let ((result (gethash (cons jid node) jabber-disco-items-cache)))
602 (if result
603 (and callback (run-with-timer 0 nil callback jc closure-data result))
604 (jabber-send-iq jc jid
605 "get"
606 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
607 ,@(when node `((node . ,node)))))
608 #'jabber-disco-got-items (cons callback closure-data)
609 (lambda (jc xml-data callback-data)
610 (when (car callback-data)
611 (funcall (car callback-data) jc (cdr callback-data) (jabber-iq-error xml-data))))
612 (cons callback closure-data)))))
613
614 (defun jabber-disco-got-items (jc xml-data callback-data)
615 (let ((jid (jabber-xml-get-attribute xml-data 'from))
616 (node (jabber-xml-get-attribute (jabber-iq-query xml-data)
617 'node))
618 (result
619 (mapcar
620 #'(lambda (item)
621 (vector
622 (jabber-xml-get-attribute item 'name)
623 (jabber-xml-get-attribute item 'jid)
624 (jabber-xml-get-attribute item 'node)))
625 (jabber-xml-get-children (jabber-iq-query xml-data) 'item))))
626 (puthash (cons jid node) result jabber-disco-items-cache)
627 (when (car callback-data)
628 (funcall (car callback-data) jc (cdr callback-data) result))))
629
630 (defun jabber-disco-get-items-immediately (jid node)
631 (gethash (cons jid node) jabber-disco-items-cache))
632
633 (defun jabber-disco-publish (jc node item-name item-jid item-node)
634 "Publish the given item under disco node NODE."
635 (jabber-send-iq jc nil
636 "set"
637 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
638 ,@(when node `((node . ,node))))
639 (item ((action . "update")
640 (jid . ,item-jid)
641 ,@(when item-name
642 `((name . ,item-name)))
643 ,@(when item-node
644 `((node . ,item-node))))))
645 'jabber-report-success "Disco publish"
646 'jabber-report-success "Disco publish"))
647
648 (defun jabber-disco-publish-remove (jc node item-jid item-node)
649 "Remove the given item from published disco items.
650
651 JC is the Jabber connection."
652 (jabber-send-iq jc nil
653 "set"
654 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
655 ,@(when node `((node . ,node))))
656 (item ((action . "remove")
657 (jid . ,item-jid)
658 ,@(when item-node
659 `((node . ,item-node))))))
660 'jabber-report-success "Disco removal"
661 'jabber-report-success "Disco removal"))
662
663 (provide 'jabber-disco)
664
665 ;;; arch-tag: 71f5c76f-2956-4ed2-b871-9f5fe198092d