Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
-
723e5007
by lovetox at 2020-08-26T21:39:15+02:00
-
3199c2f0
by lovetox at 2020-08-26T22:20:34+02:00
-
597c55ea
by lovetox at 2020-08-27T22:05:41+02:00
-
570a967b
by lovetox at 2020-08-27T22:06:52+02:00
30 changed files:
- nbxmpp/client.py
- nbxmpp/dispatcher.py
- nbxmpp/examples/client.py
- nbxmpp/modules/activity.py
- nbxmpp/modules/adhoc.py
- nbxmpp/modules/annotations.py
- nbxmpp/modules/bookmarks.py
- nbxmpp/modules/dataforms.py
- nbxmpp/modules/delay.py
- nbxmpp/modules/location.py
- nbxmpp/modules/mam.py
- nbxmpp/modules/message.py
- nbxmpp/modules/misc.py
- nbxmpp/modules/mood.py
- nbxmpp/modules/muc.py
- nbxmpp/modules/nickname.py
- nbxmpp/modules/omemo.py
- nbxmpp/modules/openpgp.py
- nbxmpp/modules/presence.py
- nbxmpp/modules/receipts.py
- nbxmpp/modules/register.py
- nbxmpp/modules/tune.py
- nbxmpp/modules/user_avatar.py
- nbxmpp/old_dispatcher.py
- nbxmpp/protocol.py
- nbxmpp/structs.py
- test/lib/util.py
- test/unit/test_bookmarks.py
- test/unit/test_error_parsing.py
- test/unit/test_jid_parsing.py
Changes:
| ... | ... | @@ -313,7 +313,7 @@ class Client(Observable): |
| 313 | 313 |
return self._jid
|
| 314 | 314 |
|
| 315 | 315 |
def _set_bound_jid(self, jid):
|
| 316 |
- self._jid = JID(jid)
|
|
| 316 |
+ self._jid = JID.from_string(jid)
|
|
| 317 | 317 |
|
| 318 | 318 |
@property
|
| 319 | 319 |
def has_error(self):
|
| ... | ... | @@ -362,7 +362,7 @@ class StanzaDispatcher(Observable): |
| 362 | 362 |
|
| 363 | 363 |
if name == 'iq':
|
| 364 | 364 |
if stanza.getFrom() is None and own_jid is not None:
|
| 365 |
- stanza.setFrom(own_jid.getBare())
|
|
| 365 |
+ stanza.setFrom(own_jid.bare)
|
|
| 366 | 366 |
|
| 367 | 367 |
if name == 'message':
|
| 368 | 368 |
# https://tools.ietf.org/html/rfc6120#section-8.1.1.1
|
| ... | ... | @@ -373,13 +373,14 @@ class StanzaDispatcher(Observable): |
| 373 | 373 |
to = stanza.getTo()
|
| 374 | 374 |
if to is None:
|
| 375 | 375 |
stanza.setTo(own_jid)
|
| 376 |
- elif not to.bareMatch(own_jid):
|
|
| 376 |
+ |
|
| 377 |
+ elif not to.bare_match(own_jid):
|
|
| 377 | 378 |
self._log.warning('Message addressed to someone else: %s',
|
| 378 | 379 |
stanza)
|
| 379 | 380 |
return
|
| 380 | 381 |
|
| 381 | 382 |
if stanza.getFrom() is None:
|
| 382 |
- stanza.setFrom(own_jid.getBare())
|
|
| 383 |
+ stanza.setFrom(own_jid.bare)
|
|
| 383 | 384 |
|
| 384 | 385 |
# Unwrap carbon
|
| 385 | 386 |
try:
|
| ... | ... | @@ -80,8 +80,8 @@ class TestClient(Gtk.Window): |
| 80 | 80 |
|
| 81 | 81 |
def _create_client(self):
|
| 82 | 82 |
self._client = Client(log_context='TEST')
|
| 83 |
- self._client.set_domain(self.address.getDomain())
|
|
| 84 |
- self._client.set_username(self.address.getNode())
|
|
| 83 |
+ self._client.set_domain(self.address.domain)
|
|
| 84 |
+ self._client.set_username(self.address.localpart)
|
|
| 85 | 85 |
self._client.set_resource('test')
|
| 86 | 86 |
|
| 87 | 87 |
proxy_ip = self._builder.proxy_ip.get_text()
|
| ... | ... | @@ -117,7 +117,7 @@ class TestClient(Gtk.Window): |
| 117 | 117 |
|
| 118 | 118 |
@property
|
| 119 | 119 |
def address(self):
|
| 120 |
- return JID(self._builder.address.get_text())
|
|
| 120 |
+ return JID.from_string(self._builder.address.get_text())
|
|
| 121 | 121 |
|
| 122 | 122 |
@property
|
| 123 | 123 |
def xml_box(self):
|
| ... | ... | @@ -91,6 +91,6 @@ class Activity(BaseModule): |
| 91 | 91 |
if data.text:
|
| 92 | 92 |
item.addChild('text', payload=data.text)
|
| 93 | 93 |
|
| 94 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 94 |
+ jid = self._client.get_bound_jid().bare
|
|
| 95 | 95 |
self._client.get_module('PubSub').publish(
|
| 96 | 96 |
jid, Namespace.ACTIVITY, item, id_='current')
|
| ... | ... | @@ -41,7 +41,7 @@ class AdHoc(BaseModule): |
| 41 | 41 |
@call_on_response('_command_list_received')
|
| 42 | 42 |
def request_command_list(self, jid=None):
|
| 43 | 43 |
if jid is None:
|
| 44 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 44 |
+ jid = self._client.get_bound_jid().bare
|
|
| 45 | 45 |
return get_disco_request(Namespace.DISCO_ITEMS,
|
| 46 | 46 |
jid,
|
| 47 | 47 |
node=Namespace.COMMANDS)
|
| ... | ... | @@ -38,7 +38,7 @@ class Annotations(BaseModule): |
| 38 | 38 |
|
| 39 | 39 |
@property
|
| 40 | 40 |
def domain(self):
|
| 41 |
- return self._client.get_bound_jid().getDomain()
|
|
| 41 |
+ return self._client.get_bound_jid().domain
|
|
| 42 | 42 |
|
| 43 | 43 |
@call_on_response('_annotations_received')
|
| 44 | 44 |
def request_annotations(self):
|
| ... | ... | @@ -59,7 +59,7 @@ class Annotations(BaseModule): |
| 59 | 59 |
notes = []
|
| 60 | 60 |
for note in storage.getTags('note'):
|
| 61 | 61 |
try:
|
| 62 |
- jid = JID(note.getAttr('jid'))
|
|
| 62 |
+ jid = JID.from_string(note.getAttr('jid'))
|
|
| 63 | 63 |
except Exception as error:
|
| 64 | 64 |
self._log.warning('Invalid JID: %s, %s',
|
| 65 | 65 |
note.getAttr('jid'), error)
|
| ... | ... | @@ -131,7 +131,7 @@ class Bookmarks(BaseModule): |
| 131 | 131 |
confs = storage.getTags('conference')
|
| 132 | 132 |
for conf in confs:
|
| 133 | 133 |
try:
|
| 134 |
- jid = JID(conf.getAttr('jid'))
|
|
| 134 |
+ jid = JID.from_string(conf.getAttr('jid'))
|
|
| 135 | 135 |
except Exception as error:
|
| 136 | 136 |
self._log.warning('Invalid JID: "%s", %s',
|
| 137 | 137 |
conf.getAttr('jid'),
|
| ... | ... | @@ -161,7 +161,7 @@ class Bookmarks(BaseModule): |
| 161 | 161 |
return None
|
| 162 | 162 |
|
| 163 | 163 |
try:
|
| 164 |
- jid = JID(item.getAttr('id'))
|
|
| 164 |
+ jid = JID.from_string(item.getAttr('id'))
|
|
| 165 | 165 |
except Exception as error:
|
| 166 | 166 |
self._log.warning('Invalid JID: "%s", %s',
|
| 167 | 167 |
item.getAttr('id'),
|
| ... | ... | @@ -209,7 +209,7 @@ class Bookmarks(BaseModule): |
| 209 | 209 |
|
| 210 | 210 |
@call_on_response('_bookmarks_received')
|
| 211 | 211 |
def request_bookmarks(self, type_):
|
| 212 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 212 |
+ jid = self._client.get_bound_jid().bare
|
|
| 213 | 213 |
if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2:
|
| 214 | 214 |
self._log.info('Request bookmarks 2 (PubSub)')
|
| 215 | 215 |
request = get_pubsub_request(jid, Namespace.BOOKMARKS_2)
|
| ... | ... | @@ -272,7 +272,7 @@ class Bookmarks(BaseModule): |
| 272 | 272 |
|
| 273 | 273 |
from_ = stanza.getFrom()
|
| 274 | 274 |
if from_ is None:
|
| 275 |
- from_ = self._client.get_bound_jid().getBare()
|
|
| 275 |
+ from_ = self._client.get_bound_jid().bare
|
|
| 276 | 276 |
self._log.info('Received bookmarks from: %s', from_)
|
| 277 | 277 |
for bookmark in bookmarks:
|
| 278 | 278 |
self._log.info(bookmark)
|
| ... | ... | @@ -315,14 +315,14 @@ class Bookmarks(BaseModule): |
| 315 | 315 |
|
| 316 | 316 |
def retract_bookmark(self, bookmark_jid):
|
| 317 | 317 |
self._log.info('Retract Bookmark: %s', bookmark_jid)
|
| 318 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 318 |
+ jid = self._client.get_bound_jid().bare
|
|
| 319 | 319 |
self._client.get_module('PubSub').retract(jid,
|
| 320 | 320 |
Namespace.BOOKMARKS_2,
|
| 321 | 321 |
str(bookmark_jid))
|
| 322 | 322 |
|
| 323 | 323 |
def _store_bookmark_1(self, bookmarks):
|
| 324 | 324 |
self._log.info('Store Bookmarks 1 (PubSub)')
|
| 325 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 325 |
+ jid = self._client.get_bound_jid().bare
|
|
| 326 | 326 |
self._bookmark_1_queue = bookmarks
|
| 327 | 327 |
item = self._build_storage_node(bookmarks)
|
| 328 | 328 |
options = get_publish_options(BOOKMARK_1_OPTIONS)
|
| ... | ... | @@ -341,7 +341,7 @@ class Bookmarks(BaseModule): |
| 341 | 341 |
return
|
| 342 | 342 |
|
| 343 | 343 |
self._log.info('Store Bookmarks 2 (PubSub)')
|
| 344 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 344 |
+ jid = self._client.get_bound_jid().bare
|
|
| 345 | 345 |
for bookmark in bookmarks:
|
| 346 | 346 |
self._bookmark_2_queue[bookmark.jid] = bookmark
|
| 347 | 347 |
item = self._build_conference_node(bookmark)
|
| ... | ... | @@ -367,7 +367,7 @@ class Bookmarks(BaseModule): |
| 367 | 367 |
return
|
| 368 | 368 |
|
| 369 | 369 |
self._node_configuration_in_progress = True
|
| 370 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 370 |
+ jid = self._client.get_bound_jid().bare
|
|
| 371 | 371 |
self._client.get_module('PubSub').get_node_configuration(
|
| 372 | 372 |
jid,
|
| 373 | 373 |
node,
|
| ... | ... | @@ -424,7 +424,7 @@ class JidSingleField(ListSingleField): |
| 424 | 424 |
def is_valid(self):
|
| 425 | 425 |
if self.value:
|
| 426 | 426 |
try:
|
| 427 |
- JID(self.value)
|
|
| 427 |
+ JID.from_string(self.value)
|
|
| 428 | 428 |
return True, ''
|
| 429 | 429 |
except Exception as error:
|
| 430 | 430 |
return False, error
|
| ... | ... | @@ -479,7 +479,7 @@ class JidMultiField(ListMultiField): |
| 479 | 479 |
if self.values:
|
| 480 | 480 |
for value in self.values:
|
| 481 | 481 |
try:
|
| 482 |
- JID(value)
|
|
| 482 |
+ JID.from_string(value)
|
|
| 483 | 483 |
except Exception as error:
|
| 484 | 484 |
return False, error
|
| 485 | 485 |
return True, ''
|
| ... | ... | @@ -47,18 +47,18 @@ class Delay(BaseModule): |
| 47 | 47 |
# to indicate when the user has set the subject,
|
| 48 | 48 |
# the 'from' attr on these delays is the MUC server
|
| 49 | 49 |
# but we treat it as user timestamp
|
| 50 |
- jids = [properties.jid.getBare(),
|
|
| 51 |
- properties.jid.getDomain()]
|
|
| 50 |
+ jids = [properties.jid.bare,
|
|
| 51 |
+ properties.jid.domain]
|
|
| 52 | 52 |
|
| 53 | 53 |
properties.user_timestamp = parse_delay(stanza, from_=jids)
|
| 54 | 54 |
|
| 55 | 55 |
else:
|
| 56 | 56 |
if properties.from_muc:
|
| 57 | 57 |
# Some servers use the MUC JID, others the domain
|
| 58 |
- jids = [properties.jid.getBare(),
|
|
| 59 |
- properties.jid.getDomain()]
|
|
| 58 |
+ jids = [properties.jid.bare,
|
|
| 59 |
+ properties.jid.domain]
|
|
| 60 | 60 |
else:
|
| 61 |
- jids = [self._client.get_bound_jid().getDomain()]
|
|
| 61 |
+ jids = [self._client.get_bound_jid().domain]
|
|
| 62 | 62 |
|
| 63 | 63 |
server_delay = parse_delay(stanza, from_=jids)
|
| 64 | 64 |
if server_delay is not None:
|
| ... | ... | @@ -70,6 +70,6 @@ class Location(BaseModule): |
| 70 | 70 |
if value is not None:
|
| 71 | 71 |
item.addChild(tag, payload=value)
|
| 72 | 72 |
|
| 73 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 73 |
+ jid = self._client.get_bound_jid().bare
|
|
| 74 | 74 |
self._client.get_module('PubSub').publish(
|
| 75 | 75 |
jid, Namespace.LOCATION, item, id_='current')
|
| ... | ... | @@ -183,7 +183,7 @@ class MAM(BaseModule): |
| 183 | 183 |
continue
|
| 184 | 184 |
|
| 185 | 185 |
try:
|
| 186 |
- jid = JID(jid)
|
|
| 186 |
+ jid = JID.from_string(jid)
|
|
| 187 | 187 |
except Exception:
|
| 188 | 188 |
self._log.warning('Invalid jid found in preferences: %s',
|
| 189 | 189 |
jid)
|
| ... | ... | @@ -48,7 +48,7 @@ class BaseMessage(BaseModule): |
| 48 | 48 |
|
| 49 | 49 |
elif properties.is_mam_message and not properties.type.is_groupchat:
|
| 50 | 50 |
own_jid = self._client.get_bound_jid()
|
| 51 |
- if own_jid.bareMatch(stanza.getFrom()):
|
|
| 51 |
+ if own_jid.bare_match(stanza.getFrom()):
|
|
| 52 | 52 |
properties.jid = stanza.getTo()
|
| 53 | 53 |
else:
|
| 54 | 54 |
properties.jid = stanza.getFrom()
|
| ... | ... | @@ -106,4 +106,4 @@ class BaseMessage(BaseModule): |
| 106 | 106 |
def _parse_self_message(stanza, properties):
|
| 107 | 107 |
if properties.type.is_groupchat:
|
| 108 | 108 |
return False
|
| 109 |
- return stanza.getFrom().bareMatch(stanza.getTo())
|
|
| 109 |
+ return stanza.getFrom().bare_match(stanza.getTo())
|
| ... | ... | @@ -38,7 +38,7 @@ def unwrap_carbon(stanza, own_jid): |
| 38 | 38 |
return stanza, None
|
| 39 | 39 |
|
| 40 | 40 |
# Carbon must be from our bare jid
|
| 41 |
- if not stanza.getFrom() == own_jid.getBare():
|
|
| 41 |
+ if stanza.getFrom() != own_jid.new_as_bare():
|
|
| 42 | 42 |
raise InvalidFrom('Invalid from: %s' % stanza.getAttr('from'))
|
| 43 | 43 |
|
| 44 | 44 |
forwarded = carbon.getTag('forwarded', namespace=Namespace.FORWARD)
|
| ... | ... | @@ -49,14 +49,14 @@ def unwrap_carbon(stanza, own_jid): |
| 49 | 49 |
# Fill missing to/from
|
| 50 | 50 |
to = message.getTo()
|
| 51 | 51 |
if to is None:
|
| 52 |
- message.setTo(own_jid.getBare())
|
|
| 52 |
+ message.setTo(own_jid.bare)
|
|
| 53 | 53 |
|
| 54 | 54 |
frm = message.getFrom()
|
| 55 | 55 |
if frm is None:
|
| 56 |
- message.setFrom(own_jid.getBare())
|
|
| 56 |
+ message.setFrom(own_jid.bare)
|
|
| 57 | 57 |
|
| 58 | 58 |
if type_ == 'received':
|
| 59 |
- if message.getFrom().bareMatch(own_jid):
|
|
| 59 |
+ if message.getFrom().bare_match(own_jid):
|
|
| 60 | 60 |
# Drop 'received' Carbons from ourself, we already
|
| 61 | 61 |
# got the message with the 'sent' Carbon or via the
|
| 62 | 62 |
# message itself
|
| ... | ... | @@ -95,11 +95,11 @@ def unwrap_mam(stanza, own_jid): |
| 95 | 95 |
# Fill missing to/from
|
| 96 | 96 |
to = message.getTo()
|
| 97 | 97 |
if to is None:
|
| 98 |
- message.setTo(own_jid.getBare())
|
|
| 98 |
+ message.setTo(own_jid.bare)
|
|
| 99 | 99 |
|
| 100 | 100 |
frm = message.getFrom()
|
| 101 | 101 |
if frm is None:
|
| 102 |
- message.setFrom(own_jid.getBare())
|
|
| 102 |
+ message.setFrom(own_jid.bare)
|
|
| 103 | 103 |
|
| 104 | 104 |
# Timestamp parsing
|
| 105 | 105 |
# Most servers dont set the 'from' attr, so we cant check for it
|
| ... | ... | @@ -80,6 +80,6 @@ class Mood(BaseModule): |
| 80 | 80 |
if data.text:
|
| 81 | 81 |
item.addChild('text', payload=data.text)
|
| 82 | 82 |
|
| 83 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 83 |
+ jid = self._client.get_bound_jid().bare
|
|
| 84 | 84 |
self._client.get_module('PubSub').publish(
|
| 85 | 85 |
jid, Namespace.MOOD, item, id_='current')
|
| ... | ... | @@ -92,24 +92,22 @@ class MUC(BaseModule): |
| 92 | 92 |
if muc is None:
|
| 93 | 93 |
return
|
| 94 | 94 |
properties.from_muc = True
|
| 95 |
- properties.muc_jid = properties.jid.copy()
|
|
| 96 |
- properties.muc_jid.setBare()
|
|
| 97 |
- properties.muc_nickname = properties.jid.getResource()
|
|
| 95 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 96 |
+ properties.muc_nickname = properties.jid.resource
|
|
| 98 | 97 |
|
| 99 | 98 |
def _process_muc_user_presence(self, _client, stanza, properties):
|
| 100 | 99 |
muc_user = stanza.getTag('x', namespace=Namespace.MUC_USER)
|
| 101 | 100 |
if muc_user is None:
|
| 102 | 101 |
return
|
| 103 | 102 |
properties.from_muc = True
|
| 104 |
- properties.muc_jid = properties.jid.copy()
|
|
| 105 |
- properties.muc_jid.setBare()
|
|
| 103 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 106 | 104 |
|
| 107 | 105 |
destroy = muc_user.getTag('destroy')
|
| 108 | 106 |
if destroy is not None:
|
| 109 | 107 |
alternate = destroy.getAttr('jid')
|
| 110 | 108 |
if alternate is not None:
|
| 111 | 109 |
try:
|
| 112 |
- alternate = JID(alternate)
|
|
| 110 |
+ alternate = JID.from_string(alternate)
|
|
| 113 | 111 |
except Exception as error:
|
| 114 | 112 |
self._log.warning('Invalid alternate JID provided: %s',
|
| 115 | 113 |
error)
|
| ... | ... | @@ -121,7 +119,7 @@ class MUC(BaseModule): |
| 121 | 119 |
password=muc_user.getTagData('password'))
|
| 122 | 120 |
return
|
| 123 | 121 |
|
| 124 |
- properties.muc_nickname = properties.jid.getResource()
|
|
| 122 |
+ properties.muc_nickname = properties.jid.resource
|
|
| 125 | 123 |
|
| 126 | 124 |
# https://xmpp.org/extensions/xep-0045.html#registrar-statuscodes
|
| 127 | 125 |
message_status_codes = [
|
| ... | ... | @@ -170,9 +168,8 @@ class MUC(BaseModule): |
| 170 | 168 |
|
| 171 | 169 |
def _process_groupchat_message(self, _client, stanza, properties):
|
| 172 | 170 |
properties.from_muc = True
|
| 173 |
- properties.muc_jid = properties.jid.copy()
|
|
| 174 |
- properties.muc_jid.setBare()
|
|
| 175 |
- properties.muc_nickname = properties.jid.getResource() or None
|
|
| 171 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 172 |
+ properties.muc_nickname = properties.jid.resource
|
|
| 176 | 173 |
|
| 177 | 174 |
muc_user = stanza.getTag('x', namespace=Namespace.MUC_USER)
|
| 178 | 175 |
if muc_user is not None:
|
| ... | ... | @@ -188,7 +185,7 @@ class MUC(BaseModule): |
| 188 | 185 |
if addresses is not None:
|
| 189 | 186 |
address = addresses.getTag('address', attrs={'type': 'ofrom'})
|
| 190 | 187 |
if address is not None:
|
| 191 |
- properties.muc_ofrom = JID(address.getAttr('jid'))
|
|
| 188 |
+ properties.muc_ofrom = JID.from_string(address.getAttr('jid'))
|
|
| 192 | 189 |
|
| 193 | 190 |
def _process_message(self, _client, stanza, properties):
|
| 194 | 191 |
muc_user = stanza.getTag('x', namespace=Namespace.MUC_USER)
|
| ... | ... | @@ -206,10 +203,9 @@ class MUC(BaseModule): |
| 206 | 203 |
return
|
| 207 | 204 |
|
| 208 | 205 |
properties.from_muc = True
|
| 209 |
- properties.muc_jid = properties.jid.copy()
|
|
| 210 |
- properties.muc_jid.setBare()
|
|
| 206 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 211 | 207 |
|
| 212 |
- if not properties.jid.isBare:
|
|
| 208 |
+ if not properties.jid.is_bare:
|
|
| 213 | 209 |
return
|
| 214 | 210 |
|
| 215 | 211 |
# MUC Config change
|
| ... | ... | @@ -253,7 +249,7 @@ class MUC(BaseModule): |
| 253 | 249 |
return
|
| 254 | 250 |
|
| 255 | 251 |
data = {}
|
| 256 |
- data['muc'] = JID(direct.getAttr('jid'))
|
|
| 252 |
+ data['muc'] = JID.from_string(direct.getAttr('jid'))
|
|
| 257 | 253 |
data['from_'] = properties.jid
|
| 258 | 254 |
data['reason'] = direct.getAttr('reason')
|
| 259 | 255 |
data['password'] = direct.getAttr('password')
|
| ... | ... | @@ -272,15 +268,14 @@ class MUC(BaseModule): |
| 272 | 268 |
return
|
| 273 | 269 |
|
| 274 | 270 |
properties.from_muc = True
|
| 275 |
- properties.muc_jid = properties.jid.copy()
|
|
| 276 |
- properties.muc_jid.setBare()
|
|
| 271 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 277 | 272 |
|
| 278 | 273 |
data = {}
|
| 279 | 274 |
|
| 280 | 275 |
invite = muc_user.getTag('invite')
|
| 281 | 276 |
if invite is not None:
|
| 282 |
- data['muc'] = JID(properties.jid.getBare())
|
|
| 283 |
- data['from_'] = JID(invite.getAttr('from'))
|
|
| 277 |
+ data['muc'] = properties.jid.new_as_bare()
|
|
| 278 |
+ data['from_'] = JID.from_string(invite.getAttr('from'))
|
|
| 284 | 279 |
data['reason'] = invite.getTagData('reason')
|
| 285 | 280 |
data['password'] = muc_user.getTagData('password')
|
| 286 | 281 |
data['type'] = InviteType.MEDIATED
|
| ... | ... | @@ -296,8 +291,8 @@ class MUC(BaseModule): |
| 296 | 291 |
|
| 297 | 292 |
decline = muc_user.getTag('decline')
|
| 298 | 293 |
if decline is not None:
|
| 299 |
- data['muc'] = JID(properties.jid.getBare())
|
|
| 300 |
- data['from_'] = JID(decline.getAttr('from'))
|
|
| 294 |
+ data['muc'] = properties.jid.new_as_bare()
|
|
| 295 |
+ data['from_'] = JID.from_string(decline.getAttr('from'))
|
|
| 301 | 296 |
data['reason'] = decline.getTagData('reason')
|
| 302 | 297 |
properties.muc_decline = DeclineData(**data)
|
| 303 | 298 |
return
|
| ... | ... | @@ -317,7 +312,7 @@ class MUC(BaseModule): |
| 317 | 312 |
nick = data_form['muc#roomnick'].value
|
| 318 | 313 |
|
| 319 | 314 |
try:
|
| 320 |
- jid = JID(data_form['muc#jid'].value)
|
|
| 315 |
+ jid = JID.from_string(data_form['muc#jid'].value)
|
|
| 321 | 316 |
except Exception:
|
| 322 | 317 |
self._log.warning('Invalid JID on voice request')
|
| 323 | 318 |
self._log.warning(stanza)
|
| ... | ... | @@ -327,8 +322,7 @@ class MUC(BaseModule): |
| 327 | 322 |
nick=nick,
|
| 328 | 323 |
form=data_form)
|
| 329 | 324 |
properties.from_muc = True
|
| 330 |
- properties.muc_jid = properties.jid.copy()
|
|
| 331 |
- properties.muc_jid.setBare()
|
|
| 325 |
+ properties.muc_jid = properties.jid.new_as_bare()
|
|
| 332 | 326 |
|
| 333 | 327 |
def approve_voice_request(self, muc_jid, voice_request):
|
| 334 | 328 |
form = voice_request.form
|
| ... | ... | @@ -354,7 +348,7 @@ class MUC(BaseModule): |
| 354 | 348 |
users_dict = {}
|
| 355 | 349 |
for item in items:
|
| 356 | 350 |
try:
|
| 357 |
- jid = JID(item.getAttr('jid'))
|
|
| 351 |
+ jid = JID.from_string(item.getAttr('jid'))
|
|
| 358 | 352 |
except Exception as error:
|
| 359 | 353 |
self._log.warning('Invalid JID: %s, %s',
|
| 360 | 354 |
item.getAttr('jid'), error)
|
| ... | ... | @@ -573,7 +567,7 @@ class MUC(BaseModule): |
| 573 | 567 |
jid = item_dict.get('jid')
|
| 574 | 568 |
if jid is not None:
|
| 575 | 569 |
try:
|
| 576 |
- jid = JID(jid)
|
|
| 570 |
+ jid = JID.from_string(jid)
|
|
| 577 | 571 |
except InvalidJid as error:
|
| 578 | 572 |
raise StanzaMalformed('invalid jid %s, %s' % (jid, error))
|
| 579 | 573 |
|
| ... | ... | @@ -87,6 +87,6 @@ class Nickname(BaseModule): |
| 87 | 87 |
item = Node('nick', {'xmlns': Namespace.NICK})
|
| 88 | 88 |
if nickname is not None:
|
| 89 | 89 |
item.addData(nickname)
|
| 90 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 90 |
+ jid = self._client.get_bound_jid().bare
|
|
| 91 | 91 |
self._client.get_module('PubSub').publish(
|
| 92 | 92 |
jid, Namespace.NICK, item, id_='current')
|
| ... | ... | @@ -192,14 +192,14 @@ class OMEMO(BaseModule): |
| 192 | 192 |
item.addChild('device').setAttr('id', device)
|
| 193 | 193 |
|
| 194 | 194 |
self._log.info('Set devicelist: %s', devicelist)
|
| 195 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 195 |
+ jid = self._client.get_bound_jid().bare
|
|
| 196 | 196 |
self._client.get_module('PubSub').publish(
|
| 197 | 197 |
jid, Namespace.OMEMO_TEMP_DL, item, id_='current')
|
| 198 | 198 |
|
| 199 | 199 |
@call_on_response('_devicelist_received')
|
| 200 | 200 |
def request_devicelist(self, jid=None):
|
| 201 | 201 |
if jid is None:
|
| 202 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 202 |
+ jid = self._client.get_bound_jid().bare
|
|
| 203 | 203 |
self._log.info('Request devicelist from: %s', jid)
|
| 204 | 204 |
return get_pubsub_request(jid, Namespace.OMEMO_TEMP_DL, max_items=1)
|
| 205 | 205 |
|
| ... | ... | @@ -225,7 +225,7 @@ class OMEMO(BaseModule): |
| 225 | 225 |
self._log.info('Set bundle')
|
| 226 | 226 |
|
| 227 | 227 |
node = '%s:%s' % (Namespace.OMEMO_TEMP_BUNDLE, device_id)
|
| 228 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 228 |
+ jid = self._client.get_bound_jid().bare
|
|
| 229 | 229 |
self._client.get_module('PubSub').publish(
|
| 230 | 230 |
jid, node, item, id_='current')
|
| 231 | 231 |
|
| ... | ... | @@ -155,7 +155,7 @@ class OpenPGP(BaseModule): |
| 155 | 155 |
item.addChild('pubkey-metadata', attrs=attrs)
|
| 156 | 156 |
|
| 157 | 157 |
self._log.info('Set keylist: %s', keylist)
|
| 158 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 158 |
+ jid = self._client.get_bound_jid().bare
|
|
| 159 | 159 |
self._client.get_module('PubSub').publish(
|
| 160 | 160 |
jid, Namespace.OPENPGP_PK, item, id_='current')
|
| 161 | 161 |
|
| ... | ... | @@ -169,7 +169,7 @@ class OpenPGP(BaseModule): |
| 169 | 169 |
node = '%s:%s' % (Namespace.OPENPGP_PK, fingerprint)
|
| 170 | 170 |
|
| 171 | 171 |
self._log.info('Set public key')
|
| 172 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 172 |
+ jid = self._client.get_bound_jid().bare
|
|
| 173 | 173 |
self._client.get_module('PubSub').publish(
|
| 174 | 174 |
jid, node, item, id_='current')
|
| 175 | 175 |
|
| ... | ... | @@ -181,7 +181,7 @@ class OpenPGP(BaseModule): |
| 181 | 181 |
|
| 182 | 182 |
@callback
|
| 183 | 183 |
def _public_key_received(self, stanza):
|
| 184 |
- jid = JID(stanza.getFrom().getBare())
|
|
| 184 |
+ jid = stanza.getFrom().bare
|
|
| 185 | 185 |
|
| 186 | 186 |
if not isResultNode(stanza):
|
| 187 | 187 |
return raise_error(self._log.info, stanza)
|
| ... | ... | @@ -219,7 +219,7 @@ class OpenPGP(BaseModule): |
| 219 | 219 |
|
| 220 | 220 |
@callback
|
| 221 | 221 |
def _keylist_received(self, stanza):
|
| 222 |
- jid = JID(stanza.getFrom().getBare())
|
|
| 222 |
+ jid = stanza.getFrom().bare
|
|
| 223 | 223 |
|
| 224 | 224 |
if not isResultNode(stanza):
|
| 225 | 225 |
return raise_error(self._log.info, stanza)
|
| ... | ... | @@ -239,7 +239,7 @@ class OpenPGP(BaseModule): |
| 239 | 239 |
@call_on_response('_secret_key_received')
|
| 240 | 240 |
def request_secret_key(self):
|
| 241 | 241 |
self._log.info('Request secret key')
|
| 242 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 242 |
+ jid = self._client.get_bound_jid().bare
|
|
| 243 | 243 |
return get_pubsub_request(jid, Namespace.OPENPGP_SK, max_items=1)
|
| 244 | 244 |
|
| 245 | 245 |
@callback
|
| ... | ... | @@ -275,7 +275,7 @@ class OpenPGP(BaseModule): |
| 275 | 275 |
item.setData(b64encode(secret_key))
|
| 276 | 276 |
|
| 277 | 277 |
self._log.info('Set secret key')
|
| 278 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 278 |
+ jid = self._client.get_bound_jid().bare
|
|
| 279 | 279 |
self._client.get_module('PubSub').publish(
|
| 280 | 280 |
jid, Namespace.OPENPGP_SK, item, id_='current')
|
| 281 | 281 |
|
| ... | ... | @@ -338,7 +338,7 @@ def create_signcrypt_node(stanza, not_encrypted_nodes): |
| 338 | 338 |
stanza.delChild(node)
|
| 339 | 339 |
|
| 340 | 340 |
signcrypt = Node('signcrypt', attrs={'xmlns': Namespace.OPENPGP})
|
| 341 |
- signcrypt.addChild('to', attrs={'jid': stanza.getTo().getBare()})
|
|
| 341 |
+ signcrypt.addChild('to', attrs={'jid': str(stanza.getTo().bare)})
|
|
| 342 | 342 |
|
| 343 | 343 |
timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
| 344 | 344 |
signcrypt.addChild('time', attrs={'stamp': timestamp})
|
| ... | ... | @@ -49,7 +49,7 @@ class BasePresence(BaseModule): |
| 49 | 49 |
|
| 50 | 50 |
own_jid = self._client.get_bound_jid()
|
| 51 | 51 |
properties.self_presence = own_jid == properties.jid
|
| 52 |
- properties.self_bare = properties.jid.bareMatch(own_jid)
|
|
| 52 |
+ properties.self_bare = properties.jid.bare_match(own_jid)
|
|
| 53 | 53 |
|
| 54 | 54 |
def _parse_priority(self, stanza):
|
| 55 | 55 |
priority = stanza.getPriority()
|
| ... | ... | @@ -68,10 +68,10 @@ def build_receipt(stanza): |
| 68 | 68 |
|
| 69 | 69 |
is_muc_pm = isMucPM(stanza)
|
| 70 | 70 |
|
| 71 |
- jid = stanza.getFrom().copy()
|
|
| 71 |
+ jid = stanza.getFrom()
|
|
| 72 | 72 |
typ = stanza.getType()
|
| 73 | 73 |
if typ == 'groupchat' or not is_muc_pm:
|
| 74 |
- jid.setBare()
|
|
| 74 |
+ jid = jid.new_as_bare()
|
|
| 75 | 75 |
|
| 76 | 76 |
message = Message(to=jid, typ=typ)
|
| 77 | 77 |
if is_muc_pm:
|
| ... | ... | @@ -168,8 +168,8 @@ class Register(BaseModule): |
| 168 | 168 |
|
| 169 | 169 |
@call_on_response('_on_password_change')
|
| 170 | 170 |
def change_password(self, password):
|
| 171 |
- domain = self._client.get_bound_jid().getDomain()
|
|
| 172 |
- username = self._client.get_bound_jid().getNode()
|
|
| 171 |
+ domain = self._client.get_bound_jid().domain
|
|
| 172 |
+ username = self._client.get_bound_jid().localpart
|
|
| 173 | 173 |
iq = Iq('set', Namespace.REGISTER, to=domain)
|
| 174 | 174 |
query = iq.getQuery()
|
| 175 | 175 |
query.setTagData('username', username)
|
| ... | ... | @@ -192,7 +192,7 @@ class Register(BaseModule): |
| 192 | 192 |
|
| 193 | 193 |
@call_on_response('_default_response')
|
| 194 | 194 |
def change_password_with_form(self, form):
|
| 195 |
- domain = self._client.get_bound_jid().getDomain()
|
|
| 195 |
+ domain = self._client.get_bound_jid().domain
|
|
| 196 | 196 |
iq = Iq('set', Namespace.REGISTER, to=domain)
|
| 197 | 197 |
iq.setQueryPayload(form)
|
| 198 | 198 |
return iq
|
| ... | ... | @@ -70,6 +70,6 @@ class Tune(BaseModule): |
| 70 | 70 |
if value is not None:
|
| 71 | 71 |
item.addChild(tag, payload=value)
|
| 72 | 72 |
|
| 73 |
- jid = self._client.get_bound_jid().getBare()
|
|
| 73 |
+ jid = self._client.get_bound_jid().bare
|
|
| 74 | 74 |
self._client.get_module('PubSub').publish(
|
| 75 | 75 |
jid, Namespace.TUNE, item, id_='current')
|
| ... | ... | @@ -88,7 +88,7 @@ class UserAvatar(BaseModule): |
| 88 | 88 |
def _avatar_data_received(self, stanza):
|
| 89 | 89 |
jid = stanza.getFrom()
|
| 90 | 90 |
if jid is None:
|
| 91 |
- jid = JID(self._client.get_bound_jid().getBare())
|
|
| 91 |
+ jid = self._client.get_bound_jid().bare
|
|
| 92 | 92 |
|
| 93 | 93 |
if not isResultNode(stanza):
|
| 94 | 94 |
return raise_error(self._log.warning, stanza)
|
| ... | ... | @@ -561,7 +561,7 @@ class XMPPDispatcher(PlugIn): |
| 561 | 561 |
|
| 562 | 562 |
if name == 'iq':
|
| 563 | 563 |
if stanza.getFrom() is None and own_jid is not None:
|
| 564 |
- stanza.setFrom(own_jid.getBare())
|
|
| 564 |
+ stanza.setFrom(own_jid.bare)
|
|
| 565 | 565 |
|
| 566 | 566 |
if name == 'message':
|
| 567 | 567 |
# https://tools.ietf.org/html/rfc6120#section-8.1.1.1
|
| ... | ... | @@ -572,12 +572,13 @@ class XMPPDispatcher(PlugIn): |
| 572 | 572 |
to = stanza.getTo()
|
| 573 | 573 |
if to is None:
|
| 574 | 574 |
stanza.setTo(own_jid)
|
| 575 |
- elif not to.bareMatch(own_jid):
|
|
| 575 |
+ |
|
| 576 |
+ elif not to.bare_match(own_jid):
|
|
| 576 | 577 |
log.warning('Message addressed to someone else: %s', stanza)
|
| 577 | 578 |
return
|
| 578 | 579 |
|
| 579 | 580 |
if stanza.getFrom() is None:
|
| 580 |
- stanza.setFrom(own_jid.getBare())
|
|
| 581 |
+ stanza.setFrom(own_jid.bare)
|
|
| 581 | 582 |
|
| 582 | 583 |
# Unwrap carbon
|
| 583 | 584 |
try:
|
| ... | ... | @@ -21,7 +21,9 @@ sub- stanzas) handling routines |
| 21 | 21 |
import time
|
| 22 | 22 |
import hashlib
|
| 23 | 23 |
import functools
|
| 24 |
+import warnings
|
|
| 24 | 25 |
from base64 import b64encode
|
| 26 |
+from collections import namedtuple
|
|
| 25 | 27 |
|
| 26 | 28 |
from gi.repository import GLib
|
| 27 | 29 |
|
| ... | ... | @@ -439,6 +441,7 @@ _status_conditions = { |
| 439 | 441 |
}
|
| 440 | 442 |
|
| 441 | 443 |
_localpart_disallowed_chars = set('"&\'/:<>@')
|
| 444 |
+_localpart_escape_chars = ' "&\'/:<>@'
|
|
| 442 | 445 |
|
| 443 | 446 |
|
| 444 | 447 |
STREAM_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-streams not-authorized'
|
| ... | ... | @@ -659,33 +662,11 @@ stream_exceptions = {'bad-format': BadFormat, |
| 659 | 662 |
'xml-not-well-formed': XMLNotWellFormed}
|
| 660 | 663 |
|
| 661 | 664 |
|
| 662 |
-@functools.lru_cache(maxsize=None)
|
|
| 663 |
-def parse_jid(jid):
|
|
| 664 |
- # https://tools.ietf.org/html/rfc7622#section-3.2
|
|
| 665 |
- |
|
| 666 |
- # Remove any portion from the first '/' character to the end of the
|
|
| 667 |
- # string (if there is a '/' character present).
|
|
| 668 |
- |
|
| 669 |
- # Remove any portion from the beginning of the string to the first
|
|
| 670 |
- # '@' character (if there is an '@' character present).
|
|
| 671 |
- |
|
| 672 |
- if jid.find('/') != -1:
|
|
| 673 |
- rest, resourcepart = jid.split('/', 1)
|
|
| 674 |
- resourcepart = validate_resourcepart(resourcepart)
|
|
| 675 |
- else:
|
|
| 676 |
- rest, resourcepart = jid, ''
|
|
| 677 |
- |
|
| 678 |
- if rest.find('@') != -1:
|
|
| 679 |
- localpart, domainpart = rest.split('@', 1)
|
|
| 680 |
- localpart = validate_localpart(localpart)
|
|
| 681 |
- else:
|
|
| 682 |
- localpart, domainpart = '', rest
|
|
| 665 |
+def deprecation_warning(message):
|
|
| 666 |
+ warnings.warn(message, DeprecationWarning)
|
|
| 683 | 667 |
|
| 684 |
- domainpart = validate_domainpart(domainpart)
|
|
| 685 |
- return localpart, domainpart, resourcepart
|
|
| 686 | 668 |
|
| 687 |
- |
|
| 688 |
-@functools.lru_cache(maxsize=32768)
|
|
| 669 |
+@functools.lru_cache(maxsize=None)
|
|
| 689 | 670 |
def validate_localpart(localpart):
|
| 690 | 671 |
if not localpart or len(localpart.encode()) > 1023:
|
| 691 | 672 |
raise LocalpartByteLimit
|
| ... | ... | @@ -700,7 +681,7 @@ def validate_localpart(localpart): |
| 700 | 681 |
raise LocalpartNotAllowedChar
|
| 701 | 682 |
|
| 702 | 683 |
|
| 703 |
-@functools.lru_cache(maxsize=32768)
|
|
| 684 |
+@functools.lru_cache(maxsize=None)
|
|
| 704 | 685 |
def validate_resourcepart(resourcepart):
|
| 705 | 686 |
if not resourcepart or len(resourcepart.encode()) > 1023:
|
| 706 | 687 |
raise ResourcepartByteLimit
|
| ... | ... | @@ -712,7 +693,7 @@ def validate_resourcepart(resourcepart): |
| 712 | 693 |
raise ResourcepartNotAllowedChar
|
| 713 | 694 |
|
| 714 | 695 |
|
| 715 |
-@functools.lru_cache(maxsize=4096)
|
|
| 696 |
+@functools.lru_cache(maxsize=None)
|
|
| 716 | 697 |
def validate_domainpart(domainpart):
|
| 717 | 698 |
if not domainpart:
|
| 718 | 699 |
raise DomainpartByteLimit
|
| ... | ... | @@ -729,167 +710,265 @@ def validate_domainpart(domainpart): |
| 729 | 710 |
domainpart = domainpart[:-1]
|
| 730 | 711 |
|
| 731 | 712 |
try:
|
| 732 |
- idna.encode(domainpart, uts46=True)
|
|
| 713 |
+ idna_encode(domainpart)
|
|
| 733 | 714 |
except Exception:
|
| 734 | 715 |
raise DomainpartNotAllowedChar
|
| 735 | 716 |
|
| 736 | 717 |
return domainpart
|
| 737 | 718 |
|
| 738 | 719 |
|
| 739 |
-class JID:
|
|
| 740 |
- """
|
|
| 741 |
- JID can be built from string, modified, compared, serialised into string
|
|
| 742 |
- """
|
|
| 720 |
+@functools.lru_cache(maxsize=None)
|
|
| 721 |
+def idna_encode(domain):
|
|
| 722 |
+ return idna.encode(domain, uts46=True).decode()
|
|
| 743 | 723 |
|
| 744 |
- def __init__(self, jid=None, node='', domain='', resource=''):
|
|
| 745 |
- """
|
|
| 746 |
- JID can be specified as string (jid argument) or as separate parts
|
|
| 747 | 724 |
|
| 748 |
- Examples:
|
|
| 749 |
- JID('node@domain/resource')
|
|
| 750 |
- JID(node='node',domain='domain.org')
|
|
| 751 |
- """
|
|
| 752 |
- if not jid and not domain:
|
|
| 753 |
- raise ValueError('JID must contain at least domain name')
|
|
| 725 |
+@functools.lru_cache(maxsize=None)
|
|
| 726 |
+def escape_localpart(localpart):
|
|
| 727 |
+ # https://xmpp.org/extensions/xep-0106.html#bizrules-algorithm
|
|
| 728 |
+ #
|
|
| 729 |
+ # If there are any instances of character sequences that correspond
|
|
| 730 |
+ # to escapings of the disallowed characters
|
|
| 731 |
+ # (e.g., the character sequence "\27") or the escaping character
|
|
| 732 |
+ # (i.e., the character sequence "\5c") in the source address,
|
|
| 733 |
+ # the leading backslash character MUST be escaped to the character
|
|
| 734 |
+ # sequence "\5c"
|
|
| 735 |
+ |
|
| 736 |
+ for char in '\\' + _localpart_escape_chars:
|
|
| 737 |
+ seq = "\\{:02x}".format(ord(char))
|
|
| 738 |
+ localpart = localpart.replace(seq, "\\5c{:02x}".format(ord(char)))
|
|
| 739 |
+ |
|
| 740 |
+ # Escape all other chars
|
|
| 741 |
+ for char in _localpart_escape_chars:
|
|
| 742 |
+ localpart = localpart.replace(char, "\\{:02x}".format(ord(char)))
|
|
| 743 |
+ |
|
| 744 |
+ return localpart
|
|
| 745 |
+ |
|
| 746 |
+ |
|
| 747 |
+@functools.lru_cache(maxsize=None)
|
|
| 748 |
+def unescape_localpart(localpart):
|
|
| 749 |
+ if localpart.startswith('\\20') or localpart.endswith('\\20'):
|
|
| 750 |
+ # Escaped JIDs are not allowed to start or end with \20
|
|
| 751 |
+ # so this localpart must be already unescaped
|
|
| 752 |
+ return localpart
|
|
| 753 |
+ |
|
| 754 |
+ for char in _localpart_escape_chars:
|
|
| 755 |
+ seq = "\\{:02x}".format(ord(char))
|
|
| 756 |
+ localpart = localpart.replace(seq, char)
|
|
| 757 |
+ |
|
| 758 |
+ for char in _localpart_escape_chars + "\\":
|
|
| 759 |
+ seq = "\\5c{:02x}".format(ord(char))
|
|
| 760 |
+ localpart = localpart.replace(seq, "\\{:02x}".format(ord(char)))
|
|
| 761 |
+ |
|
| 762 |
+ return localpart
|
|
| 763 |
+ |
|
| 764 |
+ |
|
| 765 |
+class JID(namedtuple('JID',
|
|
| 766 |
+ ['jid', 'localpart', 'domain', 'resource'])):
|
|
| 767 |
+ |
|
| 768 |
+ __slots__ = []
|
|
| 769 |
+ |
|
| 770 |
+ def __new__(cls, jid=None, localpart=None, domain=None, resource=None):
|
|
| 771 |
+ if jid is not None:
|
|
| 772 |
+ deprecation_warning('JID(jid) is deprecated, use from_string()')
|
|
| 773 |
+ return JID.from_string(str(jid))
|
|
| 774 |
+ |
|
| 775 |
+ if localpart is not None:
|
|
| 776 |
+ localpart = validate_localpart(localpart)
|
|
| 777 |
+ |
|
| 778 |
+ domain = validate_domainpart(domain)
|
|
| 779 |
+ |
|
| 780 |
+ if resource is not None:
|
|
| 781 |
+ resource = validate_resourcepart(resource)
|
|
| 782 |
+ |
|
| 783 |
+ return super().__new__(cls, None, localpart, domain, resource)
|
|
| 754 | 784 |
|
| 755 |
- if isinstance(jid, JID):
|
|
| 756 |
- self.node, self.domain = jid.node, jid.domain
|
|
| 757 |
- self.resource = jid.resource
|
|
| 758 |
- elif domain:
|
|
| 759 |
- self.node, self.domain, self.resource = node, domain, resource
|
|
| 785 |
+ @classmethod
|
|
| 786 |
+ @functools.lru_cache(maxsize=None)
|
|
| 787 |
+ def from_string(cls, jid_string):
|
|
| 788 |
+ # https://tools.ietf.org/html/rfc7622#section-3.2
|
|
| 789 |
+ |
|
| 790 |
+ # Remove any portion from the first '/' character to the end of the
|
|
| 791 |
+ # string (if there is a '/' character present).
|
|
| 792 |
+ |
|
| 793 |
+ # Remove any portion from the beginning of the string to the first
|
|
| 794 |
+ # '@' character (if there is an '@' character present).
|
|
| 795 |
+ |
|
| 796 |
+ if jid_string.find('/') != -1:
|
|
| 797 |
+ rest, resourcepart = jid_string.split('/', 1)
|
|
| 760 | 798 |
else:
|
| 761 |
- self.node, self.domain, self.resource = parse_jid(jid)
|
|
| 799 |
+ rest, resourcepart = jid_string, None
|
|
| 762 | 800 |
|
| 763 |
- def copy(self):
|
|
| 764 |
- return JID(self)
|
|
| 801 |
+ if rest.find('@') != -1:
|
|
| 802 |
+ localpart, domainpart = rest.split('@', 1)
|
|
| 803 |
+ else:
|
|
| 804 |
+ localpart, domainpart = None, rest
|
|
| 765 | 805 |
|
| 766 |
- def getNode(self):
|
|
| 767 |
- """
|
|
| 768 |
- Return the node part of the JID
|
|
| 769 |
- """
|
|
| 770 |
- return self.node
|
|
| 806 |
+ return cls(jid=None,
|
|
| 807 |
+ localpart=localpart,
|
|
| 808 |
+ domain=domainpart,
|
|
| 809 |
+ resource=resourcepart)
|
|
| 771 | 810 |
|
| 772 |
- def setNode(self, node):
|
|
| 773 |
- """
|
|
| 774 |
- Set the node part of the JID to new value. Specify None to remove
|
|
| 775 |
- the node part
|
|
| 776 |
- """
|
|
| 777 |
- self.node = node.lower()
|
|
| 811 |
+ @classmethod
|
|
| 812 |
+ @functools.lru_cache(maxsize=None)
|
|
| 813 |
+ def from_user_input(cls, user_input, escaped=False):
|
|
| 814 |
+ # Use this if we want JIDs to be escaped according to XEP-0106
|
|
| 815 |
+ # The standard JID parsing cannot be applied because user_input is
|
|
| 816 |
+ # not a valid JID.
|
|
| 778 | 817 |
|
| 779 |
- def getDomain(self):
|
|
| 780 |
- """
|
|
| 781 |
- Return the domain part of the JID
|
|
| 782 |
- """
|
|
| 783 |
- return self.domain
|
|
| 818 |
+ # Only user_input which after escaping result in a bare JID can be
|
|
| 819 |
+ # successfully parsed.
|
|
| 784 | 820 |
|
| 785 |
- def setDomain(self, domain):
|
|
| 786 |
- """
|
|
| 787 |
- Set the domain part of the JID to new value
|
|
| 788 |
- """
|
|
| 789 |
- self.domain = domain.lower()
|
|
| 821 |
+ # The assumpution is user_input is a bare JID so we start with an
|
|
| 822 |
+ # rsplit on @ because we assume there is no resource, so the char @
|
|
| 823 |
+ # in the localpart can later be escaped.
|
|
| 790 | 824 |
|
| 791 |
- def getResource(self):
|
|
| 792 |
- """
|
|
| 793 |
- Return the resource part of the JID
|
|
| 794 |
- """
|
|
| 795 |
- return self.resource
|
|
| 825 |
+ if escaped:
|
|
| 826 |
+ # for convenience
|
|
| 827 |
+ return cls.from_string(user_input)
|
|
| 796 | 828 |
|
| 797 |
- def setResource(self, resource):
|
|
| 798 |
- """
|
|
| 799 |
- Set the resource part of the JID to new value.
|
|
| 800 |
- Specify None to remove the resource part
|
|
| 801 |
- """
|
|
| 802 |
- self.resource = resource
|
|
| 829 |
+ if '@' in user_input:
|
|
| 830 |
+ localpart, domainpart = user_input.rsplit('@', 1)
|
|
| 831 |
+ if localpart.startswith(' ') or localpart.endswith(' '):
|
|
| 832 |
+ raise LocalpartNotAllowedChar
|
|
| 803 | 833 |
|
| 804 |
- def getStripped(self):
|
|
| 805 |
- """
|
|
| 806 |
- Return the bare representation of JID. I.e. string value w/o resource
|
|
| 807 |
- """
|
|
| 808 |
- if not self.node:
|
|
| 809 |
- return self.domain
|
|
| 810 |
- return '%s@%s' % (self.node, self.domain)
|
|
| 834 |
+ localpart = escape_localpart(localpart)
|
|
| 811 | 835 |
|
| 812 |
- def getBare(self):
|
|
| 813 |
- """
|
|
| 814 |
- Return the bare representation of JID. I.e. string value w/o resource
|
|
| 815 |
- """
|
|
| 816 |
- if not self.node:
|
|
| 817 |
- return self.domain
|
|
| 818 |
- return '%s@%s' % (self.node, self.domain)
|
|
| 836 |
+ else:
|
|
| 837 |
+ localpart = None
|
|
| 838 |
+ domainpart = user_input
|
|
| 819 | 839 |
|
| 820 |
- def setBare(self):
|
|
| 821 |
- """
|
|
| 822 |
- Deletes the resource which makes the JID bare
|
|
| 823 |
- """
|
|
| 824 |
- self.resource = ''
|
|
| 840 |
+ return cls(jid=None,
|
|
| 841 |
+ localpart=localpart,
|
|
| 842 |
+ domain=domainpart,
|
|
| 843 |
+ resource=None)
|
|
| 825 | 844 |
|
| 826 |
- @property
|
|
| 827 |
- def isBare(self):
|
|
| 828 |
- return self.node and self.domain and not self.resource
|
|
| 845 |
+ def __str__(self):
|
|
| 846 |
+ if self.localpart:
|
|
| 847 |
+ jid = f'{self.localpart}@{self.domain}'
|
|
| 848 |
+ else:
|
|
| 849 |
+ jid = self.domain
|
|
| 829 | 850 |
|
| 830 |
- @property
|
|
| 831 |
- def isDomain(self):
|
|
| 832 |
- return self.domain and not self.node and not self.resource
|
|
| 851 |
+ if self.resource is not None:
|
|
| 852 |
+ return f'{jid}/{self.resource}'
|
|
| 853 |
+ return jid
|
|
| 833 | 854 |
|
| 834 |
- @property
|
|
| 835 |
- def isFull(self):
|
|
| 836 |
- return bool(self.domain and self.node and self.resource)
|
|
| 855 |
+ def __hash__(self):
|
|
| 856 |
+ return hash(str(self))
|
|
| 837 | 857 |
|
| 838 | 858 |
def __eq__(self, other):
|
| 839 |
- """
|
|
| 840 |
- Compare the JID to another instance or to string for equality
|
|
| 841 |
- """
|
|
| 842 |
- if not isinstance(other, JID):
|
|
| 859 |
+ if isinstance(other, str):
|
|
| 860 |
+ deprecation_warning('comparing string with JID is deprected')
|
|
| 843 | 861 |
try:
|
| 844 |
- other = JID(other)
|
|
| 862 |
+ return JID.from_string(other) == self
|
|
| 845 | 863 |
except Exception:
|
| 846 | 864 |
return False
|
| 847 |
- |
|
| 848 |
- return (self.node == other.node and
|
|
| 849 |
- self.domain == other.domain and
|
|
| 850 |
- self.resource == other.resource)
|
|
| 865 |
+ return super().__eq__(other)
|
|
| 851 | 866 |
|
| 852 | 867 |
def __ne__(self, other):
|
| 853 |
- """
|
|
| 854 |
- Compare the JID to another instance or to string for non-equality
|
|
| 855 |
- """
|
|
| 856 | 868 |
return not self.__eq__(other)
|
| 857 | 869 |
|
| 858 |
- def bareMatch(self, other):
|
|
| 859 |
- """
|
|
| 860 |
- Compare the node and domain parts of the JID's for equality
|
|
| 861 |
- """
|
|
| 862 |
- if not isinstance(other, JID):
|
|
| 863 |
- try:
|
|
| 864 |
- other = JID(other)
|
|
| 865 |
- except Exception:
|
|
| 866 |
- return False
|
|
| 870 |
+ def domain_to_ascii(self):
|
|
| 871 |
+ return idna_encode(self.domain)
|
|
| 867 | 872 |
|
| 868 |
- return (self.node == other.node and
|
|
| 869 |
- self.domain == other.domain)
|
|
| 873 |
+ @property
|
|
| 874 |
+ def bare(self):
|
|
| 875 |
+ if self.localpart is not None:
|
|
| 876 |
+ return f'{self.localpart}@{self.domain}'
|
|
| 877 |
+ return self.domain
|
|
| 870 | 878 |
|
| 871 |
- def __str__(self):
|
|
| 872 |
- """
|
|
| 873 |
- Serialise JID into string
|
|
| 874 |
- """
|
|
| 875 |
- if self.node:
|
|
| 876 |
- jid = '%s@%s' % (self.node, self.domain)
|
|
| 879 |
+ @property
|
|
| 880 |
+ def is_bare(self):
|
|
| 881 |
+ return self.resource is None
|
|
| 882 |
+ |
|
| 883 |
+ def new_as_bare(self):
|
|
| 884 |
+ if self.resource is None:
|
|
| 885 |
+ return self
|
|
| 886 |
+ return self._replace(resource=None)
|
|
| 887 |
+ |
|
| 888 |
+ def bare_match(self, other):
|
|
| 889 |
+ if isinstance(other, str):
|
|
| 890 |
+ other = JID.from_string(other)
|
|
| 891 |
+ return self.bare == other.bare
|
|
| 892 |
+ |
|
| 893 |
+ @property
|
|
| 894 |
+ def is_domain(self):
|
|
| 895 |
+ return self.localpart is None and self.resource is None
|
|
| 896 |
+ |
|
| 897 |
+ @property
|
|
| 898 |
+ def is_full(self):
|
|
| 899 |
+ return (self.localpart is not None and
|
|
| 900 |
+ self.domain is not None and
|
|
| 901 |
+ self.resource is not None)
|
|
| 902 |
+ |
|
| 903 |
+ def new_with(self, **kwargs):
|
|
| 904 |
+ return self._replace(**kwargs)
|
|
| 905 |
+ |
|
| 906 |
+ def to_user_string(self, show_punycode=True):
|
|
| 907 |
+ domain = self.domain_to_ascii()
|
|
| 908 |
+ if domain.startswith('xn--') and show_punycode:
|
|
| 909 |
+ domain_encoded = f' ({domain})'
|
|
| 877 | 910 |
else:
|
| 878 |
- jid = self.domain
|
|
| 911 |
+ domain_encoded = ''
|
|
| 879 | 912 |
|
| 880 |
- if self.resource:
|
|
| 881 |
- return '%s/%s' % (jid, self.resource)
|
|
| 882 |
- return jid
|
|
| 913 |
+ if self.localpart is None:
|
|
| 914 |
+ return f'{self}{domain_encoded}'
|
|
| 883 | 915 |
|
| 884 |
- def __hash__(self):
|
|
| 885 |
- """
|
|
| 886 |
- Produce hash of the JID, Allows to use JID objects as keys of the
|
|
| 887 |
- dictionary
|
|
| 888 |
- """
|
|
| 889 |
- return hash(str(self))
|
|
| 916 |
+ localpart = unescape_localpart(self.localpart)
|
|
| 917 |
+ |
|
| 918 |
+ if self.resource is None:
|
|
| 919 |
+ return f'{localpart}@{self.domain}{domain_encoded}'
|
|
| 920 |
+ return f'{localpart}@{self.domain}/{self.resource}{domain_encoded}'
|
|
| 921 |
+ |
|
| 922 |
+ def bareMatch(self, other):
|
|
| 923 |
+ deprecation_warning('bareMatch() is deprected use bare_match()')
|
|
| 924 |
+ return self.bare_match(other)
|
|
| 925 |
+ |
|
| 926 |
+ @property
|
|
| 927 |
+ def isBare(self):
|
|
| 928 |
+ deprecation_warning('isBare() is deprected use '
|
|
| 929 |
+ 'the attribute is_bare')
|
|
| 930 |
+ return self.is_bare
|
|
| 931 |
+ |
|
| 932 |
+ @property
|
|
| 933 |
+ def isDomain(self):
|
|
| 934 |
+ deprecation_warning('isDomain() is deprected use '
|
|
| 935 |
+ 'the attribute is_domain')
|
|
| 936 |
+ return self.is_domain
|
|
| 890 | 937 |
|
| 891 |
- def __repr__(self):
|
|
| 892 |
- return str(self)
|
|
| 938 |
+ @property
|
|
| 939 |
+ def isFull(self):
|
|
| 940 |
+ deprecation_warning('isFull() is deprected use '
|
|
| 941 |
+ 'the attribute is_full')
|
|
| 942 |
+ return self.is_full
|
|
| 943 |
+ |
|
| 944 |
+ def copy(self):
|
|
| 945 |
+ deprecation_warning('copy() is not needed, JID is immutable')
|
|
| 946 |
+ return self
|
|
| 947 |
+ |
|
| 948 |
+ def getNode(self):
|
|
| 949 |
+ deprecation_warning('getNode() is deprected use '
|
|
| 950 |
+ 'the attribute localpart')
|
|
| 951 |
+ return self.localpart
|
|
| 952 |
+ |
|
| 953 |
+ def getDomain(self):
|
|
| 954 |
+ deprecation_warning('getDomain() is deprected use '
|
|
| 955 |
+ 'the attribute domain')
|
|
| 956 |
+ return self.domain
|
|
| 957 |
+ |
|
| 958 |
+ def getResource(self):
|
|
| 959 |
+ deprecation_warning('getResource() is deprected use '
|
|
| 960 |
+ 'the attribute resource')
|
|
| 961 |
+ return self.resource
|
|
| 962 |
+ |
|
| 963 |
+ def getStripped(self):
|
|
| 964 |
+ deprecation_warning('getStripped() is deprected use '
|
|
| 965 |
+ 'the attribute bare')
|
|
| 966 |
+ return self.bare
|
|
| 967 |
+ |
|
| 968 |
+ def getBare(self):
|
|
| 969 |
+ deprecation_warning('getBare() is deprected use '
|
|
| 970 |
+ 'the attribute bare')
|
|
| 971 |
+ return self.bare
|
|
| 893 | 972 |
|
| 894 | 973 |
|
| 895 | 974 |
class StreamErrorNode(Node):
|
| ... | ... | @@ -1035,7 +1114,7 @@ class Protocol(Node): |
| 1035 | 1114 |
"""
|
| 1036 | 1115 |
attr = self.getAttr('jid')
|
| 1037 | 1116 |
if attr:
|
| 1038 |
- return JID(attr)
|
|
| 1117 |
+ return JID.from_string(attr)
|
|
| 1039 | 1118 |
return attr
|
| 1040 | 1119 |
|
| 1041 | 1120 |
def getID(self):
|
| ... | ... | @@ -1048,7 +1127,9 @@ class Protocol(Node): |
| 1048 | 1127 |
"""
|
| 1049 | 1128 |
Set the value of the 'to' attribute
|
| 1050 | 1129 |
"""
|
| 1051 |
- self.setAttr('to', JID(val))
|
|
| 1130 |
+ if not isinstance(val, JID):
|
|
| 1131 |
+ val = JID.from_string(val)
|
|
| 1132 |
+ self.setAttr('to', val)
|
|
| 1052 | 1133 |
|
| 1053 | 1134 |
def getType(self):
|
| 1054 | 1135 |
"""
|
| ... | ... | @@ -1060,7 +1141,9 @@ class Protocol(Node): |
| 1060 | 1141 |
"""
|
| 1061 | 1142 |
Set the value of the 'from' attribute
|
| 1062 | 1143 |
"""
|
| 1063 |
- self.setAttr('from', JID(val))
|
|
| 1144 |
+ if not isinstance(val, JID):
|
|
| 1145 |
+ val = JID.from_string(val)
|
|
| 1146 |
+ self.setAttr('from', val)
|
|
| 1064 | 1147 |
|
| 1065 | 1148 |
def setType(self, val):
|
| 1066 | 1149 |
"""
|
| ... | ... | @@ -1202,7 +1285,8 @@ class Protocol(Node): |
| 1202 | 1285 |
Set the item 'item' to the value 'val'
|
| 1203 | 1286 |
"""
|
| 1204 | 1287 |
if item in ['to', 'from']:
|
| 1205 |
- val = JID(val)
|
|
| 1288 |
+ if not isinstance(val, JID):
|
|
| 1289 |
+ val = JID.from_string(val)
|
|
| 1206 | 1290 |
return self.setAttr(item, val)
|
| 1207 | 1291 |
|
| 1208 | 1292 |
|
| ... | ... | @@ -250,7 +250,7 @@ class DiscoInfo(namedtuple('DiscoInfo', 'stanza identities features dataforms ti |
| 250 | 250 |
return self.muc_identity_name
|
| 251 | 251 |
|
| 252 | 252 |
if self.jid is not None:
|
| 253 |
- return self.jid.getNode()
|
|
| 253 |
+ return self.jid.localpart
|
|
| 254 | 254 |
return None
|
| 255 | 255 |
|
| 256 | 256 |
@property
|
| ... | ... | @@ -931,9 +931,7 @@ class PresenceProperties: |
| 931 | 931 |
def new_jid(self):
|
| 932 | 932 |
if not self.is_nickname_changed:
|
| 933 | 933 |
raise ValueError('This is not a nickname change')
|
| 934 |
- jid = JID(self.jid)
|
|
| 935 |
- jid.setResource(self.muc_user.nick)
|
|
| 936 |
- return jid
|
|
| 934 |
+ return self.jid.new_with(resource=self.muc_user.nick)
|
|
| 937 | 935 |
|
| 938 | 936 |
@property
|
| 939 | 937 |
def is_kicked(self):
|
| ... | ... | @@ -14,7 +14,7 @@ class StanzaHandlerTest(unittest.TestCase): |
| 14 | 14 |
self.client.is_websocket = False
|
| 15 | 15 |
self.dispatcher = StanzaDispatcher(self.client)
|
| 16 | 16 |
|
| 17 |
- self.client.get_bound_jid.return_value = JID('test@test.test')
|
|
| 17 |
+ self.client.get_bound_jid.return_value = JID.from_string('test@test.test')
|
|
| 18 | 18 |
|
| 19 | 19 |
self.dispatcher.reset_parser()
|
| 20 | 20 |
self.dispatcher.process_data(STREAM_START)
|
| ... | ... | @@ -13,12 +13,12 @@ class BookmarkTest(StanzaHandlerTest): |
| 13 | 13 |
def _on_message(_con, _stanza, properties):
|
| 14 | 14 |
|
| 15 | 15 |
data = [
|
| 16 |
- BookmarkData(jid=JID('theplay@conference.shakespeare.lit'),
|
|
| 16 |
+ BookmarkData(jid=JID.from_string('theplay@conference.shakespeare.lit'),
|
|
| 17 | 17 |
name='The Play\'s the Thing',
|
| 18 | 18 |
autojoin=True,
|
| 19 | 19 |
password='pass',
|
| 20 | 20 |
nick='JC'),
|
| 21 |
- BookmarkData(jid=JID('second@conference.shakespeare.lit'),
|
|
| 21 |
+ BookmarkData(jid=JID.from_string('second@conference.shakespeare.lit'),
|
|
| 22 | 22 |
name='Second room',
|
| 23 | 23 |
autojoin=False,
|
| 24 | 24 |
password=None,
|
| ... | ... | @@ -71,7 +71,7 @@ class BookmarkTest(StanzaHandlerTest): |
| 71 | 71 |
def test_bookmark_2_parsing(self):
|
| 72 | 72 |
def _on_message(_con, _stanza, properties):
|
| 73 | 73 |
|
| 74 |
- data = BookmarkData(jid=JID('theplay@conference.shakespeare.lit'),
|
|
| 74 |
+ data = BookmarkData(jid=JID.from_string('theplay@conference.shakespeare.lit'),
|
|
| 75 | 75 |
name='The Play\'s the Thing',
|
| 76 | 76 |
autojoin=True,
|
| 77 | 77 |
password=None,
|
| 1 | 1 |
import unittest
|
| 2 | 2 |
|
| 3 | 3 |
from nbxmpp.protocol import Iq
|
| 4 |
+from nbxmpp.protocol import JID
|
|
| 4 | 5 |
from nbxmpp.util import error_factory
|
| 5 | 6 |
|
| 6 | 7 |
|
| ... | ... | @@ -29,4 +30,4 @@ class TestErrorParsing(unittest.TestCase): |
| 29 | 30 |
self.assertEqual(error.get_text('de'), 'File zu groß. Erlaubt sind 20000 bytes')
|
| 30 | 31 |
self.assertEqual(error.type, 'modify')
|
| 31 | 32 |
self.assertEqual(error.id, 'step_03')
|
| 32 |
- self.assertEqual(error.jid, 'upload.montague.tld')
|
|
| 33 |
+ self.assertEqual(error.jid, JID.from_string('upload.montague.tld'))
|
| ... | ... | @@ -30,7 +30,7 @@ class JIDParsing(unittest.TestCase): |
| 30 | 30 |
]
|
| 31 | 31 |
|
| 32 | 32 |
for jid in tests:
|
| 33 |
- JID(jid)
|
|
| 33 |
+ JID.from_string(jid)
|
|
| 34 | 34 |
|
| 35 | 35 |
def test_invalid_jids(self):
|
| 36 | 36 |
tests = [
|
| ... | ... | @@ -48,7 +48,7 @@ class JIDParsing(unittest.TestCase): |
| 48 | 48 |
|
| 49 | 49 |
for jid, exception in tests:
|
| 50 | 50 |
with self.assertRaises(exception):
|
| 51 |
- JID(jid)
|
|
| 51 |
+ JID.from_string(jid)
|
|
| 52 | 52 |
|
| 53 | 53 |
def test_ip_literals(self):
|
| 54 | 54 |
tests = [
|
| ... | ... | @@ -57,7 +57,7 @@ class JIDParsing(unittest.TestCase): |
| 57 | 57 |
]
|
| 58 | 58 |
|
| 59 | 59 |
for jid in tests:
|
| 60 |
- JID(jid)
|
|
| 60 |
+ JID.from_string(jid)
|
|
| 61 | 61 |
|
| 62 | 62 |
def test_jid_equality(self):
|
| 63 | 63 |
tests = [
|
| ... | ... | @@ -67,4 +67,112 @@ class JIDParsing(unittest.TestCase): |
| 67 | 67 |
]
|
| 68 | 68 |
|
| 69 | 69 |
for jid in tests:
|
| 70 |
- self.assertTrue(JID(jid) == jid)
|
|
| 70 |
+ self.assertTrue(JID.from_string(jid) == JID.from_string(jid))
|
|
| 71 |
+ |
|
| 72 |
+ def test_jid_escaping(self):
|
|
| 73 |
+ # (user input, escaped)
|
|
| 74 |
+ tests = [
|
|
| 75 |
+ (r'space cadet@example.com',
|
|
| 76 |
+ r'space\20cadet@example.com'),
|
|
| 77 |
+ |
|
| 78 |
+ (r'call me "ishmael"@example.com',
|
|
| 79 |
+ r'call\20me\20\22ishmael\22@example.com'),
|
|
| 80 |
+ |
|
| 81 |
+ (r'at&t guy@example.com',
|
|
| 82 |
+ r'at\26t\20guy@example.com'),
|
|
| 83 |
+ |
|
| 84 |
+ ('d\'artagnan@example.com',
|
|
| 85 |
+ r'd\27artagnan@example.com'),
|
|
| 86 |
+ |
|
| 87 |
+ (r'/.fanboy@example.com',
|
|
| 88 |
+ r'\2f.fanboy@example.com'),
|
|
| 89 |
+ |
|
| 90 |
+ (r'::foo::@example.com',
|
|
| 91 |
+ r'\3a\3afoo\3a\3a@example.com'),
|
|
| 92 |
+ |
|
| 93 |
+ (r'<foo>@example.com',
|
|
| 94 |
+ r'\3cfoo\3e@example.com'),
|
|
| 95 |
+ |
|
| 96 |
+ (r'user@host@example.com',
|
|
| 97 |
+ r'user\40host@example.com'),
|
|
| 98 |
+ |
|
| 99 |
+ (r'c:\net@example.com',
|
|
| 100 |
+ r'c\3a\net@example.com'),
|
|
| 101 |
+ |
|
| 102 |
+ (r'c:\\net@example.com',
|
|
| 103 |
+ r'c\3a\\net@example.com'),
|
|
| 104 |
+ |
|
| 105 |
+ (r'c:\cool stuff@example.com',
|
|
| 106 |
+ r'c\3a\cool\20stuff@example.com'),
|
|
| 107 |
+ |
|
| 108 |
+ (r'c:\5commas@example.com',
|
|
| 109 |
+ r'c\3a\5c5commas@example.com'),
|
|
| 110 |
+ |
|
| 111 |
+ (r'call me\20@example.com',
|
|
| 112 |
+ r'call\20me\5c20@example.com'),
|
|
| 113 |
+ ]
|
|
| 114 |
+ |
|
| 115 |
+ tests2 = [
|
|
| 116 |
+ 'juliet@example.com',
|
|
| 117 |
+ 'juliet@example.com',
|
|
| 118 |
+ 'juliet@example.com',
|
|
| 119 |
+ 'juliet@example.com',
|
|
| 120 |
+ 'fussball@example.com',
|
|
| 121 |
+ 'fu\U000000DFball@example.com',
|
|
| 122 |
+ '\U000003C0@example.com',
|
|
| 123 |
+ '\U000003A3@example.com',
|
|
| 124 |
+ '\U000003C3@example.com',
|
|
| 125 |
+ '\U000003C2@example.com',
|
|
| 126 |
+ 'example.com',
|
|
| 127 |
+ ]
|
|
| 128 |
+ |
|
| 129 |
+ test3 = [
|
|
| 130 |
+ '\\20callme\\20@example.com',
|
|
| 131 |
+ '\\20callme@example.com',
|
|
| 132 |
+ 'callme\\20@example.com',
|
|
| 133 |
+ ]
|
|
| 134 |
+ |
|
| 135 |
+ test4 = [
|
|
| 136 |
+ ('call\\20me@example.com', 'call me@example.com',)
|
|
| 137 |
+ ]
|
|
| 138 |
+ |
|
| 139 |
+ fail_tests = [
|
|
| 140 |
+ r'c:\5commas@example.com/asd',
|
|
| 141 |
+ r'juliet@example.com/test'
|
|
| 142 |
+ ]
|
|
| 143 |
+ |
|
| 144 |
+ for user_input, escaped in tests:
|
|
| 145 |
+ # Parse user input and escape it
|
|
| 146 |
+ jid = JID.from_user_input(user_input)
|
|
| 147 |
+ |
|
| 148 |
+ self.assertTrue(jid.domain == 'example.com')
|
|
| 149 |
+ self.assertTrue(str(jid) == escaped)
|
|
| 150 |
+ self.assertTrue(jid.to_user_string() == user_input)
|
|
| 151 |
+ |
|
| 152 |
+ # We must fail on invalid JIDs
|
|
| 153 |
+ with self.assertRaises(Exception):
|
|
| 154 |
+ JID.from_string(user_input)
|
|
| 155 |
+ |
|
| 156 |
+ # Parse escaped JIDs
|
|
| 157 |
+ jid = JID.from_string(escaped)
|
|
| 158 |
+ self.assertTrue(str(jid) == escaped)
|
|
| 159 |
+ self.assertTrue(jid.domain == 'example.com')
|
|
| 160 |
+ |
|
| 161 |
+ for jid in tests2:
|
|
| 162 |
+ # show that from_string() and from_user_input() produce the same
|
|
| 163 |
+ # result for valid bare JIDs
|
|
| 164 |
+ self.assertTrue(JID.from_string(jid) == JID.from_user_input(jid))
|
|
| 165 |
+ |
|
| 166 |
+ for jid in test3:
|
|
| 167 |
+ # JIDs starting or ending with \20 are not escaped
|
|
| 168 |
+ self.assertTrue(JID.from_string(jid).to_user_string() == jid)
|
|
| 169 |
+ |
|
| 170 |
+ for user_input, user_string in test4:
|
|
| 171 |
+ # Test escaped keyword argument
|
|
| 172 |
+ self.assertTrue(JID.from_user_input(user_input, escaped=True) != JID.from_user_input(user_input))
|
|
| 173 |
+ self.assertTrue(JID.from_user_input(user_input, escaped=True).to_user_string() == user_string)
|
|
| 174 |
+ |
|
| 175 |
+ for user_input in fail_tests:
|
|
| 176 |
+ # from_user_input does only support bare jids
|
|
| 177 |
+ with self.assertRaises(Exception):
|
|
| 178 |
+ JID.from_user_input(user_input)
|