branch: elpa/jabber
commit 4a9ea2a8572f123a524823a479a2e8349f60e5d8
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    uri: Separate command dispatch
---
 lisp/jabber-uri.el        | 81 +++++++++++++++++++++++++++++++++++++++++++++++
 lisp/jabber-util.el       | 69 ----------------------------------------
 lisp/jabber.el            |  1 +
 tests/jabber-test-util.el | 25 +++++++++++++++
 4 files changed, 107 insertions(+), 69 deletions(-)

diff --git a/lisp/jabber-uri.el b/lisp/jabber-uri.el
new file mode 100644
index 0000000000..43df85e3ea
--- /dev/null
+++ b/lisp/jabber-uri.el
@@ -0,0 +1,81 @@
+;;; jabber-uri.el --- XMPP URI parsing and dispatch  -*- lexical-binding: t; 
-*-
+
+;; Copyright (C) 2026  Thanos Apollo
+
+;; Maintainer: Thanos Apollo <[email protected]>
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;;; Commentary:
+
+;; Parse XMPP URIs separately from dispatching their interactive commands.
+
+;;; Code:
+
+(require 'url-parse)
+(require 'jabber-ahc)
+(require 'jabber-chat)
+(require 'jabber-muc)
+(require 'jabber-register)
+(require 'jabber-util)
+
+(defun jabber-uri--parse-args (text)
+  "Return decoded key-value pairs from URI argument TEXT."
+  (when text
+    (mapcar (lambda (pair)
+              (pcase-let ((`(,key ,value) (split-string pair "=")))
+                (cons key (jabber-unhex value))))
+            (split-string text ";"))))
+
+(defun jabber-uri-parse (uri)
+  "Return the JID, method, and arguments parsed from XMPP URI."
+  (when (string-match "//" uri)
+    (error "URIs with authority part are not supported"))
+  (unless (string-match
+           "\\`xmpp:\\([^?]+\\)\\(?:\\?\\([a-z]+\\)\\(?:;\\(.*\\)\\)?\\)?\\'"
+           uri)
+    (error "Invalid XMPP URI '%s'" uri))
+  (list :jid (jabber-unhex (match-string 1 uri))
+        :method (match-string 2 uri)
+        :args (jabber-uri--parse-args (match-string 3 uri))))
+
+(defun jabber-uri--dispatch (uri-data)
+  "Run the command described by parsed URI-DATA."
+  (let ((account (jabber-read-account))
+        (jid (plist-get uri-data :jid))
+        (method (plist-get uri-data :method))
+        (args (plist-get uri-data :args)))
+    (cond
+     ((string= method "join")
+      (jabber-muc-join
+       account jid (jabber-muc-read-my-nickname account jid) t))
+     ((string= method "register")
+      (jabber-get-register account jid))
+     ((string= method "command")
+      (jabber-ahc-execute-command account jid (cdr (assoc "node" args))))
+     (t
+      (jabber-chat-with account jid)))))
+
+(defun jabber-handle-uri (uri &rest _ignored-args)
+  "Handle XMPP URI according to draft-saintandre-xmpp-iri-04.
+See Info node `(jabber)XMPP URIs'.
+IGNORED-ARGS are ignored arguments the handler may pass."
+  (interactive "sEnter XMPP URI: ")
+  (let ((uri-data (jabber-uri-parse uri)))
+    (raise-frame)
+    (jabber-uri--dispatch uri-data)))
+
+(defun jabber-url-xmpp (url)
+  "Handle XMPP URLs from internal Emacs functions."
+  (jabber-handle-uri (url-recreate-url url)))
+
+(fset 'url-xmpp #'jabber-url-xmpp)
+
+(provide 'jabber-uri)
+
+;;; jabber-uri.el ends here
diff --git a/lisp/jabber-util.el b/lisp/jabber-util.el
index 08eae7fb4b..cb36ada8a8 100644
--- a/lisp/jabber-util.el
+++ b/lisp/jabber-util.el
@@ -46,14 +46,6 @@
 ;; Global reference declarations
 
 (declare-function auth-source-search "auth-source" (&rest spec))
-(declare-function jabber-chat-with "jabber-chat.el"
-                  (jc jid &optional other-window))
-(declare-function jabber-ahc-execute-command "jabber-ahc.el" (jc to node))
-(declare-function jabber-get-register "jabber-register.el" (jc to))
-(declare-function jabber-muc-read-my-nickname "jabber-muc.el"
-                  (jc group &optional default))
-(declare-function jabber-muc-join "jabber-muc.el"
-                  (jc group nickname &optional popup))
 (defvar jabber-delay-xmlns)            ; jabber-xml.el
 (defvar jabber-delay-legacy-xmlns)     ; jabber-xml.el
 (defvar jabber-stanzas-xmlns)          ; jabber-xml.el
@@ -776,67 +768,6 @@ For example, \"ji%C5%99i@%C4%8Dechy.example/v%20Praze\" 
becomes
 \"jiři@čechy.example/v Praze\"."
   (decode-coding-string (url-unhex-string string) 'utf-8))
 
-(defun jabber-handle-uri (uri &rest _ignored-args)
-  "Handle XMPP links according to draft-saintandre-xmpp-iri-04.
-See Info node `(jabber)XMPP URIs'.
-URI is a string with the \"xmpp://\" link to handle.
-IGNORED-ARGS are ignored arguments the handler may pass."
-  (interactive "sEnter XMPP URI: ")
-
-  (when (string-match "//" uri)
-    (error "URIs with authority part are not supported"))
-
-  ;; This regexp handles three cases:
-  ;; xmpp:[email protected]
-  ;; xmpp:[email protected]?roster
-  ;; xmpp:[email protected]?roster;name=Romeo%20Montague;group=Lovers
-  (unless (string-match 
"^xmpp:\\([^?]+\\)\\(\\?\\([a-z]+\\)\\(;\\(.*\\)\\)?\\)?" uri)
-    (error "Invalid XMPP URI '%s'" uri))
-
-  ;; We start by raising the Emacs frame.
-  (raise-frame)
-
-  (let ((jid (jabber-unhex (match-string 1 uri)))
-       (method (match-string 3 uri))
-       (args (let ((text (match-string 5 uri)))
-               ;; If there are arguments...
-               (when text
-                 ;; ...split the pairs by ';'...
-                 (let ((pairs (split-string text ";")))
-                   (mapcar (lambda (pair)
-                             ;; ...and split keys from values by '='.
-                             (pcase-let ((`(,key ,value)
-                                          (split-string pair "=")))
-                               ;; Values can be hex-coded.
-                               (cons key (jabber-unhex value))))
-                           pairs))))))
-    ;; The full list of methods is at
-    ;; <URL:http://www.jabber.org/registrar/querytypes.html>.
-    (cond
-     ;; Join an MUC.
-     ((string= method "join")
-      (let ((account (jabber-read-account)))
-       (jabber-muc-join
-        account jid (jabber-muc-read-my-nickname account jid) t)))
-     ;; Register with a service.
-     ((string= method "register")
-      (jabber-get-register (jabber-read-account) jid))
-     ;; Run an ad-hoc command
-     ((string= method "command")
-      ;; XXX: does the 'action' attribute make sense?
-      (jabber-ahc-execute-command
-       (jabber-read-account) jid (cdr (assoc "node" args))))
-     ;; Everything else: open a chat buffer.
-     (t
-      (jabber-chat-with (jabber-read-account) jid)))))
-
-(defun jabber-url-xmpp (url)
-  "Handle XMPP URLs from internal Emacs functions."
-  ;; URL handlers are looked up by scheme as `url-SCHEME'.
-  (jabber-handle-uri (url-recreate-url url)))
-
-(fset 'url-xmpp #'jabber-url-xmpp)
-
 (defun jabber-string>-numerical (s1 s2)
   "Return t when S1 collates after S2 in numerical order."
   (cond ((string= s1 s2) nil)
diff --git a/lisp/jabber.el b/lisp/jabber.el
index 1250dcafb8..cff9b632e3 100644
--- a/lisp/jabber.el
+++ b/lisp/jabber.el
@@ -196,6 +196,7 @@ One disabled account with a non-standard port:
 (require 'jabber-disco-menu)
 (require 'jabber-roster-menu)
 (require 'jabber-chat-commands)
+(require 'jabber-uri)
 (require 'jabber-truncate)
 (when (featurep 'dbusbind)
   (require 'jabber-notifications nil t))
diff --git a/tests/jabber-test-util.el b/tests/jabber-test-util.el
index f75df5f379..c1235fb8fa 100644
--- a/tests/jabber-test-util.el
+++ b/tests/jabber-test-util.el
@@ -8,6 +8,7 @@
 
 (require 'ert)
 (require 'jabber-util)
+(require 'jabber-uri)
 
 (defvar jabber-jid-obarray (make-vector 127 0))
 
@@ -351,5 +352,29 @@
     (should (equal '("[email protected]" "[email protected]" "[email protected]")
                    (funcall sorter '("[email protected]" "[email protected]" 
"[email protected]"))))))
 
+;;; Group 7: XMPP URI parsing
+
+(ert-deftest jabber-test-uri-parse-chat ()
+  "Parse a plain chat URI without a query method."
+  (should (equal (jabber-uri-parse "xmpp:[email protected]")
+                 '(:jid "[email protected]" :method nil :args nil))))
+
+(ert-deftest jabber-test-uri-parse-method-and-arguments ()
+  "Parse and decode an XMPP URI method and its arguments."
+  (should (equal
+           (jabber-uri-parse
+            "xmpp:[email protected]?command;node=hello%20world;action=next")
+           '(:jid "[email protected]"
+             :method "command"
+             :args (("node" . "hello world") ("action" . "next"))))))
+
+(ert-deftest jabber-test-uri-rejects-authority ()
+  "Reject XMPP URIs containing an authority part."
+  (should-error (jabber-uri-parse "xmpp://[email protected]")))
+
+(ert-deftest jabber-test-uri-rejects-trailing-text ()
+  "Reject text after a complete XMPP URI."
+  (should-error (jabber-uri-parse "xmpp:[email protected]?join trailing")))
+
 (provide 'jabber-test-util)
 ;;; jabber-test-util.el ends here

Reply via email to