]> crepu.dev Git - config.git/blob - djavu-asus/emacs/elpa/jabber-20230715.456/jabber-util.el
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpa / jabber-20230715.456 / jabber-util.el
1 ;; jabber-util.el - various utility functions -*- coding: utf-8; -*-
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 ;; Copyright (C) 2008, 2010 - Terechkov Evgenii - evg@altlinux.org
6 ;; Copyright (C) 2010 - Kirill A. Korinskiy - catap@catap.ru
7
8 ;; This file is a part of jabber.el.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2 of the License, or
13 ;; (at your option) any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program; if not, write to the Free Software
22 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
24 (require 'cl-lib)
25 (require 'password-cache)
26 (condition-case nil
27 (require 'auth-source)
28 (error nil))
29
30 (defvar jabber-jid-history nil
31 "History of entered JIDs.")
32
33 (defalias 'jabber-propertize 'propertize)
34
35 (defsubst jabber-read-with-input-method (prompt &optional initial-contents history default-value)
36 "Like `read-string', but always inheriting the current input method."
37 ;; Preserve input method when entering a minibuffer.
38 (read-string prompt initial-contents history default-value t))
39
40 (unless (fboundp 'delete-and-extract-region)
41 (defsubst delete-and-extract-region (start end)
42 (prog1
43 (buffer-substring start end)
44 (delete-region start end))))
45
46 (unless (fboundp 'access-file)
47 (defsubst access-file (filename error-message)
48 (unless (file-readable-p filename)
49 (error error-message))))
50
51 (defalias 'jabber-float-time 'float-time)
52
53 (defalias 'jabber-cancel-timer 'cancel-timer)
54
55 (defvar jabber-connections)
56 (defun jabber-concat-rosters ()
57 "Concatenate the rosters of all connected accounts."
58 (apply #'append
59 (mapcar
60 (lambda (jc)
61 (plist-get (fsm-get-state-data jc) :roster))
62 jabber-connections)))
63
64 (defun jabber-concat-rosters-full ()
65 "Concatenate the rosters of all connected accounts.
66 Show full JIDs, with resources."
67 (let ((jids (apply #'append
68 (mapcar
69 (lambda (jc)
70 (plist-get (fsm-get-state-data jc) :roster))
71 jabber-connections))))
72 (apply #'append
73 (mapcar (lambda (jid)
74 (mapcar (lambda (res) (intern (format "%s/%s" jid (car res))))
75 (get (jabber-jid-symbol jid) 'resources)))
76 jids))))
77
78 (defun jabber-connection-jid (jc)
79 "Return the full JID of connection JC."
80 (let ((sd (fsm-get-state-data jc)))
81 (concat (plist-get sd :username) "@"
82 (plist-get sd :server) "/"
83 (plist-get sd :resource))))
84
85 (defun jabber-connection-bare-jid (jc)
86 "Return the bare JID of connection JC."
87 (let ((sd (fsm-get-state-data jc)))
88 (concat (plist-get sd :username) "@"
89 (plist-get sd :server))))
90
91 (defun jabber-connection-original-jid (jc)
92 "Return the original JID of connection JC.
93 The \"original JID\" is the JID we authenticated with. The
94 server might subsequently assign us a different JID at resource
95 binding."
96 (plist-get (fsm-get-state-data jc) :original-jid))
97
98 (defun jabber-find-connection (bare-jid)
99 "Find the connection to the account named by BARE-JID.
100 Return nil if none found."
101 (cl-dolist (jc jabber-connections)
102 (when (string= bare-jid (jabber-connection-bare-jid jc))
103 (cl-return jc))))
104
105 (defun jabber-find-active-connection (dead-jc)
106 "Find an active connection for dead connection DEAD-JC.
107 Return nil if none found."
108 (let ((jid (jabber-connection-bare-jid dead-jc)))
109 (jabber-find-connection jid)))
110
111 (defun jabber-jid-username (jid)
112 "Return the username portion of JID, or nil if none found.
113 JID must be a string."
114 (when (string-match "\\(.*\\)@.*\\(/.*\\)?" jid)
115 (match-string 1 jid)))
116
117 (defun jabber-jid-user (jid)
118 "Return the user portion (username@server) of JID.
119 JID must be a string."
120 ;;transports don't have @, so don't require it
121 ;;(string-match ".*@[^/]*" jid)
122 (string-match "[^/]*" jid)
123 (match-string 0 jid))
124
125 (defun jabber-jid-server (jid)
126 "Return the server portion of JID."
127 (string-match "^\\(.*@\\)?\\([^@/]+\\)\\(/.*\\)?$" jid)
128 (match-string 2 jid))
129
130 (defun jabber-jid-rostername (user)
131 "Return the name of USER if present in roster, or nil."
132 (let ((user (jabber-jid-symbol user)))
133 (if (> (length (get user 'name)) 0)
134 (get user 'name))))
135
136 (defun jabber-jid-displayname (string)
137 "Return the name of the user from STRING as in roster, else username@server."
138 (or (jabber-jid-rostername string)
139 (jabber-jid-user (if (symbolp string)
140 (symbol-name string)
141 string))))
142
143 (defvar jabber-bookmarks)
144 (defun jabber-jid-bookmarkname (string)
145 "Return from STRING the conference name from boomarks or displayname.
146 Use the name according to roster or else the JID if none set."
147 (or (cl-loop for conference in (cl-first (cl-loop for value being the hash-values of jabber-bookmarks
148 collect value))
149 do (let ((ls (cadr conference)))
150 (if (string= (cdr (assoc 'jid ls)) string)
151 (cl-return (cdr (assoc 'name ls))))))
152 (jabber-jid-displayname string)))
153
154 (defun jabber-jid-resource (jid)
155 "Return the resource portion of a JID, or nil if there is none.
156 JID must be a string."
157 (when (string-match "^\\(\\([^/]*@\\)?[^/]*\\)/\\(.*\\)" jid)
158 (match-string 3 jid)))
159
160 (defvar jabber-jid-obarray)
161 (defun jabber-jid-symbol (jid)
162 "Return the symbol for JID, which must be a symbol or a string."
163 ;; If it's already a symbol, just return it.
164 (if (symbolp jid)
165 jid
166 ;; XXX: "downcase" is a poor man's nodeprep. See XMPP CORE.
167 (intern (downcase (jabber-jid-user jid)) jabber-jid-obarray)))
168
169 (defvar jabber-account-list)
170 (defun jabber-my-jid-p (jc jid)
171 "Return non-nil if the specified JID is in the `jabber-account-list'.
172 Comment: (modulo resource).
173 Also return non-nil if JID matches JC, modulo resource."
174 (or
175 (equal (jabber-jid-user jid)
176 (jabber-connection-bare-jid jc))
177 (member (jabber-jid-user jid) (mapcar (lambda (x) (jabber-jid-user (car x))) jabber-account-list))))
178
179 (defvar *jabber-active-groupchats*)
180 (defun jabber-read-jid-completing (prompt &optional subset require-match default resource fulljids)
181 "Read a jid out of the current roster from the minibuffer.
182 If SUBSET is non-nil, it should be a list of symbols from which
183 the JID is to be selected, instead of using the entire roster.
184 If REQUIRE-MATCH is non-nil, the JID must be in the list used.
185 If DEFAULT is non-nil, it's used as the default value, otherwise
186 the default is inferred from context.
187 RESOURCE is one of the following:
188
189 nil Accept full or bare JID, as entered
190 full Turn bare JIDs to full ones with highest-priority resource
191 bare-or-muc Turn full JIDs to bare ones, except for in MUC
192
193 If FULLJIDS is non-nil, complete jids with resources."
194 (let ((jid-at-point (or
195 (and default
196 ;; default can be either a symbol or a string
197 (if (symbolp default)
198 (symbol-name default)
199 default))
200 (let* ((jid (get-text-property (point) 'jabber-jid))
201 (res (get (jabber-jid-symbol jid) 'resource)))
202 (when jid
203 (if (and fulljids res (not (jabber-jid-resource jid)))
204 (format "%s/%s" jid res)
205 jid)))
206 (bound-and-true-p jabber-chatting-with)
207 (bound-and-true-p jabber-group)))
208 (completion-ignore-case t)
209 (jid-completion-table (mapcar #'(lambda (item)
210 (cons (symbol-name item) item))
211 (or subset (funcall (if fulljids
212 'jabber-concat-rosters-full
213 'jabber-concat-rosters)))))
214 chosen)
215 (dolist (item (or subset (jabber-concat-rosters)))
216 (if (get item 'name)
217 (push (cons (get item 'name) item) jid-completion-table)))
218 ;; if the default is not in the allowed subset, it's not a good default
219 (if (and subset (not (assoc jid-at-point jid-completion-table)))
220 (setq jid-at-point nil))
221 (let ((input
222 (completing-read (concat prompt
223 (if jid-at-point
224 (format "(default %s) " jid-at-point)))
225 jid-completion-table
226 nil require-match nil 'jabber-jid-history jid-at-point)))
227 (setq chosen
228 (if (and input (assoc-string input jid-completion-table t))
229 (symbol-name (cdr (assoc-string input jid-completion-table t)))
230 (and (not (zerop (length input)))
231 input))))
232
233 (when chosen
234 (cl-case resource
235 (full
236 ;; If JID is bare, add the highest-priority resource.
237 (if (jabber-jid-resource chosen)
238 chosen
239 (let ((highest-resource (get (jabber-jid-symbol chosen) 'resource)))
240 (if highest-resource
241 (concat chosen "/" highest-resource)
242 chosen))))
243 (bare-or-muc
244 ;; If JID is full and non-MUC, remove resource.
245 (if (null (jabber-jid-resource chosen))
246 chosen
247 (let ((bare (jabber-jid-user chosen)))
248 (if (assoc bare *jabber-active-groupchats*)
249 chosen
250 bare))))
251 (t
252 chosen)))))
253
254 (defun jabber-read-node (prompt)
255 "Read node name, taking default from disco item at point."
256 (let ((node-at-point (get-text-property (point) 'jabber-node)))
257 (read-string (concat prompt
258 (if node-at-point
259 (format "(default %s) " node-at-point)))
260 node-at-point)))
261
262 (defun jabber-password-key (bare-jid)
263 "Construct key for `password' library from BARE-JID."
264 (concat "xmpp:" bare-jid))
265
266 (defun jabber-read-password (bare-jid)
267 "Read Jabber password from minibuffer."
268 (let ((found
269 (and (fboundp 'auth-source-search)
270 (nth 0 (auth-source-search
271 :user (jabber-jid-username bare-jid)
272 :host (jabber-jid-server bare-jid)
273 :port "xmpp"
274 :max 1
275 :require '(:secret))))))
276 (if found
277 (let ((secret (plist-get found :secret)))
278 (copy-sequence
279 (if (functionp secret)
280 (funcall secret)
281 secret)))
282 (let ((prompt (format "Jabber password for %s: " bare-jid)))
283 ;; Need to copy the password, as sasl.el wants to erase it.
284 (copy-sequence
285 (password-read prompt (jabber-password-key bare-jid)))))))
286
287 (defun jabber-cache-password (bare-jid password)
288 "Cache PASSWORD for BARE-JID."
289 (password-cache-add (jabber-password-key bare-jid) password))
290
291 (defun jabber-uncache-password (bare-jid)
292 "Uncache cached password for BARE-JID.
293 Useful if the password proved to be wrong."
294 (interactive (list (jabber-jid-user
295 (completing-read "Forget password of account: " jabber-account-list nil nil nil 'jabber-account-history))))
296 (password-cache-remove (jabber-password-key bare-jid)))
297
298 (defvar jabber-buffer-connection)
299 (defun jabber-read-account (&optional always-ask contact-hint)
300 "Ask for which connected account to use.
301 If ALWAYS-ASK is nil and there is only one account, return that
302 account.
303 If CONTACT-HINT is a string or a JID symbol, default to an account
304 that has that contact in its roster."
305 (let ((completions
306 (mapcar (lambda (c)
307 (cons
308 (jabber-connection-bare-jid c)
309 c))
310 jabber-connections)))
311 (cond
312 ((null jabber-connections)
313 (error "Not connected to Jabber"))
314 ((and (null (cdr jabber-connections)) (not always-ask))
315 ;; only one account
316 (car jabber-connections))
317 (t
318 (or
319 ;; if there is a jabber-account property at point,
320 ;; present it as default value
321 (cdr (assoc (let ((at-point (get-text-property (point) 'jabber-account)))
322 (when (and at-point
323 (memq at-point jabber-connections))
324 (jabber-connection-bare-jid at-point))) completions))
325 (let* ((default
326 (or
327 (and contact-hint
328 (setq contact-hint (jabber-jid-symbol contact-hint))
329 (let ((matching
330 (cl-find-if
331 (lambda (jc)
332 (memq contact-hint (plist-get (fsm-get-state-data jc) :roster)))
333 jabber-connections)))
334 (when matching
335 (jabber-connection-bare-jid matching))))
336 ;; if the buffer is associated with a connection, use it
337 (when (and jabber-buffer-connection
338 (jabber-find-active-connection jabber-buffer-connection))
339 (jabber-connection-bare-jid jabber-buffer-connection))
340 ;; else, use the first connection in the list
341 (caar completions)))
342 (input (completing-read
343 (concat "Select Jabber account (default "
344 default
345 "): ")
346 completions nil t nil 'jabber-account-history
347 default)))
348 (cdr (assoc input completions))))))))
349
350 (defun jabber-iq-query (xml-data)
351 "Return the query part of an IQ stanza.
352 An IQ stanza may have zero or one query child, and zero or one <error/> child.
353 The query child is often but not always <query/>.
354
355 XML-DATA is the parsed tree data from the stream (stanzas)
356 obtained from `xml-parse-region'."
357 (let (query)
358 (dolist (x (jabber-xml-node-children xml-data))
359 (if (and
360 (listp x)
361 (not (eq (jabber-xml-node-name x) 'error)))
362 (setq query x)))
363 query))
364
365 (defun jabber-iq-error (xml-data)
366 "Return the <error/> part of an IQ stanza, if any.
367
368 XML-DATA is the parsed tree data from the stream (stanzas)
369 obtained from `xml-parse-region'."
370 (car (jabber-xml-get-children xml-data 'error)))
371
372 (defun jabber-iq-xmlns (xml-data)
373 "Return the namespace of an IQ stanza, i.e. the namespace of its query part.
374
375 XML-DATA is the parsed tree data from the stream (stanzas)
376 obtained from `xml-parse-region'."
377 (jabber-xml-get-attribute (jabber-iq-query xml-data) 'xmlns))
378
379 (defun jabber-message-timestamp (xml-data)
380 "Given a <message/> element, return its timestamp, or nil if none.
381
382 XML-DATA is the parsed tree data from the stream (stanzas)
383 obtained from `xml-parse-region'."
384 (jabber-x-delay
385 (or
386 (jabber-xml-path xml-data '(("urn:xmpp:delay" . "delay")))
387 (jabber-xml-path xml-data '(("jabber:x:delay" . "x"))))))
388
389 (defun jabber-x-delay (xml-data)
390 "Return timestamp given a delayed delivery element.
391 This can be either a <delay/> tag in namespace urn:xmpp:delay (XEP-0203), or
392 a <x/> tag in namespace jabber:x:delay (XEP-0091).
393 Return nil if no such data available.
394
395 XML-DATA is the parsed tree data from the stream (stanzas)
396 obtained from `xml-parse-region'."
397 (cond
398 ((and (eq (jabber-xml-node-name xml-data) 'x)
399 (string= (jabber-xml-get-attribute xml-data 'xmlns) "jabber:x:delay"))
400 (let ((stamp (jabber-xml-get-attribute xml-data 'stamp)))
401 (if (and (stringp stamp)
402 (= (length stamp) 17))
403 (jabber-parse-legacy-time stamp))))
404 ((and (eq (jabber-xml-node-name xml-data) 'delay)
405 (string= (jabber-xml-get-attribute xml-data 'xmlns) "urn:xmpp:delay"))
406 (let ((stamp (jabber-xml-get-attribute xml-data 'stamp)))
407 (when (stringp stamp)
408 (jabber-parse-time stamp))))))
409
410 (defun jabber-parse-legacy-time (timestamp)
411 "Parse timestamp in ccyymmddThh:mm:ss format (UTC) and return as internal
412 time value."
413 (let ((year (string-to-number (substring timestamp 0 4)))
414 (month (string-to-number (substring timestamp 4 6)))
415 (day (string-to-number (substring timestamp 6 8)))
416 (hour (string-to-number (substring timestamp 9 11)))
417 (minute (string-to-number (substring timestamp 12 14)))
418 (second (string-to-number (substring timestamp 15 17))))
419 (encode-time second minute hour day month year 0)))
420
421 (defun jabber-encode-legacy-time (timestamp)
422 "Parse TIMESTAMP as internal time value and encode as ccyymmddThh:mm:ss (UTC)."
423 (if (featurep 'xemacs)
424 ;; XEmacs doesn't have `universal' argument to format-time-string,
425 ;; so we have to do it ourselves.
426 (format-time-string "%Y%m%dT%H:%M:%S"
427 (time-subtract timestamp
428 (list 0 (car (current-time-zone)))))
429 (format-time-string "%Y%m%dT%H:%M:%S" timestamp t)))
430
431 (defun jabber-encode-time (time)
432 "Convert TIME to a string by XEP-0082.
433 TIME is in a format accepted by `format-time-string'."
434 (format-time-string "%Y-%m-%dT%H:%M:%SZ" time t))
435
436 (defun jabber-encode-timezone ()
437 (let ((time-zone-offset (nth 0 (current-time-zone))))
438 (if (null time-zone-offset)
439 "Z"
440 (let* ((positivep (>= time-zone-offset 0))
441 (hours (/ (abs time-zone-offset) 3600))
442 (minutes (/ (% (abs time-zone-offset) 3600) 60)))
443 (format "%s%02d:%02d"(if positivep "+" "-") hours minutes)))))
444
445 (defun jabber-parse-time (raw-time)
446 "Parse the DateTime encoded in TIME according to XEP-0082."
447 (let* ((time (if (string= (substring raw-time 4 5) "-")
448 raw-time
449 (concat
450 (substring raw-time 0 4) "-"
451 (substring raw-time 4 6) "-"
452 (substring raw-time 6 (length raw-time)))))
453 (year (string-to-number (substring time 0 4)))
454 (month (string-to-number (substring time 5 7)))
455 (day (string-to-number (substring time 8 10)))
456 (hour (string-to-number (substring time 11 13)))
457 (minute (string-to-number (substring time 14 16)))
458 (second (string-to-number (substring time 17 19)))
459 (timezone (if (eq (aref time 19) ?.)
460 ;; fractions are optional
461 (let ((timezone (cadr
462 (split-string (substring time 20)
463 "[-+Z]"))))
464 (if (string= "" timezone)
465 "Z"
466 timezone))
467 (substring time 19))))
468 ;; timezone is either Z (UTC) or [+-]HH:MM
469 (let ((timezone-seconds
470 (if (string= timezone "Z")
471 0
472 (* (if (eq (aref timezone 0) ?+) 1 -1)
473 (* 60 (+ (* 60 (string-to-number (substring timezone 1 3)))
474 (string-to-number (substring timezone 4 6))))))))
475 (encode-time second minute hour day month year timezone-seconds))))
476
477 (defun jabber-report-success (_jc xml-data context)
478 "IQ callback reporting success or failure of the operation.
479 CONTEXT is a string describing the action.
480 \"CONTEXT succeeded\" or \"CONTEXT failed: REASON\" is displayed in
481 the echo area.
482 JC is the Jabber connection.
483 XML-DATA is the parsed tree data from the stream (stanzas)
484 obtained from `xml-parse-region'."
485 (let ((type (jabber-xml-get-attribute xml-data 'type)))
486 (message (concat context
487 (if (string= type "result")
488 " succeeded"
489 (concat
490 " failed: "
491 (let ((the-error (jabber-iq-error xml-data)))
492 (if the-error
493 (jabber-parse-error the-error)
494 "No error message given"))))))))
495
496 (defconst jabber-error-messages
497 (list
498 (cons 'bad-request "Bad request")
499 (cons 'conflict "Conflict")
500 (cons 'feature-not-implemented "Feature not implemented")
501 (cons 'forbidden "Forbidden")
502 (cons 'gone "Gone")
503 (cons 'internal-server-error "Internal server error")
504 (cons 'item-not-found "Item not found")
505 (cons 'jid-malformed "JID malformed")
506 (cons 'not-acceptable "Not acceptable")
507 (cons 'not-allowed "Not allowed")
508 (cons 'not-authorized "Not authorized")
509 (cons 'payment-required "Payment required")
510 (cons 'recipient-unavailable "Recipient unavailable")
511 (cons 'redirect "Redirect")
512 (cons 'registration-required "Registration required")
513 (cons 'remote-server-not-found "Remote server not found")
514 (cons 'remote-server-timeout "Remote server timeout")
515 (cons 'resource-constraint "Resource constraint")
516 (cons 'service-unavailable "Service unavailable")
517 (cons 'subscription-required "Subscription required")
518 (cons 'undefined-condition "Undefined condition")
519 (cons 'unexpected-request "Unexpected request"))
520 "String descriptions of XMPP stanza errors.")
521
522 (defconst jabber-legacy-error-messages
523 (list
524 (cons 302 "Redirect")
525 (cons 400 "Bad request")
526 (cons 401 "Unauthorized")
527 (cons 402 "Payment required")
528 (cons 403 "Forbidden")
529 (cons 404 "Not found")
530 (cons 405 "Not allowed")
531 (cons 406 "Not acceptable")
532 (cons 407 "Registration required")
533 (cons 408 "Request timeout")
534 (cons 409 "Conflict")
535 (cons 500 "Internal server error")
536 (cons 501 "Not implemented")
537 (cons 502 "Remote server error")
538 (cons 503 "Service unavailable")
539 (cons 504 "Remote server timeout")
540 (cons 510 "Disconnected"))
541 "String descriptions of legacy errors (XEP-0086).")
542
543 (defun jabber-parse-error (error-xml)
544 "Parse the given <error/> tag and return a string fit for human consumption.
545 See secton 9.3, Stanza Errors, of XMPP Core, and XEP-0086, Legacy Errors."
546 (let ((error-type (jabber-xml-get-attribute error-xml 'type))
547 (error-code (jabber-xml-get-attribute error-xml 'code))
548 condition text)
549 (if error-type
550 ;; If the <error/> tag has a type element, it is new-school.
551 (dolist (child (jabber-xml-node-children error-xml))
552 (when (string=
553 (jabber-xml-get-attribute child 'xmlns)
554 "urn:ietf:params:xml:ns:xmpp-stanzas")
555 (if (eq (jabber-xml-node-name child) 'text)
556 (setq text (car (jabber-xml-node-children child)))
557 (setq condition
558 (or (cdr (assq (jabber-xml-node-name child) jabber-error-messages))
559 (symbol-name (jabber-xml-node-name child)))))))
560 (setq condition (or (cdr (assq (string-to-number error-code) jabber-legacy-error-messages))
561 error-code))
562 (setq text (car (jabber-xml-node-children error-xml))))
563 (concat condition
564 (if text (format ": %s" text)))))
565
566 (defun jabber-error-condition (error-xml)
567 "Parse the given <error/> tag and return the condition symbol."
568 (catch 'condition
569 (dolist (child (jabber-xml-node-children error-xml))
570 (when (string=
571 (jabber-xml-get-attribute child 'xmlns)
572 "urn:ietf:params:xml:ns:xmpp-stanzas")
573 (throw 'condition (jabber-xml-node-name child))))))
574
575 (defvar jabber-stream-error-messages
576 (list
577 (cons 'bad-format "Bad XML format")
578 (cons 'bad-namespace-prefix "Bad namespace prefix")
579 (cons 'conflict "Conflict")
580 (cons 'connection-timeout "Connection timeout")
581 (cons 'host-gone "Host gone")
582 (cons 'host-unknown "Host unknown")
583 (cons 'improper-addressing "Improper addressing") ; actually only s2s
584 (cons 'internal-server-error "Internal server error")
585 (cons 'invalid-from "Invalid from")
586 (cons 'invalid-id "Invalid id")
587 (cons 'invalid-namespace "Invalid namespace")
588 (cons 'invalid-xml "Invalid XML")
589 (cons 'not-authorized "Not authorized")
590 (cons 'policy-violation "Policy violation")
591 (cons 'remote-connection-failed "Remote connection failed")
592 (cons 'resource-constraint "Resource constraint")
593 (cons 'restricted-xml "Restricted XML")
594 (cons 'see-other-host "See other host")
595 (cons 'system-shutdown "System shutdown")
596 (cons 'undefined-condition "Undefined condition")
597 (cons 'unsupported-encoding "Unsupported encoding")
598 (cons 'unsupported-stanza-type "Unsupported stanza type")
599 (cons 'unsupported-version "Unsupported version")
600 (cons 'xml-not-well-formed "XML not well formed"))
601 "String descriptions of XMPP stream errors.")
602
603 (defun jabber-stream-error-condition (error-xml)
604 "Return the condition of a <stream:error/> tag."
605 ;; as we don't know the node name of the condition, we have to
606 ;; search for it.
607 (cl-dolist (node (jabber-xml-node-children error-xml))
608 (when (and (string= (jabber-xml-get-attribute node 'xmlns)
609 "urn:ietf:params:xml:ns:xmpp-streams")
610 (assq (jabber-xml-node-name node)
611 jabber-stream-error-messages))
612 (cl-return (jabber-xml-node-name node)))))
613
614 (defun jabber-parse-stream-error (error-xml)
615 "Parse the given error tag and return a string fit for human consumption.
616 ERROR-XML is a <stream:error/> tag parsed with `xml-parse-region'."
617 (let ((text-node (car (jabber-xml-get-children error-xml 'text)))
618 (condition (jabber-stream-error-condition error-xml)))
619 (concat (if condition (cdr (assq condition jabber-stream-error-messages))
620 "Unknown stream error")
621 (if (and text-node (stringp (car (jabber-xml-node-children text-node))))
622 (concat ": " (car (jabber-xml-node-children text-node)))))))
623
624 (put 'jabber-error
625 'error-conditions
626 '(error jabber-error))
627 (put 'jabber-error
628 'error-message
629 "Jabber error")
630
631
632 ;; https://www.rfc-editor.org/rfc/rfc6120.html#section-8.3 explains
633 ;; that there are stanza errors, which are recoverable and do not
634 ;; terminate the stream.
635
636 ;; Each stanza has a type which are the one explained at the
637 ;; ERROR-TYPE parameter. checkdoc throws warnings stating that errors
638 ;; messages should start with capital letters, thus the `downcase'
639 ;; function is used as a workaround.
640 (defun jabber-signal-error (error-type condition &optional text app-specific)
641 "Signal an error to be sent by Jabber.
642 ERROR-TYPE is one of \"Cancel\", \"Continue\", \"Mmodify\", \"Auth\"
643 and \"Wait\" (lowercase versions make `checkdoc' to throw errors).
644 CONDITION is a symbol denoting a defined XMPP condition.
645 TEXT is a string to be sent in the error message, or nil for no text.
646 APP-SPECIFIC is a list of extra XML tags.
647
648 See section 9.3 of XMPP Core (RFC 3920).
649 See section 8.3 of XMPP Core (RFC 6120)."
650 (signal 'jabber-error
651 (list (downcase error-type) condition text app-specific)))
652
653 (defun jabber-unhex (string)
654 "Convert a hex-encoded UTF-8 string to Emacs representation.
655 For example, \"ji%C5%99i@%C4%8Dechy.example/v%20Praze\" becomes
656 \"jiři@čechy.example/v Praze\"."
657 (decode-coding-string (url-unhex-string string) 'utf-8))
658
659 (defun jabber-handle-uri (uri &rest _ignored-args)
660 "Handle XMPP links according to draft-saintandre-xmpp-iri-04.
661 See Info node `(jabber)XMPP URIs'.
662 URI is a string with the \"xmpp://\" link to handle.
663 IGNORED-ARGS are ignored arguments the handler may pass. "
664 (interactive "sEnter XMPP URI: ")
665
666 (when (string-match "//" uri)
667 (error "URIs with authority part are not supported"))
668
669 ;; This regexp handles three cases:
670 ;; xmpp:romeo@montague.net
671 ;; xmpp:romeo@montague.net?roster
672 ;; xmpp:romeo@montague.net?roster;name=Romeo%20Montague;group=Lovers
673 (unless (string-match "^xmpp:\\([^?]+\\)\\(\\?\\([a-z]+\\)\\(;\\(.*\\)\\)?\\)?" uri)
674 (error "Invalid XMPP URI '%s'" uri))
675
676 ;; We start by raising the Emacs frame.
677 (raise-frame)
678
679 (let ((jid (jabber-unhex (match-string 1 uri)))
680 (method (match-string 3 uri))
681 (args (let ((text (match-string 5 uri)))
682 ;; If there are arguments...
683 (when text
684 ;; ...split the pairs by ';'...
685 (let ((pairs (split-string text ";")))
686 (mapcar (lambda (pair)
687 ;; ...and split keys from values by '='.
688 (cl-destructuring-bind (key value)
689 (split-string pair "=")
690 ;; Values can be hex-coded.
691 (cons key (jabber-unhex value))))
692 pairs))))))
693 ;; The full list of methods is at
694 ;; <URL:http://www.jabber.org/registrar/querytypes.html>.
695 (cond
696 ;; Join an MUC.
697 ((string= method "join")
698 (let ((account (jabber-read-account)))
699 (jabber-muc-join
700 account jid (jabber-muc-read-my-nickname account jid) t)))
701 ;; Register with a service.
702 ((string= method "register")
703 (jabber-get-register (jabber-read-account) jid))
704 ;; Run an ad-hoc command
705 ((string= method "command")
706 ;; XXX: does the 'action' attribute make sense?
707 (jabber-ahc-execute-command
708 (jabber-read-account) jid (cdr (assoc "node" args))))
709 ;; Everything else: open a chat buffer.
710 (t
711 (jabber-chat-with (jabber-read-account) jid)))))
712
713 (defun url-xmpp (url)
714 "Handle XMPP URLs from internal Emacs functions."
715 ;; XXX: This parsing roundtrip is redundant, and the parser of the
716 ;; url package might lose information.
717 (jabber-handle-uri (url-recreate-url url)))
718
719 (defun string>-numerical (s1 s2)
720 "Return t if first arg string is more than second in numerical order."
721 (cond ((string= s1 s2) nil)
722 ((> (length s1) (length s2)) t)
723 ((< (length s1) (length s2)) nil)
724 ((< (string-to-number (substring s1 0 1)) (string-to-number (substring s2 0 1))) nil)
725 ((> (string-to-number (substring s1 0 1)) (string-to-number (substring s2 0 1))) t)
726 (t (string>-numerical (substring s1 1) (substring s2 1)))))
727
728 (defun jabber-append-string-to-file (string file &optional func &rest args)
729 "Append STRING (may be nil) to FILE. Create FILE if needed.
730 If FUNC is non-nil, then call FUNC with ARGS at beginning of
731 temporaly buffer _before_ inserting STRING."
732 (when (or (stringp string) (functionp func))
733 (with-temp-buffer
734 (when (functionp func) (apply func args))
735 (when (stringp string) (insert string))
736 (write-region (point-min) (point-max) file t (list t)))))
737
738 (defun jabber-tree-map (fn tree)
739 "Apply FN to all nodes in the TREE starting with root.
740 FN is applied to the node and not to the data itself."
741 (let ((result (cons nil nil)))
742 (cl-do ((tail tree (cdr tail))
743 (prev result end)
744 (end result (let* ((x (car tail))
745 (val (if (atom x)
746 (funcall fn x)
747 (jabber-tree-map fn x))))
748 (setf (car end) val (cdr end) (cons nil
749 nil)))))
750 ((atom tail)
751 (progn
752 (setf (cdr prev) (if tail (funcall fn tail) nil))
753 result)))))
754
755 (provide 'jabber-util)
756
757 ;;; arch-tag: cfbb73ac-e2d7-4652-a08d-dc789bcded8a