Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gajim/common/const.py13
-rw-r--r--gajim/common/helpers.py83
-rw-r--r--gajim/common/structs.py4
-rw-r--r--gajim/gtk/menus.py4
-rw-r--r--gajim/gtk/start_chat.py9
-rw-r--r--test/no_gui/test_styling.py2
6 files changed, 74 insertions, 41 deletions
diff --git a/gajim/common/const.py b/gajim/common/const.py
index 278a04edd..5cc9cb4b7 100644
--- a/gajim/common/const.py
+++ b/gajim/common/const.py
@@ -15,6 +15,7 @@
from __future__ import annotations
from typing import Any
+from typing import Optional
from typing import Union
from typing import NamedTuple
@@ -226,10 +227,18 @@ class URIType(Enum):
TEL = 'tel'
-class URIAction(Enum):
+# https://xmpp.org/registrar/querytypes.html
+class XmppUriQuery(Enum):
+ NONE = ''
MESSAGE = 'message'
JOIN = 'join'
- SUBSCRIBE = 'subscribe'
+
+ @staticmethod
+ def from_str(s: str) -> Optional[XmppUriQuery]:
+ try:
+ return XmppUriQuery(s)
+ except ValueError:
+ return None
class MUCJoinedState(Enum):
diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py
index 25f9c75bc..438cf3456 100644
--- a/gajim/common/helpers.py
+++ b/gajim/common/helpers.py
@@ -94,7 +94,7 @@ from gajim.common.i18n import get_rfc5646_lang
from gajim.common.const import SHOW_STRING
from gajim.common.const import SHOW_STRING_MNEMONIC
from gajim.common.const import URIType
-from gajim.common.const import URIAction
+from gajim.common.const import XmppUriQuery
from gajim.common.const import GIO_TLS_ERRORS
from gajim.common.const import SHOW_LIST
from gajim.common.const import CONSONANTS
@@ -936,26 +936,30 @@ def catch_exceptions(func):
return func_wrapper
-def parse_uri_actions(uri: str) -> tuple[str, dict[str, str]]:
- uri = uri[5:]
- if '?' not in uri:
- return 'message', {'jid': uri}
+def parse_xmpp_uri_query(pct_iquerycomp: str) -> tuple[str, dict[str, str]]:
+ '''
+ Parses 'mess%61ge;b%6Fdy=Hello%20%F0%9F%8C%90%EF%B8%8F' into
+ ('message', {'body': 'Hello 🌐️'}), empty string into ('', {}).
+ '''
+ # Syntax and terminology from
+ # <https://rfc-editor.org/rfc/rfc5122#section-2.2>; the pct_ prefix means
+ # percent encoding not being undone yet.
- jid, action = uri.split('?', 1)
- data = {'jid': jid}
- if ';' in action:
- action, keys = action.split(';', 1)
- action_keys = keys.split(';')
- for key in action_keys:
- if key.startswith('subject='):
- data['subject'] = unquote(key[8:])
+ if not pct_iquerycomp:
+ return '', {}
- elif key.startswith('body='):
- data['body'] = unquote(key[5:])
+ pct_iquerytype, _, pct_ipairs = pct_iquerycomp.partition(';')
+ iquerytype = unquote(pct_iquerytype, errors='strict')
+ if not pct_ipairs:
+ return iquerytype, {}
- elif key.startswith('thread='):
- data['thread'] = key[7:]
- return action, data
+ pairs: dict[str, str] = {}
+ for pct_ipair in pct_ipairs.split(';'):
+ pct_ikey, _, pct_ival = pct_ipair.partition('=')
+ pairs[
+ unquote(pct_ikey, errors='strict')
+ ] = unquote(pct_ival, errors='replace')
+ return iquerytype, pairs
def parse_uri(uri: str) -> URI:
@@ -979,15 +983,24 @@ def parse_uri(uri: str) -> URI:
return URI(URIType.WEB, uri, data=uri)
if scheme == 'xmpp':
- action, data = parse_uri_actions(uri)
+ if not urlparts.path.startswith('/'):
+ pct_jid = urlparts.path
+ else:
+ pct_jid = urlparts.path[1:]
+
+ data: dict[str, str] = {}
try:
+ data['jid'] = unquote(pct_jid, errors='strict')
validate_jid(data['jid'])
- return URI(URIType.XMPP, uri,
- action=URIAction(action),
- data=data)
- except ValueError:
- # Unknown action
- return URI(URIType.UNKNOWN, uri)
+ qtype, qparams = parse_xmpp_uri_query(urlparts.query)
+ except ValueError as err:
+ data['error'] = str(err)
+ return URI(URIType.INVALID, uri, data=data)
+
+ return URI(URIType.XMPP, uri,
+ query_type=qtype,
+ query_params=qparams,
+ data=data)
if scheme == 'mailto':
data = uri[7:]
@@ -1051,19 +1064,29 @@ def open_uri(uri: Union[URI, str], account: Optional[str] = None) -> None:
if isinstance(uri.data, dict):
jid = uri.data['jid']
- message = uri.data.get('body')
else:
log.warning('Cant open URI: %s', uri)
return
- if uri.action == URIAction.JOIN:
+ qtype, qparams = XmppUriQuery.from_str(uri.query_type), uri.query_params
+ if not qtype:
+ log.info('open_uri: can\'t "%s": '
+ 'unsupported query type in %s', uri.query_type, uri)
+ # From <rfc5122#section-2.5>:
+ # > If the processing application does not understand [...] the
+ # > specified query type, it MUST ignore the query component and
+ # > treat the IRI/URI as consisting of, for example,
+ # > <xmpp:example-node@example.com> rather than
+ # > <xmpp:example-node@example.com?query>."
+ qtype, qparams = XmppUriQuery.NONE, {}
+
+ if qtype == XmppUriQuery.JOIN:
app.app.activate_action(
'groupchat-join',
GLib.Variant('as', [account, jid]))
- elif uri.action == URIAction.MESSAGE:
- app.window.start_chat_from_jid(account, jid, message=message)
else:
- log.warning('Cant open URI: %s', uri)
+ message = qparams.get('body')
+ app.window.start_chat_from_jid(account, jid, message=message)
else:
log.warning('Cant open URI: %s', uri)
diff --git a/gajim/common/structs.py b/gajim/common/structs.py
index 5fd732bfc..b6d7364d6 100644
--- a/gajim/common/structs.py
+++ b/gajim/common/structs.py
@@ -42,7 +42,6 @@ from gajim.common.const import MUCJoinedState
from gajim.common.const import KindConstant
from gajim.common.const import PresenceShowExt
from gajim.common.const import URIType
-from gajim.common.const import URIAction
_T = TypeVar('_T')
@@ -51,7 +50,8 @@ _T = TypeVar('_T')
class URI(NamedTuple):
type: URIType
source: str
- action: Optional[URIAction] = None
+ query_type: str = ''
+ query_params: dict[str, str] = {}
data: Optional[Union[dict[str, str], str]] = None
diff --git a/gajim/gtk/menus.py b/gajim/gtk/menus.py
index 824b0c10f..2d9258082 100644
--- a/gajim/gtk/menus.py
+++ b/gajim/gtk/menus.py
@@ -38,7 +38,7 @@ from gajim.common.helpers import jid_is_blocked
from gajim.common.i18n import _
from gajim.common.i18n import get_short_lang_code
from gajim.common.const import URIType
-from gajim.common.const import URIAction
+from gajim.common.const import XmppUriQuery
from gajim.common.structs import URI
from gajim.common.structs import VariantMixin
from gajim.common.modules.contacts import GroupchatContact
@@ -281,7 +281,7 @@ def get_conv_action_context_menu(account: str,
def get_conv_uri_context_menu(account: str, uri: URI) -> Optional[Gtk.Menu]:
if uri.type == URIType.XMPP:
- if uri.action == URIAction.JOIN:
+ if XmppUriQuery.from_str(uri.query_type) == XmppUriQuery.JOIN:
context_menu = [
('copy-text', _('Copy XMPP Address')),
('groupchat-join', _('Join Groupchat')),
diff --git a/gajim/gtk/start_chat.py b/gajim/gtk/start_chat.py
index 7d4a5259a..df16f5571 100644
--- a/gajim/gtk/start_chat.py
+++ b/gajim/gtk/start_chat.py
@@ -40,6 +40,8 @@ from nbxmpp.task import Task
from gajim.common import app
from gajim.common.const import Direction
+from gajim.common.const import URIType
+from gajim.common.helpers import parse_uri
from gajim.common.helpers import validate_jid
from gajim.common.helpers import to_user_string
from gajim.common.helpers import get_group_chat_nick
@@ -549,10 +551,9 @@ class StartChatDialog(Gtk.ApplicationWindow):
return
search_text = search_entry.get_text()
- if search_text.startswith('xmpp:'):
- search_text = search_text.removeprefix('xmpp:')
- search_text = search_text.removesuffix('?join')
- search_entry.set_text(search_text)
+ uri = parse_uri(search_text)
+ if uri.type == URIType.XMPP:
+ search_entry.set_text(uri.data['jid'])
return
if '@' in search_text:
diff --git a/test/no_gui/test_styling.py b/test/no_gui/test_styling.py
index e80584a01..af6ed9f83 100644
--- a/test/no_gui/test_styling.py
+++ b/test/no_gui/test_styling.py
@@ -295,7 +295,7 @@ URIS = [
'xmpp:asd@at',
'xmpp:asd@asd.at',
'xmpp:asd-asd@asd.asdasd.at.',
- #'xmpp:me@%5B::1%5D', FIXME: unescape before validating domainpart
+ 'xmpp:me@%5B::1%5D',
'xmpp:myself@127.13.42.69',
'xmpp:myself@127.13.42.69/localhost',
'xmpp:%23room%25irc.example@biboumi.xmpp.example',