summaryrefslogtreecommitdiff
path: root/sleekxmpp
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp')
-rw-r--r--sleekxmpp/__init__.py30
-rw-r--r--sleekxmpp/api.py200
-rw-r--r--sleekxmpp/basexmpp.py831
-rw-r--r--sleekxmpp/clientxmpp.py338
-rw-r--r--sleekxmpp/componentxmpp.py164
-rw-r--r--sleekxmpp/exceptions.py91
-rw-r--r--sleekxmpp/features/__init__.py16
-rw-r--r--sleekxmpp/features/feature_bind/__init__.py19
-rw-r--r--sleekxmpp/features/feature_bind/bind.py65
-rw-r--r--sleekxmpp/features/feature_bind/stanza.py21
-rw-r--r--sleekxmpp/features/feature_mechanisms/__init__.py22
-rw-r--r--sleekxmpp/features/feature_mechanisms/mechanisms.py244
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/__init__.py16
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/abort.py24
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/auth.py49
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/challenge.py39
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/failure.py76
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py53
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/response.py39
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/success.py38
-rw-r--r--sleekxmpp/features/feature_preapproval/__init__.py15
-rw-r--r--sleekxmpp/features/feature_preapproval/preapproval.py42
-rw-r--r--sleekxmpp/features/feature_preapproval/stanza.py17
-rw-r--r--sleekxmpp/features/feature_rosterver/__init__.py19
-rw-r--r--sleekxmpp/features/feature_rosterver/rosterver.py42
-rw-r--r--sleekxmpp/features/feature_rosterver/stanza.py17
-rw-r--r--sleekxmpp/features/feature_session/__init__.py19
-rw-r--r--sleekxmpp/features/feature_session/session.py54
-rw-r--r--sleekxmpp/features/feature_session/stanza.py20
-rw-r--r--sleekxmpp/features/feature_starttls/__init__.py19
-rw-r--r--sleekxmpp/features/feature_starttls/stanza.py45
-rw-r--r--sleekxmpp/features/feature_starttls/starttls.py66
-rw-r--r--sleekxmpp/jid.py632
-rw-r--r--sleekxmpp/plugins/__init__.py88
-rw-r--r--sleekxmpp/plugins/base.py360
-rw-r--r--sleekxmpp/plugins/gmail_notify.py149
-rw-r--r--sleekxmpp/plugins/google/__init__.py47
-rw-r--r--sleekxmpp/plugins/google/auth/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/auth/auth.py52
-rw-r--r--sleekxmpp/plugins/google/auth/stanza.py49
-rw-r--r--sleekxmpp/plugins/google/gmail/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/gmail/notifications.py96
-rw-r--r--sleekxmpp/plugins/google/gmail/stanza.py101
-rw-r--r--sleekxmpp/plugins/google/nosave/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/nosave/nosave.py83
-rw-r--r--sleekxmpp/plugins/google/nosave/stanza.py59
-rw-r--r--sleekxmpp/plugins/google/settings/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/settings/settings.py63
-rw-r--r--sleekxmpp/plugins/google/settings/stanza.py110
-rw-r--r--sleekxmpp/plugins/xep_0004/__init__.py22
-rw-r--r--sleekxmpp/plugins/xep_0004/dataforms.py57
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/__init__.py10
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py185
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py279
-rw-r--r--sleekxmpp/plugins/xep_0009/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0009/binding.py173
-rw-r--r--sleekxmpp/plugins/xep_0009/remote.py779
-rw-r--r--sleekxmpp/plugins/xep_0009/rpc.py218
-rw-r--r--sleekxmpp/plugins/xep_0009/stanza/RPC.py64
-rw-r--r--sleekxmpp/plugins/xep_0009/stanza/__init__.py9
-rw-r--r--sleekxmpp/plugins/xep_0012/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0012/last_activity.py157
-rw-r--r--sleekxmpp/plugins/xep_0012/stanza.py32
-rw-r--r--sleekxmpp/plugins/xep_0013/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0013/offline.py134
-rw-r--r--sleekxmpp/plugins/xep_0013/stanza.py53
-rw-r--r--sleekxmpp/plugins/xep_0016/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0016/privacy.py110
-rw-r--r--sleekxmpp/plugins/xep_0016/stanza.py103
-rw-r--r--sleekxmpp/plugins/xep_0020/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0020/feature_negotiation.py36
-rw-r--r--sleekxmpp/plugins/xep_0020/stanza.py17
-rw-r--r--sleekxmpp/plugins/xep_0027/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0027/gpg.py170
-rw-r--r--sleekxmpp/plugins/xep_0027/stanza.py53
-rw-r--r--sleekxmpp/plugins/xep_0030/__init__.py23
-rw-r--r--sleekxmpp/plugins/xep_0030/disco.py743
-rw-r--r--sleekxmpp/plugins/xep_0030/stanza/__init__.py10
-rw-r--r--sleekxmpp/plugins/xep_0030/stanza/info.py276
-rw-r--r--sleekxmpp/plugins/xep_0030/stanza/items.py152
-rw-r--r--sleekxmpp/plugins/xep_0030/static.py430
-rw-r--r--sleekxmpp/plugins/xep_0033/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0033/addresses.py37
-rw-r--r--sleekxmpp/plugins/xep_0033/stanza.py131
-rw-r--r--sleekxmpp/plugins/xep_0045.py412
-rw-r--r--sleekxmpp/plugins/xep_0047/__init__.py21
-rw-r--r--sleekxmpp/plugins/xep_0047/ibb.py215
-rw-r--r--sleekxmpp/plugins/xep_0047/stanza.py67
-rw-r--r--sleekxmpp/plugins/xep_0047/stream.py148
-rw-r--r--sleekxmpp/plugins/xep_0048/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0048/bookmarks.py76
-rw-r--r--sleekxmpp/plugins/xep_0048/stanza.py65
-rw-r--r--sleekxmpp/plugins/xep_0049/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0049/private_storage.py53
-rw-r--r--sleekxmpp/plugins/xep_0049/stanza.py17
-rw-r--r--sleekxmpp/plugins/xep_0050/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0050/adhoc.py701
-rw-r--r--sleekxmpp/plugins/xep_0050/stanza.py185
-rw-r--r--sleekxmpp/plugins/xep_0054/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0054/stanza.py562
-rw-r--r--sleekxmpp/plugins/xep_0054/vcard_temp.py146
-rw-r--r--sleekxmpp/plugins/xep_0059/__init__.py18
-rw-r--r--sleekxmpp/plugins/xep_0059/rsm.py145
-rw-r--r--sleekxmpp/plugins/xep_0059/stanza.py108
-rw-r--r--sleekxmpp/plugins/xep_0060/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0060/pubsub.py577
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/__init__.py12
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/base.py29
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub.py272
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py86
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py151
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py134
-rw-r--r--sleekxmpp/plugins/xep_0065/__init__.py7
-rw-r--r--sleekxmpp/plugins/xep_0065/proxy.py292
-rw-r--r--sleekxmpp/plugins/xep_0065/stanza.py47
-rw-r--r--sleekxmpp/plugins/xep_0066/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0066/oob.py158
-rw-r--r--sleekxmpp/plugins/xep_0066/stanza.py33
-rw-r--r--sleekxmpp/plugins/xep_0071/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0071/stanza.py81
-rw-r--r--sleekxmpp/plugins/xep_0071/xhtml_im.py30
-rw-r--r--sleekxmpp/plugins/xep_0077/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0077/register.py115
-rw-r--r--sleekxmpp/plugins/xep_0077/stanza.py73
-rw-r--r--sleekxmpp/plugins/xep_0078/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0078/legacyauth.py147
-rw-r--r--sleekxmpp/plugins/xep_0078/stanza.py41
-rw-r--r--sleekxmpp/plugins/xep_0079/__init__.py18
-rw-r--r--sleekxmpp/plugins/xep_0079/amp.py79
-rw-r--r--sleekxmpp/plugins/xep_0079/stanza.py96
-rw-r--r--sleekxmpp/plugins/xep_0080/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0080/geoloc.py125
-rw-r--r--sleekxmpp/plugins/xep_0080/stanza.py266
-rw-r--r--sleekxmpp/plugins/xep_0082.py228
-rw-r--r--sleekxmpp/plugins/xep_0084/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0084/avatar.py111
-rw-r--r--sleekxmpp/plugins/xep_0084/stanza.py81
-rw-r--r--sleekxmpp/plugins/xep_0085/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0085/chat_states.py56
-rw-r--r--sleekxmpp/plugins/xep_0085/stanza.py94
-rw-r--r--sleekxmpp/plugins/xep_0086/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0086/legacy_error.py46
-rw-r--r--sleekxmpp/plugins/xep_0086/stanza.py91
-rw-r--r--sleekxmpp/plugins/xep_0091/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0091/legacy_delay.py29
-rw-r--r--sleekxmpp/plugins/xep_0091/stanza.py47
-rw-r--r--sleekxmpp/plugins/xep_0092/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0092/stanza.py42
-rw-r--r--sleekxmpp/plugins/xep_0092/version.py85
-rw-r--r--sleekxmpp/plugins/xep_0095/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0095/stanza.py25
-rw-r--r--sleekxmpp/plugins/xep_0095/stream_initiation.py214
-rw-r--r--sleekxmpp/plugins/xep_0096/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0096/file_transfer.py59
-rw-r--r--sleekxmpp/plugins/xep_0096/stanza.py48
-rw-r--r--sleekxmpp/plugins/xep_0106.py26
-rw-r--r--sleekxmpp/plugins/xep_0107/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0107/stanza.py55
-rw-r--r--sleekxmpp/plugins/xep_0107/user_mood.py93
-rw-r--r--sleekxmpp/plugins/xep_0108/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0108/stanza.py83
-rw-r--r--sleekxmpp/plugins/xep_0108/user_activity.py88
-rw-r--r--sleekxmpp/plugins/xep_0115/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0115/caps.py345
-rw-r--r--sleekxmpp/plugins/xep_0115/stanza.py19
-rw-r--r--sleekxmpp/plugins/xep_0115/static.py146
-rw-r--r--sleekxmpp/plugins/xep_0118/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0118/stanza.py25
-rw-r--r--sleekxmpp/plugins/xep_0118/user_tune.py96
-rw-r--r--sleekxmpp/plugins/xep_0122/__init__.py11
-rw-r--r--sleekxmpp/plugins/xep_0122/data_validation.py19
-rw-r--r--sleekxmpp/plugins/xep_0122/stanza.py94
-rw-r--r--sleekxmpp/plugins/xep_0128/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0128/extended_disco.py99
-rw-r--r--sleekxmpp/plugins/xep_0128/static.py73
-rw-r--r--sleekxmpp/plugins/xep_0131/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0131/headers.py41
-rw-r--r--sleekxmpp/plugins/xep_0131/stanza.py51
-rw-r--r--sleekxmpp/plugins/xep_0133.py54
-rw-r--r--sleekxmpp/plugins/xep_0138.py148
-rw-r--r--sleekxmpp/plugins/xep_0152/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0152/reachability.py93
-rw-r--r--sleekxmpp/plugins/xep_0152/stanza.py29
-rw-r--r--sleekxmpp/plugins/xep_0153/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0153/stanza.py29
-rw-r--r--sleekxmpp/plugins/xep_0153/vcard_avatar.py152
-rw-r--r--sleekxmpp/plugins/xep_0163.py123
-rw-r--r--sleekxmpp/plugins/xep_0172/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0172/stanza.py67
-rw-r--r--sleekxmpp/plugins/xep_0172/user_nick.py92
-rw-r--r--sleekxmpp/plugins/xep_0184/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0184/receipt.py131
-rw-r--r--sleekxmpp/plugins/xep_0184/stanza.py72
-rw-r--r--sleekxmpp/plugins/xep_0186/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0186/invisible_command.py44
-rw-r--r--sleekxmpp/plugins/xep_0186/stanza.py23
-rw-r--r--sleekxmpp/plugins/xep_0191/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0191/blocking.py83
-rw-r--r--sleekxmpp/plugins/xep_0191/stanza.py50
-rw-r--r--sleekxmpp/plugins/xep_0196/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0196/stanza.py20
-rw-r--r--sleekxmpp/plugins/xep_0196/user_gaming.py97
-rw-r--r--sleekxmpp/plugins/xep_0198/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0198/stanza.py150
-rw-r--r--sleekxmpp/plugins/xep_0198/stream_management.py314
-rw-r--r--sleekxmpp/plugins/xep_0199/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0199/ping.py186
-rw-r--r--sleekxmpp/plugins/xep_0199/stanza.py36
-rw-r--r--sleekxmpp/plugins/xep_0202/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0202/stanza.py127
-rw-r--r--sleekxmpp/plugins/xep_0202/time.py99
-rw-r--r--sleekxmpp/plugins/xep_0203/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0203/delay.py37
-rw-r--r--sleekxmpp/plugins/xep_0203/stanza.py46
-rw-r--r--sleekxmpp/plugins/xep_0221/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0221/media.py27
-rw-r--r--sleekxmpp/plugins/xep_0221/stanza.py42
-rw-r--r--sleekxmpp/plugins/xep_0222.py127
-rw-r--r--sleekxmpp/plugins/xep_0223.py127
-rw-r--r--sleekxmpp/plugins/xep_0224/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0224/attention.py75
-rw-r--r--sleekxmpp/plugins/xep_0224/stanza.py40
-rw-r--r--sleekxmpp/plugins/xep_0231/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0231/bob.py140
-rw-r--r--sleekxmpp/plugins/xep_0231/stanza.py36
-rw-r--r--sleekxmpp/plugins/xep_0235/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0235/oauth.py32
-rw-r--r--sleekxmpp/plugins/xep_0235/stanza.py80
-rw-r--r--sleekxmpp/plugins/xep_0242.py21
-rw-r--r--sleekxmpp/plugins/xep_0249/__init__.py19
-rw-r--r--sleekxmpp/plugins/xep_0249/invite.py83
-rw-r--r--sleekxmpp/plugins/xep_0249/stanza.py39
-rw-r--r--sleekxmpp/plugins/xep_0256.py73
-rw-r--r--sleekxmpp/plugins/xep_0257/__init__.py17
-rw-r--r--sleekxmpp/plugins/xep_0257/client_cert_management.py65
-rw-r--r--sleekxmpp/plugins/xep_0257/stanza.py87
-rw-r--r--sleekxmpp/plugins/xep_0258/__init__.py18
-rw-r--r--sleekxmpp/plugins/xep_0258/security_labels.py44
-rw-r--r--sleekxmpp/plugins/xep_0258/stanza.py141
-rw-r--r--sleekxmpp/plugins/xep_0270.py20
-rw-r--r--sleekxmpp/plugins/xep_0279/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0279/ipcheck.py39
-rw-r--r--sleekxmpp/plugins/xep_0279/stanza.py30
-rw-r--r--sleekxmpp/plugins/xep_0280/__init__.py17
-rw-r--r--sleekxmpp/plugins/xep_0280/carbons.py81
-rw-r--r--sleekxmpp/plugins/xep_0280/stanza.py64
-rw-r--r--sleekxmpp/plugins/xep_0297/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0297/forwarded.py64
-rw-r--r--sleekxmpp/plugins/xep_0297/stanza.py36
-rw-r--r--sleekxmpp/plugins/xep_0302.py21
-rw-r--r--sleekxmpp/plugins/xep_0308/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0308/correction.py52
-rw-r--r--sleekxmpp/plugins/xep_0308/stanza.py16
-rw-r--r--sleekxmpp/plugins/xep_0313/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0313/mam.py94
-rw-r--r--sleekxmpp/plugins/xep_0313/stanza.py139
-rw-r--r--sleekxmpp/plugins/xep_0319/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0319/idle.py75
-rw-r--r--sleekxmpp/plugins/xep_0319/stanza.py28
-rw-r--r--sleekxmpp/plugins/xep_0323/__init__.py18
-rw-r--r--sleekxmpp/plugins/xep_0323/device.py258
-rw-r--r--sleekxmpp/plugins/xep_0323/sensordata.py712
-rw-r--r--sleekxmpp/plugins/xep_0323/stanza/__init__.py12
-rw-r--r--sleekxmpp/plugins/xep_0323/stanza/base.py13
-rw-r--r--sleekxmpp/plugins/xep_0323/stanza/sensordata.py792
-rw-r--r--sleekxmpp/plugins/xep_0323/timerreset.py69
-rw-r--r--sleekxmpp/plugins/xep_0325/__init__.py18
-rw-r--r--sleekxmpp/plugins/xep_0325/control.py569
-rw-r--r--sleekxmpp/plugins/xep_0325/device.py125
-rw-r--r--sleekxmpp/plugins/xep_0325/stanza/__init__.py12
-rw-r--r--sleekxmpp/plugins/xep_0325/stanza/base.py13
-rw-r--r--sleekxmpp/plugins/xep_0325/stanza/control.py527
-rw-r--r--sleekxmpp/plugins/xep_0332/__init__.py17
-rw-r--r--sleekxmpp/plugins/xep_0332/http.py159
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/__init__.py13
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/data.py30
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/request.py71
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/response.py66
-rw-r--r--sleekxmpp/roster/__init__.py11
-rw-r--r--sleekxmpp/roster/item.py497
-rw-r--r--sleekxmpp/roster/multi.py224
-rw-r--r--sleekxmpp/roster/single.py339
-rw-r--r--sleekxmpp/stanza/__init__.py15
-rw-r--r--sleekxmpp/stanza/atom.py43
-rw-r--r--sleekxmpp/stanza/error.py174
-rw-r--r--sleekxmpp/stanza/htmlim.py21
-rw-r--r--sleekxmpp/stanza/iq.py281
-rw-r--r--sleekxmpp/stanza/message.py199
-rw-r--r--sleekxmpp/stanza/nick.py23
-rw-r--r--sleekxmpp/stanza/presence.py191
-rw-r--r--sleekxmpp/stanza/rootstanza.py90
-rw-r--r--sleekxmpp/stanza/roster.py158
-rw-r--r--sleekxmpp/stanza/stream_error.py83
-rw-r--r--sleekxmpp/stanza/stream_features.py57
-rw-r--r--sleekxmpp/test/__init__.py11
-rw-r--r--sleekxmpp/test/livesocket.py172
-rw-r--r--sleekxmpp/test/mocksocket.py153
-rw-r--r--sleekxmpp/test/sleektest.py774
-rw-r--r--sleekxmpp/thirdparty/__init__.py13
-rw-r--r--sleekxmpp/thirdparty/gnupg.py1017
-rw-r--r--sleekxmpp/thirdparty/mini_dateutil.py273
-rw-r--r--sleekxmpp/thirdparty/ordereddict.py127
-rw-r--r--sleekxmpp/thirdparty/orderedset.py89
-rw-r--r--sleekxmpp/thirdparty/socks.py387
-rw-r--r--sleekxmpp/thirdparty/statemachine.py294
-rw-r--r--sleekxmpp/util/__init__.py48
-rw-r--r--sleekxmpp/util/misc_ops.py165
-rw-r--r--sleekxmpp/util/sasl/__init__.py17
-rw-r--r--sleekxmpp/util/sasl/client.py174
-rw-r--r--sleekxmpp/util/sasl/mechanisms.py550
-rw-r--r--sleekxmpp/util/stringprep_profiles.py151
-rw-r--r--sleekxmpp/version.py13
-rw-r--r--sleekxmpp/xmlstream/__init__.py19
-rw-r--r--sleekxmpp/xmlstream/cert.py184
-rw-r--r--sleekxmpp/xmlstream/filesocket.py54
-rw-r--r--sleekxmpp/xmlstream/handler/__init__.py15
-rw-r--r--sleekxmpp/xmlstream/handler/base.py84
-rw-r--r--sleekxmpp/xmlstream/handler/callback.py79
-rw-r--r--sleekxmpp/xmlstream/handler/collector.py66
-rw-r--r--sleekxmpp/xmlstream/handler/waiter.py83
-rw-r--r--sleekxmpp/xmlstream/handler/xmlcallback.py36
-rw-r--r--sleekxmpp/xmlstream/handler/xmlwaiter.py33
-rw-r--r--sleekxmpp/xmlstream/jid.py5
-rw-r--r--sleekxmpp/xmlstream/matcher/__init__.py17
-rw-r--r--sleekxmpp/xmlstream/matcher/base.py31
-rw-r--r--sleekxmpp/xmlstream/matcher/id.py29
-rw-r--r--sleekxmpp/xmlstream/matcher/idsender.py47
-rw-r--r--sleekxmpp/xmlstream/matcher/many.py40
-rw-r--r--sleekxmpp/xmlstream/matcher/stanzapath.py43
-rw-r--r--sleekxmpp/xmlstream/matcher/xmlmask.py117
-rw-r--r--sleekxmpp/xmlstream/matcher/xpath.py59
-rw-r--r--sleekxmpp/xmlstream/resolver.py333
-rw-r--r--sleekxmpp/xmlstream/scheduler.py250
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py1654
-rw-r--r--sleekxmpp/xmlstream/tostring.py172
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py1817
336 files changed, 0 insertions, 38432 deletions
diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py
deleted file mode 100644
index 85ee32b6..00000000
--- a/sleekxmpp/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-if hasattr(logging, 'NullHandler'):
- NullHandler = logging.NullHandler
-else:
- class NullHandler(logging.Handler):
- def handle(self, record):
- pass
-logging.getLogger(__name__).addHandler(NullHandler())
-del NullHandler
-
-
-from sleekxmpp.stanza import Message, Presence, Iq
-from sleekxmpp.jid import JID, InvalidJID
-from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import *
-from sleekxmpp.xmlstream import XMLStream, RestartStream
-from sleekxmpp.xmlstream.matcher import *
-from sleekxmpp.basexmpp import BaseXMPP
-from sleekxmpp.clientxmpp import ClientXMPP
-from sleekxmpp.componentxmpp import ComponentXMPP
-
-from sleekxmpp.version import __version__, __version_info__
diff --git a/sleekxmpp/api.py b/sleekxmpp/api.py
deleted file mode 100644
index 8de61b34..00000000
--- a/sleekxmpp/api.py
+++ /dev/null
@@ -1,200 +0,0 @@
-from sleekxmpp.xmlstream import JID
-
-
-class APIWrapper(object):
-
- def __init__(self, api, name):
- self.api = api
- self.name = name
- if name not in self.api.settings:
- self.api.settings[name] = {}
-
- def __getattr__(self, attr):
- """Curry API management commands with the API name."""
- if attr == 'name':
- return self.name
- elif attr == 'settings':
- return self.api.settings[self.name]
- elif attr == 'register':
- def partial(handler, op, jid=None, node=None, default=False):
- register = getattr(self.api, attr)
- return register(handler, self.name, op, jid, node, default)
- return partial
- elif attr == 'register_default':
- def partial(handler, op, jid=None, node=None):
- return getattr(self.api, attr)(handler, self.name, op)
- return partial
- elif attr in ('run', 'restore_default', 'unregister'):
- def partial(*args, **kwargs):
- return getattr(self.api, attr)(self.name, *args, **kwargs)
- return partial
- return None
-
- def __getitem__(self, attr):
- def partial(jid=None, node=None, ifrom=None, args=None):
- return self.api.run(self.name, attr, jid, node, ifrom, args)
- return partial
-
-
-class APIRegistry(object):
-
- def __init__(self, xmpp):
- self._handlers = {}
- self._handler_defaults = {}
- self.xmpp = xmpp
- self.settings = {}
-
- def _setup(self, ctype, op):
- """Initialize the API callback dictionaries.
-
- :param string ctype: The name of the API to initialize.
- :param string op: The API operation to initialize.
- """
- if ctype not in self.settings:
- self.settings[ctype] = {}
- if ctype not in self._handler_defaults:
- self._handler_defaults[ctype] = {}
- if ctype not in self._handlers:
- self._handlers[ctype] = {}
- if op not in self._handlers[ctype]:
- self._handlers[ctype][op] = {'global': None,
- 'jid': {},
- 'node': {}}
-
- def wrap(self, ctype):
- """Return a wrapper object that targets a specific API."""
- return APIWrapper(self, ctype)
-
- def purge(self, ctype):
- """Remove all information for a given API."""
- del self.settings[ctype]
- del self._handler_defaults[ctype]
- del self._handlers[ctype]
-
- def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
- """Execute an API callback, based on specificity.
-
- The API callback that is executed is chosen based on the combination
- of the provided JID and node:
-
- JID | node | Handler
- ==============================
- Given | Given | Node handler
- Given | None | JID handler
- None | None | Global handler
-
- A node handler is responsible for servicing a single node at a single
- JID, while a JID handler may respond for any node at a given JID, and
- the global handler will answer to any JID+node combination.
-
- Handlers should check that the JID ``ifrom`` is authorized to perform
- the desired action.
-
- :param string ctype: The name of the API to use.
- :param string op: The API operation to perform.
- :param JID jid: Optionally provide specific JID.
- :param string node: Optionally provide specific node.
- :param JID ifrom: Optionally provide the requesting JID.
- :param tuple args: Optional positional arguments to the handler.
- """
- self._setup(ctype, op)
-
- if not jid:
- jid = self.xmpp.boundjid
- elif jid and not isinstance(jid, JID):
- jid = JID(jid)
- elif jid == JID(''):
- jid = self.xmpp.boundjid
-
- if node is None:
- node = ''
-
- if self.xmpp.is_component:
- if self.settings[ctype].get('component_bare', False):
- jid = jid.bare
- else:
- jid = jid.full
- else:
- if self.settings[ctype].get('client_bare', False):
- jid = jid.bare
- else:
- jid = jid.full
-
- jid = JID(jid)
-
- handler = self._handlers[ctype][op]['node'].get((jid, node), None)
- if handler is None:
- handler = self._handlers[ctype][op]['jid'].get(jid, None)
- if handler is None:
- handler = self._handlers[ctype][op].get('global', None)
-
- if handler:
- try:
- return handler(jid, node, ifrom, args)
- except TypeError:
- # To preserve backward compatibility, drop the ifrom
- # parameter for existing handlers that don't understand it.
- return handler(jid, node, args)
-
- def register(self, handler, ctype, op, jid=None, node=None, default=False):
- """Register an API callback, with JID+node specificity.
-
- The API callback can later be executed based on the
- specificity of the provided JID+node combination.
-
- See :meth:`~ApiRegistry.run` for more details.
-
- :param string ctype: The name of the API to use.
- :param string op: The API operation to perform.
- :param JID jid: Optionally provide specific JID.
- :param string node: Optionally provide specific node.
- """
- self._setup(ctype, op)
- if jid is None and node is None:
- if handler is None:
- handler = self._handler_defaults[op]
- self._handlers[ctype][op]['global'] = handler
- elif jid is not None and node is None:
- self._handlers[ctype][op]['jid'][jid] = handler
- else:
- self._handlers[ctype][op]['node'][(jid, node)] = handler
-
- if default:
- self.register_default(handler, ctype, op)
-
- def register_default(self, handler, ctype, op):
- """Register a default, global handler for an operation.
-
- :param func handler: The default, global handler for the operation.
- :param string ctype: The name of the API to modify.
- :param string op: The API operation to use.
- """
- self._setup(ctype, op)
- self._handler_defaults[ctype][op] = handler
-
- def unregister(self, ctype, op, jid=None, node=None):
- """Remove an API callback.
-
- The API callback chosen for removal is based on the
- specificity of the provided JID+node combination.
-
- See :meth:`~ApiRegistry.run` for more details.
-
- :param string ctype: The name of the API to use.
- :param string op: The API operation to perform.
- :param JID jid: Optionally provide specific JID.
- :param string node: Optionally provide specific node.
- """
- self._setup(ctype, op)
- self.register(None, ctype, op, jid, node)
-
- def restore_default(self, ctype, op, jid=None, node=None):
- """Reset an API callback to use a default handler.
-
- :param string ctype: The name of the API to use.
- :param string op: The API operation to perform.
- :param JID jid: Optionally provide specific JID.
- :param string node: Optionally provide specific node.
- """
- self.unregister(ctype, op, jid, node)
- self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
deleted file mode 100644
index cb72b9bd..00000000
--- a/sleekxmpp/basexmpp.py
+++ /dev/null
@@ -1,831 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.basexmpp
- ~~~~~~~~~~~~~~~~~~
-
- This module provides the common XMPP functionality
- for both clients and components.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import with_statement, unicode_literals
-
-import sys
-import logging
-import threading
-
-from sleekxmpp import plugins, roster, stanza
-from sleekxmpp.api import APIRegistry
-from sleekxmpp.exceptions import IqError, IqTimeout
-
-from sleekxmpp.stanza import Message, Presence, Iq, StreamError
-from sleekxmpp.stanza.roster import Roster
-from sleekxmpp.stanza.nick import Nick
-
-from sleekxmpp.xmlstream import XMLStream, JID
-from sleekxmpp.xmlstream import ET, register_stanza_plugin
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.stanzabase import XML_NS
-
-from sleekxmpp.plugins import PluginManager, load_plugin
-
-
-log = logging.getLogger(__name__)
-
-# In order to make sure that Unicode is handled properly
-# in Python 2.x, reset the default encoding.
-if sys.version_info < (3, 0):
- from sleekxmpp.util.misc_ops import setdefaultencoding
- setdefaultencoding('utf8')
-
-
-class BaseXMPP(XMLStream):
-
- """
- The BaseXMPP class adapts the generic XMLStream class for use
- with XMPP. It also provides a plugin mechanism to easily extend
- and add support for new XMPP features.
-
- :param default_ns: Ensure that the correct default XML namespace
- is used during initialization.
- """
-
- def __init__(self, jid='', default_ns='jabber:client', **kwargs):
- XMLStream.__init__(self, **kwargs)
-
- self.default_ns = default_ns
- self.stream_ns = 'http://etherx.jabber.org/streams'
- self.namespace_map[self.stream_ns] = 'stream'
-
- #: An identifier for the stream as given by the server.
- self.stream_id = None
-
- #: The JabberID (JID) requested for this connection.
- self.requested_jid = JID(jid, cache_lock=True)
-
- #: The JabberID (JID) used by this connection,
- #: as set after session binding. This may even be a
- #: different bare JID than what was requested.
- self.boundjid = JID(jid, cache_lock=True)
-
- self._expected_server_name = self.boundjid.host
- self._redirect_attempts = 0
-
- #: The maximum number of consecutive see-other-host
- #: redirections that will be followed before quitting.
- self.max_redirects = 5
-
- self.session_bind_event = threading.Event()
-
- #: A dictionary mapping plugin names to plugins.
- self.plugin = PluginManager(self)
-
- #: Configuration options for whitelisted plugins.
- #: If a plugin is registered without any configuration,
- #: and there is an entry here, it will be used.
- self.plugin_config = {}
-
- #: A list of plugins that will be loaded if
- #: :meth:`register_plugins` is called.
- self.plugin_whitelist = []
-
- #: The main roster object. This roster supports multiple
- #: owner JIDs, as in the case for components. For clients
- #: which only have a single JID, see :attr:`client_roster`.
- self.roster = roster.Roster(self)
- self.roster.add(self.boundjid)
-
- #: The single roster for the bound JID. This is the
- #: equivalent of::
- #:
- #: self.roster[self.boundjid.bare]
- self.client_roster = self.roster[self.boundjid]
-
- #: The distinction between clients and components can be
- #: important, primarily for choosing how to handle the
- #: ``'to'`` and ``'from'`` JIDs of stanzas.
- self.is_component = False
-
- #: Messages may optionally be tagged with ID values. Setting
- #: :attr:`use_message_ids` to `True` will assign all outgoing
- #: messages an ID. Some plugin features require enabling
- #: this option.
- self.use_message_ids = False
-
- #: Presence updates may optionally be tagged with ID values.
- #: Setting :attr:`use_message_ids` to `True` will assign all
- #: outgoing messages an ID.
- self.use_presence_ids = False
-
- #: The API registry is a way to process callbacks based on
- #: JID+node combinations. Each callback in the registry is
- #: marked with:
- #:
- #: - An API name, e.g. xep_0030
- #: - The name of an action, e.g. get_info
- #: - The JID that will be affected
- #: - The node that will be affected
- #:
- #: API handlers with no JID or node will act as global handlers,
- #: while those with a JID and no node will service all nodes
- #: for a JID, and handlers with both a JID and node will be
- #: used only for that specific combination. The handler that
- #: provides the most specificity will be used.
- self.api = APIRegistry(self)
-
- #: Flag indicating that the initial presence broadcast has
- #: been sent. Until this happens, some servers may not
- #: behave as expected when sending stanzas.
- self.sentpresence = False
-
- #: A reference to :mod:`sleekxmpp.stanza` to make accessing
- #: stanza classes easier.
- self.stanza = stanza
-
- self.register_handler(
- Callback('IM',
- MatchXPath('{%s}message/{%s}body' % (self.default_ns,
- self.default_ns)),
- self._handle_message))
- self.register_handler(
- Callback('Presence',
- MatchXPath("{%s}presence" % self.default_ns),
- self._handle_presence))
-
- self.register_handler(
- Callback('Stream Error',
- MatchXPath("{%s}error" % self.stream_ns),
- self._handle_stream_error))
-
- self.add_event_handler('session_start',
- self._handle_session_start)
- self.add_event_handler('disconnected',
- self._handle_disconnected)
- self.add_event_handler('presence_available',
- self._handle_available)
- self.add_event_handler('presence_dnd',
- self._handle_available)
- self.add_event_handler('presence_xa',
- self._handle_available)
- self.add_event_handler('presence_chat',
- self._handle_available)
- self.add_event_handler('presence_away',
- self._handle_available)
- self.add_event_handler('presence_unavailable',
- self._handle_unavailable)
- self.add_event_handler('presence_subscribe',
- self._handle_subscribe)
- self.add_event_handler('presence_subscribed',
- self._handle_subscribed)
- self.add_event_handler('presence_unsubscribe',
- self._handle_unsubscribe)
- self.add_event_handler('presence_unsubscribed',
- self._handle_unsubscribed)
- self.add_event_handler('roster_subscription_request',
- self._handle_new_subscription)
-
- # Set up the XML stream with XMPP's root stanzas.
- self.register_stanza(Message)
- self.register_stanza(Iq)
- self.register_stanza(Presence)
- self.register_stanza(StreamError)
-
- # Initialize a few default stanza plugins.
- register_stanza_plugin(Iq, Roster)
- register_stanza_plugin(Message, Nick)
-
- def start_stream_handler(self, xml):
- """Save the stream ID once the streams have been established.
-
- :param xml: The incoming stream's root element.
- """
- self.stream_id = xml.get('id', '')
- self.stream_version = xml.get('version', '')
- self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
-
- if not self.is_component and not self.stream_version:
- log.warning('Legacy XMPP 0.9 protocol detected.')
- self.event('legacy_protocol')
-
- def process(self, *args, **kwargs):
- """Initialize plugins and begin processing the XML stream.
-
- The number of threads used for processing stream events is determined
- by :data:`HANDLER_THREADS`.
-
- :param bool block: If ``False``, then event dispatcher will run
- in a separate thread, allowing for the stream to be
- used in the background for another application.
- Otherwise, ``process(block=True)`` blocks the current
- thread. Defaults to ``False``.
- :param bool threaded: **DEPRECATED**
- If ``True``, then event dispatcher will run
- in a separate thread, allowing for the stream to be
- used in the background for another application.
- Defaults to ``True``. This does **not** mean that no
- threads are used at all if ``threaded=False``.
-
- Regardless of these threading options, these threads will
- always exist:
-
- - The event queue processor
- - The send queue processor
- - The scheduler
- """
- for name in self.plugin:
- if not hasattr(self.plugin[name], 'post_inited'):
- if hasattr(self.plugin[name], 'post_init'):
- self.plugin[name].post_init()
- self.plugin[name].post_inited = True
- return XMLStream.process(self, *args, **kwargs)
-
- def register_plugin(self, plugin, pconfig=None, module=None):
- """Register and configure a plugin for use in this stream.
-
- :param plugin: The name of the plugin class. Plugin names must
- be unique.
- :param pconfig: A dictionary of configuration data for the plugin.
- Defaults to an empty dictionary.
- :param module: Optional refence to the module containing the plugin
- class if using custom plugins.
- """
-
- # Use the global plugin config cache, if applicable
- if not pconfig:
- pconfig = self.plugin_config.get(plugin, {})
-
- if not self.plugin.registered(plugin):
- load_plugin(plugin, module)
- self.plugin.enable(plugin, pconfig)
-
- def register_plugins(self):
- """Register and initialize all built-in plugins.
-
- Optionally, the list of plugins loaded may be limited to those
- contained in :attr:`plugin_whitelist`.
-
- Plugin configurations stored in :attr:`plugin_config` will be used.
- """
- if self.plugin_whitelist:
- plugin_list = self.plugin_whitelist
- else:
- plugin_list = plugins.__all__
-
- for plugin in plugin_list:
- if plugin in plugins.__all__:
- self.register_plugin(plugin)
- else:
- raise NameError("Plugin %s not in plugins.__all__." % plugin)
-
- def __getitem__(self, key):
- """Return a plugin given its name, if it has been registered."""
- if key in self.plugin:
- return self.plugin[key]
- else:
- log.warning("Plugin '%s' is not loaded.", key)
- return False
-
- def get(self, key, default):
- """Return a plugin given its name, if it has been registered."""
- return self.plugin.get(key, default)
-
- def Message(self, *args, **kwargs):
- """Create a Message stanza associated with this stream."""
- msg = Message(self, *args, **kwargs)
- msg['lang'] = self.default_lang
- return msg
-
- def Iq(self, *args, **kwargs):
- """Create an Iq stanza associated with this stream."""
- return Iq(self, *args, **kwargs)
-
- def Presence(self, *args, **kwargs):
- """Create a Presence stanza associated with this stream."""
- pres = Presence(self, *args, **kwargs)
- pres['lang'] = self.default_lang
- return pres
-
- def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
- """Create a new Iq stanza with a given Id and from JID.
-
- :param id: An ideally unique ID value for this stanza thread.
- Defaults to 0.
- :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
- one of: ``'get'``, ``'set'``, ``'result'``,
- or ``'error'``.
- :param iquery: Optional namespace for adding a query element.
- """
- iq = self.Iq()
- iq['id'] = str(id)
- iq['to'] = ito
- iq['from'] = ifrom
- iq['type'] = itype
- iq['query'] = iquery
- return iq
-
- def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
- """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.
-
- Optionally, a query element may be added.
-
- :param queryxmlns: The namespace of the query to use.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- """
- if not iq:
- iq = self.Iq()
- iq['type'] = 'get'
- iq['query'] = queryxmlns
- if ito:
- iq['to'] = ito
- if ifrom:
- iq['from'] = ifrom
- return iq
-
- def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
- """
- Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
- ``'result'`` with the given ID value.
-
- :param id: An ideally unique ID value. May use :meth:`new_id()`.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- """
- if not iq:
- iq = self.Iq()
- if id is None:
- id = self.new_id()
- iq['id'] = id
- iq['type'] = 'result'
- if ito:
- iq['to'] = ito
- if ifrom:
- iq['from'] = ifrom
- return iq
-
- def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
- """
- Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.
-
- Optionally, a substanza may be given to use as the
- stanza's payload.
-
- :param sub: Either an
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza object or an
- :class:`~xml.etree.ElementTree.Element` XML object
- to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- """
- if not iq:
- iq = self.Iq()
- iq['type'] = 'set'
- if sub != None:
- iq.append(sub)
- if ito:
- iq['to'] = ito
- if ifrom:
- iq['from'] = ifrom
- return iq
-
- def make_iq_error(self, id, type='cancel',
- condition='feature-not-implemented',
- text=None, ito=None, ifrom=None, iq=None):
- """
- Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
-
- :param id: An ideally unique ID value. May use :meth:`new_id()`.
- :param type: The type of the error, such as ``'cancel'`` or
- ``'modify'``. Defaults to ``'cancel'``.
- :param condition: The error condition. Defaults to
- ``'feature-not-implemented'``.
- :param text: A message describing the cause of the error.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- """
- if not iq:
- iq = self.Iq()
- iq['id'] = id
- iq['error']['type'] = type
- iq['error']['condition'] = condition
- iq['error']['text'] = text
- if ito:
- iq['to'] = ito
- if ifrom:
- iq['from'] = ifrom
- return iq
-
- def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
- """
- Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
- to use the given query namespace.
-
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- :param xmlns: The query's namespace.
- :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
- for this stanza.
- :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
- to use for this stanza.
- """
- if not iq:
- iq = self.Iq()
- iq['query'] = xmlns
- if ito:
- iq['to'] = ito
- if ifrom:
- iq['from'] = ifrom
- return iq
-
- def make_query_roster(self, iq=None):
- """Create a roster query element.
-
- :param iq: Optionally use an existing stanza instead
- of generating a new one.
- """
- if iq:
- iq['query'] = 'jabber:iq:roster'
- return ET.Element("{jabber:iq:roster}query")
-
- def make_message(self, mto, mbody=None, msubject=None, mtype=None,
- mhtml=None, mfrom=None, mnick=None):
- """
- Create and initialize a new
- :class:`~sleekxmpp.stanza.message.Message` stanza.
-
- :param mto: The recipient of the message.
- :param mbody: The main contents of the message.
- :param msubject: Optional subject for the message.
- :param mtype: The message's type, such as ``'chat'`` or
- ``'groupchat'``.
- :param mhtml: Optional HTML body content in the form of a string.
- :param mfrom: The sender of the message. if sending from a client,
- be aware that some servers require that the full JID
- of the sender be used.
- :param mnick: Optional nickname of the sender.
- """
- message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
- message['body'] = mbody
- message['subject'] = msubject
- if mnick is not None:
- message['nick'] = mnick
- if mhtml is not None:
- message['html']['body'] = mhtml
- return message
-
- def make_presence(self, pshow=None, pstatus=None, ppriority=None,
- pto=None, ptype=None, pfrom=None, pnick=None):
- """
- Create and initialize a new
- :class:`~sleekxmpp.stanza.presence.Presence` stanza.
-
- :param pshow: The presence's show value.
- :param pstatus: The presence's status message.
- :param ppriority: This connection's priority.
- :param pto: The recipient of a directed presence.
- :param ptype: The type of presence, such as ``'subscribe'``.
- :param pfrom: The sender of the presence.
- :param pnick: Optional nickname of the presence's sender.
- """
- presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
- if pshow is not None:
- presence['type'] = pshow
- if pfrom is None and self.is_component:
- presence['from'] = self.boundjid.full
- presence['priority'] = ppriority
- presence['status'] = pstatus
- presence['nick'] = pnick
- return presence
-
- def send_message(self, mto, mbody, msubject=None, mtype=None,
- mhtml=None, mfrom=None, mnick=None):
- """
- Create, initialize, and send a new
- :class:`~sleekxmpp.stanza.message.Message` stanza.
-
- :param mto: The recipient of the message.
- :param mbody: The main contents of the message.
- :param msubject: Optional subject for the message.
- :param mtype: The message's type, such as ``'chat'`` or
- ``'groupchat'``.
- :param mhtml: Optional HTML body content in the form of a string.
- :param mfrom: The sender of the message. if sending from a client,
- be aware that some servers require that the full JID
- of the sender be used.
- :param mnick: Optional nickname of the sender.
- """
- self.make_message(mto, mbody, msubject, mtype,
- mhtml, mfrom, mnick).send()
-
- def send_presence(self, pshow=None, pstatus=None, ppriority=None,
- pto=None, pfrom=None, ptype=None, pnick=None):
- """
- Create, initialize, and send a new
- :class:`~sleekxmpp.stanza.presence.Presence` stanza.
-
- :param pshow: The presence's show value.
- :param pstatus: The presence's status message.
- :param ppriority: This connection's priority.
- :param pto: The recipient of a directed presence.
- :param ptype: The type of presence, such as ``'subscribe'``.
- :param pfrom: The sender of the presence.
- :param pnick: Optional nickname of the presence's sender.
- """
- self.make_presence(pshow, pstatus, ppriority, pto,
- ptype, pfrom, pnick).send()
-
- def send_presence_subscription(self, pto, pfrom=None,
- ptype='subscribe', pnick=None):
- """
- Create, initialize, and send a new
- :class:`~sleekxmpp.stanza.presence.Presence` stanza of
- type ``'subscribe'``.
-
- :param pto: The recipient of a directed presence.
- :param pfrom: The sender of the presence.
- :param ptype: The type of presence, such as ``'subscribe'``.
- :param pnick: Optional nickname of the presence's sender.
- """
- self.make_presence(ptype=ptype,
- pfrom=pfrom,
- pto=JID(pto).bare,
- pnick=pnick).send()
-
- @property
- def jid(self):
- """Attribute accessor for bare jid"""
- log.warning("jid property deprecated. Use boundjid.bare")
- return self.boundjid.bare
-
- @jid.setter
- def jid(self, value):
- log.warning("jid property deprecated. Use boundjid.bare")
- self.boundjid.bare = value
-
- @property
- def fulljid(self):
- """Attribute accessor for full jid"""
- log.warning("fulljid property deprecated. Use boundjid.full")
- return self.boundjid.full
-
- @fulljid.setter
- def fulljid(self, value):
- log.warning("fulljid property deprecated. Use boundjid.full")
- self.boundjid.full = value
-
- @property
- def resource(self):
- """Attribute accessor for jid resource"""
- log.warning("resource property deprecated. Use boundjid.resource")
- return self.boundjid.resource
-
- @resource.setter
- def resource(self, value):
- log.warning("fulljid property deprecated. Use boundjid.resource")
- self.boundjid.resource = value
-
- @property
- def username(self):
- """Attribute accessor for jid usernode"""
- log.warning("username property deprecated. Use boundjid.user")
- return self.boundjid.user
-
- @username.setter
- def username(self, value):
- log.warning("username property deprecated. Use boundjid.user")
- self.boundjid.user = value
-
- @property
- def server(self):
- """Attribute accessor for jid host"""
- log.warning("server property deprecated. Use boundjid.host")
- return self.boundjid.server
-
- @server.setter
- def server(self, value):
- log.warning("server property deprecated. Use boundjid.host")
- self.boundjid.server = value
-
- @property
- def auto_authorize(self):
- """Auto accept or deny subscription requests.
-
- If ``True``, auto accept subscription requests.
- If ``False``, auto deny subscription requests.
- If ``None``, don't automatically respond.
- """
- return self.roster.auto_authorize
-
- @auto_authorize.setter
- def auto_authorize(self, value):
- self.roster.auto_authorize = value
-
- @property
- def auto_subscribe(self):
- """Auto send requests for mutual subscriptions.
-
- If ``True``, auto send mutual subscription requests.
- """
- return self.roster.auto_subscribe
-
- @auto_subscribe.setter
- def auto_subscribe(self, value):
- self.roster.auto_subscribe = value
-
- def set_jid(self, jid):
- """Rip a JID apart and claim it as our own."""
- log.debug("setting jid to %s", jid)
- self.boundjid = JID(jid, cache_lock=True)
-
- def getjidresource(self, fulljid):
- if '/' in fulljid:
- return fulljid.split('/', 1)[-1]
- else:
- return ''
-
- def getjidbare(self, fulljid):
- return fulljid.split('/', 1)[0]
-
- def _handle_session_start(self, event):
- """Reset redirection attempt count."""
- self._redirect_attempts = 0
-
- def _handle_disconnected(self, event):
- """When disconnected, reset the roster"""
- self.roster.reset()
- self.session_bind_event.clear()
-
- def _handle_stream_error(self, error):
- self.event('stream_error', error)
-
- if error['condition'] == 'see-other-host':
- other_host = error['see_other_host']
- if not other_host:
- log.warning("No other host specified.")
- return
-
- if self._redirect_attempts > self.max_redirects:
- log.error("Exceeded maximum number of redirection attempts.")
- return
-
- self._redirect_attempts += 1
-
- host = other_host
- port = 5222
-
- if '[' in other_host and ']' in other_host:
- host = other_host.split(']')[0][1:]
- elif ':' in other_host:
- host = other_host.split(':')[0]
-
- port_sec = other_host.split(']')[-1]
- if ':' in port_sec:
- port = int(port_sec.split(':')[1])
-
- self.address = (host, port)
- self.default_domain = host
- self.dns_records = None
- self.reconnect_delay = None
- self.reconnect()
-
- def _handle_message(self, msg):
- """Process incoming message stanzas."""
- if not self.is_component and not msg['to'].bare:
- msg['to'] = self.boundjid
- self.event('message', msg)
-
- def _handle_available(self, pres):
- self.roster[pres['to']][pres['from']].handle_available(pres)
-
- def _handle_unavailable(self, pres):
- self.roster[pres['to']][pres['from']].handle_unavailable(pres)
-
- def _handle_new_subscription(self, pres):
- """Attempt to automatically handle subscription requests.
-
- Subscriptions will be approved if the request is from
- a whitelisted JID, of :attr:`auto_authorize` is True. They
- will be rejected if :attr:`auto_authorize` is False. Setting
- :attr:`auto_authorize` to ``None`` will disable automatic
- subscription handling (except for whitelisted JIDs).
-
- If a subscription is accepted, a request for a mutual
- subscription will be sent if :attr:`auto_subscribe` is ``True``.
- """
- roster = self.roster[pres['to']]
- item = self.roster[pres['to']][pres['from']]
- if item['whitelisted']:
- item.authorize()
- if roster.auto_subscribe:
- item.subscribe()
- elif roster.auto_authorize:
- item.authorize()
- if roster.auto_subscribe:
- item.subscribe()
- elif roster.auto_authorize == False:
- item.unauthorize()
-
- def _handle_removed_subscription(self, pres):
- self.roster[pres['to']][pres['from']].handle_unauthorize(pres)
-
- def _handle_subscribe(self, pres):
- self.roster[pres['to']][pres['from']].handle_subscribe(pres)
-
- def _handle_subscribed(self, pres):
- self.roster[pres['to']][pres['from']].handle_subscribed(pres)
-
- def _handle_unsubscribe(self, pres):
- self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)
-
- def _handle_unsubscribed(self, pres):
- self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)
-
- def _handle_presence(self, presence):
- """Process incoming presence stanzas.
-
- Update the roster with presence information.
- """
- if not self.is_component and not presence['to'].bare:
- presence['to'] = self.boundjid
-
- self.event('presence', presence)
- self.event('presence_%s' % presence['type'], presence)
-
- # Check for changes in subscription state.
- if presence['type'] in ('subscribe', 'subscribed',
- 'unsubscribe', 'unsubscribed'):
- self.event('changed_subscription', presence)
- return
- elif not presence['type'] in ('available', 'unavailable') and \
- not presence['type'] in presence.showtypes:
- return
-
- def exception(self, exception):
- """Process any uncaught exceptions, notably
- :class:`~sleekxmpp.exceptions.IqError` and
- :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
-
- :param exception: An unhandled :class:`Exception` object.
- """
- if isinstance(exception, IqError):
- iq = exception.iq
- log.error('%s: %s', iq['error']['condition'],
- iq['error']['text'])
- log.warning('You should catch IqError exceptions')
- elif isinstance(exception, IqTimeout):
- iq = exception.iq
- log.error('Request timed out: %s', iq)
- log.warning('You should catch IqTimeout exceptions')
- elif isinstance(exception, SyntaxError):
- # Hide stream parsing errors that occur when the
- # stream is disconnected (they've been handled, we
- # don't need to make a mess in the logs).
- pass
- else:
- log.exception(exception)
-
-
-# Restore the old, lowercased name for backwards compatibility.
-basexmpp = BaseXMPP
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-BaseXMPP.registerPlugin = BaseXMPP.register_plugin
-BaseXMPP.makeIq = BaseXMPP.make_iq
-BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
-BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
-BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
-BaseXMPP.makeIqError = BaseXMPP.make_iq_error
-BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
-BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
-BaseXMPP.makeMessage = BaseXMPP.make_message
-BaseXMPP.makePresence = BaseXMPP.make_presence
-BaseXMPP.sendMessage = BaseXMPP.send_message
-BaseXMPP.sendPresence = BaseXMPP.send_presence
-BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription
diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
deleted file mode 100644
index 31a5a70b..00000000
--- a/sleekxmpp/clientxmpp.py
+++ /dev/null
@@ -1,338 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.clientxmpp
- ~~~~~~~~~~~~~~~~~~~~
-
- This module provides XMPP functionality that
- is specific to client connections.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import absolute_import, unicode_literals
-
-import logging
-
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.basexmpp import BaseXMPP
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import XMLStream
-from sleekxmpp.xmlstream.matcher import StanzaPath, MatchXPath
-from sleekxmpp.xmlstream.handler import Callback
-
-# Flag indicating if DNS SRV records are available for use.
-try:
- import dns.resolver
-except ImportError:
- DNSPYTHON = False
-else:
- DNSPYTHON = True
-
-
-log = logging.getLogger(__name__)
-
-
-class ClientXMPP(BaseXMPP):
-
- """
- SleekXMPP's client class. (Use only for good, not for evil.)
-
- Typical use pattern:
-
- .. code-block:: python
-
- xmpp = ClientXMPP('user@server.tld/resource', 'password')
- # ... Register plugins and event handlers ...
- xmpp.connect()
- xmpp.process(block=False) # block=True will block the current
- # thread. By default, block=False
-
- :param jid: The JID of the XMPP user account.
- :param password: The password for the XMPP user account.
- :param plugin_config: A dictionary of plugin configurations.
- :param plugin_whitelist: A list of approved plugins that
- will be loaded when calling
- :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
- :param escape_quotes: **Deprecated.**
- """
-
- def __init__(self, jid, password, plugin_config=None,
- plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
- lang='en', **kwargs):
- if not plugin_whitelist:
- plugin_whitelist = []
- if not plugin_config:
- plugin_config = {}
-
- BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
-
- self.escape_quotes = escape_quotes
- self.plugin_config = plugin_config
- self.plugin_whitelist = plugin_whitelist
- self.default_port = 5222
- self.default_lang = lang
-
- self.credentials = {}
-
- self.password = password
-
- self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
- self.boundjid.host,
- "xmlns:stream='%s'" % self.stream_ns,
- "xmlns='%s'" % self.default_ns,
- "xml:lang='%s'" % self.default_lang,
- "version='1.0'")
- self.stream_footer = "</stream:stream>"
-
- self.features = set()
- self._stream_feature_handlers = {}
- self._stream_feature_order = []
-
- self.dns_service = 'xmpp-client'
-
- #TODO: Use stream state here
- self.authenticated = False
- self.sessionstarted = False
- self.bound = False
- self.bindfail = False
-
- self.add_event_handler('connected', self._reset_connection_state)
- self.add_event_handler('session_bind', self._handle_session_bind)
- self.add_event_handler('roster_update', self._handle_roster)
-
- self.register_stanza(StreamFeatures)
-
- self.register_handler(
- Callback('Stream Features',
- MatchXPath('{%s}features' % self.stream_ns),
- self._handle_stream_features))
- self.register_handler(
- Callback('Roster Update',
- StanzaPath('iq@type=set/roster'),
- lambda iq: self.event('roster_update', iq)))
-
- # Setup default stream features
- self.register_plugin('feature_starttls')
- self.register_plugin('feature_bind')
- self.register_plugin('feature_session')
- self.register_plugin('feature_rosterver')
- self.register_plugin('feature_preapproval')
- self.register_plugin('feature_mechanisms')
-
- if sasl_mech:
- self['feature_mechanisms'].use_mech = sasl_mech
-
- @property
- def password(self):
- return self.credentials.get('password', '')
-
- @password.setter
- def password(self, value):
- self.credentials['password'] = value
-
- def connect(self, address=tuple(), reattempt=True,
- use_tls=True, use_ssl=False):
- """Connect to the XMPP server.
-
- When no address is given, a SRV lookup for the server will
- be attempted. If that fails, the server user in the JID
- will be used.
-
- :param address: A tuple containing the server's host and port.
- :param reattempt: If ``True``, repeat attempting to connect if an
- error occurs. Defaults to ``True``.
- :param use_tls: Indicates if TLS should be used for the
- connection. Defaults to ``True``.
- :param use_ssl: Indicates if the older SSL connection method
- should be used. Defaults to ``False``.
- """
- self.session_started_event.clear()
-
- # If an address was provided, disable using DNS SRV lookup;
- # otherwise, use the domain from the client JID with the standard
- # XMPP client port and allow SRV lookup.
- if address:
- self.dns_service = None
- else:
- address = (self.boundjid.host, 5222)
- self.dns_service = 'xmpp-client'
-
- return XMLStream.connect(self, address[0], address[1],
- use_tls=use_tls, use_ssl=use_ssl,
- reattempt=reattempt)
-
- def register_feature(self, name, handler, restart=False, order=5000):
- """Register a stream feature handler.
-
- :param name: The name of the stream feature.
- :param handler: The function to execute if the feature is received.
- :param restart: Indicates if feature processing should halt with
- this feature. Defaults to ``False``.
- :param order: The relative ordering in which the feature should
- be negotiated. Lower values will be attempted
- earlier when available.
- """
- self._stream_feature_handlers[name] = (handler, restart)
- self._stream_feature_order.append((order, name))
- self._stream_feature_order.sort()
-
- def unregister_feature(self, name, order):
- if name in self._stream_feature_handlers:
- del self._stream_feature_handlers[name]
- self._stream_feature_order.remove((order, name))
- self._stream_feature_order.sort()
-
- def update_roster(self, jid, **kwargs):
- """Add or change a roster item.
-
- :param jid: The JID of the entry to modify.
- :param name: The user's nickname for this JID.
- :param subscription: The subscription status. May be one of
- ``'to'``, ``'from'``, ``'both'``, or
- ``'none'``. If set to ``'remove'``,
- the entry will be deleted.
- :param groups: The roster groups that contain this item.
- :param block: Specify if the roster request will block
- until a response is received, or a timeout
- occurs. Defaults to ``True``.
- :param timeout: The length of time (in seconds) to wait
- for a response before continuing if blocking
- is used. Defaults to
- :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
- :param callback: Optional reference to a stream handler function.
- Will be executed when the roster is received.
- Implies ``block=False``.
- """
- current = self.client_roster[jid]
-
- name = kwargs.get('name', current['name'])
- subscription = kwargs.get('subscription', current['subscription'])
- groups = kwargs.get('groups', current['groups'])
-
- block = kwargs.get('block', True)
- timeout = kwargs.get('timeout', None)
- callback = kwargs.get('callback', None)
-
- return self.client_roster.update(jid, name, subscription, groups,
- block, timeout, callback)
-
- def del_roster_item(self, jid):
- """Remove an item from the roster.
-
- This is done by setting its subscription status to ``'remove'``.
-
- :param jid: The JID of the item to remove.
- """
- return self.client_roster.remove(jid)
-
- def get_roster(self, block=True, timeout=None, callback=None):
- """Request the roster from the server.
-
- :param block: Specify if the roster request will block until a
- response is received, or a timeout occurs.
- Defaults to ``True``.
- :param timeout: The length of time (in seconds) to wait for a response
- before continuing if blocking is used.
- Defaults to
- :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
- :param callback: Optional reference to a stream handler function. Will
- be executed when the roster is received.
- Implies ``block=False``.
- """
- iq = self.Iq()
- iq['type'] = 'get'
- iq.enable('roster')
- if 'rosterver' in self.features:
- iq['roster']['ver'] = self.client_roster.version
-
-
- if not block or callback is not None:
- block = False
- if callback is None:
- callback = lambda resp: self.event('roster_update', resp)
- else:
- orig_cb = callback
- def wrapped(resp):
- self.event('roster_update', resp)
- orig_cb(resp)
- callback = wrapped
-
- response = iq.send(block, timeout, callback)
-
- if block:
- self.event('roster_update', response)
- return response
-
- def _reset_connection_state(self, event=None):
- #TODO: Use stream state here
- self.authenticated = False
- self.sessionstarted = False
- self.bound = False
- self.bindfail = False
- self.features = set()
-
- def _handle_stream_features(self, features):
- """Process the received stream features.
-
- :param features: The features stanza.
- """
- for order, name in self._stream_feature_order:
- if name in features['features']:
- handler, restart = self._stream_feature_handlers[name]
- if handler(features) and restart:
- # Don't continue if the feature requires
- # restarting the XML stream.
- return True
- log.debug('Finished processing stream features.')
- self.event('stream_negotiated')
-
- def _handle_roster(self, iq):
- """Update the roster after receiving a roster stanza.
-
- :param iq: The roster stanza.
- """
- if iq['type'] == 'set':
- if iq['from'].bare and iq['from'].bare != self.boundjid.bare:
- raise XMPPError(condition='service-unavailable')
-
- roster = self.client_roster
- if iq['roster']['ver']:
- roster.version = iq['roster']['ver']
- items = iq['roster']['items']
-
- valid_subscriptions = ('to', 'from', 'both', 'none', 'remove')
- for jid, item in items.items():
- if item['subscription'] in valid_subscriptions:
- roster[jid]['name'] = item['name']
- roster[jid]['groups'] = item['groups']
- roster[jid]['from'] = item['subscription'] in ('from', 'both')
- roster[jid]['to'] = item['subscription'] in ('to', 'both')
- roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
-
- roster[jid].save(remove=(item['subscription'] == 'remove'))
-
- if iq['type'] == 'set':
- resp = self.Iq(stype='result',
- sto=iq['from'],
- sid=iq['id'])
- resp.enable('roster')
- resp.send()
-
- def _handle_session_bind(self, jid):
- """Set the client roster to the JID set by the server.
-
- :param :class:`sleekxmpp.xmlstream.jid.JID` jid: The bound JID as
- dictated by the server. The same as :attr:`boundjid`.
- """
- self.client_roster = self.roster[jid]
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-ClientXMPP.updateRoster = ClientXMPP.update_roster
-ClientXMPP.delRosterItem = ClientXMPP.del_roster_item
-ClientXMPP.getRoster = ClientXMPP.get_roster
-ClientXMPP.registerFeature = ClientXMPP.register_feature
diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py
deleted file mode 100644
index 4b229a6f..00000000
--- a/sleekxmpp/componentxmpp.py
+++ /dev/null
@@ -1,164 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.clientxmpp
- ~~~~~~~~~~~~~~~~~~~~
-
- This module provides XMPP functionality that
- is specific to external server component connections.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import absolute_import
-
-import logging
-import sys
-import hashlib
-
-from sleekxmpp.basexmpp import BaseXMPP
-from sleekxmpp.xmlstream import XMLStream
-from sleekxmpp.xmlstream import ET
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.xmlstream.handler import Callback
-
-
-log = logging.getLogger(__name__)
-
-
-class ComponentXMPP(BaseXMPP):
-
- """
- SleekXMPP's basic XMPP server component.
-
- Use only for good, not for evil.
-
- :param jid: The JID of the component.
- :param secret: The secret or password for the component.
- :param host: The server accepting the component.
- :param port: The port used to connect to the server.
- :param plugin_config: A dictionary of plugin configurations.
- :param plugin_whitelist: A list of approved plugins that
- will be loaded when calling
- :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
- :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
- should be used instead of the standard
- ``'jabber:component:accept'`` namespace.
- Defaults to ``False``.
- """
-
- def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
-
- if not plugin_whitelist:
- plugin_whitelist = []
- if not plugin_config:
- plugin_config = {}
-
- if use_jc_ns:
- default_ns = 'jabber:client'
- else:
- default_ns = 'jabber:component:accept'
- BaseXMPP.__init__(self, jid, default_ns)
-
- self.auto_authorize = None
- self.stream_header = "<stream:stream %s %s to='%s'>" % (
- 'xmlns="jabber:component:accept"',
- 'xmlns:stream="%s"' % self.stream_ns,
- jid)
- self.stream_footer = "</stream:stream>"
- self.server_host = host
- self.server_port = port
- self.secret = secret
-
- self.plugin_config = plugin_config
- self.plugin_whitelist = plugin_whitelist
- self.is_component = True
-
- self.register_handler(
- Callback('Handshake',
- MatchXPath('{jabber:component:accept}handshake'),
- self._handle_handshake))
- self.add_event_handler('presence_probe',
- self._handle_probe)
-
- def connect(self, host=None, port=None, use_ssl=False,
- use_tls=False, reattempt=True):
- """Connect to the server.
-
- Setting ``reattempt`` to ``True`` will cause connection attempts to
- be made every second until a successful connection is established.
-
- :param host: The name of the desired server for the connection.
- Defaults to :attr:`server_host`.
- :param port: Port to connect to on the server.
- Defauts to :attr:`server_port`.
- :param use_ssl: Flag indicating if SSL should be used by connecting
- directly to a port using SSL.
- :param use_tls: Flag indicating if TLS should be used, allowing for
- connecting to a port without using SSL immediately and
- later upgrading the connection.
- :param reattempt: Flag indicating if the socket should reconnect
- after disconnections.
- """
- if host is None:
- host = self.server_host
- if port is None:
- port = self.server_port
-
- self.server_name = self.boundjid.host
-
- if use_tls:
- log.info("XEP-0114 components can not use TLS")
-
- log.debug("Connecting to %s:%s", host, port)
- return XMLStream.connect(self, host=host, port=port,
- use_ssl=use_ssl,
- use_tls=False,
- reattempt=reattempt)
-
- def incoming_filter(self, xml):
- """
- Pre-process incoming XML stanzas by converting any
- ``'jabber:client'`` namespaced elements to the component's
- default namespace.
-
- :param xml: The XML stanza to pre-process.
- """
- if xml.tag.startswith('{jabber:client}'):
- xml.tag = xml.tag.replace('jabber:client', self.default_ns)
- return xml
-
- def start_stream_handler(self, xml):
- """
- Once the streams are established, attempt to handshake
- with the server to be accepted as a component.
-
- :param xml: The incoming stream's root element.
- """
- BaseXMPP.start_stream_handler(self, xml)
-
- # Construct a hash of the stream ID and the component secret.
- sid = xml.get('id', '')
- pre_hash = '%s%s' % (sid, self.secret)
- if sys.version_info >= (3, 0):
- # Handle Unicode byte encoding in Python 3.
- pre_hash = bytes(pre_hash, 'utf-8')
-
- handshake = ET.Element('{jabber:component:accept}handshake')
- handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
- self.send_xml(handshake, now=True)
-
- def _handle_handshake(self, xml):
- """The handshake has been accepted.
-
- :param xml: The reply handshake stanza.
- """
- self.session_bind_event.set()
- self.session_started_event.set()
- self.event('session_bind', self.boundjid, direct=True)
- self.event('session_start')
-
- def _handle_probe(self, pres):
- self.roster[pres['to']][pres['from']].handle_probe(pres)
diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py
deleted file mode 100644
index 8a2aa75c..00000000
--- a/sleekxmpp/exceptions.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.exceptions
- ~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-
-class XMPPError(Exception):
-
- """
- A generic exception that may be raised while processing an XMPP stanza
- to indicate that an error response stanza should be sent.
-
- The exception method for stanza objects extending
- :class:`~sleekxmpp.stanza.rootstanza.RootStanza` will create an error
- stanza and initialize any additional substanzas using the extension
- information included in the exception.
-
- Meant for use in SleekXMPP plugins and applications using SleekXMPP.
-
- Extension information can be included to add additional XML elements
- to the generated error stanza.
-
- :param condition: The XMPP defined error condition.
- Defaults to ``'undefined-condition'``.
- :param text: Human readable text describing the error.
- :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``.
- Defaults to ``'cancel'``.
- :param extension: Tag name of the extension's XML content.
- :param extension_ns: XML namespace of the extensions' XML content.
- :param extension_args: Content and attributes for the extension
- element. Same as the additional arguments to
- the :class:`~xml.etree.ElementTree.Element`
- constructor.
- :param clear: Indicates if the stanza's contents should be
- removed before replying with an error.
- Defaults to ``True``.
- """
-
- def __init__(self, condition='undefined-condition', text='',
- etype='cancel', extension=None, extension_ns=None,
- extension_args=None, clear=True):
- if extension_args is None:
- extension_args = {}
-
- self.condition = condition
- self.text = text
- self.etype = etype
- self.clear = clear
- self.extension = extension
- self.extension_ns = extension_ns
- self.extension_args = extension_args
-
-
-class IqTimeout(XMPPError):
-
- """
- An exception which indicates that an IQ request response has not been
- received within the alloted time window.
- """
-
- def __init__(self, iq):
- super(IqTimeout, self).__init__(
- condition='remote-server-timeout',
- etype='cancel')
-
- #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
- #: did not arrive before the timeout expired.
- self.iq = iq
-
-
-class IqError(XMPPError):
-
- """
- An exception raised when an Iq stanza of type 'error' is received
- after making a blocking send call.
- """
-
- def __init__(self, iq):
- super(IqError, self).__init__(
- condition=iq['error']['condition'],
- text=iq['error']['text'],
- etype=iq['error']['type'])
-
- #: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza.
- self.iq = iq
diff --git a/sleekxmpp/features/__init__.py b/sleekxmpp/features/__init__.py
deleted file mode 100644
index 869de7e9..00000000
--- a/sleekxmpp/features/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-__all__ = [
- 'feature_starttls',
- 'feature_mechanisms',
- 'feature_bind',
- 'feature_session',
- 'feature_rosterver',
- 'feature_preapproval'
-]
diff --git a/sleekxmpp/features/feature_bind/__init__.py b/sleekxmpp/features/feature_bind/__init__.py
deleted file mode 100644
index 9e0831dd..00000000
--- a/sleekxmpp/features/feature_bind/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_bind.bind import FeatureBind
-from sleekxmpp.features.feature_bind.stanza import Bind
-
-
-register_plugin(FeatureBind)
-
-
-# Retain some backwards compatibility
-feature_bind = FeatureBind
diff --git a/sleekxmpp/features/feature_bind/bind.py b/sleekxmpp/features/feature_bind/bind.py
deleted file mode 100644
index ee4c1e9b..00000000
--- a/sleekxmpp/features/feature_bind/bind.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.jid import JID
-from sleekxmpp.stanza import Iq, StreamFeatures
-from sleekxmpp.features.feature_bind import stanza
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-
-
-log = logging.getLogger(__name__)
-
-
-class FeatureBind(BasePlugin):
-
- name = 'feature_bind'
- description = 'RFC 6120: Stream Feature: Resource Binding'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_feature('bind',
- self._handle_bind_resource,
- restart=False,
- order=10000)
-
- register_stanza_plugin(Iq, stanza.Bind)
- register_stanza_plugin(StreamFeatures, stanza.Bind)
-
- def _handle_bind_resource(self, features):
- """
- Handle requesting a specific resource.
-
- Arguments:
- features -- The stream features stanza.
- """
- log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource)
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq.enable('bind')
- if self.xmpp.requested_jid.resource:
- iq['bind']['resource'] = self.xmpp.requested_jid.resource
- response = iq.send(now=True)
-
- self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
- self.xmpp.bound = True
- self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
- self.xmpp.session_bind_event.set()
-
- self.xmpp.features.add('bind')
-
- log.info("JID set to: %s", self.xmpp.boundjid.full)
-
- if 'session' not in features['features']:
- log.debug("Established Session")
- self.xmpp.sessionstarted = True
- self.xmpp.session_started_event.set()
- self.xmpp.event('session_start')
diff --git a/sleekxmpp/features/feature_bind/stanza.py b/sleekxmpp/features/feature_bind/stanza.py
deleted file mode 100644
index 8ce7536f..00000000
--- a/sleekxmpp/features/feature_bind/stanza.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Bind(ElementBase):
-
- """
- """
-
- name = 'bind'
- namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
- interfaces = set(('resource', 'jid'))
- sub_interfaces = interfaces
- plugin_attrib = 'bind'
diff --git a/sleekxmpp/features/feature_mechanisms/__init__.py b/sleekxmpp/features/feature_mechanisms/__init__.py
deleted file mode 100644
index 9f7611ed..00000000
--- a/sleekxmpp/features/feature_mechanisms/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms
-from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms
-from sleekxmpp.features.feature_mechanisms.stanza import Auth
-from sleekxmpp.features.feature_mechanisms.stanza import Success
-from sleekxmpp.features.feature_mechanisms.stanza import Failure
-
-
-register_plugin(FeatureMechanisms)
-
-
-# Retain some backwards compatibility
-feature_mechanisms = FeatureMechanisms
diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py
deleted file mode 100644
index 1d8f8798..00000000
--- a/sleekxmpp/features/feature_mechanisms/mechanisms.py
+++ /dev/null
@@ -1,244 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import ssl
-import logging
-
-from sleekxmpp.util import sasl
-from sleekxmpp.util.stringprep_profiles import StringPrepError
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.features.feature_mechanisms import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class FeatureMechanisms(BasePlugin):
-
- name = 'feature_mechanisms'
- description = 'RFC 6120: Stream Feature: SASL'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'use_mech': None,
- 'use_mechs': None,
- 'min_mech': None,
- 'sasl_callback': None,
- 'security_callback': None,
- 'encrypted_plain': True,
- 'unencrypted_plain': False,
- 'unencrypted_digest': False,
- 'unencrypted_cram': False,
- 'unencrypted_scram': True,
- 'order': 100
- }
-
- def plugin_init(self):
- if self.sasl_callback is None:
- self.sasl_callback = self._default_credentials
-
- if self.security_callback is None:
- self.security_callback = self._default_security
-
- creds = self.sasl_callback(set(['username']), set())
- if not self.use_mech and not creds['username']:
- self.use_mech = 'ANONYMOUS'
-
- self.mech = None
- self.mech_list = set()
- self.attempted_mechs = set()
-
- register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
-
- self.xmpp.register_stanza(stanza.Success)
- self.xmpp.register_stanza(stanza.Failure)
- self.xmpp.register_stanza(stanza.Auth)
- self.xmpp.register_stanza(stanza.Challenge)
- self.xmpp.register_stanza(stanza.Response)
- self.xmpp.register_stanza(stanza.Abort)
-
- self.xmpp.register_handler(
- Callback('SASL Success',
- MatchXPath(stanza.Success.tag_name()),
- self._handle_success,
- instream=True))
- self.xmpp.register_handler(
- Callback('SASL Failure',
- MatchXPath(stanza.Failure.tag_name()),
- self._handle_fail,
- instream=True))
- self.xmpp.register_handler(
- Callback('SASL Challenge',
- MatchXPath(stanza.Challenge.tag_name()),
- self._handle_challenge))
-
- self.xmpp.register_feature('mechanisms',
- self._handle_sasl_auth,
- restart=True,
- order=self.order)
-
- def _default_credentials(self, required_values, optional_values):
- creds = self.xmpp.credentials
- result = {}
- values = required_values.union(optional_values)
- for value in values:
- if value == 'username':
- result[value] = creds.get('username', self.xmpp.requested_jid.user)
- elif value == 'email':
- jid = self.xmpp.requested_jid.bare
- result[value] = creds.get('email', jid)
- elif value == 'channel_binding':
- if hasattr(self.xmpp.socket, 'get_channel_binding'):
- result[value] = self.xmpp.socket.get_channel_binding()
- else:
- log.debug("Channel binding not supported.")
- log.debug("Use Python 3.3+ for channel binding and " + \
- "SCRAM-SHA-1-PLUS support")
- result[value] = None
- elif value == 'host':
- result[value] = creds.get('host', self.xmpp.requested_jid.domain)
- elif value == 'realm':
- result[value] = creds.get('realm', self.xmpp.requested_jid.domain)
- elif value == 'service-name':
- result[value] = creds.get('service-name', self.xmpp._service_name)
- elif value == 'service':
- result[value] = creds.get('service', 'xmpp')
- elif value in creds:
- result[value] = creds[value]
- return result
-
- def _default_security(self, values):
- result = {}
- for value in values:
- if value == 'encrypted':
- if 'starttls' in self.xmpp.features:
- result[value] = True
- elif isinstance(self.xmpp.socket, ssl.SSLSocket):
- result[value] = True
- else:
- result[value] = False
- else:
- result[value] = self.config.get(value, False)
- return result
-
- def _handle_sasl_auth(self, features):
- """
- Handle authenticating using SASL.
-
- Arguments:
- features -- The stream features stanza.
- """
- if 'mechanisms' in self.xmpp.features:
- # SASL authentication has already succeeded, but the
- # server has incorrectly offered it again.
- return False
-
- enforce_limit = False
- limited_mechs = self.use_mechs
-
- if limited_mechs is None:
- limited_mechs = set()
- elif limited_mechs and not isinstance(limited_mechs, set):
- limited_mechs = set(limited_mechs)
- enforce_limit = True
-
- if self.use_mech:
- limited_mechs.add(self.use_mech)
- enforce_limit = True
-
- if enforce_limit:
- self.use_mechs = limited_mechs
-
- self.mech_list = set(features['mechanisms'])
-
- return self._send_auth()
-
- def _send_auth(self):
- mech_list = self.mech_list - self.attempted_mechs
- try:
- self.mech = sasl.choose(mech_list,
- self.sasl_callback,
- self.security_callback,
- limit=self.use_mechs,
- min_mech=self.min_mech)
- except sasl.SASLNoAppropriateMechanism:
- log.error("No appropriate login method.")
- self.xmpp.event("no_auth", direct=True)
- self.xmpp.event("failed_auth", direct=True)
- self.attempted_mechs = set()
- return self.xmpp.disconnect()
- except StringPrepError:
- log.exception("A credential value did not pass SASLprep.")
- self.xmpp.disconnect()
-
- resp = stanza.Auth(self.xmpp)
- resp['mechanism'] = self.mech.name
- try:
- resp['value'] = self.mech.process()
- except sasl.SASLCancelled:
- self.attempted_mechs.add(self.mech.name)
- self._send_auth()
- except sasl.SASLMutualAuthFailed:
- log.error("Mutual authentication failed! " + \
- "A security breach is possible.")
- self.attempted_mechs.add(self.mech.name)
- self.xmpp.disconnect()
- except sasl.SASLFailed:
- self.attempted_mechs.add(self.mech.name)
- self._send_auth()
- else:
- resp.send(now=True)
-
- return True
-
- def _handle_challenge(self, stanza):
- """SASL challenge received. Process and send response."""
- resp = self.stanza.Response(self.xmpp)
- try:
- resp['value'] = self.mech.process(stanza['value'])
- except sasl.SASLCancelled:
- self.stanza.Abort(self.xmpp).send()
- except sasl.SASLMutualAuthFailed:
- log.error("Mutual authentication failed! " + \
- "A security breach is possible.")
- self.attempted_mechs.add(self.mech.name)
- self.xmpp.disconnect()
- except sasl.SASLFailed:
- self.stanza.Abort(self.xmpp).send()
- else:
- if resp.get_value() == '':
- resp.del_value()
- resp.send(now=True)
-
- def _handle_success(self, stanza):
- """SASL authentication succeeded. Restart the stream."""
- try:
- final = self.mech.process(stanza['value'])
- except sasl.SASLMutualAuthFailed:
- log.error("Mutual authentication failed! " + \
- "A security breach is possible.")
- self.attempted_mechs.add(self.mech.name)
- self.xmpp.disconnect()
- else:
- self.attempted_mechs = set()
- self.xmpp.authenticated = True
- self.xmpp.features.add('mechanisms')
- self.xmpp.event('auth_success', stanza, direct=True)
- raise RestartStream()
-
- def _handle_fail(self, stanza):
- """SASL authentication failed. Disconnect and shutdown."""
- self.attempted_mechs.add(self.mech.name)
- log.info("Authentication failed: %s", stanza['condition'])
- self.xmpp.event("failed_auth", stanza, direct=True)
- self._send_auth()
- return True
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py b/sleekxmpp/features/feature_mechanisms/stanza/__init__.py
deleted file mode 100644
index 38991d89..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms
-from sleekxmpp.features.feature_mechanisms.stanza.auth import Auth
-from sleekxmpp.features.feature_mechanisms.stanza.success import Success
-from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure
-from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge
-from sleekxmpp.features.feature_mechanisms.stanza.response import Response
-from sleekxmpp.features.feature_mechanisms.stanza.abort import Abort
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/abort.py b/sleekxmpp/features/feature_mechanisms/stanza/abort.py
deleted file mode 100644
index aaca348d..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/abort.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class Abort(StanzaBase):
-
- """
- """
-
- name = 'abort'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set()
- plugin_attrib = name
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/auth.py b/sleekxmpp/features/feature_mechanisms/stanza/auth.py
deleted file mode 100644
index 6b6f85a3..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/auth.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import base64
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class Auth(StanzaBase):
-
- """
- """
-
- name = 'auth'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(('mechanism', 'value'))
- plugin_attrib = name
-
- #: Some SASL mechs require sending values as is,
- #: without converting base64.
- plain_mechs = set(['X-MESSENGER-OAUTH2'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_value(self):
- if not self['mechanism'] in self.plain_mechs:
- return base64.b64decode(bytes(self.xml.text))
- else:
- return self.xml.text
-
- def set_value(self, values):
- if not self['mechanism'] in self.plain_mechs:
- if values:
- self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
- elif values == b'':
- self.xml.text = '='
- else:
- self.xml.text = bytes(values).decode('utf-8')
-
- def del_value(self):
- self.xml.text = ''
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py b/sleekxmpp/features/feature_mechanisms/stanza/challenge.py
deleted file mode 100644
index 24290281..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import base64
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class Challenge(StanzaBase):
-
- """
- """
-
- name = 'challenge'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(('value',))
- plugin_attrib = name
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_value(self):
- return base64.b64decode(bytes(self.xml.text))
-
- def set_value(self, values):
- if values:
- self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
- else:
- self.xml.text = '='
-
- def del_value(self):
- self.xml.text = ''
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/failure.py b/sleekxmpp/features/feature_mechanisms/stanza/failure.py
deleted file mode 100644
index b9f32605..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/failure.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import StanzaBase, ET
-
-
-class Failure(StanzaBase):
-
- """
- """
-
- name = 'failure'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(('condition', 'text'))
- plugin_attrib = name
- sub_interfaces = set(('text',))
- conditions = set(('aborted', 'account-disabled', 'credentials-expired',
- 'encryption-required', 'incorrect-encoding', 'invalid-authzid',
- 'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
- 'not-authorized', 'temporary-auth-failure'))
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup.
-
- Sets a default error type and condition, and changes the
- parent stanza's type to 'error'.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- # StanzaBase overrides self.namespace
- self.namespace = Failure.namespace
-
- if StanzaBase.setup(self, xml):
- #If we had to generate XML then set default values.
- self['condition'] = 'not-authorized'
-
- self.xml.tag = self.tag_name()
-
- def get_condition(self):
- """Return the condition element's name."""
- for child in self.xml:
- if "{%s}" % self.namespace in child.tag:
- cond = child.tag.split('}', 1)[-1]
- if cond in self.conditions:
- return cond
- return 'not-authorized'
-
- def set_condition(self, value):
- """
- Set the tag name of the condition element.
-
- Arguments:
- value -- The tag name of the condition element.
- """
- if value in self.conditions:
- del self['condition']
- self.xml.append(ET.Element("{%s}%s" % (self.namespace, value)))
- return self
-
- def del_condition(self):
- """Remove the condition element."""
- for child in self.xml:
- if "{%s}" % self.condition_ns in child.tag:
- tag = child.tag.split('}', 1)[-1]
- if tag in self.conditions:
- self.xml.remove(child)
- return self
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
deleted file mode 100644
index bbd56813..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Mechanisms(ElementBase):
-
- """
- """
-
- name = 'mechanisms'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(('mechanisms', 'required'))
- plugin_attrib = name
- is_extension = True
-
- def get_required(self):
- """
- """
- return True
-
- def get_mechanisms(self):
- """
- """
- results = []
- mechs = self.findall('{%s}mechanism' % self.namespace)
- if mechs:
- for mech in mechs:
- results.append(mech.text)
- return results
-
- def set_mechanisms(self, values):
- """
- """
- self.del_mechanisms()
- for val in values:
- mech = ET.Element('{%s}mechanism' % self.namespace)
- mech.text = val
- self.append(mech)
-
- def del_mechanisms(self):
- """
- """
- mechs = self.findall('{%s}mechanism' % self.namespace)
- if mechs:
- for mech in mechs:
- self.xml.remove(mech)
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/response.py b/sleekxmpp/features/feature_mechanisms/stanza/response.py
deleted file mode 100644
index ca7624f1..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/response.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import base64
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class Response(StanzaBase):
-
- """
- """
-
- name = 'response'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(('value',))
- plugin_attrib = name
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_value(self):
- return base64.b64decode(bytes(self.xml.text))
-
- def set_value(self, values):
- if values:
- self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
- else:
- self.xml.text = '='
-
- def del_value(self):
- self.xml.text = ''
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/success.py b/sleekxmpp/features/feature_mechanisms/stanza/success.py
deleted file mode 100644
index 7a4eab8e..00000000
--- a/sleekxmpp/features/feature_mechanisms/stanza/success.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import base64
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import StanzaBase
-
-class Success(StanzaBase):
-
- """
- """
-
- name = 'success'
- namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
- interfaces = set(['value'])
- plugin_attrib = name
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_value(self):
- return base64.b64decode(bytes(self.xml.text))
-
- def set_value(self, values):
- if values:
- self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
- else:
- self.xml.text = '='
-
- def del_value(self):
- self.xml.text = ''
diff --git a/sleekxmpp/features/feature_preapproval/__init__.py b/sleekxmpp/features/feature_preapproval/__init__.py
deleted file mode 100644
index ae8b6b70..00000000
--- a/sleekxmpp/features/feature_preapproval/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval
-from sleekxmpp.features.feature_preapproval.stanza import PreApproval
-
-
-register_plugin(FeaturePreApproval)
diff --git a/sleekxmpp/features/feature_preapproval/preapproval.py b/sleekxmpp/features/feature_preapproval/preapproval.py
deleted file mode 100644
index c7106ed3..00000000
--- a/sleekxmpp/features/feature_preapproval/preapproval.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.features.feature_preapproval import stanza
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin
-
-
-log = logging.getLogger(__name__)
-
-
-class FeaturePreApproval(BasePlugin):
-
- name = 'feature_preapproval'
- description = 'RFC 6121: Stream Feature: Subscription Pre-Approval'
- dependences = set()
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_feature('preapproval',
- self._handle_preapproval,
- restart=False,
- order=9001)
-
- register_stanza_plugin(StreamFeatures, stanza.PreApproval)
-
- def _handle_preapproval(self, features):
- """Save notice that the server support subscription pre-approvals.
-
- Arguments:
- features -- The stream features stanza.
- """
- log.debug("Server supports subscription pre-approvals.")
- self.xmpp.features.add('preapproval')
diff --git a/sleekxmpp/features/feature_preapproval/stanza.py b/sleekxmpp/features/feature_preapproval/stanza.py
deleted file mode 100644
index 4a59bd16..00000000
--- a/sleekxmpp/features/feature_preapproval/stanza.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class PreApproval(ElementBase):
-
- name = 'sub'
- namespace = 'urn:xmpp:features:pre-approval'
- interfaces = set()
- plugin_attrib = 'preapproval'
diff --git a/sleekxmpp/features/feature_rosterver/__init__.py b/sleekxmpp/features/feature_rosterver/__init__.py
deleted file mode 100644
index 33bbf416..00000000
--- a/sleekxmpp/features/feature_rosterver/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_rosterver.rosterver import FeatureRosterVer
-from sleekxmpp.features.feature_rosterver.stanza import RosterVer
-
-
-register_plugin(FeatureRosterVer)
-
-
-# Retain some backwards compatibility
-feature_rosterver = FeatureRosterVer
diff --git a/sleekxmpp/features/feature_rosterver/rosterver.py b/sleekxmpp/features/feature_rosterver/rosterver.py
deleted file mode 100644
index 2991f587..00000000
--- a/sleekxmpp/features/feature_rosterver/rosterver.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.features.feature_rosterver import stanza
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin
-
-
-log = logging.getLogger(__name__)
-
-
-class FeatureRosterVer(BasePlugin):
-
- name = 'feature_rosterver'
- description = 'RFC 6121: Stream Feature: Roster Versioning'
- dependences = set()
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_feature('rosterver',
- self._handle_rosterver,
- restart=False,
- order=9000)
-
- register_stanza_plugin(StreamFeatures, stanza.RosterVer)
-
- def _handle_rosterver(self, features):
- """Enable using roster versioning.
-
- Arguments:
- features -- The stream features stanza.
- """
- log.debug("Enabling roster versioning.")
- self.xmpp.features.add('rosterver')
diff --git a/sleekxmpp/features/feature_rosterver/stanza.py b/sleekxmpp/features/feature_rosterver/stanza.py
deleted file mode 100644
index 025872fa..00000000
--- a/sleekxmpp/features/feature_rosterver/stanza.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class RosterVer(ElementBase):
-
- name = 'ver'
- namespace = 'urn:xmpp:features:rosterver'
- interfaces = set()
- plugin_attrib = 'rosterver'
diff --git a/sleekxmpp/features/feature_session/__init__.py b/sleekxmpp/features/feature_session/__init__.py
deleted file mode 100644
index 28bb3f77..00000000
--- a/sleekxmpp/features/feature_session/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_session.session import FeatureSession
-from sleekxmpp.features.feature_session.stanza import Session
-
-
-register_plugin(FeatureSession)
-
-
-# Retain some backwards compatibility
-feature_session = FeatureSession
diff --git a/sleekxmpp/features/feature_session/session.py b/sleekxmpp/features/feature_session/session.py
deleted file mode 100644
index ceadd5f3..00000000
--- a/sleekxmpp/features/feature_session/session.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Iq, StreamFeatures
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-
-from sleekxmpp.features.feature_session import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class FeatureSession(BasePlugin):
-
- name = 'feature_session'
- description = 'RFC 3920: Stream Feature: Start Session'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_feature('session',
- self._handle_start_session,
- restart=False,
- order=10001)
-
- register_stanza_plugin(Iq, stanza.Session)
- register_stanza_plugin(StreamFeatures, stanza.Session)
-
- def _handle_start_session(self, features):
- """
- Handle the start of the session.
-
- Arguments:
- feature -- The stream features element.
- """
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq.enable('session')
- iq.send(now=True)
-
- self.xmpp.features.add('session')
-
- log.debug("Established Session")
- self.xmpp.sessionstarted = True
- self.xmpp.session_started_event.set()
- self.xmpp.event('session_start')
diff --git a/sleekxmpp/features/feature_session/stanza.py b/sleekxmpp/features/feature_session/stanza.py
deleted file mode 100644
index 94e949ee..00000000
--- a/sleekxmpp/features/feature_session/stanza.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Session(ElementBase):
-
- """
- """
-
- name = 'session'
- namespace = 'urn:ietf:params:xml:ns:xmpp-session'
- interfaces = set()
- plugin_attrib = 'session'
diff --git a/sleekxmpp/features/feature_starttls/__init__.py b/sleekxmpp/features/feature_starttls/__init__.py
deleted file mode 100644
index 68697ce5..00000000
--- a/sleekxmpp/features/feature_starttls/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.features.feature_starttls.starttls import FeatureSTARTTLS
-from sleekxmpp.features.feature_starttls.stanza import *
-
-
-register_plugin(FeatureSTARTTLS)
-
-
-# Retain some backwards compatibility
-feature_starttls = FeatureSTARTTLS
diff --git a/sleekxmpp/features/feature_starttls/stanza.py b/sleekxmpp/features/feature_starttls/stanza.py
deleted file mode 100644
index b968e134..00000000
--- a/sleekxmpp/features/feature_starttls/stanza.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import StanzaBase, ElementBase
-
-
-class STARTTLS(ElementBase):
-
- """
- """
-
- name = 'starttls'
- namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
- interfaces = set(('required',))
- plugin_attrib = name
-
- def get_required(self):
- """
- """
- return True
-
-
-class Proceed(StanzaBase):
-
- """
- """
-
- name = 'proceed'
- namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
- interfaces = set()
-
-
-class Failure(StanzaBase):
-
- """
- """
-
- name = 'failure'
- namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
- interfaces = set()
diff --git a/sleekxmpp/features/feature_starttls/starttls.py b/sleekxmpp/features/feature_starttls/starttls.py
deleted file mode 100644
index eb5eee1d..00000000
--- a/sleekxmpp/features/feature_starttls/starttls.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.features.feature_starttls import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class FeatureSTARTTLS(BasePlugin):
-
- name = 'feature_starttls'
- description = 'RFC 6120: Stream Feature: STARTTLS'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('STARTTLS Proceed',
- MatchXPath(stanza.Proceed.tag_name()),
- self._handle_starttls_proceed,
- instream=True))
- self.xmpp.register_feature('starttls',
- self._handle_starttls,
- restart=True,
- order=self.config.get('order', 0))
-
- self.xmpp.register_stanza(stanza.Proceed)
- self.xmpp.register_stanza(stanza.Failure)
- register_stanza_plugin(StreamFeatures, stanza.STARTTLS)
-
- def _handle_starttls(self, features):
- """
- Handle notification that the server supports TLS.
-
- Arguments:
- features -- The stream:features element.
- """
- if 'starttls' in self.xmpp.features:
- # We have already negotiated TLS, but the server is
- # offering it again, against spec.
- return False
- elif not self.xmpp.use_tls:
- return False
- else:
- self.xmpp.send(features['starttls'], now=True)
- return True
-
- def _handle_starttls_proceed(self, proceed):
- """Restart the XML stream when TLS is accepted."""
- log.debug("Starting TLS")
- if self.xmpp.start_tls():
- self.xmpp.features.add('starttls')
- raise RestartStream()
diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py
deleted file mode 100644
index 754a3d3a..00000000
--- a/sleekxmpp/jid.py
+++ /dev/null
@@ -1,632 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.jid
- ~~~~~~~~~~~~~~~~~~~~~~~
-
- This module allows for working with Jabber IDs (JIDs).
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import unicode_literals
-
-import re
-import socket
-import stringprep
-import threading
-import encodings.idna
-
-from copy import deepcopy
-
-from sleekxmpp.util import stringprep_profiles
-from sleekxmpp.thirdparty import OrderedDict
-
-#: These characters are not allowed to appear in a JID.
-ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \
- '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \
- '\x1a\x1b\x1c\x1d\x1e\x1f' + \
- ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f'
-
-#: The basic regex pattern that a JID must match in order to determine
-#: the local, domain, and resource parts. This regex does NOT do any
-#: validation, which requires application of nodeprep, resourceprep, etc.
-JID_PATTERN = re.compile(
- "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$"
-)
-
-#: The set of escape sequences for the characters not allowed by nodeprep.
-JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f',
- '\\3a', '\\3c', '\\3e', '\\40', '\\5c'])
-
-#: A mapping of unallowed characters to their escape sequences. An escape
-#: sequence for '\' is also included since it must also be escaped in
-#: certain situations.
-JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20',
- '"': '\\22',
- '&': '\\26',
- "'": '\\27',
- '/': '\\2f',
- ':': '\\3a',
- '<': '\\3c',
- '>': '\\3e',
- '@': '\\40',
- '\\': '\\5c'}
-
-#: The reverse mapping of escape sequences to their original forms.
-JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
- '\\22': '"',
- '\\26': '&',
- '\\27': "'",
- '\\2f': '/',
- '\\3a': ':',
- '\\3c': '<',
- '\\3e': '>',
- '\\40': '@',
- '\\5c': '\\'}
-
-JID_CACHE = OrderedDict()
-JID_CACHE_LOCK = threading.Lock()
-JID_CACHE_MAX_SIZE = 1024
-
-def _cache(key, parts, locked):
- with JID_CACHE_LOCK:
- JID_CACHE[key] = (parts, locked)
- while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
- found = None
- for key, item in JID_CACHE.items():
- if not item[1]: # if not locked
- found = key
- break
- if not found: # more than MAX_SIZE locked
- # warn?
- break
- del JID_CACHE[found]
-
-# pylint: disable=c0103
-#: The nodeprep profile of stringprep used to validate the local,
-#: or username, portion of a JID.
-nodeprep = stringprep_profiles.create(
- nfkc=True,
- bidi=True,
- mappings=[
- stringprep_profiles.b1_mapping,
- stringprep.map_table_b2],
- prohibited=[
- stringprep.in_table_c11,
- stringprep.in_table_c12,
- stringprep.in_table_c21,
- stringprep.in_table_c22,
- stringprep.in_table_c3,
- stringprep.in_table_c4,
- stringprep.in_table_c5,
- stringprep.in_table_c6,
- stringprep.in_table_c7,
- stringprep.in_table_c8,
- stringprep.in_table_c9,
- lambda c: c in ' \'"&/:<>@'],
- unassigned=[stringprep.in_table_a1])
-
-# pylint: disable=c0103
-#: The resourceprep profile of stringprep, which is used to validate
-#: the resource portion of a JID.
-resourceprep = stringprep_profiles.create(
- nfkc=True,
- bidi=True,
- mappings=[stringprep_profiles.b1_mapping],
- prohibited=[
- stringprep.in_table_c12,
- stringprep.in_table_c21,
- stringprep.in_table_c22,
- stringprep.in_table_c3,
- stringprep.in_table_c4,
- stringprep.in_table_c5,
- stringprep.in_table_c6,
- stringprep.in_table_c7,
- stringprep.in_table_c8,
- stringprep.in_table_c9],
- unassigned=[stringprep.in_table_a1])
-
-
-def _parse_jid(data):
- """
- Parse string data into the node, domain, and resource
- components of a JID, if possible.
-
- :param string data: A string that is potentially a JID.
-
- :raises InvalidJID:
-
- :returns: tuple of the validated local, domain, and resource strings
- """
- match = JID_PATTERN.match(data)
- if not match:
- raise InvalidJID('JID could not be parsed')
-
- (node, domain, resource) = match.groups()
-
- node = _validate_node(node)
- domain = _validate_domain(domain)
- resource = _validate_resource(resource)
-
- return node, domain, resource
-
-
-def _validate_node(node):
- """Validate the local, or username, portion of a JID.
-
- :raises InvalidJID:
-
- :returns: The local portion of a JID, as validated by nodeprep.
- """
- try:
- if node is not None:
- node = nodeprep(node)
-
- if not node:
- raise InvalidJID('Localpart must not be 0 bytes')
- if len(node) > 1023:
- raise InvalidJID('Localpart must be less than 1024 bytes')
- return node
- except stringprep_profiles.StringPrepError:
- raise InvalidJID('Invalid local part')
-
-
-def _validate_domain(domain):
- """Validate the domain portion of a JID.
-
- IP literal addresses are left as-is, if valid. Domain names
- are stripped of any trailing label separators (`.`), and are
- checked with the nameprep profile of stringprep. If the given
- domain is actually a punyencoded version of a domain name, it
- is converted back into its original Unicode form. Domains must
- also not start or end with a dash (`-`).
-
- :raises InvalidJID:
-
- :returns: The validated domain name
- """
- ip_addr = False
-
- # First, check if this is an IPv4 address
- try:
- socket.inet_aton(domain)
- ip_addr = True
- except socket.error:
- pass
-
- # Check if this is an IPv6 address
- if not ip_addr and hasattr(socket, 'inet_pton'):
- try:
- socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
- domain = '[%s]' % domain.strip('[]')
- ip_addr = True
- except (socket.error, ValueError):
- pass
-
- if not ip_addr:
- # This is a domain name, which must be checked further
-
- if domain and domain[-1] == '.':
- domain = domain[:-1]
-
- domain_parts = []
- for label in domain.split('.'):
- try:
- label = encodings.idna.nameprep(label)
- encodings.idna.ToASCII(label)
- pass_nameprep = True
- except UnicodeError:
- pass_nameprep = False
-
- if not pass_nameprep:
- raise InvalidJID('Could not encode domain as ASCII')
-
- if label.startswith('xn--'):
- label = encodings.idna.ToUnicode(label)
-
- for char in label:
- if char in ILLEGAL_CHARS:
- raise InvalidJID('Domain contains illegal characters')
-
- if '-' in (label[0], label[-1]):
- raise InvalidJID('Domain started or ended with -')
-
- domain_parts.append(label)
- domain = '.'.join(domain_parts)
-
- if not domain:
- raise InvalidJID('Domain must not be 0 bytes')
- if len(domain) > 1023:
- raise InvalidJID('Domain must be less than 1024 bytes')
-
- return domain
-
-
-def _validate_resource(resource):
- """Validate the resource portion of a JID.
-
- :raises InvalidJID:
-
- :returns: The local portion of a JID, as validated by resourceprep.
- """
- try:
- if resource is not None:
- resource = resourceprep(resource)
-
- if not resource:
- raise InvalidJID('Resource must not be 0 bytes')
- if len(resource) > 1023:
- raise InvalidJID('Resource must be less than 1024 bytes')
- return resource
- except stringprep_profiles.StringPrepError:
- raise InvalidJID('Invalid resource')
-
-
-def _escape_node(node):
- """Escape the local portion of a JID."""
- result = []
-
- for i, char in enumerate(node):
- if char == '\\':
- if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES:
- result.append('\\5c')
- continue
- result.append(char)
-
- for i, char in enumerate(result):
- if char != '\\':
- result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char)
-
- escaped = ''.join(result)
-
- if escaped.startswith('\\20') or escaped.endswith('\\20'):
- raise InvalidJID('Escaped local part starts or ends with "\\20"')
-
- _validate_node(escaped)
-
- return escaped
-
-
-def _unescape_node(node):
- """Unescape a local portion of a JID.
-
- .. note::
- The unescaped local portion is meant ONLY for presentation,
- and should not be used for other purposes.
- """
- unescaped = []
- seq = ''
- for i, char in enumerate(node):
- if char == '\\':
- seq = node[i:i+3]
- if seq not in JID_ESCAPE_SEQUENCES:
- seq = ''
- if seq:
- if len(seq) == 3:
- unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char))
-
- # Pop character off the escape sequence, and ignore it
- seq = seq[1:]
- else:
- unescaped.append(char)
- unescaped = ''.join(unescaped)
-
- return unescaped
-
-
-def _format_jid(local=None, domain=None, resource=None):
- """Format the given JID components into a full or bare JID.
-
- :param string local: Optional. The local portion of the JID.
- :param string domain: Required. The domain name portion of the JID.
- :param strin resource: Optional. The resource portion of the JID.
-
- :return: A full or bare JID string.
- """
- result = []
- if local:
- result.append(local)
- result.append('@')
- if domain:
- result.append(domain)
- if resource:
- result.append('/')
- result.append(resource)
- return ''.join(result)
-
-
-class InvalidJID(ValueError):
- """
- Raised when attempting to create a JID that does not pass validation.
-
- It can also be raised if modifying an existing JID in such a way as
- to make it invalid, such trying to remove the domain from an existing
- full JID while the local and resource portions still exist.
- """
-
-# pylint: disable=R0903
-class UnescapedJID(object):
-
- """
- .. versionadded:: 1.1.10
- """
-
- def __init__(self, local, domain, resource):
- self._jid = (local, domain, resource)
-
- # pylint: disable=R0911
- def __getattr__(self, name):
- """Retrieve the given JID component.
-
- :param name: one of: user, server, domain, resource,
- full, or bare.
- """
- if name == 'resource':
- return self._jid[2] or ''
- elif name in ('user', 'username', 'local', 'node'):
- return self._jid[0] or ''
- elif name in ('server', 'domain', 'host'):
- return self._jid[1] or ''
- elif name in ('full', 'jid'):
- return _format_jid(*self._jid)
- elif name == 'bare':
- return _format_jid(self._jid[0], self._jid[1])
- elif name == '_jid':
- return getattr(super(JID, self), '_jid')
- else:
- return None
-
- def __str__(self):
- """Use the full JID as the string value."""
- return _format_jid(*self._jid)
-
- def __repr__(self):
- """Use the full JID as the representation."""
- return self.__str__()
-
-
-class JID(object):
-
- """
- A representation of a Jabber ID, or JID.
-
- Each JID may have three components: a user, a domain, and an optional
- resource. For example: user@domain/resource
-
- When a resource is not used, the JID is called a bare JID.
- The JID is a full JID otherwise.
-
- **JID Properties:**
- :jid: Alias for ``full``.
- :full: The string value of the full JID.
- :bare: The string value of the bare JID.
- :user: The username portion of the JID.
- :username: Alias for ``user``.
- :local: Alias for ``user``.
- :node: Alias for ``user``.
- :domain: The domain name portion of the JID.
- :server: Alias for ``domain``.
- :host: Alias for ``domain``.
- :resource: The resource portion of the JID.
-
- :param string jid:
- A string of the form ``'[user@]domain[/resource]'``.
- :param string local:
- Optional. Specify the local, or username, portion
- of the JID. If provided, it will override the local
- value provided by the `jid` parameter. The given
- local value will also be escaped if necessary.
- :param string domain:
- Optional. Specify the domain of the JID. If
- provided, it will override the domain given by
- the `jid` parameter.
- :param string resource:
- Optional. Specify the resource value of the JID.
- If provided, it will override the domain given
- by the `jid` parameter.
-
- :raises InvalidJID:
- """
-
- # pylint: disable=W0212
- def __init__(self, jid=None, **kwargs):
- locked = kwargs.get('cache_lock', False)
- in_local = kwargs.get('local', None)
- in_domain = kwargs.get('domain', None)
- in_resource = kwargs.get('resource', None)
- parts = None
- if in_local or in_domain or in_resource:
- parts = (in_local, in_domain, in_resource)
-
- # only check cache if there is a jid string, or parts, not if there
- # are both
- self._jid = None
- key = None
- if (jid is not None) and (parts is None):
- if isinstance(jid, JID):
- # it's already good to go, and there are no additions
- self._jid = jid._jid
- return
- key = jid
- self._jid, locked = JID_CACHE.get(jid, (None, locked))
- elif jid is None and parts is not None:
- key = parts
- self._jid, locked = JID_CACHE.get(parts, (None, locked))
- if not self._jid:
- if not jid:
- parsed_jid = (None, None, None)
- elif not isinstance(jid, JID):
- parsed_jid = _parse_jid(jid)
- else:
- parsed_jid = jid._jid
-
- local, domain, resource = parsed_jid
-
- if 'local' in kwargs:
- local = _escape_node(in_local)
- if 'domain' in kwargs:
- domain = _validate_domain(in_domain)
- if 'resource' in kwargs:
- resource = _validate_resource(in_resource)
-
- self._jid = (local, domain, resource)
- if key:
- _cache(key, self._jid, locked)
-
- def unescape(self):
- """Return an unescaped JID object.
-
- Using an unescaped JID is preferred for displaying JIDs
- to humans, and they should NOT be used for any other
- purposes than for presentation.
-
- :return: :class:`UnescapedJID`
-
- .. versionadded:: 1.1.10
- """
- return UnescapedJID(_unescape_node(self._jid[0]),
- self._jid[1],
- self._jid[2])
-
- def regenerate(self):
- """No-op
-
- .. deprecated:: 1.1.10
- """
- pass
-
- def reset(self, data):
- """Start fresh from a new JID string.
-
- :param string data: A string of the form ``'[user@]domain[/resource]'``.
-
- .. deprecated:: 1.1.10
- """
- self._jid = JID(data)._jid
-
- @property
- def resource(self):
- return self._jid[2] or ''
-
- @property
- def user(self):
- return self._jid[0] or ''
-
- @property
- def local(self):
- return self._jid[0] or ''
-
- @property
- def node(self):
- return self._jid[0] or ''
-
- @property
- def username(self):
- return self._jid[0] or ''
-
- @property
- def server(self):
- return self._jid[1] or ''
-
- @property
- def domain(self):
- return self._jid[1] or ''
-
- @property
- def host(self):
- return self._jid[1] or ''
-
- @property
- def full(self):
- return _format_jid(*self._jid)
-
- @property
- def jid(self):
- return _format_jid(*self._jid)
-
- @property
- def bare(self):
- return _format_jid(self._jid[0], self._jid[1])
-
- @resource.setter
- def resource(self, value):
- self._jid = JID(self, resource=value)._jid
-
- @user.setter
- def user(self, value):
- self._jid = JID(self, local=value)._jid
-
- @username.setter
- def username(self, value):
- self._jid = JID(self, local=value)._jid
-
- @local.setter
- def local(self, value):
- self._jid = JID(self, local=value)._jid
-
- @node.setter
- def node(self, value):
- self._jid = JID(self, local=value)._jid
-
- @server.setter
- def server(self, value):
- self._jid = JID(self, domain=value)._jid
-
- @domain.setter
- def domain(self, value):
- self._jid = JID(self, domain=value)._jid
-
- @host.setter
- def host(self, value):
- self._jid = JID(self, domain=value)._jid
-
- @full.setter
- def full(self, value):
- self._jid = JID(value)._jid
-
- @jid.setter
- def jid(self, value):
- self._jid = JID(value)._jid
-
- @bare.setter
- def bare(self, value):
- parsed = JID(value)._jid
- self._jid = (parsed[0], parsed[1], self._jid[2])
-
-
- def __str__(self):
- """Use the full JID as the string value."""
- return _format_jid(*self._jid)
-
- def __repr__(self):
- """Use the full JID as the representation."""
- return self.__str__()
-
- # pylint: disable=W0212
- def __eq__(self, other):
- """Two JIDs are equal if they have the same full JID value."""
- if isinstance(other, UnescapedJID):
- return False
-
- other = JID(other)
- return self._jid == other._jid
-
- # pylint: disable=W0212
- def __ne__(self, other):
- """Two JIDs are considered unequal if they are not equal."""
- return not self == other
-
- def __hash__(self):
- """Hash a JID based on the string version of its full JID."""
- return hash(self.__str__())
-
- def __copy__(self):
- """Generate a duplicate JID."""
- return JID(self)
-
- def __deepcopy__(self, memo):
- """Generate a duplicate JID."""
- return JID(deepcopy(str(self), memo))
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
deleted file mode 100644
index f501687b..00000000
--- a/sleekxmpp/plugins/__init__.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin
-from sleekxmpp.plugins.base import register_plugin, load_plugin
-
-
-__all__ = [
- # XEPS
- 'xep_0004', # Data Forms
- 'xep_0009', # Jabber-RPC
- 'xep_0012', # Last Activity
- 'xep_0013', # Flexible Offline Message Retrieval
- 'xep_0016', # Privacy Lists
- 'xep_0020', # Feature Negotiation
- 'xep_0027', # Current Jabber OpenPGP Usage
- 'xep_0030', # Service Discovery
- 'xep_0033', # Extended Stanza Addresses
- 'xep_0045', # Multi-User Chat (Client)
- 'xep_0047', # In-Band Bytestreams
- 'xep_0048', # Bookmarks
- 'xep_0049', # Private XML Storage
- 'xep_0050', # Ad-hoc Commands
- 'xep_0054', # vcard-temp
- 'xep_0059', # Result Set Management
- 'xep_0060', # Pubsub (Client)
- 'xep_0065', # SOCKS5 Bytestreams
- 'xep_0066', # Out of Band Data
- 'xep_0071', # XHTML-IM
- 'xep_0077', # In-Band Registration
-# 'xep_0078', # Non-SASL auth. Don't automatically load
- 'xep_0079', # Advanced Message Processing
- 'xep_0080', # User Location
- 'xep_0082', # XMPP Date and Time Profiles
- 'xep_0084', # User Avatar
- 'xep_0085', # Chat State Notifications
- 'xep_0086', # Legacy Error Codes
- 'xep_0091', # Legacy Delayed Delivery
- 'xep_0092', # Software Version
- 'xep_0106', # JID Escaping
- 'xep_0107', # User Mood
- 'xep_0108', # User Activity
- 'xep_0115', # Entity Capabilities
- 'xep_0118', # User Tune
- 'xep_0122', # Data Forms Validation
- 'xep_0128', # Extended Service Discovery
- 'xep_0131', # Standard Headers and Internet Metadata
- 'xep_0133', # Service Administration
- 'xep_0152', # Reachability Addresses
- 'xep_0153', # vCard-Based Avatars
- 'xep_0163', # Personal Eventing Protocol
- 'xep_0172', # User Nickname
- 'xep_0184', # Message Receipts
- 'xep_0186', # Invisible Command
- 'xep_0191', # Blocking Command
- 'xep_0196', # User Gaming
- 'xep_0198', # Stream Management
- 'xep_0199', # Ping
- 'xep_0202', # Entity Time
- 'xep_0203', # Delayed Delivery
- 'xep_0221', # Data Forms Media Element
- 'xep_0222', # Persistent Storage of Public Data via Pubsub
- 'xep_0223', # Persistent Storage of Private Data via Pubsub
- 'xep_0224', # Attention
- 'xep_0231', # Bits of Binary
- 'xep_0235', # OAuth Over XMPP
- 'xep_0242', # XMPP Client Compliance 2009
- 'xep_0249', # Direct MUC Invitations
- 'xep_0256', # Last Activity in Presence
- 'xep_0257', # Client Certificate Management for SASL EXTERNAL
- 'xep_0258', # Security Labels in XMPP
- 'xep_0270', # XMPP Compliance Suites 2010
- 'xep_0279', # Server IP Check
- 'xep_0280', # Message Carbons
- 'xep_0297', # Stanza Forwarding
- 'xep_0302', # XMPP Compliance Suites 2012
- 'xep_0308', # Last Message Correction
- 'xep_0313', # Message Archive Management
- 'xep_0319', # Last User Interaction in Presence
- 'xep_0323', # IoT Systems Sensor Data
- 'xep_0325', # IoT Systems Control
- 'xep_0332', # HTTP Over XMPP Transport
-]
diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py
deleted file mode 100644
index 67675908..00000000
--- a/sleekxmpp/plugins/base.py
+++ /dev/null
@@ -1,360 +0,0 @@
-# -*- encoding: utf-8 -*-
-
-"""
- sleekxmpp.plugins.base
- ~~~~~~~~~~~~~~~~~~~~~~
-
- This module provides XMPP functionality that
- is specific to client connections.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2012 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-import sys
-import copy
-import logging
-import threading
-
-
-if sys.version_info >= (3, 0):
- unicode = str
-
-
-log = logging.getLogger(__name__)
-
-
-#: Associate short string names of plugins with implementations. The
-#: plugin names are based on the spec used by the plugin, such as
-#: `'xep_0030'` for a plugin that implements XEP-0030.
-PLUGIN_REGISTRY = {}
-
-#: In order to do cascading plugin disabling, reverse dependencies
-#: must be tracked.
-PLUGIN_DEPENDENTS = {}
-
-#: Only allow one thread to manipulate the plugin registry at a time.
-REGISTRY_LOCK = threading.RLock()
-
-
-class PluginNotFound(Exception):
- """Raised if an unknown plugin is accessed."""
-
-
-def register_plugin(impl, name=None):
- """Add a new plugin implementation to the registry.
-
- :param class impl: The plugin class.
-
- The implementation class must provide a :attr:`~BasePlugin.name`
- value that will be used as a short name for enabling and disabling
- the plugin. The name should be based on the specification used by
- the plugin. For example, a plugin implementing XEP-0030 would be
- named `'xep_0030'`.
- """
- if name is None:
- name = impl.name
- with REGISTRY_LOCK:
- PLUGIN_REGISTRY[name] = impl
- if name not in PLUGIN_DEPENDENTS:
- PLUGIN_DEPENDENTS[name] = set()
- for dep in impl.dependencies:
- if dep not in PLUGIN_DEPENDENTS:
- PLUGIN_DEPENDENTS[dep] = set()
- PLUGIN_DEPENDENTS[dep].add(name)
-
-
-def load_plugin(name, module=None):
- """Find and import a plugin module so that it can be registered.
-
- This function is called to import plugins that have selected for
- enabling, but no matching registered plugin has been found.
-
- :param str name: The name of the plugin. It is expected that
- plugins are in packages matching their name,
- even though the plugin class name does not
- have to match.
- :param str module: The name of the base module to search
- for the plugin.
- """
- try:
- if not module:
- try:
- module = 'sleekxmpp.plugins.%s' % name
- __import__(module)
- mod = sys.modules[module]
- except ImportError:
- module = 'sleekxmpp.features.%s' % name
- __import__(module)
- mod = sys.modules[module]
- elif isinstance(module, (str, unicode)):
- __import__(module)
- mod = sys.modules[module]
- else:
- mod = module
-
- # Add older style plugins to the registry.
- if hasattr(mod, name):
- plugin = getattr(mod, name)
- if hasattr(plugin, 'xep') or hasattr(plugin, 'rfc'):
- plugin.name = name
- # Mark the plugin as an older style plugin so
- # we can work around dependency issues.
- plugin.old_style = True
- register_plugin(plugin, name)
- except ImportError:
- log.exception("Unable to load plugin: %s", name)
-
-
-class PluginManager(object):
- def __init__(self, xmpp, config=None):
- #: We will track all enabled plugins in a set so that we
- #: can enable plugins in batches and pull in dependencies
- #: without problems.
- self._enabled = set()
-
- #: Maintain references to active plugins.
- self._plugins = {}
-
- self._plugin_lock = threading.RLock()
-
- #: Globally set default plugin configuration. This will
- #: be used for plugins that are auto-enabled through
- #: dependency loading.
- self.config = config if config else {}
-
- self.xmpp = xmpp
-
- def register(self, plugin, enable=True):
- """Register a new plugin, and optionally enable it.
-
- :param class plugin: The implementation class of the plugin
- to register.
- :param bool enable: If ``True``, immediately enable the
- plugin after registration.
- """
- register_plugin(plugin)
- if enable:
- self.enable(plugin.name)
-
- def enable(self, name, config=None, enabled=None):
- """Enable a plugin, including any dependencies.
-
- :param string name: The short name of the plugin.
- :param dict config: Optional settings dictionary for
- configuring plugin behaviour.
- """
- top_level = False
- if enabled is None:
- enabled = set()
-
- with self._plugin_lock:
- if name not in self._enabled:
- enabled.add(name)
- self._enabled.add(name)
- if not self.registered(name):
- load_plugin(name)
-
- plugin_class = PLUGIN_REGISTRY.get(name, None)
- if not plugin_class:
- raise PluginNotFound(name)
-
- if config is None:
- config = self.config.get(name, None)
-
- plugin = plugin_class(self.xmpp, config)
- self._plugins[name] = plugin
- for dep in plugin.dependencies:
- self.enable(dep, enabled=enabled)
- plugin._init()
-
- if top_level:
- for name in enabled:
- if hasattr(self.plugins[name], 'old_style'):
- # Older style plugins require post_init()
- # to run just before stream processing begins,
- # so we don't call it here.
- pass
- self.plugins[name].post_init()
-
- def enable_all(self, names=None, config=None):
- """Enable all registered plugins.
-
- :param list names: A list of plugin names to enable. If
- none are provided, all registered plugins
- will be enabled.
- :param dict config: A dictionary mapping plugin names to
- configuration dictionaries, as used by
- :meth:`~PluginManager.enable`.
- """
- names = names if names else PLUGIN_REGISTRY.keys()
- if config is None:
- config = {}
- for name in names:
- self.enable(name, config.get(name, {}))
-
- def enabled(self, name):
- """Check if a plugin has been enabled.
-
- :param string name: The name of the plugin to check.
- :return: boolean
- """
- return name in self._enabled
-
- def registered(self, name):
- """Check if a plugin has been registered.
-
- :param string name: The name of the plugin to check.
- :return: boolean
- """
- return name in PLUGIN_REGISTRY
-
- def disable(self, name, _disabled=None):
- """Disable a plugin, including any dependent upon it.
-
- :param string name: The name of the plugin to disable.
- :param set _disabled: Private set used to track the
- disabled status of plugins during
- the cascading process.
- """
- if _disabled is None:
- _disabled = set()
- with self._plugin_lock:
- if name not in _disabled and name in self._enabled:
- _disabled.add(name)
- plugin = self._plugins.get(name, None)
- if plugin is None:
- raise PluginNotFound(name)
- for dep in PLUGIN_DEPENDENTS[name]:
- self.disable(dep, _disabled)
- plugin._end()
- if name in self._enabled:
- self._enabled.remove(name)
- del self._plugins[name]
-
- def __keys__(self):
- """Return the set of enabled plugins."""
- return self._plugins.keys()
-
- def __getitem__(self, name):
- """
- Allow plugins to be accessed through the manager as if
- it were a dictionary.
- """
- plugin = self._plugins.get(name, None)
- if plugin is None:
- raise PluginNotFound(name)
- return plugin
-
- def __iter__(self):
- """Return an iterator over the set of enabled plugins."""
- return self._plugins.__iter__()
-
- def __len__(self):
- """Return the number of enabled plugins."""
- return len(self._plugins)
-
-
-class BasePlugin(object):
-
- #: A short name for the plugin based on the implemented specification.
- #: For example, a plugin for XEP-0030 would use `'xep_0030'`.
- name = ''
-
- #: A longer name for the plugin, describing its purpose. For example,
- #: a plugin for XEP-0030 would use `'Service Discovery'` as its
- #: description value.
- description = ''
-
- #: Some plugins may depend on others in order to function properly.
- #: Any plugin names included in :attr:`~BasePlugin.dependencies` will
- #: be initialized as needed if this plugin is enabled.
- dependencies = set()
-
- #: The basic, standard configuration for the plugin, which may
- #: be overridden when initializing the plugin. The configuration
- #: fields included here may be accessed directly as attributes of
- #: the plugin. For example, including the configuration field 'foo'
- #: would mean accessing `plugin.foo` returns the current value of
- #: `plugin.config['foo']`.
- default_config = {}
-
- def __init__(self, xmpp, config=None):
- self.xmpp = xmpp
- if self.xmpp:
- self.api = self.xmpp.api.wrap(self.name)
-
- #: A plugin's behaviour may be configurable, in which case those
- #: configuration settings will be provided as a dictionary.
- self.config = copy.copy(self.default_config)
- if config:
- self.config.update(config)
-
- def __getattr__(self, key):
- """Provide direct access to configuration fields.
-
- If the standard configuration includes the option `'foo'`, then
- accessing `self.foo` should be the same as `self.config['foo']`.
- """
- if key in self.default_config:
- return self.config.get(key, None)
- else:
- return object.__getattribute__(self, key)
-
- def __setattr__(self, key, value):
- """Provide direct assignment to configuration fields.
-
- If the standard configuration includes the option `'foo'`, then
- assigning to `self.foo` should be the same as assigning to
- `self.config['foo']`.
- """
- if key in self.default_config:
- self.config[key] = value
- else:
- super(BasePlugin, self).__setattr__(key, value)
-
- def _init(self):
- """Initialize plugin state, such as registering event handlers.
-
- Also sets up required event handlers.
- """
- if self.xmpp is not None:
- self.xmpp.add_event_handler('session_bind', self.session_bind)
- if self.xmpp.session_bind_event.is_set():
- self.session_bind(self.xmpp.boundjid.full)
- self.plugin_init()
- log.debug('Loaded Plugin: %s', self.description)
-
- def _end(self):
- """Cleanup plugin state, and prepare for plugin removal.
-
- Also removes required event handlers.
- """
- if self.xmpp is not None:
- self.xmpp.del_event_handler('session_bind', self.session_bind)
- self.plugin_end()
- log.debug('Disabled Plugin: %s' % self.description)
-
- def plugin_init(self):
- """Initialize plugin state, such as registering event handlers."""
- pass
-
- def plugin_end(self):
- """Cleanup plugin state, and prepare for plugin removal."""
- pass
-
- def session_bind(self, jid):
- """Initialize plugin state based on the bound JID."""
- pass
-
- def post_init(self):
- """Initialize any cross-plugin state.
-
- Only needed if the plugin has circular dependencies.
- """
- pass
-
-
-base_plugin = BasePlugin
diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py
deleted file mode 100644
index fc97a2ab..00000000
--- a/sleekxmpp/plugins/gmail_notify.py
+++ /dev/null
@@ -1,149 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-from . import base
-from .. xmlstream.handler.callback import Callback
-from .. xmlstream.matcher.xpath import MatchXPath
-from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
-from .. stanza.iq import Iq
-
-
-log = logging.getLogger(__name__)
-
-
-class GmailQuery(ElementBase):
- namespace = 'google:mail:notify'
- name = 'query'
- plugin_attrib = 'gmail'
- interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
-
- def getSearch(self):
- return self['q']
-
- def setSearch(self, search):
- self['q'] = search
-
- def delSearch(self):
- del self['q']
-
-
-class MailBox(ElementBase):
- namespace = 'google:mail:notify'
- name = 'mailbox'
- plugin_attrib = 'mailbox'
- interfaces = set(('result-time', 'total-matched', 'total-estimate',
- 'url', 'threads', 'matched', 'estimate'))
-
- def getThreads(self):
- threads = []
- for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
- MailThread.name)):
- threads.append(MailThread(xml=threadXML, parent=None))
- return threads
-
- def getMatched(self):
- return self['total-matched']
-
- def getEstimate(self):
- return self['total-estimate'] == '1'
-
-
-class MailThread(ElementBase):
- namespace = 'google:mail:notify'
- name = 'mail-thread-info'
- plugin_attrib = 'thread'
- interfaces = set(('tid', 'participation', 'messages', 'date',
- 'senders', 'url', 'labels', 'subject', 'snippet'))
- sub_interfaces = set(('labels', 'subject', 'snippet'))
-
- def getSenders(self):
- senders = []
- sendersXML = self.xml.find('{%s}senders' % self.namespace)
- if sendersXML is not None:
- for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
- senders.append(MailSender(xml=senderXML, parent=None))
- return senders
-
-
-class MailSender(ElementBase):
- namespace = 'google:mail:notify'
- name = 'sender'
- plugin_attrib = 'sender'
- interfaces = set(('address', 'name', 'originator', 'unread'))
-
- def getOriginator(self):
- return self.xml.attrib.get('originator', '0') == '1'
-
- def getUnread(self):
- return self.xml.attrib.get('unread', '0') == '1'
-
-
-class NewMail(ElementBase):
- namespace = 'google:mail:notify'
- name = 'new-mail'
- plugin_attrib = 'new-mail'
-
-
-class gmail_notify(base.base_plugin):
- """
- Google Talk: Gmail Notifications
- """
-
- def plugin_init(self):
- self.description = 'Google Talk: Gmail Notifications'
-
- self.xmpp.registerHandler(
- Callback('Gmail Result',
- MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
- MailBox.namespace,
- MailBox.name)),
- self.handle_gmail))
-
- self.xmpp.registerHandler(
- Callback('Gmail New Mail',
- MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
- NewMail.namespace,
- NewMail.name)),
- self.handle_new_mail))
-
- registerStanzaPlugin(Iq, GmailQuery)
- registerStanzaPlugin(Iq, MailBox)
- registerStanzaPlugin(Iq, NewMail)
-
- self.last_result_time = None
-
- def handle_gmail(self, iq):
- mailbox = iq['mailbox']
- approx = ' approximately' if mailbox['estimated'] else ''
- log.info('Gmail: Received%s %s emails', approx, mailbox['total-matched'])
- self.last_result_time = mailbox['result-time']
- self.xmpp.event('gmail_messages', iq)
-
- def handle_new_mail(self, iq):
- log.info("Gmail: New emails received!")
- self.xmpp.event('gmail_notify')
- self.checkEmail()
-
- def getEmail(self, query=None):
- return self.search(query)
-
- def checkEmail(self):
- return self.search(newer=self.last_result_time)
-
- def search(self, query=None, newer=None):
- if query is None:
- log.info("Gmail: Checking for new emails")
- else:
- log.info('Gmail: Searching for emails matching: "%s"', query)
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = self.xmpp.boundjid.bare
- iq['gmail']['q'] = query
- iq['gmail']['newer-than-time'] = newer
- return iq.send()
diff --git a/sleekxmpp/plugins/google/__init__.py b/sleekxmpp/plugins/google/__init__.py
deleted file mode 100644
index bd7ca123..00000000
--- a/sleekxmpp/plugins/google/__init__.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin, BasePlugin
-
-from sleekxmpp.plugins.google.gmail import Gmail
-from sleekxmpp.plugins.google.auth import GoogleAuth
-from sleekxmpp.plugins.google.settings import GoogleSettings
-from sleekxmpp.plugins.google.nosave import GoogleNoSave
-
-
-class Google(BasePlugin):
-
- """
- Google: Custom GTalk Features
-
- Also see: <https://developers.google.com/talk/jep_extensions/extensions>
- """
-
- name = 'google'
- description = 'Google: Custom GTalk Features'
- dependencies = set([
- 'gmail',
- 'google_settings',
- 'google_nosave',
- 'google_auth'
- ])
-
- def __getitem__(self, attr):
- if attr in ('settings', 'nosave', 'auth'):
- return self.xmpp['google_%s' % attr]
- elif attr == 'gmail':
- return self.xmpp['gmail']
- else:
- raise KeyError(attr)
-
-
-register_plugin(Gmail)
-register_plugin(GoogleAuth)
-register_plugin(GoogleSettings)
-register_plugin(GoogleNoSave)
-register_plugin(Google)
diff --git a/sleekxmpp/plugins/google/auth/__init__.py b/sleekxmpp/plugins/google/auth/__init__.py
deleted file mode 100644
index 5a8feb0d..00000000
--- a/sleekxmpp/plugins/google/auth/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.google.auth import stanza
-from sleekxmpp.plugins.google.auth.auth import GoogleAuth
diff --git a/sleekxmpp/plugins/google/auth/auth.py b/sleekxmpp/plugins/google/auth/auth.py
deleted file mode 100644
index 042bd404..00000000
--- a/sleekxmpp/plugins/google/auth/auth.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google.auth import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class GoogleAuth(BasePlugin):
-
- """
- Google: Auth Extensions (JID Domain Discovery, OAuth2)
-
- Also see:
- <https://developers.google.com/talk/jep_extensions/jid_domain_change>
- <https://developers.google.com/talk/jep_extensions/oauth>
- """
-
- name = 'google_auth'
- description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
- dependencies = set(['feature_mechanisms'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
-
- register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
- stanza.GoogleAuth)
-
- self.xmpp.add_filter('out', self._auth)
-
- def plugin_end(self):
- self.xmpp.del_filter('out', self._auth)
-
- def _auth(self, stanza):
- if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
- stanza.stream = self.xmpp
- stanza['google']['client_uses_full_bind_result'] = True
- if stanza['mechanism'] == 'X-OAUTH2':
- stanza['google']['service'] = 'oauth2'
- print(stanza)
- return stanza
diff --git a/sleekxmpp/plugins/google/auth/stanza.py b/sleekxmpp/plugins/google/auth/stanza.py
deleted file mode 100644
index 2d13f85a..00000000
--- a/sleekxmpp/plugins/google/auth/stanza.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class GoogleAuth(ElementBase):
- name = 'auth'
- namespace = 'http://www.google.com/talk/protocol/auth'
- plugin_attrib = 'google'
- interfaces = set(['client_uses_full_bind_result', 'service'])
-
- discovery_attr= '{%s}client-uses-full-bind-result' % namespace
- service_attr= '{%s}service' % namespace
-
- def setup(self, xml):
- """Don't create XML for the plugin."""
- self.xml = ET.Element('')
- print('setting up google extension')
-
- def get_client_uses_full_bind_result(self):
- return self.parent()._get_attr(self.discovery_attr) == 'true'
-
- def set_client_uses_full_bind_result(self, value):
- print('>>>', value)
- if value in (True, 'true'):
- self.parent()._set_attr(self.discovery_attr, 'true')
- else:
- self.parent()._del_attr(self.discovery_attr)
-
- def del_client_uses_full_bind_result(self):
- self.parent()._del_attr(self.discovery_attr)
-
- def get_service(self):
- return self.parent()._get_attr(self.service_attr, '')
-
- def set_service(self, value):
- if value:
- self.parent()._set_attr(self.service_attr, value)
- else:
- self.parent()._del_attr(self.service_attr)
-
- def del_service(self):
- self.parent()._del_attr(self.service_attr)
diff --git a/sleekxmpp/plugins/google/gmail/__init__.py b/sleekxmpp/plugins/google/gmail/__init__.py
deleted file mode 100644
index a92e363b..00000000
--- a/sleekxmpp/plugins/google/gmail/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.google.gmail import stanza
-from sleekxmpp.plugins.google.gmail.notifications import Gmail
diff --git a/sleekxmpp/plugins/google/gmail/notifications.py b/sleekxmpp/plugins/google/gmail/notifications.py
deleted file mode 100644
index e65b2ca7..00000000
--- a/sleekxmpp/plugins/google/gmail/notifications.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Iq
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google.gmail import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class Gmail(BasePlugin):
-
- """
- Google: Gmail Notifications
-
- Also see <https://developers.google.com/talk/jep_extensions/gmail>.
- """
-
- name = 'gmail'
- description = 'Google: Gmail Notifications'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, stanza.GmailQuery)
- register_stanza_plugin(Iq, stanza.MailBox)
- register_stanza_plugin(Iq, stanza.NewMail)
-
- self.xmpp.register_handler(
- Callback('Gmail New Mail',
- MatchXPath('{%s}iq/{%s}%s' % (
- self.xmpp.default_ns,
- stanza.NewMail.namespace,
- stanza.NewMail.name)),
- self._handle_new_mail))
-
- self._last_result_time = None
- self._last_result_tid = None
-
- def plugin_end(self):
- self.xmpp.remove_handler('Gmail New Mail')
-
- def _handle_new_mail(self, iq):
- log.info('Gmail: New email!')
- iq.reply().send()
- self.xmpp.event('gmail_notification')
-
- def check(self, block=True, timeout=None, callback=None):
- last_time = self._last_result_time
- last_tid = self._last_result_tid
-
- if not block:
- callback = lambda iq: self._update_last_results(iq, callback)
-
- resp = self.search(newer_time=last_time,
- newer_tid=last_tid,
- block=block,
- timeout=timeout,
- callback=callback)
-
- if block:
- self._update_last_results(resp)
- return resp
-
- def _update_last_results(self, iq, callback=None):
- self._last_result_time = iq['gmail_messages']['result_time']
- threads = iq['gmail_messages']['threads']
- if threads:
- self._last_result_tid = threads[0]['tid']
- if callback:
- callback(iq)
-
- def search(self, query=None, newer_time=None, newer_tid=None, block=True,
- timeout=None, callback=None):
- if not query:
- log.info('Gmail: Checking for new email')
- else:
- log.info('Gmail: Searching for emails matching: "%s"', query)
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = self.xmpp.boundjid.bare
- iq['gmail']['search'] = query
- iq['gmail']['newer_than_time'] = newer_time
- iq['gmail']['newer_than_tid'] = newer_tid
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/google/gmail/stanza.py b/sleekxmpp/plugins/google/gmail/stanza.py
deleted file mode 100644
index e7e308e1..00000000
--- a/sleekxmpp/plugins/google/gmail/stanza.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class GmailQuery(ElementBase):
- namespace = 'google:mail:notify'
- name = 'query'
- plugin_attrib = 'gmail'
- interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
-
- def get_search(self):
- return self._get_attr('q', '')
-
- def set_search(self, search):
- self._set_attr('q', search)
-
- def del_search(self):
- self._del_attr('q')
-
- def get_newer_than_time(self):
- return self._get_attr('newer-than-time', '')
-
- def set_newer_than_time(self, value):
- self._set_attr('newer-than-time', value)
-
- def del_newer_than_time(self):
- self._del_attr('newer-than-time')
-
- def get_newer_than_tid(self):
- return self._get_attr('newer-than-tid', '')
-
- def set_newer_than_tid(self, value):
- self._set_attr('newer-than-tid', value)
-
- def del_newer_than_tid(self):
- self._del_attr('newer-than-tid')
-
-
-class MailBox(ElementBase):
- namespace = 'google:mail:notify'
- name = 'mailbox'
- plugin_attrib = 'gmail_messages'
- interfaces = set(['result_time', 'url', 'matched', 'estimate'])
-
- def get_matched(self):
- return self._get_attr('total-matched', '')
-
- def get_estimate(self):
- return self._get_attr('total-estimate', '') == '1'
-
- def get_result_time(self):
- return self._get_attr('result-time', '')
-
-
-class MailThread(ElementBase):
- namespace = 'google:mail:notify'
- name = 'mail-thread-info'
- plugin_attrib = 'thread'
- plugin_multi_attrib = 'threads'
- interfaces = set(['tid', 'participation', 'messages', 'date',
- 'senders', 'url', 'labels', 'subject', 'snippet'])
- sub_interfaces = set(['labels', 'subject', 'snippet'])
-
- def get_senders(self):
- result = []
- senders = self.xml.findall('{%s}senders/{%s}sender' % (
- self.namespace, self.namespace))
-
- for sender in senders:
- result.append(MailSender(xml=sender))
-
- return result
-
-
-class MailSender(ElementBase):
- namespace = 'google:mail:notify'
- name = 'sender'
- plugin_attrib = name
- interfaces = set(['address', 'name', 'originator', 'unread'])
-
- def get_originator(self):
- return self.xml.attrib.get('originator', '0') == '1'
-
- def get_unread(self):
- return self.xml.attrib.get('unread', '0') == '1'
-
-
-class NewMail(ElementBase):
- namespace = 'google:mail:notify'
- name = 'new-mail'
- plugin_attrib = 'gmail_notification'
-
-
-register_stanza_plugin(MailBox, MailThread, iterable=True)
diff --git a/sleekxmpp/plugins/google/nosave/__init__.py b/sleekxmpp/plugins/google/nosave/__init__.py
deleted file mode 100644
index 57847af5..00000000
--- a/sleekxmpp/plugins/google/nosave/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.google.nosave import stanza
-from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave
diff --git a/sleekxmpp/plugins/google/nosave/nosave.py b/sleekxmpp/plugins/google/nosave/nosave.py
deleted file mode 100644
index d6bef615..00000000
--- a/sleekxmpp/plugins/google/nosave/nosave.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Iq, Message
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google.nosave import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class GoogleNoSave(BasePlugin):
-
- """
- Google: Off the Record Chats
-
- NOTE: This is NOT an encryption method.
-
- Also see <https://developers.google.com/talk/jep_extensions/otr>.
- """
-
- name = 'google_nosave'
- description = 'Google: Off the Record Chats'
- dependencies = set(['google_settings'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, stanza.NoSave)
- register_stanza_plugin(Iq, stanza.NoSaveQuery)
-
- self.xmpp.register_handler(
- Callback('Google Nosave',
- StanzaPath('iq@type=set/google_nosave'),
- self._handle_nosave_change))
-
- def plugin_end(self):
- self.xmpp.remove_handler('Google Nosave')
-
- def enable(self, jid=None, block=True, timeout=None, callback=None):
- if jid is None:
- self.xmpp['google_settings'].update({'archiving_enabled': False},
- block=block, timeout=timeout, callback=callback)
- else:
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['google_nosave']['item']['jid'] = jid
- iq['google_nosave']['item']['value'] = True
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def disable(self, jid=None, block=True, timeout=None, callback=None):
- if jid is None:
- self.xmpp['google_settings'].update({'archiving_enabled': True},
- block=block, timeout=timeout, callback=callback)
- else:
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['google_nosave']['item']['jid'] = jid
- iq['google_nosave']['item']['value'] = False
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def get(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq.enable('google_nosave')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def _handle_nosave_change(self, iq):
- reply = self.xmpp.Iq()
- reply['type'] = 'result'
- reply['id'] = iq['id']
- reply['to'] = iq['from']
- reply.send()
- self.xmpp.event('google_nosave_change', iq)
diff --git a/sleekxmpp/plugins/google/nosave/stanza.py b/sleekxmpp/plugins/google/nosave/stanza.py
deleted file mode 100644
index 791d4b0c..00000000
--- a/sleekxmpp/plugins/google/nosave/stanza.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class NoSave(ElementBase):
- name = 'x'
- namespace = 'google:nosave'
- plugin_attrib = 'google_nosave'
- interfaces = set(['value'])
-
- def get_value(self):
- return self._get_attr('value', '') == 'enabled'
-
- def set_value(self, value):
- self._set_attr('value', 'enabled' if value else 'disabled')
-
-
-class NoSaveQuery(ElementBase):
- name = 'query'
- namespace = 'google:nosave'
- plugin_attrib = 'google_nosave'
- interfaces = set()
-
-
-class Item(ElementBase):
- name = 'item'
- namespace = 'google:nosave'
- plugin_attrib = 'item'
- plugin_multi_attrib = 'items'
- interfaces = set(['jid', 'source', 'value'])
-
- def get_value(self):
- return self._get_attr('value', '') == 'enabled'
-
- def set_value(self, value):
- self._set_attr('value', 'enabled' if value else 'disabled')
-
- def get_jid(self):
- return JID(self._get_attr('jid', ''))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_source(self):
- return JID(self._get_attr('source', ''))
-
- def set_source(self, value):
- self._set_attr('source', str(value))
-
-
-register_stanza_plugin(NoSaveQuery, Item)
diff --git a/sleekxmpp/plugins/google/settings/__init__.py b/sleekxmpp/plugins/google/settings/__init__.py
deleted file mode 100644
index c3a0471d..00000000
--- a/sleekxmpp/plugins/google/settings/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.google.settings import stanza
-from sleekxmpp.plugins.google.settings.settings import GoogleSettings
diff --git a/sleekxmpp/plugins/google/settings/settings.py b/sleekxmpp/plugins/google/settings/settings.py
deleted file mode 100644
index 591956fc..00000000
--- a/sleekxmpp/plugins/google/settings/settings.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Iq
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google.settings import stanza
-
-
-class GoogleSettings(BasePlugin):
-
- """
- Google: Gmail Notifications
-
- Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
- """
-
- name = 'google_settings'
- description = 'Google: User Settings'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, stanza.UserSettings)
-
- self.xmpp.register_handler(
- Callback('Google Settings',
- StanzaPath('iq@type=set/google_settings'),
- self._handle_settings_change))
-
- def plugin_end(self):
- self.xmpp.remove_handler('Google Settings')
-
- def get(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq.enable('google_settings')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def update(self, settings, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq.enable('google_settings')
-
- for setting, value in settings.items():
- iq['google_settings'][setting] = value
-
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def _handle_settings_change(self, iq):
- reply = self.xmpp.Iq()
- reply['type'] = 'result'
- reply['id'] = iq['id']
- reply['to'] = iq['from']
- reply.send()
- self.xmpp.event('google_settings_change', iq)
diff --git a/sleekxmpp/plugins/google/settings/stanza.py b/sleekxmpp/plugins/google/settings/stanza.py
deleted file mode 100644
index d8161770..00000000
--- a/sleekxmpp/plugins/google/settings/stanza.py
+++ /dev/null
@@ -1,110 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
-
-
-class UserSettings(ElementBase):
- name = 'usersetting'
- namespace = 'google:setting'
- plugin_attrib = 'google_settings'
- interfaces = set(['auto_accept_suggestions',
- 'mail_notifications',
- 'archiving_enabled',
- 'gmail',
- 'email_verified',
- 'domain_privacy_notice',
- 'display_name'])
-
- def _get_setting(self, setting):
- xml = self.xml.find('{%s}%s' % (self.namespace, setting))
- if xml is not None:
- return xml.attrib.get('value', '') == 'true'
- return False
-
- def _set_setting(self, setting, value):
- self._del_setting(setting)
- if value in (True, False):
- xml = ET.Element('{%s}%s' % (self.namespace, setting))
- xml.attrib['value'] = 'true' if value else 'false'
- self.xml.append(xml)
-
- def _del_setting(self, setting):
- xml = self.xml.find('{%s}%s' % (self.namespace, setting))
- if xml is not None:
- self.xml.remove(xml)
-
- def get_display_name(self):
- xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
- if xml is not None:
- return xml.attrib.get('value', '')
- return ''
-
- def set_display_name(self, value):
- self._del_setting(setting)
- if value:
- xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
- xml.attrib['value'] = value
- self.xml.append(xml)
-
- def del_display_name(self):
- self._del_setting('displayname')
-
- def get_auto_accept_suggestions(self):
- return self._get_setting('autoacceptsuggestions')
-
- def get_mail_notifications(self):
- return self._get_setting('mailnotifications')
-
- def get_archiving_enabled(self):
- return self._get_setting('archivingenabled')
-
- def get_gmail(self):
- return self._get_setting('gmail')
-
- def get_email_verified(self):
- return self._get_setting('emailverified')
-
- def get_domain_privacy_notice(self):
- return self._get_setting('domainprivacynotice')
-
- def set_auto_accept_suggestions(self, value):
- self._set_setting('autoacceptsuggestions', value)
-
- def set_mail_notifications(self, value):
- self._set_setting('mailnotifications', value)
-
- def set_archiving_enabled(self, value):
- self._set_setting('archivingenabled', value)
-
- def set_gmail(self, value):
- self._set_setting('gmail', value)
-
- def set_email_verified(self, value):
- self._set_setting('emailverified', value)
-
- def set_domain_privacy_notice(self, value):
- self._set_setting('domainprivacynotice', value)
-
- def del_auto_accept_suggestions(self):
- self._del_setting('autoacceptsuggestions')
-
- def del_mail_notifications(self):
- self._del_setting('mailnotifications')
-
- def del_archiving_enabled(self):
- self._del_setting('archivingenabled')
-
- def del_gmail(self):
- self._del_setting('gmail')
-
- def del_email_verified(self):
- self._del_setting('emailverified')
-
- def del_domain_privacy_notice(self):
- self._del_setting('domainprivacynotice')
diff --git a/sleekxmpp/plugins/xep_0004/__init__.py b/sleekxmpp/plugins/xep_0004/__init__.py
deleted file mode 100644
index 2cd18ec8..00000000
--- a/sleekxmpp/plugins/xep_0004/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0004.stanza import Form
-from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
-from sleekxmpp.plugins.xep_0004.dataforms import XEP_0004
-
-
-register_plugin(XEP_0004)
-
-
-# Retain some backwards compatibility
-xep_0004 = XEP_0004
-xep_0004.makeForm = xep_0004.make_form
-xep_0004.buildForm = xep_0004.build_form
diff --git a/sleekxmpp/plugins/xep_0004/dataforms.py b/sleekxmpp/plugins/xep_0004/dataforms.py
deleted file mode 100644
index dde6e6a8..00000000
--- a/sleekxmpp/plugins/xep_0004/dataforms.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Message
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0004 import stanza
-from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
-
-
-class XEP_0004(BasePlugin):
-
- """
- XEP-0004: Data Forms
- """
-
- name = 'xep_0004'
- description = 'XEP-0004: Data Forms'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('Data Form',
- StanzaPath('message/form'),
- self.handle_form))
-
- register_stanza_plugin(FormField, FieldOption, iterable=True)
- register_stanza_plugin(Form, FormField, iterable=True)
- register_stanza_plugin(Message, Form)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Data Form')
- self.xmpp['xep_0030'].del_feature(feature='jabber:x:data')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('jabber:x:data')
-
- def make_form(self, ftype='form', title='', instructions=''):
- f = Form()
- f['type'] = ftype
- f['title'] = title
- f['instructions'] = instructions
- return f
-
- def handle_form(self, message):
- self.xmpp.event("message_xform", message)
-
- def build_form(self, xml):
- return Form(xml=xml)
diff --git a/sleekxmpp/plugins/xep_0004/stanza/__init__.py b/sleekxmpp/plugins/xep_0004/stanza/__init__.py
deleted file mode 100644
index 6ad35298..00000000
--- a/sleekxmpp/plugins/xep_0004/stanza/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption
-from sleekxmpp.plugins.xep_0004.stanza.form import Form
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
deleted file mode 100644
index 73e48758..00000000
--- a/sleekxmpp/plugins/xep_0004/stanza/field.py
+++ /dev/null
@@ -1,185 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class FormField(ElementBase):
- namespace = 'jabber:x:data'
- name = 'field'
- plugin_attrib = 'field'
- plugin_multi_attrib = 'fields'
- interfaces = set(('answer', 'desc', 'required', 'value',
- 'label', 'type', 'var'))
- sub_interfaces = set(('desc',))
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
- 'jid-single', 'list-multi', 'list-single',
- 'text-multi', 'text-private', 'text-single'))
-
- true_values = set((True, '1', 'true'))
- option_types = set(('list-multi', 'list-single'))
- multi_line_types = set(('hidden', 'text-multi'))
- multi_value_types = set(('hidden', 'jid-multi',
- 'list-multi', 'text-multi'))
-
- def setup(self, xml=None):
- if ElementBase.setup(self, xml):
- self._type = None
- else:
- self._type = self['type']
-
- def set_type(self, value):
- self._set_attr('type', value)
- if value:
- self._type = value
-
- def add_option(self, label='', value=''):
- if self._type is None or self._type in self.option_types:
- opt = FieldOption()
- opt['label'] = label
- opt['value'] = value
- self.append(opt)
- else:
- raise ValueError("Cannot add options to " + \
- "a %s field." % self['type'])
-
- def del_options(self):
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- self.xml.remove(optXML)
-
- def del_required(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- if reqXML is not None:
- self.xml.remove(reqXML)
-
- def del_value(self):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- for valXML in valsXML:
- self.xml.remove(valXML)
-
- def get_answer(self):
- return self['value']
-
- def get_options(self):
- options = []
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- opt = FieldOption(xml=optXML)
- options.append({'label': opt['label'], 'value': opt['value']})
- return options
-
- def get_required(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- return reqXML is not None
-
- def get_value(self, convert=True):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- if len(valsXML) == 0:
- return None
- elif self._type == 'boolean':
- if convert:
- return valsXML[0].text in self.true_values
- return valsXML[0].text
- elif self._type in self.multi_value_types or len(valsXML) > 1:
- values = []
- for valXML in valsXML:
- if valXML.text is None:
- valXML.text = ''
- values.append(valXML.text)
- if self._type == 'text-multi' and convert:
- values = "\n".join(values)
- return values
- else:
- if valsXML[0].text is None:
- return ''
- return valsXML[0].text
-
- def set_answer(self, answer):
- self['value'] = answer
-
- def set_false(self):
- self['value'] = False
-
- def set_options(self, options):
- for value in options:
- if isinstance(value, dict):
- self.add_option(**value)
- else:
- self.add_option(value=value)
-
- def set_required(self, required):
- exists = self['required']
- if not exists and required:
- self.xml.append(ET.Element('{%s}required' % self.namespace))
- elif exists and not required:
- del self['required']
-
- def set_true(self):
- self['value'] = True
-
- def set_value(self, value):
- del self['value']
- valXMLName = '{%s}value' % self.namespace
-
- if self._type == 'boolean':
- if value in self.true_values:
- valXML = ET.Element(valXMLName)
- valXML.text = '1'
- self.xml.append(valXML)
- else:
- valXML = ET.Element(valXMLName)
- valXML.text = '0'
- self.xml.append(valXML)
- elif self._type in self.multi_value_types or self._type in ('', None):
- if isinstance(value, bool):
- value = [value]
- if not isinstance(value, list):
- value = value.replace('\r', '')
- value = value.split('\n')
- for val in value:
- if self._type in ('', None) and val in self.true_values:
- val = '1'
- valXML = ET.Element(valXMLName)
- valXML.text = val
- self.xml.append(valXML)
- else:
- if isinstance(value, list):
- raise ValueError("Cannot add multiple values " + \
- "to a %s field." % self._type)
- valXML = ET.Element(valXMLName)
- valXML.text = value
- self.xml.append(valXML)
-
-
-class FieldOption(ElementBase):
- namespace = 'jabber:x:data'
- name = 'option'
- plugin_attrib = 'option'
- interfaces = set(('label', 'value'))
- sub_interfaces = set(('value',))
- plugin_multi_attrib = 'options'
-
-
-FormField.addOption = FormField.add_option
-FormField.delOptions = FormField.del_options
-FormField.delRequired = FormField.del_required
-FormField.delValue = FormField.del_value
-FormField.getAnswer = FormField.get_answer
-FormField.getOptions = FormField.get_options
-FormField.getRequired = FormField.get_required
-FormField.getValue = FormField.get_value
-FormField.setAnswer = FormField.set_answer
-FormField.setFalse = FormField.set_false
-FormField.setOptions = FormField.set_options
-FormField.setRequired = FormField.set_required
-FormField.setTrue = FormField.set_true
-FormField.setValue = FormField.set_value
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
deleted file mode 100644
index 3dcc7821..00000000
--- a/sleekxmpp/plugins/xep_0004/stanza/form.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import copy
-import logging
-
-from sleekxmpp.thirdparty import OrderedDict, OrderedSet
-
-from sleekxmpp.xmlstream import ElementBase, ET
-from sleekxmpp.plugins.xep_0004.stanza import FormField
-
-
-log = logging.getLogger(__name__)
-
-
-class Form(ElementBase):
- namespace = 'jabber:x:data'
- name = 'x'
- plugin_attrib = 'form'
- interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
- sub_interfaces = set(('title',))
- form_types = set(('cancel', 'form', 'result', 'submit'))
-
- def __init__(self, *args, **kwargs):
- title = None
- if 'title' in kwargs:
- title = kwargs['title']
- del kwargs['title']
- ElementBase.__init__(self, *args, **kwargs)
- if title is not None:
- self['title'] = title
-
- def setup(self, xml=None):
- if ElementBase.setup(self, xml):
- # If we had to generate xml
- self['type'] = 'form'
-
- @property
- def field(self):
- return self.get_fields()
-
- def set_type(self, ftype):
- self._set_attr('type', ftype)
- if ftype == 'submit':
- fields = self.get_fields()
- for var in fields:
- field = fields[var]
- del field['type']
- del field['label']
- del field['desc']
- del field['required']
- del field['options']
- elif ftype == 'cancel':
- del self['fields']
-
- def add_field(self, var='', ftype=None, label='', desc='',
- required=False, value=None, options=None, **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
-
- field = FormField()
- field['var'] = var
- field['type'] = kwtype
- field['value'] = value
- if self['type'] in ('form', 'result'):
- field['label'] = label
- field['desc'] = desc
- field['required'] = required
- if options is not None:
- for option in options:
- field.add_option(**option)
- else:
- del field['type']
- self.append(field)
- return field
-
- def getXML(self, type='submit'):
- self['type'] = type
- log.warning("Form.getXML() is deprecated API compatibility " + \
- "with plugins/old_0004.py")
- return self.xml
-
- def fromXML(self, xml):
- log.warning("Form.fromXML() is deprecated API compatibility " + \
- "with plugins/old_0004.py")
- n = Form(xml=xml)
- return n
-
- def add_item(self, values):
- itemXML = ET.Element('{%s}item' % self.namespace)
- self.xml.append(itemXML)
- reported_vars = self['reported'].keys()
- for var in reported_vars:
- field = FormField()
- field._type = self['reported'][var]['type']
- field['var'] = var
- field['value'] = values.get(var, None)
- itemXML.append(field.xml)
-
- def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
- reported = self.xml.find('{%s}reported' % self.namespace)
- if reported is None:
- reported = ET.Element('{%s}reported' % self.namespace)
- self.xml.append(reported)
- fieldXML = ET.Element('{%s}field' % FormField.namespace)
- reported.append(fieldXML)
- field = FormField(xml=fieldXML)
- field['var'] = var
- field['type'] = kwtype
- field['label'] = label
- field['desc'] = desc
- return field
-
- def cancel(self):
- self['type'] = 'cancel'
-
- def del_fields(self):
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- self.xml.remove(fieldXML)
-
- def del_instructions(self):
- instsXML = self.xml.findall('{%s}instructions')
- for instXML in instsXML:
- self.xml.remove(instXML)
-
- def del_items(self):
- itemsXML = self.xml.find('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- self.xml.remove(itemXML)
-
- def del_reported(self):
- reportedXML = self.xml.find('{%s}reported' % self.namespace)
- if reportedXML is not None:
- self.xml.remove(reportedXML)
-
- def get_fields(self, use_dict=False):
- fields = OrderedDict()
- for stanza in self['substanzas']:
- if isinstance(stanza, FormField):
- fields[stanza['var']] = stanza
- return fields
-
- def get_instructions(self):
- instsXML = self.xml.findall('{%s}instructions' % self.namespace)
- return "\n".join([instXML.text for instXML in instsXML])
-
- def get_items(self):
- items = []
- itemsXML = self.xml.findall('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- item = OrderedDict()
- fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- item[field['var']] = field['value']
- items.append(item)
- return items
-
- def get_reported(self):
- fields = OrderedDict()
- xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
- FormField.namespace))
- for field in xml:
- field = FormField(xml=field)
- fields[field['var']] = field
- return fields
-
- def get_values(self):
- values = OrderedDict()
- fields = self.get_fields()
- for var in fields:
- values[var] = fields[var]['value']
- return values
-
- def reply(self):
- if self['type'] == 'form':
- self['type'] = 'submit'
- elif self['type'] == 'submit':
- self['type'] = 'result'
-
- def set_fields(self, fields):
- del self['fields']
- if not isinstance(fields, list):
- fields = fields.items()
- for var, field in fields:
- field['var'] = var
- self.add_field(
- var = field.get('var'),
- label = field.get('label'),
- desc = field.get('desc'),
- required = field.get('required'),
- value = field.get('value'),
- options = field.get('options'),
- type = field.get('type'))
-
- def set_instructions(self, instructions):
- del self['instructions']
- if instructions in [None, '']:
- return
- if not isinstance(instructions, list):
- instructions = instructions.split('\n')
- for instruction in instructions:
- inst = ET.Element('{%s}instructions' % self.namespace)
- inst.text = instruction
- self.xml.append(inst)
-
- def set_items(self, items):
- for item in items:
- self.add_item(item)
-
- def set_reported(self, reported):
- """
- This either needs a dictionary or dictionaries or a dictionary of form fields.
- :param reported:
- :return:
- """
- for var in reported:
- field = reported[var]
-
- if isinstance(field, dict):
- self.add_reported(**field)
- else:
- reported = self.xml.find('{%s}reported' % self.namespace)
- if reported is None:
- reported = ET.Element('{%s}reported' % self.namespace)
- self.xml.append(reported)
-
- fieldXML = ET.Element('{%s}field' % FormField.namespace)
- reported.append(fieldXML)
- new_field = FormField(xml=fieldXML)
- new_field.values = field.values
-
- def set_values(self, values):
- fields = self.get_fields()
- for field in values:
- if field not in self.get_fields():
- fields[field] = self.add_field(var=field)
- self.get_fields()[field]['value'] = values[field]
-
- def merge(self, other):
- new = copy.copy(self)
- if type(other) == dict:
- new['values'] = other
- return new
- nfields = new['fields']
- ofields = other['fields']
- nfields.update(ofields)
- new['fields'] = nfields
- return new
-
-
-Form.setType = Form.set_type
-Form.addField = Form.add_field
-Form.addItem = Form.add_item
-Form.addReported = Form.add_reported
-Form.delFields = Form.del_fields
-Form.delInstructions = Form.del_instructions
-Form.delItems = Form.del_items
-Form.delReported = Form.del_reported
-Form.getFields = Form.get_fields
-Form.getInstructions = Form.get_instructions
-Form.getItems = Form.get_items
-Form.getReported = Form.get_reported
-Form.getValues = Form.get_values
-Form.setFields = Form.set_fields
-Form.setInstructions = Form.set_instructions
-Form.setItems = Form.set_items
-Form.setReported = Form.set_reported
-Form.setValues = Form.set_values
diff --git a/sleekxmpp/plugins/xep_0009/__init__.py b/sleekxmpp/plugins/xep_0009/__init__.py
deleted file mode 100644
index 0ce3cf2c..00000000
--- a/sleekxmpp/plugins/xep_0009/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0009 import stanza
-from sleekxmpp.plugins.xep_0009.rpc import XEP_0009
-from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
-
-
-register_plugin(XEP_0009)
-
-
-# Retain some backwards compatibility
-xep_0009 = XEP_0009
diff --git a/sleekxmpp/plugins/xep_0009/binding.py b/sleekxmpp/plugins/xep_0009/binding.py
deleted file mode 100644
index a55993ad..00000000
--- a/sleekxmpp/plugins/xep_0009/binding.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET
-import base64
-import logging
-import time
-import sys
-
-if sys.version_info > (3, 0):
- unicode = str
-
-log = logging.getLogger(__name__)
-
-_namespace = 'jabber:iq:rpc'
-
-def fault2xml(fault):
- value = dict()
- value['faultCode'] = fault['code']
- value['faultString'] = fault['string']
- fault = ET.Element("fault", {'xmlns': _namespace})
- fault.append(_py2xml((value)))
- return fault
-
-def xml2fault(params):
- vals = []
- for value in params.findall('{%s}value' % _namespace):
- vals.append(_xml2py(value))
- fault = dict()
- fault['code'] = vals[0]['faultCode']
- fault['string'] = vals[0]['faultString']
- return fault
-
-def py2xml(*args):
- params = ET.Element("{%s}params" % _namespace)
- for x in args:
- param = ET.Element("{%s}param" % _namespace)
- param.append(_py2xml(x))
- params.append(param) #<params><param>...
- return params
-
-def _py2xml(*args):
- for x in args:
- val = ET.Element("{%s}value" % _namespace)
- if x is None:
- nil = ET.Element("{%s}nil" % _namespace)
- val.append(nil)
- elif type(x) is int:
- i4 = ET.Element("{%s}i4" % _namespace)
- i4.text = str(x)
- val.append(i4)
- elif type(x) is bool:
- boolean = ET.Element("{%s}boolean" % _namespace)
- boolean.text = str(int(x))
- val.append(boolean)
- elif type(x) in (str, unicode):
- string = ET.Element("{%s}string" % _namespace)
- string.text = x
- val.append(string)
- elif type(x) is float:
- double = ET.Element("{%s}double" % _namespace)
- double.text = str(x)
- val.append(double)
- elif type(x) is rpcbase64:
- b64 = ET.Element("{%s}base64" % _namespace)
- b64.text = x.encoded()
- val.append(b64)
- elif type(x) is rpctime:
- iso = ET.Element("{%s}dateTime.iso8601" % _namespace)
- iso.text = str(x)
- val.append(iso)
- elif type(x) in (list, tuple):
- array = ET.Element("{%s}array" % _namespace)
- data = ET.Element("{%s}data" % _namespace)
- for y in x:
- data.append(_py2xml(y))
- array.append(data)
- val.append(array)
- elif type(x) is dict:
- struct = ET.Element("{%s}struct" % _namespace)
- for y in x.keys():
- member = ET.Element("{%s}member" % _namespace)
- name = ET.Element("{%s}name" % _namespace)
- name.text = y
- member.append(name)
- member.append(_py2xml(x[y]))
- struct.append(member)
- val.append(struct)
- return val
-
-def xml2py(params):
- namespace = 'jabber:iq:rpc'
- vals = []
- for param in params.findall('{%s}param' % namespace):
- vals.append(_xml2py(param.find('{%s}value' % namespace)))
- return vals
-
-def _xml2py(value):
- namespace = 'jabber:iq:rpc'
- if value.find('{%s}nil' % namespace) is not None:
- return None
- if value.find('{%s}i4' % namespace) is not None:
- return int(value.find('{%s}i4' % namespace).text)
- if value.find('{%s}int' % namespace) is not None:
- return int(value.find('{%s}int' % namespace).text)
- if value.find('{%s}boolean' % namespace) is not None:
- return bool(int(value.find('{%s}boolean' % namespace).text))
- if value.find('{%s}string' % namespace) is not None:
- return value.find('{%s}string' % namespace).text
- if value.find('{%s}double' % namespace) is not None:
- return float(value.find('{%s}double' % namespace).text)
- if value.find('{%s}base64' % namespace) is not None:
- return rpcbase64(value.find('{%s}base64' % namespace).text.encode())
- if value.find('{%s}Base64' % namespace) is not None:
- # Older versions of XEP-0009 used Base64
- return rpcbase64(value.find('{%s}Base64' % namespace).text.encode())
- if value.find('{%s}dateTime.iso8601' % namespace) is not None:
- return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text)
- if value.find('{%s}struct' % namespace) is not None:
- struct = {}
- for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
- struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
- return struct
- if value.find('{%s}array' % namespace) is not None:
- array = []
- for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
- array.append(_xml2py(val))
- return array
- raise ValueError()
-
-
-
-class rpcbase64(object):
-
- def __init__(self, data):
- #base 64 encoded string
- self.data = data
-
- def decode(self):
- return base64.b64decode(self.data)
-
- def __str__(self):
- return self.decode().decode()
-
- def encoded(self):
- return self.data.decode()
-
-
-
-class rpctime(object):
-
- def __init__(self,data=None):
- #assume string data is in iso format YYYYMMDDTHH:MM:SS
- if type(data) in (str, unicode):
- self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
- elif type(data) is time.struct_time:
- self.timestamp = data
- elif data is None:
- self.timestamp = time.gmtime()
- else:
- raise ValueError()
-
- def iso8601(self):
- #return a iso8601 string
- return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
-
- def __str__(self):
- return self.iso8601()
diff --git a/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py
deleted file mode 100644
index b02f587e..00000000
--- a/sleekxmpp/plugins/xep_0009/remote.py
+++ /dev/null
@@ -1,779 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
-from threading import RLock
-import abc
-import inspect
-import logging
-import sleekxmpp
-import sys
-import threading
-import traceback
-
-log = logging.getLogger(__name__)
-
-# Define a function _isstr() to check if an object is a string in a way
-# compatible with Python 2 and Python 3 (basestring does not exists in Python 3).
-try:
- basestring # This evaluation will throw an exception if basestring does not exists (Python 3).
- def _isstr(obj):
- return isinstance(obj, basestring)
-except NameError:
- def _isstr(obj):
- return isinstance(obj, str)
-
-
-# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
-# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
-#
-# Copyright (c) 2010-2015 Benjamin Peterson
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-def _add_metaclass(metaclass):
- def wrapper(cls):
- orig_vars = cls.__dict__.copy()
- slots = orig_vars.get('__slots__')
- if slots is not None:
- if isinstance(slots, str):
- slots = [slots]
- for slots_var in slots:
- orig_vars.pop(slots_var)
- orig_vars.pop('__dict__', None)
- orig_vars.pop('__weakref__', None)
- return metaclass(cls.__name__, cls.__bases__, orig_vars)
- return wrapper
-
-def _intercept(method, name, public):
- def _resolver(instance, *args, **kwargs):
- log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
- try:
- value = method(instance, *args, **kwargs)
- if value == NotImplemented:
- raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__))
- return value
- except InvocationException:
- raise
- except Exception as e:
- raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
- _resolver._rpc = public
- _resolver._rpc_name = method.__name__ if name is None else name
- return _resolver
-
-def remote(function_argument, public = True):
- '''
- Decorator for methods which are remotely callable. This decorator
- works in conjunction with classes which extend ABC Endpoint.
- Example:
-
- @remote
- def remote_method(arg1, arg2)
-
- Arguments:
- function_argument -- a stand-in for either the actual method
- OR a new name (string) for the method. In that case the
- method is considered mapped:
- Example:
-
- @remote("new_name")
- def remote_method(arg1, arg2)
-
- public -- A flag which indicates if this method should be part
- of the known dictionary of remote methods. Defaults to True.
- Example:
-
- @remote(False)
- def remote_method(arg1, arg2)
-
- Note: renaming and revising (public vs. private) can be combined.
- Example:
-
- @remote("new_name", False)
- def remote_method(arg1, arg2)
- '''
- if hasattr(function_argument, '__call__'):
- return _intercept(function_argument, None, public)
- else:
- if not _isstr(function_argument):
- if not isinstance(function_argument, bool):
- raise Exception('Expected an RPC method name or visibility modifier!')
- else:
- def _wrap_revised(function):
- function = _intercept(function, None, function_argument)
- return function
- return _wrap_revised
- def _wrap_remapped(function):
- function = _intercept(function, function_argument, public)
- return function
- return _wrap_remapped
-
-
-class ACL:
- '''
- An Access Control List (ACL) is a list of rules, which are evaluated
- in order until a match is found. The policy of the matching rule
- is then applied.
-
- Rules are 3-tuples, consisting of a policy enumerated type, a JID
- expression and a RCP resource expression.
-
- Examples:
- [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions
- [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions
- [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'),
- (ACL.DENY, '*', '*') ] deny everyone everything, except named
- JID, which is allowed access to endpoint 'test' only.
-
- The use of wildcards is allowed in expressions, as follows:
- '*' everyone, or everything (= all endpoints and methods)
- 'test@xmpp.org/*' every JID regardless of JID resource
- '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc'
- 'frank@*' every 'frank', regardless of domain or JID res
- 'system.*' all methods of endpoint 'system'
- '*.reboot' all methods reboot regardless of endpoint
- '''
- ALLOW = True
- DENY = False
-
- @classmethod
- def check(cls, rules, jid, resource):
- if rules is None:
- return cls.DENY # No rules means no access!
- jid = str(jid) # Check the string representation of the JID.
- if not jid:
- return cls.DENY # Can't check an empty JID.
- for rule in rules:
- policy = cls._check(rule, jid, resource)
- if policy is not None:
- return policy
- return cls.DENY # By default if not rule matches, deny access.
-
- @classmethod
- def _check(cls, rule, jid, resource):
- if cls._match(jid, rule[1]) and cls._match(resource, rule[2]):
- return rule[0]
- else:
- return None
-
- @classmethod
- def _next_token(cls, expression, index):
- new_index = expression.find('*', index)
- if new_index == 0:
- return ''
- else:
- if new_index == -1:
- return expression[index : ]
- else:
- return expression[index : new_index]
-
- @classmethod
- def _match(cls, value, expression):
- #! print "_match [VALUE] %s [EXPR] %s" % (value, expression)
- index = 0
- position = 0
- while index < len(expression):
- token = cls._next_token(expression, index)
- #! print "[TOKEN] '%s'" % token
- size = len(token)
- if size > 0:
- token_index = value.find(token, position)
- if token_index == -1:
- return False
- else:
- #! print "[INDEX-OF] %s" % token_index
- position = token_index + len(token)
- pass
- if size == 0:
- index += 1
- else:
- index += size
- #! print "index %s position %s" % (index, position)
- return True
-
-ANY_ALL = [ (ACL.ALLOW, '*', '*') ]
-
-
-class RemoteException(Exception):
- '''
- Base exception for RPC. This exception is raised when a problem
- occurs in the network layer.
- '''
-
- def __init__(self, message="", cause=None):
- '''
- Initializes a new RemoteException.
-
- Arguments:
- message -- The message accompanying this exception.
- cause -- The underlying cause of this exception.
- '''
- self._message = message
- self._cause = cause
- pass
-
- def __str__(self):
- return repr(self._message)
-
- def get_message(self):
- return self._message
-
- def get_cause(self):
- return self._cause
-
-
-
-class InvocationException(RemoteException):
- '''
- Exception raised when a problem occurs during the remote invocation
- of a method.
- '''
- pass
-
-
-
-class AuthorizationException(RemoteException):
- '''
- Exception raised when the caller is not authorized to invoke the
- remote method.
- '''
- pass
-
-
-class TimeoutException(Exception):
- '''
- Exception raised when the synchronous execution of a method takes
- longer than the given threshold because an underlying asynchronous
- reply did not arrive in time.
- '''
- pass
-
-
-@_add_metaclass(abc.ABCMeta)
-class Callback(object):
- '''
- A base class for callback handlers.
- '''
-
- @abc.abstractproperty
- def set_value(self, value):
- return NotImplemented
-
- @abc.abstractproperty
- def cancel_with_error(self, exception):
- return NotImplemented
-
-
-class Future(Callback):
- '''
- Represents the result of an asynchronous computation.
- '''
-
- def __init__(self):
- '''
- Initializes a new Future.
- '''
- self._value = None
- self._exception = None
- self._event = threading.Event()
- pass
-
- def set_value(self, value):
- '''
- Sets the value of this Future. Once the value is set, a caller
- blocked on get_value will be able to continue.
- '''
- self._value = value
- self._event.set()
-
- def get_value(self, timeout=None):
- '''
- Gets the value of this Future. This call will block until
- the result is available, or until an optional timeout expires.
- When this Future is cancelled with an error,
-
- Arguments:
- timeout -- The maximum waiting time to obtain the value.
- '''
- self._event.wait(timeout)
- if self._exception:
- raise self._exception
- if not self._event.is_set():
- raise TimeoutException
- return self._value
-
- def is_done(self):
- '''
- Returns true if a value has been returned.
- '''
- return self._event.is_set()
-
- def cancel_with_error(self, exception):
- '''
- Cancels the Future because of an error. Once cancelled, a
- caller blocked on get_value will be able to continue.
- '''
- self._exception = exception
- self._event.set()
-
-
-@_add_metaclass(abc.ABCMeta)
-class Endpoint(object):
- '''
- The Endpoint class is an abstract base class for all objects
- participating in an RPC-enabled XMPP network.
-
- A user subclassing this class is required to implement the method:
- FQN(self)
- where FQN stands for Fully Qualified Name, an unambiguous name
- which specifies which object an RPC call refers to. It is the
- first part in a RPC method name '<fqn>.<method>'.
- '''
-
- def __init__(self, session, target_jid):
- '''
- Initialize a new Endpoint. This constructor should never be
- invoked by a user, instead it will be called by the factories
- which instantiate the RPC-enabled objects, of which only
- the classes are provided by the user.
-
- Arguments:
- session -- An RPC session instance.
- target_jid -- the identity of the remote XMPP entity.
- '''
- self.session = session
- self.target_jid = target_jid
-
- @abc.abstractproperty
- def FQN(self):
- return NotImplemented
-
- def get_methods(self):
- '''
- Returns a dictionary of all RPC method names provided by this
- class. This method returns the actual method names as found
- in the class definition which have been decorated with:
-
- @remote
- def some_rpc_method(arg1, arg2)
-
-
- Unless:
- (1) the name has been remapped, in which case the new
- name will be returned.
-
- @remote("new_name")
- def some_rpc_method(arg1, arg2)
-
- (2) the method is set to hidden
-
- @remote(False)
- def some_hidden_method(arg1, arg2)
- '''
- result = dict()
- for function in dir(self):
- test_attr = getattr(self, function, None)
- try:
- if test_attr._rpc:
- result[test_attr._rpc_name] = test_attr
- except Exception:
- pass
- return result
-
-
-
-class Proxy(Endpoint):
- '''
- Implementation of the Proxy pattern which is intended to wrap
- around Endpoints in order to intercept calls, marshall them and
- forward them to the remote object.
- '''
-
- def __init__(self, endpoint, callback = None):
- '''
- Initializes a new Proxy.
-
- Arguments:
- endpoint -- The endpoint which is proxified.
- '''
- self._endpoint = endpoint
- self._callback = callback
-
- def __getattribute__(self, name, *args):
- if name in ('__dict__', '_endpoint', 'async', '_callback'):
- return object.__getattribute__(self, name)
- else:
- attribute = self._endpoint.__getattribute__(name)
- if hasattr(attribute, '__call__'):
- try:
- if attribute._rpc:
- def _remote_call(*args, **kwargs):
- log.debug("Remotely calling '%s.%s' with arguments %s.", self._endpoint.FQN(), attribute._rpc_name, args)
- return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs)
- return _remote_call
- except:
- pass # If the attribute doesn't exist, don't care!
- return attribute
-
- def async(self, callback):
- return Proxy(self._endpoint, callback)
-
- def get_endpoint(self):
- '''
- Returns the proxified endpoint.
- '''
- return self._endpoint
-
- def FQN(self):
- return self._endpoint.FQN()
-
-
-class JabberRPCEntry(object):
-
-
- def __init__(self, endpoint_FQN, call):
- self._endpoint_FQN = endpoint_FQN
- self._call = call
-
- def call_method(self, args):
- return_value = self._call(*args)
- if return_value is None:
- return return_value
- else:
- return self._return(return_value)
-
- def get_endpoint_FQN(self):
- return self._endpoint_FQN
-
- def _return(self, *args):
- return args
-
-
-class RemoteSession(object):
- '''
- A context object for a Jabber-RPC session.
- '''
-
-
- def __init__(self, client, session_close_callback):
- '''
- Initializes a new RPC session.
-
- Arguments:
- client -- The SleekXMPP client associated with this session.
- session_close_callback -- A callback called when the
- session is closed.
- '''
- self._client = client
- self._session_close_callback = session_close_callback
- self._event = threading.Event()
- self._entries = {}
- self._callbacks = {}
- self._acls = {}
- self._lock = RLock()
-
- def _wait(self):
- self._event.wait()
-
- def _notify(self, event):
- log.debug("RPC Session as %s started.", self._client.boundjid.full)
- self._client.sendPresence()
- self._event.set()
- pass
-
- def _register_call(self, endpoint, method, name=None):
- '''
- Registers a method from an endpoint as remotely callable.
- '''
- if name is None:
- name = method.__name__
- key = "%s.%s" % (endpoint, name)
- log.debug("Registering call handler for %s (%s).", key, method)
- with self._lock:
- if key in self._entries:
- raise KeyError("A handler for %s has already been regisered!" % endpoint)
- self._entries[key] = JabberRPCEntry(endpoint, method)
- return key
-
- def _register_acl(self, endpoint, acl):
- log.debug("Registering ACL %s for endpoint %s.", repr(acl), endpoint)
- with self._lock:
- self._acls[endpoint] = acl
-
- def _register_callback(self, pid, callback):
- with self._lock:
- self._callbacks[pid] = callback
-
- def forget_callback(self, callback):
- with self._lock:
- pid = self._find_key(self._callbacks, callback)
- if pid is not None:
- del self._callback[pid]
- else:
- raise ValueError("Unknown callback!")
- pass
-
- def _find_key(self, dict, value):
- """return the key of dictionary dic given the value"""
- search = [k for k, v in dict.items() if v == value]
- if len(search) == 0:
- return None
- else:
- return search[0]
-
- def _unregister_call(self, key):
- #removes the registered call
- with self._lock:
- if self._entries[key]:
- del self._entries[key]
- else:
- raise ValueError()
-
- def new_proxy(self, target_jid, endpoint_cls):
- '''
- Instantiates a new proxy object, which proxies to a remote
- endpoint. This method uses a class reference without
- constructor arguments to instantiate the proxy.
-
- Arguments:
- target_jid -- the XMPP entity ID hosting the endpoint.
- endpoint_cls -- The remote (duck) type.
- '''
- try:
- argspec = inspect.getargspec(endpoint_cls.__init__)
- args = [None] * (len(argspec[0]) - 1)
- result = endpoint_cls(*args)
- Endpoint.__init__(result, self, target_jid)
- return Proxy(result)
- except:
- traceback.print_exc(file=sys.stdout)
-
- def new_handler(self, acl, handler_cls, *args, **kwargs):
- '''
- Instantiates a new handler object, which is called remotely
- by others. The user can control the effect of the call by
- implementing the remote method in the local endpoint class. The
- returned reference can be called locally and will behave as a
- regular instance.
-
- Arguments:
- acl -- Access control list (see ACL class)
- handler_clss -- The local (duck) type.
- *args -- Constructor arguments for the local type.
- **kwargs -- Constructor keyworded arguments for the local
- type.
- '''
- argspec = inspect.getargspec(handler_cls.__init__)
- base_argspec = inspect.getargspec(Endpoint.__init__)
- if(argspec == base_argspec):
- result = handler_cls(self, self._client.boundjid.full)
- else:
- result = handler_cls(*args, **kwargs)
- Endpoint.__init__(result, self, self._client.boundjid.full)
- method_dict = result.get_methods()
- for method_name, method in method_dict.items():
- #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
- self._register_call(result.FQN(), method, method_name)
- self._register_acl(result.FQN(), acl)
- return result
-
-# def is_available(self, targetCls, pto):
-# return self._client.is_available(pto)
-
- def _call_remote(self, pto, pmethod, callback, *arguments):
- iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments))
- pid = iq['id']
- if callback is None:
- future = Future()
- self._register_callback(pid, future)
- iq.send()
- return future.get_value(30)
- else:
- log.debug("[RemoteSession] _call_remote %s", callback)
- self._register_callback(pid, callback)
- iq.send()
-
- def close(self, wait=False):
- '''
- Closes this session.
- '''
- self._client.disconnect(wait=wait)
- self._session_close_callback()
-
- def _on_jabber_rpc_method_call(self, iq):
- iq.enable('rpc_query')
- params = iq['rpc_query']['method_call']['params']
- args = xml2py(params)
- pmethod = iq['rpc_query']['method_call']['method_name']
- try:
- with self._lock:
- entry = self._entries[pmethod]
- rules = self._acls[entry.get_endpoint_FQN()]
- if ACL.check(rules, iq['from'], pmethod):
- return_value = entry.call_method(args)
- else:
- raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from']))
- if return_value is None:
- return_value = ()
- response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value))
- response.send()
- except InvocationException as ie:
- fault = dict()
- fault['code'] = 500
- fault['string'] = ie.get_message()
- self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault))
- except AuthorizationException as ae:
- log.error(ae.get_message())
- error = self._client.plugin['xep_0009']._forbidden(iq)
- error.send()
- except Exception as e:
- if isinstance(e, KeyError):
- log.error("No handler available for %s!", pmethod)
- error = self._client.plugin['xep_0009']._item_not_found(iq)
- else:
- traceback.print_exc(file=sys.stderr)
- log.error("An unexpected problem occurred invoking method %s!", pmethod)
- error = self._client.plugin['xep_0009']._undefined_condition(iq)
- #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e
- error.send()
-
- def _on_jabber_rpc_method_response(self, iq):
- iq.enable('rpc_query')
- args = xml2py(iq['rpc_query']['method_response']['params'])
- pid = iq['id']
- with self._lock:
- callback = self._callbacks[pid]
- del self._callbacks[pid]
- if(len(args) > 0):
- callback.set_value(args[0])
- else:
- callback.set_value(None)
- pass
-
- def _on_jabber_rpc_method_response2(self, iq):
- iq.enable('rpc_query')
- if iq['rpc_query']['method_response']['fault'] is not None:
- self._on_jabber_rpc_method_fault(iq)
- else:
- args = xml2py(iq['rpc_query']['method_response']['params'])
- pid = iq['id']
- with self._lock:
- callback = self._callbacks[pid]
- del self._callbacks[pid]
- if(len(args) > 0):
- callback.set_value(args[0])
- else:
- callback.set_value(None)
- pass
-
- def _on_jabber_rpc_method_fault(self, iq):
- iq.enable('rpc_query')
- fault = xml2fault(iq['rpc_query']['method_response']['fault'])
- pid = iq['id']
- with self._lock:
- callback = self._callbacks[pid]
- del self._callbacks[pid]
- e = {
- 500: InvocationException
- }[fault['code']](fault['string'])
- callback.cancel_with_error(e)
-
- def _on_jabber_rpc_error(self, iq):
- pid = iq['id']
- pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query'])
- code = iq['error']['code']
- type = iq['error']['type']
- condition = iq['error']['condition']
- #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition)
- with self._lock:
- callback = self._callbacks[pid]
- del self._callbacks[pid]
- e = {
- 'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
- 'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
- 'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
- }[condition]
- if e is None:
- RemoteException("An unexpected exception occurred at %s!" % iq['from'])
- callback.cancel_with_error(e)
-
-
-class Remote(object):
- '''
- Bootstrap class for Jabber-RPC sessions. New sessions are openend
- with an existing XMPP client, or one is instantiated on demand.
- '''
- _instance = None
- _sessions = dict()
- _lock = threading.RLock()
-
- @classmethod
- def new_session_with_client(cls, client, callback=None):
- '''
- Opens a new session with a given client.
-
- Arguments:
- client -- An XMPP client.
- callback -- An optional callback which can be used to track
- the starting state of the session.
- '''
- with Remote._lock:
- if(client.boundjid.bare in cls._sessions):
- raise RemoteException("There already is a session associated with these credentials!")
- else:
- cls._sessions[client.boundjid.bare] = client
-
- def _session_close_callback():
- with Remote._lock:
- del cls._sessions[client.boundjid.bare]
- result = RemoteSession(client, _session_close_callback)
- client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True)
- client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True)
- client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True)
- client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True)
- if callback is None:
- start_event_handler = result._notify
- else:
- start_event_handler = callback
- client.add_event_handler("session_start", start_event_handler)
- if client.connect():
- client.process(threaded=True)
- else:
- raise RemoteException("Could not connect to XMPP server!")
- pass
- if callback is None:
- result._wait()
- return result
-
- @classmethod
- def new_session(cls, jid, password, callback=None):
- '''
- Opens a new session and instantiates a new XMPP client.
-
- Arguments:
- jid -- The XMPP JID for logging in.
- password -- The password for logging in.
- callback -- An optional callback which can be used to track
- the starting state of the session.
- '''
- client = sleekxmpp.ClientXMPP(jid, password)
- #? Register plug-ins.
- client.registerPlugin('xep_0004') # Data Forms
- client.registerPlugin('xep_0009') # Jabber-RPC
- client.registerPlugin('xep_0030') # Service Discovery
- client.registerPlugin('xep_0060') # PubSub
- client.registerPlugin('xep_0199') # XMPP Ping
- return cls.new_session_with_client(client, callback)
-
diff --git a/sleekxmpp/plugins/xep_0009/rpc.py b/sleekxmpp/plugins/xep_0009/rpc.py
deleted file mode 100644
index 6179355e..00000000
--- a/sleekxmpp/plugins/xep_0009/rpc.py
+++ /dev/null
@@ -1,218 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import ET, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0009 import stanza
-from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0009(BasePlugin):
-
- name = 'xep_0009'
- description = 'XEP-0009: Jabber-RPC'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, RPCQuery)
- register_stanza_plugin(RPCQuery, MethodCall)
- register_stanza_plugin(RPCQuery, MethodResponse)
-
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
- self._handle_method_call)
- )
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
- self._handle_method_response)
- )
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
- self._handle_error)
- )
- self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call)
- self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response)
- self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault)
- self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error)
- self.xmpp.add_event_handler('error', self._handle_error)
- #self.activeCalls = []
-
- self.xmpp['xep_0030'].add_feature('jabber:iq:rpc')
- self.xmpp['xep_0030'].add_identity('automation','rpc')
-
- def make_iq_method_call(self, pto, pmethod, params):
- iq = self.xmpp.makeIqSet()
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_call']['method_name'] = pmethod
- iq['rpc_query']['method_call']['params'] = params
- return iq
-
- def make_iq_method_response(self, pid, pto, params):
- iq = self.xmpp.makeIqResult(pid)
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_response']['params'] = params
- return iq
-
- def make_iq_method_response_fault(self, pid, pto, params):
- iq = self.xmpp.makeIqResult(pid)
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_response']['params'] = None
- iq['rpc_query']['method_response']['fault'] = params
- return iq
-
-# def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition):
-# iq = self.xmpp.makeIqError(pid)
-# iq.attrib['to'] = pto
-# iq.attrib['from'] = self.xmpp.boundjid.full
-# iq['error']['code'] = code
-# iq['error']['type'] = type
-# iq['error']['condition'] = condition
-# iq['rpc_query']['method_call']['method_name'] = pmethod
-# iq['rpc_query']['method_call']['params'] = params
-# return iq
-
- def _item_not_found(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '404'
- iq['error']['type'] = 'cancel'
- iq['error']['condition'] = 'item-not-found'
- return iq
-
- def _undefined_condition(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '500'
- iq['error']['type'] = 'cancel'
- iq['error']['condition'] = 'undefined-condition'
- return iq
-
- def _forbidden(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '403'
- iq['error']['type'] = 'auth'
- iq['error']['condition'] = 'forbidden'
- return iq
-
- def _recipient_unvailable(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '404'
- iq['error']['type'] = 'wait'
- iq['error']['condition'] = 'recipient-unavailable'
- return iq
-
- def _handle_method_call(self, iq):
- type = iq['type']
- if type == 'set':
- log.debug("Incoming Jabber-RPC call from %s", iq['from'])
- self.xmpp.event('jabber_rpc_method_call', iq)
- else:
- if type == 'error' and ['rpc_query'] is None:
- self.handle_error(iq)
- else:
- log.debug("Incoming Jabber-RPC error from %s", iq['from'])
- self.xmpp.event('jabber_rpc_error', iq)
-
- def _handle_method_response(self, iq):
- if iq['rpc_query']['method_response']['fault'] is not None:
- log.debug("Incoming Jabber-RPC fault from %s", iq['from'])
- #self._on_jabber_rpc_method_fault(iq)
- self.xmpp.event('jabber_rpc_method_fault', iq)
- else:
- log.debug("Incoming Jabber-RPC response from %s", iq['from'])
- self.xmpp.event('jabber_rpc_method_response', iq)
-
- def _handle_error(self, iq):
- print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq)
- print("#######################")
- print("### NOT IMPLEMENTED ###")
- print("#######################")
-
- def _on_jabber_rpc_method_call(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC method call. If another
- handler is registered, this one will defer and not run.
-
- If this handler is called by your own custom handler with
- forwarded set to True, then it will run as normal.
- """
- if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1:
- return
- # Reply with error by default
- error = self.client.plugin['xep_0009']._item_not_found(iq)
- error.send()
-
- def _on_jabber_rpc_method_response(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC method response. If another
- handler is registered, this one will defer and not run.
-
- If this handler is called by your own custom handler with
- forwarded set to True, then it will run as normal.
- """
- if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
- error.send()
-
- def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC fault response. If another
- handler is registered, this one will defer and not run.
-
- If this handler is called by your own custom handler with
- forwarded set to True, then it will run as normal.
- """
- if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
- error.send()
-
- def _on_jabber_rpc_error(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC error response. If another
- handler is registered, this one will defer and not run.
-
- If this handler is called by your own custom handler with
- forwarded set to True, then it will run as normal.
- """
- if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
- error.send()
-
- def _send_fault(self, iq, fault_xml): #
- fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml)
- fault.send()
-
- def _send_error(self, iq):
- print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq)
- print("#######################")
- print("### NOT IMPLEMENTED ###")
- print("#######################")
-
- def _extract_method(self, stanza):
- xml = ET.fromstring("%s" % stanza)
- return xml.find("./methodCall/methodName").text
diff --git a/sleekxmpp/plugins/xep_0009/stanza/RPC.py b/sleekxmpp/plugins/xep_0009/stanza/RPC.py
deleted file mode 100644
index 3d1c77a2..00000000
--- a/sleekxmpp/plugins/xep_0009/stanza/RPC.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.stanzabase import ElementBase
-from xml.etree import cElementTree as ET
-
-
-class RPCQuery(ElementBase):
- name = 'query'
- namespace = 'jabber:iq:rpc'
- plugin_attrib = 'rpc_query'
- interfaces = set(())
- subinterfaces = set(())
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
-
-class MethodCall(ElementBase):
- name = 'methodCall'
- namespace = 'jabber:iq:rpc'
- plugin_attrib = 'method_call'
- interfaces = set(('method_name', 'params'))
- subinterfaces = set(())
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def get_method_name(self):
- return self._get_sub_text('methodName')
-
- def set_method_name(self, value):
- return self._set_sub_text('methodName', value)
-
- def get_params(self):
- return self.xml.find('{%s}params' % self.namespace)
-
- def set_params(self, params):
- self.append(params)
-
-
-class MethodResponse(ElementBase):
- name = 'methodResponse'
- namespace = 'jabber:iq:rpc'
- plugin_attrib = 'method_response'
- interfaces = set(('params', 'fault'))
- subinterfaces = set(())
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def get_params(self):
- return self.xml.find('{%s}params' % self.namespace)
-
- def set_params(self, params):
- self.append(params)
-
- def get_fault(self):
- return self.xml.find('{%s}fault' % self.namespace)
-
- def set_fault(self, fault):
- self.append(fault)
diff --git a/sleekxmpp/plugins/xep_0009/stanza/__init__.py b/sleekxmpp/plugins/xep_0009/stanza/__init__.py
deleted file mode 100644
index 5dcbf330..00000000
--- a/sleekxmpp/plugins/xep_0009/stanza/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
diff --git a/sleekxmpp/plugins/xep_0012/__init__.py b/sleekxmpp/plugins/xep_0012/__init__.py
deleted file mode 100644
index 6b778fc1..00000000
--- a/sleekxmpp/plugins/xep_0012/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0012.stanza import LastActivity
-from sleekxmpp.plugins.xep_0012.last_activity import XEP_0012
-
-
-register_plugin(XEP_0012)
-
-
-# Retain some backwards compatibility
-xep_0004 = XEP_0012
diff --git a/sleekxmpp/plugins/xep_0012/last_activity.py b/sleekxmpp/plugins/xep_0012/last_activity.py
deleted file mode 100644
index 8790b47c..00000000
--- a/sleekxmpp/plugins/xep_0012/last_activity.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-from datetime import datetime, timedelta
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp import Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import JID, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0012(BasePlugin):
-
- """
- XEP-0012 Last Activity
- """
-
- name = 'xep_0012'
- description = 'XEP-0012: Last Activity'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, LastActivity)
-
- self._last_activities = {}
-
- self.xmpp.register_handler(
- Callback('Last Activity',
- StanzaPath('iq@type=get/last_activity'),
- self._handle_get_last_activity))
-
- self.api.register(self._default_get_last_activity,
- 'get_last_activity',
- default=True)
- self.api.register(self._default_set_last_activity,
- 'set_last_activity',
- default=True)
- self.api.register(self._default_del_last_activity,
- 'del_last_activity',
- default=True)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Last Activity')
- self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('jabber:iq:last')
-
- def begin_idle(self, jid=None, status=None):
- self.set_last_activity(jid, 0, status)
-
- def end_idle(self, jid=None):
- self.del_last_activity(jid)
-
- def start_uptime(self, status=None):
- self.set_last_activity(jid, 0, status)
-
- def set_last_activity(self, jid=None, seconds=None, status=None):
- self.api['set_last_activity'](jid, args={
- 'seconds': seconds,
- 'status': status})
-
- def del_last_activity(self, jid):
- self.api['del_last_activity'](jid)
-
- def get_last_activity(self, jid, local=False, ifrom=None, block=True,
- timeout=None, callback=None):
- if jid is not None and not isinstance(jid, JID):
- jid = JID(jid)
-
- if self.xmpp.is_component:
- if jid.domain == self.xmpp.boundjid.domain:
- local = True
- else:
- if str(jid) == str(self.xmpp.boundjid):
- local = True
- jid = jid.full
-
- if local or jid in (None, ''):
- log.debug("Looking up local last activity data for %s", jid)
- return self.api['get_last_activity'](jid, None, ifrom, None)
-
- iq = self.xmpp.Iq()
- iq['from'] = ifrom
- iq['to'] = jid
- iq['type'] = 'get'
- iq.enable('last_activity')
- return iq.send(timeout=timeout,
- block=block,
- callback=callback)
-
- def _handle_get_last_activity(self, iq):
- log.debug("Received last activity query from " + \
- "<%s> to <%s>.", iq['from'], iq['to'])
- reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq)
- reply.send()
-
- # =================================================================
- # Default in-memory implementations for storing last activity data.
- # =================================================================
-
- def _default_set_last_activity(self, jid, node, ifrom, data):
- seconds = data.get('seconds', None)
- if seconds is None:
- seconds = 0
-
- status = data.get('status', None)
- if status is None:
- status = ''
-
- self._last_activities[jid] = {
- 'seconds': datetime.now() - timedelta(seconds=seconds),
- 'status': status}
-
- def _default_del_last_activity(self, jid, node, ifrom, data):
- if jid in self._last_activities:
- del self._last_activities[jid]
-
- def _default_get_last_activity(self, jid, node, ifrom, iq):
- if not isinstance(iq, Iq):
- reply = self.xmpp.Iq()
- else:
- iq.reply()
- reply = iq
-
- if jid not in self._last_activities:
- raise XMPPError('service-unavailable')
-
- bare = JID(jid).bare
-
- if bare != self.xmpp.boundjid.bare:
- if bare in self.xmpp.roster[jid]:
- sub = self.xmpp.roster[jid][bare]['subscription']
- if sub not in ('from', 'both'):
- raise XMPPError('forbidden')
-
- td = datetime.now() - self._last_activities[jid]['seconds']
- seconds = td.seconds + td.days * 24 * 3600
- status = self._last_activities[jid]['status']
-
- reply['last_activity']['seconds'] = seconds
- reply['last_activity']['status'] = status
-
- return reply
diff --git a/sleekxmpp/plugins/xep_0012/stanza.py b/sleekxmpp/plugins/xep_0012/stanza.py
deleted file mode 100644
index 079865b9..00000000
--- a/sleekxmpp/plugins/xep_0012/stanza.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class LastActivity(ElementBase):
-
- name = 'query'
- namespace = 'jabber:iq:last'
- plugin_attrib = 'last_activity'
- interfaces = set(('seconds', 'status'))
-
- def get_seconds(self):
- return int(self._get_attr('seconds'))
-
- def set_seconds(self, value):
- self._set_attr('seconds', str(value))
-
- def get_status(self):
- return self.xml.text
-
- def set_status(self, value):
- self.xml.text = str(value)
-
- def del_status(self):
- self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0013/__init__.py b/sleekxmpp/plugins/xep_0013/__init__.py
deleted file mode 100644
index ad400949..00000000
--- a/sleekxmpp/plugins/xep_0013/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0013.stanza import Offline
-from sleekxmpp.plugins.xep_0013.offline import XEP_0013
-
-
-register_plugin(XEP_0013)
diff --git a/sleekxmpp/plugins/xep_0013/offline.py b/sleekxmpp/plugins/xep_0013/offline.py
deleted file mode 100644
index a0d992a7..00000000
--- a/sleekxmpp/plugins/xep_0013/offline.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.stanza import Message, Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream.handler import Collector
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0013 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0013(BasePlugin):
-
- """
- XEP-0013 Flexible Offline Message Retrieval
- """
-
- name = 'xep_0013'
- description = 'XEP-0013: Flexible Offline Message Retrieval'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, stanza.Offline)
- register_stanza_plugin(Message, stanza.Offline)
-
- def get_count(self, **kwargs):
- return self.xmpp['xep_0030'].get_info(
- node='http://jabber.org/protocol/offline',
- local=False,
- **kwargs)
-
- def get_headers(self, **kwargs):
- return self.xmpp['xep_0030'].get_items(
- node='http://jabber.org/protocol/offline',
- local=False,
- **kwargs)
-
- def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
- if not isinstance(nodes, (list, set)):
- nodes = [nodes]
-
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
- offline = iq['offline']
- for node in nodes:
- item = stanza.Item()
- item['node'] = node
- item['action'] = 'view'
- offline.append(item)
-
- collector = Collector(
- 'Offline_Results_%s' % iq['id'],
- StanzaPath('message/offline'))
- self.xmpp.register_handler(collector)
-
- if not block and callback is not None:
- def wrapped_cb(iq):
- results = collector.stop()
- if iq['type'] == 'result':
- iq['offline']['results'] = results
- callback(iq)
- return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
- else:
- try:
- resp = iq.send(block=block, timeout=timeout, callback=callback)
- resp['offline']['results'] = collector.stop()
- return resp
- except XMPPError as e:
- collector.stop()
- raise e
-
- def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
- if not isinstance(nodes, (list, set)):
- nodes = [nodes]
-
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- offline = iq['offline']
- for node in nodes:
- item = stanza.Item()
- item['node'] = node
- item['action'] = 'remove'
- offline.append(item)
-
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq['offline']['fetch'] = True
-
- collector = Collector(
- 'Offline_Results_%s' % iq['id'],
- StanzaPath('message/offline'))
- self.xmpp.register_handler(collector)
-
- if not block and callback is not None:
- def wrapped_cb(iq):
- results = collector.stop()
- if iq['type'] == 'result':
- iq['offline']['results'] = results
- callback(iq)
- return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
- else:
- try:
- resp = iq.send(block=block, timeout=timeout, callback=callback)
- resp['offline']['results'] = collector.stop()
- return resp
- except XMPPError as e:
- collector.stop()
- raise e
-
- def purge(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq['offline']['purge'] = True
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0013/stanza.py b/sleekxmpp/plugins/xep_0013/stanza.py
deleted file mode 100644
index c9c69786..00000000
--- a/sleekxmpp/plugins/xep_0013/stanza.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class Offline(ElementBase):
- name = 'offline'
- namespace = 'http://jabber.org/protocol/offline'
- plugin_attrib = 'offline'
- interfaces = set(['fetch', 'purge', 'results'])
- bool_interfaces = interfaces
-
- def setup(self, xml=None):
- ElementBase.setup(self, xml)
- self._results = []
-
- # The results interface is meant only as an easy
- # way to access the set of collected message responses
- # from the query.
-
- def get_results(self):
- return self._results
-
- def set_results(self, values):
- self._results = values
-
- def del_results(self):
- self._results = []
-
-
-class Item(ElementBase):
- name = 'item'
- namespace = 'http://jabber.org/protocol/offline'
- plugin_attrib = 'item'
- interfaces = set(['action', 'node', 'jid'])
-
- actions = set(['view', 'remove'])
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
-
-register_stanza_plugin(Offline, Item, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0016/__init__.py b/sleekxmpp/plugins/xep_0016/__init__.py
deleted file mode 100644
index 06704d26..00000000
--- a/sleekxmpp/plugins/xep_0016/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0016 import stanza
-from sleekxmpp.plugins.xep_0016.stanza import Privacy
-from sleekxmpp.plugins.xep_0016.privacy import XEP_0016
-
-
-register_plugin(XEP_0016)
diff --git a/sleekxmpp/plugins/xep_0016/privacy.py b/sleekxmpp/plugins/xep_0016/privacy.py
deleted file mode 100644
index 79fd68f0..00000000
--- a/sleekxmpp/plugins/xep_0016/privacy.py
+++ /dev/null
@@ -1,110 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0016 import stanza
-from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item
-
-
-class XEP_0016(BasePlugin):
-
- name = 'xep_0016'
- description = 'XEP-0016: Privacy Lists'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, Privacy)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Privacy.namespace)
-
- def get_privacy_lists(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq.enable('privacy')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def get_list(self, name, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['privacy']['list']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def get_active(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['privacy'].enable('active')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def get_default(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['privacy'].enable('default')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def activate(self, name, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy']['active']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def deactivate(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy'].enable('active')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def make_default(self, name, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy']['default']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def remove_default(self, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy'].enable('default')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def edit_list(self, name, rules, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy']['list']['name'] = name
- priv_list = iq['privacy']['list']
-
- if not rules:
- rules = []
-
- for rule in rules:
- if isinstance(rule, Item):
- priv_list.append(rule)
- continue
-
- priv_list.add_item(
- rule['value'],
- rule['action'],
- rule['order'],
- itype=rule.get('type', None),
- iq=rule.get('iq', None),
- message=rule.get('message', None),
- presence_in=rule.get('presence_in',
- rule.get('presence-in', None)),
- presence_out=rule.get('presence_out',
- rule.get('presence-out', None)))
-
- def remove_list(self, name, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['privacy']['list']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0016/stanza.py b/sleekxmpp/plugins/xep_0016/stanza.py
deleted file mode 100644
index 3f9977fc..00000000
--- a/sleekxmpp/plugins/xep_0016/stanza.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
-
-
-class Privacy(ElementBase):
- name = 'query'
- namespace = 'jabber:iq:privacy'
- plugin_attrib = 'privacy'
- interfaces = set()
-
- def add_list(self, name):
- priv_list = List()
- priv_list['name'] = name
- self.append(priv_list)
- return priv_list
-
-
-class Active(ElementBase):
- name = 'active'
- namespace = 'jabber:iq:privacy'
- plugin_attrib = name
- interfaces = set(['name'])
-
-
-class Default(ElementBase):
- name = 'default'
- namespace = 'jabber:iq:privacy'
- plugin_attrib = name
- interfaces = set(['name'])
-
-
-class List(ElementBase):
- name = 'list'
- namespace = 'jabber:iq:privacy'
- plugin_attrib = name
- plugin_multi_attrib = 'lists'
- interfaces = set(['name'])
-
- def add_item(self, value, action, order, itype=None, iq=False,
- message=False, presence_in=False, presence_out=False):
- item = Item()
- item.values = {'type': itype,
- 'value': value,
- 'action': action,
- 'order': order,
- 'message': message,
- 'iq': iq,
- 'presence_in': presence_in,
- 'presence_out': presence_out}
- self.append(item)
- return item
-
-
-class Item(ElementBase):
- name = 'item'
- namespace = 'jabber:iq:privacy'
- plugin_attrib = name
- plugin_multi_attrib = 'items'
- interfaces = set(['type', 'value', 'action', 'order', 'iq',
- 'message', 'presence_in', 'presence_out'])
- bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out'])
-
- type_values = ('', 'jid', 'group', 'subscription')
- action_values = ('allow', 'deny')
-
- def set_type(self, value):
- if value and value not in self.type_values:
- raise ValueError('Unknown type value: %s' % value)
- else:
- self._set_attr('type', value)
-
- def set_action(self, value):
- if value not in self.action_values:
- raise ValueError('Unknown action value: %s' % value)
- else:
- self._set_attr('action', value)
-
- def set_presence_in(self, value):
- keep = True if value else False
- self._set_sub_text('presence-in', '', keep=keep)
-
- def get_presence_in(self):
- pres = self.xml.find('{%s}presence-in' % self.namespace)
- return pres is not None
-
- def del_presence_in(self):
- self._del_sub('{%s}presence-in' % self.namespace)
-
- def set_presence_out(self, value):
- keep = True if value else False
- self._set_sub_text('presence-in', '', keep=keep)
-
- def get_presence_out(self):
- pres = self.xml.find('{%s}presence-in' % self.namespace)
- return pres is not None
-
- def del_presence_out(self):
- self._del_sub('{%s}presence-in' % self.namespace)
-
-
-register_stanza_plugin(Privacy, Active)
-register_stanza_plugin(Privacy, Default)
-register_stanza_plugin(Privacy, List, iterable=True)
-register_stanza_plugin(List, Item, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0020/__init__.py b/sleekxmpp/plugins/xep_0020/__init__.py
deleted file mode 100644
index c6aafe97..00000000
--- a/sleekxmpp/plugins/xep_0020/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0020 import stanza
-from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation
-from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020
-
-
-register_plugin(XEP_0020)
diff --git a/sleekxmpp/plugins/xep_0020/feature_negotiation.py b/sleekxmpp/plugins/xep_0020/feature_negotiation.py
deleted file mode 100644
index 7cb82cd5..00000000
--- a/sleekxmpp/plugins/xep_0020/feature_negotiation.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation
-from sleekxmpp.plugins.xep_0004 import Form
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0020(BasePlugin):
-
- name = 'xep_0020'
- description = 'XEP-0020: Feature Negotiation'
- dependencies = set(['xep_0004', 'xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace)
-
- register_stanza_plugin(FeatureNegotiation, Form)
-
- register_stanza_plugin(Iq, FeatureNegotiation)
- register_stanza_plugin(Message, FeatureNegotiation)
diff --git a/sleekxmpp/plugins/xep_0020/stanza.py b/sleekxmpp/plugins/xep_0020/stanza.py
deleted file mode 100644
index 13e4056e..00000000
--- a/sleekxmpp/plugins/xep_0020/stanza.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class FeatureNegotiation(ElementBase):
-
- name = 'feature'
- namespace = 'http://jabber.org/protocol/feature-neg'
- plugin_attrib = 'feature_neg'
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0027/__init__.py b/sleekxmpp/plugins/xep_0027/__init__.py
deleted file mode 100644
index b6ed9676..00000000
--- a/sleekxmpp/plugins/xep_0027/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0027.stanza import Signed, Encrypted
-from sleekxmpp.plugins.xep_0027.gpg import XEP_0027
-
-
-register_plugin(XEP_0027)
diff --git a/sleekxmpp/plugins/xep_0027/gpg.py b/sleekxmpp/plugins/xep_0027/gpg.py
deleted file mode 100644
index 52c1c461..00000000
--- a/sleekxmpp/plugins/xep_0027/gpg.py
+++ /dev/null
@@ -1,170 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.thirdparty import GPG
-
-from sleekxmpp.stanza import Presence, Message
-from sleekxmpp.plugins.base import BasePlugin, register_plugin
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0027 import stanza, Signed, Encrypted
-
-
-def _extract_data(data, kind):
- stripped = []
- begin_headers = False
- begin_data = False
- for line in data.split('\n'):
- if not begin_headers and 'BEGIN PGP %s' % kind in line:
- begin_headers = True
- continue
- if begin_headers and line.strip() == '':
- begin_data = True
- continue
- if 'END PGP %s' % kind in line:
- return '\n'.join(stripped)
- if begin_data:
- stripped.append(line)
- return ''
-
-
-class XEP_0027(BasePlugin):
-
- name = 'xep_0027'
- description = 'XEP-0027: Current Jabber OpenPGP Usage'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'gpg_binary': 'gpg',
- 'gpg_home': '',
- 'use_agent': True,
- 'keyring': None,
- 'key_server': 'pgp.mit.edu'
- }
-
- def plugin_init(self):
- self.gpg = GPG(gnupghome=self.gpg_home,
- gpgbinary=self.gpg_binary,
- use_agent=self.use_agent,
- keyring=self.keyring)
-
- self.xmpp.add_filter('out', self._sign_presence)
-
- self._keyids = {}
-
- self.api.register(self._set_keyid, 'set_keyid', default=True)
- self.api.register(self._get_keyid, 'get_keyid', default=True)
- self.api.register(self._del_keyid, 'del_keyid', default=True)
- self.api.register(self._get_keyids, 'get_keyids', default=True)
-
- register_stanza_plugin(Presence, Signed)
- register_stanza_plugin(Message, Encrypted)
-
- self.xmpp.add_event_handler('unverified_signed_presence',
- self._handle_unverified_signed_presence,
- threaded=True)
-
- self.xmpp.register_handler(
- Callback('Signed Presence',
- StanzaPath('presence/signed'),
- self._handle_signed_presence))
-
- self.xmpp.register_handler(
- Callback('Encrypted Message',
- StanzaPath('message/encrypted'),
- self._handle_encrypted_message))
-
- def plugin_end(self):
- self.xmpp.remove_handler('Encrypted Message')
- self.xmpp.remove_handler('Signed Presence')
- self.xmpp.del_filter('out', self._sign_presence)
- self.xmpp.del_event_handler('unverified_signed_presence',
- self._handle_unverified_signed_presence)
-
- def _sign_presence(self, stanza):
- if isinstance(stanza, Presence):
- if stanza['type'] == 'available' or \
- stanza['type'] in Presence.showtypes:
- stanza['signed'] = stanza['status']
- return stanza
-
- def sign(self, data, jid=None):
- keyid = self.get_keyid(jid)
- if keyid:
- signed = self.gpg.sign(data, keyid=keyid)
- return _extract_data(signed.data, 'SIGNATURE')
-
- def encrypt(self, data, jid=None):
- keyid = self.get_keyid(jid)
- if keyid:
- enc = self.gpg.encrypt(data, keyid)
- return _extract_data(enc.data, 'MESSAGE')
-
- def decrypt(self, data, jid=None):
- template = '-----BEGIN PGP MESSAGE-----\n' + \
- '\n' + \
- '%s\n' + \
- '-----END PGP MESSAGE-----\n'
- dec = self.gpg.decrypt(template % data)
- return dec.data
-
- def verify(self, data, sig, jid=None):
- template = '-----BEGIN PGP SIGNED MESSAGE-----\n' + \
- 'Hash: SHA1\n' + \
- '\n' + \
- '%s\n' + \
- '-----BEGIN PGP SIGNATURE-----\n' + \
- '\n' + \
- '%s\n' + \
- '-----END PGP SIGNATURE-----\n'
- v = self.gpg.verify(template % (data, sig))
- return v
-
- def set_keyid(self, jid=None, keyid=None):
- self.api['set_keyid'](jid, args=keyid)
-
- def get_keyid(self, jid=None):
- return self.api['get_keyid'](jid)
-
- def del_keyid(self, jid=None):
- self.api['del_keyid'](jid)
-
- def get_keyids(self):
- return self.api['get_keyids']()
-
- def _handle_signed_presence(self, pres):
- self.xmpp.event('unverified_signed_presence', pres)
-
- def _handle_unverified_signed_presence(self, pres):
- verified = self.verify(pres['status'], pres['signed'])
- if verified.key_id:
- if not self.get_keyid(pres['from']):
- known_keyids = [e['keyid'] for e in self.gpg.list_keys()]
- if verified.key_id not in known_keyids:
- self.gpg.recv_keys(self.key_server, verified.key_id)
- self.set_keyid(jid=pres['from'], keyid=verified.key_id)
- self.xmpp.event('signed_presence', pres)
-
- def _handle_encrypted_message(self, msg):
- self.xmpp.event('encrypted_message', msg)
-
- # =================================================================
-
- def _set_keyid(self, jid, node, ifrom, keyid):
- self._keyids[jid] = keyid
-
- def _get_keyid(self, jid, node, ifrom, keyid):
- return self._keyids.get(jid, None)
-
- def _del_keyid(self, jid, node, ifrom, keyid):
- if jid in self._keyids:
- del self._keyids[jid]
-
- def _get_keyids(self, jid, node, ifrom, data):
- return self._keyids
diff --git a/sleekxmpp/plugins/xep_0027/stanza.py b/sleekxmpp/plugins/xep_0027/stanza.py
deleted file mode 100644
index 08f2032b..00000000
--- a/sleekxmpp/plugins/xep_0027/stanza.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Signed(ElementBase):
- name = 'x'
- namespace = 'jabber:x:signed'
- plugin_attrib = 'signed'
- interfaces = set(['signed'])
- is_extension = True
-
- def set_signed(self, value):
- parent = self.parent()
- xmpp = parent.stream
- data = xmpp['xep_0027'].sign(value, parent['from'])
- if data:
- self.xml.text = data
- else:
- del parent['signed']
-
- def get_signed(self):
- return self.xml.text
-
-
-class Encrypted(ElementBase):
- name = 'x'
- namespace = 'jabber:x:encrypted'
- plugin_attrib = 'encrypted'
- interfaces = set(['encrypted'])
- is_extension = True
-
- def set_encrypted(self, value):
- parent = self.parent()
- xmpp = parent.stream
- data = xmpp['xep_0027'].encrypt(value, parent['to'])
- if data:
- self.xml.text = data
- else:
- del parent['encrypted']
-
- def get_encrypted(self):
- parent = self.parent()
- xmpp = parent.stream
- if self.xml.text:
- return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
- return None
diff --git a/sleekxmpp/plugins/xep_0030/__init__.py b/sleekxmpp/plugins/xep_0030/__init__.py
deleted file mode 100644
index 0d1de65b..00000000
--- a/sleekxmpp/plugins/xep_0030/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0030 import stanza
-from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems
-from sleekxmpp.plugins.xep_0030.static import StaticDisco
-from sleekxmpp.plugins.xep_0030.disco import XEP_0030
-
-
-register_plugin(XEP_0030)
-
-# Retain some backwards compatibility
-xep_0030 = XEP_0030
-XEP_0030.getInfo = XEP_0030.get_info
-XEP_0030.getItems = XEP_0030.get_items
-XEP_0030.make_static = XEP_0030.restore_defaults
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py
deleted file mode 100644
index 721f73f6..00000000
--- a/sleekxmpp/plugins/xep_0030/disco.py
+++ /dev/null
@@ -1,743 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0030 import stanza, DiscoInfo, DiscoItems
-from sleekxmpp.plugins.xep_0030 import StaticDisco
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0030(BasePlugin):
-
- """
- XEP-0030: Service Discovery
-
- Service discovery in XMPP allows entities to discover information about
- other agents in the network, such as the feature sets supported by a
- client, or signposts to other, related entities.
-
- Also see <http://www.xmpp.org/extensions/xep-0030.html>.
-
- The XEP-0030 plugin works using a hierarchy of dynamic
- node handlers, ranging from global handlers to specific
- JID+node handlers. The default set of handlers operate
- in a static manner, storing disco information in memory.
- However, custom handlers may use any available backend
- storage mechanism desired, such as SQLite or Redis.
-
- Node handler hierarchy:
- JID | Node | Level
- ---------------------
- None | None | Global
- Given | None | All nodes for the JID
- None | Given | Node on self.xmpp.boundjid
- Given | Given | A single node
-
- Stream Handlers:
- Disco Info -- Any Iq stanze that includes a query with the
- namespace http://jabber.org/protocol/disco#info.
- Disco Items -- Any Iq stanze that includes a query with the
- namespace http://jabber.org/protocol/disco#items.
-
- Events:
- disco_info -- Received a disco#info Iq query result.
- disco_items -- Received a disco#items Iq query result.
- disco_info_query -- Received a disco#info Iq query request.
- disco_items_query -- Received a disco#items Iq query request.
-
- Attributes:
- stanza -- A reference to the module containing the
- stanza classes provided by this plugin.
- static -- Object containing the default set of
- static node handlers.
- default_handlers -- A dictionary mapping operations to the default
- global handler (by default, the static handlers).
- xmpp -- The main SleekXMPP object.
-
- Methods:
- set_node_handler -- Assign a handler to a JID/node combination.
- del_node_handler -- Remove a handler from a JID/node combination.
- get_info -- Retrieve disco#info data, locally or remote.
- get_items -- Retrieve disco#items data, locally or remote.
- set_identities --
- set_features --
- set_items --
- del_items --
- del_identity --
- del_feature --
- del_item --
- add_identity --
- add_feature --
- add_item --
- """
-
- name = 'xep_0030'
- description = 'XEP-0030: Service Discovery'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'use_cache': True,
- 'wrap_results': False
- }
-
- def plugin_init(self):
- """
- Start the XEP-0030 plugin.
- """
- self.xmpp.register_handler(
- Callback('Disco Info',
- StanzaPath('iq/disco_info'),
- self._handle_disco_info))
-
- self.xmpp.register_handler(
- Callback('Disco Items',
- StanzaPath('iq/disco_items'),
- self._handle_disco_items))
-
- register_stanza_plugin(Iq, DiscoInfo)
- register_stanza_plugin(Iq, DiscoItems)
-
- self.static = StaticDisco(self.xmpp, self)
-
- self._disco_ops = [
- 'get_info', 'set_info', 'set_identities', 'set_features',
- 'get_items', 'set_items', 'del_items', 'add_identity',
- 'del_identity', 'add_feature', 'del_feature', 'add_item',
- 'del_item', 'del_identities', 'del_features', 'cache_info',
- 'get_cached_info', 'supports', 'has_identity']
-
- for op in self._disco_ops:
- self.api.register(getattr(self.static, op), op, default=True)
-
- def _add_disco_op(self, op, default_handler):
- self.api.register(default_handler, op)
- self.api.register_default(default_handler, op)
-
- def set_node_handler(self, htype, jid=None, node=None, handler=None):
- """
- Add a node handler for the given hierarchy level and
- handler type.
-
- Node handlers are ordered in a hierarchy where the
- most specific handler is executed. Thus, a fallback,
- global handler can be used for the majority of cases
- with a few node specific handler that override the
- global behavior.
-
- Node handler hierarchy:
- JID | Node | Level
- ---------------------
- None | None | Global
- Given | None | All nodes for the JID
- None | Given | Node on self.xmpp.boundjid
- Given | Given | A single node
-
- Handler types:
- get_info
- get_items
- set_identities
- set_features
- set_items
- del_items
- del_identities
- del_identity
- del_feature
- del_features
- del_item
- add_identity
- add_feature
- add_item
-
- Arguments:
- htype -- The operation provided by the handler.
- jid -- The JID the handler applies to. May be narrowed
- further if a node is given.
- node -- The particular node the handler is for. If no JID
- is given, then the self.xmpp.boundjid.full is
- assumed.
- handler -- The handler function to use.
- """
- self.api.register(handler, htype, jid, node)
-
- def del_node_handler(self, htype, jid, node):
- """
- Remove a handler type for a JID and node combination.
-
- The next handler in the hierarchy will be used if one
- exists. If removing the global handler, make sure that
- other handlers exist to process existing nodes.
-
- Node handler hierarchy:
- JID | Node | Level
- ---------------------
- None | None | Global
- Given | None | All nodes for the JID
- None | Given | Node on self.xmpp.boundjid
- Given | Given | A single node
-
- Arguments:
- htype -- The type of handler to remove.
- jid -- The JID from which to remove the handler.
- node -- The node from which to remove the handler.
- """
- self.api.unregister(htype, jid, node)
-
- def restore_defaults(self, jid=None, node=None, handlers=None):
- """
- Change all or some of a node's handlers to the default
- handlers. Useful for manually overriding the contents
- of a node that would otherwise be handled by a JID level
- or global level dynamic handler.
-
- The default is to use the built-in static handlers, but that
- may be changed by modifying self.default_handlers.
-
- Arguments:
- jid -- The JID owning the node to modify.
- node -- The node to change to using static handlers.
- handlers -- Optional list of handlers to change to the
- default version. If provided, only these
- handlers will be changed. Otherwise, all
- handlers will use the default version.
- """
- if handlers is None:
- handlers = self._disco_ops
- for op in handlers:
- self.api.restore_default(op, jid, node)
-
- def supports(self, jid=None, node=None, feature=None, local=False,
- cached=True, ifrom=None):
- """
- Check if a JID supports a given feature.
-
- Return values:
- True -- The feature is supported
- False -- The feature is not listed as supported
- None -- Nothing could be found due to a timeout
-
- Arguments:
- jid -- Request info from this JID.
- node -- The particular node to query.
- feature -- The name of the feature to check.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- ifrom -- Specifiy the sender's JID.
- """
- data = {'feature': feature,
- 'local': local,
- 'cached': cached}
- return self.api['supports'](jid, node, ifrom, data)
-
- def has_identity(self, jid=None, node=None, category=None, itype=None,
- lang=None, local=False, cached=True, ifrom=None):
- """
- Check if a JID provides a given identity.
-
- Return values:
- True -- The identity is provided
- False -- The identity is not listed
- None -- Nothing could be found due to a timeout
-
- Arguments:
- jid -- Request info from this JID.
- node -- The particular node to query.
- category -- The category of the identity to check.
- itype -- The type of the identity to check.
- lang -- The language of the identity to check.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- ifrom -- Specifiy the sender's JID.
- """
- data = {'category': category,
- 'itype': itype,
- 'lang': lang,
- 'local': local,
- 'cached': cached}
- return self.api['has_identity'](jid, node, ifrom, data)
-
- def get_info(self, jid=None, node=None, local=None,
- cached=None, **kwargs):
- """
- Retrieve the disco#info results from a given JID/node combination.
-
- Info may be retrieved from both local resources and remote agents;
- the local parameter indicates if the information should be gathered
- by executing the local node handlers, or if a disco#info stanza
- must be generated and sent.
-
- If requesting items from a local JID/node, then only a DiscoInfo
- stanza will be returned. Otherwise, an Iq stanza will be returned.
-
- Arguments:
- jid -- Request info from this JID.
- node -- The particular node to query.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely. The
- timeout value is only used when block=True.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- timeout_callback -- Optional callback to execute when no result
- has been received in timeout seconds.
- """
- if local is None:
- if jid is not None and not isinstance(jid, JID):
- jid = JID(jid)
- if self.xmpp.is_component:
- if jid.domain == self.xmpp.boundjid.domain:
- local = True
- else:
- if str(jid) == str(self.xmpp.boundjid):
- local = True
- jid = jid.full
- elif jid in (None, ''):
- local = True
-
- if local:
- log.debug("Looking up local disco#info data " + \
- "for %s, node %s.", jid, node)
- info = self.api['get_info'](jid, node,
- kwargs.get('ifrom', None),
- kwargs)
- info = self._fix_default_info(info)
- return self._wrap(kwargs.get('ifrom', None), jid, info)
-
- if cached:
- log.debug("Looking up cached disco#info data " + \
- "for %s, node %s.", jid, node)
- info = self.api['get_cached_info'](jid, node,
- kwargs.get('ifrom', None),
- kwargs)
- if info is not None:
- return self._wrap(kwargs.get('ifrom', None), jid, info)
-
- iq = self.xmpp.Iq()
- # Check dfrom parameter for backwards compatibility
- iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
- iq['to'] = jid
- iq['type'] = 'get'
- iq['disco_info']['node'] = node if node else ''
- return iq.send(timeout=kwargs.get('timeout', None),
- block=kwargs.get('block', True),
- callback=kwargs.get('callback', None),
- timeout_callback=kwargs.get('timeout_callback', None))
-
- def set_info(self, jid=None, node=None, info=None):
- """
- Set the disco#info data for a JID/node based on an existing
- disco#info stanza.
- """
- if isinstance(info, Iq):
- info = info['disco_info']
- self.api['set_info'](jid, node, None, info)
-
- def get_items(self, jid=None, node=None, local=False, **kwargs):
- """
- Retrieve the disco#items results from a given JID/node combination.
-
- Items may be retrieved from both local resources and remote agents;
- the local parameter indicates if the items should be gathered by
- executing the local node handlers, or if a disco#items stanza must
- be generated and sent.
-
- If requesting items from a local JID/node, then only a DiscoItems
- stanza will be returned. Otherwise, an Iq stanza will be returned.
-
- Arguments:
- jid -- Request info from this JID.
- node -- The particular node to query.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the items.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- iterator -- If True, return a result set iterator using
- the XEP-0059 plugin, if the plugin is loaded.
- Otherwise the parameter is ignored.
- timeout_callback -- Optional callback to execute when no result
- has been received in timeout seconds.
- """
- if local or local is None and jid is None:
- items = self.api['get_items'](jid, node,
- kwargs.get('ifrom', None),
- kwargs)
- return self._wrap(kwargs.get('ifrom', None), jid, items)
-
- iq = self.xmpp.Iq()
- # Check dfrom parameter for backwards compatibility
- iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
- iq['to'] = jid
- iq['type'] = 'get'
- iq['disco_items']['node'] = node if node else ''
- if kwargs.get('iterator', False) and self.xmpp['xep_0059']:
- return self.xmpp['xep_0059'].iterate(iq, 'disco_items')
- else:
- return iq.send(timeout=kwargs.get('timeout', None),
- block=kwargs.get('block', True),
- callback=kwargs.get('callback', None),
- timeout_callback=kwargs.get('timeout_callback', None))
-
- def set_items(self, jid=None, node=None, **kwargs):
- """
- Set or replace all items for the specified JID/node combination.
-
- The given items must be in a list or set where each item is a
- tuple of the form: (jid, node, name).
-
- Arguments:
- jid -- The JID to modify.
- node -- Optional node to modify.
- items -- A series of items in tuple format.
- """
- self.api['set_items'](jid, node, None, kwargs)
-
- def del_items(self, jid=None, node=None, **kwargs):
- """
- Remove all items from the given JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- Optional node to modify.
- """
- self.api['del_items'](jid, node, None, kwargs)
-
- def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
- """
- Add a new item element to the given JID/node combination.
-
- Each item is required to have a JID, but may also specify
- a node value to reference non-addressable entities.
-
- Arguments:
- jid -- The JID for the item.
- name -- Optional name for the item.
- node -- The node to modify.
- subnode -- Optional node for the item.
- ijid -- The JID to modify.
- """
- if not jid:
- jid = self.xmpp.boundjid.full
- kwargs = {'ijid': jid,
- 'name': name,
- 'inode': subnode}
- self.api['add_item'](ijid, node, None, kwargs)
-
- def del_item(self, jid=None, node=None, **kwargs):
- """
- Remove a single item from the given JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- ijid -- The item's JID.
- inode -- The item's node.
- """
- self.api['del_item'](jid, node, None, kwargs)
-
- def add_identity(self, category='', itype='', name='',
- node=None, jid=None, lang=None):
- """
- Add a new identity to the given JID/node combination.
-
- Each identity must be unique in terms of all four identity
- components: category, type, name, and language.
-
- Multiple, identical category/type pairs are allowed only
- if the xml:lang values are different. Likewise, multiple
- category/type/xml:lang pairs are allowed so long as the
- names are different. A category and type is always required.
-
- Arguments:
- category -- The identity's category.
- itype -- The identity's type.
- name -- Optional name for the identity.
- lang -- Optional two-letter language code.
- node -- The node to modify.
- jid -- The JID to modify.
- """
- kwargs = {'category': category,
- 'itype': itype,
- 'name': name,
- 'lang': lang}
- self.api['add_identity'](jid, node, None, kwargs)
-
- def add_feature(self, feature, node=None, jid=None):
- """
- Add a feature to a JID/node combination.
-
- Arguments:
- feature -- The namespace of the supported feature.
- node -- The node to modify.
- jid -- The JID to modify.
- """
- kwargs = {'feature': feature}
- self.api['add_feature'](jid, node, None, kwargs)
-
- def del_identity(self, jid=None, node=None, **kwargs):
- """
- Remove an identity from the given JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- category -- The identity's category.
- itype -- The identity's type value.
- name -- Optional, human readable name for the identity.
- lang -- Optional, the identity's xml:lang value.
- """
- self.api['del_identity'](jid, node, None, kwargs)
-
- def del_feature(self, jid=None, node=None, **kwargs):
- """
- Remove a feature from a given JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- feature -- The feature's namespace.
- """
- self.api['del_feature'](jid, node, None, kwargs)
-
- def set_identities(self, jid=None, node=None, **kwargs):
- """
- Add or replace all identities for the given JID/node combination.
-
- The identities must be in a set where each identity is a tuple
- of the form: (category, type, lang, name)
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- identities -- A set of identities in tuple form.
- lang -- Optional, xml:lang value.
- """
- self.api['set_identities'](jid, node, None, kwargs)
-
- def del_identities(self, jid=None, node=None, **kwargs):
- """
- Remove all identities for a JID/node combination.
-
- If a language is specified, only identities using that
- language will be removed.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- lang -- Optional. If given, only remove identities
- using this xml:lang value.
- """
- self.api['del_identities'](jid, node, None, kwargs)
-
- def set_features(self, jid=None, node=None, **kwargs):
- """
- Add or replace the set of supported features
- for a JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- features -- The new set of supported features.
- """
- self.api['set_features'](jid, node, None, kwargs)
-
- def del_features(self, jid=None, node=None, **kwargs):
- """
- Remove all features from a JID/node combination.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- """
- self.api['del_features'](jid, node, None, kwargs)
-
- def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
- """
- Execute the most specific node handler for the given
- JID/node combination.
-
- Arguments:
- htype -- The handler type to execute.
- jid -- The JID requested.
- node -- The node requested.
- data -- Optional, custom data to pass to the handler.
- """
- if not data:
- data = {}
-
- return self.api[htype](jid, node, ifrom, data)
-
- def _handle_disco_info(self, iq):
- """
- Process an incoming disco#info stanza. If it is a get
- request, find and return the appropriate identities
- and features. If it is an info result, fire the
- disco_info event.
-
- Arguments:
- iq -- The incoming disco#items stanza.
- """
- if iq['type'] == 'get':
- log.debug("Received disco info query from " + \
- "<%s> to <%s>.", iq['from'], iq['to'])
- info = self.api['get_info'](iq['to'],
- iq['disco_info']['node'],
- iq['from'],
- iq)
- if isinstance(info, Iq):
- info['id'] = iq['id']
- info.send()
- else:
- iq.reply()
- if info:
- info = self._fix_default_info(info)
- iq.set_payload(info.xml)
- iq.send()
- elif iq['type'] == 'result':
- log.debug("Received disco info result from " + \
- "<%s> to <%s>.", iq['from'], iq['to'])
- if self.use_cache:
- log.debug("Caching disco info result from " \
- "<%s> to <%s>.", iq['from'], iq['to'])
- if self.xmpp.is_component:
- ito = iq['to'].full
- else:
- ito = None
- self.api['cache_info'](iq['from'],
- iq['disco_info']['node'],
- ito,
- iq)
- self.xmpp.event('disco_info', iq)
-
- def _handle_disco_items(self, iq):
- """
- Process an incoming disco#items stanza. If it is a get
- request, find and return the appropriate items. If it
- is an items result, fire the disco_items event.
-
- Arguments:
- iq -- The incoming disco#items stanza.
- """
- if iq['type'] == 'get':
- log.debug("Received disco items query from " + \
- "<%s> to <%s>.", iq['from'], iq['to'])
- items = self.api['get_items'](iq['to'],
- iq['disco_items']['node'],
- iq['from'],
- iq)
- if isinstance(items, Iq):
- items.send()
- else:
- iq.reply()
- if items:
- iq.set_payload(items.xml)
- iq.send()
- elif iq['type'] == 'result':
- log.debug("Received disco items result from " + \
- "%s to %s.", iq['from'], iq['to'])
- self.xmpp.event('disco_items', iq)
-
- def _fix_default_info(self, info):
- """
- Disco#info results for a JID are required to include at least
- one identity and feature. As a default, if no other identity is
- provided, SleekXMPP will use either the generic component or the
- bot client identity. A the standard disco#info feature will also be
- added if no features are provided.
-
- Arguments:
- info -- The disco#info quest (not the full Iq stanza) to modify.
- """
- result = info
- if isinstance(info, Iq):
- info = info['disco_info']
- if not info['node']:
- if not info['identities']:
- if self.xmpp.is_component:
- log.debug("No identity found for this entity. " + \
- "Using default component identity.")
- info.add_identity('component', 'generic')
- else:
- log.debug("No identity found for this entity. " + \
- "Using default client identity.")
- info.add_identity('client', 'bot')
- if not info['features']:
- log.debug("No features found for this entity. " + \
- "Using default disco#info feature.")
- info.add_feature(info.namespace)
- return result
-
- def _wrap(self, ito, ifrom, payload, force=False):
- """
- Ensure that results are wrapped in an Iq stanza
- if self.wrap_results has been set to True.
-
- Arguments:
- ito -- The JID to use as the 'to' value
- ifrom -- The JID to use as the 'from' value
- payload -- The disco data to wrap
- force -- Force wrapping, regardless of self.wrap_results
- """
- if (force or self.wrap_results) and not isinstance(payload, Iq):
- iq = self.xmpp.Iq()
- # Since we're simulating a result, we have to treat
- # the 'from' and 'to' values opposite the normal way.
- iq['to'] = self.xmpp.boundjid if ito is None else ito
- iq['from'] = self.xmpp.boundjid if ifrom is None else ifrom
- iq['type'] = 'result'
- iq.append(payload)
- return iq
- return payload
diff --git a/sleekxmpp/plugins/xep_0030/stanza/__init__.py b/sleekxmpp/plugins/xep_0030/stanza/__init__.py
deleted file mode 100644
index 0d97cf3d..00000000
--- a/sleekxmpp/plugins/xep_0030/stanza/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0030.stanza.info import DiscoInfo
-from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems
diff --git a/sleekxmpp/plugins/xep_0030/stanza/info.py b/sleekxmpp/plugins/xep_0030/stanza/info.py
deleted file mode 100644
index 25d1d07f..00000000
--- a/sleekxmpp/plugins/xep_0030/stanza/info.py
+++ /dev/null
@@ -1,276 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class DiscoInfo(ElementBase):
-
- """
- XMPP allows for users and agents to find the identities and features
- supported by other entities in the XMPP network through service discovery,
- or "disco". In particular, the "disco#info" query type for <iq> stanzas is
- used to request the list of identities and features offered by a JID.
-
- An identity is a combination of a category and type, such as the 'client'
- category with a type of 'pc' to indicate the agent is a human operated
- client with a GUI, or a category of 'gateway' with a type of 'aim' to
- identify the agent as a gateway for the legacy AIM protocol. See
- <http://xmpp.org/registrar/disco-categories.html> for a full list of
- accepted category and type combinations.
-
- Features are simply a set of the namespaces that identify the supported
- features. For example, a client that supports service discovery will
- include the feature 'http://jabber.org/protocol/disco#info'.
-
- Since clients and components may operate in several roles at once, identity
- and feature information may be grouped into "nodes". If one were to write
- all of the identities and features used by a client, then node names would
- be like section headings.
-
- Example disco#info stanzas:
- <iq type="get">
- <query xmlns="http://jabber.org/protocol/disco#info" />
- </iq>
-
- <iq type="result">
- <query xmlns="http://jabber.org/protocol/disco#info">
- <identity category="client" type="bot" name="SleekXMPP Bot" />
- <feature var="http://jabber.org/protocol/disco#info" />
- <feature var="jabber:x:data" />
- <feature var="urn:xmpp:ping" />
- </query>
- </iq>
-
- Stanza Interface:
- node -- The name of the node to either
- query or return info from.
- identities -- A set of 4-tuples, where each tuple contains
- the category, type, xml:lang, and name
- of an identity.
- features -- A set of namespaces for features.
-
- Methods:
- add_identity -- Add a new, single identity.
- del_identity -- Remove a single identity.
- get_identities -- Return all identities in tuple form.
- set_identities -- Use multiple identities, each given in tuple form.
- del_identities -- Remove all identities.
- add_feature -- Add a single feature.
- del_feature -- Remove a single feature.
- get_features -- Return a list of all features.
- set_features -- Use a given list of features.
- del_features -- Remove all features.
- """
-
- name = 'query'
- namespace = 'http://jabber.org/protocol/disco#info'
- plugin_attrib = 'disco_info'
- interfaces = set(('node', 'features', 'identities'))
- lang_interfaces = set(('identities',))
-
- # Cache identities and features
- _identities = set()
- _features = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches identity and feature information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
-
- self._identities = set([id[0:3] for id in self['identities']])
- self._features = self['features']
-
- def add_identity(self, category, itype, name=None, lang=None):
- """
- Add a new identity element. Each identity must be unique
- in terms of all four identity components.
-
- Multiple, identical category/type pairs are allowed only
- if the xml:lang values are different. Likewise, multiple
- category/type/xml:lang pairs are allowed so long as the names
- are different. In any case, a category and type are required.
-
- Arguments:
- category -- The general category to which the agent belongs.
- itype -- A more specific designation with the category.
- name -- Optional human readable name for this identity.
- lang -- Optional standard xml:lang value.
- """
- identity = (category, itype, lang)
- if identity not in self._identities:
- self._identities.add(identity)
- id_xml = ET.Element('{%s}identity' % self.namespace)
- id_xml.attrib['category'] = category
- id_xml.attrib['type'] = itype
- if lang:
- id_xml.attrib['{%s}lang' % self.xml_ns] = lang
- if name:
- id_xml.attrib['name'] = name
- self.xml.append(id_xml)
- return True
- return False
-
- def del_identity(self, category, itype, name=None, lang=None):
- """
- Remove a given identity.
-
- Arguments:
- category -- The general category to which the agent belonged.
- itype -- A more specific designation with the category.
- name -- Optional human readable name for this identity.
- lang -- Optional, standard xml:lang value.
- """
- identity = (category, itype, lang)
- if identity in self._identities:
- self._identities.remove(identity)
- for id_xml in self.findall('{%s}identity' % self.namespace):
- id = (id_xml.attrib['category'],
- id_xml.attrib['type'],
- id_xml.attrib.get('{%s}lang' % self.xml_ns, None))
- if id == identity:
- self.xml.remove(id_xml)
- return True
- return False
-
- def get_identities(self, lang=None, dedupe=True):
- """
- Return a set of all identities in tuple form as so:
- (category, type, lang, name)
-
- If a language was specified, only return identities using
- that language.
-
- Arguments:
- lang -- Optional, standard xml:lang value.
- dedupe -- If True, de-duplicate identities, otherwise
- return a list of all identities.
- """
- if dedupe:
- identities = set()
- else:
- identities = []
- for id_xml in self.findall('{%s}identity' % self.namespace):
- xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
- if lang is None or xml_lang == lang:
- id = (id_xml.attrib['category'],
- id_xml.attrib['type'],
- id_xml.attrib.get('{%s}lang' % self.xml_ns, None),
- id_xml.attrib.get('name', None))
- if dedupe:
- identities.add(id)
- else:
- identities.append(id)
- return identities
-
- def set_identities(self, identities, lang=None):
- """
- Add or replace all identities. The identities must be a in set
- where each identity is a tuple of the form:
- (category, type, lang, name)
-
- If a language is specifified, any identities using that language
- will be removed to be replaced with the given identities.
-
- NOTE: An identity's language will not be changed regardless of
- the value of lang.
-
- Arguments:
- identities -- A set of identities in tuple form.
- lang -- Optional, standard xml:lang value.
- """
- self.del_identities(lang)
- for identity in identities:
- category, itype, lang, name = identity
- self.add_identity(category, itype, name, lang)
-
- def del_identities(self, lang=None):
- """
- Remove all identities. If a language was specified, only
- remove identities using that language.
-
- Arguments:
- lang -- Optional, standard xml:lang value.
- """
- for id_xml in self.findall('{%s}identity' % self.namespace):
- if lang is None:
- self.xml.remove(id_xml)
- elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang:
- self._identities.remove((
- id_xml.attrib['category'],
- id_xml.attrib['type'],
- id_xml.attrib.get('{%s}lang' % self.xml_ns, None)))
- self.xml.remove(id_xml)
-
- def add_feature(self, feature):
- """
- Add a single, new feature.
-
- Arguments:
- feature -- The namespace of the supported feature.
- """
- if feature not in self._features:
- self._features.add(feature)
- feature_xml = ET.Element('{%s}feature' % self.namespace)
- feature_xml.attrib['var'] = feature
- self.xml.append(feature_xml)
- return True
- return False
-
- def del_feature(self, feature):
- """
- Remove a single feature.
-
- Arguments:
- feature -- The namespace of the removed feature.
- """
- if feature in self._features:
- self._features.remove(feature)
- for feature_xml in self.findall('{%s}feature' % self.namespace):
- if feature_xml.attrib['var'] == feature:
- self.xml.remove(feature_xml)
- return True
- return False
-
- def get_features(self, dedupe=True):
- """Return the set of all supported features."""
- if dedupe:
- features = set()
- else:
- features = []
- for feature_xml in self.findall('{%s}feature' % self.namespace):
- if dedupe:
- features.add(feature_xml.attrib['var'])
- else:
- features.append(feature_xml.attrib['var'])
- return features
-
- def set_features(self, features):
- """
- Add or replace the set of supported features.
-
- Arguments:
- features -- The new set of supported features.
- """
- self.del_features()
- for feature in features:
- self.add_feature(feature)
-
- def del_features(self):
- """Remove all features."""
- self._features = set()
- for feature_xml in self.findall('{%s}feature' % self.namespace):
- self.xml.remove(feature_xml)
diff --git a/sleekxmpp/plugins/xep_0030/stanza/items.py b/sleekxmpp/plugins/xep_0030/stanza/items.py
deleted file mode 100644
index 10458614..00000000
--- a/sleekxmpp/plugins/xep_0030/stanza/items.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class DiscoItems(ElementBase):
-
- """
- Example disco#items stanzas:
- <iq type="get">
- <query xmlns="http://jabber.org/protocol/disco#items" />
- </iq>
-
- <iq type="result">
- <query xmlns="http://jabber.org/protocol/disco#items">
- <item jid="chat.example.com"
- node="xmppdev"
- name="XMPP Dev" />
- <item jid="chat.example.com"
- node="sleekdev"
- name="SleekXMPP Dev" />
- </query>
- </iq>
-
- Stanza Interface:
- node -- The name of the node to either
- query or return info from.
- items -- A list of 3-tuples, where each tuple contains
- the JID, node, and name of an item.
-
- Methods:
- add_item -- Add a single new item.
- del_item -- Remove a single item.
- get_items -- Return all items.
- set_items -- Set or replace all items.
- del_items -- Remove all items.
- """
-
- name = 'query'
- namespace = 'http://jabber.org/protocol/disco#items'
- plugin_attrib = 'disco_items'
- interfaces = set(('node', 'items'))
-
- # Cache items
- _items = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._items = set([item[0:2] for item in self['items']])
-
- def add_item(self, jid, node=None, name=None):
- """
- Add a new item element. Each item is required to have a
- JID, but may also specify a node value to reference
- non-addressable entitities.
-
- Arguments:
- jid -- The JID for the item.
- node -- Optional additional information to reference
- non-addressable items.
- name -- Optional human readable name for the item.
- """
- if (jid, node) not in self._items:
- self._items.add((jid, node))
- item = DiscoItem(parent=self)
- item['jid'] = jid
- item['node'] = node
- item['name'] = name
- self.iterables.append(item)
- return True
- return False
-
- def del_item(self, jid, node=None):
- """
- Remove a single item.
-
- Arguments:
- jid -- JID of the item to remove.
- node -- Optional extra identifying information.
- """
- if (jid, node) in self._items:
- for item_xml in self.findall('{%s}item' % self.namespace):
- item = (item_xml.attrib['jid'],
- item_xml.attrib.get('node', None))
- if item == (jid, node):
- self.xml.remove(item_xml)
- return True
- return False
-
- def get_items(self):
- """Return all items."""
- items = set()
- for item in self['substanzas']:
- if isinstance(item, DiscoItem):
- items.add((item['jid'], item['node'], item['name']))
- return items
-
- def set_items(self, items):
- """
- Set or replace all items. The given items must be in a
- list or set where each item is a tuple of the form:
- (jid, node, name)
-
- Arguments:
- items -- A series of items in tuple format.
- """
- self.del_items()
- for item in items:
- jid, node, name = item
- self.add_item(jid, node, name)
-
- def del_items(self):
- """Remove all items."""
- self._items = set()
- items = [i for i in self.iterables if isinstance(i, DiscoItem)]
- for item in items:
- self.xml.remove(item.xml)
- self.iterables.remove(item)
-
-
-class DiscoItem(ElementBase):
- name = 'item'
- namespace = 'http://jabber.org/protocol/disco#items'
- plugin_attrib = name
- interfaces = set(('jid', 'node', 'name'))
-
- def get_node(self):
- """Return the item's node name or ``None``."""
- return self._get_attr('node', None)
-
- def get_name(self):
- """Return the item's human readable name, or ``None``."""
- return self._get_attr('name', None)
-
-
-register_stanza_plugin(DiscoItems, DiscoItem, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py
deleted file mode 100644
index dd5317d1..00000000
--- a/sleekxmpp/plugins/xep_0030/static.py
+++ /dev/null
@@ -1,430 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import threading
-
-from sleekxmpp import Iq
-from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems
-
-
-log = logging.getLogger(__name__)
-
-
-class StaticDisco(object):
-
- """
- While components will likely require fully dynamic handling
- of service discovery information, most clients and simple bots
- only need to manage a few disco nodes that will remain mostly
- static.
-
- StaticDisco provides a set of node handlers that will store
- static sets of disco info and items in memory.
-
- Attributes:
- nodes -- A dictionary mapping (JID, node) tuples to a dict
- containing a disco#info and a disco#items stanza.
- xmpp -- The main SleekXMPP object.
- """
-
- def __init__(self, xmpp, disco):
- """
- Create a static disco interface. Sets of disco#info and
- disco#items are maintained for every given JID and node
- combination. These stanzas are used to store disco
- information in memory without any additional processing.
-
- Arguments:
- xmpp -- The main SleekXMPP object.
- """
- self.nodes = {}
- self.xmpp = xmpp
- self.disco = disco
- self.lock = threading.RLock()
-
- def add_node(self, jid=None, node=None, ifrom=None):
- """
- Create a new set of stanzas for the provided
- JID and node combination.
-
- Arguments:
- jid -- The JID that will own the new stanzas.
- node -- The node that will own the new stanzas.
- """
- with self.lock:
- if jid is None:
- jid = self.xmpp.boundjid.full
- if node is None:
- node = ''
- if ifrom is None:
- ifrom = ''
- if isinstance(ifrom, JID):
- ifrom = ifrom.full
- if (jid, node, ifrom) not in self.nodes:
- self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(),
- 'items': DiscoItems()}
- self.nodes[(jid, node, ifrom)]['info']['node'] = node
- self.nodes[(jid, node, ifrom)]['items']['node'] = node
-
- def get_node(self, jid=None, node=None, ifrom=None):
- with self.lock:
- if jid is None:
- jid = self.xmpp.boundjid.full
- if node is None:
- node = ''
- if ifrom is None:
- ifrom = ''
- if isinstance(ifrom, JID):
- ifrom = ifrom.full
- if (jid, node, ifrom) not in self.nodes:
- self.add_node(jid, node, ifrom)
- return self.nodes[(jid, node, ifrom)]
-
- def node_exists(self, jid=None, node=None, ifrom=None):
- with self.lock:
- if jid is None:
- jid = self.xmpp.boundjid.full
- if node is None:
- node = ''
- if ifrom is None:
- ifrom = ''
- if isinstance(ifrom, JID):
- ifrom = ifrom.full
- if (jid, node, ifrom) not in self.nodes:
- return False
- return True
-
- # =================================================================
- # Node Handlers
- #
- # Each handler accepts four arguments: jid, node, ifrom, and data.
- # The jid and node parameters together determine the set of info
- # and items stanzas that will be retrieved or added. Additionally,
- # the ifrom value allows for cached results when results vary based
- # on the requester's JID. The data parameter is a dictionary with
- # additional parameters that will be passed to other calls.
- #
- # This implementation does not allow different responses based on
- # the requester's JID, except for cached results. To do that,
- # register a custom node handler.
-
- def supports(self, jid, node, ifrom, data):
- """
- Check if a JID supports a given feature.
-
- The data parameter may provide:
- feature -- The feature to check for support.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- feature = data.get('feature', None)
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- if not feature:
- return False
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- features = info['disco_info']['features']
- return feature in features
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def has_identity(self, jid, node, ifrom, data):
- """
- Check if a JID has a given identity.
-
- The data parameter may provide:
- category -- The category of the identity to check.
- itype -- The type of the identity to check.
- lang -- The language of the identity to check.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- identity = (data.get('category', None),
- data.get('itype', None),
- data.get('lang', None))
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- trunc = lambda i: (i[0], i[1], i[2])
- return identity in map(trunc, info['disco_info']['identities'])
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def get_info(self, jid, node, ifrom, data):
- """
- Return the stored info data for the requested JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if not self.node_exists(jid, node):
- if not node:
- return DiscoInfo()
- else:
- raise XMPPError(condition='item-not-found')
- else:
- return self.get_node(jid, node)['info']
-
- def set_info(self, jid, node, ifrom, data):
- """
- Set the entire info stanza for a JID/node at once.
-
- The data parameter is a disco#info substanza.
- """
- with self.lock:
- self.add_node(jid, node)
- self.get_node(jid, node)['info'] = data
-
- def del_info(self, jid, node, ifrom, data):
- """
- Reset the info stanza for a given JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if self.node_exists(jid, node):
- self.get_node(jid, node)['info'] = DiscoInfo()
-
- def get_items(self, jid, node, ifrom, data):
- """
- Return the stored items data for the requested JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if not self.node_exists(jid, node):
- if not node:
- return DiscoItems()
- else:
- raise XMPPError(condition='item-not-found')
- else:
- return self.get_node(jid, node)['items']
-
- def set_items(self, jid, node, ifrom, data):
- """
- Replace the stored items data for a JID/node combination.
-
- The data parameter may provide:
- items -- A set of items in tuple format.
- """
- with self.lock:
- items = data.get('items', set())
- self.add_node(jid, node)
- self.get_node(jid, node)['items']['items'] = items
-
- def del_items(self, jid, node, ifrom, data):
- """
- Reset the items stanza for a given JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if self.node_exists(jid, node):
- self.get_node(jid, node)['items'] = DiscoItems()
-
- def add_identity(self, jid, node, ifrom, data):
- """
- Add a new identity to te JID/node combination.
-
- The data parameter may provide:
- category -- The general category to which the agent belongs.
- itype -- A more specific designation with the category.
- name -- Optional human readable name for this identity.
- lang -- Optional standard xml:lang value.
- """
- with self.lock:
- self.add_node(jid, node)
- self.get_node(jid, node)['info'].add_identity(
- data.get('category', ''),
- data.get('itype', ''),
- data.get('name', None),
- data.get('lang', None))
-
- def set_identities(self, jid, node, ifrom, data):
- """
- Add or replace all identities for a JID/node combination.
-
- The data parameter should include:
- identities -- A list of identities in tuple form:
- (category, type, name, lang)
- """
- with self.lock:
- identities = data.get('identities', set())
- self.add_node(jid, node)
- self.get_node(jid, node)['info']['identities'] = identities
-
- def del_identity(self, jid, node, ifrom, data):
- """
- Remove an identity from a JID/node combination.
-
- The data parameter may provide:
- category -- The general category to which the agent belonged.
- itype -- A more specific designation with the category.
- name -- Optional human readable name for this identity.
- lang -- Optional, standard xml:lang value.
- """
- with self.lock:
- if self.node_exists(jid, node):
- self.get_node(jid, node)['info'].del_identity(
- data.get('category', ''),
- data.get('itype', ''),
- data.get('name', None),
- data.get('lang', None))
-
- def del_identities(self, jid, node, ifrom, data):
- """
- Remove all identities from a JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if self.node_exists(jid, node):
- del self.get_node(jid, node)['info']['identities']
-
- def add_feature(self, jid, node, ifrom, data):
- """
- Add a feature to a JID/node combination.
-
- The data parameter should include:
- feature -- The namespace of the supported feature.
- """
- with self.lock:
- self.add_node(jid, node)
- self.get_node(jid, node)['info'].add_feature(
- data.get('feature', ''))
-
- def set_features(self, jid, node, ifrom, data):
- """
- Add or replace all features for a JID/node combination.
-
- The data parameter should include:
- features -- The new set of supported features.
- """
- with self.lock:
- features = data.get('features', set())
- self.add_node(jid, node)
- self.get_node(jid, node)['info']['features'] = features
-
- def del_feature(self, jid, node, ifrom, data):
- """
- Remove a feature from a JID/node combination.
-
- The data parameter should include:
- feature -- The namespace of the removed feature.
- """
- with self.lock:
- if self.node_exists(jid, node):
- self.get_node(jid, node)['info'].del_feature(
- data.get('feature', ''))
-
- def del_features(self, jid, node, ifrom, data):
- """
- Remove all features from a JID/node combination.
-
- The data parameter is not used.
- """
- with self.lock:
- if not self.node_exists(jid, node):
- return
- del self.get_node(jid, node)['info']['features']
-
- def add_item(self, jid, node, ifrom, data):
- """
- Add an item to a JID/node combination.
-
- The data parameter may include:
- ijid -- The JID for the item.
- inode -- Optional additional information to reference
- non-addressable items.
- name -- Optional human readable name for the item.
- """
- with self.lock:
- self.add_node(jid, node)
- self.get_node(jid, node)['items'].add_item(
- data.get('ijid', ''),
- node=data.get('inode', ''),
- name=data.get('name', ''))
-
- def del_item(self, jid, node, ifrom, data):
- """
- Remove an item from a JID/node combination.
-
- The data parameter may include:
- ijid -- JID of the item to remove.
- inode -- Optional extra identifying information.
- """
- with self.lock:
- if self.node_exists(jid, node):
- self.get_node(jid, node)['items'].del_item(
- data.get('ijid', ''),
- node=data.get('inode', None))
-
- def cache_info(self, jid, node, ifrom, data):
- """
- Cache disco information for an external JID.
-
- The data parameter is the Iq result stanza
- containing the disco info to cache, or
- the disco#info substanza itself.
- """
- with self.lock:
- if isinstance(data, Iq):
- data = data['disco_info']
-
- self.add_node(jid, node, ifrom)
- self.get_node(jid, node, ifrom)['info'] = data
-
- def get_cached_info(self, jid, node, ifrom, data):
- """
- Retrieve cached disco info data.
-
- The data parameter is not used.
- """
- with self.lock:
- if not self.node_exists(jid, node, ifrom):
- return None
- else:
- return self.get_node(jid, node, ifrom)['info']
diff --git a/sleekxmpp/plugins/xep_0033/__init__.py b/sleekxmpp/plugins/xep_0033/__init__.py
deleted file mode 100644
index ba8152c4..00000000
--- a/sleekxmpp/plugins/xep_0033/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0033 import stanza
-from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address
-from sleekxmpp.plugins.xep_0033.addresses import XEP_0033
-
-
-register_plugin(XEP_0033)
-
-# Retain some backwards compatibility
-xep_0033 = XEP_0033
-Addresses.addAddress = Addresses.add_address
diff --git a/sleekxmpp/plugins/xep_0033/addresses.py b/sleekxmpp/plugins/xep_0033/addresses.py
deleted file mode 100644
index 13cb7267..00000000
--- a/sleekxmpp/plugins/xep_0033/addresses.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Message, Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0033 import stanza, Addresses
-
-
-class XEP_0033(BasePlugin):
-
- """
- XEP-0033: Extended Stanza Addressing
- """
-
- name = 'xep_0033'
- description = 'XEP-0033: Extended Stanza Addressing'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, Addresses)
- register_stanza_plugin(Presence, Addresses)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Addresses.namespace)
-
diff --git a/sleekxmpp/plugins/xep_0033/stanza.py b/sleekxmpp/plugins/xep_0033/stanza.py
deleted file mode 100644
index 1ff9fb20..00000000
--- a/sleekxmpp/plugins/xep_0033/stanza.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin
-
-
-class Addresses(ElementBase):
-
- name = 'addresses'
- namespace = 'http://jabber.org/protocol/address'
- plugin_attrib = 'addresses'
- interfaces = set()
-
- def add_address(self, atype='to', jid='', node='', uri='',
- desc='', delivered=False):
- addr = Address(parent=self)
- addr['type'] = atype
- addr['jid'] = jid
- addr['node'] = node
- addr['uri'] = uri
- addr['desc'] = desc
- addr['delivered'] = delivered
-
- return addr
-
- # Additional methods for manipulating sets of addresses
- # based on type are generated below.
-
-
-class Address(ElementBase):
-
- name = 'address'
- namespace = 'http://jabber.org/protocol/address'
- plugin_attrib = 'address'
- interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered'])
-
- address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_delivered(self):
- value = self._get_attr('delivered', False)
- return value and value.lower() in ('true', '1')
-
- def set_delivered(self, delivered):
- if delivered:
- self._set_attr('delivered', 'true')
- else:
- del self['delivered']
-
- def set_uri(self, uri):
- if uri:
- del self['jid']
- del self['node']
- self._set_attr('uri', uri)
- else:
- self._del_attr('uri')
-
-
-# =====================================================================
-# Auto-generate address type filters for the Addresses class.
-
-def _addr_filter(atype):
- def _type_filter(addr):
- if isinstance(addr, Address):
- if atype == 'all' or addr['type'] == atype:
- return True
- return False
- return _type_filter
-
-
-def _build_methods(atype):
-
- def get_multi(self):
- return list(filter(_addr_filter(atype), self))
-
- def set_multi(self, value):
- del self[atype]
- for addr in value:
-
- # Support assigning dictionary versions of addresses
- # instead of full Address objects.
- if not isinstance(addr, Address):
- if atype != 'all':
- addr['type'] = atype
- elif 'atype' in addr and 'type' not in addr:
- addr['type'] = addr['atype']
- addrObj = Address()
- addrObj.values = addr
- addr = addrObj
-
- self.append(addr)
-
- def del_multi(self):
- res = list(filter(_addr_filter(atype), self))
- for addr in res:
- self.iterables.remove(addr)
- self.xml.remove(addr.xml)
-
- return get_multi, set_multi, del_multi
-
-
-for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
- get_multi, set_multi, del_multi = _build_methods(atype)
-
- Addresses.interfaces.add(atype)
- setattr(Addresses, "get_%s" % atype, get_multi)
- setattr(Addresses, "set_%s" % atype, set_multi)
- setattr(Addresses, "del_%s" % atype, del_multi)
-
- # To retain backwards compatibility:
- setattr(Addresses, "get%s" % atype.title(), get_multi)
- setattr(Addresses, "set%s" % atype.title(), set_multi)
- setattr(Addresses, "del%s" % atype.title(), del_multi)
- if atype == 'all':
- Addresses.interfaces.add('addresses')
- setattr(Addresses, "getAddresses", get_multi)
- setattr(Addresses, "setAddresses", set_multi)
- setattr(Addresses, "delAddresses", del_multi)
-
-
-register_stanza_plugin(Addresses, Address, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py
deleted file mode 100644
index cc96d66e..00000000
--- a/sleekxmpp/plugins/xep_0045.py
+++ /dev/null
@@ -1,412 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-from __future__ import with_statement
-
-import logging
-
-from sleekxmpp import Presence
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
-from sleekxmpp.xmlstream.handler.callback import Callback
-from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
-from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
-from sleekxmpp.exceptions import IqError, IqTimeout
-
-
-log = logging.getLogger(__name__)
-
-
-class MUCPresence(ElementBase):
- name = 'x'
- namespace = 'http://jabber.org/protocol/muc#user'
- plugin_attrib = 'muc'
- interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
- affiliations = set(('', ))
- roles = set(('', ))
-
- def getXMLItem(self):
- item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
- if item is None:
- item = ET.Element('{http://jabber.org/protocol/muc#user}item')
- self.xml.append(item)
- return item
-
- def getAffiliation(self):
- #TODO if no affilation, set it to the default and return default
- item = self.getXMLItem()
- return item.get('affiliation', '')
-
- def setAffiliation(self, value):
- item = self.getXMLItem()
- #TODO check for valid affiliation
- item.attrib['affiliation'] = value
- return self
-
- def delAffiliation(self):
- item = self.getXMLItem()
- #TODO set default affiliation
- if 'affiliation' in item.attrib: del item.attrib['affiliation']
- return self
-
- def getJid(self):
- item = self.getXMLItem()
- return JID(item.get('jid', ''))
-
- def setJid(self, value):
- item = self.getXMLItem()
- if not isinstance(value, str):
- value = str(value)
- item.attrib['jid'] = value
- return self
-
- def delJid(self):
- item = self.getXMLItem()
- if 'jid' in item.attrib: del item.attrib['jid']
- return self
-
- def getRole(self):
- item = self.getXMLItem()
- #TODO get default role, set default role if none
- return item.get('role', '')
-
- def setRole(self, value):
- item = self.getXMLItem()
- #TODO check for valid role
- item.attrib['role'] = value
- return self
-
- def delRole(self):
- item = self.getXMLItem()
- #TODO set default role
- if 'role' in item.attrib: del item.attrib['role']
- return self
-
- def getNick(self):
- return self.parent()['from'].resource
-
- def getRoom(self):
- return self.parent()['from'].bare
-
- def setNick(self, value):
- log.warning("Cannot set nick through mucpresence plugin.")
- return self
-
- def setRoom(self, value):
- log.warning("Cannot set room through mucpresence plugin.")
- return self
-
- def delNick(self):
- log.warning("Cannot delete nick through mucpresence plugin.")
- return self
-
- def delRoom(self):
- log.warning("Cannot delete room through mucpresence plugin.")
- return self
-
-
-class XEP_0045(BasePlugin):
-
- """
- Implements XEP-0045 Multi-User Chat
- """
-
- name = 'xep_0045'
- description = 'XEP-0045: Multi-User Chat'
- dependencies = set(['xep_0030', 'xep_0004'])
-
- def plugin_init(self):
- self.rooms = {}
- self.ourNicks = {}
- self.xep = '0045'
- # load MUC support in presence stanzas
- register_stanza_plugin(Presence, MUCPresence)
- self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
- self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message))
- self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
- self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
- self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
- self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
- self.xmpp.default_ns,
- 'http://jabber.org/protocol/muc#user',
- 'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite))
-
- def handle_groupchat_invite(self, inv):
- """ Handle an invite into a muc.
- """
- logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv)
- if inv['from'] not in self.rooms.keys():
- self.xmpp.event("groupchat_invite", inv)
-
- def handle_config_change(self, msg):
- """Handle a MUC configuration change (with status code)."""
- self.xmpp.event('groupchat_config_status', msg)
- self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg)
-
- def handle_groupchat_presence(self, pr):
- """ Handle a presence in a muc.
- """
- got_offline = False
- got_online = False
- if pr['muc']['room'] not in self.rooms.keys():
- return
- entry = pr['muc'].getStanzaValues()
- entry['show'] = pr['show']
- entry['status'] = pr['status']
- entry['alt_nick'] = pr['nick']
- if pr['type'] == 'unavailable':
- if entry['nick'] in self.rooms[entry['room']]:
- del self.rooms[entry['room']][entry['nick']]
- got_offline = True
- else:
- if entry['nick'] not in self.rooms[entry['room']]:
- got_online = True
- self.rooms[entry['room']][entry['nick']] = entry
- log.debug("MUC presence from %s/%s : %s", entry['room'],entry['nick'], entry)
- self.xmpp.event("groupchat_presence", pr)
- self.xmpp.event("muc::%s::presence" % entry['room'], pr)
- if got_offline:
- self.xmpp.event("muc::%s::got_offline" % entry['room'], pr)
- if got_online:
- self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
-
- def handle_groupchat_message(self, msg):
- """ Handle a message event in a muc.
- """
- self.xmpp.event('groupchat_message', msg)
- self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
-
- def handle_groupchat_error_message(self, msg):
- """ Handle a message error event in a muc.
- """
- self.xmpp.event('groupchat_message_error', msg)
- self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg)
-
-
-
- def handle_groupchat_subject(self, msg):
- """ Handle a message coming from a muc indicating
- a change of subject (or announcing it when joining the room)
- """
- self.xmpp.event('groupchat_subject', msg)
-
- def jidInRoom(self, room, jid):
- for nick in self.rooms[room]:
- entry = self.rooms[room][nick]
- if entry is not None and entry['jid'].full == jid:
- return True
- return False
-
- def getNick(self, room, jid):
- for nick in self.rooms[room]:
- entry = self.rooms[room][nick]
- if entry is not None and entry['jid'].full == jid:
- return nick
-
- def configureRoom(self, room, form=None, ifrom=None):
- if form is None:
- form = self.getRoomConfig(room, ifrom=ifrom)
- iq = self.xmpp.makeIqSet()
- iq['to'] = room
- if ifrom is not None:
- iq['from'] = ifrom
- query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
- form = form.getXML('submit')
- query.append(form)
- iq.append(query)
- # For now, swallow errors to preserve existing API
- try:
- result = iq.send()
- except IqError:
- return False
- except IqTimeout:
- return False
- return True
-
- def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
- """ Join the specified room, requesting 'maxhistory' lines of history.
- """
- stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
- x = ET.Element('{http://jabber.org/protocol/muc}x')
- if password:
- passelement = ET.Element('{http://jabber.org/protocol/muc}password')
- passelement.text = password
- x.append(passelement)
- if maxhistory:
- history = ET.Element('{http://jabber.org/protocol/muc}history')
- if maxhistory == "0":
- history.attrib['maxchars'] = maxhistory
- else:
- history.attrib['maxstanzas'] = maxhistory
- x.append(history)
- stanza.append(x)
- if not wait:
- self.xmpp.send(stanza)
- else:
- #wait for our own room presence back
- expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
- self.xmpp.send(stanza, expect)
- self.rooms[room] = {}
- self.ourNicks[room] = nick
-
- def destroy(self, room, reason='', altroom = '', ifrom=None):
- iq = self.xmpp.makeIqSet()
- if ifrom is not None:
- iq['from'] = ifrom
- iq['to'] = room
- query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
- destroy = ET.Element('{http://jabber.org/protocol/muc#owner}destroy')
- if altroom:
- destroy.attrib['jid'] = altroom
- xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason')
- xreason.text = reason
- destroy.append(xreason)
- query.append(destroy)
- iq.append(query)
- # For now, swallow errors to preserve existing API
- try:
- r = iq.send()
- except IqError:
- return False
- except IqTimeout:
- return False
- return True
-
- def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
- """ Change room affiliation."""
- if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
- raise TypeError
- query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
- if nick is not None:
- item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'nick':nick})
- else:
- item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'jid':jid})
- query.append(item)
- iq = self.xmpp.makeIqSet(query)
- iq['to'] = room
- iq['from'] = ifrom
- # For now, swallow errors to preserve existing API
- try:
- result = iq.send()
- except IqError:
- return False
- except IqTimeout:
- return False
- return True
-
- def setRole(self, room, nick, role):
- """ Change role property of a nick in a room.
- Typically, roles are temporary (they last only as long as you are in the
- room), whereas affiliations are permanent (they last across groupchat
- sessions).
- """
- if role not in ('moderator', 'participant', 'visitor', 'none'):
- raise TypeError
- query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
- item = ET.Element('item', {'role':role, 'nick':nick})
- query.append(item)
- iq = self.xmpp.makeIqSet(query)
- iq['to'] = room
- result = iq.send()
- if result is False or result['type'] != 'result':
- raise ValueError
- return True
-
- def invite(self, room, jid, reason='', mfrom=''):
- """ Invite a jid to a room."""
- msg = self.xmpp.makeMessage(room)
- msg['from'] = mfrom
- x = ET.Element('{http://jabber.org/protocol/muc#user}x')
- invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
- if reason:
- rxml = ET.Element('{http://jabber.org/protocol/muc#user}reason')
- rxml.text = reason
- invite.append(rxml)
- x.append(invite)
- msg.append(x)
- self.xmpp.send(msg)
-
- def leaveMUC(self, room, nick, msg='', pfrom=None):
- """ Leave the specified room.
- """
- if msg:
- self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom)
- else:
- self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
- del self.rooms[room]
-
- def getRoomConfig(self, room, ifrom=''):
- iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
- iq['to'] = room
- iq['from'] = ifrom
- # For now, swallow errors to preserve existing API
- try:
- result = iq.send()
- except IqError:
- raise ValueError
- except IqTimeout:
- raise ValueError
- form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
- if form is None:
- raise ValueError
- return self.xmpp.plugin['xep_0004'].buildForm(form)
-
- def cancelConfig(self, room, ifrom=None):
- query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
- x = ET.Element('{jabber:x:data}x', type='cancel')
- query.append(x)
- iq = self.xmpp.makeIqSet(query)
- iq['to'] = room
- iq['from'] = ifrom
- iq.send()
-
- def setRoomConfig(self, room, config, ifrom=''):
- query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
- x = config.getXML('submit')
- query.append(x)
- iq = self.xmpp.makeIqSet(query)
- iq['to'] = room
- iq['from'] = ifrom
- iq.send()
-
- def getJoinedRooms(self):
- return self.rooms.keys()
-
- def getOurJidInRoom(self, roomJid):
- """ Return the jid we're using in a room.
- """
- return "%s/%s" % (roomJid, self.ourNicks[roomJid])
-
- def getJidProperty(self, room, nick, jidProperty):
- """ Get the property of a nick in a room, such as its 'jid' or 'affiliation'
- If not found, return None.
- """
- if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
- return self.rooms[room][nick][jidProperty]
- else:
- return None
-
- def getRoster(self, room):
- """ Get the list of nicks in a room.
- """
- if room not in self.rooms.keys():
- return None
- return self.rooms[room].keys()
-
- def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
- if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
- raise TypeError
- query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
- item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
- query.append(item)
- iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
- iq.append(query)
- return iq.send()
-
-
-xep_0045 = XEP_0045
-register_plugin(XEP_0045)
diff --git a/sleekxmpp/plugins/xep_0047/__init__.py b/sleekxmpp/plugins/xep_0047/__init__.py
deleted file mode 100644
index 5cd7df2e..00000000
--- a/sleekxmpp/plugins/xep_0047/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0047 import stanza
-from sleekxmpp.plugins.xep_0047.stanza import Open, Close, Data
-from sleekxmpp.plugins.xep_0047.stream import IBBytestream
-from sleekxmpp.plugins.xep_0047.ibb import XEP_0047
-
-
-register_plugin(XEP_0047)
-
-
-# Retain some backwards compatibility
-xep_0047 = XEP_0047
diff --git a/sleekxmpp/plugins/xep_0047/ibb.py b/sleekxmpp/plugins/xep_0047/ibb.py
deleted file mode 100644
index 62dddac2..00000000
--- a/sleekxmpp/plugins/xep_0047/ibb.py
+++ /dev/null
@@ -1,215 +0,0 @@
-import uuid
-import logging
-import threading
-
-from sleekxmpp import Message, Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0047 import stanza, Open, Close, Data, IBBytestream
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0047(BasePlugin):
-
- name = 'xep_0047'
- description = 'XEP-0047: In-band Bytestreams'
- dependencies = set(['xep_0030'])
- stanza = stanza
- default_config = {
- 'block_size': 4096,
- 'max_block_size': 8192,
- 'window_size': 1,
- 'auto_accept': False,
- }
-
- def plugin_init(self):
- self._streams = {}
- self._pending_streams = {}
- self._pending_lock = threading.Lock()
- self._stream_lock = threading.Lock()
-
- self._preauthed_sids_lock = threading.Lock()
- self._preauthed_sids = {}
-
- register_stanza_plugin(Iq, Open)
- register_stanza_plugin(Iq, Close)
- register_stanza_plugin(Iq, Data)
- register_stanza_plugin(Message, Data)
-
- self.xmpp.register_handler(Callback(
- 'IBB Open',
- StanzaPath('iq@type=set/ibb_open'),
- self._handle_open_request))
-
- self.xmpp.register_handler(Callback(
- 'IBB Close',
- StanzaPath('iq@type=set/ibb_close'),
- self._handle_close))
-
- self.xmpp.register_handler(Callback(
- 'IBB Data',
- StanzaPath('iq@type=set/ibb_data'),
- self._handle_data))
-
- self.xmpp.register_handler(Callback(
- 'IBB Message Data',
- StanzaPath('message/ibb_data'),
- self._handle_data))
-
- self.api.register(self._authorized, 'authorized', default=True)
- self.api.register(self._authorized_sid, 'authorized_sid', default=True)
- self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
- self.api.register(self._get_stream, 'get_stream', default=True)
- self.api.register(self._set_stream, 'set_stream', default=True)
- self.api.register(self._del_stream, 'del_stream', default=True)
-
- def plugin_end(self):
- self.xmpp.remove_handler('IBB Open')
- self.xmpp.remove_handler('IBB Close')
- self.xmpp.remove_handler('IBB Data')
- self.xmpp.remove_handler('IBB Message Data')
- self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
-
- def _get_stream(self, jid, sid, peer_jid, data):
- return self._streams.get((jid, sid, peer_jid), None)
-
- def _set_stream(self, jid, sid, peer_jid, stream):
- self._streams[(jid, sid, peer_jid)] = stream
-
- def _del_stream(self, jid, sid, peer_jid, data):
- with self._stream_lock:
- if (jid, sid, peer_jid) in self._streams:
- del self._streams[(jid, sid, peer_jid)]
-
- def _accept_stream(self, iq):
- receiver = iq['to']
- sender = iq['from']
- sid = iq['ibb_open']['sid']
-
- if self.api['authorized_sid'](receiver, sid, sender, iq):
- return True
- return self.api['authorized'](receiver, sid, sender, iq)
-
- def _authorized(self, jid, sid, ifrom, iq):
- if self.auto_accept:
- if iq['ibb_open']['block_size'] <= self.max_block_size:
- return True
- return False
-
- def _authorized_sid(self, jid, sid, ifrom, iq):
- with self._preauthed_sids_lock:
- if (jid, sid, ifrom) in self._preauthed_sids:
- del self._preauthed_sids[(jid, sid, ifrom)]
- return True
- return False
-
- def _preauthorize_sid(self, jid, sid, ifrom, data):
- with self._preauthed_sids_lock:
- self._preauthed_sids[(jid, sid, ifrom)] = True
-
- def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
- ifrom=None, block=True, timeout=None, callback=None):
- if sid is None:
- sid = str(uuid.uuid4())
- if block_size is None:
- block_size = self.block_size
-
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- iq['ibb_open']['block_size'] = block_size
- iq['ibb_open']['sid'] = sid
- iq['ibb_open']['stanza'] = 'iq'
-
- stream = IBBytestream(self.xmpp, sid, block_size,
- iq['from'], iq['to'], window,
- use_messages)
-
- with self._stream_lock:
- self._pending_streams[iq['id']] = stream
-
- self._pending_streams[iq['id']] = stream
-
- if block:
- resp = iq.send(timeout=timeout)
- self._handle_opened_stream(resp)
- return stream
- else:
- cb = None
- if callback is not None:
- def chained(resp):
- self._handle_opened_stream(resp)
- callback(resp)
- cb = chained
- else:
- cb = self._handle_opened_stream
- return iq.send(block=block, timeout=timeout, callback=cb)
-
- def _handle_opened_stream(self, iq):
- if iq['type'] == 'result':
- with self._stream_lock:
- stream = self._pending_streams.get(iq['id'], None)
- if stream is not None:
- log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
- stream.self_jid = iq['to']
- stream.peer_jid = iq['from']
- stream.stream_started.set()
- self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
- self.xmpp.event('ibb_stream_start', stream)
- self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
-
- with self._stream_lock:
- if iq['id'] in self._pending_streams:
- del self._pending_streams[iq['id']]
-
- def _handle_open_request(self, iq):
- sid = iq['ibb_open']['sid']
- size = iq['ibb_open']['block_size'] or self.block_size
-
- log.debug('Received IBB stream request from %s', iq['from'])
-
- if not sid:
- raise XMPPError(etype='modify', condition='bad-request')
-
- if not self._accept_stream(iq):
- raise XMPPError(etype='modify', condition='not-acceptable')
-
- if size > self.max_block_size:
- raise XMPPError('resource-constraint')
-
- stream = IBBytestream(self.xmpp, sid, size,
- iq['to'], iq['from'],
- self.window_size)
- stream.stream_started.set()
- self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
- iq.reply()
- iq.send()
-
- self.xmpp.event('ibb_stream_start', stream)
- self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
-
- def _handle_data(self, stanza):
- sid = stanza['ibb_data']['sid']
- stream = self.api['get_stream'](stanza['to'], sid, stanza['from'])
- if stream is not None and stanza['from'] == stream.peer_jid:
- stream._recv_data(stanza)
- else:
- raise XMPPError('item-not-found')
-
- def _handle_close(self, iq):
- sid = iq['ibb_close']['sid']
- stream = self.api['get_stream'](iq['to'], sid, iq['from'])
- if stream is not None and iq['from'] == stream.peer_jid:
- stream._closed(iq)
- self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid)
- else:
- raise XMPPError('item-not-found')
diff --git a/sleekxmpp/plugins/xep_0047/stanza.py b/sleekxmpp/plugins/xep_0047/stanza.py
deleted file mode 100644
index 7e5d2fed..00000000
--- a/sleekxmpp/plugins/xep_0047/stanza.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import re
-import base64
-
-from sleekxmpp.util import bytes
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import ElementBase
-
-
-VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*')
-
-
-def to_b64(data):
- return bytes(base64.b64encode(bytes(data))).decode('utf-8')
-
-
-def from_b64(data):
- return bytes(base64.b64decode(bytes(data)))
-
-
-class Open(ElementBase):
- name = 'open'
- namespace = 'http://jabber.org/protocol/ibb'
- plugin_attrib = 'ibb_open'
- interfaces = set(('block_size', 'sid', 'stanza'))
-
- def get_block_size(self):
- return int(self._get_attr('block-size'))
-
- def set_block_size(self, value):
- self._set_attr('block-size', str(value))
-
- def del_block_size(self):
- self._del_attr('block-size')
-
-
-class Data(ElementBase):
- name = 'data'
- namespace = 'http://jabber.org/protocol/ibb'
- plugin_attrib = 'ibb_data'
- interfaces = set(('seq', 'sid', 'data'))
- sub_interfaces = set(['data'])
-
- def get_seq(self):
- return int(self._get_attr('seq', '0'))
-
- def set_seq(self, value):
- self._set_attr('seq', str(value))
-
- def get_data(self):
- b64_data = self.xml.text.strip()
- if VALID_B64.match(b64_data).group() == b64_data:
- return from_b64(b64_data)
- else:
- raise XMPPError('not-acceptable')
-
- def set_data(self, value):
- self.xml.text = to_b64(value)
-
- def del_data(self):
- self.xml.text = ''
-
-
-class Close(ElementBase):
- name = 'close'
- namespace = 'http://jabber.org/protocol/ibb'
- plugin_attrib = 'ibb_close'
- interfaces = set(['sid'])
diff --git a/sleekxmpp/plugins/xep_0047/stream.py b/sleekxmpp/plugins/xep_0047/stream.py
deleted file mode 100644
index 9651edf8..00000000
--- a/sleekxmpp/plugins/xep_0047/stream.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import socket
-import threading
-import logging
-
-from sleekxmpp.stanza import Iq
-from sleekxmpp.util import Queue
-from sleekxmpp.exceptions import XMPPError
-
-
-log = logging.getLogger(__name__)
-
-
-class IBBytestream(object):
-
- def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False):
- self.xmpp = xmpp
- self.sid = sid
- self.block_size = block_size
- self.window_size = window_size
- self.use_messages = use_messages
-
- if jid is None:
- jid = xmpp.boundjid
- self.self_jid = jid
- self.peer_jid = peer
-
- self.send_seq = -1
- self.recv_seq = -1
-
- self._send_seq_lock = threading.Lock()
- self._recv_seq_lock = threading.Lock()
-
- self.stream_started = threading.Event()
- self.stream_in_closed = threading.Event()
- self.stream_out_closed = threading.Event()
-
- self.recv_queue = Queue()
-
- self.send_window = threading.BoundedSemaphore(value=self.window_size)
- self.window_ids = set()
- self.window_empty = threading.Event()
- self.window_empty.set()
-
- def send(self, data):
- if not self.stream_started.is_set() or \
- self.stream_out_closed.is_set():
- raise socket.error
- data = data[0:self.block_size]
- self.send_window.acquire()
- with self._send_seq_lock:
- self.send_seq = (self.send_seq + 1) % 65535
- seq = self.send_seq
- if self.use_messages:
- msg = self.xmpp.Message()
- msg['to'] = self.peer_jid
- msg['from'] = self.self_jid
- msg['id'] = self.xmpp.new_id()
- msg['ibb_data']['sid'] = self.sid
- msg['ibb_data']['seq'] = seq
- msg['ibb_data']['data'] = data
- msg.send()
- self.send_window.release()
- else:
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = self.peer_jid
- iq['from'] = self.self_jid
- iq['ibb_data']['sid'] = self.sid
- iq['ibb_data']['seq'] = seq
- iq['ibb_data']['data'] = data
- self.window_empty.clear()
- self.window_ids.add(iq['id'])
- iq.send(block=False, callback=self._recv_ack)
- return len(data)
-
- def sendall(self, data):
- sent_len = 0
- while sent_len < len(data):
- sent_len += self.send(data[sent_len:])
-
- def _recv_ack(self, iq):
- self.window_ids.remove(iq['id'])
- if not self.window_ids:
- self.window_empty.set()
- self.send_window.release()
- if iq['type'] == 'error':
- self.close()
-
- def _recv_data(self, stanza):
- with self._recv_seq_lock:
- new_seq = stanza['ibb_data']['seq']
- if new_seq != (self.recv_seq + 1) % 65535:
- self.close()
- raise XMPPError('unexpected-request')
- self.recv_seq = new_seq
-
- data = stanza['ibb_data']['data']
- if len(data) > self.block_size:
- self.close()
- raise XMPPError('not-acceptable')
-
- self.recv_queue.put(data)
- self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
-
- if isinstance(stanza, Iq):
- stanza.reply()
- stanza.send()
-
- def recv(self, *args, **kwargs):
- return self.read(block=True)
-
- def read(self, block=True, timeout=None, **kwargs):
- if not self.stream_started.is_set() or \
- self.stream_in_closed.is_set():
- raise socket.error
- if timeout is not None:
- block = True
- try:
- return self.recv_queue.get(block, timeout)
- except:
- return None
-
- def close(self):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = self.peer_jid
- iq['from'] = self.self_jid
- iq['ibb_close']['sid'] = self.sid
- self.stream_out_closed.set()
- iq.send(block=False,
- callback=lambda x: self.stream_in_closed.set())
- self.xmpp.event('ibb_stream_end', self)
-
- def _closed(self, iq):
- self.stream_in_closed.set()
- self.stream_out_closed.set()
- iq.reply()
- iq.send()
- self.xmpp.event('ibb_stream_end', self)
-
- def makefile(self, *args, **kwargs):
- return self
-
- def connect(*args, **kwargs):
- return None
-
- def shutdown(self, *args, **kwargs):
- return None
diff --git a/sleekxmpp/plugins/xep_0048/__init__.py b/sleekxmpp/plugins/xep_0048/__init__.py
deleted file mode 100644
index 2c98d061..00000000
--- a/sleekxmpp/plugins/xep_0048/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL
-from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048
-
-
-register_plugin(XEP_0048)
diff --git a/sleekxmpp/plugins/xep_0048/bookmarks.py b/sleekxmpp/plugins/xep_0048/bookmarks.py
deleted file mode 100644
index 0bb5ae38..00000000
--- a/sleekxmpp/plugins/xep_0048/bookmarks.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0048(BasePlugin):
-
- name = 'xep_0048'
- description = 'XEP-0048: Bookmarks'
- dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'])
- stanza = stanza
- default_config = {
- 'auto_join': False,
- 'storage_method': 'xep_0049'
- }
-
- def plugin_init(self):
- register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks)
-
- self.xmpp['xep_0049'].register(Bookmarks)
- self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks)
-
- self.xmpp.add_event_handler('session_start', self._autojoin)
-
- def plugin_end(self):
- self.xmpp.del_event_handler('session_start', self._autojoin)
-
- def _autojoin(self, __):
- if not self.auto_join:
- return
-
- try:
- result = self.get_bookmarks(method=self.storage_method)
- except XMPPError:
- return
-
- if self.storage_method == 'xep_0223':
- bookmarks = result['pubsub']['items']['item']['bookmarks']
- else:
- bookmarks = result['private']['bookmarks']
-
- for conf in bookmarks['conferences']:
- if conf['autojoin']:
- log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
- self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
- password=conf['password'])
-
- def set_bookmarks(self, bookmarks, method=None, **iqargs):
- if not method:
- method = self.storage_method
- return self.xmpp[method].store(bookmarks, **iqargs)
-
- def get_bookmarks(self, method=None, **iqargs):
- if not method:
- method = self.storage_method
-
- loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks'
-
- return self.xmpp[method].retrieve(loc, **iqargs)
diff --git a/sleekxmpp/plugins/xep_0048/stanza.py b/sleekxmpp/plugins/xep_0048/stanza.py
deleted file mode 100644
index 21829392..00000000
--- a/sleekxmpp/plugins/xep_0048/stanza.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
-
-
-class Bookmarks(ElementBase):
- name = 'storage'
- namespace = 'storage:bookmarks'
- plugin_attrib = 'bookmarks'
- interfaces = set()
-
- def add_conference(self, jid, nick, name=None, autojoin=None, password=None):
- conf = Conference()
- conf['jid'] = jid
- conf['nick'] = nick
- if name is None:
- name = jid
- conf['name'] = name
- conf['autojoin'] = autojoin
- conf['password'] = password
- self.append(conf)
-
- def add_url(self, url, name=None):
- saved_url = URL()
- saved_url['url'] = url
- if name is None:
- name = url
- saved_url['name'] = name
- self.append(saved_url)
-
-
-class Conference(ElementBase):
- name = 'conference'
- namespace = 'storage:bookmarks'
- plugin_attrib = 'conference'
- plugin_multi_attrib = 'conferences'
- interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name'])
- sub_interfaces = set(['nick', 'password'])
-
- def get_autojoin(self):
- value = self._get_attr('autojoin')
- return value in ('1', 'true')
-
- def set_autojoin(self, value):
- del self['autojoin']
- if value in ('1', 'true', True):
- self._set_attr('autojoin', 'true')
-
-
-class URL(ElementBase):
- name = 'url'
- namespace = 'storage:bookmarks'
- plugin_attrib = 'url'
- plugin_multi_attrib = 'urls'
- interfaces = set(['url', 'name'])
-
-
-register_stanza_plugin(Bookmarks, Conference, iterable=True)
-register_stanza_plugin(Bookmarks, URL, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0049/__init__.py b/sleekxmpp/plugins/xep_0049/__init__.py
deleted file mode 100644
index b0c4f904..00000000
--- a/sleekxmpp/plugins/xep_0049/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0049.stanza import PrivateXML
-from sleekxmpp.plugins.xep_0049.private_storage import XEP_0049
-
-
-register_plugin(XEP_0049)
diff --git a/sleekxmpp/plugins/xep_0049/private_storage.py b/sleekxmpp/plugins/xep_0049/private_storage.py
deleted file mode 100644
index ef6cbdde..00000000
--- a/sleekxmpp/plugins/xep_0049/private_storage.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0049 import stanza, PrivateXML
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0049(BasePlugin):
-
- name = 'xep_0049'
- description = 'XEP-0049: Private XML Storage'
- dependencies = set([])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, PrivateXML)
-
- def register(self, stanza):
- register_stanza_plugin(PrivateXML, stanza, iterable=True)
-
- def store(self, data, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
-
- if not isinstance(data, list):
- data = [data]
-
- for elem in data:
- iq['private'].append(elem)
-
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def retrieve(self, name, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
- iq['private'].enable(name)
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0049/stanza.py b/sleekxmpp/plugins/xep_0049/stanza.py
deleted file mode 100644
index d424e2f0..00000000
--- a/sleekxmpp/plugins/xep_0049/stanza.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET, ElementBase
-
-
-class PrivateXML(ElementBase):
-
- name = 'query'
- namespace = 'jabber:iq:private'
- plugin_attrib = 'private'
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0050/__init__.py b/sleekxmpp/plugins/xep_0050/__init__.py
deleted file mode 100644
index 640b182d..00000000
--- a/sleekxmpp/plugins/xep_0050/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0050.stanza import Command
-from sleekxmpp.plugins.xep_0050.adhoc import XEP_0050
-
-
-register_plugin(XEP_0050)
-
-
-# Retain some backwards compatibility
-xep_0050 = XEP_0050
diff --git a/sleekxmpp/plugins/xep_0050/adhoc.py b/sleekxmpp/plugins/xep_0050/adhoc.py
deleted file mode 100644
index 7ab659f4..00000000
--- a/sleekxmpp/plugins/xep_0050/adhoc.py
+++ /dev/null
@@ -1,701 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import time
-
-from sleekxmpp import Iq
-from sleekxmpp.exceptions import IqError
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0050 import stanza
-from sleekxmpp.plugins.xep_0050 import Command
-from sleekxmpp.plugins.xep_0004 import Form
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0050(BasePlugin):
-
- """
- XEP-0050: Ad-Hoc Commands
-
- XMPP's Adhoc Commands provides a generic workflow mechanism for
- interacting with applications. The result is similar to menu selections
- and multi-step dialogs in normal desktop applications. Clients do not
- need to know in advance what commands are provided by any particular
- application or agent. While adhoc commands provide similar functionality
- to Jabber-RPC, adhoc commands are used primarily for human interaction.
-
- Also see <http://xmpp.org/extensions/xep-0050.html>
-
- Configuration Values:
- threaded -- Indicates if command events should be threaded.
- Defaults to True.
-
- Events:
- command_execute -- Received a command with action="execute"
- command_next -- Received a command with action="next"
- command_complete -- Received a command with action="complete"
- command_cancel -- Received a command with action="cancel"
-
- Attributes:
- threaded -- Indicates if command events should be threaded.
- Defaults to True.
- commands -- A dictionary mapping JID/node pairs to command
- names and handlers.
- sessions -- A dictionary or equivalent backend mapping
- session IDs to dictionaries containing data
- relevant to a command's session.
-
- Methods:
- plugin_init -- Overrides base_plugin.plugin_init
- post_init -- Overrides base_plugin.post_init
- new_session -- Return a new session ID.
- prep_handlers -- Placeholder. May call with a list of handlers
- to prepare them for use with the session storage
- backend, if needed.
- set_backend -- Replace the default session storage with some
- external storage mechanism, such as a database.
- The provided backend wrapper must be able to
- act using the same syntax as a dictionary.
- add_command -- Add a command for use by external entitites.
- get_commands -- Retrieve a list of commands provided by a
- remote agent.
- send_command -- Send a command request to a remote agent.
- start_command -- Command user API: initiate a command session
- continue_command -- Command user API: proceed to the next step
- cancel_command -- Command user API: cancel a command
- complete_command -- Command user API: finish a command
- terminate_command -- Command user API: delete a command's session
- """
-
- name = 'xep_0050'
- description = 'XEP-0050: Ad-Hoc Commands'
- dependencies = set(['xep_0030', 'xep_0004'])
- stanza = stanza
- default_config = {
- 'threaded': True,
- 'session_db': None
- }
-
- def plugin_init(self):
- """Start the XEP-0050 plugin."""
- self.sessions = self.session_db
- if self.sessions is None:
- self.sessions = {}
-
- self.commands = {}
-
- self.xmpp.register_handler(
- Callback("Ad-Hoc Execute",
- StanzaPath('iq@type=set/command'),
- self._handle_command))
-
- register_stanza_plugin(Iq, Command)
- register_stanza_plugin(Command, Form, iterable=True)
-
- self.xmpp.add_event_handler('command_execute',
- self._handle_command_start,
- threaded=self.threaded)
- self.xmpp.add_event_handler('command_next',
- self._handle_command_next,
- threaded=self.threaded)
- self.xmpp.add_event_handler('command_cancel',
- self._handle_command_cancel,
- threaded=self.threaded)
- self.xmpp.add_event_handler('command_complete',
- self._handle_command_complete,
- threaded=self.threaded)
-
- def plugin_end(self):
- self.xmpp.del_event_handler('command_execute',
- self._handle_command_start)
- self.xmpp.del_event_handler('command_next',
- self._handle_command_next)
- self.xmpp.del_event_handler('command_cancel',
- self._handle_command_cancel)
- self.xmpp.del_event_handler('command_complete',
- self._handle_command_complete)
- self.xmpp.remove_handler('Ad-Hoc Execute')
- self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
- self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Command.namespace)
- self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
-
- def set_backend(self, db):
- """
- Replace the default session storage dictionary with
- a generic, external data storage mechanism.
-
- The replacement backend must be able to interact through
- the same syntax and interfaces as a normal dictionary.
-
- Arguments:
- db -- The new session storage mechanism.
- """
- self.sessions = db
-
- def prep_handlers(self, handlers, **kwargs):
- """
- Prepare a list of functions for use by the backend service.
-
- Intended to be replaced by the backend service as needed.
-
- Arguments:
- handlers -- A list of function pointers
- **kwargs -- Any additional parameters required by the backend.
- """
- pass
-
- # =================================================================
- # Server side (command provider) API
-
- def add_command(self, jid=None, node=None, name='', handler=None):
- """
- Make a new command available to external entities.
-
- Access control may be implemented in the provided handler.
-
- Command workflow is done across a sequence of command handlers. The
- first handler is given the initial Iq stanza of the request in order
- to support access control. Subsequent handlers are given only the
- payload items of the command. All handlers will receive the command's
- session data.
-
- Arguments:
- jid -- The JID that will expose the command.
- node -- The node associated with the command.
- name -- A human readable name for the command.
- handler -- A function that will generate the response to the
- initial command request, as well as enforcing any
- access control policies.
- """
- if jid is None:
- jid = self.xmpp.boundjid
- elif not isinstance(jid, JID):
- jid = JID(jid)
- item_jid = jid.full
-
- self.xmpp['xep_0030'].add_identity(category='automation',
- itype='command-list',
- name='Ad-Hoc commands',
- node=Command.namespace,
- jid=jid)
- self.xmpp['xep_0030'].add_item(jid=item_jid,
- name=name,
- node=Command.namespace,
- subnode=node,
- ijid=jid)
- self.xmpp['xep_0030'].add_identity(category='automation',
- itype='command-node',
- name=name,
- node=node,
- jid=jid)
- self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid)
-
- self.commands[(item_jid, node)] = (name, handler)
-
- def new_session(self):
- """Return a new session ID."""
- return str(time.time()) + '-' + self.xmpp.new_id()
-
- def _handle_command(self, iq):
- """Raise command events based on the command action."""
- self.xmpp.event('command_%s' % iq['command']['action'], iq)
-
- def _handle_command_start(self, iq):
- """
- Process an initial request to execute a command.
-
- Arguments:
- iq -- The command execution request.
- """
- sessionid = self.new_session()
- node = iq['command']['node']
- key = (iq['to'].full, node)
- name, handler = self.commands.get(key, ('Not found', None))
- if not handler:
- log.debug('Command not found: %s, %s', key, self.commands)
-
- payload = []
- for stanza in iq['command']['substanzas']:
- payload.append(stanza)
-
- if len(payload) == 1:
- payload = payload[0]
-
- interfaces = set([item.plugin_attrib for item in payload])
- payload_classes = set([item.__class__ for item in payload])
-
- initial_session = {'id': sessionid,
- 'from': iq['from'],
- 'to': iq['to'],
- 'node': node,
- 'payload': payload,
- 'interfaces': interfaces,
- 'payload_classes': payload_classes,
- 'notes': None,
- 'has_next': False,
- 'allow_complete': False,
- 'allow_prev': False,
- 'past': [],
- 'next': None,
- 'prev': None,
- 'cancel': None}
-
- session = handler(iq, initial_session)
-
- self._process_command_response(iq, session)
-
- def _handle_command_next(self, iq):
- """
- Process a request for the next step in the workflow
- for a command with multiple steps.
-
- Arguments:
- iq -- The command continuation request.
- """
- sessionid = iq['command']['sessionid']
- session = self.sessions.get(sessionid)
-
- if session:
- handler = session['next']
- interfaces = session['interfaces']
- results = []
- for stanza in iq['command']['substanzas']:
- if stanza.plugin_attrib in interfaces:
- results.append(stanza)
- if len(results) == 1:
- results = results[0]
-
- session = handler(results, session)
-
- self._process_command_response(iq, session)
- else:
- raise XMPPError('item-not-found')
-
- def _handle_command_prev(self, iq):
- """
- Process a request for the prev step in the workflow
- for a command with multiple steps.
-
- Arguments:
- iq -- The command continuation request.
- """
- sessionid = iq['command']['sessionid']
- session = self.sessions.get(sessionid)
-
- if session:
- handler = session['prev']
- interfaces = session['interfaces']
- results = []
- for stanza in iq['command']['substanzas']:
- if stanza.plugin_attrib in interfaces:
- results.append(stanza)
- if len(results) == 1:
- results = results[0]
-
- session = handler(results, session)
-
- self._process_command_response(iq, session)
- else:
- raise XMPPError('item-not-found')
-
- def _process_command_response(self, iq, session):
- """
- Generate a command reply stanza based on the
- provided session data.
-
- Arguments:
- iq -- The command request stanza.
- session -- A dictionary of relevant session data.
- """
- sessionid = session['id']
-
- payload = session['payload']
- if payload is None:
- payload = []
- if not isinstance(payload, list):
- payload = [payload]
-
- interfaces = session.get('interfaces', set())
- payload_classes = session.get('payload_classes', set())
-
- interfaces.update(set([item.plugin_attrib for item in payload]))
- payload_classes.update(set([item.__class__ for item in payload]))
-
- session['interfaces'] = interfaces
- session['payload_classes'] = payload_classes
-
- self.sessions[sessionid] = session
-
- for item in payload:
- register_stanza_plugin(Command, item.__class__, iterable=True)
-
- iq.reply()
- iq['command']['node'] = session['node']
- iq['command']['sessionid'] = session['id']
-
- if session['next'] is None:
- iq['command']['actions'] = []
- iq['command']['status'] = 'completed'
- elif session['has_next']:
- actions = ['next']
- if session['allow_complete']:
- actions.append('complete')
- if session['allow_prev']:
- actions.append('prev')
- iq['command']['actions'] = actions
- iq['command']['status'] = 'executing'
- else:
- iq['command']['actions'] = ['complete']
- iq['command']['status'] = 'executing'
-
- iq['command']['notes'] = session['notes']
-
- for item in payload:
- iq['command'].append(item)
-
- iq.send()
-
- def _handle_command_cancel(self, iq):
- """
- Process a request to cancel a command's execution.
-
- Arguments:
- iq -- The command cancellation request.
- """
- node = iq['command']['node']
- sessionid = iq['command']['sessionid']
-
- session = self.sessions.get(sessionid)
-
- if session:
- handler = session['cancel']
- if handler:
- handler(iq, session)
- del self.sessions[sessionid]
- iq.reply()
- iq['command']['node'] = node
- iq['command']['sessionid'] = sessionid
- iq['command']['status'] = 'canceled'
- iq['command']['notes'] = session['notes']
- iq.send()
- else:
- raise XMPPError('item-not-found')
-
-
- def _handle_command_complete(self, iq):
- """
- Process a request to finish the execution of command
- and terminate the workflow.
-
- All data related to the command session will be removed.
-
- Arguments:
- iq -- The command completion request.
- """
- node = iq['command']['node']
- sessionid = iq['command']['sessionid']
- session = self.sessions.get(sessionid)
-
- if session:
- handler = session['next']
- interfaces = session['interfaces']
- results = []
- for stanza in iq['command']['substanzas']:
- if stanza.plugin_attrib in interfaces:
- results.append(stanza)
- if len(results) == 1:
- results = results[0]
-
- if handler:
- handler(results, session)
-
- del self.sessions[sessionid]
-
- payload = session['payload']
- if payload is None:
- payload = []
- if not isinstance(payload, list):
- payload = [payload]
-
- for item in payload:
- register_stanza_plugin(Command, item.__class__, iterable=True)
-
- iq.reply()
- iq['command']['node'] = node
- iq['command']['sessionid'] = sessionid
- iq['command']['actions'] = []
- iq['command']['status'] = 'completed'
- iq['command']['notes'] = session['notes']
-
- for item in payload:
- iq['command'].append(item)
-
- iq.send()
- else:
- raise XMPPError('item-not-found')
-
- # =================================================================
- # Client side (command user) API
-
- def get_commands(self, jid, **kwargs):
- """
- Return a list of commands provided by a given JID.
-
- Arguments:
- jid -- The JID to query for commands.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the items.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- iterator -- If True, return a result set iterator using
- the XEP-0059 plugin, if the plugin is loaded.
- Otherwise the parameter is ignored.
- """
- return self.xmpp['xep_0030'].get_items(jid=jid,
- node=Command.namespace,
- **kwargs)
-
- def send_command(self, jid, node, ifrom=None, action='execute',
- payload=None, sessionid=None, flow=False, **kwargs):
- """
- Create and send a command stanza, without using the provided
- workflow management APIs.
-
- Arguments:
- jid -- The JID to send the command request or result.
- node -- The node for the command.
- ifrom -- Specify the sender's JID.
- action -- May be one of: execute, cancel, complete,
- or cancel.
- payload -- Either a list of payload items, or a single
- payload item such as a data form.
- sessionid -- The current session's ID value.
- flow -- If True, process the Iq result using the
- command workflow methods contained in the
- session instead of returning the response
- stanza itself. Defaults to False.
- block -- Specify if the send call will block until a
- response is received, or a timeout occurs.
- Defaults to True.
- timeout -- The length of time (in seconds) to wait for a
- response before exiting the send call
- if blocking is used. Defaults to
- sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler
- function. Will be executed when a reply
- stanza is received if flow=False.
- """
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- iq['command']['node'] = node
- iq['command']['action'] = action
- if sessionid is not None:
- iq['command']['sessionid'] = sessionid
- if payload is not None:
- if not isinstance(payload, list):
- payload = [payload]
- for item in payload:
- iq['command'].append(item)
- if not flow:
- return iq.send(**kwargs)
- else:
- if kwargs.get('block', True):
- try:
- result = iq.send(**kwargs)
- except IqError as err:
- result = err.iq
- self._handle_command_result(result)
- else:
- iq.send(block=False, callback=self._handle_command_result)
-
- def start_command(self, jid, node, session, ifrom=None, block=False):
- """
- Initiate executing a command provided by a remote agent.
-
- The default workflow provided is non-blocking, but a blocking
- version may be used with block=True.
-
- The provided session dictionary should contain:
- next -- A handler for processing the command result.
- error -- A handler for processing any error stanzas
- generated by the request.
-
- Arguments:
- jid -- The JID to send the command request.
- node -- The node for the desired command.
- session -- A dictionary of relevant session data.
- ifrom -- Optionally specify the sender's JID.
- block -- If True, block execution until a result
- is received. Defaults to False.
- """
- session['jid'] = jid
- session['node'] = node
- session['timestamp'] = time.time()
- session['block'] = block
- if 'payload' not in session:
- session['payload'] = None
-
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- session['from'] = ifrom
- iq['command']['node'] = node
- iq['command']['action'] = 'execute'
- if session['payload'] is not None:
- payload = session['payload']
- if not isinstance(payload, list):
- payload = list(payload)
- for stanza in payload:
- iq['command'].append(stanza)
- sessionid = 'client:pending_' + iq['id']
- session['id'] = sessionid
- self.sessions[sessionid] = session
- if session['block']:
- try:
- result = iq.send(block=True)
- except IqError as err:
- result = err.iq
- self._handle_command_result(result)
- else:
- iq.send(block=False, callback=self._handle_command_result)
-
- def continue_command(self, session, direction='next'):
- """
- Execute the next action of the command.
-
- Arguments:
- session -- All stored data relevant to the current
- command session.
- """
- sessionid = 'client:' + session['id']
- self.sessions[sessionid] = session
-
- self.send_command(session['jid'],
- session['node'],
- ifrom=session.get('from', None),
- action=direction,
- payload=session.get('payload', None),
- sessionid=session['id'],
- flow=True,
- block=session['block'])
-
- def cancel_command(self, session):
- """
- Cancel the execution of a command.
-
- Arguments:
- session -- All stored data relevant to the current
- command session.
- """
- sessionid = 'client:' + session['id']
- self.sessions[sessionid] = session
-
- self.send_command(session['jid'],
- session['node'],
- ifrom=session.get('from', None),
- action='cancel',
- payload=session.get('payload', None),
- sessionid=session['id'],
- flow=True,
- block=session['block'])
-
- def complete_command(self, session):
- """
- Finish the execution of a command workflow.
-
- Arguments:
- session -- All stored data relevant to the current
- command session.
- """
- sessionid = 'client:' + session['id']
- self.sessions[sessionid] = session
-
- self.send_command(session['jid'],
- session['node'],
- ifrom=session.get('from', None),
- action='complete',
- payload=session.get('payload', None),
- sessionid=session['id'],
- flow=True,
- block=session['block'])
-
- def terminate_command(self, session):
- """
- Delete a command's session after a command has completed
- or an error has occured.
-
- Arguments:
- session -- All stored data relevant to the current
- command session.
- """
- sessionid = 'client:' + session['id']
- try:
- del self.sessions[sessionid]
- except Exception as e:
- log.error("Error deleting adhoc command session: %s" % e.message)
-
- def _handle_command_result(self, iq):
- """
- Process the results of a command request.
-
- Will execute the 'next' handler stored in the session
- data, or the 'error' handler depending on the Iq's type.
-
- Arguments:
- iq -- The command response.
- """
- sessionid = 'client:' + iq['command']['sessionid']
- pending = False
-
- if sessionid not in self.sessions:
- pending = True
- pendingid = 'client:pending_' + iq['id']
- if pendingid not in self.sessions:
- return
- sessionid = pendingid
-
- session = self.sessions[sessionid]
- sessionid = 'client:' + iq['command']['sessionid']
- session['id'] = iq['command']['sessionid']
-
- self.sessions[sessionid] = session
-
- if pending:
- del self.sessions[pendingid]
-
- handler_type = 'next'
- if iq['type'] == 'error':
- handler_type = 'error'
- handler = session.get(handler_type, None)
- if handler:
- handler(iq, session)
- elif iq['type'] == 'error':
- self.terminate_command(session)
-
- if iq['command']['status'] == 'completed':
- self.terminate_command(session)
diff --git a/sleekxmpp/plugins/xep_0050/stanza.py b/sleekxmpp/plugins/xep_0050/stanza.py
deleted file mode 100644
index 2367c77b..00000000
--- a/sleekxmpp/plugins/xep_0050/stanza.py
+++ /dev/null
@@ -1,185 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Command(ElementBase):
-
- """
- XMPP's Adhoc Commands provides a generic workflow mechanism for
- interacting with applications. The result is similar to menu selections
- and multi-step dialogs in normal desktop applications. Clients do not
- need to know in advance what commands are provided by any particular
- application or agent. While adhoc commands provide similar functionality
- to Jabber-RPC, adhoc commands are used primarily for human interaction.
-
- Also see <http://xmpp.org/extensions/xep-0050.html>
-
- Example command stanzas:
- <iq type="set">
- <command xmlns="http://jabber.org/protocol/commands"
- node="run_foo"
- action="execute" />
- </iq>
-
- <iq type="result">
- <command xmlns="http://jabber.org/protocol/commands"
- node="run_foo"
- sessionid="12345"
- status="executing">
- <actions>
- <complete />
- </actions>
- <note type="info">Information!</note>
- <x xmlns="jabber:x:data">
- <field var="greeting"
- type="text-single"
- label="Greeting" />
- </x>
- </command>
- </iq>
-
- Stanza Interface:
- action -- The action to perform.
- actions -- The set of allowable next actions.
- node -- The node associated with the command.
- notes -- A list of tuples for informative notes.
- sessionid -- A unique identifier for a command session.
- status -- May be one of: canceled, completed, or executing.
-
- Attributes:
- actions -- A set of allowed action values.
- statuses -- A set of allowed status values.
- next_actions -- A set of allowed next action names.
-
- Methods:
- get_action -- Return the requested action.
- get_actions -- Return the allowable next actions.
- set_actions -- Set the allowable next actions.
- del_actions -- Remove the current set of next actions.
- get_notes -- Return a list of informative note data.
- set_notes -- Set informative notes.
- del_notes -- Remove any note data.
- add_note -- Add a single note.
- """
-
- name = 'command'
- namespace = 'http://jabber.org/protocol/commands'
- plugin_attrib = 'command'
- interfaces = set(('action', 'sessionid', 'node',
- 'status', 'actions', 'notes'))
- actions = set(('cancel', 'complete', 'execute', 'next', 'prev'))
- statuses = set(('canceled', 'completed', 'executing'))
- next_actions = set(('prev', 'next', 'complete'))
-
- def get_action(self):
- """
- Return the value of the action attribute.
-
- If the Iq stanza's type is "set" then use a default
- value of "execute".
- """
- if self.parent()['type'] == 'set':
- return self._get_attr('action', default='execute')
- return self._get_attr('action')
-
- def set_actions(self, values):
- """
- Assign the set of allowable next actions.
-
- Arguments:
- values -- A list containing any combination of:
- 'prev', 'next', and 'complete'
- """
- self.del_actions()
- if values:
- self._set_sub_text('{%s}actions' % self.namespace, '', True)
- actions = self.find('{%s}actions' % self.namespace)
- for val in values:
- if val in self.next_actions:
- action = ET.Element('{%s}%s' % (self.namespace, val))
- actions.append(action)
-
- def get_actions(self):
- """
- Return the set of allowable next actions.
- """
- actions = set()
- actions_xml = self.find('{%s}actions' % self.namespace)
- if actions_xml is not None:
- for action in self.next_actions:
- action_xml = actions_xml.find('{%s}%s' % (self.namespace,
- action))
- if action_xml is not None:
- actions.add(action)
- return actions
-
- def del_actions(self):
- """
- Remove all allowable next actions.
- """
- self._del_sub('{%s}actions' % self.namespace)
-
- def get_notes(self):
- """
- Return a list of note information.
-
- Example:
- [('info', 'Some informative data'),
- ('warning', 'Use caution'),
- ('error', 'The command ran, but had errors')]
- """
- notes = []
- notes_xml = self.findall('{%s}note' % self.namespace)
- for note in notes_xml:
- notes.append((note.attrib.get('type', 'info'),
- note.text))
- return notes
-
- def set_notes(self, notes):
- """
- Add multiple notes to the command result.
-
- Each note is a tuple, with the first item being one of:
- 'info', 'warning', or 'error', and the second item being
- any human readable message.
-
- Example:
- [('info', 'Some informative data'),
- ('warning', 'Use caution'),
- ('error', 'The command ran, but had errors')]
-
-
- Arguments:
- notes -- A list of tuples of note information.
- """
- self.del_notes()
- for note in notes:
- self.add_note(note[1], note[0])
-
- def del_notes(self):
- """
- Remove all notes associated with the command result.
- """
- notes_xml = self.findall('{%s}note' % self.namespace)
- for note in notes_xml:
- self.xml.remove(note)
-
- def add_note(self, msg='', ntype='info'):
- """
- Add a single note annotation to the command.
-
- Arguments:
- msg -- A human readable message.
- ntype -- One of: 'info', 'warning', 'error'
- """
- xml = ET.Element('{%s}note' % self.namespace)
- xml.attrib['type'] = ntype
- xml.text = msg
- self.xml.append(xml)
diff --git a/sleekxmpp/plugins/xep_0054/__init__.py b/sleekxmpp/plugins/xep_0054/__init__.py
deleted file mode 100644
index d460cc8a..00000000
--- a/sleekxmpp/plugins/xep_0054/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0054.stanza import VCardTemp
-from sleekxmpp.plugins.xep_0054.vcard_temp import XEP_0054
-
-
-register_plugin(XEP_0054)
diff --git a/sleekxmpp/plugins/xep_0054/stanza.py b/sleekxmpp/plugins/xep_0054/stanza.py
deleted file mode 100644
index 2d017d6e..00000000
--- a/sleekxmpp/plugins/xep_0054/stanza.py
+++ /dev/null
@@ -1,562 +0,0 @@
-import base64
-import datetime as dt
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID
-from sleekxmpp.plugins import xep_0082
-
-
-class VCardTemp(ElementBase):
- name = 'vCard'
- namespace = 'vcard-temp'
- plugin_attrib = 'vcard_temp'
- interfaces = set(['FN', 'VERSION'])
- sub_interfaces = set(['FN', 'VERSION'])
-
-
-class Name(ElementBase):
- name = 'N'
- namespace = 'vcard-temp'
- plugin_attrib = name
- interfaces = set(['FAMILY', 'GIVEN', 'MIDDLE', 'PREFIX', 'SUFFIX'])
- sub_interfaces = interfaces
-
- def _set_component(self, name, value):
- if isinstance(value, list):
- value = ','.join(value)
- if value is not None:
- self._set_sub_text(name, value, keep=True)
- else:
- self._del_sub(name)
-
- def _get_component(self, name):
- value = self._get_sub_text(name, '')
- if ',' in value:
- value = [v.strip() for v in value.split(',')]
- return value
-
- def set_family(self, value):
- self._set_component('FAMILY', value)
-
- def get_family(self):
- return self._get_component('FAMILY')
-
- def set_given(self, value):
- self._set_component('GIVEN', value)
-
- def get_given(self):
- return self._get_component('GIVEN')
-
- def set_middle(self, value):
- print(value)
- self._set_component('MIDDLE', value)
-
- def get_middle(self):
- return self._get_component('MIDDLE')
-
- def set_prefix(self, value):
- self._set_component('PREFIX', value)
-
- def get_prefix(self):
- return self._get_component('PREFIX')
-
- def set_suffix(self, value):
- self._set_component('SUFFIX', value)
-
- def get_suffix(self):
- return self._get_component('SUFFIX')
-
-
-class Nickname(ElementBase):
- name = 'NICKNAME'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'nicknames'
- interfaces = set([name])
- is_extension = True
-
- def set_nickname(self, value):
- if not value:
- self.xml.text = ''
- return
-
- if not isinstance(value, list):
- value = [value]
-
- self.xml.text = ','.join(value)
-
- def get_nickname(self):
- if self.xml.text:
- return self.xml.text.split(',')
-
-
-class Email(ElementBase):
- name = 'EMAIL'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'emails'
- interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
- sub_interfaces = set(['USERID'])
- bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
-
-
-class Address(ElementBase):
- name = 'ADR'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'addresses'
- interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
- 'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
- 'REGION', 'PCODE', 'CTRY'])
- sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
- 'REGION', 'PCODE', 'CTRY'])
- bool_interfaces = set(['HOME', 'WORK', 'DOM', 'INTL', 'PREF'])
-
-
-class Telephone(ElementBase):
- name = 'TEL'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'telephone_numbers'
- interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
- 'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
- 'PREF', 'NUMBER'])
- sub_interfaces = set(['NUMBER'])
- bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
- 'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
- 'ISDN', 'PCS', 'PREF'])
-
- def setup(self, xml=None):
- super(Telephone, self).setup(xml=xml)
- ## this blanks out numbers received from server
- ##self._set_sub_text('NUMBER', '', keep=True)
-
- def set_number(self, value):
- self._set_sub_text('NUMBER', value, keep=True)
-
- def del_number(self):
- self._set_sub_text('NUMBER', '', keep=True)
-
-
-class Label(ElementBase):
- name = 'LABEL'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'labels'
- interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
- 'PREF', 'lines'])
- bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
- 'INT', 'PREF'])
-
- def add_line(self, value):
- line = ET.Element('{%s}LINE' % self.namespace)
- line.text = value
- self.xml.append(line)
-
- def get_lines(self):
- lines = self.xml.find('{%s}LINE' % self.namespace)
- if lines is None:
- return []
- return [line.text for line in lines]
-
- def set_lines(self, values):
- self.del_lines()
- for line in values:
- self.add_line(line)
-
- def del_lines(self):
- lines = self.xml.find('{%s}LINE' % self.namespace)
- if lines is None:
- return
- for line in lines:
- self.xml.remove(line)
-
-
-class Geo(ElementBase):
- name = 'GEO'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'geolocations'
- interfaces = set(['LAT', 'LON'])
- sub_interfaces = interfaces
-
-
-class Org(ElementBase):
- name = 'ORG'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'organizations'
- interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
- sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
-
- def add_orgunit(self, value):
- orgunit = ET.Element('{%s}ORGUNIT' % self.namespace)
- orgunit.text = value
- self.xml.append(orgunit)
-
- def get_orgunits(self):
- orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace)
- if orgunits is None:
- return []
- return [orgunit.text for orgunit in orgunits]
-
- def set_orgunits(self, values):
- self.del_orgunits()
- for orgunit in values:
- self.add_orgunit(orgunit)
-
- def del_orgunits(self):
- orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace)
- if orgunits is None:
- return
- for orgunit in orgunits:
- self.xml.remove(orgunit)
-
-
-class Photo(ElementBase):
- name = 'PHOTO'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'photos'
- interfaces = set(['TYPE', 'EXTVAL'])
- sub_interfaces = interfaces
-
-
-class Logo(ElementBase):
- name = 'LOGO'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'logos'
- interfaces = set(['TYPE', 'EXTVAL'])
- sub_interfaces = interfaces
-
-
-class Sound(ElementBase):
- name = 'SOUND'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'sounds'
- interfaces = set(['PHONETC', 'EXTVAL'])
- sub_interfaces = interfaces
-
-
-class BinVal(ElementBase):
- name = 'BINVAL'
- namespace = 'vcard-temp'
- plugin_attrib = name
- interfaces = set(['BINVAL'])
- is_extension = True
-
- def setup(self, xml=None):
- self.xml = ET.Element('')
- return True
-
- def set_binval(self, value):
- self.del_binval()
- parent = self.parent()
- if value:
- xml = ET.Element('{%s}BINVAL' % self.namespace)
- xml.text = bytes(base64.b64encode(value)).decode('utf-8')
- parent.append(xml)
-
- def get_binval(self):
- parent = self.parent()
- xml = parent.find('{%s}BINVAL' % self.namespace)
- if xml is not None:
- return base64.b64decode(bytes(xml.text))
- return b''
-
- def del_binval(self):
- self.parent()._del_sub('{%s}BINVAL' % self.namespace)
-
-
-class Classification(ElementBase):
- name = 'CLASS'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'classifications'
- interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
- bool_interfaces = interfaces
-
-
-class Categories(ElementBase):
- name = 'CATEGORIES'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'categories'
- interfaces = set([name])
- is_extension = True
-
- def set_categories(self, values):
- self.del_categories()
- for keyword in values:
- item = ET.Element('{%s}KEYWORD' % self.namespace)
- item.text = keyword
- self.xml.append(item)
-
- def get_categories(self):
- items = self.xml.findall('{%s}KEYWORD' % self.namespace)
- if items is None:
- return []
- keywords = []
- for item in items:
- keywords.append(item.text)
- return keywords
-
- def del_categories(self):
- items = self.xml.findall('{%s}KEYWORD' % self.namespace)
- for item in items:
- self.xml.remove(item)
-
-
-class Birthday(ElementBase):
- name = 'BDAY'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'birthdays'
- interfaces = set([name])
- is_extension = True
-
- def set_bday(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self.xml.text = value
-
- def get_bday(self):
- if not self.xml.text:
- return None
- return xep_0082.parse(self.xml.text)
-
-
-class Rev(ElementBase):
- name = 'REV'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'revision_dates'
- interfaces = set([name])
- is_extension = True
-
- def set_rev(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self.xml.text = value
-
- def get_rev(self):
- if not self.xml.text:
- return None
- return xep_0082.parse(self.xml.text)
-
-
-class Title(ElementBase):
- name = 'TITLE'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'titles'
- interfaces = set([name])
- is_extension = True
-
- def set_title(self, value):
- self.xml.text = value
-
- def get_title(self):
- return self.xml.text
-
-
-class Role(ElementBase):
- name = 'ROLE'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'roles'
- interfaces = set([name])
- is_extension = True
-
- def set_role(self, value):
- self.xml.text = value
-
- def get_role(self):
- return self.xml.text
-
-
-class Note(ElementBase):
- name = 'NOTE'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'notes'
- interfaces = set([name])
- is_extension = True
-
- def set_note(self, value):
- self.xml.text = value
-
- def get_note(self):
- return self.xml.text
-
-
-class Desc(ElementBase):
- name = 'DESC'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'descriptions'
- interfaces = set([name])
- is_extension = True
-
- def set_desc(self, value):
- self.xml.text = value
-
- def get_desc(self):
- return self.xml.text
-
-
-class URL(ElementBase):
- name = 'URL'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'urls'
- interfaces = set([name])
- is_extension = True
-
- def set_url(self, value):
- self.xml.text = value
-
- def get_url(self):
- return self.xml.text
-
-
-class UID(ElementBase):
- name = 'UID'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'uids'
- interfaces = set([name])
- is_extension = True
-
- def set_uid(self, value):
- self.xml.text = value
-
- def get_uid(self):
- return self.xml.text
-
-
-class ProdID(ElementBase):
- name = 'PRODID'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'product_ids'
- interfaces = set([name])
- is_extension = True
-
- def set_prodid(self, value):
- self.xml.text = value
-
- def get_prodid(self):
- return self.xml.text
-
-
-class Mailer(ElementBase):
- name = 'MAILER'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'mailers'
- interfaces = set([name])
- is_extension = True
-
- def set_mailer(self, value):
- self.xml.text = value
-
- def get_mailer(self):
- return self.xml.text
-
-
-class SortString(ElementBase):
- name = 'SORT-STRING'
- namespace = 'vcard-temp'
- plugin_attrib = 'SORT_STRING'
- plugin_multi_attrib = 'sort_strings'
- interfaces = set([name])
- is_extension = True
-
- def set_sort_string(self, value):
- self.xml.text = value
-
- def get_sort_string(self):
- return self.xml.text
-
-
-class Agent(ElementBase):
- name = 'AGENT'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'agents'
- interfaces = set(['EXTVAL'])
- sub_interfaces = interfaces
-
-
-class JabberID(ElementBase):
- name = 'JABBERID'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'jids'
- interfaces = set([name])
- is_extension = True
-
- def set_jabberid(self, value):
- self.xml.text = JID(value).bare
-
- def get_jabberid(self):
- return JID(self.xml.text)
-
-
-class TimeZone(ElementBase):
- name = 'TZ'
- namespace = 'vcard-temp'
- plugin_attrib = name
- plugin_multi_attrib = 'timezones'
- interfaces = set([name])
- is_extension = True
-
- def set_tz(self, value):
- time = xep_0082.time(offset=value)
- if time[-1] == 'Z':
- self.xml.text = 'Z'
- else:
- self.xml.text = time[-6:]
-
- def get_tz(self):
- if not self.xml.text:
- return xep_0082.tzutc()
- time = xep_0082.parse('00:00:00%s' % self.xml.text)
- return time.tzinfo
-
-
-register_stanza_plugin(VCardTemp, Name)
-register_stanza_plugin(VCardTemp, Address, iterable=True)
-register_stanza_plugin(VCardTemp, Agent, iterable=True)
-register_stanza_plugin(VCardTemp, Birthday, iterable=True)
-register_stanza_plugin(VCardTemp, Categories, iterable=True)
-register_stanza_plugin(VCardTemp, Desc, iterable=True)
-register_stanza_plugin(VCardTemp, Email, iterable=True)
-register_stanza_plugin(VCardTemp, Geo, iterable=True)
-register_stanza_plugin(VCardTemp, JabberID, iterable=True)
-register_stanza_plugin(VCardTemp, Label, iterable=True)
-register_stanza_plugin(VCardTemp, Logo, iterable=True)
-register_stanza_plugin(VCardTemp, Mailer, iterable=True)
-register_stanza_plugin(VCardTemp, Note, iterable=True)
-register_stanza_plugin(VCardTemp, Nickname, iterable=True)
-register_stanza_plugin(VCardTemp, Org, iterable=True)
-register_stanza_plugin(VCardTemp, Photo, iterable=True)
-register_stanza_plugin(VCardTemp, ProdID, iterable=True)
-register_stanza_plugin(VCardTemp, Rev, iterable=True)
-register_stanza_plugin(VCardTemp, Role, iterable=True)
-register_stanza_plugin(VCardTemp, SortString, iterable=True)
-register_stanza_plugin(VCardTemp, Sound, iterable=True)
-register_stanza_plugin(VCardTemp, Telephone, iterable=True)
-register_stanza_plugin(VCardTemp, Title, iterable=True)
-register_stanza_plugin(VCardTemp, TimeZone, iterable=True)
-register_stanza_plugin(VCardTemp, UID, iterable=True)
-register_stanza_plugin(VCardTemp, URL, iterable=True)
-
-register_stanza_plugin(Photo, BinVal)
-register_stanza_plugin(Logo, BinVal)
-register_stanza_plugin(Sound, BinVal)
-
-register_stanza_plugin(Agent, VCardTemp)
diff --git a/sleekxmpp/plugins/xep_0054/vcard_temp.py b/sleekxmpp/plugins/xep_0054/vcard_temp.py
deleted file mode 100644
index 97da8c7c..00000000
--- a/sleekxmpp/plugins/xep_0054/vcard_temp.py
+++ /dev/null
@@ -1,146 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import JID, Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0054 import VCardTemp, stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0054(BasePlugin):
-
- """
- XEP-0054: vcard-temp
- """
-
- name = 'xep_0054'
- description = 'XEP-0054: vcard-temp'
- dependencies = set(['xep_0030', 'xep_0082'])
- stanza = stanza
-
- def plugin_init(self):
- """
- Start the XEP-0054 plugin.
- """
- register_stanza_plugin(Iq, VCardTemp)
-
-
- self.api.register(self._set_vcard, 'set_vcard', default=True)
- self.api.register(self._get_vcard, 'get_vcard', default=True)
- self.api.register(self._del_vcard, 'del_vcard', default=True)
-
- self._vcard_cache = {}
-
- self.xmpp.register_handler(
- Callback('VCardTemp',
- StanzaPath('iq/vcard_temp'),
- self._handle_get_vcard))
-
- def plugin_end(self):
- self.xmpp.remove_handler('VCardTemp')
- self.xmpp['xep_0030'].del_feature(feature='vcard-temp')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('vcard-temp')
-
- def make_vcard(self):
- return VCardTemp()
-
- def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
- block=True, callback=None, timeout=None):
- if local is None:
- if jid is not None and not isinstance(jid, JID):
- jid = JID(jid)
- if self.xmpp.is_component:
- if jid.domain == self.xmpp.boundjid.domain:
- local = True
- else:
- if str(jid) == str(self.xmpp.boundjid):
- local = True
- jid = jid.full
- elif jid in (None, ''):
- local = True
-
- if local:
- vcard = self.api['get_vcard'](jid, None, ifrom)
- if not isinstance(vcard, Iq):
- iq = self.xmpp.Iq()
- if vcard is None:
- vcard = VCardTemp()
- iq.append(vcard)
- return iq
- return vcard
-
- if cached:
- vcard = self.api['get_vcard'](jid, None, ifrom)
- if vcard is not None:
- if not isinstance(vcard, Iq):
- iq = self.xmpp.Iq()
- iq.append(vcard)
- return iq
- return vcard
-
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
- iq.enable('vcard_temp')
-
- vcard = iq.send(block=block, callback=callback, timeout=timeout)
-
- if block:
- self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
- return vcard
-
- def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
- callback=None, timeout=None):
- self.api['set_vcard'](jid, None, ifrom, vcard)
- if self.xmpp.is_component:
- return
-
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'set'
- iq.append(vcard)
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def _handle_get_vcard(self, iq):
- if iq['type'] == 'result':
- self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
- return
- elif iq['type'] == 'get':
- vcard = self.api['get_vcard'](iq['from'].bare)
- if isinstance(vcard, Iq):
- vcard.send()
- else:
- iq.reply()
- iq.append(vcard)
- iq.send()
- elif iq['type'] == 'set':
- raise XMPPError('service-unavailable')
-
- # =================================================================
-
- def _set_vcard(self, jid, node, ifrom, vcard):
- self._vcard_cache[jid.bare] = vcard
-
- def _get_vcard(self, jid, node, ifrom, vcard):
- return self._vcard_cache.get(jid.bare, None)
-
- def _del_vcard(self, jid, node, ifrom, vcard):
- if jid.bare in self._vcard_cache:
- del self._vcard_cache[jid.bare]
diff --git a/sleekxmpp/plugins/xep_0059/__init__.py b/sleekxmpp/plugins/xep_0059/__init__.py
deleted file mode 100644
index 3464ce32..00000000
--- a/sleekxmpp/plugins/xep_0059/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0059.stanza import Set
-from sleekxmpp.plugins.xep_0059.rsm import ResultIterator, XEP_0059
-
-
-register_plugin(XEP_0059)
-
-# Retain some backwards compatibility
-xep_0059 = XEP_0059
diff --git a/sleekxmpp/plugins/xep_0059/rsm.py b/sleekxmpp/plugins/xep_0059/rsm.py
deleted file mode 100644
index d73b45bc..00000000
--- a/sleekxmpp/plugins/xep_0059/rsm.py
+++ /dev/null
@@ -1,145 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0059 import stanza, Set
-from sleekxmpp.exceptions import XMPPError
-
-
-log = logging.getLogger(__name__)
-
-
-class ResultIterator():
-
- """
- An iterator for Result Set Managment
- """
-
- def __init__(self, query, interface, results='substanzas', amount=10,
- start=None, reverse=False):
- """
- Arguments:
- query -- The template query
- interface -- The substanza of the query, for example disco_items
- results -- The query stanza's interface which provides a
- countable list of query results.
- amount -- The max amounts of items to request per iteration
- start -- From which item id to start
- reverse -- If True, page backwards through the results
-
- Example:
- q = Iq()
- q['to'] = 'pubsub.example.com'
- q['disco_items']['node'] = 'blog'
- for i in ResultIterator(q, 'disco_items', '10'):
- print i['disco_items']['items']
-
- """
- self.query = query
- self.amount = amount
- self.start = start
- self.interface = interface
- self.results = results
- self.reverse = reverse
- self._stop = False
-
- def __iter__(self):
- return self
-
- def __next__(self):
- return self.next()
-
- def next(self):
- """
- Return the next page of results from a query.
-
- Note: If using backwards paging, then the next page of
- results will be the items before the current page
- of items.
- """
- if self._stop:
- raise StopIteration
- self.query[self.interface]['rsm']['before'] = self.reverse
- self.query['id'] = self.query.stream.new_id()
- self.query[self.interface]['rsm']['max'] = str(self.amount)
-
- if self.start and self.reverse:
- self.query[self.interface]['rsm']['before'] = self.start
- elif self.start:
- self.query[self.interface]['rsm']['after'] = self.start
-
- try:
- r = self.query.send(block=True)
-
- if not r[self.interface]['rsm']['first'] and \
- not r[self.interface]['rsm']['last']:
- raise StopIteration
-
- if r[self.interface]['rsm']['count'] and \
- r[self.interface]['rsm']['first_index']:
- count = int(r[self.interface]['rsm']['count'])
- first = int(r[self.interface]['rsm']['first_index'])
- num_items = len(r[self.interface][self.results])
- if first + num_items == count:
- self._stop = True
-
- if self.reverse:
- self.start = r[self.interface]['rsm']['first']
- else:
- self.start = r[self.interface]['rsm']['last']
-
- return r
- except XMPPError:
- raise StopIteration
-
-
-class XEP_0059(BasePlugin):
-
- """
- XEP-0050: Result Set Management
- """
-
- name = 'xep_0059'
- description = 'XEP-0059: Result Set Management'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- """
- Start the XEP-0059 plugin.
- """
- register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems,
- self.stanza.Set)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Set.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Set.namespace)
-
- def iterate(self, stanza, interface, results='substanzas'):
- """
- Create a new result set iterator for a given stanza query.
-
- Arguments:
- stanza -- A stanza object to serve as a template for
- queries made each iteration. For example, a
- basic disco#items query.
- interface -- The name of the substanza to which the
- result set management stanza should be
- appended. For example, for disco#items queries
- the interface 'disco_items' should be used.
- results -- The name of the interface containing the
- query results (typically just 'substanzas').
- """
- return ResultIterator(stanza, interface, results)
diff --git a/sleekxmpp/plugins/xep_0059/stanza.py b/sleekxmpp/plugins/xep_0059/stanza.py
deleted file mode 100644
index 48f5c8a0..00000000
--- a/sleekxmpp/plugins/xep_0059/stanza.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems
-
-
-class Set(ElementBase):
-
- """
- XEP-0059 (Result Set Managment) can be used to manage the
- results of queries. For example, limiting the number of items
- per response or starting at certain positions.
-
- Example set stanzas:
- <iq type="get">
- <query xmlns="http://jabber.org/protocol/disco#items">
- <set xmlns="http://jabber.org/protocol/rsm">
- <max>2</max>
- </set>
- </query>
- </iq>
-
- <iq type="result">
- <query xmlns="http://jabber.org/protocol/disco#items">
- <item jid="conference.example.com" />
- <item jid="pubsub.example.com" />
- <set xmlns="http://jabber.org/protocol/rsm">
- <first>conference.example.com</first>
- <last>pubsub.example.com</last>
- </set>
- </query>
- </iq>
-
- Stanza Interface:
- first_index -- The index attribute of <first>
- after -- The id defining from which item to start
- before -- The id defining from which item to
- start when browsing backwards
- max -- Max amount per response
- first -- Id for the first item in the response
- last -- Id for the last item in the response
- index -- Used to set an index to start from
- count -- The number of remote items available
-
- Methods:
- set_first_index -- Sets the index attribute for <first> and
- creates the element if it doesn't exist
- get_first_index -- Returns the value of the index
- attribute for <first>
- del_first_index -- Removes the index attribute for <first>
- but keeps the element
- set_before -- Sets the value of <before>, if the value is True
- then the element will be created without a value
- get_before -- Returns the value of <before>, if it is
- empty it will return True
-
- """
- namespace = 'http://jabber.org/protocol/rsm'
- name = 'set'
- plugin_attrib = 'rsm'
- sub_interfaces = set(('first', 'after', 'before', 'count',
- 'index', 'last', 'max'))
- interfaces = set(('first_index', 'first', 'after', 'before',
- 'count', 'index', 'last', 'max'))
-
- def set_first_index(self, val):
- fi = self.find("{%s}first" % (self.namespace))
- if fi is not None:
- if val:
- fi.attrib['index'] = val
- elif 'index' in fi.attrib:
- del fi.attrib['index']
- elif val:
- fi = ET.Element("{%s}first" % (self.namespace))
- fi.attrib['index'] = val
- self.xml.append(fi)
-
- def get_first_index(self):
- fi = self.find("{%s}first" % (self.namespace))
- if fi is not None:
- return fi.attrib.get('index', '')
-
- def del_first_index(self):
- fi = self.xml.find("{%s}first" % (self.namespace))
- if fi is not None:
- del fi.attrib['index']
-
- def set_before(self, val):
- b = self.xml.find("{%s}before" % (self.namespace))
- if b is None and val is True:
- self._set_sub_text('{%s}before' % self.namespace, '', True)
- else:
- self._set_sub_text('{%s}before' % self.namespace, val)
-
- def get_before(self):
- b = self.xml.find("{%s}before" % (self.namespace))
- if b is not None and not b.text:
- return True
- elif b is not None:
- return b.text
- else:
- return None
diff --git a/sleekxmpp/plugins/xep_0060/__init__.py b/sleekxmpp/plugins/xep_0060/__init__.py
deleted file mode 100644
index 86e2f472..00000000
--- a/sleekxmpp/plugins/xep_0060/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0060.pubsub import XEP_0060
-from sleekxmpp.plugins.xep_0060 import stanza
-
-
-register_plugin(XEP_0060)
-
-
-# Retain some backwards compatibility
-xep_0060 = XEP_0060
diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py
deleted file mode 100644
index bec5f565..00000000
--- a/sleekxmpp/plugins/xep_0060/pubsub.py
+++ /dev/null
@@ -1,577 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0060 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0060(BasePlugin):
-
- """
- XEP-0060 Publish Subscribe
- """
-
- name = 'xep_0060'
- description = 'XEP-0060: Publish-Subscribe'
- dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131'])
- stanza = stanza
-
- def plugin_init(self):
- self.node_event_map = {}
-
- self.xmpp.register_handler(
- Callback('Pubsub Event: Items',
- StanzaPath('message/pubsub_event/items'),
- self._handle_event_items))
- self.xmpp.register_handler(
- Callback('Pubsub Event: Purge',
- StanzaPath('message/pubsub_event/purge'),
- self._handle_event_purge))
- self.xmpp.register_handler(
- Callback('Pubsub Event: Delete',
- StanzaPath('message/pubsub_event/delete'),
- self._handle_event_delete))
- self.xmpp.register_handler(
- Callback('Pubsub Event: Configuration',
- StanzaPath('message/pubsub_event/configuration'),
- self._handle_event_configuration))
- self.xmpp.register_handler(
- Callback('Pubsub Event: Subscription',
- StanzaPath('message/pubsub_event/subscription'),
- self._handle_event_subscription))
-
- self.xmpp['xep_0131'].supported_headers.add('SubID')
-
- def plugin_end(self):
- self.xmpp.remove_handler('Pubsub Event: Items')
- self.xmpp.remove_handler('Pubsub Event: Purge')
- self.xmpp.remove_handler('Pubsub Event: Delete')
- self.xmpp.remove_handler('Pubsub Event: Configuration')
- self.xmpp.remove_handler('Pubsub Event: Subscription')
-
- def _handle_event_items(self, msg):
- """Raise events for publish and retraction notifications."""
- node = msg['pubsub_event']['items']['node']
-
- multi = len(msg['pubsub_event']['items']) > 1
- values = {}
- if multi:
- values = msg.values
- del values['pubsub_event']
-
- for item in msg['pubsub_event']['items']:
- event_name = self.node_event_map.get(node, None)
- event_type = 'publish'
- if item.name == 'retract':
- event_type = 'retract'
-
- if multi:
- condensed = self.xmpp.Message()
- condensed.values = values
- condensed['pubsub_event']['items']['node'] = node
- condensed['pubsub_event']['items'].append(item)
- self.xmpp.event('pubsub_%s' % event_type, msg)
- if event_name:
- self.xmpp.event('%s_%s' % (event_name, event_type),
- condensed)
- else:
- self.xmpp.event('pubsub_%s' % event_type, msg)
- if event_name:
- self.xmpp.event('%s_%s' % (event_name, event_type), msg)
-
- def _handle_event_purge(self, msg):
- """Raise events for node purge notifications."""
- node = msg['pubsub_event']['purge']['node']
- event_name = self.node_event_map.get(node, None)
-
- self.xmpp.event('pubsub_purge', msg)
- if event_name:
- self.xmpp.event('%s_purge' % event_name, msg)
-
- def _handle_event_delete(self, msg):
- """Raise events for node deletion notifications."""
- node = msg['pubsub_event']['delete']['node']
- event_name = self.node_event_map.get(node, None)
-
- self.xmpp.event('pubsub_delete', msg)
- if event_name:
- self.xmpp.event('%s_delete' % event_name, msg)
-
- def _handle_event_configuration(self, msg):
- """Raise events for node configuration notifications."""
- node = msg['pubsub_event']['configuration']['node']
- event_name = self.node_event_map.get(node, None)
-
- self.xmpp.event('pubsub_config', msg)
- if event_name:
- self.xmpp.event('%s_config' % event_name, msg)
-
- def _handle_event_subscription(self, msg):
- """Raise events for node subscription notifications."""
- node = msg['pubsub_event']['subscription']['node']
- event_name = self.node_event_map.get(node, None)
-
- self.xmpp.event('pubsub_subscription', msg)
- if event_name:
- self.xmpp.event('%s_subscription' % event_name, msg)
-
- def map_node_event(self, node, event_name):
- """
- Map node names to events.
-
- When a pubsub event is received for the given node,
- raise the provided event.
-
- For example::
-
- map_node_event('http://jabber.org/protocol/tune',
- 'user_tune')
-
- will produce the events 'user_tune_publish' and 'user_tune_retract'
- when the respective notifications are received from the node
- 'http://jabber.org/protocol/tune', among other events.
-
- Arguments:
- node -- The node name to map to an event.
- event_name -- The name of the event to raise when a
- notification from the given node is received.
- """
- self.node_event_map[node] = event_name
-
- def create_node(self, jid, node, config=None, ntype=None, ifrom=None,
- block=True, callback=None, timeout=None):
- """
- Create and configure a new pubsub node.
-
- A server MAY use a different name for the node than the one provided,
- so be sure to check the result stanza for a server assigned name.
-
- If no configuration form is provided, the node will be created using
- the server's default configuration. To get the default configuration
- use get_node_config().
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- Optional name of the node to create. If no name is
- provided, the server MAY generate a node ID for you.
- The server can also assign a different name than the
- one you provide; check the result stanza to see if
- the server assigned a name.
- config -- Optional XEP-0004 data form of configuration settings.
- ntype -- The type of node to create. Servers typically default
- to using 'leaf' if no type is provided.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub']['create']['node'] = node
-
- if config is not None:
- form_type = 'http://jabber.org/protocol/pubsub#node_config'
- if 'FORM_TYPE' in config['fields']:
- config.field['FORM_TYPE']['value'] = form_type
- else:
- config.add_field(var='FORM_TYPE',
- ftype='hidden',
- value=form_type)
- if ntype:
- if 'pubsub#node_type' in config['fields']:
- config.field['pubsub#node_type']['value'] = ntype
- else:
- config.add_field(var='pubsub#node_type', value=ntype)
- iq['pubsub']['configure'].append(config)
-
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def subscribe(self, jid, node, bare=True, subscribee=None, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Subscribe to updates from a pubsub node.
-
- The rules for determining the JID that is subscribing to the node are:
- 1. If subscribee is given, use that as provided.
- 2. If ifrom was given, use the bare or full version based on bare.
- 3. Otherwise, use self.xmpp.boundjid based on bare.
-
- Arguments:
- jid -- The pubsub service JID.
- node -- The node to subscribe to.
- bare -- Indicates if the subscribee is a bare or full JID.
- Defaults to True for a bare JID.
- subscribee -- The JID that is subscribing to the node.
- options --
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a
- response before exiting the send call if blocking
- is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub']['subscribe']['node'] = node
-
- if subscribee is None:
- if ifrom:
- if bare:
- subscribee = JID(ifrom).bare
- else:
- subscribee = ifrom
- else:
- if bare:
- subscribee = self.xmpp.boundjid.bare
- else:
- subscribee = self.xmpp.boundjid
-
- iq['pubsub']['subscribe']['jid'] = subscribee
- if options is not None:
- iq['pubsub']['options'].append(options)
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Unubscribe from updates from a pubsub node.
-
- The rules for determining the JID that is unsubscribing
- from the node are:
- 1. If subscribee is given, use that as provided.
- 2. If ifrom was given, use the bare or full version based on bare.
- 3. Otherwise, use self.xmpp.boundjid based on bare.
-
- Arguments:
- jid -- The pubsub service JID.
- node -- The node to subscribe to.
- subid -- The specific subscription, if multiple subscriptions
- exist for this JID/node combination.
- bare -- Indicates if the subscribee is a bare or full JID.
- Defaults to True for a bare JID.
- subscribee -- The JID that is subscribing to the node.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a
- response before exiting the send call if blocking
- is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub']['unsubscribe']['node'] = node
-
- if subscribee is None:
- if ifrom:
- if bare:
- subscribee = JID(ifrom).bare
- else:
- subscribee = ifrom
- else:
- if bare:
- subscribee = self.xmpp.boundjid.bare
- else:
- subscribee = self.xmpp.boundjid
-
- iq['pubsub']['unsubscribe']['jid'] = subscribee
- iq['pubsub']['unsubscribe']['subid'] = subid
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_subscriptions(self, jid, node=None, ifrom=None, block=True,
- callback=None, timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub']['subscriptions']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_affiliations(self, jid, node=None, ifrom=None, block=True,
- callback=None, timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub']['affiliations']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_subscription_options(self, jid, node=None, user_jid=None,
- ifrom=None, block=True, callback=None,
- timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- if user_jid is None:
- iq['pubsub']['default']['node'] = node
- else:
- iq['pubsub']['options']['node'] = node
- iq['pubsub']['options']['jid'] = user_jid
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def set_subscription_options(self, jid, node, user_jid, options,
- ifrom=None, block=True, callback=None,
- timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub']['options']['node'] = node
- iq['pubsub']['options']['jid'] = user_jid
- iq['pubsub']['options'].append(options)
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_node_config(self, jid, node=None, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Retrieve the configuration for a node, or the pubsub service's
- default configuration for new nodes.
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- The node to retrieve the configuration for. If None,
- the default configuration for new nodes will be
- requested. Defaults to None.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- if node is None:
- iq['pubsub_owner']['default']
- else:
- iq['pubsub_owner']['configure']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_node_subscriptions(self, jid, node, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Retrieve the subscriptions associated with a given node.
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- The node to retrieve subscriptions from.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub_owner']['subscriptions']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_node_affiliations(self, jid, node, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Retrieve the affiliations associated with a given node.
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- The node to retrieve affiliations from.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub_owner']['affiliations']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def delete_node(self, jid, node, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Delete a a pubsub node.
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- The node to delete.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub_owner']['delete']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def set_node_config(self, jid, node, config, ifrom=None, block=True,
- callback=None, timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub_owner']['configure']['node'] = node
- iq['pubsub_owner']['configure'].append(config)
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def publish(self, jid, node, id=None, payload=None, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Add a new item to a node, or edit an existing item.
-
- For services that support it, you can use the publish command
- as an event signal by not including an ID or payload.
-
- When including a payload and you do not provide an ID then
- the service will generally create an ID for you.
-
- Publish options may be specified, and how those options
- are processed is left to the service, such as treating
- the options as preconditions that the node's settings
- must match.
-
- Arguments:
- jid -- The JID of the pubsub service.
- node -- The node to publish the item to.
- id -- Optionally specify the ID of the item.
- payload -- The item content to publish.
- options -- A form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub']['publish']['node'] = node
- if id is not None:
- iq['pubsub']['publish']['item']['id'] = id
- if payload is not None:
- iq['pubsub']['publish']['item']['payload'] = payload
- iq['pubsub']['publish_options'] = options
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def retract(self, jid, node, id, notify=None, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Delete a single item from a node.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
-
- iq['pubsub']['retract']['node'] = node
- iq['pubsub']['retract']['notify'] = notify
- iq['pubsub']['retract']['item']['id'] = id
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def purge(self, jid, node, ifrom=None, block=True, callback=None,
- timeout=None):
- """
- Remove all items from a node.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub_owner']['purge']['node'] = node
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_nodes(self, *args, **kwargs):
- """
- Discover the nodes provided by a Pubsub service, using disco.
- """
- return self.xmpp['xep_0030'].get_items(*args, **kwargs)
-
- def get_item(self, jid, node, item_id, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Retrieve the content of an individual item.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- item = stanza.Item()
- item['id'] = item_id
- iq['pubsub']['items']['node'] = node
- iq['pubsub']['items'].append(item)
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_items(self, jid, node, item_ids=None, max_items=None,
- iterator=False, ifrom=None, block=False,
- callback=None, timeout=None):
- """
- Request the contents of a node's items.
-
- The desired items can be specified, or a query for the last
- few published items can be used.
-
- Pubsub services may use result set management for nodes with
- many items, so an iterator can be returned if needed.
- """
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
- iq['pubsub']['items']['node'] = node
- iq['pubsub']['items']['max_items'] = max_items
-
- if item_ids is not None:
- for item_id in item_ids:
- item = stanza.Item()
- item['id'] = item_id
- iq['pubsub']['items'].append(item)
-
- if iterator:
- return self.xmpp['xep_0059'].iterate(iq, 'pubsub')
- else:
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def get_item_ids(self, jid, node, ifrom=None, block=True,
- callback=None, timeout=None, iterator=False):
- """
- Retrieve the ItemIDs hosted by a given node, using disco.
- """
- return self.xmpp['xep_0030'].get_items(jid, node,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout,
- iterator=iterator)
-
- def modify_affiliations(self, jid, node, affiliations=None, ifrom=None,
- block=True, callback=None, timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub_owner']['affiliations']['node'] = node
-
- if affiliations is None:
- affiliations = []
-
- for jid, affiliation in affiliations:
- aff = stanza.OwnerAffiliation()
- aff['jid'] = jid
- aff['affiliation'] = affiliation
- iq['pubsub_owner']['affiliations'].append(aff)
-
- return iq.send(block=block, callback=callback, timeout=timeout)
-
- def modify_subscriptions(self, jid, node, subscriptions=None, ifrom=None,
- block=True, callback=None, timeout=None):
- iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
- iq['pubsub_owner']['subscriptions']['node'] = node
-
- if subscriptions is None:
- subscriptions = []
-
- for jid, subscription in subscriptions:
- sub = stanza.OwnerSubscription()
- sub['jid'] = jid
- sub['subscription'] = subscription
- iq['pubsub_owner']['subscriptions'].append(sub)
-
- return iq.send(block=block, callback=callback, timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/__init__.py b/sleekxmpp/plugins/xep_0060/stanza/__init__.py
deleted file mode 100644
index 37f52f0e..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0060.stanza.pubsub import *
-from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import *
-from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import *
-from sleekxmpp.plugins.xep_0060.stanza.pubsub_errors import *
diff --git a/sleekxmpp/plugins/xep_0060/stanza/base.py b/sleekxmpp/plugins/xep_0060/stanza/base.py
deleted file mode 100644
index d0b7851e..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/base.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET
-
-
-class OptionalSetting(object):
-
- interfaces = set(('required',))
-
- def set_required(self, value):
- if value in (True, 'true', 'True', '1'):
- self.xml.append(ET.Element("{%s}required" % self.namespace))
- elif self['required']:
- self.del_required()
-
- def get_required(self):
- required = self.xml.find("{%s}required" % self.namespace)
- return required is not None
-
- def del_required(self):
- required = self.xml.find("{%s}required" % self.namespace)
- if required is not None:
- self.xml.remove(required)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
deleted file mode 100644
index c1907a13..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
+++ /dev/null
@@ -1,272 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
-from sleekxmpp.plugins import xep_0004
-from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
-
-
-class Pubsub(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'pubsub'
- plugin_attrib = name
- interfaces = set(tuple())
-
-
-class Affiliations(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'affiliations'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class Affiliation(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'affiliation'
- plugin_attrib = name
- interfaces = set(('node', 'affiliation', 'jid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class Subscription(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscription'
- plugin_attrib = name
- interfaces = set(('jid', 'node', 'subscription', 'subid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class Subscriptions(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscriptions'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class SubscribeOptions(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscribe-options'
- plugin_attrib = 'suboptions'
- interfaces = set(('required',))
-
-
-class Item(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'item'
- plugin_attrib = name
- interfaces = set(('id', 'payload'))
-
- def set_payload(self, value):
- del self['payload']
- if isinstance(value, ElementBase):
- if value.tag_name() in self.plugin_tag_map:
- self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
- self.xml.append(value.xml)
- else:
- self.xml.append(value)
-
- def get_payload(self):
- childs = list(self.xml)
- if len(childs) > 0:
- return childs[0]
-
- def del_payload(self):
- for child in self.xml:
- self.xml.remove(child)
-
-
-class Items(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'items'
- plugin_attrib = name
- interfaces = set(('node', 'max_items'))
-
- def set_max_items(self, value):
- self._set_attr('max_items', str(value))
-
-
-class Create(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'create'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class Default(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'default'
- plugin_attrib = name
- interfaces = set(('node', 'type'))
-
- def get_type(self):
- t = self._get_attr('type')
- if not t:
- return 'leaf'
- return t
-
-
-class Publish(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'publish'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class Retract(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'retract'
- plugin_attrib = name
- interfaces = set(('node', 'notify'))
-
- def get_notify(self):
- notify = self._get_attr('notify')
- if notify in ('0', 'false'):
- return False
- elif notify in ('1', 'true'):
- return True
- return None
-
- def set_notify(self, value):
- del self['notify']
- if value is None:
- return
- elif value in (True, '1', 'true', 'True'):
- self._set_attr('notify', 'true')
- else:
- self._set_attr('notify', 'false')
-
-
-class Unsubscribe(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'unsubscribe'
- plugin_attrib = name
- interfaces = set(('node', 'jid', 'subid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class Subscribe(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscribe'
- plugin_attrib = name
- interfaces = set(('node', 'jid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class Configure(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'configure'
- plugin_attrib = name
- interfaces = set(('node', 'type'))
-
- def getType(self):
- t = self._get_attr('type')
- if not t:
- t == 'leaf'
- return t
-
-
-class Options(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'options'
- plugin_attrib = name
- interfaces = set(('jid', 'node', 'options'))
-
- def __init__(self, *args, **kwargs):
- ElementBase.__init__(self, *args, **kwargs)
-
- def get_options(self):
- config = self.xml.find('{jabber:x:data}x')
- form = xep_0004.Form(xml=config)
- return form
-
- def set_options(self, value):
- self.xml.append(value.getXML())
- return self
-
- def del_options(self):
- config = self.xml.find('{jabber:x:data}x')
- self.xml.remove(config)
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class PublishOptions(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'publish-options'
- plugin_attrib = 'publish_options'
- interfaces = set(('publish_options',))
- is_extension = True
-
- def get_publish_options(self):
- config = self.xml.find('{jabber:x:data}x')
- if config is None:
- return None
- form = xep_0004.Form(xml=config)
- return form
-
- def set_publish_options(self, value):
- if value is None:
- self.del_publish_options()
- else:
- self.xml.append(value.getXML())
- return self
-
- def del_publish_options(self):
- config = self.xml.find('{jabber:x:data}x')
- if config is not None:
- self.xml.remove(config)
- self.parent().xml.remove(self.xml)
-
-
-register_stanza_plugin(Iq, Pubsub)
-register_stanza_plugin(Pubsub, Affiliations)
-register_stanza_plugin(Pubsub, Configure)
-register_stanza_plugin(Pubsub, Create)
-register_stanza_plugin(Pubsub, Default)
-register_stanza_plugin(Pubsub, Items)
-register_stanza_plugin(Pubsub, Options)
-register_stanza_plugin(Pubsub, Publish)
-register_stanza_plugin(Pubsub, PublishOptions)
-register_stanza_plugin(Pubsub, Retract)
-register_stanza_plugin(Pubsub, Subscribe)
-register_stanza_plugin(Pubsub, Subscription)
-register_stanza_plugin(Pubsub, Subscriptions)
-register_stanza_plugin(Pubsub, Unsubscribe)
-register_stanza_plugin(Affiliations, Affiliation, iterable=True)
-register_stanza_plugin(Configure, xep_0004.Form)
-register_stanza_plugin(Items, Item, iterable=True)
-register_stanza_plugin(Publish, Item, iterable=True)
-register_stanza_plugin(Retract, Item)
-register_stanza_plugin(Subscribe, Options)
-register_stanza_plugin(Subscription, SubscribeOptions)
-register_stanza_plugin(Subscriptions, Subscription, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
deleted file mode 100644
index 59cf1a50..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class PubsubErrorCondition(ElementBase):
-
- plugin_attrib = 'pubsub'
- interfaces = set(('condition', 'unsupported'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
- conditions = set(('closed-node', 'configuration-required', 'invalid-jid',
- 'invalid-options', 'invalid-payload', 'invalid-subid',
- 'item-forbidden', 'item-required', 'jid-required',
- 'max-items-exceeded', 'max-nodes-exceeded',
- 'nodeid-required', 'not-in-roster-group',
- 'not-subscribed', 'payload-too-big',
- 'payload-required', 'pending-subscription',
- 'presence-subscription-required', 'subid-required',
- 'too-many-subscriptions', 'unsupported'))
- condition_ns = 'http://jabber.org/protocol/pubsub#errors'
-
- def setup(self, xml):
- """Don't create XML for the plugin."""
- self.xml = ET.Element('')
-
- def get_condition(self):
- """Return the condition element's name."""
- for child in self.parent().xml:
- if "{%s}" % self.condition_ns in child.tag:
- cond = child.tag.split('}', 1)[-1]
- if cond in self.conditions:
- return cond
- return ''
-
- def set_condition(self, value):
- """
- Set the tag name of the condition element.
-
- Arguments:
- value -- The tag name of the condition element.
- """
- if value in self.conditions:
- del self['condition']
- cond = ET.Element("{%s}%s" % (self.condition_ns, value))
- self.parent().xml.append(cond)
- return self
-
- def del_condition(self):
- """Remove the condition element."""
- for child in self.parent().xml:
- if "{%s}" % self.condition_ns in child.tag:
- tag = child.tag.split('}', 1)[-1]
- if tag in self.conditions:
- self.parent().xml.remove(child)
- return self
-
- def get_unsupported(self):
- """Return the name of an unsupported feature"""
- xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
- if xml is not None:
- return xml.attrib.get('feature', '')
- return ''
-
- def set_unsupported(self, value):
- """Mark a feature as unsupported"""
- self.del_unsupported()
- xml = ET.Element('{%s}unsupported' % self.condition_ns)
- xml.attrib['feature'] = value
- self.parent().xml.append(xml)
-
- def del_unsupported(self):
- """Delete an unsupported feature condition."""
- xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
- if xml is not None:
- self.parent().xml.remove(xml)
-
-
-register_stanza_plugin(Error, PubsubErrorCondition)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py
deleted file mode 100644
index 32f217fa..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py
+++ /dev/null
@@ -1,151 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp import Message
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
-from sleekxmpp.plugins.xep_0004 import Form
-from sleekxmpp.plugins import xep_0082
-
-
-class Event(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'event'
- plugin_attrib = 'pubsub_event'
- interfaces = set()
-
-
-class EventItem(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'item'
- plugin_attrib = name
- interfaces = set(('id', 'payload', 'node', 'publisher'))
-
- def set_payload(self, value):
- self.xml.append(value)
-
- def get_payload(self):
- childs = list(self.xml)
- if len(childs) > 0:
- return childs[0]
-
- def del_payload(self):
- for child in self.xml:
- self.xml.remove(child)
-
-
-class EventRetract(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'retract'
- plugin_attrib = name
- interfaces = set(('id',))
-
-
-class EventItems(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'items'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventCollection(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'collection'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventAssociate(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'associate'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventDisassociate(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'disassociate'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventConfiguration(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'configuration'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventPurge(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'purge'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class EventDelete(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'delete'
- plugin_attrib = name
- interfaces = set(('node', 'redirect'))
-
- def set_redirect(self, uri):
- del self['redirect']
- redirect = ET.Element('{%s}redirect' % self.namespace)
- redirect.attrib['uri'] = uri
- self.xml.append(redirect)
-
- def get_redirect(self):
- redirect = self.xml.find('{%s}redirect' % self.namespace)
- if redirect is not None:
- return redirect.attrib.get('uri', '')
- return ''
-
- def del_redirect(self):
- redirect = self.xml.find('{%s}redirect' % self.namespace)
- if redirect is not None:
- self.xml.remove(redirect)
-
-
-class EventSubscription(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#event'
- name = 'subscription'
- plugin_attrib = name
- interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription'))
-
- def get_expiry(self):
- expiry = self._get_attr('expiry')
- if expiry.lower() == 'presence':
- return expiry
- return xep_0082.parse(expiry)
-
- def set_expiry(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_attr('expiry', value)
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-register_stanza_plugin(Message, Event)
-register_stanza_plugin(Event, EventCollection)
-register_stanza_plugin(Event, EventConfiguration)
-register_stanza_plugin(Event, EventPurge)
-register_stanza_plugin(Event, EventDelete)
-register_stanza_plugin(Event, EventItems)
-register_stanza_plugin(Event, EventSubscription)
-register_stanza_plugin(EventCollection, EventAssociate)
-register_stanza_plugin(EventCollection, EventDisassociate)
-register_stanza_plugin(EventConfiguration, Form)
-register_stanza_plugin(EventItems, EventItem, iterable=True)
-register_stanza_plugin(EventItems, EventRetract, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
deleted file mode 100644
index d975a46d..00000000
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
-from sleekxmpp.plugins.xep_0004 import Form
-from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
-from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation
-from sleekxmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions
-
-
-class PubsubOwner(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'pubsub'
- plugin_attrib = 'pubsub_owner'
- interfaces = set(tuple())
-
-
-class DefaultConfig(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'default'
- plugin_attrib = name
- interfaces = set(('node', 'config'))
-
- def __init__(self, *args, **kwargs):
- ElementBase.__init__(self, *args, **kwargs)
-
- def get_config(self):
- return self['form']
-
- def set_config(self, value):
- del self['from']
- self.append(value)
- return self
-
-
-class OwnerAffiliations(Affiliations):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node',))
-
- def append(self, affiliation):
- if not isinstance(affiliation, OwnerAffiliation):
- raise TypeError
- self.xml.append(affiliation.xml)
-
-
-class OwnerAffiliation(Affiliation):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('affiliation', 'jid'))
-
-
-class OwnerConfigure(Configure):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'configure'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class OwnerDefault(OwnerConfigure):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node',))
-
-
-class OwnerDelete(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'delete'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class OwnerPurge(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'purge'
- plugin_attrib = name
- interfaces = set(('node',))
-
-
-class OwnerRedirect(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'redirect'
- plugin_attrib = name
- interfaces = set(('node', 'jid'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class OwnerSubscriptions(Subscriptions):
- name = 'subscriptions'
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- plugin_attrib = name
- interfaces = set(('node',))
-
- def append(self, subscription):
- if not isinstance(subscription, OwnerSubscription):
- raise TypeError
- self.xml.append(subscription.xml)
-
-
-class OwnerSubscription(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'subscription'
- plugin_attrib = name
- interfaces = set(('jid', 'subscription'))
-
- def set_jid(self, value):
- self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-register_stanza_plugin(Iq, PubsubOwner)
-register_stanza_plugin(PubsubOwner, DefaultConfig)
-register_stanza_plugin(PubsubOwner, OwnerAffiliations)
-register_stanza_plugin(PubsubOwner, OwnerConfigure)
-register_stanza_plugin(PubsubOwner, OwnerDefault)
-register_stanza_plugin(PubsubOwner, OwnerDelete)
-register_stanza_plugin(PubsubOwner, OwnerPurge)
-register_stanza_plugin(PubsubOwner, OwnerSubscriptions)
-register_stanza_plugin(DefaultConfig, Form)
-register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True)
-register_stanza_plugin(OwnerConfigure, Form)
-register_stanza_plugin(OwnerDefault, Form)
-register_stanza_plugin(OwnerDelete, OwnerRedirect)
-register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py
deleted file mode 100644
index feca2ef1..00000000
--- a/sleekxmpp/plugins/xep_0065/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0065.stanza import Socks5
-from sleekxmpp.plugins.xep_0065.proxy import XEP_0065
-
-
-register_plugin(XEP_0065)
diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py
deleted file mode 100644
index d890b57a..00000000
--- a/sleekxmpp/plugins/xep_0065/proxy.py
+++ /dev/null
@@ -1,292 +0,0 @@
-import logging
-import threading
-import socket
-
-from hashlib import sha1
-from uuid import uuid4
-
-from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
-
-from sleekxmpp.stanza import Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.base import base_plugin
-
-from sleekxmpp.plugins.xep_0065 import stanza, Socks5
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0065(base_plugin):
-
- name = 'xep_0065'
- description = "Socks5 Bytestreams"
- dependencies = set(['xep_0030'])
- default_config = {
- 'auto_accept': False
- }
-
- def plugin_init(self):
- register_stanza_plugin(Iq, Socks5)
-
- self._proxies = {}
- self._sessions = {}
- self._sessions_lock = threading.Lock()
-
- self._preauthed_sids_lock = threading.Lock()
- self._preauthed_sids = {}
-
- self.xmpp.register_handler(
- Callback('Socks5 Bytestreams',
- StanzaPath('iq@type=set/socks/streamhost'),
- self._handle_streamhost))
-
- self.api.register(self._authorized, 'authorized', default=True)
- self.api.register(self._authorized_sid, 'authorized_sid', default=True)
- self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Socks5.namespace)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Socks5 Bytestreams')
- self.xmpp.remove_handler('Socks5 Streamhost Used')
- self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace)
-
- def get_socket(self, sid):
- """Returns the socket associated to the SID."""
- return self._sessions.get(sid, None)
-
- def handshake(self, to, ifrom=None, sid=None, timeout=None):
- """ Starts the handshake to establish the socks5 bytestreams
- connection.
- """
- if not self._proxies:
- self._proxies = self.discover_proxies()
-
- if sid is None:
- sid = uuid4().hex
-
- used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
- proxy = used['socks']['streamhost_used']['jid']
-
- if proxy not in self._proxies:
- log.warning('Received unknown SOCKS5 proxy: %s', proxy)
- return
-
- with self._sessions_lock:
- self._sessions[sid] = self._connect_proxy(
- sid,
- self.xmpp.boundjid,
- to,
- self._proxies[proxy][0],
- self._proxies[proxy][1],
- peer=to)
-
- # Request that the proxy activate the session with the target.
- self.activate(proxy, sid, to, timeout=timeout)
- socket = self.get_socket(sid)
- self.xmpp.event('stream:%s:%s' % (sid, to), socket)
- return socket
-
- def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
- if sid is None:
- sid = uuid4().hex
-
- # Requester initiates S5B negotiation with Target by sending
- # IQ-set that includes the JabberID and network address of
- # StreamHost as well as the StreamID (SID) of the proposed
- # bytestream.
- iq = self.xmpp.Iq()
- iq['to'] = to
- iq['from'] = ifrom
- iq['type'] = 'set'
- iq['socks']['sid'] = sid
- for proxy, (host, port) in self._proxies.items():
- iq['socks'].add_streamhost(proxy, host, port)
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def discover_proxies(self, jid=None, ifrom=None, timeout=None):
- """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
- if jid is None:
- if self.xmpp.is_component:
- jid = self.xmpp.server
- else:
- jid = self.xmpp.boundjid.server
-
- discovered = set()
-
- disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
-
- for item in disco_items['disco_items']['items']:
- try:
- disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
- except XMPPError:
- continue
- else:
- # Verify that the identity is a bytestream proxy.
- identities = disco_info['disco_info']['identities']
- for identity in identities:
- if identity[0] == 'proxy' and identity[1] == 'bytestreams':
- discovered.add(disco_info['from'])
-
- for jid in discovered:
- try:
- addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
- self._proxies[jid] = (addr['socks']['streamhost']['host'],
- addr['socks']['streamhost']['port'])
- except XMPPError:
- continue
-
- return self._proxies
-
- def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None):
- """Get the network information of a proxy."""
- iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
- iq.enable('socks')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def _handle_streamhost(self, iq):
- """Handle incoming SOCKS5 session request."""
- sid = iq['socks']['sid']
- if not sid:
- raise XMPPError(etype='modify', condition='bad-request')
-
- if not self._accept_stream(iq):
- raise XMPPError(etype='modify', condition='not-acceptable')
-
- streamhosts = iq['socks']['streamhosts']
- conn = None
- used_streamhost = None
-
- sender = iq['from']
- for streamhost in streamhosts:
- try:
- conn = self._connect_proxy(sid,
- sender,
- self.xmpp.boundjid,
- streamhost['host'],
- streamhost['port'],
- peer=sender)
- used_streamhost = streamhost['jid']
- break
- except socket.error:
- continue
- else:
- raise XMPPError(etype='cancel', condition='item-not-found')
-
- iq.reply()
- with self._sessions_lock:
- self._sessions[sid] = conn
- iq['socks']['sid'] = sid
- iq['socks']['streamhost_used']['jid'] = used_streamhost
- iq.send()
- self.xmpp.event('socks5_stream', conn)
- self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
-
- def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
- """Activate the socks5 session that has been negotiated."""
- iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
- iq['socks']['sid'] = sid
- iq['socks']['activate'] = target
- iq.send(block=block, timeout=timeout, callback=callback)
-
- def deactivate(self, sid):
- """Closes the proxy socket associated with this SID."""
- sock = self._sessions.get(sid)
- if sock:
- try:
- # sock.close() will also delete sid from self._sessions (see _connect_proxy)
- sock.close()
- except socket.error:
- pass
- # Though this should not be neccessary remove the closed session anyway
- with self._sessions_lock:
- if sid in self._sessions:
- log.warn(('SOCKS5 session with sid = "%s" was not ' +
- 'removed from _sessions by sock.close()') % sid)
- del self._sessions[sid]
-
- def close(self):
- """Closes all proxy sockets."""
- for sid, sock in self._sessions.items():
- sock.close()
- with self._sessions_lock:
- self._sessions = {}
-
- def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
- """ Establishes a connection between the client and the server-side
- Socks5 proxy.
-
- sid : The StreamID. <str>
- requester : The JID of the requester. <str>
- target : The JID of the target. <str>
- proxy_host : The hostname or the IP of the proxy. <str>
- proxy_port : The port of the proxy. <str> or <int>
- peer : The JID for the other side of the stream, regardless
- of target or requester status.
- """
- # Because the xep_0065 plugin uses the proxy_port as string,
- # the Proxy class accepts the proxy_port argument as a string
- # or an integer. Here, we force to use the port as an integer.
- proxy_port = int(proxy_port)
-
- sock = socksocket()
- sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port)
-
- # The hostname MUST be SHA1(SID + Requester JID + Target JID)
- # where the output is hexadecimal-encoded (not binary).
- digest = sha1()
- digest.update(sid.encode('utf-8'))
- digest.update(str(requester).encode('utf-8'))
- digest.update(str(target).encode('utf-8'))
-
- dest = digest.hexdigest()
-
- # The port MUST be 0.
- sock.connect((dest, 0))
- log.info('Socket connected.')
-
- _close = sock.close
- def close(*args, **kwargs):
- with self._sessions_lock:
- if sid in self._sessions:
- del self._sessions[sid]
- _close()
- log.info('Socket closed.')
- sock.close = close
-
- sock.peer_jid = peer
- sock.self_jid = target if requester == peer else requester
-
- self.xmpp.event('socks_connected', sid)
- return sock
-
- def _accept_stream(self, iq):
- receiver = iq['to']
- sender = iq['from']
- sid = iq['socks']['sid']
-
- if self.api['authorized_sid'](receiver, sid, sender, iq):
- return True
- return self.api['authorized'](receiver, sid, sender, iq)
-
- def _authorized(self, jid, sid, ifrom, iq):
- return self.auto_accept
-
- def _authorized_sid(self, jid, sid, ifrom, iq):
- with self._preauthed_sids_lock:
- log.debug('>>> authed sids: %s', self._preauthed_sids)
- log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
- if (jid, sid, ifrom) in self._preauthed_sids:
- del self._preauthed_sids[(jid, sid, ifrom)]
- return True
- return False
-
- def _preauthorize_sid(self, jid, sid, ifrom, data):
- log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data)
- with self._preauthed_sids_lock:
- self._preauthed_sids[(jid, sid, ifrom)] = True
diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py
deleted file mode 100644
index e48bf1b5..00000000
--- a/sleekxmpp/plugins/xep_0065/stanza.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class Socks5(ElementBase):
- name = 'query'
- namespace = 'http://jabber.org/protocol/bytestreams'
- plugin_attrib = 'socks'
- interfaces = set(['sid', 'activate'])
- sub_interfaces = set(['activate'])
-
- def add_streamhost(self, jid, host, port):
- sh = StreamHost(parent=self)
- sh['jid'] = jid
- sh['host'] = host
- sh['port'] = port
-
-
-class StreamHost(ElementBase):
- name = 'streamhost'
- namespace = 'http://jabber.org/protocol/bytestreams'
- plugin_attrib = 'streamhost'
- plugin_multi_attrib = 'streamhosts'
- interfaces = set(['host', 'jid', 'port'])
-
- def set_jid(self, value):
- return self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-class StreamHostUsed(ElementBase):
- name = 'streamhost-used'
- namespace = 'http://jabber.org/protocol/bytestreams'
- plugin_attrib = 'streamhost_used'
- interfaces = set(['jid'])
-
- def set_jid(self, value):
- return self._set_attr('jid', str(value))
-
- def get_jid(self):
- return JID(self._get_attr('jid'))
-
-
-register_stanza_plugin(Socks5, StreamHost, iterable=True)
-register_stanza_plugin(Socks5, StreamHostUsed)
diff --git a/sleekxmpp/plugins/xep_0066/__init__.py b/sleekxmpp/plugins/xep_0066/__init__.py
deleted file mode 100644
index 68a50180..00000000
--- a/sleekxmpp/plugins/xep_0066/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0066 import stanza
-from sleekxmpp.plugins.xep_0066.stanza import OOB, OOBTransfer
-from sleekxmpp.plugins.xep_0066.oob import XEP_0066
-
-
-register_plugin(XEP_0066)
-
-
-# Retain some backwards compatibility
-xep_0066 = XEP_0066
diff --git a/sleekxmpp/plugins/xep_0066/oob.py b/sleekxmpp/plugins/xep_0066/oob.py
deleted file mode 100644
index 959c15a2..00000000
--- a/sleekxmpp/plugins/xep_0066/oob.py
+++ /dev/null
@@ -1,158 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Message, Presence, Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0066 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0066(BasePlugin):
-
- """
- XEP-0066: Out of Band Data
-
- Out of Band Data is a basic method for transferring files between
- XMPP agents. The URL of the resource in question is sent to the receiving
- entity, which then downloads the resource before responding to the OOB
- request. OOB is also used as a generic means to transmit URLs in other
- stanzas to indicate where to find additional information.
-
- Also see <http://www.xmpp.org/extensions/xep-0066.html>.
-
- Events:
- oob_transfer -- Raised when a request to download a resource
- has been received.
-
- Methods:
- send_oob -- Send a request to another entity to download a file
- or other addressable resource.
- """
-
- name = 'xep_0066'
- description = 'XEP-0066: Out of Band Data'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- """Start the XEP-0066 plugin."""
-
- self.url_handlers = {'global': self._default_handler,
- 'jid': {}}
-
- register_stanza_plugin(Iq, stanza.OOBTransfer)
- register_stanza_plugin(Message, stanza.OOB)
- register_stanza_plugin(Presence, stanza.OOB)
-
- self.xmpp.register_handler(
- Callback('OOB Transfer',
- StanzaPath('iq@type=set/oob_transfer'),
- self._handle_transfer))
-
- def plugin_end(self):
- self.xmpp.remove_handler('OOB Transfer')
- self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace)
- self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace)
- self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace)
-
- def register_url_handler(self, jid=None, handler=None):
- """
- Register a handler to process download requests, either for all
- JIDs or a single JID.
-
- Arguments:
- jid -- If None, then set the handler as a global default.
- handler -- If None, then remove the existing handler for the
- given JID, or reset the global handler if the JID
- is None.
- """
- if jid is None:
- if handler is not None:
- self.url_handlers['global'] = handler
- else:
- self.url_handlers['global'] = self._default_handler
- else:
- if handler is not None:
- self.url_handlers['jid'][jid] = handler
- else:
- del self.url_handlers['jid'][jid]
-
- def send_oob(self, to, url, desc=None, ifrom=None, **iqargs):
- """
- Initiate a basic file transfer by sending the URL of
- a file or other resource.
-
- Arguments:
- url -- The URL of the resource to transfer.
- desc -- An optional human readable description of the item
- that is to be transferred.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- """
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = to
- iq['from'] = ifrom
- iq['oob_transfer']['url'] = url
- iq['oob_transfer']['desc'] = desc
- return iq.send(**iqargs)
-
- def _run_url_handler(self, iq):
- """
- Execute the appropriate handler for a transfer request.
-
- Arguments:
- iq -- The Iq stanza containing the OOB transfer request.
- """
- if iq['to'] in self.url_handlers['jid']:
- return self.url_handlers['jid'][iq['to']](iq)
- else:
- if self.url_handlers['global']:
- self.url_handlers['global'](iq)
- else:
- raise XMPPError('service-unavailable')
-
- def _default_handler(self, iq):
- """
- As a safe default, don't actually download files.
-
- Register a new handler using self.register_url_handler to
- screen requests and download files.
-
- Arguments:
- iq -- The Iq stanza containing the OOB transfer request.
- """
- raise XMPPError('service-unavailable')
-
- def _handle_transfer(self, iq):
- """
- Handle receiving an out-of-band transfer request.
-
- Arguments:
- iq -- An Iq stanza containing an OOB transfer request.
- """
- log.debug('Received out-of-band data request for %s from %s:' % (
- iq['oob_transfer']['url'], iq['from']))
- self._run_url_handler(iq)
- iq.reply().send()
diff --git a/sleekxmpp/plugins/xep_0066/stanza.py b/sleekxmpp/plugins/xep_0066/stanza.py
deleted file mode 100644
index 21387485..00000000
--- a/sleekxmpp/plugins/xep_0066/stanza.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class OOBTransfer(ElementBase):
-
- """
- """
-
- name = 'query'
- namespace = 'jabber:iq:oob'
- plugin_attrib = 'oob_transfer'
- interfaces = set(('url', 'desc', 'sid'))
- sub_interfaces = set(('url', 'desc'))
-
-
-class OOB(ElementBase):
-
- """
- """
-
- name = 'x'
- namespace = 'jabber:x:oob'
- plugin_attrib = 'oob'
- interfaces = set(('url', 'desc'))
- sub_interfaces = interfaces
diff --git a/sleekxmpp/plugins/xep_0071/__init__.py b/sleekxmpp/plugins/xep_0071/__init__.py
deleted file mode 100644
index c21e9265..00000000
--- a/sleekxmpp/plugins/xep_0071/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM
-from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071
-
-
-register_plugin(XEP_0071)
diff --git a/sleekxmpp/plugins/xep_0071/stanza.py b/sleekxmpp/plugins/xep_0071/stanza.py
deleted file mode 100644
index d5ff1a1b..00000000
--- a/sleekxmpp/plugins/xep_0071/stanza.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Message
-from sleekxmpp.util import unicode
-from sleekxmpp.thirdparty import OrderedDict
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring
-
-
-XHTML_NS = 'http://www.w3.org/1999/xhtml'
-
-
-class XHTML_IM(ElementBase):
-
- namespace = 'http://jabber.org/protocol/xhtml-im'
- name = 'html'
- interfaces = set(['body'])
- lang_interfaces = set(['body'])
- plugin_attrib = name
-
- def set_body(self, content, lang=None):
- if lang is None:
- lang = self.get_lang()
- self.del_body(lang)
- if lang == '*':
- for sublang, subcontent in content.items():
- self.set_body(subcontent, sublang)
- else:
- if isinstance(content, type(ET.Element('test'))):
- content = unicode(ET.tostring(content))
- else:
- content = unicode(content)
- header = '<body xmlns="%s"' % XHTML_NS
- if lang:
- header = '%s xml:lang="%s"' % (header, lang)
- content = '%s>%s</body>' % (header, content)
- xhtml = ET.fromstring(content)
- self.xml.append(xhtml)
-
- def get_body(self, lang=None):
- """Return the contents of the HTML body."""
- if lang is None:
- lang = self.get_lang()
-
- bodies = self.xml.findall('{%s}body' % XHTML_NS)
-
- if lang == '*':
- result = OrderedDict()
- for body in bodies:
- body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '')
- body_result = []
- body_result.append(body.text if body.text else '')
- for child in body:
- body_result.append(tostring(child, xmlns=XHTML_NS))
- body_result.append(body.tail if body.tail else '')
- result[body_lang] = ''.join(body_result)
- return result
- else:
- for body in bodies:
- if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
- result = []
- result.append(body.text if body.text else '')
- for child in body:
- result.append(tostring(child, xmlns=XHTML_NS))
- result.append(body.tail if body.tail else '')
- return ''.join(result)
- return ''
-
- def del_body(self, lang=None):
- if lang is None:
- lang = self.get_lang()
- bodies = self.xml.findall('{%s}body' % XHTML_NS)
- for body in bodies:
- if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
- self.xml.remove(body)
- return
diff --git a/sleekxmpp/plugins/xep_0071/xhtml_im.py b/sleekxmpp/plugins/xep_0071/xhtml_im.py
deleted file mode 100644
index 096a00aa..00000000
--- a/sleekxmpp/plugins/xep_0071/xhtml_im.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.stanza import Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM
-
-
-class XEP_0071(BasePlugin):
-
- name = 'xep_0071'
- description = 'XEP-0071: XHTML-IM'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, XHTML_IM)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace)
diff --git a/sleekxmpp/plugins/xep_0077/__init__.py b/sleekxmpp/plugins/xep_0077/__init__.py
deleted file mode 100644
index 779ae0ac..00000000
--- a/sleekxmpp/plugins/xep_0077/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0077.stanza import Register, RegisterFeature
-from sleekxmpp.plugins.xep_0077.register import XEP_0077
-
-
-register_plugin(XEP_0077)
-
-
-# Retain some backwards compatibility
-xep_0077 = XEP_0077
diff --git a/sleekxmpp/plugins/xep_0077/register.py b/sleekxmpp/plugins/xep_0077/register.py
deleted file mode 100644
index ee07548b..00000000
--- a/sleekxmpp/plugins/xep_0077/register.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import ssl
-
-from sleekxmpp.stanza import StreamFeatures, Iq
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0077 import stanza, Register, RegisterFeature
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0077(BasePlugin):
-
- """
- XEP-0077: In-Band Registration
- """
-
- name = 'xep_0077'
- description = 'XEP-0077: In-Band Registration'
- dependencies = set(['xep_0004', 'xep_0066'])
- stanza = stanza
- default_config = {
- 'create_account': True,
- 'force_registration': False,
- 'order': 50
- }
-
- def plugin_init(self):
- register_stanza_plugin(StreamFeatures, RegisterFeature)
- register_stanza_plugin(Iq, Register)
-
- if not self.xmpp.is_component:
- self.xmpp.register_feature('register',
- self._handle_register_feature,
- restart=False,
- order=self.order)
-
- register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
- register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
-
- self.xmpp.add_event_handler('connected', self._force_registration)
-
- def plugin_end(self):
- if not self.xmpp.is_component:
- self.xmpp.unregister_feature('register', self.order)
-
- def _force_registration(self, event):
- if self.force_registration:
- self.xmpp.add_filter('in', self._force_stream_feature)
-
- def _force_stream_feature(self, stanza):
- if isinstance(stanza, StreamFeatures):
- if self.xmpp.use_tls or self.xmpp.use_ssl:
- if 'starttls' not in self.xmpp.features:
- return stanza
- elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
- return stanza
- if 'mechanisms' not in self.xmpp.features:
- log.debug('Forced adding in-band registration stream feature')
- stanza.enable('register')
- self.xmpp.del_filter('in', self._force_stream_feature)
- return stanza
-
- def _handle_register_feature(self, features):
- if 'mechanisms' in self.xmpp.features:
- # We have already logged in with an account
- return False
-
- if self.create_account and self.xmpp.event_handled('register'):
- form = self.get_registration()
- self.xmpp.event('register', form, direct=True)
- return True
- return False
-
- def get_registration(self, jid=None, ifrom=None, block=True,
- timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = jid
- iq['from'] = ifrom
- iq.enable('register')
- return iq.send(block=block, timeout=timeout,
- callback=callback, now=True)
-
- def cancel_registration(self, jid=None, ifrom=None, block=True,
- timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- iq['register']['remove'] = True
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def change_password(self, password, jid=None, ifrom=None, block=True,
- timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- if self.xmpp.is_component:
- ifrom = JID(ifrom)
- iq['register']['username'] = ifrom.user
- else:
- iq['register']['username'] = self.xmpp.boundjid.user
- iq['register']['password'] = password
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0077/stanza.py b/sleekxmpp/plugins/xep_0077/stanza.py
deleted file mode 100644
index e06c1910..00000000
--- a/sleekxmpp/plugins/xep_0077/stanza.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from __future__ import unicode_literals
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Register(ElementBase):
-
- namespace = 'jabber:iq:register'
- name = 'query'
- plugin_attrib = 'register'
- interfaces = set(('username', 'password', 'email', 'nick', 'name',
- 'first', 'last', 'address', 'city', 'state', 'zip',
- 'phone', 'url', 'date', 'misc', 'text', 'key',
- 'registered', 'remove', 'instructions', 'fields'))
- sub_interfaces = interfaces
- form_fields = set(('username', 'password', 'email', 'nick', 'name',
- 'first', 'last', 'address', 'city', 'state', 'zip',
- 'phone', 'url', 'date', 'misc', 'text', 'key'))
-
- def get_registered(self):
- present = self.xml.find('{%s}registered' % self.namespace)
- return present is not None
-
- def get_remove(self):
- present = self.xml.find('{%s}remove' % self.namespace)
- return present is not None
-
- def set_registered(self, value):
- if value:
- self.add_field('registered')
- else:
- del self['registered']
-
- def set_remove(self, value):
- if value:
- self.add_field('remove')
- else:
- del self['remove']
-
- def add_field(self, value):
- self._set_sub_text(value, '', keep=True)
-
- def get_fields(self):
- fields = set()
- for field in self.form_fields:
- if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
- fields.add(field)
- return fields
-
- def set_fields(self, fields):
- del self['fields']
- for field in fields:
- self._set_sub_text(field, '', keep=True)
-
- def del_fields(self):
- for field in self.form_fields:
- self._del_sub(field)
-
-
-class RegisterFeature(ElementBase):
-
- name = 'register'
- namespace = 'http://jabber.org/features/iq-register'
- plugin_attrib = name
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0078/__init__.py b/sleekxmpp/plugins/xep_0078/__init__.py
deleted file mode 100644
index 2ea72ffb..00000000
--- a/sleekxmpp/plugins/xep_0078/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0078 import stanza
-from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature
-from sleekxmpp.plugins.xep_0078.legacyauth import XEP_0078
-
-
-register_plugin(XEP_0078)
-
-
-# Retain some backwards compatibility
-xep_0078 = XEP_0078
diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py
deleted file mode 100644
index da6bfa2c..00000000
--- a/sleekxmpp/plugins/xep_0078/legacyauth.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import uuid
-import logging
-import hashlib
-import random
-import sys
-
-from sleekxmpp.jid import JID
-from sleekxmpp.exceptions import IqError, IqTimeout
-from sleekxmpp.stanza import Iq, StreamFeatures
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0078 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0078(BasePlugin):
-
- """
- XEP-0078 NON-SASL Authentication
-
- This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin
- unless you are forced to use an old XMPP server implementation.
- """
-
- name = 'xep_0078'
- description = 'XEP-0078: Non-SASL Authentication'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'order': 15
- }
-
- def plugin_init(self):
- self.xmpp.register_feature('auth',
- self._handle_auth,
- restart=False,
- order=self.order)
-
- self.xmpp.add_event_handler('legacy_protocol',
- self._handle_legacy_protocol)
-
- register_stanza_plugin(Iq, stanza.IqAuth)
- register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
-
- def plugin_end(self):
- self.xmpp.del_event_handler('legacy_protocol',
- self._handle_legacy_protocol)
- self.xmpp.unregister_feature('auth', self.order)
-
- def _handle_auth(self, features):
- # If we can or have already authenticated with SASL, do nothing.
- if 'mechanisms' in features['features']:
- return False
- return self.authenticate()
-
- def _handle_legacy_protocol(self, event):
- self.authenticate()
-
- def authenticate(self):
- if self.xmpp.authenticated:
- return False
-
- log.debug("Starting jabber:iq:auth Authentication")
-
- # Step 1: Request the auth form
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = self.xmpp.requested_jid.host
- iq['auth']['username'] = self.xmpp.requested_jid.user
-
- try:
- resp = iq.send(now=True)
- except IqError as err:
- log.info("Authentication failed: %s", err.iq['error']['condition'])
- self.xmpp.event('failed_auth', direct=True)
- self.xmpp.disconnect()
- return True
- except IqTimeout:
- log.info("Authentication failed: %s", 'timeout')
- self.xmpp.event('failed_auth', direct=True)
- self.xmpp.disconnect()
- return True
-
- # Step 2: Fill out auth form for either password or digest auth
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['auth']['username'] = self.xmpp.requested_jid.user
-
- # A resource is required, so create a random one if necessary
- resource = self.xmpp.requested_jid.resource
- if not resource:
- resource = str(uuid.uuid4())
-
- iq['auth']['resource'] = resource
-
- if 'digest' in resp['auth']['fields']:
- log.debug('Authenticating via jabber:iq:auth Digest')
- if sys.version_info < (3, 0):
- stream_id = bytes(self.xmpp.stream_id)
- password = bytes(self.xmpp.password)
- else:
- stream_id = bytes(self.xmpp.stream_id, encoding='utf-8')
- password = bytes(self.xmpp.password, encoding='utf-8')
-
- digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest()
- iq['auth']['digest'] = digest
- else:
- log.warning('Authenticating via jabber:iq:auth Plain.')
- iq['auth']['password'] = self.xmpp.password
-
- # Step 3: Send credentials
- try:
- result = iq.send(now=True)
- except IqError as err:
- log.info("Authentication failed")
- self.xmpp.event("failed_auth", direct=True)
- self.xmpp.disconnect()
- except IqTimeout:
- log.info("Authentication failed")
- self.xmpp.event("failed_auth", direct=True)
- self.xmpp.disconnect()
-
- self.xmpp.features.add('auth')
-
- self.xmpp.authenticated = True
-
- self.xmpp.boundjid = JID(self.xmpp.requested_jid,
- resource=resource,
- cache_lock=True)
- self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
-
- log.debug("Established Session")
- self.xmpp.sessionstarted = True
- self.xmpp.session_started_event.set()
- self.xmpp.event('session_start')
-
- return True
diff --git a/sleekxmpp/plugins/xep_0078/stanza.py b/sleekxmpp/plugins/xep_0078/stanza.py
deleted file mode 100644
index c8b26071..00000000
--- a/sleekxmpp/plugins/xep_0078/stanza.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class IqAuth(ElementBase):
- namespace = 'jabber:iq:auth'
- name = 'query'
- plugin_attrib = 'auth'
- interfaces = set(('fields', 'username', 'password', 'resource', 'digest'))
- sub_interfaces = set(('username', 'password', 'resource', 'digest'))
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- def get_fields(self):
- fields = set()
- for field in self.sub_interfaces:
- if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
- fields.add(field)
- return fields
-
- def set_resource(self, value):
- self._set_sub_text('resource', value, keep=True)
-
- def set_password(self, value):
- self._set_sub_text('password', value, keep=True)
-
-
-class AuthFeature(ElementBase):
- namespace = 'http://jabber.org/features/iq-auth'
- name = 'auth'
- plugin_attrib = 'auth'
- interfaces = set()
- plugin_tag_map = {}
- plugin_attrib_map = {}
diff --git a/sleekxmpp/plugins/xep_0079/__init__.py b/sleekxmpp/plugins/xep_0079/__init__.py
deleted file mode 100644
index 09e66715..00000000
--- a/sleekxmpp/plugins/xep_0079/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0079.stanza import (
- AMP, Rule, InvalidRules, UnsupportedConditions,
- UnsupportedActions, FailedRules, FailedRule,
- AMPFeature)
-from sleekxmpp.plugins.xep_0079.amp import XEP_0079
-
-
-register_plugin(XEP_0079)
diff --git a/sleekxmpp/plugins/xep_0079/amp.py b/sleekxmpp/plugins/xep_0079/amp.py
deleted file mode 100644
index 918fb841..00000000
--- a/sleekxmpp/plugins/xep_0079/amp.py
+++ /dev/null
@@ -1,79 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-from sleekxmpp.stanza import Message, Error, StreamFeatures
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0079 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0079(BasePlugin):
-
- """
- XEP-0079 Advanced Message Processing
- """
-
- name = 'xep_0079'
- description = 'XEP-0079: Advanced Message Processing'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, stanza.AMP)
- register_stanza_plugin(Error, stanza.InvalidRules)
- register_stanza_plugin(Error, stanza.UnsupportedConditions)
- register_stanza_plugin(Error, stanza.UnsupportedActions)
- register_stanza_plugin(Error, stanza.FailedRules)
-
- self.xmpp.register_handler(
- Callback('AMP Response',
- MatchMany([
- StanzaPath('message/error/failed_rules'),
- StanzaPath('message/amp')
- ]),
- self._handle_amp_response))
-
- if not self.xmpp.is_component:
- self.xmpp.register_feature('amp',
- self._handle_amp_feature,
- restart=False,
- order=9000)
- register_stanza_plugin(StreamFeatures, stanza.AMPFeature)
-
- def plugin_end(self):
- self.xmpp.remove_handler('AMP Response')
-
- def _handle_amp_response(self, msg):
- log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
- if msg['type'] == 'error':
- self.xmpp.event('amp_error', msg)
- elif msg['amp']['status'] in ('alert', 'notify'):
- self.xmpp.event('amp_%s' % msg['amp']['status'], msg)
-
- def _handle_amp_feature(self, features):
- log.debug('Advanced Message Processing is available.')
- self.xmpp.features.add('amp')
-
- def discover_support(self, jid=None, **iqargs):
- if jid is None:
- if self.xmpp.is_component:
- jid = self.xmpp.server_host
- else:
- jid = self.xmpp.boundjid.host
-
- return self.xmpp['xep_0030'].get_info(
- jid=jid,
- node='http://jabber.org/protocol/amp',
- **iqargs)
diff --git a/sleekxmpp/plugins/xep_0079/stanza.py b/sleekxmpp/plugins/xep_0079/stanza.py
deleted file mode 100644
index cb6932d6..00000000
--- a/sleekxmpp/plugins/xep_0079/stanza.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from __future__ import unicode_literals
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class AMP(ElementBase):
- namespace = 'http://jabber.org/protocol/amp'
- name = 'amp'
- plugin_attrib = 'amp'
- interfaces = set(['from', 'to', 'status', 'per_hop'])
-
- def get_from(self):
- return JID(self._get_attr('from'))
-
- def set_from(self, value):
- return self._set_attr('from', str(value))
-
- def get_to(self):
- return JID(self._get_attr('from'))
-
- def set_to(self, value):
- return self._set_attr('to', str(value))
-
- def get_per_hop(self):
- return self._get_attr('per-hop') == 'true'
-
- def set_per_hop(self, value):
- if value:
- return self._set_attr('per-hop', 'true')
- else:
- return self._del_attr('per-hop')
-
- def del_per_hop(self):
- return self._del_attr('per-hop')
-
- def add_rule(self, action, condition, value):
- rule = Rule(parent=self)
- rule['action'] = action
- rule['condition'] = condition
- rule['value'] = value
-
-
-class Rule(ElementBase):
- namespace = 'http://jabber.org/protocol/amp'
- name = 'rule'
- plugin_attrib = name
- plugin_multi_attrib = 'rules'
- interfaces = set(['action', 'condition', 'value'])
-
-
-class InvalidRules(ElementBase):
- namespace = 'http://jabber.org/protocol/amp'
- name = 'invalid-rules'
- plugin_attrib = 'invalid_rules'
-
-
-class UnsupportedConditions(ElementBase):
- namespace = 'http://jabber.org/protocol/amp'
- name = 'unsupported-conditions'
- plugin_attrib = 'unsupported_conditions'
-
-
-class UnsupportedActions(ElementBase):
- namespace = 'http://jabber.org/protocol/amp'
- name = 'unsupported-actions'
- plugin_attrib = 'unsupported_actions'
-
-
-class FailedRule(Rule):
- namespace = 'http://jabber.org/protocol/amp#errors'
-
-
-class FailedRules(ElementBase):
- namespace = 'http://jabber.org/protocol/amp#errors'
- name = 'failed-rules'
- plugin_attrib = 'failed_rules'
-
-
-class AMPFeature(ElementBase):
- namespace = 'http://jabber.org/features/amp'
- name = 'amp'
-
-
-register_stanza_plugin(AMP, Rule, iterable=True)
-register_stanza_plugin(InvalidRules, Rule, iterable=True)
-register_stanza_plugin(UnsupportedConditions, Rule, iterable=True)
-register_stanza_plugin(UnsupportedActions, Rule, iterable=True)
-register_stanza_plugin(FailedRules, FailedRule, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0080/__init__.py b/sleekxmpp/plugins/xep_0080/__init__.py
deleted file mode 100644
index cad23d22..00000000
--- a/sleekxmpp/plugins/xep_0080/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0080.stanza import Geoloc
-from sleekxmpp.plugins.xep_0080.geoloc import XEP_0080
-
-
-register_plugin(XEP_0080)
diff --git a/sleekxmpp/plugins/xep_0080/geoloc.py b/sleekxmpp/plugins/xep_0080/geoloc.py
deleted file mode 100644
index ba594cce..00000000
--- a/sleekxmpp/plugins/xep_0080/geoloc.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0080 import stanza, Geoloc
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0080(BasePlugin):
-
- """
- XEP-0080: User Location
- """
-
- name = 'xep_0080'
- description = 'XEP-0080: User Location'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_end(self):
- self.xmpp['xep_0163'].remove_interest(Geoloc.namespace)
- self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_location', Geoloc)
-
- def publish_location(self, **kwargs):
- """
- Publish the user's current location.
-
- Arguments:
- accuracy -- Horizontal GPS error in meters.
- alt -- Altitude in meters above or below sea level.
- area -- A named area such as a campus or neighborhood.
- bearing -- GPS bearing (direction in which the entity is
- heading to reach its next waypoint), measured in
- decimal degrees relative to true north.
- building -- A specific building on a street or in an area.
- country -- The nation where the user is located.
- countrycode -- The ISO 3166 two-letter country code.
- datum -- GPS datum.
- description -- A natural-language name for or description of
- the location.
- error -- Horizontal GPS error in arc minutes. Obsoleted by
- the accuracy parameter.
- floor -- A particular floor in a building.
- lat -- Latitude in decimal degrees North.
- locality -- A locality within the administrative region, such
- as a town or city.
- lon -- Longitude in decimal degrees East.
- postalcode -- A code used for postal delivery.
- region -- An administrative region of the nation, such
- as a state or province.
- room -- A particular room in a building.
- speed -- The speed at which the entity is moving,
- in meters per second.
- street -- A thoroughfare within the locality, or a crossing
- of two thoroughfares.
- text -- A catch-all element that captures any other
- information about the location.
- timestamp -- UTC timestamp specifying the moment when the
- reading was taken.
- uri -- A URI or URL pointing to information about
- the location.
-
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- options = kwargs.get('options', None)
- ifrom = kwargs.get('ifrom', None)
- block = kwargs.get('block', None)
- callback = kwargs.get('callback', None)
- timeout = kwargs.get('timeout', None)
- for param in ('ifrom', 'block', 'callback', 'timeout', 'options'):
- if param in kwargs:
- del kwargs[param]
-
- geoloc = Geoloc()
- geoloc.values = kwargs
-
- return self.xmpp['xep_0163'].publish(geoloc,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user location information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- geoloc = Geoloc()
- return self.xmpp['xep_0163'].publish(geoloc,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0080/stanza.py b/sleekxmpp/plugins/xep_0080/stanza.py
deleted file mode 100644
index 8f466516..00000000
--- a/sleekxmpp/plugins/xep_0080/stanza.py
+++ /dev/null
@@ -1,266 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.plugins import xep_0082
-
-
-class Geoloc(ElementBase):
-
- """
- XMPP's <geoloc> stanza allows entities to know the current
- geographical or physical location of an entity. (XEP-0080: User Location)
-
- Example <geoloc> stanzas:
- <geoloc xmlns='http://jabber.org/protocol/geoloc'/>
-
- <geoloc xmlns='http://jabber.org/protocol/geoloc' xml:lang='en'>
- <accuracy>20</accuracy>
- <country>Italy</country>
- <lat>45.44</lat>
- <locality>Venice</locality>
- <lon>12.33</lon>
- </geoloc>
-
- Stanza Interface:
- accuracy -- Horizontal GPS error in meters.
- alt -- Altitude in meters above or below sea level.
- area -- A named area such as a campus or neighborhood.
- bearing -- GPS bearing (direction in which the entity is
- heading to reach its next waypoint), measured in
- decimal degrees relative to true north.
- building -- A specific building on a street or in an area.
- country -- The nation where the user is located.
- countrycode -- The ISO 3166 two-letter country code.
- datum -- GPS datum.
- description -- A natural-language name for or description of
- the location.
- error -- Horizontal GPS error in arc minutes. Obsoleted by
- the accuracy parameter.
- floor -- A particular floor in a building.
- lat -- Latitude in decimal degrees North.
- locality -- A locality within the administrative region, such
- as a town or city.
- lon -- Longitude in decimal degrees East.
- postalcode -- A code used for postal delivery.
- region -- An administrative region of the nation, such
- as a state or province.
- room -- A particular room in a building.
- speed -- The speed at which the entity is moving,
- in meters per second.
- street -- A thoroughfare within the locality, or a crossing
- of two thoroughfares.
- text -- A catch-all element that captures any other
- information about the location.
- timestamp -- UTC timestamp specifying the moment when the
- reading was taken.
- uri -- A URI or URL pointing to information about
- the location.
- """
-
- namespace = 'http://jabber.org/protocol/geoloc'
- name = 'geoloc'
- interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
- 'country', 'countrycode', 'datum', 'dscription',
- 'error', 'floor', 'lat', 'locality', 'lon',
- 'postalcode', 'region', 'room', 'speed', 'street',
- 'text', 'timestamp', 'uri'))
- sub_interfaces = interfaces
- plugin_attrib = name
-
- def exception(self, e):
- """
- Override exception passback for presence.
- """
- pass
-
- def set_accuracy(self, accuracy):
- """
- Set the value of the <accuracy> element.
-
- Arguments:
- accuracy -- Horizontal GPS error in meters
- """
- self._set_sub_text('accuracy', text=str(accuracy))
- return self
-
- def get_accuracy(self):
- """
- Return the value of the <accuracy> element as an integer.
- """
- p = self._get_sub_text('accuracy')
- if not p:
- return None
- else:
- try:
- return int(p)
- except ValueError:
- return None
-
- def set_alt(self, alt):
- """
- Set the value of the <alt> element.
-
- Arguments:
- alt -- Altitude in meters above or below sea level
- """
- self._set_sub_text('alt', text=str(alt))
- return self
-
- def get_alt(self):
- """
- Return the value of the <alt> element as an integer.
- """
- p = self._get_sub_text('alt')
- if not p:
- return None
- else:
- try:
- return int(p)
- except ValueError:
- return None
-
- def set_bearing(self, bearing):
- """
- Set the value of the <bearing> element.
-
- Arguments:
- bearing -- GPS bearing (direction in which the entity is heading
- to reach its next waypoint), measured in decimal
- degrees relative to true north
- """
- self._set_sub_text('bearing', text=str(bearing))
- return self
-
- def get_bearing(self):
- """
- Return the value of the <bearing> element as a float.
- """
- p = self._get_sub_text('bearing')
- if not p:
- return None
- else:
- try:
- return float(p)
- except ValueError:
- return None
-
- def set_error(self, error):
- """
- Set the value of the <error> element.
-
- Arguments:
- error -- Horizontal GPS error in arc minutes; this
- element is deprecated in favor of <accuracy/>
- """
- self._set_sub_text('error', text=str(error))
- return self
-
- def get_error(self):
- """
- Return the value of the <error> element as a float.
- """
- p = self._get_sub_text('error')
- if not p:
- return None
- else:
- try:
- return float(p)
- except ValueError:
- return None
-
- def set_lat(self, lat):
- """
- Set the value of the <lat> element.
-
- Arguments:
- lat -- Latitude in decimal degrees North
- """
- self._set_sub_text('lat', text=str(lat))
- return self
-
- def get_lat(self):
- """
- Return the value of the <lat> element as a float.
- """
- p = self._get_sub_text('lat')
- if not p:
- return None
- else:
- try:
- return float(p)
- except ValueError:
- return None
-
- def set_lon(self, lon):
- """
- Set the value of the <lon> element.
-
- Arguments:
- lon -- Longitude in decimal degrees East
- """
- self._set_sub_text('lon', text=str(lon))
- return self
-
- def get_lon(self):
- """
- Return the value of the <lon> element as a float.
- """
- p = self._get_sub_text('lon')
- if not p:
- return None
- else:
- try:
- return float(p)
- except ValueError:
- return None
-
- def set_speed(self, speed):
- """
- Set the value of the <speed> element.
-
- Arguments:
- speed -- The speed at which the entity is moving,
- in meters per second
- """
- self._set_sub_text('speed', text=str(speed))
- return self
-
- def get_speed(self):
- """
- Return the value of the <speed> element as a float.
- """
- p = self._get_sub_text('speed')
- if not p:
- return None
- else:
- try:
- return float(p)
- except ValueError:
- return None
-
- def set_timestamp(self, timestamp):
- """
- Set the value of the <timestamp> element.
-
- Arguments:
- timestamp -- UTC timestamp specifying the moment when
- the reading was taken
- """
- self._set_sub_text('timestamp', text=str(xep_0082.datetime(timestamp)))
- return self
-
- def get_timestamp(self):
- """
- Return the value of the <timestamp> element as a DateTime.
- """
- p = self._get_sub_text('timestamp')
- if not p:
- return None
- else:
- return xep_0082.datetime(p)
diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py
deleted file mode 100644
index 26eb68fa..00000000
--- a/sleekxmpp/plugins/xep_0082.py
+++ /dev/null
@@ -1,228 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso
-
-
-# =====================================================================
-# To make it easier for stanzas without direct access to plugin objects
-# to use the XEP-0082 utility methods, we will define them as top-level
-# functions and then just reference them in the plugin itself.
-
-def parse(time_str):
- """
- Convert a string timestamp into a datetime object.
-
- Arguments:
- time_str -- A formatted timestamp string.
- """
- return parse_iso(time_str)
-
-
-def format_date(time_obj):
- """
- Return a formatted string version of a date object.
-
- Format:
- YYYY-MM-DD
-
- Arguments:
- time_obj -- A date or datetime object.
- """
- if isinstance(time_obj, dt.datetime):
- time_obj = time_obj.date()
- return time_obj.isoformat()
-
-
-def format_time(time_obj):
- """
- Return a formatted string version of a time object.
-
- format:
- hh:mm:ss[.sss][TZD]
-
- arguments:
- time_obj -- A time or datetime object.
- """
- if isinstance(time_obj, dt.datetime):
- time_obj = time_obj.timetz()
- timestamp = time_obj.isoformat()
- if time_obj.tzinfo == tzutc():
- timestamp = timestamp[:-6]
- return '%sZ' % timestamp
- return timestamp
-
-
-def format_datetime(time_obj):
- """
- Return a formatted string version of a datetime object.
-
- Format:
- YYYY-MM-DDThh:mm:ss[.sss]TZD
-
- arguments:
- time_obj -- A datetime object.
- """
- timestamp = time_obj.isoformat('T')
- if time_obj.tzinfo == tzutc():
- timestamp = timestamp[:-6]
- return '%sZ' % timestamp
- return timestamp
-
-
-def date(year=None, month=None, day=None, obj=False):
- """
- Create a date only timestamp for the given instant.
-
- Unspecified components default to their current counterparts.
-
- Arguments:
- year -- Integer value of the year (4 digits)
- month -- Integer value of the month
- day -- Integer value of the day of the month.
- obj -- If True, return the date object instead
- of a formatted string. Defaults to False.
- """
- today = dt.datetime.utcnow()
- if year is None:
- year = today.year
- if month is None:
- month = today.month
- if day is None:
- day = today.day
- value = dt.date(year, month, day)
- if obj:
- return value
- return format_date(value)
-
-
-def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
- """
- Create a time only timestamp for the given instant.
-
- Unspecified components default to their current counterparts.
-
- Arguments:
- hour -- Integer value of the hour.
- min -- Integer value of the number of minutes.
- sec -- Integer value of the number of seconds.
- micro -- Integer value of the number of microseconds.
- offset -- Either a positive or negative number of seconds
- to offset from UTC to match a desired timezone,
- or a tzinfo object.
- obj -- If True, return the time object instead
- of a formatted string. Defaults to False.
- """
- now = dt.datetime.utcnow()
- if hour is None:
- hour = now.hour
- if min is None:
- min = now.minute
- if sec is None:
- sec = now.second
- if micro is None:
- micro = now.microsecond
- if offset is None:
- offset = tzutc()
- elif not isinstance(offset, dt.tzinfo):
- offset = tzoffset(None, offset)
- value = dt.time(hour, min, sec, micro, offset)
- if obj:
- return value
- return format_time(value)
-
-
-def datetime(year=None, month=None, day=None, hour=None,
- min=None, sec=None, micro=None, offset=None,
- separators=True, obj=False):
- """
- Create a datetime timestamp for the given instant.
-
- Unspecified components default to their current counterparts.
-
- Arguments:
- year -- Integer value of the year (4 digits)
- month -- Integer value of the month
- day -- Integer value of the day of the month.
- hour -- Integer value of the hour.
- min -- Integer value of the number of minutes.
- sec -- Integer value of the number of seconds.
- micro -- Integer value of the number of microseconds.
- offset -- Either a positive or negative number of seconds
- to offset from UTC to match a desired timezone,
- or a tzinfo object.
- obj -- If True, return the datetime object instead
- of a formatted string. Defaults to False.
- """
- now = dt.datetime.utcnow()
- if year is None:
- year = now.year
- if month is None:
- month = now.month
- if day is None:
- day = now.day
- if hour is None:
- hour = now.hour
- if min is None:
- min = now.minute
- if sec is None:
- sec = now.second
- if micro is None:
- micro = now.microsecond
- if offset is None:
- offset = tzutc()
- elif not isinstance(offset, dt.tzinfo):
- offset = tzoffset(None, offset)
-
- value = dt.datetime(year, month, day, hour,
- min, sec, micro, offset)
- if obj:
- return value
- return format_datetime(value)
-
-
-class XEP_0082(BasePlugin):
-
- """
- XEP-0082: XMPP Date and Time Profiles
-
- XMPP uses a subset of the formats allowed by ISO 8601 as a matter of
- pragmatism based on the relatively few formats historically used by
- the XMPP.
-
- Also see <http://www.xmpp.org/extensions/xep-0082.html>.
-
- Methods:
- date -- Create a time stamp using the Date profile.
- datetime -- Create a time stamp using the DateTime profile.
- time -- Create a time stamp using the Time profile.
- format_date -- Format an existing date object.
- format_datetime -- Format an existing datetime object.
- format_time -- Format an existing time object.
- parse -- Convert a time string into a Python datetime object.
- """
-
- name = 'xep_0082'
- description = 'XEP-0082: XMPP Date and Time Profiles'
- dependencies = set()
-
- def plugin_init(self):
- """Start the XEP-0082 plugin."""
- self.date = date
- self.datetime = datetime
- self.time = time
- self.format_date = format_date
- self.format_datetime = format_datetime
- self.format_time = format_time
- self.parse = parse
-
-
-register_plugin(XEP_0082)
diff --git a/sleekxmpp/plugins/xep_0084/__init__.py b/sleekxmpp/plugins/xep_0084/__init__.py
deleted file mode 100644
index 6b87573f..00000000
--- a/sleekxmpp/plugins/xep_0084/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0084 import stanza
-from sleekxmpp.plugins.xep_0084.stanza import Data, MetaData
-from sleekxmpp.plugins.xep_0084.avatar import XEP_0084
-
-
-register_plugin(XEP_0084)
diff --git a/sleekxmpp/plugins/xep_0084/avatar.py b/sleekxmpp/plugins/xep_0084/avatar.py
deleted file mode 100644
index 677a888d..00000000
--- a/sleekxmpp/plugins/xep_0084/avatar.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import hashlib
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0084 import stanza, Data, MetaData
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0084(BasePlugin):
-
- name = 'xep_0084'
- description = 'XEP-0084: User Avatar'
- dependencies = set(['xep_0163', 'xep_0060'])
- stanza = stanza
-
- def plugin_init(self):
- pubsub_stanza = self.xmpp['xep_0060'].stanza
- register_stanza_plugin(pubsub_stanza.Item, Data)
- register_stanza_plugin(pubsub_stanza.EventItem, Data)
-
- self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace)
- self.xmpp['xep_0163'].remove_interest(MetaData.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
-
- def generate_id(self, data):
- return hashlib.sha1(data).hexdigest()
-
- def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True,
- callback=None, timeout=None):
- return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def publish_avatar(self, data, ifrom=None, block=True, callback=None,
- timeout=None):
- payload = Data()
- payload['value'] = data
- return self.xmpp['xep_0163'].publish(payload,
- id=self.generate_id(data),
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def publish_avatar_metadata(self, items=None, pointers=None,
- ifrom=None, block=True,
- callback=None, timeout=None):
- metadata = MetaData()
- if items is None:
- items = []
- if not isinstance(items, (list, set)):
- items = [items]
- for info in items:
- metadata.add_info(info['id'], info['type'], info['bytes'],
- height=info.get('height', ''),
- width=info.get('width', ''),
- url=info.get('url', ''))
-
- if pointers is not None:
- for pointer in pointers:
- metadata.add_pointer(pointer)
-
- return self.xmpp['xep_0163'].publish(metadata,
- id=info['id'],
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing avatar metadata information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- metadata = MetaData()
- return self.xmpp['xep_0163'].publish(metadata,
- node=MetaData.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0084/stanza.py b/sleekxmpp/plugins/xep_0084/stanza.py
deleted file mode 100644
index fd21e6f1..00000000
--- a/sleekxmpp/plugins/xep_0084/stanza.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from base64 import b64encode, b64decode
-
-from sleekxmpp.util import bytes as sbytes
-from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
-
-
-class Data(ElementBase):
- name = 'data'
- namespace = 'urn:xmpp:avatar:data'
- plugin_attrib = 'avatar_data'
- interfaces = set(['value'])
-
- def get_value(self):
- if self.xml.text:
- return b64decode(sbytes(self.xml.text))
- return ''
-
- def set_value(self, value):
- if value:
- self.xml.text = b64encode(sbytes(value))
- # Python3 base64 encoded is bytes and needs to be decoded to string
- if isinstance(self.xml.text, bytes):
- self.xml.text = self.xml.text.decode()
- else:
- self.xml.text = ''
-
- def del_value(self):
- self.xml.text = ''
-
-
-class MetaData(ElementBase):
- name = 'metadata'
- namespace = 'urn:xmpp:avatar:metadata'
- plugin_attrib = 'avatar_metadata'
- interfaces = set()
-
- def add_info(self, id, itype, ibytes, height=None, width=None, url=None):
- info = Info()
- info.values = {'id': id,
- 'type': itype,
- 'bytes': '%s' % ibytes,
- 'height': height,
- 'width': width,
- 'url': url}
- self.append(info)
-
- def add_pointer(self, xml):
- if not isinstance(xml, Pointer):
- pointer = Pointer()
- pointer.append(xml)
- self.append(pointer)
- else:
- self.append(xml)
-
-
-class Info(ElementBase):
- name = 'info'
- namespace = 'urn:xmpp:avatar:metadata'
- plugin_attrib = 'info'
- plugin_multi_attrib = 'items'
- interfaces = set(['bytes', 'height', 'id', 'type', 'url', 'width'])
-
-
-class Pointer(ElementBase):
- name = 'pointer'
- namespace = 'urn:xmpp:avatar:metadata'
- plugin_attrib = 'pointer'
- plugin_multi_attrib = 'pointers'
- interfaces = set()
-
-
-register_stanza_plugin(MetaData, Info, iterable=True)
-register_stanza_plugin(MetaData, Pointer, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0085/__init__.py b/sleekxmpp/plugins/xep_0085/__init__.py
deleted file mode 100644
index 445d5059..00000000
--- a/sleekxmpp/plugins/xep_0085/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0085.stanza import ChatState
-from sleekxmpp.plugins.xep_0085.chat_states import XEP_0085
-
-
-register_plugin(XEP_0085)
-
-
-# Retain some backwards compatibility
-xep_0085 = XEP_0085
diff --git a/sleekxmpp/plugins/xep_0085/chat_states.py b/sleekxmpp/plugins/xep_0085/chat_states.py
deleted file mode 100644
index 17f82afd..00000000
--- a/sleekxmpp/plugins/xep_0085/chat_states.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0085 import stanza, ChatState
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0085(BasePlugin):
-
- """
- XEP-0085 Chat State Notifications
- """
-
- name = 'xep_0085'
- description = 'XEP-0085: Chat State Notifications'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('Chat State',
- StanzaPath('message/chat_state'),
- self._handle_chat_state))
-
- register_stanza_plugin(Message, stanza.Active)
- register_stanza_plugin(Message, stanza.Composing)
- register_stanza_plugin(Message, stanza.Gone)
- register_stanza_plugin(Message, stanza.Inactive)
- register_stanza_plugin(Message, stanza.Paused)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Chat State')
-
- def session_bind(self, jid):
- self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
-
- def _handle_chat_state(self, msg):
- state = msg['chat_state']
- log.debug("Chat State: %s, %s", state, msg['from'].jid)
- self.xmpp.event('chatstate', msg)
- self.xmpp.event('chatstate_%s' % state, msg)
diff --git a/sleekxmpp/plugins/xep_0085/stanza.py b/sleekxmpp/plugins/xep_0085/stanza.py
deleted file mode 100644
index c2cafb19..00000000
--- a/sleekxmpp/plugins/xep_0085/stanza.py
+++ /dev/null
@@ -1,94 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import sleekxmpp
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class ChatState(ElementBase):
-
- """
- Example chat state stanzas:
- <message>
- <active xmlns="http://jabber.org/protocol/chatstates" />
- </message>
-
- <message>
- <paused xmlns="http://jabber.org/protocol/chatstates" />
- </message>
-
- Stanza Interfaces:
- chat_state
-
- Attributes:
- states
-
- Methods:
- get_chat_state
- set_chat_state
- del_chat_state
- """
-
- name = ''
- namespace = 'http://jabber.org/protocol/chatstates'
- plugin_attrib = 'chat_state'
- interfaces = set(('chat_state',))
- sub_interfaces = interfaces
- is_extension = True
-
- states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
-
- def setup(self, xml=None):
- self.xml = ET.Element('')
- return True
-
- def get_chat_state(self):
- parent = self.parent()
- for state in self.states:
- state_xml = parent.find('{%s}%s' % (self.namespace, state))
- if state_xml is not None:
- self.xml = state_xml
- return state
- return ''
-
- def set_chat_state(self, state):
- self.del_chat_state()
- parent = self.parent()
- if state in self.states:
- self.xml = ET.Element('{%s}%s' % (self.namespace, state))
- parent.append(self.xml)
- elif state not in [None, '']:
- raise ValueError('Invalid chat state')
-
- def del_chat_state(self):
- parent = self.parent()
- for state in self.states:
- state_xml = parent.find('{%s}%s' % (self.namespace, state))
- if state_xml is not None:
- self.xml = ET.Element('')
- parent.xml.remove(state_xml)
-
-
-class Active(ChatState):
- name = 'active'
-
-
-class Composing(ChatState):
- name = 'composing'
-
-
-class Gone(ChatState):
- name = 'gone'
-
-
-class Inactive(ChatState):
- name = 'inactive'
-
-
-class Paused(ChatState):
- name = 'paused'
diff --git a/sleekxmpp/plugins/xep_0086/__init__.py b/sleekxmpp/plugins/xep_0086/__init__.py
deleted file mode 100644
index 94600e85..00000000
--- a/sleekxmpp/plugins/xep_0086/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0086.stanza import LegacyError
-from sleekxmpp.plugins.xep_0086.legacy_error import XEP_0086
-
-
-register_plugin(XEP_0086)
-
-
-# Retain some backwards compatibility
-xep_0086 = XEP_0086
diff --git a/sleekxmpp/plugins/xep_0086/legacy_error.py b/sleekxmpp/plugins/xep_0086/legacy_error.py
deleted file mode 100644
index f7d0ac9c..00000000
--- a/sleekxmpp/plugins/xep_0086/legacy_error.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0086 import stanza, LegacyError
-
-
-class XEP_0086(BasePlugin):
-
- """
- XEP-0086: Error Condition Mappings
-
- Older XMPP implementations used code based error messages, similar
- to HTTP response codes. Since then, error condition elements have
- been introduced. XEP-0086 provides a mapping between the new
- condition elements and a combination of error types and the older
- response codes.
-
- Also see <http://xmpp.org/extensions/xep-0086.html>.
-
- Configuration Values:
- override -- Indicates if applying legacy error codes should
- be done automatically. Defaults to True.
- If False, then inserting legacy error codes can
- be done using:
- iq['error']['legacy']['condition'] = ...
- """
-
- name = 'xep_0086'
- description = 'XEP-0086: Error Condition Mappings'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'override': True
- }
-
- def plugin_init(self):
- register_stanza_plugin(Error, LegacyError,
- overrides=self.override)
diff --git a/sleekxmpp/plugins/xep_0086/stanza.py b/sleekxmpp/plugins/xep_0086/stanza.py
deleted file mode 100644
index d4909806..00000000
--- a/sleekxmpp/plugins/xep_0086/stanza.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class LegacyError(ElementBase):
-
- """
- Older XMPP implementations used code based error messages, similar
- to HTTP response codes. Since then, error condition elements have
- been introduced. XEP-0086 provides a mapping between the new
- condition elements and a combination of error types and the older
- response codes.
-
- Also see <http://xmpp.org/extensions/xep-0086.html>.
-
- Example legacy error stanzas:
- <error xmlns="jabber:client" code="501" type="cancel">
- <feature-not-implemented
- xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
- </error>
-
- <error code="402" type="auth">
- <payment-required
- xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
- </error>
-
- Attributes:
- error_map -- A map of error conditions to error types and
- code values.
- Methods:
- setup -- Overrides ElementBase.setup
- set_condition -- Remap the type and code interfaces when a
- condition is set.
- """
-
- name = 'legacy'
- namespace = Error.namespace
- plugin_attrib = name
- interfaces = set(('condition',))
- overrides = ['set_condition']
-
- error_map = {'bad-request': ('modify', '400'),
- 'conflict': ('cancel', '409'),
- 'feature-not-implemented': ('cancel', '501'),
- 'forbidden': ('auth', '403'),
- 'gone': ('modify', '302'),
- 'internal-server-error': ('wait', '500'),
- 'item-not-found': ('cancel', '404'),
- 'jid-malformed': ('modify', '400'),
- 'not-acceptable': ('modify', '406'),
- 'not-allowed': ('cancel', '405'),
- 'not-authorized': ('auth', '401'),
- 'payment-required': ('auth', '402'),
- 'recipient-unavailable': ('wait', '404'),
- 'redirect': ('modify', '302'),
- 'registration-required': ('auth', '407'),
- 'remote-server-not-found': ('cancel', '404'),
- 'remote-server-timeout': ('wait', '504'),
- 'resource-constraint': ('wait', '500'),
- 'service-unavailable': ('cancel', '503'),
- 'subscription-required': ('auth', '407'),
- 'undefined-condition': (None, '500'),
- 'unexpected-request': ('wait', '400')}
-
- def setup(self, xml):
- """Don't create XML for the plugin."""
- self.xml = ET.Element('')
-
- def set_condition(self, value):
- """
- Set the error type and code based on the given error
- condition value.
-
- Arguments:
- value -- The new error condition.
- """
- self.parent().set_condition(value)
-
- error_data = self.error_map.get(value, None)
- if error_data is not None:
- if error_data[0] is not None:
- self.parent()['type'] = error_data[0]
- self.parent()['code'] = error_data[1]
diff --git a/sleekxmpp/plugins/xep_0091/__init__.py b/sleekxmpp/plugins/xep_0091/__init__.py
deleted file mode 100644
index 04f21ef5..00000000
--- a/sleekxmpp/plugins/xep_0091/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0091 import stanza
-from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay
-from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091
-
-
-register_plugin(XEP_0091)
diff --git a/sleekxmpp/plugins/xep_0091/legacy_delay.py b/sleekxmpp/plugins/xep_0091/legacy_delay.py
deleted file mode 100644
index 7323d468..00000000
--- a/sleekxmpp/plugins/xep_0091/legacy_delay.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.stanza import Message, Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0091 import stanza
-
-
-class XEP_0091(BasePlugin):
-
- """
- XEP-0091: Legacy Delayed Delivery
- """
-
- name = 'xep_0091'
- description = 'XEP-0091: Legacy Delayed Delivery'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, stanza.LegacyDelay)
- register_stanza_plugin(Presence, stanza.LegacyDelay)
diff --git a/sleekxmpp/plugins/xep_0091/stanza.py b/sleekxmpp/plugins/xep_0091/stanza.py
deleted file mode 100644
index 17e55764..00000000
--- a/sleekxmpp/plugins/xep_0091/stanza.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.plugins import xep_0082
-
-
-class LegacyDelay(ElementBase):
-
- name = 'x'
- namespace = 'jabber:x:delay'
- plugin_attrib = 'legacy_delay'
- interfaces = set(('from', 'stamp', 'text'))
-
- def get_from(self):
- from_ = self._get_attr('from')
- return JID(from_) if from_ else None
-
- def set_from(self, value):
- self._set_attr('from', str(value))
-
- def get_stamp(self):
- timestamp = self._get_attr('stamp')
- return xep_0082.parse('%sZ' % timestamp) if timestamp else None
-
- def set_stamp(self, value):
- if isinstance(value, dt.datetime):
- value = value.astimezone(xep_0082.tzutc)
- value = xep_0082.format_datetime(value)
- self._set_attr('stamp', value[0:19].replace('-', ''))
-
- def get_text(self):
- return self.xml.text
-
- def set_text(self, value):
- self.xml.text = value
-
- def del_text(self):
- self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0092/__init__.py b/sleekxmpp/plugins/xep_0092/__init__.py
deleted file mode 100644
index 293eaae6..00000000
--- a/sleekxmpp/plugins/xep_0092/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0092 import stanza
-from sleekxmpp.plugins.xep_0092.stanza import Version
-from sleekxmpp.plugins.xep_0092.version import XEP_0092
-
-
-register_plugin(XEP_0092)
-
-
-# Retain some backwards compatibility
-xep_0092 = XEP_0092
diff --git a/sleekxmpp/plugins/xep_0092/stanza.py b/sleekxmpp/plugins/xep_0092/stanza.py
deleted file mode 100644
index 77654e37..00000000
--- a/sleekxmpp/plugins/xep_0092/stanza.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Version(ElementBase):
-
- """
- XMPP allows for an agent to advertise the name and version of the
- underlying software libraries, as well as the operating system
- that the agent is running on.
-
- Example version stanzas:
- <iq type="get">
- <query xmlns="jabber:iq:version" />
- </iq>
-
- <iq type="result">
- <query xmlns="jabber:iq:version">
- <name>SleekXMPP</name>
- <version>1.0</version>
- <os>Linux</os>
- </query>
- </iq>
-
- Stanza Interface:
- name -- The human readable name of the software.
- version -- The specific version of the software.
- os -- The name of the operating system running the program.
- """
-
- name = 'query'
- namespace = 'jabber:iq:version'
- plugin_attrib = 'software_version'
- interfaces = set(('name', 'version', 'os'))
- sub_interfaces = interfaces
diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py
deleted file mode 100644
index b16ad516..00000000
--- a/sleekxmpp/plugins/xep_0092/version.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0092 import Version, stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0092(BasePlugin):
-
- """
- XEP-0092: Software Version
- """
-
- name = 'xep_0092'
- description = 'XEP-0092: Software Version'
- dependencies = set(['xep_0030'])
- stanza = stanza
- default_config = {
- 'software_name': 'SleekXMPP',
- 'version': sleekxmpp.__version__,
- 'os': ''
- }
-
- def plugin_init(self):
- """
- Start the XEP-0092 plugin.
- """
- if 'name' in self.config:
- self.software_name = self.config['name']
-
- self.xmpp.register_handler(
- Callback('Software Version',
- StanzaPath('iq@type=get/software_version'),
- self._handle_version))
-
- register_stanza_plugin(Iq, Version)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Software Version')
- self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version')
-
- def session_bind(self, jid):
- self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
-
- def _handle_version(self, iq):
- """
- Respond to a software version query.
-
- Arguments:
- iq -- The Iq stanza containing the software version query.
- """
- iq.reply()
- iq['software_version']['name'] = self.software_name
- iq['software_version']['version'] = self.version
- iq['software_version']['os'] = self.os
- iq.send()
-
- def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None):
- """
- Retrieve the software version of a remote agent.
-
- Arguments:
- jid -- The JID of the entity to query.
- """
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
- iq['query'] = Version.namespace
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0095/__init__.py b/sleekxmpp/plugins/xep_0095/__init__.py
deleted file mode 100644
index 4465ef5c..00000000
--- a/sleekxmpp/plugins/xep_0095/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0095 import stanza
-from sleekxmpp.plugins.xep_0095.stanza import SI
-from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095
-
-
-register_plugin(XEP_0095)
diff --git a/sleekxmpp/plugins/xep_0095/stanza.py b/sleekxmpp/plugins/xep_0095/stanza.py
deleted file mode 100644
index 34999a11..00000000
--- a/sleekxmpp/plugins/xep_0095/stanza.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class SI(ElementBase):
- name = 'si'
- namespace = 'http://jabber.org/protocol/si'
- plugin_attrib = 'si'
- interfaces = set(['id', 'mime_type', 'profile'])
-
- def get_mime_type(self):
- return self._get_attr('mime-type', 'application/octet-stream')
-
- def set_mime_type(self, value):
- self._set_attr('mime-type', value)
-
- def del_mime_type(self):
- self._del_attr('mime-type')
diff --git a/sleekxmpp/plugins/xep_0095/stream_initiation.py b/sleekxmpp/plugins/xep_0095/stream_initiation.py
deleted file mode 100644
index 927248a5..00000000
--- a/sleekxmpp/plugins/xep_0095/stream_initiation.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import threading
-
-from uuid import uuid4
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0095 import stanza, SI
-
-
-log = logging.getLogger(__name__)
-
-
-SOCKS5 = 'http://jabber.org/protocol/bytestreams'
-IBB = 'http://jabber.org/protocol/ibb'
-
-
-class XEP_0095(BasePlugin):
-
- name = 'xep_0095'
- description = 'XEP-0095: Stream Initiation'
- dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'])
- stanza = stanza
-
- def plugin_init(self):
- self._profiles = {}
- self._methods = {}
- self._methods_order = []
- self._pending_lock = threading.Lock()
- self._pending= {}
-
- self.register_method(SOCKS5, 'xep_0065', 100)
- self.register_method(IBB, 'xep_0047', 50)
-
- register_stanza_plugin(Iq, SI)
- register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation)
-
- self.xmpp.register_handler(
- Callback('SI Request',
- StanzaPath('iq@type=set/si'),
- self._handle_request))
-
- self.api.register(self._add_pending, 'add_pending', default=True)
- self.api.register(self._get_pending, 'get_pending', default=True)
- self.api.register(self._del_pending, 'del_pending', default=True)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(SI.namespace)
-
- def plugin_end(self):
- self.xmpp.remove_handler('SI Request')
- self.xmpp['xep_0030'].del_feature(feature=SI.namespace)
-
- def register_profile(self, profile_name, plugin):
- self._profiles[profile_name] = plugin
-
- def unregister_profile(self, profile_name):
- try:
- del self._profiles[profile_name]
- except KeyError:
- pass
-
- def register_method(self, method, plugin_name, order=50):
- self._methods[method] = (plugin_name, order)
- self._methods_order.append((order, method, plugin_name))
- self._methods_order.sort()
-
- def unregister_method(self, method):
- if method in self._methods:
- plugin_name, order = self._methods[method]
- del self._methods[method]
- self._methods_order.remove((order, method, plugin_name))
- self._methods_order.sort()
-
- def _handle_request(self, iq):
- profile = iq['si']['profile']
- sid = iq['si']['id']
-
- if not sid:
- raise XMPPError(etype='modify', condition='bad-request')
- if profile not in self._profiles:
- raise XMPPError(
- etype='modify',
- condition='bad-request',
- extension='bad-profile',
- extension_ns=SI.namespace)
-
- neg = iq['si']['feature_neg']['form']['fields']
- options = neg['stream-method']['options'] or []
- methods = []
- for opt in options:
- methods.append(opt['value'])
- for method in methods:
- if method in self._methods:
- supported = True
- break
- else:
- raise XMPPError('bad-request',
- extension='no-valid-streams',
- extension_ns=SI.namespace)
-
- selected_method = None
- log.debug('Available: %s', methods)
- for order, method, plugin in self._methods_order:
- log.debug('Testing: %s', method)
- if method in methods:
- selected_method = method
- break
-
- receiver = iq['to']
- sender = iq['from']
-
- self.api['add_pending'](receiver, sid, sender, {
- 'response_id': iq['id'],
- 'method': selected_method,
- 'profile': profile
- })
- self.xmpp.event('si_request', iq)
-
- def offer(self, jid, sid=None, mime_type=None, profile=None,
- methods=None, payload=None, ifrom=None,
- **iqargs):
- if sid is None:
- sid = uuid4().hex
- if methods is None:
- methods = list(self._methods.keys())
- if not isinstance(methods, (list, tuple, set)):
- methods = [methods]
-
- si = self.xmpp.Iq()
- si['to'] = jid
- si['from'] = ifrom
- si['type'] = 'set'
- si['si']['id'] = sid
- si['si']['mime_type'] = mime_type
- si['si']['profile'] = profile
- if not isinstance(payload, (list, tuple, set)):
- payload = [payload]
- for item in payload:
- si['si'].append(item)
- si['si']['feature_neg']['form'].add_field(
- var='stream-method',
- ftype='list-single',
- options=methods)
- return si.send(**iqargs)
-
- def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None):
- stream = self.api['get_pending'](ifrom, sid, jid)
- iq = self.xmpp.Iq()
- iq['id'] = stream['response_id']
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'result'
- if payload:
- iq['si'].append(payload)
- iq['si']['feature_neg']['form']['type'] = 'submit'
- iq['si']['feature_neg']['form'].add_field(
- var='stream-method',
- ftype='list-single',
- value=stream['method'])
-
- if ifrom is None:
- ifrom = self.xmpp.boundjid
-
- method_plugin = self._methods[stream['method']][0]
- self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid)
-
- self.api['del_pending'](ifrom, sid, jid)
-
- if stream_handler:
- self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
- stream_handler,
- threaded=True,
- disposable=True)
- return iq.send()
-
- def decline(self, jid, sid, ifrom=None):
- stream = self.api['get_pending'](ifrom, sid, jid)
- if not stream:
- return
- iq = self.xmpp.Iq()
- iq['id'] = stream['response_id']
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'error'
- iq['error']['condition'] = 'forbidden'
- iq['error']['text'] = 'Offer declined'
- self.api['del_pending'](ifrom, sid, jid)
- return iq.send()
-
- def _add_pending(self, jid, node, ifrom, data):
- with self._pending_lock:
- self._pending[(jid, node, ifrom)] = data
-
- def _get_pending(self, jid, node, ifrom, data):
- with self._pending_lock:
- return self._pending.get((jid, node, ifrom), None)
-
- def _del_pending(self, jid, node, ifrom, data):
- with self._pending_lock:
- if (jid, node, ifrom) in self._pending:
- del self._pending[(jid, node, ifrom)]
diff --git a/sleekxmpp/plugins/xep_0096/__init__.py b/sleekxmpp/plugins/xep_0096/__init__.py
deleted file mode 100644
index 5f836169..00000000
--- a/sleekxmpp/plugins/xep_0096/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0096 import stanza
-from sleekxmpp.plugins.xep_0096.stanza import File
-from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096
-
-
-register_plugin(XEP_0096)
diff --git a/sleekxmpp/plugins/xep_0096/file_transfer.py b/sleekxmpp/plugins/xep_0096/file_transfer.py
deleted file mode 100644
index 52ba2f27..00000000
--- a/sleekxmpp/plugins/xep_0096/file_transfer.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0096 import stanza, File
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0096(BasePlugin):
-
- name = 'xep_0096'
- description = 'XEP-0096: SI File Transfer'
- dependencies = set(['xep_0095'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File)
-
- self.xmpp['xep_0095'].register_profile(File.namespace, self)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(File.namespace)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=File.namespace)
- self.xmpp['xep_0095'].unregister_profile(File.namespace, self)
-
- def request_file_transfer(self, jid, sid=None, name=None, size=None,
- desc=None, hash=None, date=None,
- allow_ranged=False, mime_type=None,
- **iqargs):
- data = File()
- data['name'] = name
- data['size'] = size
- data['date'] = date
- data['desc'] = desc
- data['hash'] = hash
- if allow_ranged:
- data.enable('range')
-
- return self.xmpp['xep_0095'].offer(jid,
- sid=sid,
- mime_type=mime_type,
- profile=File.namespace,
- payload=data,
- **iqargs)
diff --git a/sleekxmpp/plugins/xep_0096/stanza.py b/sleekxmpp/plugins/xep_0096/stanza.py
deleted file mode 100644
index 65eb5bc5..00000000
--- a/sleekxmpp/plugins/xep_0096/stanza.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-from sleekxmpp.plugins import xep_0082
-
-
-class File(ElementBase):
- name = 'file'
- namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
- plugin_attrib = 'file'
- interfaces = set(['name', 'size', 'date', 'hash', 'desc'])
- sub_interfaces = set(['desc'])
-
- def set_size(self, value):
- self._set_attr('size', str(value))
-
- def get_date(self):
- timestamp = self._get_attr('date')
- return xep_0082.parse(timestamp)
-
- def set_date(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_attr('date', value)
-
-
-class Range(ElementBase):
- name = 'range'
- namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
- plugin_attrib = 'range'
- interfaces = set(['length', 'offset'])
-
- def set_length(self, value):
- self._set_attr('length', str(value))
-
- def set_offset(self, value):
- self._set_attr('offset', str(value))
-
-
-register_stanza_plugin(File, Range)
diff --git a/sleekxmpp/plugins/xep_0106.py b/sleekxmpp/plugins/xep_0106.py
deleted file mode 100644
index 1859a77b..00000000
--- a/sleekxmpp/plugins/xep_0106.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-
-class XEP_0106(BasePlugin):
-
- name = 'xep_0106'
- description = 'XEP-0106: JID Escaping'
- dependencies = set(['xep_0030'])
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping')
-
-
-register_plugin(XEP_0106)
diff --git a/sleekxmpp/plugins/xep_0107/__init__.py b/sleekxmpp/plugins/xep_0107/__init__.py
deleted file mode 100644
index 04302df8..00000000
--- a/sleekxmpp/plugins/xep_0107/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0107 import stanza
-from sleekxmpp.plugins.xep_0107.stanza import UserMood
-from sleekxmpp.plugins.xep_0107.user_mood import XEP_0107
-
-
-register_plugin(XEP_0107)
diff --git a/sleekxmpp/plugins/xep_0107/stanza.py b/sleekxmpp/plugins/xep_0107/stanza.py
deleted file mode 100644
index 2c5814ea..00000000
--- a/sleekxmpp/plugins/xep_0107/stanza.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class UserMood(ElementBase):
-
- name = 'mood'
- namespace = 'http://jabber.org/protocol/mood'
- plugin_attrib = 'mood'
- interfaces = set(['value', 'text'])
- sub_interfaces = set(['text'])
- moods = set(['afraid', 'amazed', 'amorous', 'angry', 'annoyed', 'anxious',
- 'aroused', 'ashamed', 'bored', 'brave', 'calm', 'cautious',
- 'cold', 'confident', 'confused', 'contemplative', 'contented',
- 'cranky', 'crazy', 'creative', 'curious', 'dejected',
- 'depressed', 'disappointed', 'disgusted', 'dismayed',
- 'distracted', 'embarrassed', 'envious', 'excited',
- 'flirtatious', 'frustrated', 'grateful', 'grieving', 'grumpy',
- 'guilty', 'happy', 'hopeful', 'hot', 'humbled', 'humiliated',
- 'hungry', 'hurt', 'impressed', 'in_awe', 'in_love',
- 'indignant', 'interested', 'intoxicated', 'invincible',
- 'jealous', 'lonely', 'lost', 'lucky', 'mean', 'moody',
- 'nervous', 'neutral', 'offended', 'outraged', 'playful',
- 'proud', 'relaxed', 'relieved', 'remorseful', 'restless',
- 'sad', 'sarcastic', 'satisfied', 'serious', 'shocked',
- 'shy', 'sick', 'sleepy', 'spontaneous', 'stressed', 'strong',
- 'surprised', 'thankful', 'thirsty', 'tired', 'undefined',
- 'weak', 'worried'])
-
- def set_value(self, value):
- self.del_value()
- if value in self.moods:
- self._set_sub_text(value, '', keep=True)
- else:
- raise ValueError('Unknown mood value')
-
- def get_value(self):
- for child in self.xml:
- if child.tag.startswith('{%s}' % self.namespace):
- elem_name = child.tag.split('}')[-1]
- if elem_name in self.moods:
- return elem_name
- return ''
-
- def del_value(self):
- curr_value = self.get_value()
- if curr_value:
- self._set_sub_text(curr_value, '', keep=False)
diff --git a/sleekxmpp/plugins/xep_0107/user_mood.py b/sleekxmpp/plugins/xep_0107/user_mood.py
deleted file mode 100644
index 2d2f3551..00000000
--- a/sleekxmpp/plugins/xep_0107/user_mood.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Message
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0107 import stanza, UserMood
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0107(BasePlugin):
-
- """
- XEP-0107: User Mood
- """
-
- name = 'xep_0107'
- description = 'XEP-0107: User Mood'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, UserMood)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace)
- self.xmpp['xep_0163'].remove_interest(UserMood.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_mood', UserMood)
-
- def publish_mood(self, value=None, text=None, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Publish the user's current mood.
-
- Arguments:
- value -- The name of the mood to publish.
- text -- Optional natural-language description or reason
- for the mood.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- mood = UserMood()
- mood['value'] = value
- mood['text'] = text
- return self.xmpp['xep_0163'].publish(mood,
- node=UserMood.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user mood information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- mood = UserMood()
- return self.xmpp['xep_0163'].publish(mood,
- node=UserMood.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0108/__init__.py b/sleekxmpp/plugins/xep_0108/__init__.py
deleted file mode 100644
index 34d45113..00000000
--- a/sleekxmpp/plugins/xep_0108/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0108 import stanza
-from sleekxmpp.plugins.xep_0108.stanza import UserActivity
-from sleekxmpp.plugins.xep_0108.user_activity import XEP_0108
-
-
-register_plugin(XEP_0108)
diff --git a/sleekxmpp/plugins/xep_0108/stanza.py b/sleekxmpp/plugins/xep_0108/stanza.py
deleted file mode 100644
index 4650160a..00000000
--- a/sleekxmpp/plugins/xep_0108/stanza.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class UserActivity(ElementBase):
-
- name = 'activity'
- namespace = 'http://jabber.org/protocol/activity'
- plugin_attrib = 'activity'
- interfaces = set(['value', 'text'])
- sub_interfaces = set(['text'])
- general = set(['doing_chores', 'drinking', 'eating', 'exercising',
- 'grooming', 'having_appointment', 'inactive', 'relaxing',
- 'talking', 'traveling', 'undefined', 'working'])
- specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
- 'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
- 'dancing', 'day_off', 'doing_maintenance',
- 'doing_the_dishes', 'doing_the_laundry', 'driving',
- 'fishing', 'gaming', 'gardening', 'getting_a_haircut',
- 'going_out', 'hanging_out', 'having_a_beer',
- 'having_a_snack', 'having_breakfast', 'having_coffee',
- 'having_dinner', 'having_lunch', 'having_tea', 'hiding',
- 'hiking', 'in_a_car', 'in_a_meeting', 'in_real_life',
- 'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
- 'on_a_trip', 'on_the_phone', 'on_vacation',
- 'on_video_phone', 'other', 'partying', 'playing_sports',
- 'praying', 'reading', 'rehearsing', 'running',
- 'running_an_errand', 'scheduled_holiday', 'shaving',
- 'shopping', 'skiing', 'sleeping', 'smoking',
- 'socializing', 'studying', 'sunbathing', 'swimming',
- 'taking_a_bath', 'taking_a_shower', 'thinking',
- 'walking', 'walking_the_dog', 'watching_a_movie',
- 'watching_tv', 'working_out', 'writing'])
-
- def set_value(self, value):
- self.del_value()
- general = value
- specific = None
- if isinstance(value, tuple) or isinstance(value, list):
- general = value[0]
- specific = value[1]
-
- if general in self.general:
- gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
- if specific:
- spec_xml = ET.Element('{%s}%s' % (self.namespace, specific))
- if specific in self.specific:
- gen_xml.append(spec_xml)
- else:
- raise ValueError('Unknown specific activity')
- self.xml.append(gen_xml)
- else:
- raise ValueError('Unknown general activity')
-
- def get_value(self):
- general = None
- specific = None
- gen_xml = None
- for child in self.xml:
- if child.tag.startswith('{%s}' % self.namespace):
- elem_name = child.tag.split('}')[-1]
- if elem_name in self.general:
- general = elem_name
- gen_xml = child
- if gen_xml is not None:
- for child in gen_xml:
- if child.tag.startswith('{%s}' % self.namespace):
- elem_name = child.tag.split('}')[-1]
- if elem_name in self.specific:
- specific = elem_name
- return (general, specific)
-
- def del_value(self):
- curr_value = self.get_value()
- if curr_value[0]:
- self._set_sub_text(curr_value[0], '', keep=False)
diff --git a/sleekxmpp/plugins/xep_0108/user_activity.py b/sleekxmpp/plugins/xep_0108/user_activity.py
deleted file mode 100644
index 3a2f49b8..00000000
--- a/sleekxmpp/plugins/xep_0108/user_activity.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0108 import stanza, UserActivity
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0108(BasePlugin):
-
- """
- XEP-0108: User Activity
- """
-
- name = 'xep_0108'
- description = 'XEP-0108: User Activity'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace)
- self.xmpp['xep_0163'].remove_interest(UserActivity.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
-
- def publish_activity(self, general, specific=None, text=None, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Publish the user's current activity.
-
- Arguments:
- general -- The required general category of the activity.
- specific -- Optional specific activity being done as part
- of the general category.
- text -- Optional natural-language description or reason
- for the activity.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- activity = UserActivity()
- activity['value'] = (general, specific)
- activity['text'] = text
- return self.xmpp['xep_0163'].publish(activity,
- node=UserActivity.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user activity information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- activity = UserActivity()
- return self.xmpp['xep_0163'].publish(activity,
- node=UserActivity.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0115/__init__.py b/sleekxmpp/plugins/xep_0115/__init__.py
deleted file mode 100644
index 31a2c03a..00000000
--- a/sleekxmpp/plugins/xep_0115/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0115.stanza import Capabilities
-from sleekxmpp.plugins.xep_0115.static import StaticCaps
-from sleekxmpp.plugins.xep_0115.caps import XEP_0115
-
-
-register_plugin(XEP_0115)
-
-
-# Retain some backwards compatibility
-xep_0115 = XEP_0115
diff --git a/sleekxmpp/plugins/xep_0115/caps.py b/sleekxmpp/plugins/xep_0115/caps.py
deleted file mode 100644
index 41b5c52e..00000000
--- a/sleekxmpp/plugins/xep_0115/caps.py
+++ /dev/null
@@ -1,345 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import hashlib
-import base64
-import threading
-
-from sleekxmpp import __version__
-from sleekxmpp.stanza import StreamFeatures, Presence, Iq
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0115 import stanza, StaticCaps
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0115(BasePlugin):
-
- """
- XEP-0115: Entity Capabalities
- """
-
- name = 'xep_0115'
- description = 'XEP-0115: Entity Capabilities'
- dependencies = set(['xep_0030', 'xep_0128', 'xep_0004'])
- stanza = stanza
- default_config = {
- 'hash': 'sha-1',
- 'caps_node': None,
- 'broadcast': True
- }
-
- def plugin_init(self):
- self.hashes = {'sha-1': hashlib.sha1,
- 'sha1': hashlib.sha1,
- 'md5': hashlib.md5}
-
- if self.caps_node is None:
- self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__
-
- register_stanza_plugin(Presence, stanza.Capabilities)
- register_stanza_plugin(StreamFeatures, stanza.Capabilities)
-
- self._disco_ops = ['cache_caps',
- 'get_caps',
- 'assign_verstring',
- 'get_verstring',
- 'supports',
- 'has_identity']
-
- self.xmpp.register_handler(
- Callback('Entity Capabilites',
- StanzaPath('presence/caps'),
- self._handle_caps))
-
- self.xmpp.add_filter('out', self._filter_add_caps)
-
- self.xmpp.add_event_handler('entity_caps', self._process_caps,
- threaded=True)
-
- if not self.xmpp.is_component:
- self.xmpp.register_feature('caps',
- self._handle_caps_feature,
- restart=False,
- order=10010)
-
- disco = self.xmpp['xep_0030']
- self.static = StaticCaps(self.xmpp, disco.static)
-
- for op in self._disco_ops:
- self.api.register(getattr(self.static, op), op, default=True)
-
- for op in ('supports', 'has_identity'):
- self.xmpp['xep_0030'].api.register(getattr(self.static, op), op)
-
- self._run_node_handler = disco._run_node_handler
-
- disco.cache_caps = self.cache_caps
- disco.update_caps = self.update_caps
- disco.assign_verstring = self.assign_verstring
- disco.get_verstring = self.get_verstring
-
- self._processing_lock = threading.Lock()
- self._processing = set()
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
- self.xmpp.del_filter('out', self._filter_add_caps)
- self.xmpp.del_event_handler('entity_caps', self._process_caps)
- self.xmpp.remove_handler('Entity Capabilities')
- if not self.xmpp.is_component:
- self.xmpp.unregister_feature('caps', 10010)
- for op in ('supports', 'has_identity'):
- self.xmpp['xep_0030'].restore_defaults(op)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
-
- def _filter_add_caps(self, stanza):
- if not isinstance(stanza, Presence) or not self.broadcast:
- return stanza
-
- if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'):
- return stanza
-
- ver = self.get_verstring(stanza['from'])
- if ver:
- stanza['caps']['node'] = self.caps_node
- stanza['caps']['hash'] = self.hash
- stanza['caps']['ver'] = ver
- return stanza
-
- def _handle_caps(self, presence):
- if not self.xmpp.is_component:
- if presence['from'] == self.xmpp.boundjid:
- return
- self.xmpp.event('entity_caps', presence)
-
- def _handle_caps_feature(self, features):
- # We already have a method to process presence with
- # caps, so wrap things up and use that.
- p = Presence()
- p['from'] = self.xmpp.boundjid.domain
- p.append(features['caps'])
- self.xmpp.features.add('caps')
-
- self.xmpp.event('entity_caps', p)
-
- def _process_caps(self, pres):
- if not pres['caps']['hash']:
- log.debug("Received unsupported legacy caps: %s, %s, %s",
- pres['caps']['node'],
- pres['caps']['ver'],
- pres['caps']['ext'])
- self.xmpp.event('entity_caps_legacy', pres)
- return
-
- ver = pres['caps']['ver']
-
- existing_verstring = self.get_verstring(pres['from'].full)
- if str(existing_verstring) == str(ver):
- return
-
- existing_caps = self.get_caps(verstring=ver)
- if existing_caps is not None:
- self.assign_verstring(pres['from'], ver)
- return
-
- if pres['caps']['hash'] not in self.hashes:
- try:
- log.debug("Unknown caps hash: %s", pres['caps']['hash'])
- self.xmpp['xep_0030'].get_info(jid=pres['from'])
- return
- except XMPPError:
- return
-
- # Only lookup the same caps once at a time.
- with self._processing_lock:
- if ver in self._processing:
- log.debug('Already processing verstring %s' % ver)
- return
- self._processing.add(ver)
-
- log.debug("New caps verification string: %s", ver)
- try:
- node = '%s#%s' % (pres['caps']['node'], ver)
- caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
-
- if isinstance(caps, Iq):
- caps = caps['disco_info']
-
- if self._validate_caps(caps, pres['caps']['hash'],
- pres['caps']['ver']):
- self.assign_verstring(pres['from'], pres['caps']['ver'])
- except XMPPError:
- log.debug("Could not retrieve disco#info results for caps for %s", node)
-
- with self._processing_lock:
- self._processing.remove(ver)
-
- def _validate_caps(self, caps, hash, check_verstring):
- # Check Identities
- full_ids = caps.get_identities(dedupe=False)
- deduped_ids = caps.get_identities()
- if len(full_ids) != len(deduped_ids):
- log.debug("Duplicate disco identities found, invalid for caps")
- return False
-
- # Check Features
- full_features = caps.get_features(dedupe=False)
- deduped_features = caps.get_features()
- if len(full_features) != len(deduped_features):
- log.debug("Duplicate disco features found, invalid for caps")
- return False
-
- # Check Forms
- form_types = []
- deduped_form_types = set()
- for stanza in caps['substanzas']:
- if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
- log.debug("Non form extension found, ignoring for caps")
- caps.xml.remove(stanza.xml)
- continue
- if 'FORM_TYPE' in stanza['fields']:
- f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
- form_types.append(f_type)
- deduped_form_types.add(f_type)
- if len(form_types) != len(deduped_form_types):
- log.debug("Duplicated FORM_TYPE values, " + \
- "invalid for caps")
- return False
-
- if len(f_type) > 1:
- deduped_type = set(f_type)
- if len(f_type) != len(deduped_type):
- log.debug("Extra FORM_TYPE data, invalid for caps")
- return False
-
- if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
- log.debug("Field FORM_TYPE type not 'hidden', " + \
- "ignoring form for caps")
- caps.xml.remove(stanza.xml)
- else:
- log.debug("No FORM_TYPE found, ignoring form for caps")
- caps.xml.remove(stanza.xml)
-
- verstring = self.generate_verstring(caps, hash)
- if verstring != check_verstring:
- log.debug("Verification strings do not match: %s, %s" % (
- verstring, check_verstring))
- return False
-
- self.cache_caps(verstring, caps)
- return True
-
- def generate_verstring(self, info, hash):
- hash = self.hashes.get(hash, None)
- if hash is None:
- return None
-
- S = ''
-
- # Convert None to '' in the identities
- def clean_identity(id):
- return map(lambda i: i or '', id)
- identities = map(clean_identity, info['identities'])
-
- identities = sorted(('/'.join(i) for i in identities))
- features = sorted(info['features'])
-
- S += '<'.join(identities) + '<'
- S += '<'.join(features) + '<'
-
- form_types = {}
-
- for stanza in info['substanzas']:
- if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
- if 'FORM_TYPE' in stanza['fields']:
- f_type = stanza['values']['FORM_TYPE']
- if len(f_type):
- f_type = f_type[0]
- if f_type not in form_types:
- form_types[f_type] = []
- form_types[f_type].append(stanza)
-
- sorted_forms = sorted(form_types.keys())
- for f_type in sorted_forms:
- for form in form_types[f_type]:
- S += '%s<' % f_type
- fields = sorted(form['fields'].keys())
- fields.remove('FORM_TYPE')
- for field in fields:
- S += '%s<' % field
- vals = form['fields'][field].get_value(convert=False)
- if vals is None:
- S += '<'
- else:
- if not isinstance(vals, list):
- vals = [vals]
- S += '<'.join(sorted(vals)) + '<'
-
- binary = hash(S.encode('utf8')).digest()
- return base64.b64encode(binary).decode('utf-8')
-
- def update_caps(self, jid=None, node=None, preserve=False):
- try:
- info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
- if isinstance(info, Iq):
- info = info['disco_info']
- ver = self.generate_verstring(info, self.hash)
- self.xmpp['xep_0030'].set_info(
- jid=jid,
- node='%s#%s' % (self.caps_node, ver),
- info=info)
- self.cache_caps(ver, info)
- self.assign_verstring(jid, ver)
-
- if self.xmpp.session_started_event.is_set() and self.broadcast:
- if self.xmpp.is_component or preserve:
- for contact in self.xmpp.roster[jid]:
- self.xmpp.roster[jid][contact].send_last_presence()
- else:
- self.xmpp.roster[jid].send_last_presence()
- except XMPPError:
- return
-
- def get_verstring(self, jid=None):
- if jid in ('', None):
- jid = self.xmpp.boundjid.full
- if isinstance(jid, JID):
- jid = jid.full
- return self.api['get_verstring'](jid)
-
- def assign_verstring(self, jid=None, verstring=None):
- if jid in (None, ''):
- jid = self.xmpp.boundjid.full
- if isinstance(jid, JID):
- jid = jid.full
- return self.api['assign_verstring'](jid, args={
- 'verstring': verstring})
-
- def cache_caps(self, verstring=None, info=None):
- data = {'verstring': verstring, 'info': info}
- return self.api['cache_caps'](args=data)
-
- def get_caps(self, jid=None, verstring=None):
- if verstring is None:
- if jid is not None:
- verstring = self.get_verstring(jid)
- else:
- return None
- if isinstance(jid, JID):
- jid = jid.full
- data = {'verstring': verstring}
- return self.api['get_caps'](jid, args=data)
diff --git a/sleekxmpp/plugins/xep_0115/stanza.py b/sleekxmpp/plugins/xep_0115/stanza.py
deleted file mode 100644
index 3e80b5cf..00000000
--- a/sleekxmpp/plugins/xep_0115/stanza.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from __future__ import unicode_literals
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Capabilities(ElementBase):
-
- namespace = 'http://jabber.org/protocol/caps'
- name = 'c'
- plugin_attrib = 'caps'
- interfaces = set(('hash', 'node', 'ver', 'ext'))
diff --git a/sleekxmpp/plugins/xep_0115/static.py b/sleekxmpp/plugins/xep_0115/static.py
deleted file mode 100644
index f83c244c..00000000
--- a/sleekxmpp/plugins/xep_0115/static.py
+++ /dev/null
@@ -1,146 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.exceptions import IqError, IqTimeout
-
-
-log = logging.getLogger(__name__)
-
-
-class StaticCaps(object):
-
- """
- Extend the default StaticDisco implementation to provide
- support for extended identity information.
- """
-
- def __init__(self, xmpp, static):
- """
- Augment the default XEP-0030 static handler object.
-
- Arguments:
- static -- The default static XEP-0030 handler object.
- """
- self.xmpp = xmpp
- self.disco = self.xmpp['xep_0030']
- self.caps = self.xmpp['xep_0115']
- self.static = static
- self.ver_cache = {}
- self.jid_vers = {}
-
- def supports(self, jid, node, ifrom, data):
- """
- Check if a JID supports a given feature.
-
- The data parameter may provide:
- feature -- The feature to check for support.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- feature = data.get('feature', None)
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- if not feature:
- return False
-
- if node in (None, ''):
- info = self.caps.get_caps(jid)
- if info and feature in info['features']:
- return True
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- return feature in info['disco_info']['features']
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def has_identity(self, jid, node, ifrom, data):
- """
- Check if a JID has a given identity.
-
- The data parameter may provide:
- category -- The category of the identity to check.
- itype -- The type of the identity to check.
- lang -- The language of the identity to check.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- identity = (data.get('category', None),
- data.get('itype', None),
- data.get('lang', None))
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- trunc = lambda i: (i[0], i[1], i[2])
-
- if node in (None, ''):
- info = self.caps.get_caps(jid)
- if info and identity in map(trunc, info['identities']):
- return True
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- return identity in map(trunc, info['disco_info']['identities'])
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def cache_caps(self, jid, node, ifrom, data):
- with self.static.lock:
- verstring = data.get('verstring', None)
- info = data.get('info', None)
- if not verstring or not info:
- return
- self.ver_cache[verstring] = info
-
- def assign_verstring(self, jid, node, ifrom, data):
- with self.static.lock:
- if isinstance(jid, JID):
- jid = jid.full
- self.jid_vers[jid] = data.get('verstring', None)
-
- def get_verstring(self, jid, node, ifrom, data):
- with self.static.lock:
- return self.jid_vers.get(jid, None)
-
- def get_caps(self, jid, node, ifrom, data):
- with self.static.lock:
- return self.ver_cache.get(data.get('verstring', None), None)
diff --git a/sleekxmpp/plugins/xep_0118/__init__.py b/sleekxmpp/plugins/xep_0118/__init__.py
deleted file mode 100644
index 565f7844..00000000
--- a/sleekxmpp/plugins/xep_0118/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0118 import stanza
-from sleekxmpp.plugins.xep_0118.stanza import UserTune
-from sleekxmpp.plugins.xep_0118.user_tune import XEP_0118
-
-
-register_plugin(XEP_0118)
diff --git a/sleekxmpp/plugins/xep_0118/stanza.py b/sleekxmpp/plugins/xep_0118/stanza.py
deleted file mode 100644
index 3fdab284..00000000
--- a/sleekxmpp/plugins/xep_0118/stanza.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class UserTune(ElementBase):
-
- name = 'tune'
- namespace = 'http://jabber.org/protocol/tune'
- plugin_attrib = 'tune'
- interfaces = set(['artist', 'length', 'rating', 'source',
- 'title', 'track', 'uri'])
- sub_interfaces = interfaces
-
- def set_length(self, value):
- self._set_sub_text('length', str(value))
-
- def set_rating(self, value):
- self._set_sub_text('rating', str(value))
diff --git a/sleekxmpp/plugins/xep_0118/user_tune.py b/sleekxmpp/plugins/xep_0118/user_tune.py
deleted file mode 100644
index 1bb00122..00000000
--- a/sleekxmpp/plugins/xep_0118/user_tune.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0118 import stanza, UserTune
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0118(BasePlugin):
-
- """
- XEP-0118: User Tune
- """
-
- name = 'xep_0118'
- description = 'XEP-0118: User Tune'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace)
- self.xmpp['xep_0163'].remove_interest(UserTune.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
-
- def publish_tune(self, artist=None, length=None, rating=None, source=None,
- title=None, track=None, uri=None, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Publish the user's current tune.
-
- Arguments:
- artist -- The artist or performer of the song.
- length -- The length of the song in seconds.
- rating -- The user's rating of the song (from 1 to 10)
- source -- The album name, website, or other source of the song.
- title -- The title of the song.
- track -- The song's track number, or other unique identifier.
- uri -- A URL to more information about the song.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- tune = UserTune()
- tune['artist'] = artist
- tune['length'] = length
- tune['rating'] = rating
- tune['source'] = source
- tune['title'] = title
- tune['track'] = track
- tune['uri'] = uri
- return self.xmpp['xep_0163'].publish(tune,
- node=UserTune.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user tune information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- tune = UserTune()
- return self.xmpp['xep_0163'].publish(tune,
- node=UserTune.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0122/__init__.py b/sleekxmpp/plugins/xep_0122/__init__.py
deleted file mode 100644
index 4b3e9483..00000000
--- a/sleekxmpp/plugins/xep_0122/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-from sleekxmpp.plugins.base import register_plugin
-from sleekxmpp.plugins.xep_0122.stanza import FormValidation
-from sleekxmpp.plugins.xep_0122.data_validation import XEP_0122
-
-
-register_plugin(XEP_0122)
-
-
-# Retain some backwards compatibility
-xep_0122 = XEP_0122
diff --git a/sleekxmpp/plugins/xep_0122/data_validation.py b/sleekxmpp/plugins/xep_0122/data_validation.py
deleted file mode 100644
index ec2cdfcc..00000000
--- a/sleekxmpp/plugins/xep_0122/data_validation.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0004 import stanza
-from sleekxmpp.plugins.xep_0004.stanza import FormField
-from sleekxmpp.plugins.xep_0122.stanza import FormValidation
-
-
-class XEP_0122(BasePlugin):
- """
- XEP-0004: Data Forms
- """
-
- name = 'xep_0122'
- description = 'XEP-0122: Data Forms Validation'
- dependencies = set(['xep_0004'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(FormField, FormValidation)
diff --git a/sleekxmpp/plugins/xep_0122/stanza.py b/sleekxmpp/plugins/xep_0122/stanza.py
deleted file mode 100644
index bc3c177a..00000000
--- a/sleekxmpp/plugins/xep_0122/stanza.py
+++ /dev/null
@@ -1,94 +0,0 @@
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class FormValidation(ElementBase):
- """
- Validation values for form fields.
-
- Example:
-
- <field var='evt.date' type='text-single' label='Event Date/Time'>
- <validate xmlns='http://jabber.org/protocol/xdata-validate'
- datatype='xs:dateTime'/>
- <value>2003-10-06T11:22:00-07:00</value>
- </field>
-
- Questions:
- Should this look at the datatype value and convert the range values as appropriate?
- Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
- """
-
- namespace = 'http://jabber.org/protocol/xdata-validate'
- name = 'validate'
- plugin_attrib = 'validate'
- interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
- sub_interfaces = {'basic', 'open', 'range', 'regex', }
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def _add_field(self, name):
- self.remove_all()
- item_xml = ET.Element('{%s}%s' % (self.namespace, name))
- self.xml.append(item_xml)
- return item_xml
-
- def set_basic(self, value):
- if value:
- self._add_field('basic')
- else:
- del self['basic']
-
- def set_open(self, value):
- if value:
- self._add_field('open')
- else:
- del self['open']
-
- def set_regex(self, regex):
- if regex:
- _regex = self._add_field('regex')
- _regex.text = regex
- else:
- del self['regex']
-
- def set_range(self, value, minimum=None, maximum=None):
- if value:
- _range = self._add_field('range')
- _range.attrib['min'] = str(minimum)
- _range.attrib['max'] = str(maximum)
- else:
- del self['range']
-
- def remove_all(self, except_tag=None):
- for a in self.sub_interfaces:
- if a != except_tag:
- del self[a]
-
- def get_basic(self):
- present = self.xml.find('{%s}basic' % self.namespace)
- return present is not None
-
- def get_open(self):
- present = self.xml.find('{%s}open' % self.namespace)
- return present is not None
-
- def get_regex(self):
- present = self.xml.find('{%s}regex' % self.namespace)
- if present is not None:
- return present.text
-
- return False
-
- def get_range(self):
- present = self.xml.find('{%s}range' % self.namespace)
- if present is not None:
- attributes = present.attrib
- return_value = dict()
- if 'min' in attributes:
- return_value['minimum'] = attributes['min']
- if 'max' in attributes:
- return_value['maximum'] = attributes['max']
- return return_value
-
- return False
diff --git a/sleekxmpp/plugins/xep_0128/__init__.py b/sleekxmpp/plugins/xep_0128/__init__.py
deleted file mode 100644
index 27c2cc33..00000000
--- a/sleekxmpp/plugins/xep_0128/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco
-from sleekxmpp.plugins.xep_0128.extended_disco import XEP_0128
-
-
-register_plugin(XEP_0128)
-
-
-# Retain some backwards compatibility
-xep_0128 = XEP_0128
diff --git a/sleekxmpp/plugins/xep_0128/extended_disco.py b/sleekxmpp/plugins/xep_0128/extended_disco.py
deleted file mode 100644
index d785affe..00000000
--- a/sleekxmpp/plugins/xep_0128/extended_disco.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0004 import Form
-from sleekxmpp.plugins.xep_0030 import DiscoInfo
-from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco
-
-
-class XEP_0128(BasePlugin):
-
- """
- XEP-0128: Service Discovery Extensions
-
- Allow the use of data forms to add additional identity
- information to disco#info results.
-
- Also see <http://www.xmpp.org/extensions/xep-0128.html>.
-
- Attributes:
- disco -- A reference to the XEP-0030 plugin.
- static -- Object containing the default set of static
- node handlers.
- xmpp -- The main SleekXMPP object.
-
- Methods:
- set_extended_info -- Set extensions to a disco#info result.
- add_extended_info -- Add an extension to a disco#info result.
- del_extended_info -- Remove all extensions from a disco#info result.
- """
-
- name = 'xep_0128'
- description = 'XEP-0128: Service Discovery Extensions'
- dependencies = set(['xep_0030', 'xep_0004'])
-
- def plugin_init(self):
- """Start the XEP-0128 plugin."""
- self._disco_ops = ['set_extended_info',
- 'add_extended_info',
- 'del_extended_info']
-
- register_stanza_plugin(DiscoInfo, Form, iterable=True)
-
- self.disco = self.xmpp['xep_0030']
- self.static = StaticExtendedDisco(self.disco.static)
-
- self.disco.set_extended_info = self.set_extended_info
- self.disco.add_extended_info = self.add_extended_info
- self.disco.del_extended_info = self.del_extended_info
-
- for op in self._disco_ops:
- self.api.register(getattr(self.static, op), op, default=True)
-
- def set_extended_info(self, jid=None, node=None, **kwargs):
- """
- Set additional, extended identity information to a node.
-
- Replaces any existing extended information.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- data -- Either a form, or a list of forms to use
- as extended information, replacing any
- existing extensions.
- """
- self.api['set_extended_info'](jid, node, None, kwargs)
-
- def add_extended_info(self, jid=None, node=None, **kwargs):
- """
- Add additional, extended identity information to a node.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- data -- Either a form, or a list of forms to add
- as extended information.
- """
- self.api['add_extended_info'](jid, node, None, kwargs)
-
- def del_extended_info(self, jid=None, node=None, **kwargs):
- """
- Remove all extended identity information to a node.
-
- Arguments:
- jid -- The JID to modify.
- node -- The node to modify.
- """
- self.api['del_extended_info'](jid, node, None, kwargs)
diff --git a/sleekxmpp/plugins/xep_0128/static.py b/sleekxmpp/plugins/xep_0128/static.py
deleted file mode 100644
index 427011c0..00000000
--- a/sleekxmpp/plugins/xep_0128/static.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.plugins.xep_0030 import StaticDisco
-
-
-log = logging.getLogger(__name__)
-
-
-class StaticExtendedDisco(object):
-
- """
- Extend the default StaticDisco implementation to provide
- support for extended identity information.
- """
-
- def __init__(self, static):
- """
- Augment the default XEP-0030 static handler object.
-
- Arguments:
- static -- The default static XEP-0030 handler object.
- """
- self.static = static
-
- def set_extended_info(self, jid, node, ifrom, data):
- """
- Replace the extended identity data for a JID/node combination.
-
- The data parameter may provide:
- data -- Either a single data form, or a list of data forms.
- """
- with self.static.lock:
- self.del_extended_info(jid, node, ifrom, data)
- self.add_extended_info(jid, node, ifrom, data)
-
- def add_extended_info(self, jid, node, ifrom, data):
- """
- Add additional extended identity data for a JID/node combination.
-
- The data parameter may provide:
- data -- Either a single data form, or a list of data forms.
- """
- with self.static.lock:
- self.static.add_node(jid, node)
-
- forms = data.get('data', [])
- if not isinstance(forms, list):
- forms = [forms]
-
- info = self.static.get_node(jid, node)['info']
- for form in forms:
- info.append(form)
-
- def del_extended_info(self, jid, node, ifrom, data):
- """
- Replace the extended identity data for a JID/node combination.
-
- The data parameter is not used.
- """
- with self.static.lock:
- if self.static.node_exists(jid, node):
- info = self.static.get_node(jid, node)['info']
- for form in info['substanza']:
- info.xml.remove(form.xml)
diff --git a/sleekxmpp/plugins/xep_0131/__init__.py b/sleekxmpp/plugins/xep_0131/__init__.py
deleted file mode 100644
index ec71c98d..00000000
--- a/sleekxmpp/plugins/xep_0131/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0131 import stanza
-from sleekxmpp.plugins.xep_0131.stanza import Headers
-from sleekxmpp.plugins.xep_0131.headers import XEP_0131
-
-
-register_plugin(XEP_0131)
diff --git a/sleekxmpp/plugins/xep_0131/headers.py b/sleekxmpp/plugins/xep_0131/headers.py
deleted file mode 100644
index 3e47541a..00000000
--- a/sleekxmpp/plugins/xep_0131/headers.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Message, Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0131 import stanza
-from sleekxmpp.plugins.xep_0131.stanza import Headers
-
-
-class XEP_0131(BasePlugin):
-
- name = 'xep_0131'
- description = 'XEP-0131: Stanza Headers and Internet Metadata'
- dependencies = set(['xep_0030'])
- stanza = stanza
- default_config = {
- 'supported_headers': set()
- }
-
- def plugin_init(self):
- register_stanza_plugin(Message, Headers)
- register_stanza_plugin(Presence, Headers)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Headers.namespace)
- for header in self.supported_headers:
- self.xmpp['xep_0030'].del_feature(
- feature='%s#%s' % (Headers.namespace, header))
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Headers.namespace)
- for header in self.supported_headers:
- self.xmpp['xep_0030'].add_feature('%s#%s' % (
- Headers.namespace,
- header))
diff --git a/sleekxmpp/plugins/xep_0131/stanza.py b/sleekxmpp/plugins/xep_0131/stanza.py
deleted file mode 100644
index 347adf96..00000000
--- a/sleekxmpp/plugins/xep_0131/stanza.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.thirdparty import OrderedDict
-from sleekxmpp.xmlstream import ET, ElementBase
-
-
-class Headers(ElementBase):
- name = 'headers'
- namespace = 'http://jabber.org/protocol/shim'
- plugin_attrib = 'headers'
- interfaces = set(['headers'])
- is_extension = True
-
- def get_headers(self):
- result = OrderedDict()
- headers = self.xml.findall('{%s}header' % self.namespace)
- for header in headers:
- name = header.attrib.get('name', '')
- value = header.text
- if name in result:
- if not isinstance(result[name], set):
- result[name] = [result[name]]
- else:
- result[name] = []
- result[name].add(value)
- else:
- result[name] = value
- return result
-
- def set_headers(self, values):
- self.del_headers()
- for name in values:
- vals = values[name]
- if not isinstance(vals, (list, set)):
- vals = [values[name]]
- for value in vals:
- header = ET.Element('{%s}header' % self.namespace)
- header.attrib['name'] = name
- header.text = value
- self.xml.append(header)
-
- def del_headers(self):
- headers = self.xml.findall('{%s}header' % self.namespace)
- for header in headers:
- self.xml.remove(header)
diff --git a/sleekxmpp/plugins/xep_0133.py b/sleekxmpp/plugins/xep_0133.py
deleted file mode 100644
index 7bbe4c3c..00000000
--- a/sleekxmpp/plugins/xep_0133.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-
-class XEP_0133(BasePlugin):
-
- name = 'xep_0133'
- description = 'XEP-0133: Service Administration'
- dependencies = set(['xep_0030', 'xep_0004', 'xep_0050'])
- commands = set(['add-user', 'delete-user', 'disable-user',
- 'reenable-user', 'end-user-session', 'get-user-password',
- 'change-user-password', 'get-user-roster',
- 'get-user-lastlogin', 'user-stats', 'edit-blacklist',
- 'edit-whitelist', 'get-registered-users-num',
- 'get-disabled-users-num', 'get-online-users-num',
- 'get-active-users-num', 'get-idle-users-num',
- 'get-registered-users-list', 'get-disabled-users-list',
- 'get-online-users-list', 'get-online-users',
- 'get-active-users', 'get-idle-userslist', 'announce',
- 'set-motd', 'edit-motd', 'delete-motd', 'set-welcome',
- 'delete-welcome', 'edit-admin', 'restart', 'shutdown'])
-
- def get_commands(self, jid=None, **kwargs):
- if jid is None:
- jid = self.xmpp.boundjid.server
- return self.xmpp['xep_0050'].get_commands(jid, **kwargs)
-
-
-def create_command(name):
- def admin_command(self, jid=None, session=None, ifrom=None, block=False):
- if jid is None:
- jid = self.xmpp.boundjid.server
- self.xmpp['xep_0050'].start_command(
- jid=jid,
- node='http://jabber.org/protocol/admin#%s' % name,
- session=session,
- ifrom=ifrom,
- block=block)
- return admin_command
-
-
-for cmd in XEP_0133.commands:
- setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd))
-
-
-register_plugin(XEP_0133)
diff --git a/sleekxmpp/plugins/xep_0138.py b/sleekxmpp/plugins/xep_0138.py
deleted file mode 100644
index c5d8f06f..00000000
--- a/sleekxmpp/plugins/xep_0138.py
+++ /dev/null
@@ -1,148 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import socket
-import zlib
-
-from sleekxmpp.thirdparty.suelta.util import bytes
-
-
-from sleekxmpp.stanza import StreamFeatures
-from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
-from sleekxmpp.xmlstream.matcher import *
-from sleekxmpp.xmlstream.handler import *
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-log = logging.getLogger(__name__)
-
-
-class Compression(ElementBase):
- name = 'compression'
- namespace = 'http://jabber.org/features/compress'
- interfaces = set(('methods',))
- plugin_attrib = 'compression'
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- def get_methods(self):
- methods = []
- for method in self.xml.findall('{%s}method' % self.namespace):
- methods.append(method.text)
- return methods
-
-
-class Compress(StanzaBase):
- name = 'compress'
- namespace = 'http://jabber.org/protocol/compress'
- interfaces = set(('method',))
- sub_interfaces = interfaces
- plugin_attrib = 'compress'
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
-
-class Compressed(StanzaBase):
- name = 'compressed'
- namespace = 'http://jabber.org/protocol/compress'
- interfaces = set()
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
-
-
-
-class ZlibSocket(object):
-
- def __init__(self, socketobj):
- self.__socket = socketobj
- self.compressor = zlib.compressobj()
- self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)
-
- def __getattr__(self, name):
- return getattr(self.__socket, name)
-
- def send(self, data):
- sentlen = len(data)
- data = self.compressor.compress(data)
- data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
- log.debug(b'>>> (compressed)' + (data.encode("hex")))
- #return self.__socket.send(data)
- sentactuallen = self.__socket.send(data)
- assert(sentactuallen == len(data))
-
- return sentlen
-
- def recv(self, *args, **kwargs):
- data = self.__socket.recv(*args, **kwargs)
- log.debug(b'<<< (compressed)' + data.encode("hex"))
- return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)
-
-
-class XEP_0138(BasePlugin):
- """
- XEP-0138: Compression
- """
- name = "xep_0138"
- description = "XEP-0138: Compression"
- dependencies = set(["xep_0030"])
-
- def plugin_init(self):
- self.xep = '0138'
- self.description = 'Stream Compression (Generic)'
-
- self.compression_methods = {'zlib': True}
-
- register_stanza_plugin(StreamFeatures, Compression)
- self.xmpp.register_stanza(Compress)
- self.xmpp.register_stanza(Compressed)
-
- self.xmpp.register_handler(
- Callback('Compressed',
- StanzaPath('compressed'),
- self._handle_compressed,
- instream=True))
-
- self.xmpp.register_feature('compression',
- self._handle_compression,
- restart=True,
- order=self.config.get('order', 5))
-
- def register_compression_method(self, name, handler):
- self.compression_methods[name] = handler
-
- def _handle_compression(self, features):
- for method in features['compression']['methods']:
- if method in self.compression_methods:
- log.info('Attempting to use %s compression' % method)
- c = Compress(self.xmpp)
- c['method'] = method
- c.send(now=True)
- return True
- return False
-
- def _handle_compressed(self, stanza):
- self.xmpp.features.add('compression')
- log.debug('Stream Compressed!')
- compressed_socket = ZlibSocket(self.xmpp.socket)
- self.xmpp.set_socket(compressed_socket)
- raise RestartStream()
-
- def _handle_failure(self, stanza):
- pass
-
-xep_0138 = XEP_0138
-register_plugin(XEP_0138)
diff --git a/sleekxmpp/plugins/xep_0152/__init__.py b/sleekxmpp/plugins/xep_0152/__init__.py
deleted file mode 100644
index 7de031b7..00000000
--- a/sleekxmpp/plugins/xep_0152/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0152 import stanza
-from sleekxmpp.plugins.xep_0152.stanza import Reachability
-from sleekxmpp.plugins.xep_0152.reachability import XEP_0152
-
-
-register_plugin(XEP_0152)
diff --git a/sleekxmpp/plugins/xep_0152/reachability.py b/sleekxmpp/plugins/xep_0152/reachability.py
deleted file mode 100644
index 4cf81739..00000000
--- a/sleekxmpp/plugins/xep_0152/reachability.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0152 import stanza, Reachability
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0152(BasePlugin):
-
- """
- XEP-0152: Reachability Addresses
- """
-
- name = 'xep_0152'
- description = 'XEP-0152: Reachability Addresses'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace)
- self.xmpp['xep_0163'].remove_interest(Reachability.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('reachability', Reachability)
-
- def publish_reachability(self, addresses, options=None,
- ifrom=None, block=True, callback=None, timeout=None):
- """
- Publish alternative addresses where the user can be reached.
-
- Arguments:
- addresses -- A list of dictionaries containing the URI and
- optional description for each address.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if not isinstance(addresses, (list, tuple)):
- addresses = [addresses]
- reach = Reachability()
- for address in addresses:
- if not hasattr(address, 'items'):
- address = {'uri': address}
-
- addr = stanza.Address()
- for key, val in address.items():
- addr[key] = val
- reach.append(addr)
- return self.xmpp['xep_0163'].publish(reach,
- node=Reachability.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user activity information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- reach = Reachability()
- return self.xmpp['xep_0163'].publish(reach,
- node=Reachability.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0152/stanza.py b/sleekxmpp/plugins/xep_0152/stanza.py
deleted file mode 100644
index bd173ce1..00000000
--- a/sleekxmpp/plugins/xep_0152/stanza.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class Reachability(ElementBase):
- name = 'reach'
- namespace = 'urn:xmpp:reach:0'
- plugin_attrib = 'reach'
- interfaces = set()
-
-
-class Address(ElementBase):
- name = 'addr'
- namespace = 'urn:xmpp:reach:0'
- plugin_attrib = 'address'
- plugin_multi_attrib = 'addresses'
- interfaces = set(['uri', 'desc'])
- lang_interfaces = set(['desc'])
- sub_interfaces = set(['desc'])
-
-
-register_stanza_plugin(Reachability, Address, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0153/__init__.py b/sleekxmpp/plugins/xep_0153/__init__.py
deleted file mode 100644
index f52b7712..00000000
--- a/sleekxmpp/plugins/xep_0153/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0153.stanza import VCardTempUpdate
-from sleekxmpp.plugins.xep_0153.vcard_avatar import XEP_0153
-
-
-register_plugin(XEP_0153)
diff --git a/sleekxmpp/plugins/xep_0153/stanza.py b/sleekxmpp/plugins/xep_0153/stanza.py
deleted file mode 100644
index 4e6a660f..00000000
--- a/sleekxmpp/plugins/xep_0153/stanza.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class VCardTempUpdate(ElementBase):
- name = 'x'
- namespace = 'vcard-temp:x:update'
- plugin_attrib = 'vcard_temp_update'
- interfaces = set(['photo'])
- sub_interfaces = interfaces
-
- def set_photo(self, value):
- if value is not None:
- self._set_sub_text('photo', value, keep=True)
- else:
- self._del_sub('photo')
-
- def get_photo(self):
- photo = self.xml.find('{%s}photo' % self.namespace)
- if photo is None:
- return None
- return photo.text
diff --git a/sleekxmpp/plugins/xep_0153/vcard_avatar.py b/sleekxmpp/plugins/xep_0153/vcard_avatar.py
deleted file mode 100644
index ec1ae782..00000000
--- a/sleekxmpp/plugins/xep_0153/vcard_avatar.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import hashlib
-import logging
-import threading
-
-from sleekxmpp.stanza import Presence
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0153(BasePlugin):
-
- name = 'xep_0153'
- description = 'XEP-0153: vCard-Based Avatars'
- dependencies = set(['xep_0054'])
- stanza = stanza
-
- def plugin_init(self):
- self._hashes = {}
-
- self._allow_advertising = threading.Event()
-
- register_stanza_plugin(Presence, VCardTempUpdate)
-
- self.xmpp.add_filter('out', self._update_presence)
-
- self.xmpp.add_event_handler('session_start', self._start)
- self.xmpp.add_event_handler('session_end', self._end)
-
- self.xmpp.add_event_handler('presence_available', self._recv_presence)
- self.xmpp.add_event_handler('presence_dnd', self._recv_presence)
- self.xmpp.add_event_handler('presence_xa', self._recv_presence)
- self.xmpp.add_event_handler('presence_chat', self._recv_presence)
- self.xmpp.add_event_handler('presence_away', self._recv_presence)
-
- self.api.register(self._set_hash, 'set_hash', default=True)
- self.api.register(self._get_hash, 'get_hash', default=True)
- self.api.register(self._reset_hash, 'reset_hash', default=True)
-
- def plugin_end(self):
- self.xmpp.del_filter('out', self._update_presence)
- self.xmpp.del_event_handler('session_start', self._start)
- self.xmpp.del_event_handler('session_end', self._end)
- self.xmpp.del_event_handler('presence_available', self._recv_presence)
- self.xmpp.del_event_handler('presence_dnd', self._recv_presence)
- self.xmpp.del_event_handler('presence_xa', self._recv_presence)
- self.xmpp.del_event_handler('presence_chat', self._recv_presence)
- self.xmpp.del_event_handler('presence_away', self._recv_presence)
-
- def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
- timeout=None, callback=None):
- if jid is None:
- jid = self.xmpp.boundjid.bare
-
- vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
- vcard = vcard['vcard_temp']
- vcard['PHOTO']['TYPE'] = mtype
- vcard['PHOTO']['BINVAL'] = avatar
-
- self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard)
-
- self.api['reset_hash'](jid)
- self.xmpp.roster[jid].send_last_presence()
-
- def _start(self, event):
- try:
- vcard = self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
- data = vcard['vcard_temp']['PHOTO']['BINVAL']
- if not data:
- new_hash = ''
- else:
- new_hash = hashlib.sha1(data).hexdigest()
- self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
- self._allow_advertising.set()
- except XMPPError:
- log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare)
-
- def _end(self, event):
- self._allow_advertising.clear()
-
- def _update_presence(self, stanza):
- if not isinstance(stanza, Presence):
- return stanza
-
- if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'):
- return stanza
-
- current_hash = self.api['get_hash'](stanza['from'])
- stanza['vcard_temp_update']['photo'] = current_hash
- return stanza
-
- def _reset_hash(self, jid, node, ifrom, args):
- own_jid = (jid.bare == self.xmpp.boundjid.bare)
- if self.xmpp.is_component:
- own_jid = (jid.domain == self.xmpp.boundjid.domain)
-
- self.api['set_hash'](jid, args=None)
- if own_jid:
- self.xmpp.roster[jid].send_last_presence()
-
- try:
- iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
-
- data = iq['vcard_temp']['PHOTO']['BINVAL']
- if not data:
- new_hash = ''
- else:
- new_hash = hashlib.sha1(data).hexdigest()
-
- self.api['set_hash'](jid, args=new_hash)
- except XMPPError:
- log.debug('Could not retrieve vCard for %s' % jid)
-
- def _recv_presence(self, pres):
- try:
- if pres['muc']['affiliation']:
- # Don't process vCard avatars for MUC occupants
- # since they all share the same bare JID.
- return
- except: pass
-
- if not pres.match('presence/vcard_temp_update'):
- self.api['set_hash'](pres['from'], args=None)
- return
-
- data = pres['vcard_temp_update']['photo']
- if data is None:
- return
- elif data == '' or data != self.api['get_hash'](pres['from']):
- ifrom = pres['to'] if self.xmpp.is_component else None
- self.api['reset_hash'](pres['from'], ifrom=ifrom)
- self.xmpp.event('vcard_avatar_update', pres)
-
- # =================================================================
-
- def _get_hash(self, jid, node, ifrom, args):
- return self._hashes.get(jid.bare, None)
-
- def _set_hash(self, jid, node, ifrom, args):
- self._hashes[jid.bare] = args
diff --git a/sleekxmpp/plugins/xep_0163.py b/sleekxmpp/plugins/xep_0163.py
deleted file mode 100644
index 2d1a63b7..00000000
--- a/sleekxmpp/plugins/xep_0163.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin, register_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0163(BasePlugin):
-
- """
- XEP-0163: Personal Eventing Protocol (PEP)
- """
-
- name = 'xep_0163'
- description = 'XEP-0163: Personal Eventing Protocol (PEP)'
- dependencies = set(['xep_0030', 'xep_0060', 'xep_0115'])
-
- def register_pep(self, name, stanza):
- """
- Setup and configure events and stanza registration for
- the given PEP stanza:
-
- - Add disco feature for the PEP content.
- - Register disco interest in the PEP content.
- - Map events from the PEP content's namespace to the given name.
-
- :param str name: The event name prefix to use for PEP events.
- :param stanza: The stanza class for the PEP content.
- """
- pubsub_stanza = self.xmpp['xep_0060'].stanza
- register_stanza_plugin(pubsub_stanza.EventItem, stanza)
-
- self.add_interest(stanza.namespace)
- self.xmpp['xep_0030'].add_feature(stanza.namespace)
- self.xmpp['xep_0060'].map_node_event(stanza.namespace, name)
-
- def add_interest(self, namespace, jid=None):
- """
- Mark an interest in a PEP subscription by including a disco
- feature with the '+notify' extension.
-
- Arguments:
- namespace -- The base namespace to register as an interest, such
- as 'http://jabber.org/protocol/tune'. This may also
- be a list of such namespaces.
- jid -- Optionally specify the JID.
- """
- if not isinstance(namespace, set) and not isinstance(namespace, list):
- namespace = [namespace]
-
- for ns in namespace:
- self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
- jid=jid)
- self.xmpp['xep_0115'].update_caps(jid)
-
- def remove_interest(self, namespace, jid=None):
- """
- Mark an interest in a PEP subscription by including a disco
- feature with the '+notify' extension.
-
- Arguments:
- namespace -- The base namespace to remove as an interest, such
- as 'http://jabber.org/protocol/tune'. This may also
- be a list of such namespaces.
- jid -- Optionally specify the JID.
- """
- if not isinstance(namespace, (set, list)):
- namespace = [namespace]
-
- for ns in namespace:
- self.xmpp['xep_0030'].del_feature(jid=jid,
- feature='%s+notify' % namespace)
- self.xmpp['xep_0115'].update_caps(jid)
-
- def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
- block=True, callback=None, timeout=None):
- """
- Publish a PEP update.
-
- This is just a (very) thin wrapper around the XEP-0060 publish()
- method to set the defaults expected by PEP.
-
- Arguments:
- stanza -- The PEP update stanza to publish.
- node -- The node to publish the item to. If not specified,
- the stanza's namespace will be used.
- id -- Optionally specify the ID of the item.
- options -- A form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if node is None:
- node = stanza.namespace
- if id is None:
- id = 'current'
-
- return self.xmpp['xep_0060'].publish(ifrom, node,
- id=id,
- payload=stanza.xml,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
-
-register_plugin(XEP_0163)
diff --git a/sleekxmpp/plugins/xep_0172/__init__.py b/sleekxmpp/plugins/xep_0172/__init__.py
deleted file mode 100644
index aa7b9f72..00000000
--- a/sleekxmpp/plugins/xep_0172/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0172 import stanza
-from sleekxmpp.plugins.xep_0172.stanza import UserNick
-from sleekxmpp.plugins.xep_0172.user_nick import XEP_0172
-
-
-register_plugin(XEP_0172)
diff --git a/sleekxmpp/plugins/xep_0172/stanza.py b/sleekxmpp/plugins/xep_0172/stanza.py
deleted file mode 100644
index 110c237b..00000000
--- a/sleekxmpp/plugins/xep_0172/stanza.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class UserNick(ElementBase):
-
- """
- XEP-0172: User Nickname allows the addition of a <nick> element
- in several stanza types, including <message> and <presence> stanzas.
-
- The nickname contained in a <nick> should be the global, friendly or
- informal name chosen by the owner of a bare JID. The <nick> element
- may be included when establishing communications with new entities,
- such as normal XMPP users or MUC services.
-
- The nickname contained in a <nick> element will not necessarily be
- the same as the nickname used in a MUC.
-
- Example stanzas:
- <message to="user@example.com">
- <nick xmlns="http://jabber.org/nick/nick">The User</nick>
- <body>...</body>
- </message>
-
- <presence to="otheruser@example.com" type="subscribe">
- <nick xmlns="http://jabber.org/nick/nick">The User</nick>
- </presence>
-
- Stanza Interface:
- nick -- A global, friendly or informal name chosen by a user.
-
- Methods:
- setup -- Overrides ElementBase.setup.
- get_nick -- Return the nickname in the <nick> element.
- set_nick -- Add a <nick> element with the given nickname.
- del_nick -- Remove the <nick> element.
- """
-
- namespace = 'http://jabber.org/protocol/nick'
- name = 'nick'
- plugin_attrib = name
- interfaces = set(('nick',))
-
- def set_nick(self, nick):
- """
- Add a <nick> element with the given nickname.
-
- Arguments:
- nick -- A human readable, informal name.
- """
- self.xml.text = nick
-
- def get_nick(self):
- """Return the nickname in the <nick> element."""
- return self.xml.text
-
- def del_nick(self):
- """Remove the <nick> element."""
- if self.parent is not None:
- self.parent().xml.remove(self.xml)
diff --git a/sleekxmpp/plugins/xep_0172/user_nick.py b/sleekxmpp/plugins/xep_0172/user_nick.py
deleted file mode 100644
index cab13c15..00000000
--- a/sleekxmpp/plugins/xep_0172/user_nick.py
+++ /dev/null
@@ -1,92 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza.message import Message
-from sleekxmpp.stanza.presence import Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0172 import stanza, UserNick
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0172(BasePlugin):
-
- """
- XEP-0172: User Nickname
- """
-
- name = 'xep_0172'
- description = 'XEP-0172: User Nickname'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, UserNick)
- register_stanza_plugin(Presence, UserNick)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace)
- self.xmpp['xep_0163'].remove_interest(UserNick.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_nick', UserNick)
-
- def publish_nick(self, nick=None, options=None, ifrom=None, block=True,
- callback=None, timeout=None):
- """
- Publish the user's current nick.
-
- Arguments:
- nick -- The user nickname to publish.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- nickname = UserNick()
- nickname['nick'] = nick
- return self.xmpp['xep_0163'].publish(nickname,
- node=UserNick.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user nick information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- nick = UserNick()
- return self.xmpp['xep_0163'].publish(nick,
- node=UserNick.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0184/__init__.py b/sleekxmpp/plugins/xep_0184/__init__.py
deleted file mode 100644
index 4b129b6b..00000000
--- a/sleekxmpp/plugins/xep_0184/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0184.stanza import Request, Received
-from sleekxmpp.plugins.xep_0184.receipt import XEP_0184
-
-
-register_plugin(XEP_0184)
-
-
-# Retain some backwards compatibility
-xep_0184 = XEP_0184
diff --git a/sleekxmpp/plugins/xep_0184/receipt.py b/sleekxmpp/plugins/xep_0184/receipt.py
deleted file mode 100644
index 3e97d8db..00000000
--- a/sleekxmpp/plugins/xep_0184/receipt.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0184 import stanza, Request, Received
-
-
-class XEP_0184(BasePlugin):
-
- """
- XEP-0184: Message Delivery Receipts
- """
-
- name = 'xep_0184'
- description = 'XEP-0184: Message Delivery Receipts'
- dependencies = set(['xep_0030'])
- stanza = stanza
- default_config = {
- 'auto_ack': True,
- 'auto_request': False
- }
-
- ack_types = ('normal', 'chat', 'headline')
-
- def plugin_init(self):
- register_stanza_plugin(Message, Request)
- register_stanza_plugin(Message, Received)
-
- self.xmpp.add_filter('out', self._filter_add_receipt_request)
-
- self.xmpp.register_handler(
- Callback('Message Receipt',
- StanzaPath('message/receipt'),
- self._handle_receipt_received))
-
- self.xmpp.register_handler(
- Callback('Message Receipt Request',
- StanzaPath('message/request_receipt'),
- self._handle_receipt_request))
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts')
- self.xmpp.del_filter('out', self._filter_add_receipt_request)
- self.xmpp.remove_handler('Message Receipt')
- self.xmpp.remove_handler('Message Receipt Request')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts')
-
- def ack(self, msg):
- """
- Acknowledge a message by sending a receipt.
-
- Arguments:
- msg -- The message to acknowledge.
- """
- ack = self.xmpp.Message()
- ack['to'] = msg['from']
- ack['from'] = msg['to']
- ack['receipt'] = msg['id']
- ack['id'] = msg['id']
- ack.send()
-
- def _handle_receipt_received(self, msg):
- self.xmpp.event('receipt_received', msg)
-
- def _handle_receipt_request(self, msg):
- """
- Auto-ack message receipt requests if ``self.auto_ack`` is ``True``.
-
- Arguments:
- msg -- The incoming message requesting a receipt.
- """
- if self.auto_ack:
- if msg['type'] in self.ack_types:
- if not msg['receipt']:
- self.ack(msg)
-
- def _filter_add_receipt_request(self, stanza):
- """
- Auto add receipt requests to outgoing messages, if:
-
- - ``self.auto_request`` is set to ``True``
- - The message is not for groupchat
- - The message does not contain a receipt acknowledgment
- - The recipient is a bare JID or, if a full JID, one
- that has the ``urn:xmpp:receipts`` feature enabled
-
- The disco cache is checked if a full JID is specified in
- the outgoing message, which may mean a round-trip disco#info
- delay for the first message sent to the JID if entity caps
- are not used.
- """
-
- if not self.auto_request:
- return stanza
-
- if not isinstance(stanza, Message):
- return stanza
-
- if stanza['request_receipt']:
- return stanza
-
- if not stanza['type'] in self.ack_types:
- return stanza
-
- if stanza['receipt']:
- return stanza
-
- if not stanza['body']:
- return stanza
-
- if stanza['to'].resource:
- if not self.xmpp['xep_0030'].supports(stanza['to'],
- feature='urn:xmpp:receipts',
- cached=True):
- return stanza
-
- stanza['request_receipt'] = True
- return stanza
diff --git a/sleekxmpp/plugins/xep_0184/stanza.py b/sleekxmpp/plugins/xep_0184/stanza.py
deleted file mode 100644
index a7607035..00000000
--- a/sleekxmpp/plugins/xep_0184/stanza.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
-
-
-class Request(ElementBase):
- namespace = 'urn:xmpp:receipts'
- name = 'request'
- plugin_attrib = 'request_receipt'
- interfaces = set(('request_receipt',))
- sub_interfaces = interfaces
- is_extension = True
-
- def setup(self, xml=None):
- self.xml = ET.Element('')
- return True
-
- def set_request_receipt(self, val):
- self.del_request_receipt()
- if val:
- parent = self.parent()
- parent._set_sub_text("{%s}request" % self.namespace, keep=True)
- if not parent['id']:
- if parent.stream:
- parent['id'] = parent.stream.new_id()
-
- def get_request_receipt(self):
- parent = self.parent()
- if parent.find("{%s}request" % self.namespace) is not None:
- return True
- else:
- return False
-
- def del_request_receipt(self):
- self.parent()._del_sub("{%s}request" % self.namespace)
-
-
-class Received(ElementBase):
- namespace = 'urn:xmpp:receipts'
- name = 'received'
- plugin_attrib = 'receipt'
- interfaces = set(['receipt'])
- sub_interfaces = interfaces
- is_extension = True
-
- def setup(self, xml=None):
- self.xml = ET.Element('')
- return True
-
- def set_receipt(self, value):
- self.del_receipt()
- if value:
- parent = self.parent()
- xml = ET.Element("{%s}received" % self.namespace)
- xml.attrib['id'] = value
- parent.append(xml)
-
- def get_receipt(self):
- parent = self.parent()
- xml = parent.find("{%s}received" % self.namespace)
- if xml is not None:
- return xml.attrib.get('id', '')
- return ''
-
- def del_receipt(self):
- self.parent()._del_sub('{%s}received' % self.namespace)
diff --git a/sleekxmpp/plugins/xep_0186/__init__.py b/sleekxmpp/plugins/xep_0186/__init__.py
deleted file mode 100644
index c9b8c6b9..00000000
--- a/sleekxmpp/plugins/xep_0186/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0186 import stanza
-from sleekxmpp.plugins.xep_0186.stanza import Invisible, Visible
-from sleekxmpp.plugins.xep_0186.invisible_command import XEP_0186
-
-
-register_plugin(XEP_0186)
diff --git a/sleekxmpp/plugins/xep_0186/invisible_command.py b/sleekxmpp/plugins/xep_0186/invisible_command.py
deleted file mode 100644
index 15f63b2d..00000000
--- a/sleekxmpp/plugins/xep_0186/invisible_command.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0186 import stanza, Visible, Invisible
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0186(BasePlugin):
-
- name = 'xep_0186'
- description = 'XEP-0186: Invisible Command'
- dependencies = set(['xep_0030'])
-
- def plugin_init(self):
- register_stanza_plugin(Iq, Visible)
- register_stanza_plugin(Iq, Invisible)
-
- def set_invisible(self, ifrom=None, block=True, callback=None,
- timeout=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq.enable('invisible')
- iq.send(block=block, callback=callback, timeout=timeout)
-
- def set_visible(self, ifrom=None, block=True, callback=None,
- timeout=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq.enable('visible')
- iq.send(block=block, callback=callback, timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0186/stanza.py b/sleekxmpp/plugins/xep_0186/stanza.py
deleted file mode 100644
index aadbaa16..00000000
--- a/sleekxmpp/plugins/xep_0186/stanza.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Invisible(ElementBase):
- name = 'invisible'
- namespace = 'urn:xmpp:invisible:0'
- plugin_attrib = 'invisible'
- interfaces = set()
-
-
-class Visible(ElementBase):
- name = 'visible'
- namespace = 'urn:xmpp:visible:0'
- plugin_attrib = 'visible'
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0191/__init__.py b/sleekxmpp/plugins/xep_0191/__init__.py
deleted file mode 100644
index 934ac631..00000000
--- a/sleekxmpp/plugins/xep_0191/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList
-from sleekxmpp.plugins.xep_0191.blocking import XEP_0191
-
-
-register_plugin(XEP_0191)
diff --git a/sleekxmpp/plugins/xep_0191/blocking.py b/sleekxmpp/plugins/xep_0191/blocking.py
deleted file mode 100644
index 57632319..00000000
--- a/sleekxmpp/plugins/xep_0191/blocking.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0191(BasePlugin):
-
- name = 'xep_0191'
- description = 'XEP-0191: Blocking Command'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, BlockList)
- register_stanza_plugin(Iq, Block)
- register_stanza_plugin(Iq, Unblock)
-
- self.xmpp.register_handler(
- Callback('Blocked Contact',
- StanzaPath('iq@type=set/block'),
- self._handle_blocked))
-
- self.xmpp.register_handler(
- Callback('Unblocked Contact',
- StanzaPath('iq@type=set/unblock'),
- self._handle_unblocked))
-
- def plugin_end(self):
- self.xmpp.remove_handler('Blocked Contact')
- self.xmpp.remove_handler('Unblocked Contact')
-
- def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
- iq.enable('blocklist')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def block(self, jids, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
-
- if not isinstance(jids, (set, list)):
- jids = [jids]
-
- iq['block']['items'] = jids
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
-
- if jids is None:
- jids = []
- if not isinstance(jids, (set, list)):
- jids = [jids]
-
- iq['unblock']['items'] = jids
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def _handle_blocked(self, iq):
- self.xmpp.event('blocked', iq)
-
- def _handle_unblocked(self, iq):
- self.xmpp.event('unblocked', iq)
diff --git a/sleekxmpp/plugins/xep_0191/stanza.py b/sleekxmpp/plugins/xep_0191/stanza.py
deleted file mode 100644
index c5a284bd..00000000
--- a/sleekxmpp/plugins/xep_0191/stanza.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET, ElementBase, JID
-
-
-class BlockList(ElementBase):
- name = 'blocklist'
- namespace = 'urn:xmpp:blocking'
- plugin_attrib = 'blocklist'
- interfaces = set(['items'])
-
- def get_items(self):
- result = set()
- items = self.xml.findall('{%s}item' % self.namespace)
- if items is not None:
- for item in items:
- jid = JID(item.attrib.get('jid', ''))
- if jid:
- result.add(jid)
- return result
-
- def set_items(self, values):
- self.del_items()
- for jid in values:
- if jid:
- item = ET.Element('{%s}item' % self.namespace)
- item.attrib['jid'] = JID(jid).full
- self.xml.append(item)
-
- def del_items(self):
- items = self.xml.findall('{%s}item' % self.namespace)
- if items is not None:
- for item in items:
- self.xml.remove(item)
-
-
-class Block(BlockList):
- name = 'block'
- plugin_attrib = 'block'
-
-
-class Unblock(BlockList):
- name = 'unblock'
- plugin_attrib = 'unblock'
diff --git a/sleekxmpp/plugins/xep_0196/__init__.py b/sleekxmpp/plugins/xep_0196/__init__.py
deleted file mode 100644
index 7aeaf6c9..00000000
--- a/sleekxmpp/plugins/xep_0196/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0196 import stanza
-from sleekxmpp.plugins.xep_0196.stanza import UserGaming
-from sleekxmpp.plugins.xep_0196.user_gaming import XEP_0196
-
-
-register_plugin(XEP_0196)
diff --git a/sleekxmpp/plugins/xep_0196/stanza.py b/sleekxmpp/plugins/xep_0196/stanza.py
deleted file mode 100644
index 571c89d7..00000000
--- a/sleekxmpp/plugins/xep_0196/stanza.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class UserGaming(ElementBase):
-
- name = 'gaming'
- namespace = 'urn:xmpp:gaming:0'
- plugin_attrib = 'gaming'
- interfaces = set(['character_name', 'character_profile', 'name',
- 'level', 'server_address', 'server_name', 'uri'])
- sub_interfaces = interfaces
-
diff --git a/sleekxmpp/plugins/xep_0196/user_gaming.py b/sleekxmpp/plugins/xep_0196/user_gaming.py
deleted file mode 100644
index e78f1acc..00000000
--- a/sleekxmpp/plugins/xep_0196/user_gaming.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0196 import stanza, UserGaming
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0196(BasePlugin):
-
- """
- XEP-0196: User Gaming
- """
-
- name = 'xep_0196'
- description = 'XEP-0196: User Gaming'
- dependencies = set(['xep_0163'])
- stanza = stanza
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=UserGaming.namespace)
- self.xmpp['xep_0163'].remove_interest(UserGaming.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0163'].register_pep('user_gaming', UserGaming)
-
- def publish_gaming(self, name=None, level=None, server_name=None, uri=None,
- character_name=None, character_profile=None, server_address=None,
- options=None, ifrom=None, block=True, callback=None, timeout=None):
- """
- Publish the user's current gaming status.
-
- Arguments:
- name -- The name of the game.
- level -- The user's level in the game.
- uri -- A URI for the game or relevant gaming service
- server_name -- The name of the server where the user is playing.
- server_address -- The hostname or IP address of the server where the
- user is playing.
- character_name -- The name of the user's character in the game.
- character_profile -- A URI for a profile of the user's character.
- options -- Optional form of publish options.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- gaming = UserGaming()
- gaming['name'] = name
- gaming['level'] = level
- gaming['uri'] = uri
- gaming['character_name'] = character_name
- gaming['character_profile'] = character_profile
- gaming['server_name'] = server_name
- gaming['server_address'] = server_address
- return self.xmpp['xep_0163'].publish(gaming,
- node=UserGaming.namespace,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def stop(self, ifrom=None, block=True, callback=None, timeout=None):
- """
- Clear existing user gaming information to stop notifications.
-
- Arguments:
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- gaming = UserGaming()
- return self.xmpp['xep_0163'].publish(gaming,
- node=UserGaming.namespace,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0198/__init__.py b/sleekxmpp/plugins/xep_0198/__init__.py
deleted file mode 100644
index db930347..00000000
--- a/sleekxmpp/plugins/xep_0198/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0198.stanza import Enable, Enabled
-from sleekxmpp.plugins.xep_0198.stanza import Resume, Resumed
-from sleekxmpp.plugins.xep_0198.stanza import Failed
-from sleekxmpp.plugins.xep_0198.stanza import StreamManagement
-from sleekxmpp.plugins.xep_0198.stanza import Ack, RequestAck
-
-from sleekxmpp.plugins.xep_0198.stream_management import XEP_0198
-
-
-register_plugin(XEP_0198)
diff --git a/sleekxmpp/plugins/xep_0198/stanza.py b/sleekxmpp/plugins/xep_0198/stanza.py
deleted file mode 100644
index 6461d766..00000000
--- a/sleekxmpp/plugins/xep_0198/stanza.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import ElementBase, StanzaBase
-
-
-class Enable(StanzaBase):
- name = 'enable'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set(['max', 'resume'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_resume(self):
- return self._get_attr('resume', 'false').lower() in ('true', '1')
-
- def set_resume(self, val):
- self._del_attr('resume')
- self._set_attr('resume', 'true' if val else 'false')
-
-
-class Enabled(StanzaBase):
- name = 'enabled'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set(['id', 'location', 'max', 'resume'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_resume(self):
- return self._get_attr('resume', 'false').lower() in ('true', '1')
-
- def set_resume(self, val):
- self._del_attr('resume')
- self._set_attr('resume', 'true' if val else 'false')
-
-
-class Resume(StanzaBase):
- name = 'resume'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set(['h', 'previd'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_h(self):
- h = self._get_attr('h', None)
- if h:
- return int(h)
- return None
-
- def set_h(self, val):
- self._set_attr('h', str(val))
-
-
-class Resumed(StanzaBase):
- name = 'resumed'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set(['h', 'previd'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_h(self):
- h = self._get_attr('h', None)
- if h:
- return int(h)
- return None
-
- def set_h(self, val):
- self._set_attr('h', str(val))
-
-
-class Failed(StanzaBase, Error):
- name = 'failed'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set()
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
-
-class StreamManagement(ElementBase):
- name = 'sm'
- namespace = 'urn:xmpp:sm:3'
- plugin_attrib = name
- interfaces = set(['required', 'optional'])
-
- def get_required(self):
- return self.find('{%s}required' % self.namespace) is not None
-
- def set_required(self, val):
- self.del_required()
- if val:
- self._set_sub_text('required', '', keep=True)
-
- def del_required(self):
- self._del_sub('required')
-
- def get_optional(self):
- return self.find('{%s}optional' % self.namespace) is not None
-
- def set_optional(self, val):
- self.del_optional()
- if val:
- self._set_sub_text('optional', '', keep=True)
-
- def del_optional(self):
- self._del_sub('optional')
-
-
-class RequestAck(StanzaBase):
- name = 'r'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set()
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
-
-class Ack(StanzaBase):
- name = 'a'
- namespace = 'urn:xmpp:sm:3'
- interfaces = set(['h'])
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.xml.tag = self.tag_name()
-
- def get_h(self):
- h = self._get_attr('h', None)
- if h:
- return int(h)
- return None
-
- def set_h(self, val):
- self._set_attr('h', str(val))
diff --git a/sleekxmpp/plugins/xep_0198/stream_management.py b/sleekxmpp/plugins/xep_0198/stream_management.py
deleted file mode 100644
index 48029913..00000000
--- a/sleekxmpp/plugins/xep_0198/stream_management.py
+++ /dev/null
@@ -1,314 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import threading
-import collections
-
-from sleekxmpp.stanza import Message, Presence, Iq, StreamFeatures
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback, Waiter
-from sleekxmpp.xmlstream.matcher import MatchXPath, MatchMany
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0198 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-MAX_SEQ = 2 ** 32
-
-
-class XEP_0198(BasePlugin):
-
- """
- XEP-0198: Stream Management
- """
-
- name = 'xep_0198'
- description = 'XEP-0198: Stream Management'
- dependencies = set()
- stanza = stanza
- default_config = {
- #: The last ack number received from the server.
- 'last_ack': 0,
-
- #: The number of stanzas to wait between sending ack requests to
- #: the server. Setting this to ``1`` will send an ack request after
- #: every sent stanza. Defaults to ``5``.
- 'window': 5,
-
- #: The stream management ID for the stream. Knowing this value is
- #: required in order to do stream resumption.
- 'sm_id': None,
-
- #: A counter of handled incoming stanzas, mod 2^32.
- 'handled': 0,
-
- #: A counter of unacked outgoing stanzas, mod 2^32.
- 'seq': 0,
-
- #: Control whether or not the ability to resume the stream will be
- #: requested when enabling stream management. Defaults to ``True``.
- 'allow_resume': True,
-
- 'order': 10100,
- 'resume_order': 9000
- }
-
- def plugin_init(self):
- """Start the XEP-0198 plugin."""
-
- # Only enable stream management for non-components,
- # since components do not yet perform feature negotiation.
- if self.xmpp.is_component:
- return
-
- self.window_counter = self.window
- self.window_counter_lock = threading.Lock()
-
- self.enabled = threading.Event()
- self.unacked_queue = collections.deque()
-
- self.seq_lock = threading.Lock()
- self.handled_lock = threading.Lock()
- self.ack_lock = threading.Lock()
-
- register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
- self.xmpp.register_stanza(stanza.Enable)
- self.xmpp.register_stanza(stanza.Enabled)
- self.xmpp.register_stanza(stanza.Resume)
- self.xmpp.register_stanza(stanza.Resumed)
- self.xmpp.register_stanza(stanza.Ack)
- self.xmpp.register_stanza(stanza.RequestAck)
-
- # Only end the session when a </stream> element is sent,
- # not just because the connection has died.
- self.xmpp.end_session_on_disconnect = False
-
- # Register the feature twice because it may be ordered two
- # different ways: enabling after binding and resumption
- # before binding.
- self.xmpp.register_feature('sm',
- self._handle_sm_feature,
- restart=True,
- order=self.order)
- self.xmpp.register_feature('sm',
- self._handle_sm_feature,
- restart=True,
- order=self.resume_order)
-
- self.xmpp.register_handler(
- Callback('Stream Management Enabled',
- MatchXPath(stanza.Enabled.tag_name()),
- self._handle_enabled,
- instream=True))
-
- self.xmpp.register_handler(
- Callback('Stream Management Resumed',
- MatchXPath(stanza.Resumed.tag_name()),
- self._handle_resumed,
- instream=True))
-
- self.xmpp.register_handler(
- Callback('Stream Management Failed',
- MatchXPath(stanza.Failed.tag_name()),
- self._handle_failed,
- instream=True))
-
- self.xmpp.register_handler(
- Callback('Stream Management Ack',
- MatchXPath(stanza.Ack.tag_name()),
- self._handle_ack,
- instream=True))
-
- self.xmpp.register_handler(
- Callback('Stream Management Request Ack',
- MatchXPath(stanza.RequestAck.tag_name()),
- self._handle_request_ack,
- instream=True))
-
- self.xmpp.add_filter('in', self._handle_incoming)
- self.xmpp.add_filter('out_sync', self._handle_outgoing)
-
- self.xmpp.add_event_handler('session_end', self.session_end)
-
- def plugin_end(self):
- if self.xmpp.is_component:
- return
-
- self.xmpp.unregister_feature('sm', self.order)
- self.xmpp.unregister_feature('sm', self.resume_order)
- self.xmpp.del_event_handler('session_end', self.session_end)
- self.xmpp.del_filter('in', self._handle_incoming)
- self.xmpp.del_filter('out_sync', self._handle_outgoing)
- self.xmpp.remove_handler('Stream Management Enabled')
- self.xmpp.remove_handler('Stream Management Resumed')
- self.xmpp.remove_handler('Stream Management Failed')
- self.xmpp.remove_handler('Stream Management Ack')
- self.xmpp.remove_handler('Stream Management Request Ack')
- self.xmpp.remove_stanza(stanza.Enable)
- self.xmpp.remove_stanza(stanza.Enabled)
- self.xmpp.remove_stanza(stanza.Resume)
- self.xmpp.remove_stanza(stanza.Resumed)
- self.xmpp.remove_stanza(stanza.Ack)
- self.xmpp.remove_stanza(stanza.RequestAck)
-
- def session_end(self, event):
- """Reset stream management state."""
- self.enabled.clear()
- self.unacked_queue.clear()
- self.sm_id = None
- self.handled = 0
- self.seq = 0
- self.last_ack = 0
-
- def send_ack(self):
- """Send the current ack count to the server."""
- ack = stanza.Ack(self.xmpp)
- with self.handled_lock:
- ack['h'] = self.handled
- self.xmpp.send_raw(str(ack), now=True)
-
- def request_ack(self, e=None):
- """Request an ack from the server."""
- req = stanza.RequestAck(self.xmpp)
- self.xmpp.send_queue.put(str(req))
-
- def _handle_sm_feature(self, features):
- """
- Enable or resume stream management.
-
- If no SM-ID is stored, and resource binding has taken place,
- stream management will be enabled.
-
- If an SM-ID is known, and the server allows resumption, the
- previous stream will be resumed.
- """
- if 'stream_management' in self.xmpp.features:
- # We've already negotiated stream management,
- # so no need to do it again.
- return False
- if not self.sm_id:
- if 'bind' in self.xmpp.features:
- self.enabled.set()
- enable = stanza.Enable(self.xmpp)
- enable['resume'] = self.allow_resume
- enable.send(now=True)
- self.handled = 0
- elif self.sm_id and self.allow_resume:
- self.enabled.set()
- resume = stanza.Resume(self.xmpp)
- resume['h'] = self.handled
- resume['previd'] = self.sm_id
- resume.send(now=True)
-
- # Wait for a response before allowing stream feature processing
- # to continue. The actual result processing will be done in the
- # _handle_resumed() or _handle_failed() methods.
- waiter = Waiter('resumed_or_failed',
- MatchMany([
- MatchXPath(stanza.Resumed.tag_name()),
- MatchXPath(stanza.Failed.tag_name())]))
- self.xmpp.register_handler(waiter)
- result = waiter.wait()
- if result is not None and result.name == 'resumed':
- return True
- return False
-
- def _handle_enabled(self, stanza):
- """Save the SM-ID, if provided.
-
- Raises an :term:`sm_enabled` event.
- """
- self.xmpp.features.add('stream_management')
- if stanza['id']:
- self.sm_id = stanza['id']
- self.xmpp.event('sm_enabled', stanza)
-
- def _handle_resumed(self, stanza):
- """Finish resuming a stream by resending unacked stanzas.
-
- Raises a :term:`session_resumed` event.
- """
- self.xmpp.features.add('stream_management')
- self._handle_ack(stanza)
- for id, stanza in self.unacked_queue:
- self.xmpp.send(stanza, now=True, use_filters=False)
- self.xmpp.session_started_event.set()
- self.xmpp.event('session_resumed', stanza)
-
- def _handle_failed(self, stanza):
- """
- Disable and reset any features used since stream management was
- requested (tracked stanzas may have been sent during the interval
- between the enable request and the enabled response).
-
- Raises an :term:`sm_failed` event.
- """
- self.enabled.clear()
- self.unacked_queue.clear()
- self.xmpp.event('sm_failed', stanza)
-
- def _handle_ack(self, ack):
- """Process a server ack by freeing acked stanzas from the queue.
-
- Raises a :term:`stanza_acked` event for each acked stanza.
- """
- if ack['h'] == self.last_ack:
- return
-
- with self.ack_lock:
- num_acked = (ack['h'] - self.last_ack) % MAX_SEQ
- num_unacked = len(self.unacked_queue)
- log.debug("Ack: %s, Last Ack: %s, " + \
- "Unacked: %s, Num Acked: %s, " + \
- "Remaining: %s",
- ack['h'],
- self.last_ack,
- num_unacked,
- num_acked,
- num_unacked - num_acked)
- for x in range(num_acked):
- seq, stanza = self.unacked_queue.popleft()
- self.xmpp.event('stanza_acked', stanza)
- self.last_ack = ack['h']
-
- def _handle_request_ack(self, req):
- """Handle an ack request by sending an ack."""
- self.send_ack()
-
- def _handle_incoming(self, stanza):
- """Increment the handled counter for each inbound stanza."""
- if not self.enabled.is_set():
- return stanza
-
- if isinstance(stanza, (Message, Presence, Iq)):
- with self.handled_lock:
- # Sequence numbers are mod 2^32
- self.handled = (self.handled + 1) % MAX_SEQ
- return stanza
-
- def _handle_outgoing(self, stanza):
- """Store outgoing stanzas in a queue to be acked."""
- if not self.enabled.is_set():
- return stanza
-
- if isinstance(stanza, (Message, Presence, Iq)):
- seq = None
- with self.seq_lock:
- # Sequence numbers are mod 2^32
- self.seq = (self.seq + 1) % MAX_SEQ
- seq = self.seq
- self.unacked_queue.append((seq, stanza))
- with self.window_counter_lock:
- self.window_counter -= 1
- if self.window_counter == 0:
- self.window_counter = self.window
- self.request_ack()
- return stanza
diff --git a/sleekxmpp/plugins/xep_0199/__init__.py b/sleekxmpp/plugins/xep_0199/__init__.py
deleted file mode 100644
index 5231a5b5..00000000
--- a/sleekxmpp/plugins/xep_0199/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0199.stanza import Ping
-from sleekxmpp.plugins.xep_0199.ping import XEP_0199
-
-
-register_plugin(XEP_0199)
-
-
-# Backwards compatibility for names
-xep_0199 = XEP_0199
-xep_0199.sendPing = xep_0199.send_ping
diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py
deleted file mode 100644
index 836ff4ae..00000000
--- a/sleekxmpp/plugins/xep_0199/ping.py
+++ /dev/null
@@ -1,186 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import time
-import logging
-
-from sleekxmpp.jid import JID
-from sleekxmpp.stanza import Iq
-from sleekxmpp.exceptions import IqError, IqTimeout
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0199 import stanza, Ping
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0199(BasePlugin):
-
- """
- XEP-0199: XMPP Ping
-
- Given that XMPP is based on TCP connections, it is possible for the
- underlying connection to be terminated without the application's
- awareness. Ping stanzas provide an alternative to whitespace based
- keepalive methods for detecting lost connections.
-
- Also see <http://www.xmpp.org/extensions/xep-0199.html>.
-
- Attributes:
- keepalive -- If True, periodically send ping requests
- to the server. If a ping is not answered,
- the connection will be reset.
- interval -- Time in seconds between keepalive pings.
- Defaults to 300 seconds.
- timeout -- Time in seconds to wait for a ping response.
- Defaults to 30 seconds.
- Methods:
- send_ping -- Send a ping to a given JID, returning the
- round trip time.
- """
-
- name = 'xep_0199'
- description = 'XEP-0199: XMPP Ping'
- dependencies = set(['xep_0030'])
- stanza = stanza
- default_config = {
- 'keepalive': False,
- 'interval': 300,
- 'timeout': 30
- }
-
- def plugin_init(self):
- """
- Start the XEP-0199 plugin.
- """
-
- register_stanza_plugin(Iq, Ping)
-
- self.xmpp.register_handler(
- Callback('Ping',
- StanzaPath('iq@type=get/ping'),
- self._handle_ping))
-
- if self.keepalive:
- self.xmpp.add_event_handler('session_start',
- self.enable_keepalive,
- threaded=True)
- self.xmpp.add_event_handler('session_end',
- self.disable_keepalive)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
- self.xmpp.remove_handler('Ping')
- if self.keepalive:
- self.xmpp.del_event_handler('session_start',
- self.enable_keepalive)
- self.xmpp.del_event_handler('session_end',
- self.disable_keepalive)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Ping.namespace)
-
- def enable_keepalive(self, interval=None, timeout=None):
- if interval:
- self.interval = interval
- if timeout:
- self.timeout = timeout
-
- self.keepalive = True
- self.xmpp.schedule('Ping keepalive',
- self.interval,
- self._keepalive,
- repeat=True)
-
- def disable_keepalive(self, event=None):
- self.xmpp.scheduler.remove('Ping keepalive')
-
- def _keepalive(self, event=None):
- log.debug("Keepalive ping...")
- try:
- rtt = self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
- except IqTimeout:
- log.debug("Did not recieve ping back in time." + \
- "Requesting Reconnect.")
- self.xmpp.reconnect()
- else:
- log.debug('Keepalive RTT: %s' % rtt)
-
- def _handle_ping(self, iq):
- """Automatically reply to ping requests."""
- log.debug("Pinged by %s", iq['from'])
- iq.reply().send()
-
- def send_ping(self, jid, ifrom=None, block=True, timeout=None, callback=None):
- """Send a ping request.
-
- Arguments:
- jid -- The JID that will receive the ping.
- ifrom -- Specifiy the sender JID.
- block -- Indicate if execution should block until
- a pong response is received. Defaults
- to True.
- timeout -- Time in seconds to wait for a response.
- Defaults to self.timeout.
- callback -- Optional handler to execute when a pong
- is received. Useful in conjunction with
- the option block=False.
- """
- if not timeout:
- timeout = self.timeout
-
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = jid
- iq['from'] = ifrom
- iq.enable('ping')
-
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def ping(self, jid=None, ifrom=None, timeout=None):
- """Send a ping request and calculate RTT.
-
- Arguments:
- jid -- The JID that will receive the ping.
- ifrom -- Specifiy the sender JID.
- timeout -- Time in seconds to wait for a response.
- Defaults to self.timeout.
- """
- own_host = False
- if not jid:
- if self.xmpp.is_component:
- jid = self.xmpp.server
- else:
- jid = self.xmpp.boundjid.host
- jid = JID(jid)
- if jid == self.xmpp.boundjid.host or \
- self.xmpp.is_component and jid == self.xmpp.server:
- own_host = True
-
- if not timeout:
- timeout = self.timeout
-
- start = time.time()
-
- log.debug('Pinging %s' % jid)
- try:
- self.send_ping(jid, ifrom=ifrom, timeout=timeout)
- except IqError as e:
- if own_host:
- rtt = time.time() - start
- log.debug('Pinged %s, RTT: %s', jid, rtt)
- return rtt
- else:
- raise e
- else:
- rtt = time.time() - start
- log.debug('Pinged %s, RTT: %s', jid, rtt)
- return rtt
diff --git a/sleekxmpp/plugins/xep_0199/stanza.py b/sleekxmpp/plugins/xep_0199/stanza.py
deleted file mode 100644
index 6586a763..00000000
--- a/sleekxmpp/plugins/xep_0199/stanza.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import sleekxmpp
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Ping(ElementBase):
-
- """
- Given that XMPP is based on TCP connections, it is possible for the
- underlying connection to be terminated without the application's
- awareness. Ping stanzas provide an alternative to whitespace based
- keepalive methods for detecting lost connections.
-
- Example ping stanza:
- <iq type="get">
- <ping xmlns="urn:xmpp:ping" />
- </iq>
-
- Stanza Interface:
- None
-
- Methods:
- None
- """
-
- name = 'ping'
- namespace = 'urn:xmpp:ping'
- plugin_attrib = 'ping'
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py
deleted file mode 100644
index cdab3665..00000000
--- a/sleekxmpp/plugins/xep_0202/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0202 import stanza
-from sleekxmpp.plugins.xep_0202.stanza import EntityTime
-from sleekxmpp.plugins.xep_0202.time import XEP_0202
-
-
-register_plugin(XEP_0202)
-
-
-# Retain some backwards compatibility
-xep_0202 = XEP_0202
diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py
deleted file mode 100644
index b6ccc960..00000000
--- a/sleekxmpp/plugins/xep_0202/stanza.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import datetime as dt
-
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.plugins import xep_0082
-from sleekxmpp.thirdparty import tzutc, tzoffset
-
-
-class EntityTime(ElementBase):
-
- """
- The <time> element represents the local time for an XMPP agent.
- The time is expressed in UTC to make synchronization easier
- between entities, but the offset for the local timezone is also
- included.
-
- Example <time> stanzas:
- <iq type="result">
- <time xmlns="urn:xmpp:time">
- <utc>2011-07-03T11:37:12.234569</utc>
- <tzo>-07:00</tzo>
- </time>
- </iq>
-
- Stanza Interface:
- time -- The local time for the entity (updates utc and tzo).
- utc -- The UTC equivalent to local time.
- tzo -- The local timezone offset from UTC.
-
- Methods:
- get_time -- Return local time datetime object.
- set_time -- Set UTC and TZO fields.
- del_time -- Remove both UTC and TZO fields.
- get_utc -- Return datetime object of UTC time.
- set_utc -- Set the UTC time.
- get_tzo -- Return tzinfo object.
- set_tzo -- Set the local timezone offset.
- """
-
- name = 'time'
- namespace = 'urn:xmpp:time'
- plugin_attrib = 'entity_time'
- interfaces = set(('tzo', 'utc', 'time'))
- sub_interfaces = interfaces
-
- def set_time(self, value):
- """
- Set both the UTC and TZO fields given a time object.
-
- Arguments:
- value -- A datetime object or properly formatted
- string equivalent.
- """
- date = value
- if not isinstance(value, dt.datetime):
- date = xep_0082.parse(value)
- self['utc'] = date
- self['tzo'] = date.tzinfo
-
- def get_time(self):
- """
- Return the entity's local time based on the UTC and TZO data.
- """
- date = self['utc']
- tz = self['tzo']
- return date.astimezone(tz)
-
- def del_time(self):
- """Remove both the UTC and TZO fields."""
- del self['utc']
- del self['tzo']
-
- def get_tzo(self):
- """
- Return the timezone offset from UTC as a tzinfo object.
- """
- tzo = self._get_sub_text('tzo')
- if tzo == '':
- tzo = 'Z'
- time = xep_0082.parse('00:00:00%s' % tzo)
- return time.tzinfo
-
- def set_tzo(self, value):
- """
- Set the timezone offset from UTC.
-
- Arguments:
- value -- Either a tzinfo object or the number of
- seconds (positive or negative) to offset.
- """
- time = xep_0082.time(offset=value)
- if xep_0082.parse(time).tzinfo == tzutc():
- self._set_sub_text('tzo', 'Z')
- else:
- self._set_sub_text('tzo', time[-6:])
-
- def get_utc(self):
- """
- Return the time in UTC as a datetime object.
- """
- value = self._get_sub_text('utc')
- if value == '':
- return xep_0082.parse(xep_0082.datetime())
- return xep_0082.parse('%sZ' % value)
-
- def set_utc(self, value):
- """
- Set the time in UTC.
-
- Arguments:
- value -- A datetime object or properly formatted
- string equivalent.
- """
- date = value
- if not isinstance(value, dt.datetime):
- date = xep_0082.parse(value)
- date = date.astimezone(tzutc())
- value = xep_0082.format_datetime(date)[:-1]
- self._set_sub_text('utc', value)
diff --git a/sleekxmpp/plugins/xep_0202/time.py b/sleekxmpp/plugins/xep_0202/time.py
deleted file mode 100644
index 4e48eae8..00000000
--- a/sleekxmpp/plugins/xep_0202/time.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza.iq import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins import xep_0082
-from sleekxmpp.plugins.xep_0202 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0202(BasePlugin):
-
- """
- XEP-0202: Entity Time
- """
-
- name = 'xep_0202'
- description = 'XEP-0202: Entity Time'
- dependencies = set(['xep_0030', 'xep_0082'])
- stanza = stanza
- default_config = {
- #: As a default, respond to time requests with the
- #: local time returned by XEP-0082. However, a
- #: custom function can be supplied which accepts
- #: the JID of the entity to query for the time.
- 'local_time': None,
- 'tz_offset': 0
- }
-
- def plugin_init(self):
- """Start the XEP-0203 plugin."""
-
- if not self.local_time:
- def default_local_time(jid):
- return xep_0082.datetime(offset=self.tz_offset)
-
- self.local_time = default_local_time
-
- self.xmpp.register_handler(
- Callback('Entity Time',
- StanzaPath('iq/entity_time'),
- self._handle_time_request))
- register_stanza_plugin(Iq, stanza.EntityTime)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time')
- self.xmpp.remove_handler('Entity Time')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:time')
-
- def _handle_time_request(self, iq):
- """
- Respond to a request for the local time.
-
- The time is taken from self.local_time(), which may be replaced
- during plugin configuration with a function that maps JIDs to
- times.
-
- Arguments:
- iq -- The Iq time request stanza.
- """
- if iq['type'] == 'get':
- iq.reply()
- iq['entity_time']['time'] = self.local_time(iq['to'])
- iq.send()
-
- def get_entity_time(self, to, ifrom=None, **iqargs):
- """
- Request the time from another entity.
-
- Arguments:
- to -- JID of the entity to query.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- """
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = to
- iq['from'] = ifrom
- iq.enable('entity_time')
- return iq.send(**iqargs)
diff --git a/sleekxmpp/plugins/xep_0203/__init__.py b/sleekxmpp/plugins/xep_0203/__init__.py
deleted file mode 100644
index a95ead7e..00000000
--- a/sleekxmpp/plugins/xep_0203/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0203 import stanza
-from sleekxmpp.plugins.xep_0203.stanza import Delay
-from sleekxmpp.plugins.xep_0203.delay import XEP_0203
-
-
-register_plugin(XEP_0203)
-
-# Retain some backwards compatibility
-xep_0203 = XEP_0203
diff --git a/sleekxmpp/plugins/xep_0203/delay.py b/sleekxmpp/plugins/xep_0203/delay.py
deleted file mode 100644
index 31f31ce3..00000000
--- a/sleekxmpp/plugins/xep_0203/delay.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.stanza import Message, Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0203 import stanza
-
-
-class XEP_0203(BasePlugin):
-
- """
- XEP-0203: Delayed Delivery
-
- XMPP stanzas are sometimes withheld for delivery due to the recipient
- being offline, or are resent in order to establish recent history as
- is the case with MUCS. In any case, it is important to know when the
- stanza was originally sent, not just when it was last received.
-
- Also see <http://www.xmpp.org/extensions/xep-0203.html>.
- """
-
- name = 'xep_0203'
- description = 'XEP-0203: Delayed Delivery'
- dependencies = set()
- stanza = stanza
-
- def plugin_init(self):
- """Start the XEP-0203 plugin."""
- register_stanza_plugin(Message, stanza.Delay)
- register_stanza_plugin(Presence, stanza.Delay)
diff --git a/sleekxmpp/plugins/xep_0203/stanza.py b/sleekxmpp/plugins/xep_0203/stanza.py
deleted file mode 100644
index e147e975..00000000
--- a/sleekxmpp/plugins/xep_0203/stanza.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.plugins import xep_0082
-
-
-class Delay(ElementBase):
-
- name = 'delay'
- namespace = 'urn:xmpp:delay'
- plugin_attrib = 'delay'
- interfaces = set(('from', 'stamp', 'text'))
-
- def get_from(self):
- from_ = self._get_attr('from')
- return JID(from_) if from_ else None
-
- def set_from(self, value):
- self._set_attr('from', str(value))
-
- def get_stamp(self):
- timestamp = self._get_attr('stamp')
- return xep_0082.parse(timestamp) if timestamp else None
-
- def set_stamp(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_attr('stamp', value)
-
- def get_text(self):
- return self.xml.text
-
- def set_text(self, value):
- self.xml.text = value
-
- def del_text(self):
- self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0221/__init__.py b/sleekxmpp/plugins/xep_0221/__init__.py
deleted file mode 100644
index 91341f5b..00000000
--- a/sleekxmpp/plugins/xep_0221/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0221 import stanza
-from sleekxmpp.plugins.xep_0221.stanza import Media, URI
-from sleekxmpp.plugins.xep_0221.media import XEP_0221
-
-
-register_plugin(XEP_0221)
diff --git a/sleekxmpp/plugins/xep_0221/media.py b/sleekxmpp/plugins/xep_0221/media.py
deleted file mode 100644
index c4b4a90f..00000000
--- a/sleekxmpp/plugins/xep_0221/media.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0221 import stanza, Media, URI
-from sleekxmpp.plugins.xep_0004 import FormField
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0221(BasePlugin):
-
- name = 'xep_0221'
- description = 'XEP-0221: Data Forms Media Element'
- dependencies = set(['xep_0004'])
-
- def plugin_init(self):
- register_stanza_plugin(FormField, Media)
diff --git a/sleekxmpp/plugins/xep_0221/stanza.py b/sleekxmpp/plugins/xep_0221/stanza.py
deleted file mode 100644
index 915ab380..00000000
--- a/sleekxmpp/plugins/xep_0221/stanza.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
-
-
-class Media(ElementBase):
- name = 'media'
- namespace = 'urn:xmpp:media-element'
- plugin_attrib = 'media'
- interfaces = set(['height', 'width', 'alt'])
-
- def add_uri(self, value, itype):
- uri = URI()
- uri['value'] = value
- uri['type'] = itype
- self.append(uri)
-
-
-class URI(ElementBase):
- name = 'uri'
- namespace = 'urn:xmpp:media-element'
- plugin_attrib = 'uri'
- plugin_multi_attrib = 'uris'
- interfaces = set(['type', 'value'])
-
- def get_value(self):
- return self.xml.text
-
- def set_value(self, value):
- self.xml.text = value
-
- def del_value(self):
- sel.xml.text = ''
-
-
-register_stanza_plugin(Media, URI, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0222.py b/sleekxmpp/plugins/xep_0222.py
deleted file mode 100644
index 2cc7f703..00000000
--- a/sleekxmpp/plugins/xep_0222.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin, register_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0222(BasePlugin):
-
- """
- XEP-0222: Persistent Storage of Public Data via PubSub
- """
-
- name = 'xep_0222'
- description = 'XEP-0222: Persistent Storage of Public Data via PubSub'
- dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
-
- profile = {'pubsub#persist_items': True,
- 'pubsub#send_last_published_item': 'never'}
-
- def configure(self, node):
- """
- Update a node's configuration to match the public storage profile.
- """
- config = self.xmpp['xep_0004'].Form()
- config['type'] = 'submit'
-
- for field, value in self.profile.items():
- config.add_field(var=field, value=value)
-
- return self.xmpp['xep_0060'].set_node_config(None, node, config,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def store(self, stanza, node=None, id=None, ifrom=None, options=None,
- block=True, callback=None, timeout=None):
- """
- Store public data via PEP.
-
- This is just a (very) thin wrapper around the XEP-0060 publish()
- method to set the defaults expected by PEP.
-
- Arguments:
- stanza -- The private content to store.
- node -- The node to publish the content to. If not specified,
- the stanza's namespace will be used.
- id -- Optionally specify the ID of the item.
- options -- Publish options to use, which will be modified to
- fit the persistent storage option profile.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if not options:
- options = self.xmpp['xep_0004'].stanza.Form()
- options['type'] = 'submit'
- options.add_field(
- var='FORM_TYPE',
- ftype='hidden',
- value='http://jabber.org/protocol/pubsub#publish-options')
-
- fields = options['fields']
- for field, value in self.profile.items():
- if field not in fields:
- options.add_field(var=field)
- options['fields'][field]['value'] = value
-
- return self.xmpp['xep_0163'].publish(stanza, node,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def retrieve(self, node, id=None, item_ids=None, ifrom=None,
- block=True, callback=None, timeout=None):
- """
- Retrieve public data via PEP.
-
- This is just a (very) thin wrapper around the XEP-0060 publish()
- method to set the defaults expected by PEP.
-
- Arguments:
- node -- The node to retrieve content from.
- id -- Optionally specify the ID of the item.
- item_ids -- Specify a group of IDs. If id is also specified, it
- will be included in item_ids.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if item_ids is None:
- item_ids = []
- if id is not None:
- item_ids.append(id)
-
- return self.xmpp['xep_0060'].get_items(None, node,
- item_ids=item_ids,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
-
-register_plugin(XEP_0222)
diff --git a/sleekxmpp/plugins/xep_0223.py b/sleekxmpp/plugins/xep_0223.py
deleted file mode 100644
index abbecfc7..00000000
--- a/sleekxmpp/plugins/xep_0223.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin, register_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0223(BasePlugin):
-
- """
- XEP-0223: Persistent Storage of Private Data via PubSub
- """
-
- name = 'xep_0223'
- description = 'XEP-0223: Persistent Storage of Private Data via PubSub'
- dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
-
- profile = {'pubsub#persist_items': True,
- 'pubsub#send_last_published_item': 'never'}
-
- def configure(self, node):
- """
- Update a node's configuration to match the public storage profile.
- """
- config = self.xmpp['xep_0004'].Form()
- config['type'] = 'submit'
-
- for field, value in self.profile.items():
- config.add_field(var=field, value=value)
-
- return self.xmpp['xep_0060'].set_node_config(None, node, config,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def store(self, stanza, node=None, id=None, ifrom=None, options=None,
- block=True, callback=None, timeout=None):
- """
- Store private data via PEP.
-
- This is just a (very) thin wrapper around the XEP-0060 publish()
- method to set the defaults expected by PEP.
-
- Arguments:
- stanza -- The private content to store.
- node -- The node to publish the content to. If not specified,
- the stanza's namespace will be used.
- id -- Optionally specify the ID of the item.
- options -- Publish options to use, which will be modified to
- fit the persistent storage option profile.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if not options:
- options = self.xmpp['xep_0004'].stanza.Form()
- options['type'] = 'submit'
- options.add_field(
- var='FORM_TYPE',
- ftype='hidden',
- value='http://jabber.org/protocol/pubsub#publish-options')
-
- fields = options['fields']
- for field, value in self.profile.items():
- if field not in fields:
- options.add_field(var=field)
- options['fields'][field]['value'] = value
-
- return self.xmpp['xep_0163'].publish(stanza, node,
- options=options,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
- def retrieve(self, node, id=None, item_ids=None, ifrom=None,
- block=True, callback=None, timeout=None):
- """
- Retrieve private data via PEP.
-
- This is just a (very) thin wrapper around the XEP-0060 publish()
- method to set the defaults expected by PEP.
-
- Arguments:
- node -- The node to retrieve content from.
- id -- Optionally specify the ID of the item.
- item_ids -- Specify a group of IDs. If id is also specified, it
- will be included in item_ids.
- ifrom -- Specify the sender's JID.
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- """
- if item_ids is None:
- item_ids = []
- if id is not None:
- item_ids.append(id)
-
- return self.xmpp['xep_0060'].get_items(None, node,
- item_ids=item_ids,
- ifrom=ifrom,
- block=block,
- callback=callback,
- timeout=timeout)
-
-
-register_plugin(XEP_0223)
diff --git a/sleekxmpp/plugins/xep_0224/__init__.py b/sleekxmpp/plugins/xep_0224/__init__.py
deleted file mode 100644
index 1a9d2342..00000000
--- a/sleekxmpp/plugins/xep_0224/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0224 import stanza
-from sleekxmpp.plugins.xep_0224.stanza import Attention
-from sleekxmpp.plugins.xep_0224.attention import XEP_0224
-
-
-register_plugin(XEP_0224)
-
-
-# Retain some backwards compatibility
-xep_0224 = XEP_0224
diff --git a/sleekxmpp/plugins/xep_0224/attention.py b/sleekxmpp/plugins/xep_0224/attention.py
deleted file mode 100644
index 4e560604..00000000
--- a/sleekxmpp/plugins/xep_0224/attention.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0224 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0224(BasePlugin):
-
- """
- XEP-0224: Attention
- """
-
- name = 'xep_0224'
- description = 'XEP-0224: Attention'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- """Start the XEP-0224 plugin."""
- register_stanza_plugin(Message, stanza.Attention)
-
- self.xmpp.register_handler(
- Callback('Attention',
- StanzaPath('message/attention'),
- self._handle_attention))
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace)
- self.xmpp.remove_handler('Attention')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace)
-
- def request_attention(self, to, mfrom=None, mbody=''):
- """
- Send an attention message with an optional body.
-
- Arguments:
- to -- The attention request recipient's JID.
- mfrom -- Optionally specify the sender of the attention request.
- mbody -- An optional message body to include in the request.
- """
- m = self.xmpp.Message()
- m['to'] = to
- m['type'] = 'headline'
- m['attention'] = True
- if mfrom:
- m['from'] = mfrom
- m['body'] = mbody
- m.send()
-
- def _handle_attention(self, msg):
- """
- Raise an event after receiving a message with an attention request.
-
- Arguments:
- msg -- A message stanza with an attention element.
- """
- log.debug("Received attention request from: %s", msg['from'])
- self.xmpp.event('attention', msg)
diff --git a/sleekxmpp/plugins/xep_0224/stanza.py b/sleekxmpp/plugins/xep_0224/stanza.py
deleted file mode 100644
index f15172d9..00000000
--- a/sleekxmpp/plugins/xep_0224/stanza.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Attention(ElementBase):
-
- """
- """
-
- name = 'attention'
- namespace = 'urn:xmpp:attention:0'
- plugin_attrib = 'attention'
- interfaces = set(('attention',))
- is_extension = True
-
- def setup(self, xml):
- return True
-
- def set_attention(self, value):
- if value:
- xml = ET.Element(self.tag_name())
- self.parent().xml.append(xml)
- else:
- self.del_attention()
-
- def get_attention(self):
- xml = self.parent().xml.find(self.tag_name())
- return xml is not None
-
- def del_attention(self):
- xml = self.parent().xml.find(self.tag_name())
- if xml is not None:
- self.parent().xml.remove(xml)
diff --git a/sleekxmpp/plugins/xep_0231/__init__.py b/sleekxmpp/plugins/xep_0231/__init__.py
deleted file mode 100644
index 2861d67b..00000000
--- a/sleekxmpp/plugins/xep_0231/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz,
- Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0231.stanza import BitsOfBinary
-from sleekxmpp.plugins.xep_0231.bob import XEP_0231
-
-
-register_plugin(XEP_0231)
diff --git a/sleekxmpp/plugins/xep_0231/bob.py b/sleekxmpp/plugins/xep_0231/bob.py
deleted file mode 100644
index 5e1f590b..00000000
--- a/sleekxmpp/plugins/xep_0231/bob.py
+++ /dev/null
@@ -1,140 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz,
- Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import hashlib
-
-from sleekxmpp.stanza import Iq, Message, Presence
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0231 import stanza, BitsOfBinary
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0231(BasePlugin):
-
- """
- XEP-0231 Bits of Binary
- """
-
- name = 'xep_0231'
- description = 'XEP-0231: Bits of Binary'
- dependencies = set(['xep_0030'])
-
- def plugin_init(self):
- self._cids = {}
-
- register_stanza_plugin(Iq, BitsOfBinary)
- register_stanza_plugin(Message, BitsOfBinary)
- register_stanza_plugin(Presence, BitsOfBinary)
-
- self.xmpp.register_handler(
- Callback('Bits of Binary - Iq',
- StanzaPath('iq/bob'),
- self._handle_bob_iq))
-
- self.xmpp.register_handler(
- Callback('Bits of Binary - Message',
- StanzaPath('message/bob'),
- self._handle_bob))
-
- self.xmpp.register_handler(
- Callback('Bits of Binary - Presence',
- StanzaPath('presence/bob'),
- self._handle_bob))
-
- self.api.register(self._get_bob, 'get_bob', default=True)
- self.api.register(self._set_bob, 'set_bob', default=True)
- self.api.register(self._del_bob, 'del_bob', default=True)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob')
- self.xmpp.remove_handler('Bits of Binary - Iq')
- self.xmpp.remove_handler('Bits of Binary - Message')
- self.xmpp.remove_handler('Bits of Binary - Presence')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:bob')
-
- def set_bob(self, data, mtype, cid=None, max_age=None):
- if cid is None:
- cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()
-
- bob = BitsOfBinary()
- bob['data'] = data
- bob['type'] = mtype
- bob['cid'] = cid
- bob['max_age'] = max_age
-
- self.api['set_bob'](args=bob)
-
- return cid
-
- def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
- block=True, timeout=None, callback=None):
- if cached:
- data = self.api['get_bob'](None, None, ifrom, args=cid)
- if data is not None:
- if not isinstance(data, Iq):
- iq = self.xmpp.Iq()
- iq.append(data)
- return iq
- return data
-
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
- iq['bob']['cid'] = cid
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def del_bob(self, cid):
- self.api['del_bob'](args=cid)
-
- def _handle_bob_iq(self, iq):
- cid = iq['bob']['cid']
-
- if iq['type'] == 'result':
- self.api['set_bob'](iq['from'], None, iq['to'], args=iq['bob'])
- self.xmpp.event('bob', iq)
- elif iq['type'] == 'get':
- data = self.api['get_bob'](iq['to'], None, iq['from'], args=cid)
- if isinstance(data, Iq):
- data['id'] = iq['id']
- data.send()
- return
-
- iq.reply()
- iq.append(data)
- iq.send()
-
- def _handle_bob(self, stanza):
- self.api['set_bob'](stanza['from'], None,
- stanza['to'], args=stanza['bob'])
- self.xmpp.event('bob', stanza)
-
- # =================================================================
-
- def _set_bob(self, jid, node, ifrom, bob):
- self._cids[bob['cid']] = bob
-
- def _get_bob(self, jid, node, ifrom, cid):
- if cid in self._cids:
- return self._cids[cid]
- else:
- raise XMPPError('item-not-found')
-
- def _del_bob(self, jid, node, ifrom, cid):
- if cid in self._cids:
- del self._cids[cid]
diff --git a/sleekxmpp/plugins/xep_0231/stanza.py b/sleekxmpp/plugins/xep_0231/stanza.py
deleted file mode 100644
index 8bf0d6ee..00000000
--- a/sleekxmpp/plugins/xep_0231/stanza.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz,
- Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import base64
-
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import ElementBase
-
-
-class BitsOfBinary(ElementBase):
- name = 'data'
- namespace = 'urn:xmpp:bob'
- plugin_attrib = 'bob'
- interfaces = set(('cid', 'max_age', 'type', 'data'))
-
- def get_max_age(self):
- return self._get_attr('max-age')
-
- def set_max_age(self, value):
- self._set_attr('max-age', value)
-
- def get_data(self):
- return base64.b64decode(bytes(self.xml.text))
-
- def set_data(self, value):
- self.xml.text = bytes(base64.b64encode(value)).decode('utf-8')
-
- def del_data(self):
- self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0235/__init__.py b/sleekxmpp/plugins/xep_0235/__init__.py
deleted file mode 100644
index 29d4408a..00000000
--- a/sleekxmpp/plugins/xep_0235/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0235 import stanza
-from sleekxmpp.plugins.xep_0235.stanza import OAuth
-from sleekxmpp.plugins.xep_0235.oauth import XEP_0235
-
-
-register_plugin(XEP_0235)
diff --git a/sleekxmpp/plugins/xep_0235/oauth.py b/sleekxmpp/plugins/xep_0235/oauth.py
deleted file mode 100644
index df0e2ebf..00000000
--- a/sleekxmpp/plugins/xep_0235/oauth.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-import logging
-
-from sleekxmpp import Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0235 import stanza, OAuth
-
-
-class XEP_0235(BasePlugin):
-
- name = 'xep_0235'
- description = 'XEP-0235: OAuth Over XMPP'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, OAuth)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:oauth:0')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:oauth:0')
diff --git a/sleekxmpp/plugins/xep_0235/stanza.py b/sleekxmpp/plugins/xep_0235/stanza.py
deleted file mode 100644
index 0050d583..00000000
--- a/sleekxmpp/plugins/xep_0235/stanza.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import hmac
-import hashlib
-import urllib
-import base64
-
-from sleekxmpp.xmlstream import ET, ElementBase, JID
-
-
-class OAuth(ElementBase):
-
- name = 'oauth'
- namespace = 'urn:xmpp:oauth:0'
- plugin_attrib = 'oauth'
- interfaces = set(['oauth_consumer_key', 'oauth_nonce', 'oauth_signature',
- 'oauth_signature_method', 'oauth_timestamp',
- 'oauth_token', 'oauth_version'])
- sub_interfaces = interfaces
-
- def generate_signature(self, stanza, sfrom, sto, consumer_secret,
- token_secret, method='HMAC-SHA1'):
- self['oauth_signature_method'] = method
-
- request = urllib.quote('%s&%s' % (sfrom, sto), '')
- parameters = urllib.quote('&'.join([
- 'oauth_consumer_key=%s' % self['oauth_consumer_key'],
- 'oauth_nonce=%s' % self['oauth_nonce'],
- 'oauth_signature_method=%s' % self['oauth_signature_method'],
- 'oauth_timestamp=%s' % self['oauth_timestamp'],
- 'oauth_token=%s' % self['oauth_token'],
- 'oauth_version=%s' % self['oauth_version']
- ]), '')
-
- sigbase = '%s&%s&%s' % (stanza, request, parameters)
-
- consumer_secret = urllib.quote(consumer_secret, '')
- token_secret = urllib.quote(token_secret, '')
- key = '%s&%s' % (consumer_secret, token_secret)
-
- if method == 'HMAC-SHA1':
- sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest())
- elif method == 'PLAINTEXT':
- sig = key
-
- self['oauth_signature'] = sig
- return sig
-
- def verify_signature(self, stanza, sfrom, sto, consumer_secret,
- token_secret):
- method = self['oauth_signature_method']
-
- request = urllib.quote('%s&%s' % (sfrom, sto), '')
- parameters = urllib.quote('&'.join([
- 'oauth_consumer_key=%s' % self['oauth_consumer_key'],
- 'oauth_nonce=%s' % self['oauth_nonce'],
- 'oauth_signature_method=%s' % self['oauth_signature_method'],
- 'oauth_timestamp=%s' % self['oauth_timestamp'],
- 'oauth_token=%s' % self['oauth_token'],
- 'oauth_version=%s' % self['oauth_version']
- ]), '')
-
- sigbase = '%s&%s&%s' % (stanza, request, parameters)
-
- consumer_secret = urllib.quote(consumer_secret, '')
- token_secret = urllib.quote(token_secret, '')
- key = '%s&%s' % (consumer_secret, token_secret)
-
- if method == 'HMAC-SHA1':
- sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest())
- elif method == 'PLAINTEXT':
- sig = key
-
- return self['oauth_signature'] == sig
diff --git a/sleekxmpp/plugins/xep_0242.py b/sleekxmpp/plugins/xep_0242.py
deleted file mode 100644
index c1bada27..00000000
--- a/sleekxmpp/plugins/xep_0242.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-
-class XEP_0242(BasePlugin):
-
- name = 'xep_0242'
- description = 'XEP-0242: XMPP Client Compliance 2009'
- dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
- 'xep_0045', 'xep_0085', 'xep_0016',
- 'xep_0191'])
-
-
-register_plugin(XEP_0242)
diff --git a/sleekxmpp/plugins/xep_0249/__init__.py b/sleekxmpp/plugins/xep_0249/__init__.py
deleted file mode 100644
index b85f55ce..00000000
--- a/sleekxmpp/plugins/xep_0249/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dalek
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0249.stanza import Invite
-from sleekxmpp.plugins.xep_0249.invite import XEP_0249
-
-
-register_plugin(XEP_0249)
-
-
-# Retain some backwards compatibility
-xep_0249 = XEP_0249
diff --git a/sleekxmpp/plugins/xep_0249/invite.py b/sleekxmpp/plugins/xep_0249/invite.py
deleted file mode 100644
index 4b7abd4a..00000000
--- a/sleekxmpp/plugins/xep_0249/invite.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dalek
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp import Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0249 import Invite, stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0249(BasePlugin):
-
- """
- XEP-0249: Direct MUC Invitations
- """
-
- name = 'xep_0249'
- description = 'XEP-0249: Direct MUC Invitations'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('Direct MUC Invitations',
- StanzaPath('message/groupchat_invite'),
- self._handle_invite))
-
- register_stanza_plugin(Message, Invite)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=Invite.namespace)
- self.xmpp.remove_handler('Direct MUC Invitations')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(Invite.namespace)
-
- def _handle_invite(self, msg):
- """
- Raise an event for all invitations received.
- """
- log.debug("Received direct muc invitation from %s to room %s",
- msg['from'], msg['groupchat_invite']['jid'])
-
- self.xmpp.event('groupchat_direct_invite', msg)
-
- def send_invitation(self, jid, roomjid, password=None,
- reason=None, ifrom=None):
- """
- Send a direct MUC invitation to an XMPP entity.
-
- Arguments:
- jid -- The JID of the entity that will receive
- the invitation
- roomjid -- the address of the groupchat room to be joined
- password -- a password needed for entry into a
- password-protected room (OPTIONAL).
- reason -- a human-readable purpose for the invitation
- (OPTIONAL).
- """
-
- msg = self.xmpp.Message()
- msg['to'] = jid
- if ifrom is not None:
- msg['from'] = ifrom
- msg['groupchat_invite']['jid'] = roomjid
- if password is not None:
- msg['groupchat_invite']['password'] = password
- if reason is not None:
- msg['groupchat_invite']['reason'] = reason
-
- return msg.send()
diff --git a/sleekxmpp/plugins/xep_0249/stanza.py b/sleekxmpp/plugins/xep_0249/stanza.py
deleted file mode 100644
index ba4060d7..00000000
--- a/sleekxmpp/plugins/xep_0249/stanza.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dalek
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Invite(ElementBase):
-
- """
- XMPP allows for an agent in an MUC room to directly invite another
- user to join the chat room (as opposed to a mediated invitation
- done through the server).
-
- Example invite stanza:
- <message from='crone1@shakespeare.lit/desktop'
- to='hecate@shakespeare.lit'>
- <x xmlns='jabber:x:conference'
- jid='darkcave@macbeth.shakespeare.lit'
- password='cauldronburn'
- reason='Hey Hecate, this is the place for all good witches!'/>
- </message>
-
- Stanza Interface:
- jid -- The JID of the groupchat room
- password -- The password used to gain entry in the room
- (optional)
- reason -- The reason for the invitation (optional)
-
- """
-
- name = "x"
- namespace = "jabber:x:conference"
- plugin_attrib = "groupchat_invite"
- interfaces = ("jid", "password", "reason")
diff --git a/sleekxmpp/plugins/xep_0256.py b/sleekxmpp/plugins/xep_0256.py
deleted file mode 100644
index 0db8ea3b..00000000
--- a/sleekxmpp/plugins/xep_0256.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Presence
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-
-from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0256(BasePlugin):
-
- name = 'xep_0256'
- description = 'XEP-0256: Last Activity in Presence'
- dependencies = set(['xep_0012'])
- stanza = stanza
- default_config = {
- 'auto_last_activity': False
- }
-
- def plugin_init(self):
- register_stanza_plugin(Presence, LastActivity)
-
- self.xmpp.add_filter('out', self._initial_presence_activity)
- self.xmpp.add_event_handler('connected', self._reset_presence_activity)
-
- self._initial_presence = set()
-
- def plugin_end(self):
- self.xmpp.del_filter('out', self._initial_presence_activity)
- self.xmpp.del_event_handler('connected', self._reset_presence_activity)
-
- def _reset_presence_activity(self, e):
- self._initial_presence = set()
-
- def _initial_presence_activity(self, stanza):
- if isinstance(stanza, Presence):
- use_last_activity = False
-
- if self.auto_last_activity and stanza['show'] in ('xa', 'away'):
- use_last_activity = True
-
- if stanza['from'] not in self._initial_presence:
- self._initial_presence.add(stanza['from'])
- use_last_activity = True
-
- if use_last_activity:
- plugin = self.xmpp['xep_0012']
- try:
- result = plugin.api['get_last_activity'](stanza['from'],
- None,
- stanza['to'])
- seconds = result['last_activity']['seconds']
- except XMPPError:
- seconds = None
-
- if seconds is not None:
- stanza['last_activity']['seconds'] = seconds
- return stanza
-
-
-register_plugin(XEP_0256)
diff --git a/sleekxmpp/plugins/xep_0257/__init__.py b/sleekxmpp/plugins/xep_0257/__init__.py
deleted file mode 100644
index 8c5311fd..00000000
--- a/sleekxmpp/plugins/xep_0257/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0257 import stanza
-from sleekxmpp.plugins.xep_0257.stanza import Certs, AppendCert
-from sleekxmpp.plugins.xep_0257.stanza import DisableCert, RevokeCert
-from sleekxmpp.plugins.xep_0257.client_cert_management import XEP_0257
-
-
-register_plugin(XEP_0257)
diff --git a/sleekxmpp/plugins/xep_0257/client_cert_management.py b/sleekxmpp/plugins/xep_0257/client_cert_management.py
deleted file mode 100644
index 49317843..00000000
--- a/sleekxmpp/plugins/xep_0257/client_cert_management.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0257 import stanza, Certs
-from sleekxmpp.plugins.xep_0257 import AppendCert, DisableCert, RevokeCert
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0257(BasePlugin):
-
- name = 'xep_0257'
- description = 'XEP-0258: Client Certificate Management for SASL EXTERNAL'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, Certs)
- register_stanza_plugin(Iq, AppendCert)
- register_stanza_plugin(Iq, DisableCert)
- register_stanza_plugin(Iq, RevokeCert)
-
- def get_certs(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
- iq.enable('sasl_certs')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def add_cert(self, name, cert, allow_management=True, ifrom=None,
- block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq['sasl_cert_append']['name'] = name
- iq['sasl_cert_append']['x509cert'] = cert
- iq['sasl_cert_append']['cert_management'] = allow_management
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def disable_cert(self, name, ifrom=None, block=True,
- timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq['sasl_cert_disable']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def revoke_cert(self, name, ifrom=None, block=True,
- timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq['sasl_cert_revoke']['name'] = name
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0257/stanza.py b/sleekxmpp/plugins/xep_0257/stanza.py
deleted file mode 100644
index c3c41db2..00000000
--- a/sleekxmpp/plugins/xep_0257/stanza.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class Certs(ElementBase):
- name = 'items'
- namespace = 'urn:xmpp:saslcert:1'
- plugin_attrib = 'sasl_certs'
- interfaces = set()
-
-
-class CertItem(ElementBase):
- name = 'item'
- namespace = 'urn:xmpp:saslcert:1'
- plugin_attrib = 'item'
- plugin_multi_attrib = 'items'
- interfaces = set(['name', 'x509cert', 'users'])
- sub_interfaces = set(['name', 'x509cert'])
-
- def get_users(self):
- resources = self.xml.findall('{%s}users/{%s}resource' % (
- self.namespace, self.namespace))
- return set([res.text for res in resources])
-
- def set_users(self, values):
- users = self.xml.find('{%s}users' % self.namespace)
- if users is None:
- users = ET.Element('{%s}users' % self.namespace)
- self.xml.append(users)
- for resource in values:
- res = ET.Element('{%s}resource' % self.namespace)
- res.text = resource
- users.append(res)
-
- def del_users(self):
- users = self.xml.find('{%s}users' % self.namespace)
- if users is not None:
- self.xml.remove(users)
-
-
-class AppendCert(ElementBase):
- name = 'append'
- namespace = 'urn:xmpp:saslcert:1'
- plugin_attrib = 'sasl_cert_append'
- interfaces = set(['name', 'x509cert', 'cert_management'])
- sub_interfaces = set(['name', 'x509cert'])
-
- def get_cert_management(self):
- manage = self.xml.find('{%s}no-cert-management' % self.namespace)
- return manage is None
-
- def set_cert_management(self, value):
- self.del_cert_management()
- if not value:
- manage = ET.Element('{%s}no-cert-management' % self.namespace)
- self.xml.append(manage)
-
- def del_cert_management(self):
- manage = self.xml.find('{%s}no-cert-management' % self.namespace)
- if manage is not None:
- self.xml.remove(manage)
-
-
-class DisableCert(ElementBase):
- name = 'disable'
- namespace = 'urn:xmpp:saslcert:1'
- plugin_attrib = 'sasl_cert_disable'
- interfaces = set(['name'])
- sub_interfaces = interfaces
-
-
-class RevokeCert(ElementBase):
- name = 'revoke'
- namespace = 'urn:xmpp:saslcert:1'
- plugin_attrib = 'sasl_cert_revoke'
- interfaces = set(['name'])
- sub_interfaces = interfaces
-
-
-register_stanza_plugin(Certs, CertItem, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0258/__init__.py b/sleekxmpp/plugins/xep_0258/__init__.py
deleted file mode 100644
index 516a3706..00000000
--- a/sleekxmpp/plugins/xep_0258/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0258 import stanza
-from sleekxmpp.plugins.xep_0258.stanza import SecurityLabel, Label
-from sleekxmpp.plugins.xep_0258.stanza import DisplayMarking, EquivalentLabel
-from sleekxmpp.plugins.xep_0258.stanza import ESSLabel, Catalog, CatalogItem
-from sleekxmpp.plugins.xep_0258.security_labels import XEP_0258
-
-
-register_plugin(XEP_0258)
diff --git a/sleekxmpp/plugins/xep_0258/security_labels.py b/sleekxmpp/plugins/xep_0258/security_labels.py
deleted file mode 100644
index 439143c1..00000000
--- a/sleekxmpp/plugins/xep_0258/security_labels.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0258 import stanza, SecurityLabel, Catalog
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0258(BasePlugin):
-
- name = 'xep_0258'
- description = 'XEP-0258: Security Labels in XMPP'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, SecurityLabel)
- register_stanza_plugin(Iq, Catalog)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=SecurityLabel.namespace)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
-
- def get_catalog(self, jid, ifrom=None, block=True,
- callback=None, timeout=None):
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
- iq.enable('security_label_catalog')
- return iq.send(block=block, callback=callback, timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0258/stanza.py b/sleekxmpp/plugins/xep_0258/stanza.py
deleted file mode 100644
index a506064b..00000000
--- a/sleekxmpp/plugins/xep_0258/stanza.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from base64 import b64encode, b64decode
-
-from sleekxmpp.util import bytes
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class SecurityLabel(ElementBase):
- name = 'securitylabel'
- namespace = 'urn:xmpp:sec-label:0'
- plugin_attrib = 'security_label'
-
- def add_equivalent(self, label):
- equiv = EquivalentLabel(parent=self)
- equiv.append(label)
- return equiv
-
-
-class Label(ElementBase):
- name = 'label'
- namespace = 'urn:xmpp:sec-label:0'
- plugin_attrib = 'label'
-
-
-class DisplayMarking(ElementBase):
- name = 'displaymarking'
- namespace = 'urn:xmpp:sec-label:0'
- plugin_attrib = 'display_marking'
- interfaces = set(['fgcolor', 'bgcolor', 'value'])
-
- def get_fgcolor(self):
- return self._get_attr('fgcolor', 'black')
-
- def get_bgcolor(self):
- return self._get_attr('fgcolor', 'white')
-
- def get_value(self):
- return self.xml.text
-
- def set_value(self, value):
- self.xml.text = value
-
- def del_value(self):
- self.xml.text = ''
-
-
-class EquivalentLabel(ElementBase):
- name = 'equivalentlabel'
- namespace = 'urn:xmpp:sec-label:0'
- plugin_attrib = 'equivalent_label'
- plugin_multi_attrib = 'equivalent_labels'
-
-
-class Catalog(ElementBase):
- name = 'catalog'
- namespace = 'urn:xmpp:sec-label:catalog:2'
- plugin_attrib = 'security_label_catalog'
- interfaces = set(['to', 'from', 'name', 'desc', 'id', 'size', 'restrict'])
-
- def get_to(self):
- return JID(self._get_attr('to'))
- pass
-
- def set_to(self, value):
- return self._set_attr('to', str(value))
-
- def get_from(self):
- return JID(self._get_attr('from'))
-
- def set_from(self, value):
- return self._set_attr('from', str(value))
-
- def get_restrict(self):
- value = self._get_attr('restrict', '')
- if value and value.lower() in ('true', '1'):
- return True
- return False
-
- def set_restrict(self, value):
- self._del_attr('restrict')
- if value:
- self._set_attr('restrict', 'true')
- elif value is False:
- self._set_attr('restrict', 'false')
-
-
-class CatalogItem(ElementBase):
- name = 'catalog'
- namespace = 'urn:xmpp:sec-label:catalog:2'
- plugin_attrib = 'item'
- plugin_multi_attrib = 'items'
- interfaces = set(['selector', 'default'])
-
- def get_default(self):
- value = self._get_attr('default', '')
- if value.lower() in ('true', '1'):
- return True
- return False
-
- def set_default(self, value):
- self._del_attr('default')
- if value:
- self._set_attr('default', 'true')
- elif value is False:
- self._set_attr('default', 'false')
-
-
-class ESSLabel(ElementBase):
- name = 'esssecuritylabel'
- namespace = 'urn:xmpp:sec-label:ess:0'
- plugin_attrib = 'ess'
- interfaces = set(['value'])
-
- def get_value(self):
- if self.xml.text:
- return b64decode(bytes(self.xml.text))
- return ''
-
- def set_value(self, value):
- self.xml.text = ''
- if value:
- self.xml.text = b64encode(bytes(value))
-
- def del_value(self):
- self.xml.text = ''
-
-
-register_stanza_plugin(Catalog, CatalogItem, iterable=True)
-register_stanza_plugin(CatalogItem, SecurityLabel)
-register_stanza_plugin(EquivalentLabel, ESSLabel)
-register_stanza_plugin(Label, ESSLabel)
-register_stanza_plugin(SecurityLabel, DisplayMarking)
-register_stanza_plugin(SecurityLabel, EquivalentLabel, iterable=True)
-register_stanza_plugin(SecurityLabel, Label)
diff --git a/sleekxmpp/plugins/xep_0270.py b/sleekxmpp/plugins/xep_0270.py
deleted file mode 100644
index eadd45ad..00000000
--- a/sleekxmpp/plugins/xep_0270.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-
-class XEP_0270(BasePlugin):
-
- name = 'xep_0270'
- description = 'XEP-0270: XMPP Compliance Suites 2010'
- dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
- 'xep_0163', 'xep_0045', 'xep_0085'])
-
-
-register_plugin(XEP_0270)
diff --git a/sleekxmpp/plugins/xep_0279/__init__.py b/sleekxmpp/plugins/xep_0279/__init__.py
deleted file mode 100644
index 93db9e7c..00000000
--- a/sleekxmpp/plugins/xep_0279/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0279 import stanza
-from sleekxmpp.plugins.xep_0279.stanza import IPCheck
-from sleekxmpp.plugins.xep_0279.ipcheck import XEP_0279
-
-
-register_plugin(XEP_0279)
diff --git a/sleekxmpp/plugins/xep_0279/ipcheck.py b/sleekxmpp/plugins/xep_0279/ipcheck.py
deleted file mode 100644
index f8c167c7..00000000
--- a/sleekxmpp/plugins/xep_0279/ipcheck.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0279 import stanza, IPCheck
-
-
-class XEP_0279(BasePlugin):
-
- name = 'xep_0279'
- description = 'XEP-0279: Server IP Check'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, IPCheck)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:sic:0')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
-
- def check_ip(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
- iq.enable('ip_check')
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0279/stanza.py b/sleekxmpp/plugins/xep_0279/stanza.py
deleted file mode 100644
index 181b5957..00000000
--- a/sleekxmpp/plugins/xep_0279/stanza.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class IPCheck(ElementBase):
-
- name = 'ip'
- namespace = 'urn:xmpp:sic:0'
- plugin_attrib = 'ip_check'
- interfaces = set(['ip_check'])
- is_extension = True
-
- def get_ip_check(self):
- return self.xml.text
-
- def set_ip_check(self, value):
- if value:
- self.xml.text = value
- else:
- self.xml.text = ''
-
- def del_ip_check(self):
- self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0280/__init__.py b/sleekxmpp/plugins/xep_0280/__init__.py
deleted file mode 100644
index 929321af..00000000
--- a/sleekxmpp/plugins/xep_0280/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0280.stanza import ReceivedCarbon, SentCarbon
-from sleekxmpp.plugins.xep_0280.stanza import PrivateCarbon
-from sleekxmpp.plugins.xep_0280.stanza import CarbonEnable, CarbonDisable
-from sleekxmpp.plugins.xep_0280.carbons import XEP_0280
-
-
-register_plugin(XEP_0280)
diff --git a/sleekxmpp/plugins/xep_0280/carbons.py b/sleekxmpp/plugins/xep_0280/carbons.py
deleted file mode 100644
index 482d046a..00000000
--- a/sleekxmpp/plugins/xep_0280/carbons.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.stanza import Message, Iq
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0280 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0280(BasePlugin):
-
- """
- XEP-0280 Message Carbons
- """
-
- name = 'xep_0280'
- description = 'XEP-0280: Message Carbons'
- dependencies = set(['xep_0030', 'xep_0297'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('Carbon Received',
- StanzaPath('message/carbon_received'),
- self._handle_carbon_received))
- self.xmpp.register_handler(
- Callback('Carbon Sent',
- StanzaPath('message/carbon_sent'),
- self._handle_carbon_sent))
-
- register_stanza_plugin(Message, stanza.ReceivedCarbon)
- register_stanza_plugin(Message, stanza.SentCarbon)
- register_stanza_plugin(Message, stanza.PrivateCarbon)
- register_stanza_plugin(Iq, stanza.CarbonEnable)
- register_stanza_plugin(Iq, stanza.CarbonDisable)
-
- register_stanza_plugin(stanza.ReceivedCarbon,
- self.xmpp['xep_0297'].stanza.Forwarded)
- register_stanza_plugin(stanza.SentCarbon,
- self.xmpp['xep_0297'].stanza.Forwarded)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Carbon Received')
- self.xmpp.remove_handler('Carbon Sent')
- self.xmpp.plugin['xep_0030'].del_feature(feature='urn:xmpp:carbons:2')
-
- def session_bind(self, jid):
- self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:carbons:2')
-
- def _handle_carbon_received(self, msg):
- self.xmpp.event('carbon_received', msg)
-
- def _handle_carbon_sent(self, msg):
- self.xmpp.event('carbon_sent', msg)
-
- def enable(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq.enable('carbon_enable')
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def disable(self, ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
- iq.enable('carbon_disable')
- return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0280/stanza.py b/sleekxmpp/plugins/xep_0280/stanza.py
deleted file mode 100644
index 2f3aad86..00000000
--- a/sleekxmpp/plugins/xep_0280/stanza.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class ReceivedCarbon(ElementBase):
- name = 'received'
- namespace = 'urn:xmpp:carbons:2'
- plugin_attrib = 'carbon_received'
- interfaces = set(['carbon_received'])
- is_extension = True
-
- def get_carbon_received(self):
- return self['forwarded']['stanza']
-
- def del_carbon_received(self):
- del self['forwarded']['stanza']
-
- def set_carbon_received(self, stanza):
- self['forwarded']['stanza'] = stanza
-
-
-class SentCarbon(ElementBase):
- name = 'sent'
- namespace = 'urn:xmpp:carbons:2'
- plugin_attrib = 'carbon_sent'
- interfaces = set(['carbon_sent'])
- is_extension = True
-
- def get_carbon_sent(self):
- return self['forwarded']['stanza']
-
- def del_carbon_sent(self):
- del self['forwarded']['stanza']
-
- def set_carbon_sent(self, stanza):
- self['forwarded']['stanza'] = stanza
-
-
-class PrivateCarbon(ElementBase):
- name = 'private'
- namespace = 'urn:xmpp:carbons:2'
- plugin_attrib = 'carbon_private'
- interfaces = set()
-
-
-class CarbonEnable(ElementBase):
- name = 'enable'
- namespace = 'urn:xmpp:carbons:2'
- plugin_attrib = 'carbon_enable'
- interfaces = set()
-
-
-class CarbonDisable(ElementBase):
- name = 'disable'
- namespace = 'urn:xmpp:carbons:2'
- plugin_attrib = 'carbon_disable'
- interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0297/__init__.py b/sleekxmpp/plugins/xep_0297/__init__.py
deleted file mode 100644
index 551d9420..00000000
--- a/sleekxmpp/plugins/xep_0297/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0297 import stanza
-from sleekxmpp.plugins.xep_0297.stanza import Forwarded
-from sleekxmpp.plugins.xep_0297.forwarded import XEP_0297
-
-
-register_plugin(XEP_0297)
diff --git a/sleekxmpp/plugins/xep_0297/forwarded.py b/sleekxmpp/plugins/xep_0297/forwarded.py
deleted file mode 100644
index 95703a2d..00000000
--- a/sleekxmpp/plugins/xep_0297/forwarded.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-import logging
-
-from sleekxmpp import Iq, Message, Presence
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0297 import stanza, Forwarded
-
-
-class XEP_0297(BasePlugin):
-
- name = 'xep_0297'
- description = 'XEP-0297: Stanza Forwarding'
- dependencies = set(['xep_0030', 'xep_0203'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Message, Forwarded)
-
- # While these are marked as iterable, that is just for
- # making it easier to extract the forwarded stanza. There
- # still can be only a single forwarded stanza.
- register_stanza_plugin(Forwarded, Message, iterable=True)
- register_stanza_plugin(Forwarded, Presence, iterable=True)
- register_stanza_plugin(Forwarded, Iq, iterable=True)
-
- register_stanza_plugin(Forwarded, self.xmpp['xep_0203'].stanza.Delay)
-
- self.xmpp.register_handler(
- Callback('Forwarded Stanza',
- StanzaPath('message/forwarded'),
- self._handle_forwarded))
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:forward:0')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:forward:0')
- self.xmpp.remove_handler('Forwarded Stanza')
-
- def forward(self, stanza=None, mto=None, mbody=None, mfrom=None, delay=None):
- stanza.stream = None
-
- msg = self.xmpp.Message()
- msg['to'] = mto
- msg['from'] = mfrom
- msg['body'] = mbody
- msg['forwarded']['stanza'] = stanza
- if delay is not None:
- msg['forwarded']['delay']['stamp'] = delay
- msg.send()
-
- def _handle_forwarded(self, msg):
- self.xmpp.event('forwarded_stanza', msg)
diff --git a/sleekxmpp/plugins/xep_0297/stanza.py b/sleekxmpp/plugins/xep_0297/stanza.py
deleted file mode 100644
index 8b97accc..00000000
--- a/sleekxmpp/plugins/xep_0297/stanza.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Message, Presence, Iq
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Forwarded(ElementBase):
- name = 'forwarded'
- namespace = 'urn:xmpp:forward:0'
- plugin_attrib = 'forwarded'
- interfaces = set(['stanza'])
-
- def get_stanza(self):
- for stanza in self:
- if isinstance(stanza, (Message, Presence, Iq)):
- return stanza
- return ''
-
- def set_stanza(self, value):
- self.del_stanza()
- self.append(value)
-
- def del_stanza(self):
- found_stanzas = []
- for stanza in self:
- if isinstance(stanza, (Message, Presence, Iq)):
- found_stanzas.append(stanza)
- for stanza in found_stanzas:
- self.iterables.remove(stanza)
- self.xml.remove(stanza.xml)
diff --git a/sleekxmpp/plugins/xep_0302.py b/sleekxmpp/plugins/xep_0302.py
deleted file mode 100644
index dee60f91..00000000
--- a/sleekxmpp/plugins/xep_0302.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-
-
-class XEP_0302(BasePlugin):
-
- name = 'xep_0302'
- description = 'XEP-0302: XMPP Compliance Suites 2012'
- dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
- 'xep_0163', 'xep_0045', 'xep_0085',
- 'xep_0184', 'xep_0198'])
-
-
-register_plugin(XEP_0302)
diff --git a/sleekxmpp/plugins/xep_0308/__init__.py b/sleekxmpp/plugins/xep_0308/__init__.py
deleted file mode 100644
index a6a100ee..00000000
--- a/sleekxmpp/plugins/xep_0308/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0308.stanza import Replace
-from sleekxmpp.plugins.xep_0308.correction import XEP_0308
-
-
-register_plugin(XEP_0308)
diff --git a/sleekxmpp/plugins/xep_0308/correction.py b/sleekxmpp/plugins/xep_0308/correction.py
deleted file mode 100644
index d32b4bc4..00000000
--- a/sleekxmpp/plugins/xep_0308/correction.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0308 import stanza, Replace
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0308(BasePlugin):
-
- """
- XEP-0308 Last Message Correction
- """
-
- name = 'xep_0308'
- description = 'XEP-0308: Last Message Correction'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback('Message Correction',
- StanzaPath('message/replace'),
- self._handle_correction))
-
- register_stanza_plugin(Message, Replace)
-
- self.xmpp.use_message_ids = True
-
- def plugin_end(self):
- self.xmpp.remove_handler('Message Correction')
- self.xmpp.plugin['xep_0030'].del_feature(feature=Replace.namespace)
-
- def session_bind(self, jid):
- self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace)
-
- def _handle_correction(self, msg):
- self.xmpp.event('message_correction', msg)
diff --git a/sleekxmpp/plugins/xep_0308/stanza.py b/sleekxmpp/plugins/xep_0308/stanza.py
deleted file mode 100644
index 8f88cbc0..00000000
--- a/sleekxmpp/plugins/xep_0308/stanza.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Replace(ElementBase):
- name = 'replace'
- namespace = 'urn:xmpp:message-correct:0'
- plugin_attrib = 'replace'
- interfaces = set(['id'])
diff --git a/sleekxmpp/plugins/xep_0313/__init__.py b/sleekxmpp/plugins/xep_0313/__init__.py
deleted file mode 100644
index 8b6ed97d..00000000
--- a/sleekxmpp/plugins/xep_0313/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0313.stanza import Result, MAM, Preferences
-from sleekxmpp.plugins.xep_0313.mam import XEP_0313
-
-
-register_plugin(XEP_0313)
diff --git a/sleekxmpp/plugins/xep_0313/mam.py b/sleekxmpp/plugins/xep_0313/mam.py
deleted file mode 100644
index 4b82ca03..00000000
--- a/sleekxmpp/plugins/xep_0313/mam.py
+++ /dev/null
@@ -1,94 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import logging
-
-import sleekxmpp
-from sleekxmpp.stanza import Message, Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream.handler import Collector
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0313 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0313(BasePlugin):
-
- """
- XEP-0313 Message Archive Management
- """
-
- name = 'xep_0313'
- description = 'XEP-0313: Message Archive Management'
- dependencies = set(['xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, stanza.MAM)
- register_stanza_plugin(Iq, stanza.Preferences)
- register_stanza_plugin(Message, stanza.Result)
- register_stanza_plugin(Message, stanza.Archived, iterable=True)
- register_stanza_plugin(stanza.Result, self.xmpp['xep_0297'].stanza.Forwarded)
- register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
-
- def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
- block=True, timeout=None, callback=None, iterator=False):
- iq = self.xmpp.Iq()
- query_id = iq['id']
-
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
- iq['mam']['queryid'] = query_id
- iq['mam']['start'] = start
- iq['mam']['end'] = end
- iq['mam']['with'] = with_jid
-
- collector = Collector(
- 'MAM_Results_%s' % query_id,
- StanzaPath('message/mam_result@queryid=%s' % query_id))
- self.xmpp.register_handler(collector)
-
- if iterator:
- return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results')
- elif not block and callback is not None:
- def wrapped_cb(iq):
- results = collector.stop()
- if iq['type'] == 'result':
- iq['mam']['results'] = results
- callback(iq)
- return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
- else:
- try:
- resp = iq.send(block=block, timeout=timeout, callback=callback)
- resp['mam']['results'] = collector.stop()
- return resp
- except XMPPError as e:
- collector.stop()
- raise e
-
- def set_preferences(self, jid=None, default=None, always=None, never=None,
- ifrom=None, block=True, timeout=None, callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['to'] = jid
- iq['from'] = ifrom
- iq['mam_prefs']['default'] = default
- iq['mam_prefs']['always'] = always
- iq['mam_prefs']['never'] = never
- return iq.send(block=block, timeout=timeout, callback=callback)
-
- def get_configuration_commands(self, jid, **kwargs):
- return self.xmpp['xep_0030'].get_items(
- jid=jid,
- node='urn:xmpp:mam#configure',
- **kwargs)
diff --git a/sleekxmpp/plugins/xep_0313/stanza.py b/sleekxmpp/plugins/xep_0313/stanza.py
deleted file mode 100644
index 81576cd4..00000000
--- a/sleekxmpp/plugins/xep_0313/stanza.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permissio
-"""
-
-import datetime as dt
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream import ElementBase, ET
-from sleekxmpp.plugins import xep_0082
-
-
-class MAM(ElementBase):
- name = 'query'
- namespace = 'urn:xmpp:mam:tmp'
- plugin_attrib = 'mam'
- interfaces = set(['queryid', 'start', 'end', 'with', 'results'])
- sub_interfaces = set(['start', 'end', 'with'])
-
- def setup(self, xml=None):
- ElementBase.setup(self, xml)
- self._results = []
-
- def get_start(self):
- timestamp = self._get_sub_text('start')
- return xep_0082.parse(timestamp)
-
- def set_start(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_sub_text('start', value)
-
- def get_end(self):
- timestamp = self._get_sub_text('end')
- return xep_0082.parse(timestamp)
-
- def set_end(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_sub_text('end', value)
-
- def get_with(self):
- return JID(self._get_sub_text('with'))
-
- def set_with(self, value):
- self._set_sub_text('with', str(value))
-
- # The results interface is meant only as an easy
- # way to access the set of collected message responses
- # from the query.
-
- def get_results(self):
- return self._results
-
- def set_results(self, values):
- self._results = values
-
- def del_results(self):
- self._results = []
-
-
-class Preferences(ElementBase):
- name = 'prefs'
- namespace = 'urn:xmpp:mam:tmp'
- plugin_attrib = 'mam_prefs'
- interfaces = set(['default', 'always', 'never'])
- sub_interfaces = set(['always', 'never'])
-
- def get_always(self):
- results = set()
-
- jids = self.xml.findall('{%s}always/{%s}jid' % (
- self.namespace, self.namespace))
-
- for jid in jids:
- results.add(JID(jid.text))
-
- return results
-
- def set_always(self, value):
- self._set_sub_text('always', '', keep=True)
- always = self.xml.find('{%s}always' % self.namespace)
- always.clear()
-
- if not isinstance(value, (list, set)):
- value = [value]
-
- for jid in value:
- jid_xml = ET.Element('{%s}jid' % self.namespace)
- jid_xml.text = str(jid)
- always.append(jid_xml)
-
- def get_never(self):
- results = set()
-
- jids = self.xml.findall('{%s}never/{%s}jid' % (
- self.namespace, self.namespace))
-
- for jid in jids:
- results.add(JID(jid.text))
-
- return results
-
- def set_never(self, value):
- self._set_sub_text('never', '', keep=True)
- never = self.xml.find('{%s}never' % self.namespace)
- never.clear()
-
- if not isinstance(value, (list, set)):
- value = [value]
-
- for jid in value:
- jid_xml = ET.Element('{%s}jid' % self.namespace)
- jid_xml.text = str(jid)
- never.append(jid_xml)
-
-
-class Result(ElementBase):
- name = 'result'
- namespace = 'urn:xmpp:mam:tmp'
- plugin_attrib = 'mam_result'
- interfaces = set(['queryid', 'id'])
-
-
-class Archived(ElementBase):
- name = 'archived'
- namespace = 'urn:xmpp:mam:tmp'
- plugin_attrib = 'mam_archived'
- plugin_multi_attrib = 'mam_archives'
- interfaces = set(['by', 'id'])
-
- def get_by(self):
- return JID(self._get_attr('by'))
-
- def set_by(self):
- return self._set_attr('by', str(value))
diff --git a/sleekxmpp/plugins/xep_0319/__init__.py b/sleekxmpp/plugins/xep_0319/__init__.py
deleted file mode 100644
index 4756e63e..00000000
--- a/sleekxmpp/plugins/xep_0319/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0319 import stanza
-from sleekxmpp.plugins.xep_0319.stanza import Idle
-from sleekxmpp.plugins.xep_0319.idle import XEP_0319
-
-
-register_plugin(XEP_0319)
diff --git a/sleekxmpp/plugins/xep_0319/idle.py b/sleekxmpp/plugins/xep_0319/idle.py
deleted file mode 100644
index 90456f9f..00000000
--- a/sleekxmpp/plugins/xep_0319/idle.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from datetime import datetime, timedelta
-
-from sleekxmpp.stanza import Presence
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0319 import stanza
-
-
-class XEP_0319(BasePlugin):
- name = 'xep_0319'
- description = 'XEP-0319: Last User Interaction in Presence'
- dependencies = set(['xep_0012'])
- stanza = stanza
-
- def plugin_init(self):
- self._idle_stamps = {}
- register_stanza_plugin(Presence, stanza.Idle)
- self.api.register(self._set_idle,
- 'set_idle',
- default=True)
- self.api.register(self._get_idle,
- 'get_idle',
- default=True)
- self.xmpp.register_handler(
- Callback('Idle Presence',
- StanzaPath('presence/idle'),
- self._idle_presence))
- self.xmpp.add_filter('out', self._stamp_idle_presence)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:idle:1')
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:idle:1')
- self.xmpp.del_filter('out', self._stamp_idle_presence)
- self.xmpp.remove_handler('Idle Presence')
-
- def idle(self, jid=None, since=None):
- seconds = None
- if since is None:
- since = datetime.now()
- else:
- seconds = datetime.now() - since
- self.api['set_idle'](jid, None, None, since)
- self.xmpp['xep_0012'].set_last_activity(jid=jid, seconds=seconds)
-
- def active(self, jid=None):
- self.api['set_idle'](jid, None, None, None)
- self.xmpp['xep_0012'].del_last_activity(jid)
-
- def _set_idle(self, jid, node, ifrom, data):
- self._idle_stamps[jid] = data
-
- def _get_idle(self, jid, node, ifrom, data):
- return self._idle_stamps.get(jid, None)
-
- def _idle_presence(self, pres):
- self.xmpp.event('presence_idle', pres)
-
- def _stamp_idle_presence(self, stanza):
- if isinstance(stanza, Presence):
- since = self.api['get_idle'](stanza['from'] or self.xmpp.boundjid)
- if since:
- stanza['idle']['since'] = since
- return stanza
diff --git a/sleekxmpp/plugins/xep_0319/stanza.py b/sleekxmpp/plugins/xep_0319/stanza.py
deleted file mode 100644
index abfb4f41..00000000
--- a/sleekxmpp/plugins/xep_0319/stanza.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime as dt
-
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.plugins import xep_0082
-
-
-class Idle(ElementBase):
- name = 'idle'
- namespace = 'urn:xmpp:idle:1'
- plugin_attrib = 'idle'
- interfaces = set(['since'])
-
- def get_since(self):
- timestamp = self._get_attr('since')
- return xep_0082.parse(timestamp)
-
- def set_since(self, value):
- if isinstance(value, dt.datetime):
- value = xep_0082.format_datetime(value)
- self._set_attr('since', value)
diff --git a/sleekxmpp/plugins/xep_0323/__init__.py b/sleekxmpp/plugins/xep_0323/__init__.py
deleted file mode 100644
index 10779ada..00000000
--- a/sleekxmpp/plugins/xep_0323/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0323.sensordata import XEP_0323
-from sleekxmpp.plugins.xep_0323 import stanza
-
-register_plugin(XEP_0323)
-
-xep_0323=XEP_0323
diff --git a/sleekxmpp/plugins/xep_0323/device.py b/sleekxmpp/plugins/xep_0323/device.py
deleted file mode 100644
index 80e6fd95..00000000
--- a/sleekxmpp/plugins/xep_0323/device.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime
-import logging
-
-class Device(object):
- """
- Example implementation of a device readout object.
- Is registered in the XEP_0323.register_node call
- The device object may be any custom implementation to support
- specific devices, but it must implement the functions:
- has_field
- request_fields
- """
-
- def __init__(self, nodeId, fields=None):
- if not fields:
- fields = {}
-
- self.nodeId = nodeId
- self.fields = fields # see fields described below
- # {'type':'numeric',
- # 'name':'myname',
- # 'value': 42,
- # 'unit':'Z'}];
- self.timestamp_data = {}
- self.momentary_data = {}
- self.momentary_timestamp = ""
- logging.debug("Device object started nodeId %s",nodeId)
-
- def has_field(self, field):
- """
- Returns true if the supplied field name exists in this device.
-
- Arguments:
- field -- The field name
- """
- if field in self.fields.keys():
- return True
- return False
-
- def refresh(self, fields):
- """
- override method to do the refresh work
- refresh values from hardware or other
- """
- pass
-
-
- def request_fields(self, fields, flags, session, callback):
- """
- Starts a data readout. Verifies the requested fields,
- refreshes the data (if needed) and calls the callback
- with requested data.
-
-
- Arguments:
- fields -- List of field names to readout
- flags -- [optional] data classifier flags for the field, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- session -- Session id, only used in the callback as identifier
- callback -- Callback function to call when data is available.
-
- The callback function must support the following arguments:
-
- session -- Session id, as supplied in the request_fields call
- nodeId -- Identifier for this device
- result -- The current result status of the readout. Valid values are:
- "error" - Readout failed.
- "fields" - Contains readout data.
- "done" - Indicates that the readout is complete. May contain
- readout data.
- timestamp_block -- [optional] Only applies when result != "error"
- The readout data. Structured as a dictionary:
- {
- timestamp: timestamp for this datablock,
- fields: list of field dictionary (one per readout field).
- readout field dictionary format:
- {
- type: The field type (numeric, boolean, dateTime, timeSpan, string, enum)
- name: The field name
- value: The field value
- unit: The unit of the field. Only applies to type numeric.
- dataType: The datatype of the field. Only applies to type enum.
- flags: [optional] data classifier flags for the field, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- }
- }
- error_msg -- [optional] Only applies when result == "error".
- Error details when a request failed.
-
- """
- logging.debug("request_fields called looking for fields %s",fields)
- if len(fields) > 0:
- # Check availiability
- for f in fields:
- if f not in self.fields.keys():
- self._send_reject(session, callback)
- return False
- else:
- # Request all fields
- fields = self.fields.keys()
-
-
- # Refresh data from device
- # ...
- logging.debug("about to refresh device fields %s",fields)
- self.refresh(fields)
-
- if "momentary" in flags and flags['momentary'] == "true" or \
- "all" in flags and flags['all'] == "true":
- ts_block = {}
- timestamp = ""
-
- if len(self.momentary_timestamp) > 0:
- timestamp = self.momentary_timestamp
- else:
- timestamp = self._get_timestamp()
-
- field_block = []
- for f in self.momentary_data:
- if f in fields:
- field_block.append({"name": f,
- "type": self.fields[f]["type"],
- "unit": self.fields[f]["unit"],
- "dataType": self.fields[f]["dataType"],
- "value": self.momentary_data[f]["value"],
- "flags": self.momentary_data[f]["flags"]})
- ts_block["timestamp"] = timestamp
- ts_block["fields"] = field_block
-
- callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block)
- return
-
- from_flag = self._datetime_flag_parser(flags, 'from')
- to_flag = self._datetime_flag_parser(flags, 'to')
-
- for ts in sorted(self.timestamp_data.keys()):
- tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S")
- if not from_flag is None:
- if tsdt < from_flag:
- #print (str(tsdt) + " < " + str(from_flag))
- continue
- if not to_flag is None:
- if tsdt > to_flag:
- #print (str(tsdt) + " > " + str(to_flag))
- continue
-
- ts_block = {}
- field_block = []
-
- for f in self.timestamp_data[ts]:
- if f in fields:
- field_block.append({"name": f,
- "type": self.fields[f]["type"],
- "unit": self.fields[f]["unit"],
- "dataType": self.fields[f]["dataType"],
- "value": self.timestamp_data[ts][f]["value"],
- "flags": self.timestamp_data[ts][f]["flags"]})
-
- ts_block["timestamp"] = ts
- ts_block["fields"] = field_block
- callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block)
- callback(session, result="done", nodeId=self.nodeId, timestamp_block=None)
-
- def _datetime_flag_parser(self, flags, flagname):
- if not flagname in flags:
- return None
-
- dt = None
- try:
- dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S")
- except ValueError:
- # Badly formatted datetime, ignore it
- pass
- return dt
-
-
- def _get_timestamp(self):
- """
- Generates a properly formatted timestamp of current time
- """
- return datetime.datetime.now().replace(microsecond=0).isoformat()
-
- def _send_reject(self, session, callback):
- """
- Sends a reject to the caller
-
- Arguments:
- session -- Session id, see definition in request_fields function
- callback -- Callback function, see definition in request_fields function
- """
- callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject")
-
- def _add_field(self, name, typename, unit=None, dataType=None):
- """
- Adds a field to the device
-
- Arguments:
- name -- Name of the field
- typename -- Type of the field (numeric, boolean, dateTime, timeSpan, string, enum)
- unit -- [optional] only applies to "numeric". Unit for the field.
- dataType -- [optional] only applies to "enum". Datatype for the field.
- """
- self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}
-
- def _add_field_timestamp_data(self, name, timestamp, value, flags=None):
- """
- Adds timestamped data to a field
-
- Arguments:
- name -- Name of the field
- timestamp -- Timestamp for the data (string)
- value -- Field value at the timestamp
- flags -- [optional] data classifier flags for the field, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- """
- if not name in self.fields.keys():
- return False
- if not timestamp in self.timestamp_data:
- self.timestamp_data[timestamp] = {}
-
- self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}
- return True
-
- def _add_field_momentary_data(self, name, value, flags=None):
- """
- Sets momentary data to a field
-
- Arguments:
- name -- Name of the field
- value -- Field value at the timestamp
- flags -- [optional] data classifier flags for the field, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- """
- if name not in self.fields:
- return False
- if flags is None:
- flags = {}
-
- flags["momentary"] = "true"
- self.momentary_data[name] = {"value": value, "flags": flags}
- return True
-
- def _set_momentary_timestamp(self, timestamp):
- """
- This function is only for unit testing to produce predictable results.
- """
- self.momentary_timestamp = timestamp
-
diff --git a/sleekxmpp/plugins/xep_0323/sensordata.py b/sleekxmpp/plugins/xep_0323/sensordata.py
deleted file mode 100644
index a3d4cf34..00000000
--- a/sleekxmpp/plugins/xep_0323/sensordata.py
+++ /dev/null
@@ -1,712 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import time
-import datetime
-from threading import Thread, Lock, Timer
-
-from sleekxmpp.plugins.xep_0323.timerreset import TimerReset
-
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0323 import stanza
-from sleekxmpp.plugins.xep_0323.stanza import Sensordata
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0323(BasePlugin):
-
- """
- XEP-0323: IoT Sensor Data
-
-
- This XEP provides the underlying architecture, basic operations and data
- structures for sensor data communication over XMPP networks. It includes
- a hardware abstraction model, removing any technical detail implemented
- in underlying technologies.
-
- Also see <http://xmpp.org/extensions/xep-0323.html>
-
- Configuration Values:
- threaded -- Indicates if communication with sensors should be threaded.
- Defaults to True.
-
- Events:
- Sensor side
- -----------
- Sensordata Event:Req -- Received a request for data
- Sensordata Event:Cancel -- Received a cancellation for a request
-
- Client side
- -----------
- Sensordata Event:Accepted -- Received a accept from sensor for a request
- Sensordata Event:Rejected -- Received a reject from sensor for a request
- Sensordata Event:Cancelled -- Received a cancel confirm from sensor
- Sensordata Event:Fields -- Received fields from sensor for a request
- This may be triggered multiple times since
- the sensor can split up its response in
- multiple messages.
- Sensordata Event:Failure -- Received a failure indication from sensor
- for a request. Typically a comm timeout.
-
- Attributes:
- threaded -- Indicates if command events should be threaded.
- Defaults to True.
- sessions -- A dictionary or equivalent backend mapping
- session IDs to dictionaries containing data
- relevant to a request's session. This dictionary is used
- both by the client and sensor side. On client side, seqnr
- is used as key, while on sensor side, a session_id is used
- as key. This ensures that the two will not collide, so
- one instance can be both client and sensor.
- Sensor side
- -----------
- nodes -- A dictionary mapping sensor nodes that are serviced through
- this XMPP instance to their device handlers ("drivers").
- Client side
- -----------
- last_seqnr -- The last used sequence number (integer). One sequence of
- communication (e.g. -->request, <--accept, <--fields)
- between client and sensor is identified by a unique
- sequence number (unique between the client/sensor pair)
-
- Methods:
- plugin_init -- Overrides base_plugin.plugin_init
- post_init -- Overrides base_plugin.post_init
- plugin_end -- Overrides base_plugin.plugin_end
-
- Sensor side
- -----------
- register_node -- Register a sensor as available from this XMPP
- instance.
-
- Client side
- -----------
- request_data -- Initiates a request for data from one or more
- sensors. Non-blocking, a callback function will
- be called when data is available.
-
- """
-
- name = 'xep_0323'
- description = 'XEP-0323 Internet of Things - Sensor Data'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
-
- default_config = {
- 'threaded': True
- }
-
- def plugin_init(self):
- """ Start the XEP-0323 plugin """
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Req',
- StanzaPath('iq@type=get/req'),
- self._handle_event_req))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Accepted',
- StanzaPath('iq@type=result/accepted'),
- self._handle_event_accepted))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Rejected',
- StanzaPath('iq@type=error/rejected'),
- self._handle_event_rejected))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Cancel',
- StanzaPath('iq@type=get/cancel'),
- self._handle_event_cancel))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Cancelled',
- StanzaPath('iq@type=result/cancelled'),
- self._handle_event_cancelled))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Fields',
- StanzaPath('message/fields'),
- self._handle_event_fields))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Failure',
- StanzaPath('message/failure'),
- self._handle_event_failure))
-
- self.xmpp.register_handler(
- Callback('Sensordata Event:Started',
- StanzaPath('message/started'),
- self._handle_event_started))
-
- # Server side dicts
- self.nodes = {}
- self.sessions = {}
-
- self.last_seqnr = 0
- self.seqnr_lock = Lock()
-
- ## For testing only
- self.test_authenticated_from = ""
-
- def post_init(self):
- """ Init complete. Register our features in Service discovery. """
- BasePlugin.post_init(self)
- self.xmpp['xep_0030'].add_feature(Sensordata.namespace)
- self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple())
-
- def _new_session(self):
- """ Return a new session ID. """
- return str(time.time()) + '-' + self.xmpp.new_id()
-
- def session_bind(self, jid):
- logging.debug("setting the Disco discovery for %s" % Sensordata.namespace)
- self.xmpp['xep_0030'].add_feature(Sensordata.namespace)
- self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple())
-
-
- def plugin_end(self):
- """ Stop the XEP-0323 plugin """
- self.sessions.clear()
- self.xmpp.remove_handler('Sensordata Event:Req')
- self.xmpp.remove_handler('Sensordata Event:Accepted')
- self.xmpp.remove_handler('Sensordata Event:Rejected')
- self.xmpp.remove_handler('Sensordata Event:Cancel')
- self.xmpp.remove_handler('Sensordata Event:Cancelled')
- self.xmpp.remove_handler('Sensordata Event:Fields')
- self.xmpp['xep_0030'].del_feature(feature=Sensordata.namespace)
-
-
- # =================================================================
- # Sensor side (data provider) API
-
- def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None):
- """
- Register a sensor/device as available for serving of data through this XMPP
- instance.
-
- The device object may by any custom implementation to support
- specific devices, but it must implement the functions:
- has_field
- request_fields
- according to the interfaces shown in the example device.py file.
-
- Arguments:
- nodeId -- The identifier for the device
- device -- The device object
- commTimeout -- Time in seconds to wait between each callback from device during
- a data readout. Float.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- self.nodes[nodeId] = {"device": device,
- "commTimeout": commTimeout,
- "sourceId": sourceId,
- "cacheType": cacheType}
-
- def _set_authenticated(self, auth=''):
- """ Internal testing function """
- self.test_authenticated_from = auth
-
-
- def _handle_event_req(self, iq):
- """
- Event handler for reception of an Iq with req - this is a request.
-
- Verifies that
- - all the requested nodes are available
- - at least one of the requested fields is available from at least
- one of the nodes
-
- If the request passes verification, an accept response is sent, and
- the readout process is started in a separate thread.
- If the verification fails, a reject message is sent.
- """
-
- seqnr = iq['req']['seqnr']
- error_msg = ''
- req_ok = True
-
- # Authentication
- if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from:
- # Invalid authentication
- req_ok = False
- error_msg = "Access denied"
-
- # Nodes
- process_nodes = []
- if len(iq['req']['nodes']) > 0:
- for n in iq['req']['nodes']:
- if not n['nodeId'] in self.nodes:
- req_ok = False
- error_msg = "Invalid nodeId " + n['nodeId']
- process_nodes = [n['nodeId'] for n in iq['req']['nodes']]
- else:
- process_nodes = self.nodes.keys()
-
- # Fields - if we just find one we are happy, otherwise we reject
- process_fields = []
- if len(iq['req']['fields']) > 0:
- found = False
- for f in iq['req']['fields']:
- for node in self.nodes:
- if self.nodes[node]["device"].has_field(f['name']):
- found = True
- break
- if not found:
- req_ok = False
- error_msg = "Invalid field " + f['name']
- process_fields = [f['name'] for n in iq['req']['fields']]
-
- req_flags = iq['req']._get_flags()
-
- request_delay_sec = None
- if 'when' in req_flags:
- # Timed request - requires datetime string in iso format
- # ex. 2013-04-05T15:00:03
- dt = None
- try:
- dt = datetime.datetime.strptime(req_flags['when'], "%Y-%m-%dT%H:%M:%S")
- except ValueError:
- req_ok = False
- error_msg = "Invalid datetime in 'when' flag, please use ISO format (i.e. 2013-04-05T15:00:03)."
-
- if not dt is None:
- # Datetime properly formatted
- dtnow = datetime.datetime.now()
- dtdiff = dt - dtnow
- request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
- if request_delay_sec <= 0:
- req_ok = False
- error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
-
- if req_ok:
- session = self._new_session()
- self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr}
- self.sessions[session]["commTimers"] = {}
- self.sessions[session]["nodeDone"] = {}
-
- iq.reply()
- iq['accepted']['seqnr'] = seqnr
- if not request_delay_sec is None:
- iq['accepted']['queued'] = "true"
- iq.send(block=False)
-
- self.sessions[session]["node_list"] = process_nodes
-
- if not request_delay_sec is None:
- # Delay request to requested time
- timer = Timer(request_delay_sec, self._event_delayed_req, args=(session, process_fields, req_flags))
- self.sessions[session]["commTimers"]["delaytimer"] = timer
- timer.start()
- return
-
- if self.threaded:
- tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags))
- tr_req.start()
- else:
- self._threaded_node_request(session, process_fields, req_flags)
-
- else:
- iq.reply()
- iq['type'] = 'error'
- iq['rejected']['seqnr'] = seqnr
- iq['rejected']['error'] = error_msg
- iq.send(block=False)
-
- def _threaded_node_request(self, session, process_fields, flags):
- """
- Helper function to handle the device readouts in a separate thread.
-
- Arguments:
- session -- The request session id
- process_fields -- The fields to request from the devices
- flags -- [optional] flags to pass to the devices, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- """
- for node in self.sessions[session]["node_list"]:
- self.sessions[session]["nodeDone"][node] = False
-
- for node in self.sessions[session]["node_list"]:
- timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node))
- self.sessions[session]["commTimers"][node] = timer
- timer.start()
- self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback)
-
- def _event_comm_timeout(self, session, nodeId):
- """
- Triggered if any of the readout operations timeout.
- Sends a failure message back to the client, stops communicating
- with the failing device.
-
- Arguments:
- session -- The request session id
- nodeId -- The id of the device which timed out
- """
- msg = self.xmpp.Message()
- msg['from'] = self.sessions[session]['to']
- msg['to'] = self.sessions[session]['from']
- msg['failure']['seqnr'] = self.sessions[session]['seqnr']
- msg['failure']['error']['text'] = "Timeout"
- msg['failure']['error']['nodeId'] = nodeId
- msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat()
-
- # Drop communication with this device and check if we are done
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- msg['failure']['done'] = 'true'
- msg.send()
- # The session is complete, delete it
- del self.sessions[session]
-
- def _event_delayed_req(self, session, process_fields, req_flags):
- """
- Triggered when the timer from a delayed request fires.
-
- Arguments:
- session -- The request session id
- process_fields -- The fields to request from the devices
- flags -- [optional] flags to pass to the devices, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- """
- msg = self.xmpp.Message()
- msg['from'] = self.sessions[session]['to']
- msg['to'] = self.sessions[session]['from']
- msg['started']['seqnr'] = self.sessions[session]['seqnr']
- msg.send()
-
- if self.threaded:
- tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags))
- tr_req.start()
- else:
- self._threaded_node_request(session, process_fields, req_flags)
-
- def _all_nodes_done(self, session):
- """
- Checks whether all devices are done replying to the readout.
-
- Arguments:
- session -- The request session id
- """
- for n in self.sessions[session]["nodeDone"]:
- if not self.sessions[session]["nodeDone"][n]:
- return False
- return True
-
- def _device_field_request_callback(self, session, nodeId, result, timestamp_block, error_msg=None):
- """
- Callback function called by the devices when they have any additional data.
- Composes a message with the data and sends it back to the client, and resets
- the timeout timer for the device.
-
- Arguments:
- session -- The request session id
- nodeId -- The device id which initiated the callback
- result -- The current result status of the readout. Valid values are:
- "error" - Readout failed.
- "fields" - Contains readout data.
- "done" - Indicates that the readout is complete. May contain
- readout data.
- timestamp_block -- [optional] Only applies when result != "error"
- The readout data. Structured as a dictionary:
- {
- timestamp: timestamp for this datablock,
- fields: list of field dictionary (one per readout field).
- readout field dictionary format:
- {
- type: The field type (numeric, boolean, dateTime, timeSpan, string, enum)
- name: The field name
- value: The field value
- unit: The unit of the field. Only applies to type numeric.
- dataType: The datatype of the field. Only applies to type enum.
- flags: [optional] data classifier flags for the field, e.g. momentary
- Formatted as a dictionary like { "flag name": "flag value" ... }
- }
- }
- error_msg -- [optional] Only applies when result == "error".
- Error details when a request failed.
- """
- if not session in self.sessions:
- # This can happen if a session was deleted, like in a cancellation. Just drop the data.
- return
-
- if result == "error":
- self.sessions[session]["commTimers"][nodeId].cancel()
-
- msg = self.xmpp.Message()
- msg['from'] = self.sessions[session]['to']
- msg['to'] = self.sessions[session]['from']
- msg['failure']['seqnr'] = self.sessions[session]['seqnr']
- msg['failure']['error']['text'] = error_msg
- msg['failure']['error']['nodeId'] = nodeId
- msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat()
-
- # Drop communication with this device and check if we are done
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- msg['failure']['done'] = 'true'
- # The session is complete, delete it
- del self.sessions[session]
- msg.send()
- else:
- msg = self.xmpp.Message()
- msg['from'] = self.sessions[session]['to']
- msg['to'] = self.sessions[session]['from']
- msg['fields']['seqnr'] = self.sessions[session]['seqnr']
-
- if timestamp_block is not None and len(timestamp_block) > 0:
- node = msg['fields'].add_node(nodeId)
- ts = node.add_timestamp(timestamp_block["timestamp"])
-
- for f in timestamp_block["fields"]:
- data = ts.add_data( typename=f['type'],
- name=f['name'],
- value=f['value'],
- unit=f['unit'],
- dataType=f['dataType'],
- flags=f['flags'])
-
- if result == "done":
- self.sessions[session]["commTimers"][nodeId].cancel()
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- # The session is complete, delete it
- del self.sessions[session]
- msg['fields']['done'] = 'true'
- else:
- # Restart comm timer
- self.sessions[session]["commTimers"][nodeId].reset()
-
- msg.send()
-
- def _handle_event_cancel(self, iq):
- """ Received Iq with cancel - this is a cancel request.
- Delete the session and confirm. """
-
- seqnr = iq['cancel']['seqnr']
- # Find the session
- for s in self.sessions:
- if self.sessions[s]['from'] == iq['from'] and self.sessions[s]['to'] == iq['to'] and self.sessions[s]['seqnr'] == seqnr:
- # found it. Cancel all timers
- for n in self.sessions[s]["commTimers"]:
- self.sessions[s]["commTimers"][n].cancel()
-
- # Confirm
- iq.reply()
- iq['type'] = 'result'
- iq['cancelled']['seqnr'] = seqnr
- iq.send(block=False)
-
- # Delete session
- del self.sessions[s]
- return
-
- # Could not find session, send reject
- iq.reply()
- iq['type'] = 'error'
- iq['rejected']['seqnr'] = seqnr
- iq['rejected']['error'] = "Cancel request received, no matching request is active."
- iq.send(block=False)
-
- # =================================================================
- # Client side (data retriever) API
-
- def request_data(self, from_jid, to_jid, callback, nodeIds=None, fields=None, flags=None):
- """
- Called on the client side to initiate a data readout.
- Composes a message with the request and sends it to the device(s).
- Does not block, the callback will be called when data is available.
-
- Arguments:
- from_jid -- The jid of the requester
- to_jid -- The jid of the device(s)
- callback -- The callback function to call when data is available.
-
- The callback function must support the following arguments:
-
- from_jid -- The jid of the responding device(s)
- result -- The current result status of the readout. Valid values are:
- "accepted" - Readout request accepted
- "queued" - Readout request accepted and queued
- "rejected" - Readout request rejected
- "failure" - Readout failed.
- "cancelled" - Confirmation of request cancellation.
- "started" - Previously queued request is now started
- "fields" - Contains readout data.
- "done" - Indicates that the readout is complete.
-
- nodeId -- [optional] Mandatory when result == "fields" or "failure".
- The node Id of the responding device. One callback will only
- contain data from one device.
- timestamp -- [optional] Mandatory when result == "fields".
- The timestamp of data in this callback. One callback will only
- contain data from one timestamp.
- fields -- [optional] Mandatory when result == "fields".
- List of field dictionaries representing the readout data.
- Dictionary format:
- {
- typename: The field type (numeric, boolean, dateTime, timeSpan, string, enum)
- name: The field name
- value: The field value
- unit: The unit of the field. Only applies to type numeric.
- dataType: The datatype of the field. Only applies to type enum.
- flags: [optional] data classifier flags for the field, e.g. momentary.
- Formatted as a dictionary like { "flag name": "flag value" ... }
- }
-
- error_msg -- [optional] Mandatory when result == "rejected" or "failure".
- Details about why the request is rejected or failed.
- "rejected" means that the request is stopped, but note that the
- request will continue even after a "failure". "failure" only means
- that communication was stopped to that specific device, other
- device(s) (if any) will continue their readout.
-
- nodeIds -- [optional] Limits the request to the node Ids in this list.
- fields -- [optional] Limits the request to the field names in this list.
- flags -- [optional] Limits the request according to the flags, or sets
- readout conditions such as timing.
-
- Return value:
- session -- Session identifier. Client can use this as a reference to cancel
- the request.
- """
- iq = self.xmpp.Iq()
- iq['from'] = from_jid
- iq['to'] = to_jid
- iq['type'] = "get"
- seqnr = self._get_new_seqnr()
- iq['id'] = seqnr
- iq['req']['seqnr'] = seqnr
- if nodeIds is not None:
- for nodeId in nodeIds:
- iq['req'].add_node(nodeId)
- if fields is not None:
- for field in fields:
- iq['req'].add_field(field)
-
- iq['req']._set_flags(flags)
-
- self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback}
- iq.send(block=False)
-
- return seqnr
-
- def cancel_request(self, session):
- """
- Called on the client side to cancel a request for data readout.
- Composes a message with the cancellation and sends it to the device(s).
- Does not block, the callback will be called when cancellation is
- confirmed.
-
- Arguments:
- session -- The session id of the request to cancel
- """
- seqnr = session
- iq = self.xmpp.Iq()
- iq['from'] = self.sessions[seqnr]['from']
- iq['to'] = self.sessions[seqnr]['to']
- iq['type'] = "get"
- iq['id'] = seqnr
- iq['cancel']['seqnr'] = seqnr
- iq.send(block=False)
-
- def _get_new_seqnr(self):
- """ Returns a unique sequence number (unique across threads) """
- self.seqnr_lock.acquire()
- self.last_seqnr += 1
- self.seqnr_lock.release()
- return str(self.last_seqnr)
-
- def _handle_event_accepted(self, iq):
- """ Received Iq with accepted - request was accepted """
- seqnr = iq['accepted']['seqnr']
- result = "accepted"
- if iq['accepted']['queued'] == 'true':
- result = "queued"
-
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=iq['from'], result=result)
-
- def _handle_event_rejected(self, iq):
- """ Received Iq with rejected - this is a reject.
- Delete the session. """
- seqnr = iq['rejected']['seqnr']
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error'])
- # Session terminated
- del self.sessions[seqnr]
-
- def _handle_event_cancelled(self, iq):
- """
- Received Iq with cancelled - this is a cancel confirm.
- Delete the session.
- """
- seqnr = iq['cancelled']['seqnr']
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=iq['from'], result="cancelled")
- # Session cancelled
- del self.sessions[seqnr]
-
- def _handle_event_fields(self, msg):
- """
- Received Msg with fields - this is a data response to a request.
- If this is the last data block, issue a "done" callback.
- """
- seqnr = msg['fields']['seqnr']
- callback = self.sessions[seqnr]["callback"]
- for node in msg['fields']['nodes']:
- for ts in node['timestamps']:
- fields = []
- for d in ts['datas']:
- field_block = {}
- field_block["name"] = d['name']
- field_block["typename"] = d._get_typename()
- field_block["value"] = d['value']
- if not d['unit'] == "": field_block["unit"] = d['unit'];
- if not d['dataType'] == "": field_block["dataType"] = d['dataType'];
- flags = d._get_flags()
- if not len(flags) == 0:
- field_block["flags"] = flags
- fields.append(field_block)
-
- callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields)
-
- if msg['fields']['done'] == "true":
- callback(from_jid=msg['from'], result="done")
- # Session done
- del self.sessions[seqnr]
-
- def _handle_event_failure(self, msg):
- """
- Received Msg with failure - our request failed
- Delete the session.
- """
- seqnr = msg['failure']['seqnr']
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text'])
-
- # Session failed
- del self.sessions[seqnr]
-
- def _handle_event_started(self, msg):
- """
- Received Msg with started - our request was queued and is now started.
- """
- seqnr = msg['started']['seqnr']
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=msg['from'], result="started")
-
-
diff --git a/sleekxmpp/plugins/xep_0323/stanza/__init__.py b/sleekxmpp/plugins/xep_0323/stanza/__init__.py
deleted file mode 100644
index c039cefa..00000000
--- a/sleekxmpp/plugins/xep_0323/stanza/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0323.stanza.sensordata import *
-
diff --git a/sleekxmpp/plugins/xep_0323/stanza/base.py b/sleekxmpp/plugins/xep_0323/stanza/base.py
deleted file mode 100644
index 1dadcf46..00000000
--- a/sleekxmpp/plugins/xep_0323/stanza/base.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET
-
-pass
diff --git a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py
deleted file mode 100644
index e8718161..00000000
--- a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py
+++ /dev/null
@@ -1,792 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
-from re import match
-
-class Sensordata(ElementBase):
- """ Placeholder for the namespace, not used as a stanza """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'sensordata'
- plugin_attrib = name
- interfaces = set(tuple())
-
-class FieldTypes():
- """
- All field types are optional booleans that default to False
- """
- field_types = set([ 'momentary','peak','status','computed','identity','historicalSecond','historicalMinute','historicalHour', \
- 'historicalDay','historicalWeek','historicalMonth','historicalQuarter','historicalYear','historicalOther'])
-
-class FieldStatus():
- """
- All field statuses are optional booleans that default to False
- """
- field_status = set([ 'missing','automaticEstimate','manualEstimate','manualReadout','automaticReadout','timeOffset','warning','error', \
- 'signed','invoiced','endOfSeries','powerFailure','invoiceConfirmed'])
-
-class Request(ElementBase):
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'req'
- plugin_attrib = name
- interfaces = set(['seqnr','nodes','fields','serviceToken','deviceToken','userToken','from','to','when','historical','all'])
- interfaces.update(FieldTypes.field_types)
- _flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all'])
- _flags.update(FieldTypes.field_types)
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._nodes = set()
- self._fields = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._nodes = set([node['nodeId'] for node in self['nodes']])
- self._fields = set([field['name'] for field in self['fields']])
-
- def _get_flags(self):
- """
- Helper function for getting of flags. Returns all flags in
- dictionary format: { "flag name": "flag value" ... }
- """
- flags = {}
- for f in self._flags:
- if not self[f] == "":
- flags[f] = self[f]
- return flags
-
- def _set_flags(self, flags):
- """
- Helper function for setting of flags.
-
- Arguments:
- flags -- Flags in dictionary format: { "flag name": "flag value" ... }
- """
- for f in self._flags:
- if flags is not None and f in flags:
- self[f] = flags[f]
- else:
- self[f] = None
-
- def add_node(self, nodeId, sourceId=None, cacheType=None):
- """
- Add a new node element. Each item is required to have a
- nodeId, but may also specify a sourceId value and cacheType.
-
- Arguments:
- nodeId -- The ID for the node.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- if nodeId not in self._nodes:
- self._nodes.add((nodeId))
- node = RequestNode(parent=self)
- node['nodeId'] = nodeId
- node['sourceId'] = sourceId
- node['cacheType'] = cacheType
- self.iterables.append(node)
- return node
- return None
-
- def del_node(self, nodeId):
- """
- Remove a single node.
-
- Arguments:
- nodeId -- Node ID of the item to remove.
- """
- if nodeId in self._nodes:
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- if node['nodeId'] == nodeId:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
- return True
- return False
-
- def get_nodes(self):
- """Return all nodes."""
- nodes = []
- for node in self['substanzas']:
- if isinstance(node, RequestNode):
- nodes.append(node)
- return nodes
-
- def set_nodes(self, nodes):
- """
- Set or replace all nodes. The given nodes must be in a
- list or set where each item is a tuple of the form:
- (nodeId, sourceId, cacheType)
-
- Arguments:
- nodes -- A series of nodes in tuple format.
- """
- self.del_nodes()
- for node in nodes:
- if isinstance(node, RequestNode):
- self.add_node(node['nodeId'], node['sourceId'], node['cacheType'])
- else:
- nodeId, sourceId, cacheType = node
- self.add_node(nodeId, sourceId, cacheType)
-
- def del_nodes(self):
- """Remove all nodes."""
- self._nodes = set()
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
-
-
- def add_field(self, name):
- """
- Add a new field element. Each item is required to have a
- name.
-
- Arguments:
- name -- The name of the field.
- """
- if name not in self._fields:
- self._fields.add((name))
- field = RequestField(parent=self)
- field['name'] = name
- self.iterables.append(field)
- return field
- return None
-
- def del_field(self, name):
- """
- Remove a single field.
-
- Arguments:
- name -- name of field to remove.
- """
- if name in self._fields:
- fields = [i for i in self.iterables if isinstance(i, RequestField)]
- for field in fields:
- if field['name'] == name:
- self.xml.remove(field.xml)
- self.iterables.remove(field)
- return True
- return False
-
- def get_fields(self):
- """Return all fields."""
- fields = []
- for field in self['substanzas']:
- if isinstance(field, RequestField):
- fields.append(field)
- return fields
-
- def set_fields(self, fields):
- """
- Set or replace all fields. The given fields must be in a
- list or set where each item is RequestField or string
-
- Arguments:
- fields -- A series of fields in RequestField or string format.
- """
- self.del_fields()
- for field in fields:
- if isinstance(field, RequestField):
- self.add_field(field['name'])
- else:
- self.add_field(field)
-
- def del_fields(self):
- """Remove all fields."""
- self._fields = set()
- fields = [i for i in self.iterables if isinstance(i, RequestField)]
- for field in fields:
- self.xml.remove(field.xml)
- self.iterables.remove(field)
-
-
-class RequestNode(ElementBase):
- """ Node element in a request """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'node'
- plugin_attrib = name
- interfaces = set(['nodeId','sourceId','cacheType'])
-
-class RequestField(ElementBase):
- """ Field element in a request """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'field'
- plugin_attrib = name
- interfaces = set(['name'])
-
-class Accepted(ElementBase):
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'accepted'
- plugin_attrib = name
- interfaces = set(['seqnr','queued'])
-
-class Started(ElementBase):
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'started'
- plugin_attrib = name
- interfaces = set(['seqnr'])
-
-class Failure(ElementBase):
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'failure'
- plugin_attrib = name
- interfaces = set(['seqnr','done'])
-
-class Error(ElementBase):
- """ Error element in a request failure """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'error'
- plugin_attrib = name
- interfaces = set(['nodeId','timestamp','sourceId','cacheType','text'])
-
- def get_text(self):
- """Return then contents inside the XML tag."""
- return self.xml.text
-
- def set_text(self, value):
- """Set then contents inside the XML tag.
-
- :param value: string
- """
-
- self.xml.text = value
- return self
-
- def del_text(self):
- """Remove the contents inside the XML tag."""
- self.xml.text = ""
- return self
-
-class Rejected(ElementBase):
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'rejected'
- plugin_attrib = name
- interfaces = set(['seqnr','error'])
- sub_interfaces = set(['error'])
-
-class Fields(ElementBase):
- """ Fields element, top level in a response message with data """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'fields'
- plugin_attrib = name
- interfaces = set(['seqnr','done','nodes'])
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._nodes = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._nodes = set([node['nodeId'] for node in self['nodes']])
-
-
- def add_node(self, nodeId, sourceId=None, cacheType=None, substanzas=None):
- """
- Add a new node element. Each item is required to have a
- nodeId, but may also specify a sourceId value and cacheType.
-
- Arguments:
- nodeId -- The ID for the node.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- if nodeId not in self._nodes:
- self._nodes.add((nodeId))
- node = FieldsNode(parent=self)
- node['nodeId'] = nodeId
- node['sourceId'] = sourceId
- node['cacheType'] = cacheType
- if substanzas is not None:
- node.set_timestamps(substanzas)
-
- self.iterables.append(node)
- return node
- return None
-
- def del_node(self, nodeId):
- """
- Remove a single node.
-
- Arguments:
- nodeId -- Node ID of the item to remove.
- """
- if nodeId in self._nodes:
- nodes = [i for i in self.iterables if isinstance(i, FieldsNode)]
- for node in nodes:
- if node['nodeId'] == nodeId:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
- return True
- return False
-
- def get_nodes(self):
- """Return all nodes."""
- nodes = []
- for node in self['substanzas']:
- if isinstance(node, FieldsNode):
- nodes.append(node)
- return nodes
-
- def set_nodes(self, nodes):
- """
- Set or replace all nodes. The given nodes must be in a
- list or set where each item is a tuple of the form:
- (nodeId, sourceId, cacheType)
-
- Arguments:
- nodes -- A series of nodes in tuple format.
- """
- #print(str(id(self)) + " set_nodes: got " + str(nodes))
- self.del_nodes()
- for node in nodes:
- if isinstance(node, FieldsNode):
- self.add_node(node['nodeId'], node['sourceId'], node['cacheType'], substanzas=node['substanzas'])
- else:
- nodeId, sourceId, cacheType = node
- self.add_node(nodeId, sourceId, cacheType)
-
- def del_nodes(self):
- """Remove all nodes."""
- self._nodes = set()
- nodes = [i for i in self.iterables if isinstance(i, FieldsNode)]
- for node in nodes:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
-
-
-class FieldsNode(ElementBase):
- """ Node element in response fields """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'node'
- plugin_attrib = name
- interfaces = set(['nodeId','sourceId','cacheType','timestamps'])
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._timestamps = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._timestamps = set([ts['value'] for ts in self['timestamps']])
-
- def add_timestamp(self, timestamp, substanzas=None):
- """
- Add a new timestamp element.
-
- Arguments:
- timestamp -- The timestamp in ISO format.
- """
- #print(str(id(self)) + " add_timestamp: " + str(timestamp))
-
- if timestamp not in self._timestamps:
- self._timestamps.add((timestamp))
- ts = Timestamp(parent=self)
- ts['value'] = timestamp
- if not substanzas is None:
- ts.set_datas(substanzas)
- #print("add_timestamp with substanzas: " + str(substanzas))
- self.iterables.append(ts)
- #print(str(id(self)) + " added_timestamp: " + str(id(ts)))
- return ts
- return None
-
- def del_timestamp(self, timestamp):
- """
- Remove a single timestamp.
-
- Arguments:
- timestamp -- timestamp (in ISO format) of the item to remove.
- """
- #print("del_timestamp: ")
- if timestamp in self._timestamps:
- timestamps = [i for i in self.iterables if isinstance(i, Timestamp)]
- for ts in timestamps:
- if ts['value'] == timestamp:
- self.xml.remove(ts.xml)
- self.iterables.remove(ts)
- return True
- return False
-
- def get_timestamps(self):
- """Return all timestamps."""
- #print(str(id(self)) + " get_timestamps: ")
- timestamps = []
- for timestamp in self['substanzas']:
- if isinstance(timestamp, Timestamp):
- timestamps.append(timestamp)
- return timestamps
-
- def set_timestamps(self, timestamps):
- """
- Set or replace all timestamps. The given timestamps must be in a
- list or set where each item is a timestamp
-
- Arguments:
- timestamps -- A series of timestamps.
- """
- #print(str(id(self)) + " set_timestamps: got " + str(timestamps))
- self.del_timestamps()
- for timestamp in timestamps:
- #print("set_timestamps: subset " + str(timestamp))
- #print("set_timestamps: subset.substanzas " + str(timestamp['substanzas']))
- if isinstance(timestamp, Timestamp):
- self.add_timestamp(timestamp['value'], substanzas=timestamp['substanzas'])
- else:
- #print("set_timestamps: got " + str(timestamp))
- self.add_timestamp(timestamp)
-
- def del_timestamps(self):
- """Remove all timestamps."""
- #print(str(id(self)) + " del_timestamps: ")
- self._timestamps = set()
- timestamps = [i for i in self.iterables if isinstance(i, Timestamp)]
- for timestamp in timestamps:
- self.xml.remove(timestamp.xml)
- self.iterables.remove(timestamp)
-
-class Field(ElementBase):
- """
- Field element in response Timestamp. This is a base class,
- all instances of fields added to Timestamp must be of types:
- DataNumeric
- DataString
- DataBoolean
- DataDateTime
- DataTimeSpan
- DataEnum
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'field'
- plugin_attrib = name
- interfaces = set(['name','module','stringIds'])
- interfaces.update(FieldTypes.field_types)
- interfaces.update(FieldStatus.field_status)
-
- _flags = set()
- _flags.update(FieldTypes.field_types)
- _flags.update(FieldStatus.field_status)
-
- def set_stringIds(self, value):
- """Verifies stringIds according to regexp from specification XMPP-0323.
-
- :param value: string
- """
-
- pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
- if pattern.match(value) is not None:
- self.xml.stringIds = value
- else:
- # Bad content, add nothing
- pass
-
- return self
-
- def _get_flags(self):
- """
- Helper function for getting of flags. Returns all flags in
- dictionary format: { "flag name": "flag value" ... }
- """
- flags = {}
- for f in self._flags:
- if not self[f] == "":
- flags[f] = self[f]
- return flags
-
- def _set_flags(self, flags):
- """
- Helper function for setting of flags.
-
- Arguments:
- flags -- Flags in dictionary format: { "flag name": "flag value" ... }
- """
- for f in self._flags:
- if flags is not None and f in flags:
- self[f] = flags[f]
- else:
- self[f] = None
-
- def _get_typename(self):
- return "invalid type, use subclasses!"
-
-
-class Timestamp(ElementBase):
- """ Timestamp element in response Node """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'timestamp'
- plugin_attrib = name
- interfaces = set(['value','datas'])
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._datas = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._datas = set([data['name'] for data in self['datas']])
-
- def add_data(self, typename, name, value, module=None, stringIds=None, unit=None, dataType=None, flags=None):
- """
- Add a new data element.
-
- Arguments:
- typename -- The type of data element (numeric, string, boolean, dateTime, timeSpan or enum)
- value -- The value of the data element
- module -- [optional] language module to use for the data element
- stringIds -- [optional] The stringIds used to find associated text in the language module
- unit -- [optional] The unit. Only applicable for type numeric
- dataType -- [optional] The dataType. Only applicable for type enum
- """
- if name not in self._datas:
- dataObj = None
- if typename == "numeric":
- dataObj = DataNumeric(parent=self)
- dataObj['unit'] = unit
- elif typename == "string":
- dataObj = DataString(parent=self)
- elif typename == "boolean":
- dataObj = DataBoolean(parent=self)
- elif typename == "dateTime":
- dataObj = DataDateTime(parent=self)
- elif typename == "timeSpan":
- dataObj = DataTimeSpan(parent=self)
- elif typename == "enum":
- dataObj = DataEnum(parent=self)
- dataObj['dataType'] = dataType
-
- dataObj['name'] = name
- dataObj['value'] = value
- dataObj['module'] = module
- dataObj['stringIds'] = stringIds
-
- if flags is not None:
- dataObj._set_flags(flags)
-
- self._datas.add(name)
- self.iterables.append(dataObj)
- return dataObj
- return None
-
- def del_data(self, name):
- """
- Remove a single data element.
-
- Arguments:
- data_name -- The data element name to remove.
- """
- if name in self._datas:
- datas = [i for i in self.iterables if isinstance(i, Field)]
- for data in datas:
- if data['name'] == name:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
- return True
- return False
-
- def get_datas(self):
- """ Return all data elements. """
- datas = []
- for data in self['substanzas']:
- if isinstance(data, Field):
- datas.append(data)
- return datas
-
- def set_datas(self, datas):
- """
- Set or replace all data elements. The given elements must be in a
- list or set where each item is a data element (numeric, string, boolean, dateTime, timeSpan or enum)
-
- Arguments:
- datas -- A series of data elements.
- """
- self.del_datas()
- for data in datas:
- self.add_data(typename=data._get_typename(), name=data['name'], value=data['value'], module=data['module'], stringIds=data['stringIds'], unit=data['unit'], dataType=data['dataType'], flags=data._get_flags())
-
- def del_datas(self):
- """Remove all data elements."""
- self._datas = set()
- datas = [i for i in self.iterables if isinstance(i, Field)]
- for data in datas:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
-
-class DataNumeric(Field):
- """
- Field data of type numeric.
- Note that the value is expressed as a string.
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'numeric'
- plugin_attrib = name
- interfaces = set(['value', 'unit'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "numeric"
-
-class DataString(Field):
- """
- Field data of type string
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'string'
- plugin_attrib = name
- interfaces = set(['value'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "string"
-
-class DataBoolean(Field):
- """
- Field data of type boolean.
- Note that the value is expressed as a string.
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'boolean'
- plugin_attrib = name
- interfaces = set(['value'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "boolean"
-
-class DataDateTime(Field):
- """
- Field data of type dateTime.
- Note that the value is expressed as a string.
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'dateTime'
- plugin_attrib = name
- interfaces = set(['value'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "dateTime"
-
-class DataTimeSpan(Field):
- """
- Field data of type timeSpan.
- Note that the value is expressed as a string.
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'timeSpan'
- plugin_attrib = name
- interfaces = set(['value'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "timeSpan"
-
-class DataEnum(Field):
- """
- Field data of type enum.
- Note that the value is expressed as a string.
- """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'enum'
- plugin_attrib = name
- interfaces = set(['value', 'dataType'])
- interfaces.update(Field.interfaces)
-
- def _get_typename(self):
- return "enum"
-
-class Done(ElementBase):
- """ Done element used to signal that all data has been transferred """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'done'
- plugin_attrib = name
- interfaces = set(['seqnr'])
-
-class Cancel(ElementBase):
- """ Cancel element used to signal that a request shall be cancelled """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'cancel'
- plugin_attrib = name
- interfaces = set(['seqnr'])
-
-class Cancelled(ElementBase):
- """ Cancelled element used to signal that cancellation is confirmed """
- namespace = 'urn:xmpp:iot:sensordata'
- name = 'cancelled'
- plugin_attrib = name
- interfaces = set(['seqnr'])
-
-
-register_stanza_plugin(Iq, Request)
-register_stanza_plugin(Request, RequestNode, iterable=True)
-register_stanza_plugin(Request, RequestField, iterable=True)
-
-register_stanza_plugin(Iq, Accepted)
-register_stanza_plugin(Message, Failure)
-register_stanza_plugin(Failure, Error)
-
-register_stanza_plugin(Iq, Rejected)
-
-register_stanza_plugin(Message, Fields)
-register_stanza_plugin(Fields, FieldsNode, iterable=True)
-register_stanza_plugin(FieldsNode, Timestamp, iterable=True)
-register_stanza_plugin(Timestamp, Field, iterable=True)
-register_stanza_plugin(Timestamp, DataNumeric, iterable=True)
-register_stanza_plugin(Timestamp, DataString, iterable=True)
-register_stanza_plugin(Timestamp, DataBoolean, iterable=True)
-register_stanza_plugin(Timestamp, DataDateTime, iterable=True)
-register_stanza_plugin(Timestamp, DataTimeSpan, iterable=True)
-register_stanza_plugin(Timestamp, DataEnum, iterable=True)
-
-register_stanza_plugin(Message, Started)
-
-register_stanza_plugin(Iq, Cancel)
-register_stanza_plugin(Iq, Cancelled)
diff --git a/sleekxmpp/plugins/xep_0323/timerreset.py b/sleekxmpp/plugins/xep_0323/timerreset.py
deleted file mode 100644
index 398b47c1..00000000
--- a/sleekxmpp/plugins/xep_0323/timerreset.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-from threading import Thread, Event, Timer
-import time
-
-def TimerReset(*args, **kwargs):
- """ Global function for Timer """
- return _TimerReset(*args, **kwargs)
-
-
-class _TimerReset(Thread):
- """Call a function after a specified number of seconds:
-
- t = TimerReset(30.0, f, args=[], kwargs={})
- t.start()
- t.cancel() # stop the timer's action if it's still waiting
- """
-
- def __init__(self, interval, function, args=None, kwargs=None):
- if not kwargs:
- kwargs = {}
- if not args:
- args = []
-
- Thread.__init__(self)
- self.interval = interval
- self.function = function
- self.args = args
- self.kwargs = kwargs
- self.finished = Event()
- self.resetted = True
-
- def cancel(self):
- """Stop the timer if it hasn't finished yet"""
- self.finished.set()
-
- def run(self):
- #print "Time: %s - timer running..." % time.asctime()
-
- while self.resetted:
- #print "Time: %s - timer waiting for timeout in %.2f..." % (time.asctime(), self.interval)
- self.resetted = False
- self.finished.wait(self.interval)
-
- if not self.finished.isSet():
- self.function(*self.args, **self.kwargs)
- self.finished.set()
- #print "Time: %s - timer finished!" % time.asctime()
-
- def reset(self, interval=None):
- """ Reset the timer """
-
- if interval:
- #print "Time: %s - timer resetting to %.2f..." % (time.asctime(), interval)
- self.interval = interval
- else:
- #print "Time: %s - timer resetting..." % time.asctime()
- pass
-
- self.resetted = True
- self.finished.set()
- self.finished.clear()
diff --git a/sleekxmpp/plugins/xep_0325/__init__.py b/sleekxmpp/plugins/xep_0325/__init__.py
deleted file mode 100644
index 01c38dce..00000000
--- a/sleekxmpp/plugins/xep_0325/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0325.control import XEP_0325
-from sleekxmpp.plugins.xep_0325 import stanza
-
-register_plugin(XEP_0325)
-
-xep_0325=XEP_0325
diff --git a/sleekxmpp/plugins/xep_0325/control.py b/sleekxmpp/plugins/xep_0325/control.py
deleted file mode 100644
index 11e7a045..00000000
--- a/sleekxmpp/plugins/xep_0325/control.py
+++ /dev/null
@@ -1,569 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import time
-from threading import Thread, Timer, Lock
-
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0325 import stanza
-from sleekxmpp.plugins.xep_0325.stanza import Control
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0325(BasePlugin):
-
- """
- XEP-0325: IoT Control
-
-
- Actuators are devices in sensor networks that can be controlled through
- the network and act with the outside world. In sensor networks and
- Internet of Things applications, actuators make it possible to automate
- real-world processes.
- This plugin implements a mechanism whereby actuators can be controlled
- in XMPP-based sensor networks, making it possible to integrate sensors
- and actuators of different brands, makes and models into larger
- Internet of Things applications.
-
- Also see <http://xmpp.org/extensions/xep-0325.html>
-
- Configuration Values:
- threaded -- Indicates if communication with sensors should be threaded.
- Defaults to True.
-
- Events:
- Sensor side
- -----------
- Control Event:DirectSet -- Received a control message
- Control Event:SetReq -- Received a control request
-
- Client side
- -----------
- Control Event:SetResponse -- Received a response to a
- control request, type result
- Control Event:SetResponseError -- Received a response to a
- control request, type error
-
- Attributes:
- threaded -- Indicates if command events should be threaded.
- Defaults to True.
- sessions -- A dictionary or equivalent backend mapping
- session IDs to dictionaries containing data
- relevant to a request's session. This dictionary is used
- both by the client and sensor side. On client side, seqnr
- is used as key, while on sensor side, a session_id is used
- as key. This ensures that the two will not collide, so
- one instance can be both client and sensor.
- Sensor side
- -----------
- nodes -- A dictionary mapping sensor nodes that are serviced through
- this XMPP instance to their device handlers ("drivers").
- Client side
- -----------
- last_seqnr -- The last used sequence number (integer). One sequence of
- communication (e.g. -->request, <--accept, <--fields)
- between client and sensor is identified by a unique
- sequence number (unique between the client/sensor pair)
-
- Methods:
- plugin_init -- Overrides base_plugin.plugin_init
- post_init -- Overrides base_plugin.post_init
- plugin_end -- Overrides base_plugin.plugin_end
-
- Sensor side
- -----------
- register_node -- Register a sensor as available from this XMPP
- instance.
-
- Client side
- -----------
- set_request -- Initiates a control request to modify data in
- sensor(s). Non-blocking, a callback function will
- be called when the sensor has responded.
- set_command -- Initiates a control command to modify data in
- sensor(s). Non-blocking. The sensor(s) will not
- respond regardless of the result of the command,
- so no callback is made.
-
- """
-
- name = 'xep_0325'
- description = 'XEP-0325 Internet of Things - Control'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
-
- default_config = {
- 'threaded': True
-# 'session_db': None
- }
-
- def plugin_init(self):
- """ Start the XEP-0325 plugin """
-
- self.xmpp.register_handler(
- Callback('Control Event:DirectSet',
- StanzaPath('message/set'),
- self._handle_direct_set))
-
- self.xmpp.register_handler(
- Callback('Control Event:SetReq',
- StanzaPath('iq@type=set/set'),
- self._handle_set_req))
-
- self.xmpp.register_handler(
- Callback('Control Event:SetResponse',
- StanzaPath('iq@type=result/setResponse'),
- self._handle_set_response))
-
- self.xmpp.register_handler(
- Callback('Control Event:SetResponseError',
- StanzaPath('iq@type=error/setResponse'),
- self._handle_set_response))
-
- # Server side dicts
- self.nodes = {}
- self.sessions = {}
-
- self.last_seqnr = 0
- self.seqnr_lock = Lock()
-
- ## For testning only
- self.test_authenticated_from = ""
-
- def post_init(self):
- """ Init complete. Register our features in Serivce discovery. """
- BasePlugin.post_init(self)
- self.xmpp['xep_0030'].add_feature(Control.namespace)
- self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple())
-
- def _new_session(self):
- """ Return a new session ID. """
- return str(time.time()) + '-' + self.xmpp.new_id()
-
- def plugin_end(self):
- """ Stop the XEP-0325 plugin """
- self.sessions.clear()
- self.xmpp.remove_handler('Control Event:DirectSet')
- self.xmpp.remove_handler('Control Event:SetReq')
- self.xmpp.remove_handler('Control Event:SetResponse')
- self.xmpp.remove_handler('Control Event:SetResponseError')
- self.xmpp['xep_0030'].del_feature(feature=Control.namespace)
- self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple())
-
-
- # =================================================================
- # Sensor side (data provider) API
-
- def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None):
- """
- Register a sensor/device as available for control requests/commands
- through this XMPP instance.
-
- The device object may by any custom implementation to support
- specific devices, but it must implement the functions:
- has_control_field
- set_control_fields
- according to the interfaces shown in the example device.py file.
-
- Arguments:
- nodeId -- The identifier for the device
- device -- The device object
- commTimeout -- Time in seconds to wait between each callback from device during
- a data readout. Float.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- self.nodes[nodeId] = {"device": device,
- "commTimeout": commTimeout,
- "sourceId": sourceId,
- "cacheType": cacheType}
-
- def _set_authenticated(self, auth=''):
- """ Internal testing function """
- self.test_authenticated_from = auth
-
- def _get_new_seqnr(self):
- """ Returns a unique sequence number (unique across threads) """
- self.seqnr_lock.acquire()
- self.last_seqnr += 1
- self.seqnr_lock.release()
- return str(self.last_seqnr)
-
- def _handle_set_req(self, iq):
- """
- Event handler for reception of an Iq with set req - this is a
- control request.
-
- Verifies that
- - all the requested nodes are available
- (if no nodes are specified in the request, assume all nodes)
- - all the control fields are available from all requested nodes
- (if no nodes are specified in the request, assume all nodes)
-
- If the request passes verification, the control request is passed
- to the devices (in a separate thread).
- If the verification fails, a setResponse with error indication
- is sent.
- """
-
- error_msg = ''
- req_ok = True
- missing_node = None
- missing_field = None
-
- # Authentication
- if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from:
- # Invalid authentication
- req_ok = False
- error_msg = "Access denied"
-
- # Nodes
- if len(iq['set']['nodes']) > 0:
- for n in iq['set']['nodes']:
- if not n['nodeId'] in self.nodes:
- req_ok = False
- missing_node = n['nodeId']
- error_msg = "Invalid nodeId " + n['nodeId']
- process_nodes = [n['nodeId'] for n in iq['set']['nodes']]
- else:
- process_nodes = self.nodes.keys()
-
- # Fields - for control we need to find all in all devices, otherwise we reject
- process_fields = []
- if len(iq['set']['datas']) > 0:
- for f in iq['set']['datas']:
- for node in self.nodes:
- if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()):
- req_ok = False
- missing_field = f['name']
- error_msg = "Invalid field " + f['name']
- break
- process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']]
-
- if req_ok:
- session = self._new_session()
- self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']}
- self.sessions[session]["commTimers"] = {}
- self.sessions[session]["nodeDone"] = {}
- # Flag that a reply is exected when we are done
- self.sessions[session]["reply"] = True
-
- self.sessions[session]["node_list"] = process_nodes
- if self.threaded:
- #print("starting thread")
- tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields))
- tr_req.start()
- #print("started thread")
- else:
- self._threaded_node_request(session, process_fields)
-
- else:
- iq.reply()
- iq['type'] = 'error'
- iq['setResponse']['responseCode'] = "NotFound"
- if missing_node is not None:
- iq['setResponse'].add_node(missing_node)
- if missing_field is not None:
- iq['setResponse'].add_data(missing_field)
- iq['setResponse']['error']['var'] = "Output"
- iq['setResponse']['error']['text'] = error_msg
- iq.send(block=False)
-
- def _handle_direct_set(self, msg):
- """
- Event handler for reception of a Message with set command - this is a
- direct control command.
-
- Verifies that
- - all the requested nodes are available
- (if no nodes are specified in the request, assume all nodes)
- - all the control fields are available from all requested nodes
- (if no nodes are specified in the request, assume all nodes)
-
- If the request passes verification, the control request is passed
- to the devices (in a separate thread).
- If the verification fails, do nothing.
- """
- req_ok = True
-
- # Nodes
- if len(msg['set']['nodes']) > 0:
- for n in msg['set']['nodes']:
- if not n['nodeId'] in self.nodes:
- req_ok = False
- error_msg = "Invalid nodeId " + n['nodeId']
- process_nodes = [n['nodeId'] for n in msg['set']['nodes']]
- else:
- process_nodes = self.nodes.keys()
-
- # Fields - for control we need to find all in all devices, otherwise we reject
- process_fields = []
- if len(msg['set']['datas']) > 0:
- for f in msg['set']['datas']:
- for node in self.nodes:
- if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()):
- req_ok = False
- missing_field = f['name']
- error_msg = "Invalid field " + f['name']
- break
- process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']]
-
- if req_ok:
- session = self._new_session()
- self.sessions[session] = {"from": msg['from'], "to": msg['to']}
- self.sessions[session]["commTimers"] = {}
- self.sessions[session]["nodeDone"] = {}
- self.sessions[session]["reply"] = False
-
- self.sessions[session]["node_list"] = process_nodes
- if self.threaded:
- #print("starting thread")
- tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields))
- tr_req.start()
- #print("started thread")
- else:
- self._threaded_node_request(session, process_fields)
-
-
- def _threaded_node_request(self, session, process_fields):
- """
- Helper function to handle the device control in a separate thread.
-
- Arguments:
- session -- The request session id
- process_fields -- The fields to set in the devices. List of tuple format:
- (name, datatype, value)
- """
- for node in self.sessions[session]["node_list"]:
- self.sessions[session]["nodeDone"][node] = False
-
- for node in self.sessions[session]["node_list"]:
- timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node))
- self.sessions[session]["commTimers"][node] = timer
- timer.start()
- self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback)
-
- def _event_comm_timeout(self, session, nodeId):
- """
- Triggered if any of the control operations timeout.
- Stop communicating with the failing device.
- If the control command was an Iq request, sends a failure
- message back to the client.
-
- Arguments:
- session -- The request session id
- nodeId -- The id of the device which timed out
- """
-
- if self.sessions[session]["reply"]:
- # Reply is exected when we are done
- iq = self.xmpp.Iq()
- iq['from'] = self.sessions[session]['to']
- iq['to'] = self.sessions[session]['from']
- iq['type'] = "error"
- iq['id'] = self.sessions[session]['seqnr']
- iq['setResponse']['responseCode'] = "OtherError"
- iq['setResponse'].add_node(nodeId)
- iq['setResponse']['error']['var'] = "Output"
- iq['setResponse']['error']['text'] = "Timeout."
- iq.send(block=False)
-
- ## TODO - should we send one timeout per node??
-
- # Drop communication with this device and check if we are done
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- # The session is complete, delete it
- del self.sessions[session]
-
- def _all_nodes_done(self, session):
- """
- Checks wheter all devices are done replying to the control command.
-
- Arguments:
- session -- The request session id
- """
- for n in self.sessions[session]["nodeDone"]:
- if not self.sessions[session]["nodeDone"][n]:
- return False
- return True
-
- def _device_set_command_callback(self, session, nodeId, result, error_field=None, error_msg=None):
- """
- Callback function called by the devices when the control command is
- complete or failed.
- If needed, composes a message with the result and sends it back to the
- client.
-
- Arguments:
- session -- The request session id
- nodeId -- The device id which initiated the callback
- result -- The current result status of the control command. Valid values are:
- "error" - Set fields failed.
- "ok" - All fields were set.
- error_field -- [optional] Only applies when result == "error"
- The field name that failed (usually means it is missing)
- error_msg -- [optional] Only applies when result == "error".
- Error details when a request failed.
- """
-
- if not session in self.sessions:
- # This can happend if a session was deleted, like in a timeout. Just drop the data.
- return
-
- if result == "error":
- self.sessions[session]["commTimers"][nodeId].cancel()
-
- if self.sessions[session]["reply"]:
- # Reply is exected when we are done
- iq = self.xmpp.Iq()
- iq['from'] = self.sessions[session]['to']
- iq['to'] = self.sessions[session]['from']
- iq['type'] = "error"
- iq['id'] = self.sessions[session]['seqnr']
- iq['setResponse']['responseCode'] = "OtherError"
- iq['setResponse'].add_node(nodeId)
- if error_field is not None:
- iq['setResponse'].add_data(error_field)
- iq['setResponse']['error']['var'] = error_field
- iq['setResponse']['error']['text'] = error_msg
- iq.send(block=False)
-
- # Drop communication with this device and check if we are done
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- # The session is complete, delete it
- del self.sessions[session]
- else:
- self.sessions[session]["commTimers"][nodeId].cancel()
-
- self.sessions[session]["nodeDone"][nodeId] = True
- if (self._all_nodes_done(session)):
- if self.sessions[session]["reply"]:
- # Reply is exected when we are done
- iq = self.xmpp.Iq()
- iq['from'] = self.sessions[session]['to']
- iq['to'] = self.sessions[session]['from']
- iq['type'] = "result"
- iq['id'] = self.sessions[session]['seqnr']
- iq['setResponse']['responseCode'] = "OK"
- iq.send(block=False)
-
- # The session is complete, delete it
- del self.sessions[session]
-
-
- # =================================================================
- # Client side (data controller) API
-
- def set_request(self, from_jid, to_jid, callback, fields, nodeIds=None):
- """
- Called on the client side to initiade a control request.
- Composes a message with the request and sends it to the device(s).
- Does not block, the callback will be called when the device(s)
- has responded.
-
- Arguments:
- from_jid -- The jid of the requester
- to_jid -- The jid of the device(s)
- callback -- The callback function to call when data is availble.
-
- The callback function must support the following arguments:
-
- from_jid -- The jid of the responding device(s)
- result -- The result of the control request. Valid values are:
- "OK" - Control request completed successfully
- "NotFound" - One or more nodes or fields are missing
- "InsufficientPrivileges" - Not authorized.
- "Locked" - Field(s) is locked and cannot
- be changed at the moment.
- "NotImplemented" - Request feature not implemented.
- "FormError" - Error while setting with
- a form (not implemented).
- "OtherError" - Indicates other types of
- errors, such as timeout.
- Details in the error_msg.
-
-
- nodeId -- [optional] Only applicable when result == "error"
- List of node Ids of failing device(s).
-
- fields -- [optional] Only applicable when result == "error"
- List of fields that failed.[optional] Mandatory when result == "rejected" or "failure".
-
- error_msg -- Details about why the request failed.
-
- fields -- Fields to set. List of tuple format: (name, typename, value).
- nodeIds -- [optional] Limits the request to the node Ids in this list.
- """
- iq = self.xmpp.Iq()
- iq['from'] = from_jid
- iq['to'] = to_jid
- seqnr = self._get_new_seqnr()
- iq['id'] = seqnr
- iq['type'] = "set"
- if nodeIds is not None:
- for nodeId in nodeIds:
- iq['set'].add_node(nodeId)
- if fields is not None:
- for name, typename, value in fields:
- iq['set'].add_data(name=name, typename=typename, value=value)
-
- self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback}
- iq.send(block=False)
-
- def set_command(self, from_jid, to_jid, fields, nodeIds=None):
- """
- Called on the client side to initiade a control command.
- Composes a message with the set commandand sends it to the device(s).
- Does not block. Device(s) will not respond, regardless of result.
-
- Arguments:
- from_jid -- The jid of the requester
- to_jid -- The jid of the device(s)
-
- fields -- Fields to set. List of tuple format: (name, typename, value).
- nodeIds -- [optional] Limits the request to the node Ids in this list.
- """
- msg = self.xmpp.Message()
- msg['from'] = from_jid
- msg['to'] = to_jid
- msg['type'] = "set"
- if nodeIds is not None:
- for nodeId in nodeIds:
- msg['set'].add_node(nodeId)
- if fields is not None:
- for name, typename, value in fields:
- msg['set'].add_data(name, typename, value)
-
- # We won't get any reply, so don't create a session
- msg.send()
-
- def _handle_set_response(self, iq):
- """ Received response from device(s) """
- #print("ooh")
- seqnr = iq['id']
- from_jid = str(iq['from'])
- result = iq['setResponse']['responseCode']
- nodeIds = [n['name'] for n in iq['setResponse']['nodes']]
- fields = [f['name'] for f in iq['setResponse']['datas']]
- error_msg = None
-
- if not iq['setResponse'].find('error') is None and not iq['setResponse']['error']['text'] == "":
- error_msg = iq['setResponse']['error']['text']
-
- callback = self.sessions[seqnr]["callback"]
- callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg)
diff --git a/sleekxmpp/plugins/xep_0325/device.py b/sleekxmpp/plugins/xep_0325/device.py
deleted file mode 100644
index f1ed0733..00000000
--- a/sleekxmpp/plugins/xep_0325/device.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import datetime
-
-class Device(object):
- """
- Example implementation of a device control object.
-
- The device object may by any custom implementation to support
- specific devices, but it must implement the functions:
- has_control_field
- set_control_fields
- """
-
- def __init__(self, nodeId):
- self.nodeId = nodeId
- self.control_fields = {}
-
- def has_control_field(self, field, typename):
- """
- Returns true if the supplied field name exists
- and the type matches for control in this device.
-
- Arguments:
- field -- The field name
- typename -- The expected type
- """
- if field in self.control_fields and self.control_fields[field]["type"] == typename:
- return True
- return False
-
- def set_control_fields(self, fields, session, callback):
- """
- Starts a control setting procedure. Verifies the fields,
- sets the data and (if needed) and calls the callback.
-
- Arguments:
- fields -- List of control fields in tuple format:
- (name, typename, value)
- session -- Session id, only used in the callback as identifier
- callback -- Callback function to call when control set is complete.
-
- The callback function must support the following arguments:
-
- session -- Session id, as supplied in the
- request_fields call
- nodeId -- Identifier for this device
- result -- The current result status of the readout.
- Valid values are:
- "error" - Set fields failed.
- "ok" - All fields were set.
- error_field -- [optional] Only applies when result == "error"
- The field name that failed
- (usually means it is missing)
- error_msg -- [optional] Only applies when result == "error".
- Error details when a request failed.
- """
-
- if len(fields) > 0:
- # Check availiability
- for name, typename, value in fields:
- if not self.has_control_field(name, typename):
- self._send_control_reject(session, name, "NotFound", callback)
- return False
-
- for name, typename, value in fields:
- self._set_field_value(name, value)
-
- callback(session, result="ok", nodeId=self.nodeId)
- return True
-
- def _send_control_reject(self, session, field, message, callback):
- """
- Sends a reject to the caller
-
- Arguments:
- session -- Session id, see definition in
- set_control_fields function
- callback -- Callback function, see definition in
- set_control_fields function
- """
- callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message)
-
- def _add_control_field(self, name, typename, value):
- """
- Adds a control field to the device
-
- Arguments:
- name -- Name of the field
- typename -- Type of the field, one of:
- (boolean, color, string, date, dateTime,
- double, duration, int, long, time)
- value -- Field value
- """
- self.control_fields[name] = {"type": typename, "value": value}
-
- def _set_field_value(self, name, value):
- """
- Set the value of a control field
-
- Arguments:
- name -- Name of the field
- value -- New value for the field
- """
- if name in self.control_fields:
- self.control_fields[name]["value"] = value
-
- def _get_field_value(self, name):
- """
- Get the value of a control field. Only used for unit testing.
-
- Arguments:
- name -- Name of the field
- """
- if name in self.control_fields:
- return self.control_fields[name]["value"]
- return None
diff --git a/sleekxmpp/plugins/xep_0325/stanza/__init__.py b/sleekxmpp/plugins/xep_0325/stanza/__init__.py
deleted file mode 100644
index 746c2033..00000000
--- a/sleekxmpp/plugins/xep_0325/stanza/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0325.stanza.control import *
-
diff --git a/sleekxmpp/plugins/xep_0325/stanza/base.py b/sleekxmpp/plugins/xep_0325/stanza/base.py
deleted file mode 100644
index 1dadcf46..00000000
--- a/sleekxmpp/plugins/xep_0325/stanza/base.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ET
-
-pass
diff --git a/sleekxmpp/plugins/xep_0325/stanza/control.py b/sleekxmpp/plugins/xep_0325/stanza/control.py
deleted file mode 100644
index 1fd5c35d..00000000
--- a/sleekxmpp/plugins/xep_0325/stanza/control.py
+++ /dev/null
@@ -1,527 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of xeps for Internet of Things
- http://wiki.xmpp.org/web/Tech_pages/IoT_systems
- Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp import Iq, Message
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
-from re import match
-
-class Control(ElementBase):
- """ Placeholder for the namespace, not used as a stanza """
- namespace = 'urn:xmpp:iot:control'
- name = 'control'
- plugin_attrib = name
- interfaces = set(tuple())
-
-class ControlSet(ElementBase):
- namespace = 'urn:xmpp:iot:control'
- name = 'set'
- plugin_attrib = name
- interfaces = set(['nodes','datas'])
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._nodes = set()
- self._datas = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._nodes = set([node['nodeId'] for node in self['nodes']])
- self._datas = set([data['name'] for data in self['datas']])
-
- def add_node(self, nodeId, sourceId=None, cacheType=None):
- """
- Add a new node element. Each item is required to have a
- nodeId, but may also specify a sourceId value and cacheType.
-
- Arguments:
- nodeId -- The ID for the node.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- if nodeId not in self._nodes:
- self._nodes.add((nodeId))
- node = RequestNode(parent=self)
- node['nodeId'] = nodeId
- node['sourceId'] = sourceId
- node['cacheType'] = cacheType
- self.iterables.append(node)
- return node
- return None
-
- def del_node(self, nodeId):
- """
- Remove a single node.
-
- Arguments:
- nodeId -- Node ID of the item to remove.
- """
- if nodeId in self._nodes:
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- if node['nodeId'] == nodeId:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
- return True
- return False
-
- def get_nodes(self):
- """Return all nodes."""
- nodes = []
- for node in self['substanzas']:
- if isinstance(node, RequestNode):
- nodes.append(node)
- return nodes
-
- def set_nodes(self, nodes):
- """
- Set or replace all nodes. The given nodes must be in a
- list or set where each item is a tuple of the form:
- (nodeId, sourceId, cacheType)
-
- Arguments:
- nodes -- A series of nodes in tuple format.
- """
- self.del_nodes()
- for node in nodes:
- if isinstance(node, RequestNode):
- self.add_node(node['nodeId'], node['sourceId'], node['cacheType'])
- else:
- nodeId, sourceId, cacheType = node
- self.add_node(nodeId, sourceId, cacheType)
-
- def del_nodes(self):
- """Remove all nodes."""
- self._nodes = set()
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
-
-
- def add_data(self, name, typename, value):
- """
- Add a new data element.
-
- Arguments:
- name -- The name of the data element
- typename -- The type of data element
- (boolean, color, string, date, dateTime,
- double, duration, int, long, time)
- value -- The value of the data element
- """
- if name not in self._datas:
- dataObj = None
- if typename == "boolean":
- dataObj = BooleanParameter(parent=self)
- elif typename == "color":
- dataObj = ColorParameter(parent=self)
- elif typename == "string":
- dataObj = StringParameter(parent=self)
- elif typename == "date":
- dataObj = DateParameter(parent=self)
- elif typename == "dateTime":
- dataObj = DateTimeParameter(parent=self)
- elif typename == "double":
- dataObj = DoubleParameter(parent=self)
- elif typename == "duration":
- dataObj = DurationParameter(parent=self)
- elif typename == "int":
- dataObj = IntParameter(parent=self)
- elif typename == "long":
- dataObj = LongParameter(parent=self)
- elif typename == "time":
- dataObj = TimeParameter(parent=self)
-
- dataObj['name'] = name
- dataObj['value'] = value
-
- self._datas.add(name)
- self.iterables.append(dataObj)
- return dataObj
- return None
-
- def del_data(self, name):
- """
- Remove a single data element.
-
- Arguments:
- data_name -- The data element name to remove.
- """
- if name in self._datas:
- datas = [i for i in self.iterables if isinstance(i, BaseParameter)]
- for data in datas:
- if data['name'] == name:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
- return True
- return False
-
- def get_datas(self):
- """ Return all data elements. """
- datas = []
- for data in self['substanzas']:
- if isinstance(data, BaseParameter):
- datas.append(data)
- return datas
-
- def set_datas(self, datas):
- """
- Set or replace all data elements. The given elements must be in a
- list or set where each item is a data element (numeric, string, boolean, dateTime, timeSpan or enum)
-
- Arguments:
- datas -- A series of data elements.
- """
- self.del_datas()
- for data in datas:
- self.add_data(name=data['name'], typename=data._get_typename(), value=data['value'])
-
- def del_datas(self):
- """Remove all data elements."""
- self._datas = set()
- datas = [i for i in self.iterables if isinstance(i, BaseParameter)]
- for data in datas:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
-
-
-class RequestNode(ElementBase):
- """ Node element in a request """
- namespace = 'urn:xmpp:iot:control'
- name = 'node'
- plugin_attrib = name
- interfaces = set(['nodeId','sourceId','cacheType'])
-
-
-class ControlSetResponse(ElementBase):
- namespace = 'urn:xmpp:iot:control'
- name = 'setResponse'
- plugin_attrib = name
- interfaces = set(['responseCode'])
-
- def __init__(self, xml=None, parent=None):
- ElementBase.__init__(self, xml, parent)
- self._nodes = set()
- self._datas = set()
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup
-
- Caches item information.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- ElementBase.setup(self, xml)
- self._nodes = set([node['nodeId'] for node in self['nodes']])
- self._datas = set([data['name'] for data in self['datas']])
-
- def add_node(self, nodeId, sourceId=None, cacheType=None):
- """
- Add a new node element. Each item is required to have a
- nodeId, but may also specify a sourceId value and cacheType.
-
- Arguments:
- nodeId -- The ID for the node.
- sourceId -- [optional] identifying the data source controlling the device
- cacheType -- [optional] narrowing down the search to a specific kind of node
- """
- if nodeId not in self._nodes:
- self._nodes.add(nodeId)
- node = RequestNode(parent=self)
- node['nodeId'] = nodeId
- node['sourceId'] = sourceId
- node['cacheType'] = cacheType
- self.iterables.append(node)
- return node
- return None
-
- def del_node(self, nodeId):
- """
- Remove a single node.
-
- Arguments:
- nodeId -- Node ID of the item to remove.
- """
- if nodeId in self._nodes:
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- if node['nodeId'] == nodeId:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
- return True
- return False
-
- def get_nodes(self):
- """Return all nodes."""
- nodes = []
- for node in self['substanzas']:
- if isinstance(node, RequestNode):
- nodes.append(node)
- return nodes
-
- def set_nodes(self, nodes):
- """
- Set or replace all nodes. The given nodes must be in a
- list or set where each item is a tuple of the form:
- (nodeId, sourceId, cacheType)
-
- Arguments:
- nodes -- A series of nodes in tuple format.
- """
- self.del_nodes()
- for node in nodes:
- if isinstance(node, RequestNode):
- self.add_node(node['nodeId'], node['sourceId'], node['cacheType'])
- else:
- nodeId, sourceId, cacheType = node
- self.add_node(nodeId, sourceId, cacheType)
-
- def del_nodes(self):
- """Remove all nodes."""
- self._nodes = set()
- nodes = [i for i in self.iterables if isinstance(i, RequestNode)]
- for node in nodes:
- self.xml.remove(node.xml)
- self.iterables.remove(node)
-
-
- def add_data(self, name):
- """
- Add a new ResponseParameter element.
-
- Arguments:
- name -- Name of the parameter
- """
- if name not in self._datas:
- self._datas.add(name)
- data = ResponseParameter(parent=self)
- data['name'] = name
- self.iterables.append(data)
- return data
- return None
-
- def del_data(self, name):
- """
- Remove a single ResponseParameter element.
-
- Arguments:
- name -- The data element name to remove.
- """
- if name in self._datas:
- datas = [i for i in self.iterables if isinstance(i, ResponseParameter)]
- for data in datas:
- if data['name'] == name:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
- return True
- return False
-
- def get_datas(self):
- """ Return all ResponseParameter elements. """
- datas = set()
- for data in self['substanzas']:
- if isinstance(data, ResponseParameter):
- datas.add(data)
- return datas
-
- def set_datas(self, datas):
- """
- Set or replace all data elements. The given elements must be in a
- list or set of ResponseParameter elements
-
- Arguments:
- datas -- A series of data element names.
- """
- self.del_datas()
- for data in datas:
- self.add_data(name=data['name'])
-
- def del_datas(self):
- """Remove all ResponseParameter elements."""
- self._datas = set()
- datas = [i for i in self.iterables if isinstance(i, ResponseParameter)]
- for data in datas:
- self.xml.remove(data.xml)
- self.iterables.remove(data)
-
-
-class Error(ElementBase):
- namespace = 'urn:xmpp:iot:control'
- name = 'error'
- plugin_attrib = name
- interfaces = set(['var','text'])
-
- def get_text(self):
- """Return then contents inside the XML tag."""
- return self.xml.text
-
- def set_text(self, value):
- """Set then contents inside the XML tag.
-
- Arguments:
- value -- string
- """
-
- self.xml.text = value
- return self
-
- def del_text(self):
- """Remove the contents inside the XML tag."""
- self.xml.text = ""
- return self
-
-class ResponseParameter(ElementBase):
- """
- Parameter element in ControlSetResponse.
- """
- namespace = 'urn:xmpp:iot:control'
- name = 'parameter'
- plugin_attrib = name
- interfaces = set(['name'])
-
-
-class BaseParameter(ElementBase):
- """
- Parameter element in SetCommand. This is a base class,
- all instances of parameters added to SetCommand must be of types:
- BooleanParameter
- ColorParameter
- StringParameter
- DateParameter
- DateTimeParameter
- DoubleParameter
- DurationParameter
- IntParameter
- LongParameter
- TimeParameter
- """
- namespace = 'urn:xmpp:iot:control'
- name = 'baseParameter'
- plugin_attrib = name
- interfaces = set(['name','value'])
-
- def _get_typename(self):
- return self.name
-
-
-class BooleanParameter(BaseParameter):
- """
- Field data of type boolean.
- Note that the value is expressed as a string.
- """
- name = 'boolean'
- plugin_attrib = name
-
-class ColorParameter(BaseParameter):
- """
- Field data of type color.
- Note that the value is expressed as a string.
- """
- name = 'color'
- plugin_attrib = name
-
-class StringParameter(BaseParameter):
- """
- Field data of type string.
- """
- name = 'string'
- plugin_attrib = name
-
-class DateParameter(BaseParameter):
- """
- Field data of type date.
- Note that the value is expressed as a string.
- """
- name = 'date'
- plugin_attrib = name
-
-class DateTimeParameter(BaseParameter):
- """
- Field data of type dateTime.
- Note that the value is expressed as a string.
- """
- name = 'dateTime'
- plugin_attrib = name
-
-class DoubleParameter(BaseParameter):
- """
- Field data of type double.
- Note that the value is expressed as a string.
- """
- name = 'double'
- plugin_attrib = name
-
-class DurationParameter(BaseParameter):
- """
- Field data of type duration.
- Note that the value is expressed as a string.
- """
- name = 'duration'
- plugin_attrib = name
-
-class IntParameter(BaseParameter):
- """
- Field data of type int.
- Note that the value is expressed as a string.
- """
- name = 'int'
- plugin_attrib = name
-
-class LongParameter(BaseParameter):
- """
- Field data of type long (64-bit int).
- Note that the value is expressed as a string.
- """
- name = 'long'
- plugin_attrib = name
-
-class TimeParameter(BaseParameter):
- """
- Field data of type time.
- Note that the value is expressed as a string.
- """
- name = 'time'
- plugin_attrib = name
-
-register_stanza_plugin(Iq, ControlSet)
-register_stanza_plugin(Message, ControlSet)
-
-register_stanza_plugin(ControlSet, RequestNode, iterable=True)
-
-register_stanza_plugin(ControlSet, BooleanParameter, iterable=True)
-register_stanza_plugin(ControlSet, ColorParameter, iterable=True)
-register_stanza_plugin(ControlSet, StringParameter, iterable=True)
-register_stanza_plugin(ControlSet, DateParameter, iterable=True)
-register_stanza_plugin(ControlSet, DateTimeParameter, iterable=True)
-register_stanza_plugin(ControlSet, DoubleParameter, iterable=True)
-register_stanza_plugin(ControlSet, DurationParameter, iterable=True)
-register_stanza_plugin(ControlSet, IntParameter, iterable=True)
-register_stanza_plugin(ControlSet, LongParameter, iterable=True)
-register_stanza_plugin(ControlSet, TimeParameter, iterable=True)
-
-register_stanza_plugin(Iq, ControlSetResponse)
-register_stanza_plugin(ControlSetResponse, Error)
-register_stanza_plugin(ControlSetResponse, RequestNode, iterable=True)
-register_stanza_plugin(ControlSetResponse, ResponseParameter, iterable=True)
-
diff --git a/sleekxmpp/plugins/xep_0332/__init__.py b/sleekxmpp/plugins/xep_0332/__init__.py
deleted file mode 100644
index 27755faa..00000000
--- a/sleekxmpp/plugins/xep_0332/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0332 import stanza
-from sleekxmpp.plugins.xep_0332.http import XEP_0332
-
-
-register_plugin(XEP_0332)
diff --git a/sleekxmpp/plugins/xep_0332/http.py b/sleekxmpp/plugins/xep_0332/http.py
deleted file mode 100644
index 70bcafa6..00000000
--- a/sleekxmpp/plugins/xep_0332/http.py
+++ /dev/null
@@ -1,159 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-
-from sleekxmpp.plugins.base import BasePlugin
-from sleekxmpp.plugins.xep_0332.stanza import (
- HTTPRequest, HTTPResponse, HTTPData
-)
-from sleekxmpp.plugins.xep_0131.stanza import Headers
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0332(BasePlugin):
- """
- XEP-0332: HTTP over XMPP transport
- """
-
- name = 'xep_0332'
- description = 'XEP-0332: HTTP over XMPP transport'
-
- #: xep_0047 not included.
- #: xep_0001, 0137 and 0166 are missing
- dependencies = set(['xep_0030', 'xep_0131'])
-
- #: TODO: Do we really need to mention the supported_headers?!
- default_config = {
- 'supported_headers': set([
- 'Content-Length', 'Transfer-Encoding', 'DateTime',
- 'Accept-Charset', 'Location', 'Content-ID', 'Description',
- 'Content-Language', 'Content-Transfer-Encoding', 'Timestamp',
- 'Expires', 'User-Agent', 'Host', 'Proxy-Authorization', 'Date',
- 'WWW-Authenticate', 'Accept-Encoding', 'Server', 'Error-Info',
- 'Identifier', 'Content-Location', 'Content-Encoding', 'Distribute',
- 'Accept', 'Proxy-Authenticate', 'ETag', 'Expect', 'Content-Type'
- ])
- }
-
- def plugin_init(self):
- self.xmpp.register_handler(
- Callback(
- 'HTTP Request',
- StanzaPath('iq/http-req'),
- self._handle_request
- )
- )
- self.xmpp.register_handler(
- Callback(
- 'HTTP Response',
- StanzaPath('iq/http-resp'),
- self._handle_response
- )
- )
- register_stanza_plugin(Iq, HTTPRequest, iterable=True)
- register_stanza_plugin(Iq, HTTPResponse, iterable=True)
- register_stanza_plugin(HTTPRequest, Headers, iterable=True)
- register_stanza_plugin(HTTPRequest, HTTPData, iterable=True)
- register_stanza_plugin(HTTPResponse, Headers, iterable=True)
- register_stanza_plugin(HTTPResponse, HTTPData, iterable=True)
- # TODO: Should we register any api's here? self.api.register()
-
- def plugin_end(self):
- self.xmpp.remove_handler('HTTP Request')
- self.xmpp.remove_handler('HTTP Response')
- self.xmpp['xep_0030'].del_feature('urn:xmpp:http')
- for header in self.supported_headers:
- self.xmpp['xep_0030'].del_feature(
- feature='%s#%s' % (Headers.namespace, header)
- )
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:http')
- for header in self.supported_headers:
- self.xmpp['xep_0030'].add_feature(
- '%s#%s' % (Headers.namespace, header)
- )
- # TODO: Do we need to add the supported headers to xep_0131?
- # self.xmpp['xep_0131'].supported_headers.add(header)
-
- def _handle_request(self, iq):
- self.xmpp.event('http_request', iq)
-
- def _handle_response(self, iq):
- self.xmpp.event('http_response', iq)
-
- def send_request(self, to=None, method=None, resource=None, headers=None,
- data=None, **kwargs):
- iq = self.xmpp.Iq()
- iq['from'] = self.xmpp.boundjid
- iq['to'] = to
- iq['type'] = 'set'
- iq['http-req']['headers'] = headers
- iq['http-req']['method'] = method
- iq['http-req']['resource'] = resource
- iq['http-req']['version'] = '1.1' # TODO: set this implicitly
- if 'id' in kwargs:
- iq['id'] = kwargs["id"]
- if data is not None:
- iq['http-req']['data'] = data
- return iq.send(
- timeout=kwargs.get('timeout', None),
- block=kwargs.get('block', True),
- callback=kwargs.get('callback', None),
- timeout_callback=kwargs.get('timeout_callback', None)
- )
-
- def send_response(self, to=None, code=None, message=None, headers=None,
- data=None, **kwargs):
- iq = self.xmpp.Iq()
- iq['from'] = self.xmpp.boundjid
- iq['to'] = to
- iq['type'] = 'result'
- iq['http-resp']['headers'] = headers
- iq['http-resp']['code'] = code
- iq['http-resp']['message'] = message
- iq['http-resp']['version'] = '1.1' # TODO: set this implicitly
- if 'id' in kwargs:
- iq['id'] = kwargs["id"]
- if data is not None:
- iq['http-resp']['data'] = data
- return iq.send(
- timeout=kwargs.get('timeout', None),
- block=kwargs.get('block', True),
- callback=kwargs.get('callback', None),
- timeout_callback=kwargs.get('timeout_callback', None)
- )
-
- def send_error(self, to=None, ecode='500', etype='wait',
- econd='internal-server-error', **kwargs):
- iq = self.xmpp.Iq()
- iq['from'] = self.xmpp.boundjid
- iq['to'] = to
- iq['type'] = 'error'
- iq['error']['code'] = ecode
- iq['error']['type'] = etype
- iq['error']['condition'] = econd
- if 'id' in kwargs:
- iq['id'] = kwargs["id"]
- return iq.send(
- timeout=kwargs.get('timeout', None),
- block=kwargs.get('block', True),
- callback=kwargs.get('callback', None),
- timeout_callback=kwargs.get('timeout_callback', None)
- )
diff --git a/sleekxmpp/plugins/xep_0332/stanza/__init__.py b/sleekxmpp/plugins/xep_0332/stanza/__init__.py
deleted file mode 100644
index 201824b7..00000000
--- a/sleekxmpp/plugins/xep_0332/stanza/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.xep_0332.stanza.request import HTTPRequest
-from sleekxmpp.plugins.xep_0332.stanza.response import HTTPResponse
-from sleekxmpp.plugins.xep_0332.stanza.data import HTTPData
diff --git a/sleekxmpp/plugins/xep_0332/stanza/data.py b/sleekxmpp/plugins/xep_0332/stanza/data.py
deleted file mode 100644
index a3678038..00000000
--- a/sleekxmpp/plugins/xep_0332/stanza/data.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class HTTPData(ElementBase):
- """
- The data element.
- """
- name = 'data'
- namespace = 'urn:xmpp:http'
- interfaces = set(['data'])
- plugin_attrib = 'data'
- is_extension = True
-
- def get_data(self, encoding='text'):
- data = self._get_sub_text(encoding, None)
- return str(data) if data is not None else data
-
- def set_data(self, data, encoding='text'):
- self._set_sub_text(encoding, text=data)
-
diff --git a/sleekxmpp/plugins/xep_0332/stanza/request.py b/sleekxmpp/plugins/xep_0332/stanza/request.py
deleted file mode 100644
index 9a298e57..00000000
--- a/sleekxmpp/plugins/xep_0332/stanza/request.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class HTTPRequest(ElementBase):
-
- """
- All HTTP communication is done using the `Request`/`Response` paradigm.
- Each HTTP Request is made sending an `iq` stanza containing a `req`
- element to the server. Each `iq` stanza sent is of type `set`.
-
- Examples:
- <iq type='set' from='a@b.com/browser' to='x@y.com' id='1'>
- <req xmlns='urn:xmpp:http'
- method='GET'
- resource='/api/users'
- version='1.1'>
- <headers xmlns='http://jabber.org/protocol/shim'>
- <header name='Host'>b.com</header>
- </headers>
- </req>
- </iq>
-
- <iq type='set' from='a@b.com/browser' to='x@y.com' id='2'>
- <req xmlns='urn:xmpp:http'
- method='PUT'
- resource='/api/users'
- version='1.1'>
- <headers xmlns='http://jabber.org/protocol/shim'>
- <header name='Host'>b.com</header>
- <header name='Content-Type'>text/html</header>
- <header name='Content-Length'>...</header>
- </headers>
- <data>
- <text>...</text>
- </data>
- </req>
- </iq>
- """
-
- name = 'request'
- namespace = 'urn:xmpp:http'
- interfaces = set(['method', 'resource', 'version'])
- plugin_attrib = 'http-req'
-
- def get_method(self):
- return self._get_attr('method', None)
-
- def set_method(self, method):
- self._set_attr('method', method)
-
- def get_resource(self):
- return self._get_attr('resource', None)
-
- def set_resource(self, resource):
- self._set_attr('resource', resource)
-
- def get_version(self):
- return self._get_attr('version', None)
-
- def set_version(self, version='1.1'):
- self._set_attr('version', version)
diff --git a/sleekxmpp/plugins/xep_0332/stanza/response.py b/sleekxmpp/plugins/xep_0332/stanza/response.py
deleted file mode 100644
index 6804ade9..00000000
--- a/sleekxmpp/plugins/xep_0332/stanza/response.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Implementation of HTTP over XMPP transport
- http://xmpp.org/extensions/xep-0332.html
- Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class HTTPResponse(ElementBase):
-
- """
- When the HTTP Server responds, it does so by sending an `iq` stanza
- response (type=`result`) back to the client containing the `resp` element.
- Since response are asynchronous, and since multiple requests may be active
- at the same time, responses may be returned in a different order than the
- in which the original requests were made.
-
- Examples:
- <iq type='result'
- from='httpserver@clayster.com'
- to='httpclient@clayster.com/browser' id='2'>
- <resp xmlns='urn:xmpp:http'
- version='1.1'
- statusCode='200'
- statusMessage='OK'>
- <headers xmlns='http://jabber.org/protocol/shim'>
- <header name='Date'>Fri, 03 May 2013 16:39:54GMT-4</header>
- <header name='Server'>Clayster</header>
- <header name='Content-Type'>text/turtle</header>
- <header name='Content-Length'>...</header>
- <header name='Connection'>Close</header>
- </headers>
- <data>
- <text>
- ...
- </text>
- </data>
- </resp>
- </iq>
- """
-
- name = 'response'
- namespace = 'urn:xmpp:http'
- interfaces = set(['code', 'message', 'version'])
- plugin_attrib = 'http-resp'
-
- def get_code(self):
- code = self._get_attr('statusCode', None)
- return int(code) if code is not None else code
-
- def set_code(self, code):
- self._set_attr('statusCode', str(code))
-
- def get_message(self):
- return self._get_attr('statusMessage', '')
-
- def set_message(self, message):
- self._set_attr('statusMessage', message)
-
- def set_version(self, version='1.1'):
- self._set_attr('version', version)
diff --git a/sleekxmpp/roster/__init__.py b/sleekxmpp/roster/__init__.py
deleted file mode 100644
index 18b380c9..00000000
--- a/sleekxmpp/roster/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.roster.item import RosterItem
-from sleekxmpp.roster.single import RosterNode
-from sleekxmpp.roster.multi import Roster
diff --git a/sleekxmpp/roster/item.py b/sleekxmpp/roster/item.py
deleted file mode 100644
index ae194e0a..00000000
--- a/sleekxmpp/roster/item.py
+++ /dev/null
@@ -1,497 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-class RosterItem(object):
-
- """
- A RosterItem is a single entry in a roster node, and tracks
- the subscription state and user annotations of a single JID.
-
- Roster items may use an external datastore to persist roster data
- across sessions. Client applications will not need to use this
- functionality, but is intended for components that do not have their
- roster persisted automatically by the XMPP server.
-
- Roster items provide many methods for handling incoming presence
- stanzas that ensure that response stanzas are sent according to
- RFC 3921.
-
- The external datastore is accessed through a provided interface
- object which is stored in self.db. The interface object MUST
- provide two methods: load and save, both of which are responsible
- for working with a single roster item. A private dictionary,
- self._db_state, is used to store any metadata needed by the
- interface, such as the row ID of a roster item, etc.
-
- Interface for self.db.load:
- load(owner_jid, jid, db_state):
- owner_jid -- The JID that owns the roster.
- jid -- The JID of the roster item.
- db_state -- A dictionary containing any data saved
- by the interface object after a save()
- call. Will typically have the equivalent
- of a 'row_id' value.
-
- Interface for self.db.save:
- save(owner_jid, jid, item_state, db_state):
- owner_jid -- The JID that owns the roster.
- jid -- The JID of the roster item.
- item_state -- A dictionary containing the fields:
- 'from', 'to', 'pending_in', 'pending_out',
- 'whitelisted', 'subscription', 'name',
- and 'groups'.
- db_state -- A dictionary provided for persisting
- datastore specific information. Typically,
- a value equivalent to 'row_id' will be
- stored here.
-
- State Fields:
- from -- Indicates if a subscription of type 'from'
- has been authorized.
- to -- Indicates if a subscription of type 'to' has
- been authorized.
- pending_in -- Indicates if a subscription request has been
- received from this JID and it has not been
- authorized yet.
- pending_out -- Indicates if a subscription request has been sent
- to this JID and it has not been accepted yet.
- subscription -- Returns one of: 'to', 'from', 'both', or 'none'
- based on the states of from, to, pending_in,
- and pending_out. Assignment to this value does
- not affect the states of the other values.
- whitelisted -- Indicates if a subscription request from this
- JID should be automatically accepted.
- name -- A user supplied alias for the JID.
- groups -- A list of group names for the JID.
-
- Attributes:
- xmpp -- The main SleekXMPP instance.
- owner -- The JID that owns the roster.
- jid -- The JID for the roster item.
- db -- Optional datastore interface object.
- last_status -- The last presence sent to this JID.
- resources -- A dictionary of online resources for this JID.
- Will contain the fields 'show', 'status',
- and 'priority'.
-
- Methods:
- load -- Retrieve the roster item from an
- external datastore, if one was provided.
- save -- Save the roster item to an external
- datastore, if one was provided.
- remove -- Remove a subscription to the JID and revoke
- its whitelisted status.
- subscribe -- Subscribe to the JID.
- authorize -- Accept a subscription from the JID.
- unauthorize -- Deny a subscription from the JID.
- unsubscribe -- Unsubscribe from the JID.
- send_presence -- Send a directed presence to the JID.
- send_last_presence -- Resend the last sent presence.
- handle_available -- Update the JID's resource information.
- handle_unavailable -- Update the JID's resource information.
- handle_subscribe -- Handle a subscription request.
- handle_subscribed -- Handle a notice that a subscription request
- was authorized by the JID.
- handle_unsubscribe -- Handle an unsubscribe request.
- handle_unsubscribed -- Handle a notice that a subscription was
- removed by the JID.
- handle_probe -- Handle a presence probe query.
- """
-
- def __init__(self, xmpp, jid, owner=None,
- state=None, db=None, roster=None):
- """
- Create a new roster item.
-
- Arguments:
- xmpp -- The main SleekXMPP instance.
- jid -- The item's JID.
- owner -- The roster owner's JID. Defaults
- so self.xmpp.boundjid.bare.
- state -- A dictionary of initial state values.
- db -- An optional interface to an external datastore.
- roster -- The roster object containing this entry.
- """
- self.xmpp = xmpp
- self.jid = jid
- self.owner = owner or self.xmpp.boundjid.bare
- self.last_status = None
- self.resources = {}
- self.roster = roster
- self.db = db
- self._state = state or {
- 'from': False,
- 'to': False,
- 'pending_in': False,
- 'pending_out': False,
- 'whitelisted': False,
- 'subscription': 'none',
- 'name': '',
- 'groups': []}
-
- self._db_state = {}
- self.load()
-
- def set_backend(self, db=None, save=True):
- """
- Set the datastore interface object for the roster item.
-
- Arguments:
- db -- The new datastore interface.
- save -- If True, save the existing state to the new
- backend datastore. Defaults to True.
- """
- self.db = db
- if save:
- self.save()
- self.load()
-
- def load(self):
- """
- Load the item's state information from an external datastore,
- if one has been provided.
- """
- if self.db:
- item = self.db.load(self.owner, self.jid,
- self._db_state)
- if item:
- self['name'] = item['name']
- self['groups'] = item['groups']
- self['from'] = item['from']
- self['to'] = item['to']
- self['whitelisted'] = item['whitelisted']
- self['pending_out'] = item['pending_out']
- self['pending_in'] = item['pending_in']
- self['subscription'] = self._subscription()
- return self._state
- return None
-
- def save(self, remove=False):
- """
- Save the item's state information to an external datastore,
- if one has been provided.
-
- Arguments:
- remove -- If True, expunge the item from the datastore.
- """
- self['subscription'] = self._subscription()
- if remove:
- self._state['removed'] = True
- if self.db:
- self.db.save(self.owner, self.jid,
- self._state, self._db_state)
-
- # Finally, remove the in-memory copy if needed.
- if remove:
- del self.xmpp.roster[self.owner][self.jid]
-
- def __getitem__(self, key):
- """Return a state field's value."""
- if key in self._state:
- if key == 'subscription':
- return self._subscription()
- return self._state[key]
- else:
- raise KeyError
-
- def __setitem__(self, key, value):
- """
- Set the value of a state field.
-
- For boolean states, the values True, 'true', '1', 'on',
- and 'yes' are accepted as True; all others are False.
-
- Arguments:
- key -- The state field to modify.
- value -- The new value of the state field.
- """
- if key in self._state:
- if key in ['name', 'subscription', 'groups']:
- self._state[key] = value
- else:
- value = str(value).lower()
- self._state[key] = value in ('true', '1', 'on', 'yes')
- else:
- raise KeyError
-
- def _subscription(self):
- """Return the proper subscription type based on current state."""
- if self['to'] and self['from']:
- return 'both'
- elif self['from']:
- return 'from'
- elif self['to']:
- return 'to'
- else:
- return 'none'
-
- def remove(self):
- """
- Remove a JID's whitelisted status and unsubscribe if a
- subscription exists.
- """
- if self['to']:
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'unsubscribe'
- if self.xmpp.is_component:
- p['from'] = self.owner
- p.send()
- self['to'] = False
- self['whitelisted'] = False
- self.save()
-
- def subscribe(self):
- """Send a subscription request to the JID."""
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'subscribe'
- if self.xmpp.is_component:
- p['from'] = self.owner
- self['pending_out'] = True
- self.save()
- p.send()
-
- def authorize(self):
- """Authorize a received subscription request from the JID."""
- self['from'] = True
- self['pending_in'] = False
- self.save()
- self._subscribed()
- self.send_last_presence()
-
- def unauthorize(self):
- """Deny a received subscription request from the JID."""
- self['from'] = False
- self['pending_in'] = False
- self.save()
- self._unsubscribed()
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'unavailable'
- if self.xmpp.is_component:
- p['from'] = self.owner
- p.send()
-
- def _subscribed(self):
- """Handle acknowledging a subscription."""
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'subscribed'
- if self.xmpp.is_component:
- p['from'] = self.owner
- p.send()
-
- def unsubscribe(self):
- """Unsubscribe from the JID."""
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'unsubscribe'
- if self.xmpp.is_component:
- p['from'] = self.owner
- self.save()
- p.send()
-
- def _unsubscribed(self):
- """Handle acknowledging an unsubscribe request."""
- p = self.xmpp.Presence()
- p['to'] = self.jid
- p['type'] = 'unsubscribed'
- if self.xmpp.is_component:
- p['from'] = self.owner
- p.send()
-
- def send_presence(self, **kwargs):
- """
- Create, initialize, and send a Presence stanza.
-
- If no recipient is specified, send the presence immediately.
- Otherwise, forward the send request to the recipient's roster
- entry for processing.
-
- Arguments:
- pshow -- The presence's show value.
- pstatus -- The presence's status message.
- ppriority -- This connections' priority.
- pto -- The recipient of a directed presence.
- pfrom -- The sender of a directed presence, which should
- be the owner JID plus resource.
- ptype -- The type of presence, such as 'subscribe'.
- pnick -- Optional nickname of the presence's sender.
- """
- if self.xmpp.is_component and not kwargs.get('pfrom', ''):
- kwargs['pfrom'] = self.owner
- if not kwargs.get('pto', ''):
- kwargs['pto'] = self.jid
- self.xmpp.send_presence(**kwargs)
-
- def send_last_presence(self):
- if self.last_status is None:
- pres = self.roster.last_status
- if pres is None:
- self.send_presence()
- else:
- pres['to'] = self.jid
- if self.xmpp.is_component:
- pres['from'] = self.owner
- else:
- del pres['from']
- pres.send()
- else:
- self.last_status.send()
-
- def handle_available(self, presence):
- resource = presence['from'].resource
- data = {'status': presence['status'],
- 'show': presence['show'],
- 'priority': presence['priority']}
- got_online = not self.resources
- if resource not in self.resources:
- self.resources[resource] = {}
- old_status = self.resources[resource].get('status', '')
- old_show = self.resources[resource].get('show', None)
- self.resources[resource].update(data)
- if got_online:
- self.xmpp.event('got_online', presence)
- if old_show != presence['show'] or old_status != presence['status']:
- self.xmpp.event('changed_status', presence)
-
- def handle_unavailable(self, presence):
- resource = presence['from'].resource
- if not self.resources:
- return
- if resource in self.resources:
- del self.resources[resource]
- self.xmpp.event('changed_status', presence)
- if not self.resources:
- self.xmpp.event('got_offline', presence)
-
- def handle_subscribe(self, presence):
- """
- +------------------------------------------------------------------+
- | EXISTING STATE | DELIVER? | NEW STATE |
- +------------------------------------------------------------------+
- | "None" | yes | "None + Pending In" |
- | "None + Pending Out" | yes | "None + Pending Out/In" |
- | "None + Pending In" | no | no state change |
- | "None + Pending Out/In" | no | no state change |
- | "To" | yes | "To + Pending In" |
- | "To + Pending In" | no | no state change |
- | "From" | no * | no state change |
- | "From + Pending Out" | no * | no state change |
- | "Both" | no * | no state change |
- +------------------------------------------------------------------+
- """
- if self.xmpp.is_component:
- if not self['from'] and not self['pending_in']:
- self['pending_in'] = True
- self.xmpp.event('roster_subscription_request', presence)
- elif self['from']:
- self._subscribed()
- self.save()
- else:
- #server shouldn't send an invalid subscription request
- self.xmpp.event('roster_subscription_request', presence)
-
- def handle_subscribed(self, presence):
- """
- +------------------------------------------------------------------+
- | EXISTING STATE | DELIVER? | NEW STATE |
- +------------------------------------------------------------------+
- | "None" | no | no state change |
- | "None + Pending Out" | yes | "To" |
- | "None + Pending In" | no | no state change |
- | "None + Pending Out/In" | yes | "To + Pending In" |
- | "To" | no | no state change |
- | "To + Pending In" | no | no state change |
- | "From" | no | no state change |
- | "From + Pending Out" | yes | "Both" |
- | "Both" | no | no state change |
- +------------------------------------------------------------------+
- """
- if self.xmpp.is_component:
- if not self['to'] and self['pending_out']:
- self['pending_out'] = False
- self['to'] = True
- self.xmpp.event('roster_subscription_authorized', presence)
- self.save()
- else:
- self.xmpp.event('roster_subscription_authorized', presence)
-
- def handle_unsubscribe(self, presence):
- """
- +------------------------------------------------------------------+
- | EXISTING STATE | DELIVER? | NEW STATE |
- +------------------------------------------------------------------+
- | "None" | no | no state change |
- | "None + Pending Out" | no | no state change |
- | "None + Pending In" | yes * | "None" |
- | "None + Pending Out/In" | yes * | "None + Pending Out" |
- | "To" | no | no state change |
- | "To + Pending In" | yes * | "To" |
- | "From" | yes * | "None" |
- | "From + Pending Out" | yes * | "None + Pending Out |
- | "Both" | yes * | "To" |
- +------------------------------------------------------------------+
- """
- if self.xmpp.is_component:
- if not self['from'] and self['pending_in']:
- self['pending_in'] = False
- self._unsubscribed()
- elif self['from']:
- self['from'] = False
- self._unsubscribed()
- self.xmpp.event('roster_subscription_remove', presence)
- self.save()
- else:
- self.xmpp.event('roster_subscription_remove', presence)
-
- def handle_unsubscribed(self, presence):
- """
- +------------------------------------------------------------------+
- | EXISTING STATE | DELIVER? | NEW STATE |
- +------------------------------------------------------------------+
- | "None" | no | no state change |
- | "None + Pending Out" | yes | "None" |
- | "None + Pending In" | no | no state change |
- | "None + Pending Out/In" | yes | "None + Pending In" |
- | "To" | yes | "None" |
- | "To + Pending In" | yes | "None + Pending In" |
- | "From" | no | no state change |
- | "From + Pending Out" | yes | "From" |
- | "Both" | yes | "From" |
- +------------------------------------------------------------------
- """
- if self.xmpp.is_component:
- if not self['to'] and self['pending_out']:
- self['pending_out'] = False
- elif self['to'] and not self['pending_out']:
- self['to'] = False
- self.xmpp.event('roster_subscription_removed', presence)
- self.save()
- else:
- self.xmpp.event('roster_subscription_removed', presence)
-
- def handle_probe(self, presence):
- if self['from']:
- self.send_last_presence()
- if self['pending_out']:
- self.subscribe()
- if not self['from']:
- self._unsubscribed()
-
- def reset(self):
- """
- Forgot current resource presence information as part of
- a roster reset request.
- """
- self.resources = {}
-
- def __repr__(self):
- return repr(self._state)
diff --git a/sleekxmpp/roster/multi.py b/sleekxmpp/roster/multi.py
deleted file mode 100644
index 5d070ec8..00000000
--- a/sleekxmpp/roster/multi.py
+++ /dev/null
@@ -1,224 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Presence
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.roster import RosterNode
-
-
-class Roster(object):
-
- """
- SleekXMPP's roster manager.
-
- The roster is divided into "nodes", where each node is responsible
- for a single JID. While the distinction is not strictly necessary
- for client connections, it is a necessity for components that use
- multiple JIDs.
-
- Rosters may be stored and persisted in an external datastore. An
- interface object to the datastore that loads and saves roster items may
- be provided. See the documentation for the RosterItem class for the
- methods that the datastore interface object must provide.
-
- Attributes:
- xmpp -- The main SleekXMPP instance.
- db -- Optional interface object to an external datastore.
- auto_authorize -- Default auto_authorize value for new roster nodes.
- Defaults to True.
- auto_subscribe -- Default auto_subscribe value for new roster nodes.
- Defaults to True.
-
- Methods:
- add -- Create a new roster node for a JID.
- send_presence -- Shortcut for sending a presence stanza.
- """
-
- def __init__(self, xmpp, db=None):
- """
- Create a new roster.
-
- Arguments:
- xmpp -- The main SleekXMPP instance.
- db -- Optional interface object to a datastore.
- """
- self.xmpp = xmpp
- self.db = db
- self._auto_authorize = True
- self._auto_subscribe = True
- self._rosters = {}
-
- if self.db:
- for node in self.db.entries(None, {}):
- self.add(node)
-
- self.xmpp.add_filter('out', self._save_last_status)
-
- def _save_last_status(self, stanza):
-
- if isinstance(stanza, Presence):
- sfrom = stanza['from'].full
- sto = stanza['to'].full
-
- if not sfrom:
- sfrom = self.xmpp.boundjid
-
- if stanza['type'] in stanza.showtypes or \
- stanza['type'] in ('available', 'unavailable'):
- if sto:
- self[sfrom][sto].last_status = stanza
- else:
- self[sfrom].last_status = stanza
- with self[sfrom]._last_status_lock:
- for jid in self[sfrom]:
- self[sfrom][jid].last_status = None
-
- if not self.xmpp.sentpresence:
- self.xmpp.event('sent_presence')
- self.xmpp.sentpresence = True
-
- return stanza
-
- def __getitem__(self, key):
- """
- Return the roster node for a JID.
-
- A new roster node will be created if one
- does not already exist.
-
- Arguments:
- key -- Return the roster for this JID.
- """
- if key is None:
- key = self.xmpp.boundjid
- if not isinstance(key, JID):
- key = JID(key)
- key = key.bare
-
- if key not in self._rosters:
- self.add(key)
- self._rosters[key].auto_authorize = self.auto_authorize
- self._rosters[key].auto_subscribe = self.auto_subscribe
- return self._rosters[key]
-
- def keys(self):
- """Return the JIDs managed by the roster."""
- return self._rosters.keys()
-
- def __iter__(self):
- """Iterate over the roster nodes."""
- return self._rosters.__iter__()
-
- def add(self, node):
- """
- Add a new roster node for the given JID.
-
- Arguments:
- node -- The JID for the new roster node.
- """
- if not isinstance(node, JID):
- node = JID(node)
-
- node = node.bare
- if node not in self._rosters:
- self._rosters[node] = RosterNode(self.xmpp, node, self.db)
-
- def set_backend(self, db=None, save=True):
- """
- Set the datastore interface object for the roster.
-
- Arguments:
- db -- The new datastore interface.
- save -- If True, save the existing state to the new
- backend datastore. Defaults to True.
- """
- self.db = db
- existing_entries = set(self._rosters)
- new_entries = set(self.db.entries(None, {}))
-
- for node in existing_entries:
- self._rosters[node].set_backend(db, save)
- for node in new_entries - existing_entries:
- self.add(node)
-
- def reset(self):
- """
- Reset the state of the roster to forget any current
- presence information. Useful after a disconnection occurs.
- """
- for node in self:
- self[node].reset()
-
- def send_presence(self, **kwargs):
- """
- Create, initialize, and send a Presence stanza.
-
- If no recipient is specified, send the presence immediately.
- Otherwise, forward the send request to the recipient's roster
- entry for processing.
-
- Arguments:
- pshow -- The presence's show value.
- pstatus -- The presence's status message.
- ppriority -- This connections' priority.
- pto -- The recipient of a directed presence.
- pfrom -- The sender of a directed presence, which should
- be the owner JID plus resource.
- ptype -- The type of presence, such as 'subscribe'.
- pnick -- Optional nickname of the presence's sender.
- """
- if self.xmpp.is_component and not kwargs.get('pfrom', ''):
- kwargs['pfrom'] = self.jid
- self.xmpp.send_presence(**kwargs)
-
- @property
- def auto_authorize(self):
- """
- Auto accept or deny subscription requests.
-
- If True, auto accept subscription requests.
- If False, auto deny subscription requests.
- If None, don't automatically respond.
- """
- return self._auto_authorize
-
- @auto_authorize.setter
- def auto_authorize(self, value):
- """
- Auto accept or deny subscription requests.
-
- If True, auto accept subscription requests.
- If False, auto deny subscription requests.
- If None, don't automatically respond.
- """
- self._auto_authorize = value
- for node in self._rosters:
- self._rosters[node].auto_authorize = value
-
- @property
- def auto_subscribe(self):
- """
- Auto send requests for mutual subscriptions.
-
- If True, auto send mutual subscription requests.
- """
- return self._auto_subscribe
-
- @auto_subscribe.setter
- def auto_subscribe(self, value):
- """
- Auto send requests for mutual subscriptions.
-
- If True, auto send mutual subscription requests.
- """
- self._auto_subscribe = value
- for node in self._rosters:
- self._rosters[node].auto_subscribe = value
-
- def __repr__(self):
- return repr(self._rosters)
diff --git a/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py
deleted file mode 100644
index e9ce4f21..00000000
--- a/sleekxmpp/roster/single.py
+++ /dev/null
@@ -1,339 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import threading
-
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.roster import RosterItem
-
-
-class RosterNode(object):
-
- """
- A roster node is a roster for a single JID.
-
- Attributes:
- xmpp -- The main SleekXMPP instance.
- jid -- The JID that owns the roster node.
- db -- Optional interface to an external datastore.
- auto_authorize -- Determines how authorizations are handled:
- True -- Accept all subscriptions.
- False -- Reject all subscriptions.
- None -- Subscriptions must be
- manually authorized.
- Defaults to True.
- auto_subscribe -- Determines if bi-directional subscriptions
- are created after automatically authrorizing
- a subscription request.
- Defaults to True
- last_status -- The last sent presence status that was broadcast
- to all contact JIDs.
-
- Methods:
- add -- Add a JID to the roster.
- update -- Update a JID's subscription information.
- subscribe -- Subscribe to a JID.
- unsubscribe -- Unsubscribe from a JID.
- remove -- Remove a JID from the roster.
- presence -- Return presence information for a JID's resources.
- send_presence -- Shortcut for sending a presence stanza.
- """
-
- def __init__(self, xmpp, jid, db=None):
- """
- Create a roster node for a JID.
-
- Arguments:
- xmpp -- The main SleekXMPP instance.
- jid -- The JID that owns the roster.
- db -- Optional interface to an external datastore.
- """
- self.xmpp = xmpp
- self.jid = jid
- self.db = db
- self.auto_authorize = True
- self.auto_subscribe = True
- self.last_status = None
- self._version = ''
- self._jids = {}
- self._last_status_lock = threading.Lock()
-
- if self.db:
- if hasattr(self.db, 'version'):
- self._version = self.db.version(self.jid)
- for jid in self.db.entries(self.jid):
- self.add(jid)
-
- @property
- def version(self):
- """Retrieve the roster's version ID."""
- if self.db and hasattr(self.db, 'version'):
- self._version = self.db.version(self.jid)
- return self._version
-
- @version.setter
- def version(self, version):
- """Set the roster's version ID."""
- self._version = version
- if self.db and hasattr(self.db, 'set_version'):
- self.db.set_version(self.jid, version)
-
- def __getitem__(self, key):
- """
- Return the roster item for a subscribed JID.
-
- A new item entry will be created if one does not already exist.
- """
- if key is None:
- key = JID('')
- if not isinstance(key, JID):
- key = JID(key)
- key = key.bare
- if key not in self._jids:
- self.add(key, save=True)
- return self._jids[key]
-
- def __delitem__(self, key):
- """
- Remove a roster item from the local storage.
-
- To remove an item from the server, use the remove() method.
- """
- if key is None:
- key = JID('')
- if not isinstance(key, JID):
- key = JID(key)
- key = key.bare
- if key in self._jids:
- del self._jids[key]
-
- def __len__(self):
- """Return the number of JIDs referenced by the roster."""
- return len(self._jids)
-
- def keys(self):
- """Return a list of all subscribed JIDs."""
- return self._jids.keys()
-
- def has_jid(self, jid):
- """Returns whether the roster has a JID."""
- return jid in self._jids
-
- def groups(self):
- """Return a dictionary mapping group names to JIDs."""
- result = {}
- for jid in self._jids:
- groups = self._jids[jid]['groups']
- if not groups:
- if '' not in result:
- result[''] = []
- result[''].append(jid)
- for group in groups:
- if group not in result:
- result[group] = []
- result[group].append(jid)
- return result
-
- def __iter__(self):
- """Iterate over the roster items."""
- return self._jids.__iter__()
-
- def set_backend(self, db=None, save=True):
- """
- Set the datastore interface object for the roster node.
-
- Arguments:
- db -- The new datastore interface.
- save -- If True, save the existing state to the new
- backend datastore. Defaults to True.
- """
- self.db = db
- existing_entries = set(self._jids)
- new_entries = set(self.db.entries(self.jid, {}))
-
- for jid in existing_entries:
- self._jids[jid].set_backend(db, save)
- for jid in new_entries - existing_entries:
- self.add(jid)
-
- def add(self, jid, name='', groups=None, afrom=False, ato=False,
- pending_in=False, pending_out=False, whitelisted=False,
- save=False):
- """
- Add a new roster item entry.
-
- Arguments:
- jid -- The JID for the roster item.
- name -- An alias for the JID.
- groups -- A list of group names.
- afrom -- Indicates if the JID has a subscription state
- of 'from'. Defaults to False.
- ato -- Indicates if the JID has a subscription state
- of 'to'. Defaults to False.
- pending_in -- Indicates if the JID has sent a subscription
- request to this connection's JID.
- Defaults to False.
- pending_out -- Indicates if a subscription request has been sent
- to this JID.
- Defaults to False.
- whitelisted -- Indicates if a subscription request from this JID
- should be automatically authorized.
- Defaults to False.
- save -- Indicates if the item should be persisted
- immediately to an external datastore,
- if one is used.
- Defaults to False.
- """
- if isinstance(jid, JID):
- key = jid.bare
- else:
- key = jid
-
- state = {'name': name,
- 'groups': groups or [],
- 'from': afrom,
- 'to': ato,
- 'pending_in': pending_in,
- 'pending_out': pending_out,
- 'whitelisted': whitelisted,
- 'subscription': 'none'}
- self._jids[key] = RosterItem(self.xmpp, jid, self.jid,
- state=state, db=self.db,
- roster=self)
- if save:
- self._jids[key].save()
-
- def subscribe(self, jid):
- """
- Subscribe to the given JID.
-
- Arguments:
- jid -- The JID to subscribe to.
- """
- self[jid].subscribe()
-
- def unsubscribe(self, jid):
- """
- Unsubscribe from the given JID.
-
- Arguments:
- jid -- The JID to unsubscribe from.
- """
- self[jid].unsubscribe()
-
- def remove(self, jid):
- """
- Remove a JID from the roster.
-
- Arguments:
- jid -- The JID to remove.
- """
- self[jid].remove()
- if not self.xmpp.is_component:
- return self.update(jid, subscription='remove')
-
- def update(self, jid, name=None, subscription=None, groups=None, block=True, timeout=None, callback=None):
- """
- Update a JID's subscription information.
-
- Arguments:
- jid -- The JID to update.
- name -- Optional alias for the JID.
- subscription -- The subscription state. May be one of: 'to',
- 'from', 'both', 'none', or 'remove'.
- groups -- A list of group names.
- block -- Specify if the roster request will block
- until a response is received, or a timeout
- occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait
- for a response before continuing if blocking
- is used. Defaults to self.response_timeout.
- callback -- Optional reference to a stream handler function.
- Will be executed when the roster is received.
- Implies block=False.
- """
- if not groups:
- groups = []
-
- self[jid]['name'] = name
- self[jid]['groups'] = groups
- self[jid].save()
-
- if not self.xmpp.is_component:
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['roster']['items'] = {jid: {'name': name,
- 'subscription': subscription,
- 'groups': groups}}
-
- return iq.send(block, timeout, callback)
-
- def presence(self, jid, resource=None):
- """
- Retrieve the presence information of a JID.
-
- May return either all online resources' status, or
- a single resource's status.
-
- Arguments:
- jid -- The JID to lookup.
- resource -- Optional resource for returning
- only the status of a single connection.
- """
- if resource is None:
- return self[jid].resources
-
- default_presence = {'status': '',
- 'priority': 0,
- 'show': ''}
- return self[jid].resources.get(resource,
- default_presence)
-
- def reset(self):
- """
- Reset the state of the roster to forget any current
- presence information. Useful after a disconnection occurs.
- """
- for jid in self:
- self[jid].reset()
-
- def send_presence(self, **kwargs):
- """
- Create, initialize, and send a Presence stanza.
-
- If no recipient is specified, send the presence immediately.
- Otherwise, forward the send request to the recipient's roster
- entry for processing.
-
- Arguments:
- pshow -- The presence's show value.
- pstatus -- The presence's status message.
- ppriority -- This connections' priority.
- pto -- The recipient of a directed presence.
- pfrom -- The sender of a directed presence, which should
- be the owner JID plus resource.
- ptype -- The type of presence, such as 'subscribe'.
- pnick -- Optional nickname of the presence's sender.
- """
- if self.xmpp.is_component and not kwargs.get('pfrom', ''):
- kwargs['pfrom'] = self.jid
- self.xmpp.send_presence(**kwargs)
-
- def send_last_presence(self):
- if self.last_status is None:
- self.send_presence()
- else:
- pres = self.last_status
- if self.xmpp.is_component:
- pres['from'] = self.jid
- else:
- del pres['from']
- pres.send()
-
- def __repr__(self):
- return repr(self._jids)
diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py
deleted file mode 100644
index 4bd37dc5..00000000
--- a/sleekxmpp/stanza/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-from sleekxmpp.stanza.error import Error
-from sleekxmpp.stanza.iq import Iq
-from sleekxmpp.stanza.message import Message
-from sleekxmpp.stanza.presence import Presence
-from sleekxmpp.stanza.stream_features import StreamFeatures
-from sleekxmpp.stanza.stream_error import StreamError
diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py
deleted file mode 100644
index 4e9591a5..00000000
--- a/sleekxmpp/stanza/atom.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase
-
-class AtomEntry(ElementBase):
-
- """
- A simple Atom feed entry.
-
- Stanza Interface:
- title -- The title of the Atom feed entry.
- summary -- The summary of the Atom feed entry.
- """
-
- namespace = 'http://www.w3.org/2005/Atom'
- name = 'entry'
- plugin_attrib = 'entry'
- interfaces = set(('title', 'summary', 'id', 'published', 'updated'))
- sub_interfaces = set(('title', 'summary', 'id', 'published',
- 'updated'))
-
-class AtomAuthor(ElementBase):
-
- """
- An Atom author.
-
- Stanza Interface:
- name -- The printable author name
- uri -- The bare jid of the author
- """
-
- name = 'author'
- plugin_attrib = 'author'
- interfaces = set(('name', 'uri'))
- sub_interfaces = set(('name', 'uri'))
-
-register_stanza_plugin(AtomEntry, AtomAuthor)
diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py
deleted file mode 100644
index 56558ba8..00000000
--- a/sleekxmpp/stanza/error.py
+++ /dev/null
@@ -1,174 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class Error(ElementBase):
-
- """
- XMPP stanzas of type 'error' should include an <error> stanza that
- describes the nature of the error and how it should be handled.
-
- Use the 'XEP-0086: Error Condition Mappings' plugin to include error
- codes used in older XMPP versions.
-
- Example error stanza:
- <error type="cancel" code="404">
- <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
- <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
- The item was not found.
- </text>
- </error>
-
- Stanza Interface:
- code -- The error code used in older XMPP versions.
- condition -- The name of the condition element.
- text -- Human readable description of the error.
- type -- Error type indicating how the error should be handled.
-
- Attributes:
- conditions -- The set of allowable error condition elements.
- condition_ns -- The namespace for the condition element.
- types -- A set of values indicating how the error
- should be treated.
-
- Methods:
- setup -- Overrides ElementBase.setup.
- get_condition -- Retrieve the name of the condition element.
- set_condition -- Add a condition element.
- del_condition -- Remove the condition element.
- get_text -- Retrieve the contents of the <text> element.
- set_text -- Set the contents of the <text> element.
- del_text -- Remove the <text> element.
- """
-
- namespace = 'jabber:client'
- name = 'error'
- plugin_attrib = 'error'
- interfaces = set(('code', 'condition', 'text', 'type',
- 'gone', 'redirect', 'by'))
- sub_interfaces = set(('text',))
- plugin_attrib_map = {}
- plugin_tag_map = {}
- conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
- 'forbidden', 'gone', 'internal-server-error',
- 'item-not-found', 'jid-malformed', 'not-acceptable',
- 'not-allowed', 'not-authorized', 'payment-required',
- 'recipient-unavailable', 'redirect',
- 'registration-required', 'remote-server-not-found',
- 'remote-server-timeout', 'resource-constraint',
- 'service-unavailable', 'subscription-required',
- 'undefined-condition', 'unexpected-request'))
- condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
- types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
-
- def setup(self, xml=None):
- """
- Populate the stanza object using an optional XML object.
-
- Overrides ElementBase.setup.
-
- Sets a default error type and condition, and changes the
- parent stanza's type to 'error'.
-
- Arguments:
- xml -- Use an existing XML object for the stanza's values.
- """
- if ElementBase.setup(self, xml):
- #If we had to generate XML then set default values.
- self['type'] = 'cancel'
- self['condition'] = 'feature-not-implemented'
- if self.parent is not None:
- self.parent()['type'] = 'error'
-
- def get_condition(self):
- """Return the condition element's name."""
- for child in self.xml:
- if "{%s}" % self.condition_ns in child.tag:
- cond = child.tag.split('}', 1)[-1]
- if cond in self.conditions:
- return cond
- return ''
-
- def set_condition(self, value):
- """
- Set the tag name of the condition element.
-
- Arguments:
- value -- The tag name of the condition element.
- """
- if value in self.conditions:
- del self['condition']
- self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value)))
- return self
-
- def del_condition(self):
- """Remove the condition element."""
- for child in self.xml:
- if "{%s}" % self.condition_ns in child.tag:
- tag = child.tag.split('}', 1)[-1]
- if tag in self.conditions:
- self.xml.remove(child)
- return self
-
- def get_text(self):
- """Retrieve the contents of the <text> element."""
- return self._get_sub_text('{%s}text' % self.condition_ns)
-
- def set_text(self, value):
- """
- Set the contents of the <text> element.
-
- Arguments:
- value -- The new contents for the <text> element.
- """
- self._set_sub_text('{%s}text' % self.condition_ns, text=value)
- return self
-
- def del_text(self):
- """Remove the <text> element."""
- self._del_sub('{%s}text' % self.condition_ns)
- return self
-
- def get_gone(self):
- return self._get_sub_text('{%s}gone' % self.condition_ns, '')
-
- def get_redirect(self):
- return self._get_sub_text('{%s}redirect' % self.condition_ns, '')
-
- def set_gone(self, value):
- if value:
- del self['condition']
- return self._set_sub_text('{%s}gone' % self.condition_ns, value)
- elif self['condition'] == 'gone':
- del self['condition']
-
- def set_redirect(self, value):
- if value:
- del self['condition']
- ns = self.condition_ns
- return self._set_sub_text('{%s}redirect' % ns, value)
- elif self['condition'] == 'redirect':
- del self['condition']
-
- def del_gone(self):
- self._del_sub('{%s}gone' % self.condition_ns)
-
- def del_redirect(self):
- self._del_sub('{%s}redirect' % self.condition_ns)
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Error.getCondition = Error.get_condition
-Error.setCondition = Error.set_condition
-Error.delCondition = Error.del_condition
-Error.getText = Error.get_text
-Error.setText = Error.set_text
-Error.delText = Error.del_text
diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py
deleted file mode 100644
index c43178f2..00000000
--- a/sleekxmpp/stanza/htmlim.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0071 import XHTML_IM as HTMLIM
-
-
-register_stanza_plugin(Message, HTMLIM)
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-HTMLIM.setBody = HTMLIM.set_body
-HTMLIM.getBody = HTMLIM.get_body
-HTMLIM.delBody = HTMLIM.del_body
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
deleted file mode 100644
index 088de4c0..00000000
--- a/sleekxmpp/stanza/iq.py
+++ /dev/null
@@ -1,281 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza.rootstanza import RootStanza
-from sleekxmpp.xmlstream import StanzaBase, ET
-from sleekxmpp.xmlstream.handler import Waiter, Callback
-from sleekxmpp.xmlstream.matcher import MatchIDSender, MatcherId
-from sleekxmpp.exceptions import IqTimeout, IqError
-
-
-class Iq(RootStanza):
-
- """
- XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
- requesting and modifying information, similar to HTTP's GET and
- POST methods.
-
- Each <iq> stanza must have an 'id' value which associates the
- stanza with the response stanza. XMPP entities must always
- be given a response <iq> stanza with a type of 'result' after
- sending a stanza of type 'get' or 'set'.
-
- Most uses cases for <iq> stanzas will involve adding a <query>
- element whose namespace indicates the type of information
- desired. However, some custom XMPP applications use <iq> stanzas
- as a carrier stanza for an application-specific protocol instead.
-
- Example <iq> Stanzas:
- <iq to="user@example.com" type="get" id="314">
- <query xmlns="http://jabber.org/protocol/disco#items" />
- </iq>
-
- <iq to="user@localhost" type="result" id="17">
- <query xmlns='jabber:iq:roster'>
- <item jid='otheruser@example.net'
- name='John Doe'
- subscription='both'>
- <group>Friends</group>
- </item>
- </query>
- </iq>
-
- Stanza Interface:
- query -- The namespace of the <query> element if one exists.
-
- Attributes:
- types -- May be one of: get, set, result, or error.
-
- Methods:
- __init__ -- Overrides StanzaBase.__init__.
- unhandled -- Send error if there are no handlers.
- set_payload -- Overrides StanzaBase.set_payload.
- set_query -- Add or modify a <query> element.
- get_query -- Return the namespace of the <query> element.
- del_query -- Remove the <query> element.
- reply -- Overrides StanzaBase.reply
- send -- Overrides StanzaBase.send
- """
-
- namespace = 'jabber:client'
- name = 'iq'
- interfaces = set(('type', 'to', 'from', 'id', 'query'))
- types = set(('get', 'result', 'set', 'error'))
- plugin_attrib = name
-
- def __init__(self, *args, **kwargs):
- """
- Initialize a new <iq> stanza with an 'id' value.
-
- Overrides StanzaBase.__init__.
- """
- StanzaBase.__init__(self, *args, **kwargs)
- if self['id'] == '':
- if self.stream is not None:
- self['id'] = self.stream.new_id()
- else:
- self['id'] = '0'
-
- def unhandled(self):
- """
- Send a feature-not-implemented error if the stanza is not handled.
-
- Overrides StanzaBase.unhandled.
- """
- if self['type'] in ('get', 'set'):
- self.reply()
- self['error']['condition'] = 'feature-not-implemented'
- self['error']['text'] = 'No handlers registered for this request.'
- self.send()
-
- def set_payload(self, value):
- """
- Set the XML contents of the <iq> stanza.
-
- Arguments:
- value -- An XML object to use as the <iq> stanza's contents
- """
- self.clear()
- StanzaBase.set_payload(self, value)
- return self
-
- def set_query(self, value):
- """
- Add or modify a <query> element.
-
- Query elements are differentiated by their namespace.
-
- Arguments:
- value -- The namespace of the <query> element.
- """
- query = self.xml.find("{%s}query" % value)
- if query is None and value:
- plugin = self.plugin_tag_map.get('{%s}query' % value, None)
- if plugin:
- self.enable(plugin.plugin_attrib)
- else:
- self.clear()
- query = ET.Element("{%s}query" % value)
- self.xml.append(query)
- return self
-
- def get_query(self):
- """Return the namespace of the <query> element."""
- for child in self.xml:
- if child.tag.endswith('query'):
- ns = child.tag.split('}')[0]
- if '{' in ns:
- ns = ns[1:]
- return ns
- return ''
-
- def del_query(self):
- """Remove the <query> element."""
- for child in self.xml:
- if child.tag.endswith('query'):
- self.xml.remove(child)
- return self
-
- def reply(self, clear=True):
- """
- Send a reply <iq> stanza.
-
- Overrides StanzaBase.reply
-
- Sets the 'type' to 'result' in addition to the default
- StanzaBase.reply behavior.
-
- Arguments:
- clear -- Indicates if existing content should be
- removed before replying. Defaults to True.
- """
- self['type'] = 'result'
- StanzaBase.reply(self, clear)
- return self
-
- def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None):
- """
- Send an <iq> stanza over the XML stream.
-
- The send call can optionally block until a response is received or
- a timeout occurs. Be aware that using blocking in non-threaded event
- handlers can drastically impact performance. Otherwise, a callback
- handler can be provided that will be executed when the Iq stanza's
- result reply is received. Be aware though that that the callback
- handler will not be executed in its own thread.
-
- Using both block and callback is not recommended, and only the
- callback argument will be used in that case.
-
- Overrides StanzaBase.send
-
- Arguments:
- block -- Specify if the send call will block until a response
- is received, or a timeout occurs. Defaults to True.
- timeout -- The length of time (in seconds) to wait for a response
- before exiting the send call if blocking is used.
- Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
- callback -- Optional reference to a stream handler function. Will
- be executed when a reply stanza is received.
- now -- Indicates if the send queue should be skipped and send
- the stanza immediately. Used during stream
- initialization. Defaults to False.
- timeout_callback -- Optional reference to a stream handler function.
- Will be executed when the timeout expires before a
- response has been received with the originally-sent IQ
- stanza. Only called if there is a callback parameter
- (and therefore are in async mode).
- """
- if timeout is None:
- timeout = self.stream.response_timeout
-
- if self.stream.session_bind_event.is_set():
- matcher = MatchIDSender({
- 'id': self['id'],
- 'self': self.stream.boundjid,
- 'peer': self['to']
- })
- else:
- matcher = MatcherId(self['id'])
-
- if callback is not None and self['type'] in ('get', 'set'):
- handler_name = 'IqCallback_%s' % self['id']
- if timeout_callback:
- self.callback = callback
- self.timeout_callback = timeout_callback
- self.stream.schedule('IqTimeout_%s' % self['id'],
- timeout,
- self._fire_timeout,
- repeat=False)
- handler = Callback(handler_name,
- matcher,
- self._handle_result,
- once=True)
- else:
- handler = Callback(handler_name,
- matcher,
- callback,
- once=True)
- self.stream.register_handler(handler)
- StanzaBase.send(self, now=now)
- return handler_name
- elif block and self['type'] in ('get', 'set'):
- waitfor = Waiter('IqWait_%s' % self['id'], matcher)
- self.stream.register_handler(waitfor)
- StanzaBase.send(self, now=now)
- result = waitfor.wait(timeout)
- if not result:
- raise IqTimeout(self)
- if result['type'] == 'error':
- raise IqError(result)
- return result
- else:
- return StanzaBase.send(self, now=now)
-
- def _handle_result(self, iq):
- # we got the IQ, so don't fire the timeout
- self.stream.scheduler.remove('IqTimeout_%s' % self['id'])
- self.callback(iq)
-
- def _fire_timeout(self):
- # don't fire the handler for the IQ, if it finally does come in
- self.stream.remove_handler('IqCallback_%s' % self['id'])
- self.timeout_callback(self)
-
- def _set_stanza_values(self, values):
- """
- Set multiple stanza interface values using a dictionary.
-
- Stanza plugin values may be set usind nested dictionaries.
-
- If the interface 'query' is given, then it will be set
- last to avoid duplication of the <query /> element.
-
- Overrides ElementBase._set_stanza_values.
-
- Arguments:
- values -- A dictionary mapping stanza interface with values.
- Plugin interfaces may accept a nested dictionary that
- will be used recursively.
- """
- query = values.get('query', '')
- if query:
- del values['query']
- StanzaBase._set_stanza_values(self, values)
- self['query'] = query
- else:
- StanzaBase._set_stanza_values(self, values)
- return self
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Iq.setPayload = Iq.set_payload
-Iq.getQuery = Iq.get_query
-Iq.setQuery = Iq.set_query
-Iq.delQuery = Iq.del_query
diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
deleted file mode 100644
index 0bb6e587..00000000
--- a/sleekxmpp/stanza/message.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza.rootstanza import RootStanza
-from sleekxmpp.xmlstream import StanzaBase, ET
-
-
-class Message(RootStanza):
-
- """
- XMPP's <message> stanzas are a "push" mechanism to send information
- to other XMPP entities without requiring a response.
-
- Chat clients will typically use <message> stanzas that have a type
- of either "chat" or "groupchat".
-
- When handling a message event, be sure to check if the message is
- an error response.
-
- Example <message> stanzas:
- <message to="user1@example.com" from="user2@example.com">
- <body>Hi!</body>
- </message>
-
- <message type="groupchat" to="room@conference.example.com">
- <body>Hi everyone!</body>
- </message>
-
- Stanza Interface:
- body -- The main contents of the message.
- subject -- An optional description of the message's contents.
- mucroom -- (Read-only) The name of the MUC room that sent the message.
- mucnick -- (Read-only) The MUC nickname of message's sender.
-
- Attributes:
- types -- May be one of: normal, chat, headline, groupchat, or error.
-
- Methods:
- setup -- Overrides StanzaBase.setup.
- chat -- Set the message type to 'chat'.
- normal -- Set the message type to 'normal'.
- reply -- Overrides StanzaBase.reply
- get_type -- Overrides StanzaBase interface
- get_mucroom -- Return the name of the MUC room of the message.
- set_mucroom -- Dummy method to prevent assignment.
- del_mucroom -- Dummy method to prevent deletion.
- get_mucnick -- Return the MUC nickname of the message's sender.
- set_mucnick -- Dummy method to prevent assignment.
- del_mucnick -- Dummy method to prevent deletion.
- """
-
- name = 'message'
- namespace = 'jabber:client'
- plugin_attrib = name
- interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
- 'thread', 'parent_thread', 'mucroom', 'mucnick'])
- sub_interfaces = set(['body', 'subject', 'thread'])
- lang_interfaces = sub_interfaces
- types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
-
- def __init__(self, *args, **kwargs):
- """
- Initialize a new <message /> stanza with an optional 'id' value.
-
- Overrides StanzaBase.__init__.
- """
- StanzaBase.__init__(self, *args, **kwargs)
- if self['id'] == '':
- if self.stream is not None and self.stream.use_message_ids:
- self['id'] = self.stream.new_id()
-
- def get_type(self):
- """
- Return the message type.
-
- Overrides default stanza interface behavior.
-
- Returns 'normal' if no type attribute is present.
- """
- return self._get_attr('type', 'normal')
-
- def get_parent_thread(self):
- """Return the message thread's parent thread."""
- thread = self.xml.find('{%s}thread' % self.namespace)
- if thread is not None:
- return thread.attrib.get('parent', '')
- return ''
-
- def set_parent_thread(self, value):
- """Add or change the message thread's parent thread."""
- thread = self.xml.find('{%s}thread' % self.namespace)
- if value:
- if thread is None:
- thread = ET.Element('{%s}thread' % self.namespace)
- self.xml.append(thread)
- thread.attrib['parent'] = value
- else:
- if thread is not None and 'parent' in thread.attrib:
- del thread.attrib['parent']
-
- def del_parent_thread(self):
- """Delete the message thread's parent reference."""
- thread = self.xml.find('{%s}thread' % self.namespace)
- if thread is not None and 'parent' in thread.attrib:
- del thread.attrib['parent']
-
- def chat(self):
- """Set the message type to 'chat'."""
- self['type'] = 'chat'
- return self
-
- def normal(self):
- """Set the message type to 'normal'."""
- self['type'] = 'normal'
- return self
-
- def reply(self, body=None, clear=True):
- """
- Create a message reply.
-
- Overrides StanzaBase.reply.
-
- Sets proper 'to' attribute if the message is from a MUC, and
- adds a message body if one is given.
-
- Arguments:
- body -- Optional text content for the message.
- clear -- Indicates if existing content should be removed
- before replying. Defaults to True.
- """
- thread = self['thread']
- parent = self['parent_thread']
-
- StanzaBase.reply(self, clear)
- if self['type'] == 'groupchat':
- self['to'] = self['to'].bare
-
- self['thread'] = thread
- self['parent_thread'] = parent
-
- del self['id']
-
- if body is not None:
- self['body'] = body
- return self
-
- def get_mucroom(self):
- """
- Return the name of the MUC room where the message originated.
-
- Read-only stanza interface.
- """
- if self['type'] == 'groupchat':
- return self['from'].bare
- else:
- return ''
-
- def get_mucnick(self):
- """
- Return the nickname of the MUC user that sent the message.
-
- Read-only stanza interface.
- """
- if self['type'] == 'groupchat':
- return self['from'].resource
- else:
- return ''
-
- def set_mucroom(self, value):
- """Dummy method to prevent modification."""
- pass
-
- def del_mucroom(self):
- """Dummy method to prevent deletion."""
- pass
-
- def set_mucnick(self, value):
- """Dummy method to prevent modification."""
- pass
-
- def del_mucnick(self):
- """Dummy method to prevent deletion."""
- pass
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Message.getType = Message.get_type
-Message.getMucroom = Message.get_mucroom
-Message.setMucroom = Message.set_mucroom
-Message.delMucroom = Message.del_mucroom
-Message.getMucnick = Message.get_mucnick
-Message.setMucnick = Message.set_mucnick
-Message.delMucnick = Message.del_mucnick
diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py
deleted file mode 100644
index 0e9a5c2b..00000000
--- a/sleekxmpp/stanza/nick.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-# The nickname stanza has been moved to its own plugin, but the existing
-# references are kept for backwards compatibility.
-
-from sleekxmpp.stanza import Message, Presence
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins.xep_0172 import UserNick as Nick
-
-register_stanza_plugin(Message, Nick)
-register_stanza_plugin(Presence, Nick)
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Nick.setNick = Nick.set_nick
-Nick.getNick = Nick.get_nick
-Nick.delNick = Nick.del_nick
diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
deleted file mode 100644
index 84bcd122..00000000
--- a/sleekxmpp/stanza/presence.py
+++ /dev/null
@@ -1,191 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza.rootstanza import RootStanza
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class Presence(RootStanza):
-
- """
- XMPP's <presence> stanza allows entities to know the status of other
- clients and components. Since it is currently the only multi-cast
- stanza in XMPP, many extensions add more information to <presence>
- stanzas to broadcast to every entry in the roster, such as
- capabilities, music choices, or locations (XEP-0115: Entity Capabilities
- and XEP-0163: Personal Eventing Protocol).
-
- Since <presence> stanzas are broadcast when an XMPP entity changes
- its status, the bulk of the traffic in an XMPP network will be from
- <presence> stanzas. Therefore, do not include more information than
- necessary in a status message or within a <presence> stanza in order
- to help keep the network running smoothly.
-
- Example <presence> stanzas:
- <presence />
-
- <presence from="user@example.com">
- <show>away</show>
- <status>Getting lunch.</status>
- <priority>5</priority>
- </presence>
-
- <presence type="unavailable" />
-
- <presence to="user@otherhost.com" type="subscribe" />
-
- Stanza Interface:
- priority -- A value used by servers to determine message routing.
- show -- The type of status, such as away or available for chat.
- status -- Custom, human readable status message.
-
- Attributes:
- types -- One of: available, unavailable, error, probe,
- subscribe, subscribed, unsubscribe,
- and unsubscribed.
- showtypes -- One of: away, chat, dnd, and xa.
-
- Methods:
- setup -- Overrides StanzaBase.setup
- reply -- Overrides StanzaBase.reply
- set_show -- Set the value of the <show> element.
- get_type -- Get the value of the type attribute or <show> element.
- set_type -- Set the value of the type attribute or <show> element.
- get_priority -- Get the value of the <priority> element.
- set_priority -- Set the value of the <priority> element.
- """
-
- name = 'presence'
- namespace = 'jabber:client'
- plugin_attrib = name
- interfaces = set(['type', 'to', 'from', 'id', 'show',
- 'status', 'priority'])
- sub_interfaces = set(['show', 'status', 'priority'])
- lang_interfaces = set(['status'])
-
- types = set(['available', 'unavailable', 'error', 'probe', 'subscribe',
- 'subscribed', 'unsubscribe', 'unsubscribed'])
- showtypes = set(['dnd', 'chat', 'xa', 'away'])
-
- def __init__(self, *args, **kwargs):
- """
- Initialize a new <presence /> stanza with an optional 'id' value.
-
- Overrides StanzaBase.__init__.
- """
- StanzaBase.__init__(self, *args, **kwargs)
- if self['id'] == '':
- if self.stream is not None and self.stream.use_presence_ids:
- self['id'] = self.stream.new_id()
-
- def exception(self, e):
- """
- Override exception passback for presence.
- """
- pass
-
- def set_show(self, show):
- """
- Set the value of the <show> element.
-
- Arguments:
- show -- Must be one of: away, chat, dnd, or xa.
- """
- if show is None:
- self._del_sub('show')
- elif show in self.showtypes:
- self._set_sub_text('show', text=show)
- return self
-
- def get_type(self):
- """
- Return the value of the <presence> stanza's type attribute, or
- the value of the <show> element.
- """
- out = self._get_attr('type')
- if not out:
- out = self['show']
- if not out or out is None:
- out = 'available'
- return out
-
- def set_type(self, value):
- """
- Set the type attribute's value, and the <show> element
- if applicable.
-
- Arguments:
- value -- Must be in either self.types or self.showtypes.
- """
- if value in self.types:
- self['show'] = None
- if value == 'available':
- value = ''
- self._set_attr('type', value)
- elif value in self.showtypes:
- self['show'] = value
- return self
-
- def del_type(self):
- """
- Remove both the type attribute and the <show> element.
- """
- self._del_attr('type')
- self._del_sub('show')
-
- def set_priority(self, value):
- """
- Set the entity's priority value. Some server use priority to
- determine message routing behavior.
-
- Bot clients should typically use a priority of 0 if the same
- JID is used elsewhere by a human-interacting client.
-
- Arguments:
- value -- An integer value greater than or equal to 0.
- """
- self._set_sub_text('priority', text=str(value))
-
- def get_priority(self):
- """
- Return the value of the <presence> element as an integer.
- """
- p = self._get_sub_text('priority')
- if not p:
- p = 0
- try:
- return int(p)
- except ValueError:
- # The priority is not a number: we consider it 0 as a default
- return 0
-
- def reply(self, clear=True):
- """
- Set the appropriate presence reply type.
-
- Overrides StanzaBase.reply.
-
- Arguments:
- clear -- Indicates if the stanza contents should be removed
- before replying. Defaults to True.
- """
- if self['type'] == 'unsubscribe':
- self['type'] = 'unsubscribed'
- elif self['type'] == 'subscribe':
- self['type'] = 'subscribed'
- return StanzaBase.reply(self, clear)
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Presence.setShow = Presence.set_show
-Presence.getType = Presence.get_type
-Presence.setType = Presence.set_type
-Presence.delType = Presence.get_type
-Presence.getPriority = Presence.get_priority
-Presence.setPriority = Presence.set_priority
diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py
deleted file mode 100644
index 52b807e5..00000000
--- a/sleekxmpp/stanza/rootstanza.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class RootStanza(StanzaBase):
-
- """
- A top-level XMPP stanza in an XMLStream.
-
- The RootStanza class provides a more XMPP specific exception
- handler than provided by the generic StanzaBase class.
-
- Methods:
- exception -- Overrides StanzaBase.exception
- """
-
- def exception(self, e):
- """
- Create and send an error reply.
-
- Typically called when an event handler raises an exception.
- The error's type and text content are based on the exception
- object's type and content.
-
- Overrides StanzaBase.exception.
-
- Arguments:
- e -- Exception object
- """
- if isinstance(e, IqError):
- # We received an Iq error reply, but it wasn't caught
- # locally. Using the condition/text from that error
- # response could leak too much information, so we'll
- # only use a generic error here.
- self.reply()
- self['error']['condition'] = 'undefined-condition'
- self['error']['text'] = 'External error'
- self['error']['type'] = 'cancel'
- log.warning('You should catch IqError exceptions')
- self.send()
- elif isinstance(e, IqTimeout):
- self.reply()
- self['error']['condition'] = 'remote-server-timeout'
- self['error']['type'] = 'wait'
- log.warning('You should catch IqTimeout exceptions')
- self.send()
- elif isinstance(e, XMPPError):
- # We raised this deliberately
- keep_id = self['id']
- self.reply(clear=e.clear)
- self['id'] = keep_id
- self['error']['condition'] = e.condition
- self['error']['text'] = e.text
- self['error']['type'] = e.etype
- if e.extension is not None:
- # Extended error tag
- extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
- e.extension_args)
- self['error'].append(extxml)
- self.send()
- else:
- # We probably didn't raise this on purpose, so send an error stanza
- keep_id = self['id']
- self.reply()
- self['id'] = keep_id
- self['error']['condition'] = 'undefined-condition'
- self['error']['text'] = "SleekXMPP got into trouble."
- self['error']['type'] = 'cancel'
- self.send()
- # log the error
- log.exception('Error handling {%s}%s stanza',
- self.namespace, self.name)
- # Finally raise the exception to a global exception handler
- self.stream.exception(e)
-
-register_stanza_plugin(RootStanza, Error)
diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py
deleted file mode 100644
index 681efd4f..00000000
--- a/sleekxmpp/stanza/roster.py
+++ /dev/null
@@ -1,158 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Iq
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
-
-
-class Roster(ElementBase):
-
- """
- Example roster stanzas:
- <iq type="set">
- <query xmlns="jabber:iq:roster">
- <item jid="user@example.com" subscription="both" name="User">
- <group>Friends</group>
- </item>
- </query>
- </iq>
-
- Stanza Inteface:
- items -- A dictionary of roster entries contained
- in the stanza.
-
- Methods:
- get_items -- Return a dictionary of roster entries.
- set_items -- Add <item> elements.
- del_items -- Remove all <item> elements.
- """
-
- namespace = 'jabber:iq:roster'
- name = 'query'
- plugin_attrib = 'roster'
- interfaces = set(('items', 'ver'))
-
- def get_ver(self):
- """
- Ensure handling an empty ver attribute propery.
-
- The ver attribute is special in that the presence of the
- attribute with an empty value is important for boostrapping
- roster versioning.
- """
- return self.xml.attrib.get('ver', None)
-
- def set_ver(self, ver):
- """
- Ensure handling an empty ver attribute propery.
-
- The ver attribute is special in that the presence of the
- attribute with an empty value is important for boostrapping
- roster versioning.
- """
- if ver is not None:
- self.xml.attrib['ver'] = ver
- else:
- del self.xml.attrib['ver']
-
- def set_items(self, items):
- """
- Set the roster entries in the <roster> stanza.
-
- Uses a dictionary using JIDs as keys, where each entry is itself
- a dictionary that contains:
- name -- An alias or nickname for the JID.
- subscription -- The subscription type. Can be one of 'to',
- 'from', 'both', 'none', or 'remove'.
- groups -- A list of group names to which the JID
- has been assigned.
-
- Arguments:
- items -- A dictionary of roster entries.
- """
- self.del_items()
- for jid in items:
- item = RosterItem()
- item.values = items[jid]
- item['jid'] = jid
- self.append(item)
- return self
-
- def get_items(self):
- """
- Return a dictionary of roster entries.
-
- Each item is keyed using its JID, and contains:
- name -- An assigned alias or nickname for the JID.
- subscription -- The subscription type. Can be one of 'to',
- 'from', 'both', 'none', or 'remove'.
- groups -- A list of group names to which the JID has
- been assigned.
- """
- items = {}
- for item in self['substanzas']:
- if isinstance(item, RosterItem):
- items[item['jid']] = item.values
- # Remove extra JID reference to keep everything
- # backward compatible
- del items[item['jid']]['jid']
- del items[item['jid']]['lang']
- return items
-
- def del_items(self):
- """
- Remove all <item> elements from the roster stanza.
- """
- for item in self['substanzas']:
- if isinstance(item, RosterItem):
- self.xml.remove(item.xml)
-
-
-class RosterItem(ElementBase):
- namespace = 'jabber:iq:roster'
- name = 'item'
- plugin_attrib = 'item'
- interfaces = set(('jid', 'name', 'subscription', 'ask',
- 'approved', 'groups'))
-
- def get_jid(self):
- return JID(self._get_attr('jid', ''))
-
- def set_jid(self, jid):
- self._set_attr('jid', str(jid))
-
- def get_groups(self):
- groups = []
- for group in self.xml.findall('{%s}group' % self.namespace):
- if group.text:
- groups.append(group.text)
- else:
- groups.append('')
- return groups
-
- def set_groups(self, values):
- self.del_groups()
- for group in values:
- group_xml = ET.Element('{%s}group' % self.namespace)
- group_xml.text = group
- self.xml.append(group_xml)
-
- def del_groups(self):
- for group in self.xml.findall('{%s}group' % self.namespace):
- self.xml.remove(group)
-
-
-register_stanza_plugin(Iq, Roster)
-register_stanza_plugin(Roster, RosterItem, iterable=True)
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-Roster.setItems = Roster.set_items
-Roster.getItems = Roster.get_items
-Roster.delItems = Roster.del_items
diff --git a/sleekxmpp/stanza/stream_error.py b/sleekxmpp/stanza/stream_error.py
deleted file mode 100644
index ed0078c9..00000000
--- a/sleekxmpp/stanza/stream_error.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza.error import Error
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class StreamError(Error, StanzaBase):
-
- """
- XMPP stanzas of type 'error' should include an <error> stanza that
- describes the nature of the error and how it should be handled.
-
- Use the 'XEP-0086: Error Condition Mappings' plugin to include error
- codes used in older XMPP versions.
-
- The stream:error stanza is used to provide more information for
- error that occur with the underlying XML stream itself, and not
- a particular stanza.
-
- Note: The StreamError stanza is mostly the same as the normal
- Error stanza, but with different namespaces and
- condition names.
-
- Example error stanza:
- <stream:error>
- <not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams" />
- <text xmlns="urn:ietf:params:xml:ns:xmpp-streams">
- XML was not well-formed.
- </text>
- </stream:error>
-
- Stanza Interface:
- condition -- The name of the condition element.
- text -- Human readable description of the error.
-
- Attributes:
- conditions -- The set of allowable error condition elements.
- condition_ns -- The namespace for the condition element.
-
- Methods:
- setup -- Overrides ElementBase.setup.
- get_condition -- Retrieve the name of the condition element.
- set_condition -- Add a condition element.
- del_condition -- Remove the condition element.
- get_text -- Retrieve the contents of the <text> element.
- set_text -- Set the contents of the <text> element.
- del_text -- Remove the <text> element.
- """
-
- namespace = 'http://etherx.jabber.org/streams'
- interfaces = set(('condition', 'text', 'see_other_host'))
- conditions = set((
- 'bad-format', 'bad-namespace-prefix', 'conflict',
- 'connection-timeout', 'host-gone', 'host-unknown',
- 'improper-addressing', 'internal-server-error', 'invalid-from',
- 'invalid-namespace', 'invalid-xml', 'not-authorized',
- 'not-well-formed', 'policy-violation', 'remote-connection-failed',
- 'reset', 'resource-constraint', 'restricted-xml', 'see-other-host',
- 'system-shutdown', 'undefined-condition', 'unsupported-encoding',
- 'unsupported-feature', 'unsupported-stanza-type',
- 'unsupported-version'))
- condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
-
- def get_see_other_host(self):
- ns = self.condition_ns
- return self._get_sub_text('{%s}see-other-host' % ns, '')
-
- def set_see_other_host(self, value):
- if value:
- del self['condition']
- ns = self.condition_ns
- return self._set_sub_text('{%s}see-other-host' % ns, value)
- elif self['condition'] == 'see-other-host':
- del self['condition']
-
- def del_see_other_host(self):
- self._del_sub('{%s}see-other-host' % self.condition_ns)
diff --git a/sleekxmpp/stanza/stream_features.py b/sleekxmpp/stanza/stream_features.py
deleted file mode 100644
index e487721e..00000000
--- a/sleekxmpp/stanza/stream_features.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.thirdparty import OrderedDict
-from sleekxmpp.xmlstream import StanzaBase
-
-
-class StreamFeatures(StanzaBase):
-
- """
- """
-
- name = 'features'
- namespace = 'http://etherx.jabber.org/streams'
- interfaces = set(('features', 'required', 'optional'))
- sub_interfaces = interfaces
- plugin_tag_map = {}
- plugin_attrib_map = {}
-
- def setup(self, xml):
- StanzaBase.setup(self, xml)
- self.values = self.values
-
- def get_features(self):
- """
- """
- features = OrderedDict()
- for (name, lang), plugin in self.plugins.items():
- features[name] = plugin
- return features
-
- def set_features(self, value):
- """
- """
- pass
-
- def del_features(self):
- """
- """
- pass
-
- def get_required(self):
- """
- """
- features = self['features']
- return [f for n, f in features.items() if f['required']]
-
- def get_optional(self):
- """
- """
- features = self['features']
- return [f for n, f in features.items() if not f['required']]
diff --git a/sleekxmpp/test/__init__.py b/sleekxmpp/test/__init__.py
deleted file mode 100644
index 54d4dc57..00000000
--- a/sleekxmpp/test/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.test.mocksocket import TestSocket
-from sleekxmpp.test.livesocket import TestLiveSocket
-from sleekxmpp.test.sleektest import *
diff --git a/sleekxmpp/test/livesocket.py b/sleekxmpp/test/livesocket.py
deleted file mode 100644
index d70ee4eb..00000000
--- a/sleekxmpp/test/livesocket.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import socket
-import threading
-
-from sleekxmpp.util import Queue
-
-
-class TestLiveSocket(object):
-
- """
- A live test socket that reads and writes to queues in
- addition to an actual networking socket.
-
- Methods:
- next_sent -- Return the next sent stanza.
- next_recv -- Return the next received stanza.
- recv_data -- Dummy method to have same interface as TestSocket.
- recv -- Read the next stanza from the socket.
- send -- Write a stanza to the socket.
- makefile -- Dummy call, returns self.
- read -- Read the next stanza from the socket.
- """
-
- def __init__(self, *args, **kwargs):
- """
- Create a new, live test socket.
-
- Arguments:
- Same as arguments for socket.socket
- """
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.recv_buffer = []
- self.recv_queue = Queue()
- self.send_queue = Queue()
- self.send_queue_lock = threading.Lock()
- self.recv_queue_lock = threading.Lock()
- self.is_live = True
-
- def __getattr__(self, name):
- """
- Return attribute values of internal, live socket.
-
- Arguments:
- name -- Name of the attribute requested.
- """
-
- return getattr(self.socket, name)
-
- # ------------------------------------------------------------------
- # Testing Interface
-
- def disconnect_errror(self):
- """
- Used to simulate a socket disconnection error.
-
- Not used by live sockets.
- """
- try:
- self.socket.shutdown()
- self.socket.close()
- except:
- pass
-
- def next_sent(self, timeout=None):
- """
- Get the next stanza that has been sent.
-
- Arguments:
- timeout -- Optional timeout for waiting for a new value.
- """
- args = {'block': False}
- if timeout is not None:
- args = {'block': True, 'timeout': timeout}
- try:
- return self.send_queue.get(**args)
- except:
- return None
-
- def next_recv(self, timeout=None):
- """
- Get the next stanza that has been received.
-
- Arguments:
- timeout -- Optional timeout for waiting for a new value.
- """
- args = {'block': False}
- if timeout is not None:
- args = {'block': True, 'timeout': timeout}
- try:
- if self.recv_buffer:
- return self.recv_buffer.pop(0)
- else:
- return self.recv_queue.get(**args)
- except:
- return None
-
- def recv_data(self, data):
- """
- Add data to a receive buffer for cases when more than a single stanza
- was received.
- """
- self.recv_buffer.append(data)
-
- # ------------------------------------------------------------------
- # Socket Interface
-
- def recv(self, *args, **kwargs):
- """
- Read data from the socket.
-
- Store a copy in the receive queue.
-
- Arguments:
- Placeholders. Same as for socket.recv.
- """
- data = self.socket.recv(*args, **kwargs)
- with self.recv_queue_lock:
- self.recv_queue.put(data)
- return data
-
- def send(self, data):
- """
- Send data on the socket.
-
- Store a copy in the send queue.
-
- Arguments:
- data -- String value to write.
- """
- with self.send_queue_lock:
- self.send_queue.put(data)
- return self.socket.send(data)
-
- # ------------------------------------------------------------------
- # File Socket
-
- def makefile(self, *args, **kwargs):
- """
- File socket version to use with ElementTree.
-
- Arguments:
- Placeholders, same as socket.makefile()
- """
- return self
-
- def read(self, *args, **kwargs):
- """
- Implement the file socket read interface.
-
- Arguments:
- Placeholders, same as socket.recv()
- """
- return self.recv(*args, **kwargs)
-
- def clear(self):
- """
- Empty the send queue, typically done once the session has started to
- remove the feature negotiation and log in stanzas.
- """
- with self.send_queue_lock:
- for i in range(0, self.send_queue.qsize()):
- self.send_queue.get(block=False)
- with self.recv_queue_lock:
- for i in range(0, self.recv_queue.qsize()):
- self.recv_queue.get(block=False)
diff --git a/sleekxmpp/test/mocksocket.py b/sleekxmpp/test/mocksocket.py
deleted file mode 100644
index 4c9d1699..00000000
--- a/sleekxmpp/test/mocksocket.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import socket
-
-from sleekxmpp.util import Queue
-
-
-class TestSocket(object):
-
- """
- A dummy socket that reads and writes to queues instead
- of an actual networking socket.
-
- Methods:
- next_sent -- Return the next sent stanza.
- recv_data -- Make a stanza available to read next.
- recv -- Read the next stanza from the socket.
- send -- Write a stanza to the socket.
- makefile -- Dummy call, returns self.
- read -- Read the next stanza from the socket.
- """
-
- def __init__(self, *args, **kwargs):
- """
- Create a new test socket.
-
- Arguments:
- Same as arguments for socket.socket
- """
- self.socket = socket.socket(*args, **kwargs)
- self.recv_queue = Queue()
- self.send_queue = Queue()
- self.is_live = False
- self.disconnected = False
-
- def __getattr__(self, name):
- """
- Return attribute values of internal, dummy socket.
-
- Some attributes and methods are disabled to prevent the
- socket from connecting to the network.
-
- Arguments:
- name -- Name of the attribute requested.
- """
-
- def dummy(*args):
- """Method to do nothing and prevent actual socket connections."""
- return None
-
- overrides = {'connect': dummy,
- 'close': dummy,
- 'shutdown': dummy}
-
- return overrides.get(name, getattr(self.socket, name))
-
- # ------------------------------------------------------------------
- # Testing Interface
-
- def next_sent(self, timeout=None):
- """
- Get the next stanza that has been 'sent'.
-
- Arguments:
- timeout -- Optional timeout for waiting for a new value.
- """
- args = {'block': False}
- if timeout is not None:
- args = {'block': True, 'timeout': timeout}
- try:
- return self.send_queue.get(**args)
- except:
- return None
-
- def recv_data(self, data):
- """
- Add data to the receiving queue.
-
- Arguments:
- data -- String data to 'write' to the socket to be received
- by the XMPP client.
- """
- self.recv_queue.put(data)
-
- def disconnect_error(self):
- """
- Simulate a disconnect error by raising a socket.error exception
- for any current or further socket operations.
- """
- self.disconnected = True
-
- # ------------------------------------------------------------------
- # Socket Interface
-
- def recv(self, *args, **kwargs):
- """
- Read a value from the received queue.
-
- Arguments:
- Placeholders. Same as for socket.Socket.recv.
- """
- if self.disconnected:
- raise socket.error
- return self.read(block=True)
-
- def send(self, data):
- """
- Send data by placing it in the send queue.
-
- Arguments:
- data -- String value to write.
- """
- if self.disconnected:
- raise socket.error
- self.send_queue.put(data)
- return len(data)
-
- # ------------------------------------------------------------------
- # File Socket
-
- def makefile(self, *args, **kwargs):
- """
- File socket version to use with ElementTree.
-
- Arguments:
- Placeholders, same as socket.Socket.makefile()
- """
- return self
-
- def read(self, block=True, timeout=None, **kwargs):
- """
- Implement the file socket interface.
-
- Arguments:
- block -- Indicate if the read should block until a
- value is ready.
- timeout -- Time in seconds a block should last before
- returning None.
- """
- if self.disconnected:
- raise socket.error
- if timeout is not None:
- block = True
- try:
- return self.recv_queue.get(block, timeout)
- except:
- return None
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py
deleted file mode 100644
index e26f99ce..00000000
--- a/sleekxmpp/test/sleektest.py
+++ /dev/null
@@ -1,774 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import unittest
-from xml.parsers.expat import ExpatError
-
-from sleekxmpp import ClientXMPP, ComponentXMPP
-from sleekxmpp.util import Queue
-from sleekxmpp.stanza import Message, Iq, Presence
-from sleekxmpp.test import TestSocket, TestLiveSocket
-from sleekxmpp.xmlstream import ET
-from sleekxmpp.xmlstream import ElementBase
-from sleekxmpp.xmlstream.tostring import tostring
-from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId, MatchIDSender
-from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath
-
-
-class SleekTest(unittest.TestCase):
-
- """
- A SleekXMPP specific TestCase class that provides
- methods for comparing message, iq, and presence stanzas.
-
- Methods:
- Message -- Create a Message stanza object.
- Iq -- Create an Iq stanza object.
- Presence -- Create a Presence stanza object.
- check_jid -- Check a JID and its component parts.
- check -- Compare a stanza against an XML string.
- stream_start -- Initialize a dummy XMPP client.
- stream_close -- Disconnect the XMPP client.
- make_header -- Create a stream header.
- send_header -- Check that the given header has been sent.
- send_feature -- Send a raw XML element.
- send -- Check that the XMPP client sent the given
- generic stanza.
- recv -- Queue data for XMPP client to receive, or
- verify the data that was received from a
- live connection.
- recv_header -- Check that a given stream header
- was received.
- recv_feature -- Check that a given, raw XML element
- was recveived.
- fix_namespaces -- Add top-level namespace to an XML object.
- compare -- Compare XML objects against each other.
- """
-
- def __init__(self, *args, **kwargs):
- unittest.TestCase.__init__(self, *args, **kwargs)
- self.xmpp = None
-
- def parse_xml(self, xml_string):
- try:
- xml = ET.fromstring(xml_string)
- return xml
- except (SyntaxError, ExpatError) as e:
- msg = e.msg if hasattr(e, 'msg') else e.message
- if 'unbound' in msg:
- known_prefixes = {
- 'stream': 'http://etherx.jabber.org/streams'}
-
- prefix = xml_string.split('<')[1].split(':')[0]
- if prefix in known_prefixes:
- xml_string = '<fixns xmlns:%s="%s">%s</fixns>' % (
- prefix,
- known_prefixes[prefix],
- xml_string)
- xml = self.parse_xml(xml_string)
- xml = list(xml)[0]
- return xml
- else:
- self.fail("XML data was mal-formed:\n%s" % xml_string)
-
- # ------------------------------------------------------------------
- # Shortcut methods for creating stanza objects
-
- def Message(self, *args, **kwargs):
- """
- Create a Message stanza.
-
- Uses same arguments as StanzaBase.__init__
-
- Arguments:
- xml -- An XML object to use for the Message's values.
- """
- return Message(self.xmpp, *args, **kwargs)
-
- def Iq(self, *args, **kwargs):
- """
- Create an Iq stanza.
-
- Uses same arguments as StanzaBase.__init__
-
- Arguments:
- xml -- An XML object to use for the Iq's values.
- """
- return Iq(self.xmpp, *args, **kwargs)
-
- def Presence(self, *args, **kwargs):
- """
- Create a Presence stanza.
-
- Uses same arguments as StanzaBase.__init__
-
- Arguments:
- xml -- An XML object to use for the Iq's values.
- """
- return Presence(self.xmpp, *args, **kwargs)
-
- def check_jid(self, jid, user=None, domain=None, resource=None,
- bare=None, full=None, string=None):
- """
- Verify the components of a JID.
-
- Arguments:
- jid -- The JID object to test.
- user -- Optional. The user name portion of the JID.
- domain -- Optional. The domain name portion of the JID.
- resource -- Optional. The resource portion of the JID.
- bare -- Optional. The bare JID.
- full -- Optional. The full JID.
- string -- Optional. The string version of the JID.
- """
- if user is not None:
- self.assertEqual(jid.user, user,
- "User does not match: %s" % jid.user)
- if domain is not None:
- self.assertEqual(jid.domain, domain,
- "Domain does not match: %s" % jid.domain)
- if resource is not None:
- self.assertEqual(jid.resource, resource,
- "Resource does not match: %s" % jid.resource)
- if bare is not None:
- self.assertEqual(jid.bare, bare,
- "Bare JID does not match: %s" % jid.bare)
- if full is not None:
- self.assertEqual(jid.full, full,
- "Full JID does not match: %s" % jid.full)
- if string is not None:
- self.assertEqual(str(jid), string,
- "String does not match: %s" % str(jid))
-
- def check_roster(self, owner, jid, name=None, subscription=None,
- afrom=None, ato=None, pending_out=None, pending_in=None,
- groups=None):
- roster = self.xmpp.roster[owner][jid]
- if name is not None:
- self.assertEqual(roster['name'], name,
- "Incorrect name value: %s" % roster['name'])
- if subscription is not None:
- self.assertEqual(roster['subscription'], subscription,
- "Incorrect subscription: %s" % roster['subscription'])
- if afrom is not None:
- self.assertEqual(roster['from'], afrom,
- "Incorrect from state: %s" % roster['from'])
- if ato is not None:
- self.assertEqual(roster['to'], ato,
- "Incorrect to state: %s" % roster['to'])
- if pending_out is not None:
- self.assertEqual(roster['pending_out'], pending_out,
- "Incorrect pending_out state: %s" % roster['pending_out'])
- if pending_in is not None:
- self.assertEqual(roster['pending_in'], pending_out,
- "Incorrect pending_in state: %s" % roster['pending_in'])
- if groups is not None:
- self.assertEqual(roster['groups'], groups,
- "Incorrect groups: %s" % roster['groups'])
-
- # ------------------------------------------------------------------
- # Methods for comparing stanza objects to XML strings
-
- def check(self, stanza, criteria, method='exact',
- defaults=None, use_values=True):
- """
- Create and compare several stanza objects to a correct XML string.
-
- If use_values is False, tests using stanza.values will not be used.
-
- Some stanzas provide default values for some interfaces, but
- these defaults can be problematic for testing since they can easily
- be forgotten when supplying the XML string. A list of interfaces that
- use defaults may be provided and the generated stanzas will use the
- default values for those interfaces if needed.
-
- However, correcting the supplied XML is not possible for interfaces
- that add or remove XML elements. Only interfaces that map to XML
- attributes may be set using the defaults parameter. The supplied XML
- must take into account any extra elements that are included by default.
-
- Arguments:
- stanza -- The stanza object to test.
- criteria -- An expression the stanza must match against.
- method -- The type of matching to use; one of:
- 'exact', 'mask', 'id', 'xpath', and 'stanzapath'.
- Defaults to the value of self.match_method.
- defaults -- A list of stanza interfaces that have default
- values. These interfaces will be set to their
- defaults for the given and generated stanzas to
- prevent unexpected test failures.
- use_values -- Indicates if testing using stanza.values should
- be used. Defaults to True.
- """
- if method is None and hasattr(self, 'match_method'):
- method = getattr(self, 'match_method')
-
- if method != 'exact':
- matchers = {'stanzapath': StanzaPath,
- 'xpath': MatchXPath,
- 'mask': MatchXMLMask,
- 'idsender': MatchIDSender,
- 'id': MatcherId}
- Matcher = matchers.get(method, None)
- if Matcher is None:
- raise ValueError("Unknown matching method.")
- test = Matcher(criteria)
- self.failUnless(test.match(stanza),
- "Stanza did not match using %s method:\n" % method + \
- "Criteria:\n%s\n" % str(criteria) + \
- "Stanza:\n%s" % str(stanza))
- else:
- stanza_class = stanza.__class__
- if not isinstance(criteria, ElementBase):
- xml = self.parse_xml(criteria)
- else:
- xml = criteria.xml
-
- # Ensure that top level namespaces are used, even if they
- # were not provided.
- self.fix_namespaces(stanza.xml, 'jabber:client')
- self.fix_namespaces(xml, 'jabber:client')
-
- stanza2 = stanza_class(xml=xml)
-
- if use_values:
- # Using stanza.values will add XML for any interface that
- # has a default value. We need to set those defaults on
- # the existing stanzas and XML so that they will compare
- # correctly.
- default_stanza = stanza_class()
- if defaults is None:
- known_defaults = {
- Message: ['type'],
- Presence: ['priority']
- }
- defaults = known_defaults.get(stanza_class, [])
- for interface in defaults:
- stanza[interface] = stanza[interface]
- stanza2[interface] = stanza2[interface]
- # Can really only automatically add defaults for top
- # level attribute values. Anything else must be accounted
- # for in the provided XML string.
- if interface not in xml.attrib:
- if interface in default_stanza.xml.attrib:
- value = default_stanza.xml.attrib[interface]
- xml.attrib[interface] = value
-
- values = stanza2.values
- stanza3 = stanza_class()
- stanza3.values = values
-
- debug = "Three methods for creating stanzas do not match.\n"
- debug += "Given XML:\n%s\n" % tostring(xml)
- debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
- debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
- debug += "Second generated stanza:\n%s\n" % tostring(stanza3.xml)
- result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml)
- else:
- debug = "Two methods for creating stanzas do not match.\n"
- debug += "Given XML:\n%s\n" % tostring(xml)
- debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
- debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
- result = self.compare(xml, stanza.xml, stanza2.xml)
-
- self.failUnless(result, debug)
-
- # ------------------------------------------------------------------
- # Methods for simulating stanza streams.
-
- def stream_disconnect(self):
- """
- Simulate a stream disconnection.
- """
- if self.xmpp:
- self.xmpp.socket.disconnect_error()
-
- def stream_start(self, mode='client', skip=True, header=None, socket='mock', jid='tester@localhost',
- password='test', server='localhost', port=5222, sasl_mech=None, plugins=None, plugin_config=None):
- """
- Initialize an XMPP client or component using a dummy XML stream.
-
- Arguments:
- mode -- Either 'client' or 'component'. Defaults to 'client'.
- skip -- Indicates if the first item in the sent queue (the
- stream header) should be removed. Tests that wish
- to test initializing the stream should set this to
- False. Otherwise, the default of True should be used.
- socket -- Either 'mock' or 'live' to indicate if the socket
- should be a dummy, mock socket or a live, functioning
- socket. Defaults to 'mock'.
- jid -- The JID to use for the connection.
- Defaults to 'tester@localhost'.
- password -- The password to use for the connection.
- Defaults to 'test'.
- server -- The name of the XMPP server. Defaults to 'localhost'.
- port -- The port to use when connecting to the server.
- Defaults to 5222.
- plugins -- List of plugins to register. By default, all plugins
- are loaded.
- """
- if not plugin_config:
- plugin_config = {}
-
- if mode == 'client':
- self.xmpp = ClientXMPP(jid, password,
- sasl_mech=sasl_mech,
- plugin_config=plugin_config)
- elif mode == 'component':
- self.xmpp = ComponentXMPP(jid, password,
- server, port,
- plugin_config=plugin_config)
- else:
- raise ValueError("Unknown XMPP connection mode.")
-
- # Remove unique ID prefix to make it easier to test
- self.xmpp._id_prefix = ''
- self.xmpp._disconnect_wait_for_threads = False
- self.xmpp.default_lang = None
- self.xmpp.peer_default_lang = None
-
- # We will use this to wait for the session_start event
- # for live connections.
- skip_queue = Queue()
-
- if socket == 'mock':
- self.xmpp.set_socket(TestSocket())
-
- # Simulate connecting for mock sockets.
- self.xmpp.auto_reconnect = False
- self.xmpp.state._set_state('connected')
-
- # Must have the stream header ready for xmpp.process() to work.
- if not header:
- header = self.xmpp.stream_header
- self.xmpp.socket.recv_data(header)
- elif socket == 'live':
- self.xmpp.socket_class = TestLiveSocket
-
- def wait_for_session(x):
- self.xmpp.socket.clear()
- skip_queue.put('started')
-
- self.xmpp.add_event_handler('session_start', wait_for_session)
- if server is not None:
- self.xmpp.connect((server, port))
- else:
- self.xmpp.connect()
- else:
- raise ValueError("Unknown socket type.")
-
- if plugins is None:
- self.xmpp.register_plugins()
- else:
- for plugin in plugins:
- self.xmpp.register_plugin(plugin)
-
- # Some plugins require messages to have ID values. Set
- # this to True in tests related to those plugins.
- self.xmpp.use_message_ids = False
-
- self.xmpp.process(threaded=True)
- if skip:
- if socket != 'live':
- # Mark send queue as usable
- self.xmpp.session_bind_event.set()
- self.xmpp.session_started_event.set()
- # Clear startup stanzas
- self.xmpp.socket.next_sent(timeout=1)
- if mode == 'component':
- self.xmpp.socket.next_sent(timeout=1)
- else:
- skip_queue.get(block=True, timeout=10)
-
- def make_header(self, sto='',
- sfrom='',
- sid='',
- stream_ns="http://etherx.jabber.org/streams",
- default_ns="jabber:client",
- default_lang="en",
- version="1.0",
- xml_header=True):
- """
- Create a stream header to be received by the test XMPP agent.
-
- The header must be saved and passed to stream_start.
-
- Arguments:
- sto -- The recipient of the stream header.
- sfrom -- The agent sending the stream header.
- sid -- The stream's id.
- stream_ns -- The namespace of the stream's root element.
- default_ns -- The default stanza namespace.
- version -- The stream version.
- xml_header -- Indicates if the XML version header should be
- appended before the stream header.
- """
- header = '<stream:stream %s>'
- parts = []
- if xml_header:
- header = '<?xml version="1.0"?>' + header
- if sto:
- parts.append('to="%s"' % sto)
- if sfrom:
- parts.append('from="%s"' % sfrom)
- if sid:
- parts.append('id="%s"' % sid)
- if default_lang:
- parts.append('xml:lang="%s"' % default_lang)
- parts.append('version="%s"' % version)
- parts.append('xmlns:stream="%s"' % stream_ns)
- parts.append('xmlns="%s"' % default_ns)
- return header % ' '.join(parts)
-
- def recv(self, data, defaults=None, method='exact', use_values=True, timeout=1):
- """
- Pass data to the dummy XMPP client as if it came from an XMPP server.
-
- If using a live connection, verify what the server has sent.
-
- Arguments:
- data -- If a dummy socket is being used, the XML that is to
- be received next. Otherwise it is the criteria used
- to match against live data that is received.
- defaults -- A list of stanza interfaces with default values that
- may interfere with comparisons.
- method -- Select the type of comparison to use for
- verifying the received stanza. Options are 'exact',
- 'id', 'stanzapath', 'xpath', and 'mask'.
- Defaults to the value of self.match_method.
- use_values -- Indicates if stanza comparisons should test using
- stanza.values. Defaults to True.
- timeout -- Time to wait in seconds for data to be received by
- a live connection.
- """
- if not defaults:
- defaults = []
-
- if self.xmpp.socket.is_live:
- # we are working with a live connection, so we should
- # verify what has been received instead of simulating
- # receiving data.
- recv_data = self.xmpp.socket.next_recv(timeout)
- if recv_data is None:
- self.fail("No stanza was received.")
- xml = self.parse_xml(recv_data)
- self.fix_namespaces(xml, 'jabber:client')
- stanza = self.xmpp._build_stanza(xml, 'jabber:client')
- self.check(stanza, data,
- method=method,
- defaults=defaults,
- use_values=use_values)
- else:
- # place the data in the dummy socket receiving queue.
- data = str(data)
- self.xmpp.socket.recv_data(data)
-
- def recv_header(self, sto='',
- sfrom='',
- sid='',
- stream_ns="http://etherx.jabber.org/streams",
- default_ns="jabber:client",
- version="1.0",
- xml_header=False,
- timeout=1):
- """
- Check that a given stream header was received.
-
- Arguments:
- sto -- The recipient of the stream header.
- sfrom -- The agent sending the stream header.
- sid -- The stream's id. Set to None to ignore.
- stream_ns -- The namespace of the stream's root element.
- default_ns -- The default stanza namespace.
- version -- The stream version.
- xml_header -- Indicates if the XML version header should be
- appended before the stream header.
- timeout -- Length of time to wait in seconds for a
- response.
- """
- header = self.make_header(sto, sfrom, sid,
- stream_ns=stream_ns,
- default_ns=default_ns,
- version=version,
- xml_header=xml_header)
- recv_header = self.xmpp.socket.next_recv(timeout)
- if recv_header is None:
- raise ValueError("Socket did not return data.")
-
- # Apply closing elements so that we can construct
- # XML objects for comparison.
- header2 = header + '</stream:stream>'
- recv_header2 = recv_header + '</stream:stream>'
-
- xml = self.parse_xml(header2)
- recv_xml = self.parse_xml(recv_header2)
-
- if sid is None:
- # Ignore the id sent by the server since
- # we can't know in advance what it will be.
- if 'id' in recv_xml.attrib:
- del recv_xml.attrib['id']
-
- # Ignore the xml:lang attribute for now.
- if 'xml:lang' in recv_xml.attrib:
- del recv_xml.attrib['xml:lang']
- xml_ns = 'http://www.w3.org/XML/1998/namespace'
- if '{%s}lang' % xml_ns in recv_xml.attrib:
- del recv_xml.attrib['{%s}lang' % xml_ns]
-
- if list(recv_xml):
- # We received more than just the header
- for xml in recv_xml:
- self.xmpp.socket.recv_data(tostring(xml))
-
- attrib = recv_xml.attrib
- recv_xml.clear()
- recv_xml.attrib = attrib
-
- self.failUnless(
- self.compare(xml, recv_xml),
- "Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
- '%s %s' % (xml.tag, xml.attrib),
- '%s %s' % (recv_xml.tag, recv_xml.attrib)))
-
- def recv_feature(self, data, method='mask', use_values=True, timeout=1):
- """
- """
- if method is None and hasattr(self, 'match_method'):
- method = getattr(self, 'match_method')
-
- if self.xmpp.socket.is_live:
- # we are working with a live connection, so we should
- # verify what has been received instead of simulating
- # receiving data.
- recv_data = self.xmpp.socket.next_recv(timeout)
- xml = self.parse_xml(data)
- recv_xml = self.parse_xml(recv_data)
- if recv_data is None:
- self.fail("No stanza was received.")
- if method == 'exact':
- self.failUnless(self.compare(xml, recv_xml),
- "Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
- tostring(xml), tostring(recv_xml)))
- elif method == 'mask':
- matcher = MatchXMLMask(xml)
- self.failUnless(matcher.match(recv_xml),
- "Stanza did not match using %s method:\n" % method + \
- "Criteria:\n%s\n" % tostring(xml) + \
- "Stanza:\n%s" % tostring(recv_xml))
- else:
- raise ValueError("Uknown matching method: %s" % method)
- else:
- # place the data in the dummy socket receiving queue.
- data = str(data)
- self.xmpp.socket.recv_data(data)
-
- def send_header(self, sto='',
- sfrom='',
- sid='',
- stream_ns="http://etherx.jabber.org/streams",
- default_ns="jabber:client",
- default_lang="en",
- version="1.0",
- xml_header=False,
- timeout=1):
- """
- Check that a given stream header was sent.
-
- Arguments:
- sto -- The recipient of the stream header.
- sfrom -- The agent sending the stream header.
- sid -- The stream's id.
- stream_ns -- The namespace of the stream's root element.
- default_ns -- The default stanza namespace.
- version -- The stream version.
- xml_header -- Indicates if the XML version header should be
- appended before the stream header.
- timeout -- Length of time to wait in seconds for a
- response.
- """
- header = self.make_header(sto, sfrom, sid,
- stream_ns=stream_ns,
- default_ns=default_ns,
- default_lang=default_lang,
- version=version,
- xml_header=xml_header)
- sent_header = self.xmpp.socket.next_sent(timeout)
- if sent_header is None:
- raise ValueError("Socket did not return data.")
-
- # Apply closing elements so that we can construct
- # XML objects for comparison.
- header2 = header + '</stream:stream>'
- sent_header2 = sent_header + b'</stream:stream>'
-
- xml = self.parse_xml(header2)
- sent_xml = self.parse_xml(sent_header2)
-
- self.failUnless(
- self.compare(xml, sent_xml),
- "Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
- header, sent_header))
-
- def send_feature(self, data, method='mask', use_values=True, timeout=1):
- """
- """
- sent_data = self.xmpp.socket.next_sent(timeout)
- xml = self.parse_xml(data)
- sent_xml = self.parse_xml(sent_data)
- if sent_data is None:
- self.fail("No stanza was sent.")
- if method == 'exact':
- self.failUnless(self.compare(xml, sent_xml),
- "Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
- tostring(xml), tostring(sent_xml)))
- elif method == 'mask':
- matcher = MatchXMLMask(xml)
- self.failUnless(matcher.match(sent_xml),
- "Stanza did not match using %s method:\n" % method + \
- "Criteria:\n%s\n" % tostring(xml) + \
- "Stanza:\n%s" % tostring(sent_xml))
- else:
- raise ValueError("Uknown matching method: %s" % method)
-
- def send(self, data, defaults=None, use_values=True,
- timeout=.5, method='exact'):
- """
- Check that the XMPP client sent the given stanza XML.
-
- Extracts the next sent stanza and compares it with the given
- XML using check.
-
- Arguments:
- stanza_class -- The class of the sent stanza object.
- data -- The XML string of the expected Message stanza,
- or an equivalent stanza object.
- use_values -- Modifies the type of tests used by check_message.
- defaults -- A list of stanza interfaces that have defaults
- values which may interfere with comparisons.
- timeout -- Time in seconds to wait for a stanza before
- failing the check.
- method -- Select the type of comparison to use for
- verifying the sent stanza. Options are 'exact',
- 'id', 'stanzapath', 'xpath', and 'mask'.
- Defaults to the value of self.match_method.
- """
- sent = self.xmpp.socket.next_sent(timeout)
- if data is None and sent is None:
- return
- if data is None and sent is not None:
- self.fail("Stanza data was sent: %s" % sent)
- if sent is None:
- self.fail("No stanza was sent.")
-
- xml = self.parse_xml(sent)
- self.fix_namespaces(xml, 'jabber:client')
- sent = self.xmpp._build_stanza(xml, 'jabber:client')
- self.check(sent, data,
- method=method,
- defaults=defaults,
- use_values=use_values)
-
- def stream_close(self):
- """
- Disconnect the dummy XMPP client.
-
- Can be safely called even if stream_start has not been called.
-
- Must be placed in the tearDown method of a test class to ensure
- that the XMPP client is disconnected after an error.
- """
- if hasattr(self, 'xmpp') and self.xmpp is not None:
- self.xmpp.socket.recv_data(self.xmpp.stream_footer)
- self.xmpp.disconnect()
-
- # ------------------------------------------------------------------
- # XML Comparison and Cleanup
-
- def fix_namespaces(self, xml, ns):
- """
- Assign a namespace to an element and any children that
- don't have a namespace.
-
- Arguments:
- xml -- The XML object to fix.
- ns -- The namespace to add to the XML object.
- """
- if xml.tag.startswith('{'):
- return
- xml.tag = '{%s}%s' % (ns, xml.tag)
- for child in xml:
- self.fix_namespaces(child, ns)
-
- def compare(self, xml, *other):
- """
- Compare XML objects.
-
- Arguments:
- xml -- The XML object to compare against.
- *other -- The list of XML objects to compare.
- """
- if not other:
- return False
-
- # Compare multiple objects
- if len(other) > 1:
- for xml2 in other:
- if not self.compare(xml, xml2):
- return False
- return True
-
- other = other[0]
-
- # Step 1: Check tags
- if xml.tag != other.tag:
- return False
-
- # Step 2: Check attributes
- if xml.attrib != other.attrib:
- return False
-
- # Step 3: Check text
- if xml.text is None:
- xml.text = ""
- if other.text is None:
- other.text = ""
- xml.text = xml.text.strip()
- other.text = other.text.strip()
-
- if xml.text != other.text:
- return False
-
- # Step 4: Check children count
- if len(list(xml)) != len(list(other)):
- return False
-
- # Step 5: Recursively check children
- for child in xml:
- child2s = other.findall("%s" % child.tag)
- if child2s is None:
- return False
- for child2 in child2s:
- if self.compare(child, child2):
- break
- else:
- return False
-
- # Step 6: Recursively check children the other way.
- for child in other:
- child2s = xml.findall("%s" % child.tag)
- if child2s is None:
- return False
- for child2 in child2s:
- if self.compare(child, child2):
- break
- else:
- return False
-
- # Everything matches
- return True
diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py
deleted file mode 100644
index 337598ac..00000000
--- a/sleekxmpp/thirdparty/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-try:
- from collections import OrderedDict
-except:
- from sleekxmpp.thirdparty.ordereddict import OrderedDict
-
-try:
- from gnupg import GPG
-except:
- from sleekxmpp.thirdparty.gnupg import GPG
-
-from sleekxmpp.thirdparty import socks
-from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
-from sleekxmpp.thirdparty.orderedset import OrderedSet
diff --git a/sleekxmpp/thirdparty/gnupg.py b/sleekxmpp/thirdparty/gnupg.py
deleted file mode 100644
index a89289fd..00000000
--- a/sleekxmpp/thirdparty/gnupg.py
+++ /dev/null
@@ -1,1017 +0,0 @@
-""" A wrapper for the 'gpg' command::
-
-Portions of this module are derived from A.M. Kuchling's well-designed
-GPG.py, using Richard Jones' updated version 1.3, which can be found
-in the pycrypto CVS repository on Sourceforge:
-
-http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
-
-This module is *not* forward-compatible with amk's; some of the
-old interface has changed. For instance, since I've added decrypt
-functionality, I elected to initialize with a 'gnupghome' argument
-instead of 'keyring', so that gpg can find both the public and secret
-keyrings. I've also altered some of the returned objects in order for
-the caller to not have to know as much about the internals of the
-result classes.
-
-While the rest of ISconf is released under the GPL, I am releasing
-this single file under the same terms that A.M. Kuchling used for
-pycrypto.
-
-Steve Traugott, stevegt@terraluna.org
-Thu Jun 23 21:27:20 PDT 2005
-
-This version of the module has been modified from Steve Traugott's version
-(see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
-Vinay Sajip to make use of the subprocess module (Steve's version uses os.fork()
-and so does not work on Windows). Renamed to gnupg.py to avoid confusion with
-the previous versions.
-
-Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
-
-A unittest harness (test_gnupg.py) has also been added.
-"""
-import locale
-
-__version__ = "0.2.9"
-__author__ = "Vinay Sajip"
-__date__ = "$29-Mar-2012 21:12:58$"
-
-try:
- from io import StringIO
-except ImportError:
- from cStringIO import StringIO
-
-import codecs
-import locale
-import logging
-import os
-import socket
-from subprocess import Popen
-from subprocess import PIPE
-import sys
-import threading
-
-try:
- import logging.NullHandler as NullHandler
-except ImportError:
- class NullHandler(logging.Handler):
- def handle(self, record):
- pass
-try:
- unicode
- _py3k = False
-except NameError:
- _py3k = True
-
-logger = logging.getLogger(__name__)
-if not logger.handlers:
- logger.addHandler(NullHandler())
-
-def _copy_data(instream, outstream):
- # Copy one stream to another
- sent = 0
- if hasattr(sys.stdin, 'encoding'):
- enc = sys.stdin.encoding
- else:
- enc = 'ascii'
- while True:
- data = instream.read(1024)
- if len(data) == 0:
- break
- sent += len(data)
- logger.debug("sending chunk (%d): %r", sent, data[:256])
- try:
- outstream.write(data)
- except UnicodeError:
- outstream.write(data.encode(enc))
- except:
- # Can sometimes get 'broken pipe' errors even when the data has all
- # been sent
- logger.exception('Error sending data')
- break
- try:
- outstream.close()
- except IOError:
- logger.warning('Exception occurred while closing: ignored', exc_info=1)
- logger.debug("closed output, %d bytes sent", sent)
-
-def _threaded_copy_data(instream, outstream):
- wr = threading.Thread(target=_copy_data, args=(instream, outstream))
- wr.setDaemon(True)
- logger.debug('data copier: %r, %r, %r', wr, instream, outstream)
- wr.start()
- return wr
-
-def _write_passphrase(stream, passphrase, encoding):
- passphrase = '%s\n' % passphrase
- passphrase = passphrase.encode(encoding)
- stream.write(passphrase)
- logger.debug("Wrote passphrase: %r", passphrase)
-
-def _is_sequence(instance):
- return isinstance(instance,list) or isinstance(instance,tuple)
-
-def _make_binary_stream(s, encoding):
- try:
- if _py3k:
- if isinstance(s, str):
- s = s.encode(encoding)
- else:
- if type(s) is not str:
- s = s.encode(encoding)
- from io import BytesIO
- rv = BytesIO(s)
- except ImportError:
- rv = StringIO(s)
- return rv
-
-class Verify(object):
- "Handle status messages for --verify"
-
- def __init__(self, gpg):
- self.gpg = gpg
- self.valid = False
- self.fingerprint = self.creation_date = self.timestamp = None
- self.signature_id = self.key_id = None
- self.username = None
-
- def __nonzero__(self):
- return self.valid
-
- __bool__ = __nonzero__
-
- def handle_status(self, key, value):
- if key in ("TRUST_UNDEFINED", "TRUST_NEVER", "TRUST_MARGINAL",
- "TRUST_FULLY", "TRUST_ULTIMATE", "RSA_OR_IDEA", "NODATA",
- "IMPORT_RES", "PLAINTEXT", "PLAINTEXT_LENGTH",
- "POLICY_URL", "DECRYPTION_INFO", "DECRYPTION_OKAY", "IMPORTED"):
- pass
- elif key == "BADSIG":
- self.valid = False
- self.status = 'signature bad'
- self.key_id, self.username = value.split(None, 1)
- elif key == "GOODSIG":
- self.valid = True
- self.status = 'signature good'
- self.key_id, self.username = value.split(None, 1)
- elif key == "VALIDSIG":
- (self.fingerprint,
- self.creation_date,
- self.sig_timestamp,
- self.expire_timestamp) = value.split()[:4]
- # may be different if signature is made with a subkey
- self.pubkey_fingerprint = value.split()[-1]
- self.status = 'signature valid'
- elif key == "SIG_ID":
- (self.signature_id,
- self.creation_date, self.timestamp) = value.split()
- elif key == "ERRSIG":
- self.valid = False
- (self.key_id,
- algo, hash_algo,
- cls,
- self.timestamp) = value.split()[:5]
- self.status = 'signature error'
- elif key == "DECRYPTION_FAILED":
- self.valid = False
- self.key_id = value
- self.status = 'decryption failed'
- elif key == "NO_PUBKEY":
- self.valid = False
- self.key_id = value
- self.status = 'no public key'
- elif key in ("KEYEXPIRED", "SIGEXPIRED"):
- # these are useless in verify, since they are spit out for any
- # pub/subkeys on the key, not just the one doing the signing.
- # if we want to check for signatures with expired key,
- # the relevant flag is EXPKEYSIG.
- pass
- elif key in ("EXPKEYSIG", "REVKEYSIG"):
- # signed with expired or revoked key
- self.valid = False
- self.key_id = value.split()[0]
- self.status = (('%s %s') % (key[:3], key[3:])).lower()
- else:
- raise ValueError("Unknown status message: %r" % key)
-
-class ImportResult(object):
- "Handle status messages for --import"
-
- counts = '''count no_user_id imported imported_rsa unchanged
- n_uids n_subk n_sigs n_revoc sec_read sec_imported
- sec_dups not_imported'''.split()
- def __init__(self, gpg):
- self.gpg = gpg
- self.imported = []
- self.results = []
- self.fingerprints = []
- for result in self.counts:
- setattr(self, result, None)
-
- def __nonzero__(self):
- if self.not_imported: return False
- if not self.fingerprints: return False
- return True
-
- __bool__ = __nonzero__
-
- ok_reason = {
- '0': 'Not actually changed',
- '1': 'Entirely new key',
- '2': 'New user IDs',
- '4': 'New signatures',
- '8': 'New subkeys',
- '16': 'Contains private key',
- }
-
- problem_reason = {
- '0': 'No specific reason given',
- '1': 'Invalid Certificate',
- '2': 'Issuer Certificate missing',
- '3': 'Certificate Chain too long',
- '4': 'Error storing certificate',
- }
-
- def handle_status(self, key, value):
- if key == "IMPORTED":
- # this duplicates info we already see in import_ok & import_problem
- pass
- elif key == "NODATA":
- self.results.append({'fingerprint': None,
- 'problem': '0', 'text': 'No valid data found'})
- elif key == "IMPORT_OK":
- reason, fingerprint = value.split()
- reasons = []
- for code, text in list(self.ok_reason.items()):
- if int(reason) | int(code) == int(reason):
- reasons.append(text)
- reasontext = '\n'.join(reasons) + "\n"
- self.results.append({'fingerprint': fingerprint,
- 'ok': reason, 'text': reasontext})
- self.fingerprints.append(fingerprint)
- elif key == "IMPORT_PROBLEM":
- try:
- reason, fingerprint = value.split()
- except:
- reason = value
- fingerprint = '<unknown>'
- self.results.append({'fingerprint': fingerprint,
- 'problem': reason, 'text': self.problem_reason[reason]})
- elif key == "IMPORT_RES":
- import_res = value.split()
- for i in range(len(self.counts)):
- setattr(self, self.counts[i], int(import_res[i]))
- elif key == "KEYEXPIRED":
- self.results.append({'fingerprint': None,
- 'problem': '0', 'text': 'Key expired'})
- elif key == "SIGEXPIRED":
- self.results.append({'fingerprint': None,
- 'problem': '0', 'text': 'Signature expired'})
- else:
- raise ValueError("Unknown status message: %r" % key)
-
- def summary(self):
- l = []
- l.append('%d imported'%self.imported)
- if self.not_imported:
- l.append('%d not imported'%self.not_imported)
- return ', '.join(l)
-
-class ListKeys(list):
- ''' Handle status messages for --list-keys.
-
- Handle pub and uid (relating the latter to the former).
-
- Don't care about (info from src/DETAILS):
-
- crt = X.509 certificate
- crs = X.509 certificate and private key available
- sub = subkey (secondary key)
- ssb = secret subkey (secondary key)
- uat = user attribute (same as user id except for field 10).
- sig = signature
- rev = revocation signature
- pkd = public key data (special field format, see below)
- grp = reserved for gpgsm
- rvk = revocation key
- '''
- def __init__(self, gpg):
- self.gpg = gpg
- self.curkey = None
- self.fingerprints = []
- self.uids = []
-
- def key(self, args):
- vars = ("""
- type trust length algo keyid date expires dummy ownertrust uid
- """).split()
- self.curkey = {}
- for i in range(len(vars)):
- self.curkey[vars[i]] = args[i]
- self.curkey['uids'] = []
- if self.curkey['uid']:
- self.curkey['uids'].append(self.curkey['uid'])
- del self.curkey['uid']
- self.append(self.curkey)
-
- pub = sec = key
-
- def fpr(self, args):
- self.curkey['fingerprint'] = args[9]
- self.fingerprints.append(args[9])
-
- def uid(self, args):
- self.curkey['uids'].append(args[9])
- self.uids.append(args[9])
-
- def handle_status(self, key, value):
- pass
-
-class Crypt(Verify):
- "Handle status messages for --encrypt and --decrypt"
- def __init__(self, gpg):
- Verify.__init__(self, gpg)
- self.data = ''
- self.ok = False
- self.status = ''
-
- def __nonzero__(self):
- if self.ok: return True
- return False
-
- __bool__ = __nonzero__
-
- def __str__(self):
- return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
-
- def handle_status(self, key, value):
- if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
- "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
- "CARDCTRL"):
- # in the case of ERROR, this is because a more specific error
- # message will have come first
- pass
- elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
- "MISSING_PASSPHRASE", "DECRYPTION_FAILED",
- "KEY_NOT_CREATED"):
- self.status = key.replace("_", " ").lower()
- elif key == "NEED_PASSPHRASE_SYM":
- self.status = 'need symmetric passphrase'
- elif key == "BEGIN_DECRYPTION":
- self.status = 'decryption incomplete'
- elif key == "BEGIN_ENCRYPTION":
- self.status = 'encryption incomplete'
- elif key == "DECRYPTION_OKAY":
- self.status = 'decryption ok'
- self.ok = True
- elif key == "END_ENCRYPTION":
- self.status = 'encryption ok'
- self.ok = True
- elif key == "INV_RECP":
- self.status = 'invalid recipient'
- elif key == "KEYEXPIRED":
- self.status = 'key expired'
- elif key == "SIG_CREATED":
- self.status = 'sig created'
- elif key == "SIGEXPIRED":
- self.status = 'sig expired'
- else:
- Verify.handle_status(self, key, value)
-
-class GenKey(object):
- "Handle status messages for --gen-key"
- def __init__(self, gpg):
- self.gpg = gpg
- self.type = None
- self.fingerprint = None
-
- def __nonzero__(self):
- if self.fingerprint: return True
- return False
-
- __bool__ = __nonzero__
-
- def __str__(self):
- return self.fingerprint or ''
-
- def handle_status(self, key, value):
- if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA"):
- pass
- elif key == "KEY_CREATED":
- (self.type,self.fingerprint) = value.split()
- else:
- raise ValueError("Unknown status message: %r" % key)
-
-class DeleteResult(object):
- "Handle status messages for --delete-key and --delete-secret-key"
- def __init__(self, gpg):
- self.gpg = gpg
- self.status = 'ok'
-
- def __str__(self):
- return self.status
-
- problem_reason = {
- '1': 'No such key',
- '2': 'Must delete secret key first',
- '3': 'Ambigious specification',
- }
-
- def handle_status(self, key, value):
- if key == "DELETE_PROBLEM":
- self.status = self.problem_reason.get(value,
- "Unknown error: %r" % value)
- else:
- raise ValueError("Unknown status message: %r" % key)
-
-class Sign(object):
- "Handle status messages for --sign"
- def __init__(self, gpg):
- self.gpg = gpg
- self.type = None
- self.fingerprint = None
-
- def __nonzero__(self):
- return self.fingerprint is not None
-
- __bool__ = __nonzero__
-
- def __str__(self):
- return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
-
- def handle_status(self, key, value):
- if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
- "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL"):
- pass
- elif key == "SIG_CREATED":
- (self.type,
- algo, hashalgo, cls,
- self.timestamp, self.fingerprint
- ) = value.split()
- else:
- raise ValueError("Unknown status message: %r" % key)
-
-
-class GPG(object):
-
- decode_errors = 'strict'
-
- result_map = {
- 'crypt': Crypt,
- 'delete': DeleteResult,
- 'generate': GenKey,
- 'import': ImportResult,
- 'list': ListKeys,
- 'sign': Sign,
- 'verify': Verify,
- }
-
- "Encapsulate access to the gpg executable"
- def __init__(self, gpgbinary='gpg', gnupghome=None, verbose=False,
- use_agent=False, keyring=None):
- """Initialize a GPG process wrapper. Options are:
-
- gpgbinary -- full pathname for GPG binary.
-
- gnupghome -- full pathname to where we can find the public and
- private keyrings. Default is whatever gpg defaults to.
- keyring -- name of alternative keyring file to use. If specified,
- the default keyring is not used.
- """
- self.gpgbinary = gpgbinary
- self.gnupghome = gnupghome
- self.keyring = keyring
- self.verbose = verbose
- self.use_agent = use_agent
- self.encoding = locale.getpreferredencoding()
- if self.encoding is None: # This happens on Jython!
- self.encoding = sys.stdin.encoding
- if gnupghome and not os.path.isdir(self.gnupghome):
- os.makedirs(self.gnupghome,0x1C0)
- p = self._open_subprocess(["--version"])
- result = self.result_map['verify'](self) # any result will do for this
- self._collect_output(p, result, stdin=p.stdin)
- if p.returncode != 0:
- raise ValueError("Error invoking gpg: %s: %s" % (p.returncode,
- result.stderr))
-
- def _open_subprocess(self, args, passphrase=False):
- # Internal method: open a pipe to a GPG subprocess and return
- # the file objects for communicating with it.
- cmd = [self.gpgbinary, '--status-fd 2 --no-tty']
- if self.gnupghome:
- cmd.append('--homedir "%s" ' % self.gnupghome)
- if self.keyring:
- cmd.append('--no-default-keyring --keyring "%s" ' % self.keyring)
- if passphrase:
- cmd.append('--batch --passphrase-fd 0')
- if self.use_agent:
- cmd.append('--use-agent')
- cmd.extend(args)
- cmd = ' '.join(cmd)
- if self.verbose:
- print(cmd)
- logger.debug("%s", cmd)
- return Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
-
- def _read_response(self, stream, result):
- # Internal method: reads all the stderr output from GPG, taking notice
- # only of lines that begin with the magic [GNUPG:] prefix.
- #
- # Calls methods on the response object for each valid token found,
- # with the arg being the remainder of the status line.
- lines = []
- while True:
- line = stream.readline()
- if len(line) == 0:
- break
- lines.append(line)
- line = line.rstrip()
- if self.verbose:
- print(line)
- logger.debug("%s", line)
- if line[0:9] == '[GNUPG:] ':
- # Chop off the prefix
- line = line[9:]
- L = line.split(None, 1)
- keyword = L[0]
- if len(L) > 1:
- value = L[1]
- else:
- value = ""
- result.handle_status(keyword, value)
- result.stderr = ''.join(lines)
-
- def _read_data(self, stream, result):
- # Read the contents of the file from GPG's stdout
- chunks = []
- while True:
- data = stream.read(1024)
- if len(data) == 0:
- break
- logger.debug("chunk: %r" % data[:256])
- chunks.append(data)
- if _py3k:
- # Join using b'' or '', as appropriate
- result.data = type(data)().join(chunks)
- else:
- result.data = ''.join(chunks)
-
- def _collect_output(self, process, result, writer=None, stdin=None):
- """
- Drain the subprocesses output streams, writing the collected output
- to the result. If a writer thread (writing to the subprocess) is given,
- make sure it's joined before returning. If a stdin stream is given,
- close it before returning.
- """
- stderr = codecs.getreader(self.encoding)(process.stderr)
- rr = threading.Thread(target=self._read_response, args=(stderr, result))
- rr.setDaemon(True)
- logger.debug('stderr reader: %r', rr)
- rr.start()
-
- stdout = process.stdout
- dr = threading.Thread(target=self._read_data, args=(stdout, result))
- dr.setDaemon(True)
- logger.debug('stdout reader: %r', dr)
- dr.start()
-
- dr.join()
- rr.join()
- if writer is not None:
- writer.join()
- process.wait()
- if stdin is not None:
- try:
- stdin.close()
- except IOError:
- pass
- stderr.close()
- stdout.close()
-
- def _handle_io(self, args, file, result, passphrase=None, binary=False):
- "Handle a call to GPG - pass input data, collect output data"
- # Handle a basic data call - pass data to GPG, handle the output
- # including status information. Garbage In, Garbage Out :)
- p = self._open_subprocess(args, passphrase is not None)
- if not binary:
- stdin = codecs.getwriter(self.encoding)(p.stdin)
- else:
- stdin = p.stdin
- if passphrase:
- _write_passphrase(stdin, passphrase, self.encoding)
- writer = _threaded_copy_data(file, stdin)
- self._collect_output(p, result, writer, stdin)
- return result
-
- #
- # SIGNATURE METHODS
- #
- def sign(self, message, **kwargs):
- """sign message"""
- f = _make_binary_stream(message, self.encoding)
- result = self.sign_file(f, **kwargs)
- f.close()
- return result
-
- def sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
- detach=False, binary=False):
- """sign file"""
- logger.debug("sign_file: %s", file)
- if binary:
- args = ['-s']
- else:
- args = ['-sa']
- # You can't specify detach-sign and clearsign together: gpg ignores
- # the detach-sign in that case.
- if detach:
- args.append("--detach-sign")
- elif clearsign:
- args.append("--clearsign")
- if keyid:
- args.append('--default-key "%s"' % keyid)
- args.extend(['--no-version', "--comment ''"])
- result = self.result_map['sign'](self)
- #We could use _handle_io here except for the fact that if the
- #passphrase is bad, gpg bails and you can't write the message.
- p = self._open_subprocess(args, passphrase is not None)
- try:
- stdin = p.stdin
- if passphrase:
- _write_passphrase(stdin, passphrase, self.encoding)
- writer = _threaded_copy_data(file, stdin)
- except IOError:
- logging.exception("error writing message")
- writer = None
- self._collect_output(p, result, writer, stdin)
- return result
-
- def verify(self, data):
- """Verify the signature on the contents of the string 'data'
-
- >>> gpg = GPG(gnupghome="keys")
- >>> input = gpg.gen_key_input(Passphrase='foo')
- >>> key = gpg.gen_key(input)
- >>> assert key
- >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar')
- >>> assert not sig
- >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='foo')
- >>> assert sig
- >>> verify = gpg.verify(sig.data)
- >>> assert verify
-
- """
- f = _make_binary_stream(data, self.encoding)
- result = self.verify_file(f)
- f.close()
- return result
-
- def verify_file(self, file, data_filename=None):
- "Verify the signature on the contents of the file-like object 'file'"
- logger.debug('verify_file: %r, %r', file, data_filename)
- result = self.result_map['verify'](self)
- args = ['--verify']
- if data_filename is None:
- self._handle_io(args, file, result, binary=True)
- else:
- logger.debug('Handling detached verification')
- import tempfile
- fd, fn = tempfile.mkstemp(prefix='pygpg')
- s = file.read()
- file.close()
- logger.debug('Wrote to temp file: %r', s)
- os.write(fd, s)
- os.close(fd)
- args.append(fn)
- args.append('"%s"' % data_filename)
- try:
- p = self._open_subprocess(args)
- self._collect_output(p, result, stdin=p.stdin)
- finally:
- os.unlink(fn)
- return result
-
- #
- # KEY MANAGEMENT
- #
-
- def import_keys(self, key_data):
- """ import the key_data into our keyring
-
- >>> import shutil
- >>> shutil.rmtree("keys")
- >>> gpg = GPG(gnupghome="keys")
- >>> input = gpg.gen_key_input()
- >>> result = gpg.gen_key(input)
- >>> print1 = result.fingerprint
- >>> result = gpg.gen_key(input)
- >>> print2 = result.fingerprint
- >>> pubkey1 = gpg.export_keys(print1)
- >>> seckey1 = gpg.export_keys(print1,secret=True)
- >>> seckeys = gpg.list_keys(secret=True)
- >>> pubkeys = gpg.list_keys()
- >>> assert print1 in seckeys.fingerprints
- >>> assert print1 in pubkeys.fingerprints
- >>> str(gpg.delete_keys(print1))
- 'Must delete secret key first'
- >>> str(gpg.delete_keys(print1,secret=True))
- 'ok'
- >>> str(gpg.delete_keys(print1))
- 'ok'
- >>> str(gpg.delete_keys("nosuchkey"))
- 'No such key'
- >>> seckeys = gpg.list_keys(secret=True)
- >>> pubkeys = gpg.list_keys()
- >>> assert not print1 in seckeys.fingerprints
- >>> assert not print1 in pubkeys.fingerprints
- >>> result = gpg.import_keys('foo')
- >>> assert not result
- >>> result = gpg.import_keys(pubkey1)
- >>> pubkeys = gpg.list_keys()
- >>> seckeys = gpg.list_keys(secret=True)
- >>> assert not print1 in seckeys.fingerprints
- >>> assert print1 in pubkeys.fingerprints
- >>> result = gpg.import_keys(seckey1)
- >>> assert result
- >>> seckeys = gpg.list_keys(secret=True)
- >>> pubkeys = gpg.list_keys()
- >>> assert print1 in seckeys.fingerprints
- >>> assert print1 in pubkeys.fingerprints
- >>> assert print2 in pubkeys.fingerprints
-
- """
- result = self.result_map['import'](self)
- logger.debug('import_keys: %r', key_data[:256])
- data = _make_binary_stream(key_data, self.encoding)
- self._handle_io(['--import'], data, result, binary=True)
- logger.debug('import_keys result: %r', result.__dict__)
- data.close()
- return result
-
- def recv_keys(self, keyserver, *keyids):
- """Import a key from a keyserver
-
- >>> import shutil
- >>> shutil.rmtree("keys")
- >>> gpg = GPG(gnupghome="keys")
- >>> result = gpg.recv_keys('pgp.mit.edu', '3FF0DB166A7476EA')
- >>> assert result
-
- """
- result = self.result_map['import'](self)
- logger.debug('recv_keys: %r', keyids)
- data = _make_binary_stream("", self.encoding)
- #data = ""
- args = ['--keyserver', keyserver, '--recv-keys']
- args.extend(keyids)
- self._handle_io(args, data, result, binary=True)
- logger.debug('recv_keys result: %r', result.__dict__)
- data.close()
- return result
-
- def delete_keys(self, fingerprints, secret=False):
- which='key'
- if secret:
- which='secret-key'
- if _is_sequence(fingerprints):
- fingerprints = ' '.join(fingerprints)
- args = ['--batch --delete-%s "%s"' % (which, fingerprints)]
- result = self.result_map['delete'](self)
- p = self._open_subprocess(args)
- self._collect_output(p, result, stdin=p.stdin)
- return result
-
- def export_keys(self, keyids, secret=False):
- "export the indicated keys. 'keyid' is anything gpg accepts"
- which=''
- if secret:
- which='-secret-key'
- if _is_sequence(keyids):
- keyids = ' '.join(['"%s"' % k for k in keyids])
- args = ["--armor --export%s %s" % (which, keyids)]
- p = self._open_subprocess(args)
- # gpg --export produces no status-fd output; stdout will be
- # empty in case of failure
- #stdout, stderr = p.communicate()
- result = self.result_map['delete'](self) # any result will do
- self._collect_output(p, result, stdin=p.stdin)
- logger.debug('export_keys result: %r', result.data)
- return result.data.decode(self.encoding, self.decode_errors)
-
- def list_keys(self, secret=False):
- """ list the keys currently in the keyring
-
- >>> import shutil
- >>> shutil.rmtree("keys")
- >>> gpg = GPG(gnupghome="keys")
- >>> input = gpg.gen_key_input()
- >>> result = gpg.gen_key(input)
- >>> print1 = result.fingerprint
- >>> result = gpg.gen_key(input)
- >>> print2 = result.fingerprint
- >>> pubkeys = gpg.list_keys()
- >>> assert print1 in pubkeys.fingerprints
- >>> assert print2 in pubkeys.fingerprints
-
- """
-
- which='keys'
- if secret:
- which='secret-keys'
- args = "--list-%s --fixed-list-mode --fingerprint --with-colons" % (which,)
- args = [args]
- p = self._open_subprocess(args)
-
- # there might be some status thingumy here I should handle... (amk)
- # ...nope, unless you care about expired sigs or keys (stevegt)
-
- # Get the response information
- result = self.result_map['list'](self)
- self._collect_output(p, result, stdin=p.stdin)
- lines = result.data.decode(self.encoding,
- self.decode_errors).splitlines()
- valid_keywords = 'pub uid sec fpr'.split()
- for line in lines:
- if self.verbose:
- print(line)
- logger.debug("line: %r", line.rstrip())
- if not line:
- break
- L = line.strip().split(':')
- if not L:
- continue
- keyword = L[0]
- if keyword in valid_keywords:
- getattr(result, keyword)(L)
- return result
-
- def gen_key(self, input):
- """Generate a key; you might use gen_key_input() to create the
- control input.
-
- >>> gpg = GPG(gnupghome="keys")
- >>> input = gpg.gen_key_input()
- >>> result = gpg.gen_key(input)
- >>> assert result
- >>> result = gpg.gen_key('foo')
- >>> assert not result
-
- """
- args = ["--gen-key --batch"]
- result = self.result_map['generate'](self)
- f = _make_binary_stream(input, self.encoding)
- self._handle_io(args, f, result, binary=True)
- f.close()
- return result
-
- def gen_key_input(self, **kwargs):
- """
- Generate --gen-key input per gpg doc/DETAILS
- """
- parms = {}
- for key, val in list(kwargs.items()):
- key = key.replace('_','-').title()
- parms[key] = val
- parms.setdefault('Key-Type','RSA')
- parms.setdefault('Key-Length',1024)
- parms.setdefault('Name-Real', "Autogenerated Key")
- parms.setdefault('Name-Comment', "Generated by gnupg.py")
- try:
- logname = os.environ['LOGNAME']
- except KeyError:
- logname = os.environ['USERNAME']
- hostname = socket.gethostname()
- parms.setdefault('Name-Email', "%s@%s" % (logname.replace(' ', '_'),
- hostname))
- out = "Key-Type: %s\n" % parms.pop('Key-Type')
- for key, val in list(parms.items()):
- out += "%s: %s\n" % (key, val)
- out += "%commit\n"
- return out
-
- # Key-Type: RSA
- # Key-Length: 1024
- # Name-Real: ISdlink Server on %s
- # Name-Comment: Created by %s
- # Name-Email: isdlink@%s
- # Expire-Date: 0
- # %commit
- #
- #
- # Key-Type: DSA
- # Key-Length: 1024
- # Subkey-Type: ELG-E
- # Subkey-Length: 1024
- # Name-Real: Joe Tester
- # Name-Comment: with stupid passphrase
- # Name-Email: joe@foo.bar
- # Expire-Date: 0
- # Passphrase: abc
- # %pubring foo.pub
- # %secring foo.sec
- # %commit
-
- #
- # ENCRYPTION
- #
- def encrypt_file(self, file, recipients, sign=None,
- always_trust=False, passphrase=None,
- armor=True, output=None, symmetric=False):
- "Encrypt the message read from the file-like object 'file'"
- args = ['--no-version', "--comment ''"]
- if symmetric:
- args.append('--symmetric')
- else:
- args.append('--encrypt')
- if not _is_sequence(recipients):
- recipients = (recipients,)
- for recipient in recipients:
- args.append('--recipient "%s"' % recipient)
- if armor: # create ascii-armored output - set to False for binary output
- args.append('--armor')
- if output: # write the output to a file with the specified name
- if os.path.exists(output):
- os.remove(output) # to avoid overwrite confirmation message
- args.append('--output "%s"' % output)
- if sign:
- args.append('--sign --default-key "%s"' % sign)
- if always_trust:
- args.append("--always-trust")
- result = self.result_map['crypt'](self)
- self._handle_io(args, file, result, passphrase=passphrase, binary=True)
- logger.debug('encrypt result: %r', result.data)
- return result
-
- def encrypt(self, data, recipients, **kwargs):
- """Encrypt the message contained in the string 'data'
-
- >>> import shutil
- >>> if os.path.exists("keys"):
- ... shutil.rmtree("keys")
- >>> gpg = GPG(gnupghome="keys")
- >>> input = gpg.gen_key_input(passphrase='foo')
- >>> result = gpg.gen_key(input)
- >>> print1 = result.fingerprint
- >>> input = gpg.gen_key_input()
- >>> result = gpg.gen_key(input)
- >>> print2 = result.fingerprint
- >>> result = gpg.encrypt("hello",print2)
- >>> message = str(result)
- >>> assert message != 'hello'
- >>> result = gpg.decrypt(message)
- >>> assert result
- >>> str(result)
- 'hello'
- >>> result = gpg.encrypt("hello again",print1)
- >>> message = str(result)
- >>> result = gpg.decrypt(message)
- >>> result.status == 'need passphrase'
- True
- >>> result = gpg.decrypt(message,passphrase='bar')
- >>> result.status in ('decryption failed', 'bad passphrase')
- True
- >>> assert not result
- >>> result = gpg.decrypt(message,passphrase='foo')
- >>> result.status == 'decryption ok'
- True
- >>> str(result)
- 'hello again'
- >>> result = gpg.encrypt("signed hello",print2,sign=print1)
- >>> result.status == 'need passphrase'
- True
- >>> result = gpg.encrypt("signed hello",print2,sign=print1,passphrase='foo')
- >>> result.status == 'encryption ok'
- True
- >>> message = str(result)
- >>> result = gpg.decrypt(message)
- >>> result.status == 'decryption ok'
- True
- >>> assert result.fingerprint == print1
-
- """
- data = _make_binary_stream(data, self.encoding)
- result = self.encrypt_file(data, recipients, **kwargs)
- data.close()
- return result
-
- def decrypt(self, message, **kwargs):
- data = _make_binary_stream(message, self.encoding)
- result = self.decrypt_file(data, **kwargs)
- data.close()
- return result
-
- def decrypt_file(self, file, always_trust=False, passphrase=None,
- output=None):
- args = ["--decrypt"]
- if output: # write the output to a file with the specified name
- if os.path.exists(output):
- os.remove(output) # to avoid overwrite confirmation message
- args.append('--output "%s"' % output)
- if always_trust:
- args.append("--always-trust")
- result = self.result_map['crypt'](self)
- self._handle_io(args, file, result, passphrase, binary=True)
- logger.debug('decrypt result: %r', result.data)
- return result
-
diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py
deleted file mode 100644
index e751a448..00000000
--- a/sleekxmpp/thirdparty/mini_dateutil.py
+++ /dev/null
@@ -1,273 +0,0 @@
-# This module is a very stripped down version of the dateutil
-# package for when dateutil has not been installed. As a replacement
-# for dateutil.parser.parse, the parsing methods from
-# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/
-
-#As such, the following copyrights and licenses applies:
-
-
-# dateutil - Extensions to the standard python 2.3+ datetime module.
-#
-# Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
-#
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the copyright holder nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-# fixed_dateime
-#
-# Copyright (c) 2008, Red Innovation Ltd., Finland
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# * Neither the name of Red Innovation nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-
-import re
-import math
-import datetime
-
-
-ZERO = datetime.timedelta(0)
-
-
-try:
- from dateutil.parser import parse as parse_iso
- from dateutil.tz import tzoffset, tzutc
-except:
- # As a stopgap, define the two timezones here based
- # on the dateutil code.
-
- class tzutc(datetime.tzinfo):
-
- def utcoffset(self, dt):
- return ZERO
-
- def dst(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return "UTC"
-
- def __eq__(self, other):
- return (isinstance(other, tzutc) or
- (isinstance(other, tzoffset) and other._offset == ZERO))
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return "%s()" % self.__class__.__name__
-
- __reduce__ = object.__reduce__
-
- class tzoffset(datetime.tzinfo):
-
- def __init__(self, name, offset):
- self._name = name
- self._offset = datetime.timedelta(minutes=offset)
-
- def utcoffset(self, dt):
- return self._offset
-
- def dst(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return self._name
-
- def __eq__(self, other):
- return (isinstance(other, tzoffset) and
- self._offset == other._offset)
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return "%s(%s, %s)" % (self.__class__.__name__,
- repr(self._name),
- self._offset.days*86400+self._offset.seconds)
-
- __reduce__ = object.__reduce__
-
-
- _fixed_offset_tzs = { }
- UTC = tzutc()
-
- def _get_fixed_offset_tz(offsetmins):
- """For internal use only: Returns a tzinfo with
- the given fixed offset. This creates only one instance
- for each offset; the zones are kept in a dictionary"""
-
- if offsetmins == 0:
- return UTC
-
- if not offsetmins in _fixed_offset_tzs:
- if offsetmins < 0:
- sign = '-'
- absoff = -offsetmins
- else:
- sign = '+'
- absoff = offsetmins
-
- name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60)
- inst = tzoffset(name,offsetmins)
- _fixed_offset_tzs[offsetmins] = inst
-
- return _fixed_offset_tzs[offsetmins]
-
-
- _iso8601_parser = re.compile("""
- ^
- (?P<year> [0-9]{4})?(?P<ymdsep>-?)?
- (?P<month>[0-9]{2})?(?P=ymdsep)?
- (?P<day> [0-9]{2})?
-
- (?P<time>
- (?: # time part... optional... at least hour must be specified
- (?:T|\s+)?
- (?P<hour>[0-9]{2})
- (?:
- # minutes, separated with :, or none, from hours
- (?P<hmssep>[:]?)
- (?P<minute>[0-9]{2})
- (?:
- # same for seconds, separated with :, or none, from hours
- (?P=hmssep)
- (?P<second>[0-9]{2})
- )?
- )?
-
- # fractions
- (?: [,.] (?P<frac>[0-9]{1,10}))?
-
- # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
- (
- (?P<tzempty>Z)
- |
- (?P<tzh>[+-][0-9]{2})
- (?: :? # optional separator
- (?P<tzm>[0-9]{2})
- )?
- )?
- )
- )?
- $
- """, re.X) # """
-
- def parse_iso(timestamp):
- """Internal function for parsing a timestamp in
- ISO 8601 format"""
-
- timestamp = timestamp.strip()
-
- m = _iso8601_parser.match(timestamp)
- if not m:
- raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp)
-
- vals = m.groupdict()
- def_vals = {'year': 1970, 'month': 1, 'day': 1}
- for key in vals:
- if vals[key] is None:
- vals[key] = def_vals.get(key, 0)
- elif key not in ['time', 'ymdsep', 'hmssep', 'tzempty']:
- vals[key] = int(vals[key])
-
- year = vals['year']
- month = vals['month']
- day = vals['day']
-
- if m.group('time') is None:
- return datetime.date(year, month, day)
-
- h, min, s, us = None, None, None, 0
- frac = 0
- if m.group('tzempty') == None and m.group('tzh') == None:
- raise ValueError("Not a proper ISO 8601 timestamp: " +
- "missing timezone (Z or +hh[:mm])!")
-
- if m.group('frac'):
- frac = m.group('frac')
- power = len(frac)
- frac = int(frac) / 10.0 ** power
-
- if m.group('hour'):
- h = vals['hour']
-
- if m.group('minute'):
- min = vals['minute']
-
- if m.group('second'):
- s = vals['second']
-
- if frac != None:
- # ok, fractions of hour?
- if min == None:
- frac, min = math.modf(frac * 60.0)
- min = int(min)
-
- # fractions of second?
- if s == None:
- frac, s = math.modf(frac * 60.0)
- s = int(s)
-
- # and extract microseconds...
- us = int(frac * 1000000)
-
- if m.group('tzempty') == 'Z':
- offsetmins = 0
- else:
- # timezone: hour diff with sign
- offsetmins = vals['tzh'] * 60
- tzm = m.group('tzm')
-
- # add optional minutes
- if tzm != None:
- tzm = int(tzm)
- offsetmins += tzm if offsetmins > 0 else -tzm
-
- tz = _get_fixed_offset_tz(offsetmins)
- return datetime.datetime(year, month, day, h, min, s, us, tz)
diff --git a/sleekxmpp/thirdparty/ordereddict.py b/sleekxmpp/thirdparty/ordereddict.py
deleted file mode 100644
index 5b0303f5..00000000
--- a/sleekxmpp/thirdparty/ordereddict.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright (c) 2009 Raymond Hettinger
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-
-from UserDict import DictMixin
-
-class OrderedDict(dict, DictMixin):
-
- def __init__(self, *args, **kwds):
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__end
- except AttributeError:
- self.clear()
- self.update(*args, **kwds)
-
- def clear(self):
- self.__end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.__map = {} # key --> [key, prev, next]
- dict.clear(self)
-
- def __setitem__(self, key, value):
- if key not in self:
- end = self.__end
- curr = end[1]
- curr[2] = end[1] = self.__map[key] = [key, curr, end]
- dict.__setitem__(self, key, value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- key, prev, next = self.__map.pop(key)
- prev[2] = next
- next[1] = prev
-
- def __iter__(self):
- end = self.__end
- curr = end[2]
- while curr is not end:
- yield curr[0]
- curr = curr[2]
-
- def __reversed__(self):
- end = self.__end
- curr = end[1]
- while curr is not end:
- yield curr[0]
- curr = curr[1]
-
- def popitem(self, last=True):
- if not self:
- raise KeyError('dictionary is empty')
- if last:
- key = reversed(self).next()
- else:
- key = iter(self).next()
- value = self.pop(key)
- return key, value
-
- def __reduce__(self):
- items = [[k, self[k]] for k in self]
- tmp = self.__map, self.__end
- del self.__map, self.__end
- inst_dict = vars(self).copy()
- self.__map, self.__end = tmp
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def keys(self):
- return list(self)
-
- setdefault = DictMixin.setdefault
- update = DictMixin.update
- pop = DictMixin.pop
- values = DictMixin.values
- items = DictMixin.items
- iterkeys = DictMixin.iterkeys
- itervalues = DictMixin.itervalues
- iteritems = DictMixin.iteritems
-
- def __repr__(self):
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
-
- def copy(self):
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- if isinstance(other, OrderedDict):
- if len(self) != len(other):
- return False
- for p, q in zip(self.items(), other.items()):
- if p != q:
- return False
- return True
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
diff --git a/sleekxmpp/thirdparty/orderedset.py b/sleekxmpp/thirdparty/orderedset.py
deleted file mode 100644
index f6642db3..00000000
--- a/sleekxmpp/thirdparty/orderedset.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright (c) 2009 Raymond Hettinger
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-
-import collections
-
-class OrderedSet(collections.MutableSet):
-
- def __init__(self, iterable=None):
- self.end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.map = {} # key --> [key, prev, next]
- if iterable is not None:
- self |= iterable
-
- def __len__(self):
- return len(self.map)
-
- def __contains__(self, key):
- return key in self.map
-
- def add(self, key):
- if key not in self.map:
- end = self.end
- curr = end[1]
- curr[2] = end[1] = self.map[key] = [key, curr, end]
-
- def discard(self, key):
- if key in self.map:
- key, prev, next = self.map.pop(key)
- prev[2] = next
- next[1] = prev
-
- def __iter__(self):
- end = self.end
- curr = end[2]
- while curr is not end:
- yield curr[0]
- curr = curr[2]
-
- def __reversed__(self):
- end = self.end
- curr = end[1]
- while curr is not end:
- yield curr[0]
- curr = curr[1]
-
- def pop(self, last=True):
- if not self:
- raise KeyError('set is empty')
- key = self.end[1][0] if last else self.end[2][0]
- self.discard(key)
- return key
-
- def __repr__(self):
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, list(self))
-
- def __eq__(self, other):
- if isinstance(other, OrderedSet):
- return len(self) == len(other) and list(self) == list(other)
- return set(self) == set(other)
-
-
-if __name__ == '__main__':
- s = OrderedSet('abracadaba')
- t = OrderedSet('simsalabim')
- print(s | t)
- print(s & t)
- print(s - t) \ No newline at end of file
diff --git a/sleekxmpp/thirdparty/socks.py b/sleekxmpp/thirdparty/socks.py
deleted file mode 100644
index 34090d51..00000000
--- a/sleekxmpp/thirdparty/socks.py
+++ /dev/null
@@ -1,387 +0,0 @@
-"""SocksiPy - Python SOCKS module.
-Version 1.00
-
-Copyright 2006 Dan-Haim. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-3. Neither the name of Dan Haim nor the names of his contributors may be used
- to endorse or promote products derived from this software without specific
- prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
-
-
-This module provides a standard socket-like interface for Python
-for tunneling connections through SOCKS proxies.
-
-"""
-
-"""
-
-Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
-for use in PyLoris (http://pyloris.sourceforge.net/)
-
-Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
-mainly to merge bug fixes found in Sourceforge
-
-Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
-
-"""
-
-import socket
-import struct
-import sys
-
-PROXY_TYPE_SOCKS4 = 1
-PROXY_TYPE_SOCKS5 = 2
-PROXY_TYPE_HTTP = 3
-
-_defaultproxy = None
-_orgsocket = socket.socket
-
-class ProxyError(Exception): pass
-class GeneralProxyError(ProxyError): pass
-class Socks5AuthError(ProxyError): pass
-class Socks5Error(ProxyError): pass
-class Socks4Error(ProxyError): pass
-class HTTPError(ProxyError): pass
-
-_generalerrors = ("success",
- "invalid data",
- "not connected",
- "not available",
- "bad proxy type",
- "bad input")
-
-_socks5errors = ("succeeded",
- "general SOCKS server failure",
- "connection not allowed by ruleset",
- "Network unreachable",
- "Host unreachable",
- "Connection refused",
- "TTL expired",
- "Command not supported",
- "Address type not supported",
- "Unknown error")
-
-_socks5autherrors = ("succeeded",
- "authentication is required",
- "all offered authentication methods were rejected",
- "unknown username or invalid password",
- "unknown error")
-
-_socks4errors = ("request granted",
- "request rejected or failed",
- "request rejected because SOCKS server cannot connect to identd on the client",
- "request rejected because the client program and identd report different user-ids",
- "unknown error")
-
-def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
- """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
- Sets a default proxy which all further socksocket objects will use,
- unless explicitly changed.
- """
- global _defaultproxy
- _defaultproxy = (proxytype, addr, port, rdns, username, password)
-
-def wrapmodule(module):
- """wrapmodule(module)
- Attempts to replace a module's socket library with a SOCKS socket. Must set
- a default proxy using setdefaultproxy(...) first.
- This will only work on modules that import socket directly into the namespace;
- most of the Python Standard Library falls into this category.
- """
- if _defaultproxy != None:
- module.socket.socket = socksocket
- else:
- raise GeneralProxyError((4, "no proxy specified"))
-
-class socksocket(socket.socket):
- """socksocket([family[, type[, proto]]]) -> socket object
- Open a SOCKS enabled socket. The parameters are the same as
- those of the standard socket init. In order for SOCKS to work,
- you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
- """
-
- def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
- _orgsocket.__init__(self, family, type, proto, _sock)
- if _defaultproxy != None:
- self.__proxy = _defaultproxy
- else:
- self.__proxy = (None, None, None, None, None, None)
- self.__proxysockname = None
- self.__proxypeername = None
-
- def __recvall(self, count):
- """__recvall(count) -> data
- Receive EXACTLY the number of bytes requested from the socket.
- Blocks until the required number of bytes have been received.
- """
- data = self.recv(count)
- while len(data) < count:
- d = self.recv(count-len(data))
- if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
- data = data + d
- return data
-
- def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
- """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
- Sets the proxy to be used.
- proxytype - The type of the proxy to be used. Three types
- are supported: PROXY_TYPE_SOCKS4 (including socks4a),
- PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
- addr - The address of the server (IP or DNS).
- port - The port of the server. Defaults to 1080 for SOCKS
- servers and 8080 for HTTP proxy servers.
- rdns - Should DNS queries be preformed on the remote side
- (rather than the local side). The default is True.
- Note: This has no effect with SOCKS4 servers.
- username - Username to authenticate with to the server.
- The default is no authentication.
- password - Password to authenticate with to the server.
- Only relevant when username is also provided.
- """
- self.__proxy = (proxytype, addr, port, rdns, username, password)
-
- def __negotiatesocks5(self, destaddr, destport):
- """__negotiatesocks5(self,destaddr,destport)
- Negotiates a connection through a SOCKS5 server.
- """
- # First we'll send the authentication packages we support.
- if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
- # The username/password details were supplied to the
- # setproxy method so we support the USERNAME/PASSWORD
- # authentication (in addition to the standard none).
- self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
- else:
- # No username/password were entered, therefore we
- # only support connections with no authentication.
- self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
- # We'll receive the server's response to determine which
- # method was selected
- chosenauth = self.__recvall(2)
- if chosenauth[0:1] != chr(0x05).encode():
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- # Check the chosen authentication method
- if chosenauth[1:2] == chr(0x00).encode():
- # No authentication is required
- pass
- elif chosenauth[1:2] == chr(0x02).encode():
- # Okay, we need to perform a basic username/password
- # authentication.
- self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
- authstat = self.__recvall(2)
- if authstat[0:1] != chr(0x01).encode():
- # Bad response
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- if authstat[1:2] != chr(0x00).encode():
- # Authentication failed
- self.close()
- raise Socks5AuthError((3, _socks5autherrors[3]))
- # Authentication succeeded
- else:
- # Reaching here is always bad
- self.close()
- if chosenauth[1] == chr(0xFF).encode():
- raise Socks5AuthError((2, _socks5autherrors[2]))
- else:
- raise GeneralProxyError((1, _generalerrors[1]))
- # Now we can request the actual connection
- req = struct.pack('BBB', 0x05, 0x01, 0x00)
- # If the given destination address is an IP address, we'll
- # use the IPv4 address request even if remote resolving was specified.
- try:
- ipaddr = socket.inet_aton(destaddr)
- req = req + chr(0x01).encode() + ipaddr
- except socket.error:
- # Well it's not an IP number, so it's probably a DNS name.
- if self.__proxy[3]:
- # Resolve remotely
- ipaddr = None
- req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr.encode()
- else:
- # Resolve locally
- ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
- req = req + chr(0x01).encode() + ipaddr
- req += struct.pack(">H", destport)
- self.sendall(req)
- # Get the response
- resp = self.__recvall(4)
- if resp[0:1] != chr(0x05).encode():
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- elif resp[1:2] != chr(0x00).encode():
- # Connection failed
- self.close()
- if ord(resp[1:2])<=8:
- raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
- else:
- raise Socks5Error((9, _socks5errors[9]))
- # Get the bound address/port
- elif resp[3:4] == chr(0x01).encode():
- boundaddr = self.__recvall(4)
- elif resp[3:4] == chr(0x03).encode():
- resp = resp + self.recv(1)
- boundaddr = self.__recvall(ord(resp[4:5]))
- else:
- self.close()
- raise GeneralProxyError((1,_generalerrors[1]))
- boundport = struct.unpack(">H", self.__recvall(2))[0]
- self.__proxysockname = (boundaddr, boundport)
- if ipaddr != None:
- self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
- else:
- self.__proxypeername = (destaddr, destport)
-
- def getproxysockname(self):
- """getsockname() -> address info
- Returns the bound IP address and port number at the proxy.
- """
- return self.__proxysockname
-
- def getproxypeername(self):
- """getproxypeername() -> address info
- Returns the IP and port number of the proxy.
- """
- return _orgsocket.getpeername(self)
-
- def getpeername(self):
- """getpeername() -> address info
- Returns the IP address and port number of the destination
- machine (note: getproxypeername returns the proxy)
- """
- return self.__proxypeername
-
- def __negotiatesocks4(self,destaddr,destport):
- """__negotiatesocks4(self,destaddr,destport)
- Negotiates a connection through a SOCKS4 server.
- """
- # Check if the destination address provided is an IP address
- rmtrslv = False
- try:
- ipaddr = socket.inet_aton(destaddr)
- except socket.error:
- # It's a DNS name. Check where it should be resolved.
- if self.__proxy[3]:
- ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
- rmtrslv = True
- else:
- ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
- # Construct the request packet
- req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
- # The username parameter is considered userid for SOCKS4
- if self.__proxy[4] != None:
- req = req + self.__proxy[4]
- req += chr(0x00).encode()
- # DNS name if remote resolving is required
- # NOTE: This is actually an extension to the SOCKS4 protocol
- # called SOCKS4A and may not be supported in all cases.
- if rmtrslv:
- req = req + destaddr + chr(0x00).encode()
- self.sendall(req)
- # Get the response from the server
- resp = self.__recvall(8)
- if resp[0:1] != chr(0x00).encode():
- # Bad data
- self.close()
- raise GeneralProxyError((1,_generalerrors[1]))
- if resp[1:2] != chr(0x5A).encode():
- # Server returned an error
- self.close()
- if ord(resp[1:2]) in (91, 92, 93):
- self.close()
- raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
- else:
- raise Socks4Error((94, _socks4errors[4]))
- # Get the bound address/port
- self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
- if rmtrslv != None:
- self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
- else:
- self.__proxypeername = (destaddr, destport)
-
- def __negotiatehttp(self, destaddr, destport):
- """__negotiatehttp(self,destaddr,destport)
- Negotiates a connection through an HTTP server.
- """
- # If we need to resolve locally, we do this now
- if not self.__proxy[3]:
- addr = socket.gethostbyname(destaddr)
- else:
- addr = destaddr
- self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
- # We read the response until we get the string "\r\n\r\n"
- resp = self.recv(1)
- while resp.find("\r\n\r\n".encode()) == -1:
- recv = self.recv(1)
- if not recv:
- raise GeneralProxyError((1, _generalerrors[1]))
- resp = resp + recv
- # We just need the first line to check if the connection
- # was successful
- statusline = resp.splitlines()[0].split(" ".encode(), 2)
- if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- try:
- statuscode = int(statusline[1])
- except ValueError:
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- if statuscode != 200:
- self.close()
- raise HTTPError((statuscode, statusline[2]))
- self.__proxysockname = ("0.0.0.0", 0)
- self.__proxypeername = (addr, destport)
-
- def connect(self, destpair):
- """connect(self, despair)
- Connects to the specified destination through a proxy.
- destpar - A tuple of the IP/DNS address and the port number.
- (identical to socket's connect).
- To select the proxy server use setproxy().
- """
- # Do a minimal input check first
- if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
- raise GeneralProxyError((5, _generalerrors[5]))
- if self.__proxy[0] == PROXY_TYPE_SOCKS5:
- if self.__proxy[2] != None:
- portnum = self.__proxy[2]
- else:
- portnum = 1080
- _orgsocket.connect(self, (self.__proxy[1], portnum))
- self.__negotiatesocks5(destpair[0], destpair[1])
- elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
- if self.__proxy[2] != None:
- portnum = self.__proxy[2]
- else:
- portnum = 1080
- _orgsocket.connect(self,(self.__proxy[1], portnum))
- self.__negotiatesocks4(destpair[0], destpair[1])
- elif self.__proxy[0] == PROXY_TYPE_HTTP:
- if self.__proxy[2] != None:
- portnum = self.__proxy[2]
- else:
- portnum = 8080
- _orgsocket.connect(self,(self.__proxy[1], portnum))
- self.__negotiatehttp(destpair[0], destpair[1])
- elif self.__proxy[0] == None:
- _orgsocket.connect(self, (destpair[0], destpair[1]))
- else:
- raise GeneralProxyError((4, _generalerrors[4]))
diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py
deleted file mode 100644
index 6c504dce..00000000
--- a/sleekxmpp/thirdparty/statemachine.py
+++ /dev/null
@@ -1,294 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import threading
-import time
-import logging
-
-log = logging.getLogger(__name__)
-
-
-class StateMachine(object):
-
- def __init__(self, states=None):
- if not states: states = []
- self.lock = threading.Condition()
- self.__states = []
- self.addStates(states)
- self.__default_state = self.__states[0]
- self.__current_state = self.__default_state
-
- def addStates(self, states):
- self.lock.acquire()
- try:
- for state in states:
- if state in self.__states:
- raise IndexError("The state '%s' is already in the StateMachine." % state)
- self.__states.append(state)
- finally:
- self.lock.release()
-
-
- def transition(self, from_state, to_state, wait=0.0, func=None, args=None, kwargs=None):
- '''
- Transition from the given `from_state` to the given `to_state`.
- This method will return `True` if the state machine is now in `to_state`. It
- will return `False` if a timeout occurred the transition did not occur.
- If `wait` is 0 (the default,) this method returns immediately if the state machine
- is not in `from_state`.
-
- If you want the thread to block and transition once the state machine to enters
- `from_state`, set `wait` to a non-negative value. Note there is no 'block
- indefinitely' flag since this leads to deadlock. If you want to wait indefinitely,
- choose a reasonable value for `wait` (e.g. 20 seconds) and do so in a while loop like so:
-
- ::
-
- while not thread_should_exit and not state_machine.transition('disconnected', 'connecting', wait=20 ):
- pass # timeout will occur every 20s unless transition occurs
- if thread_should_exit: return
- # perform actions here after successful transition
-
- This allows the thread to be responsive by setting `thread_should_exit=True`.
-
- The optional `func` argument allows the user to pass a callable operation which occurs
- within the context of the state transition (e.g. while the state machine is locked.)
- If `func` returns a True value, the transition will occur. If `func` returns a non-
- True value or if an exception is thrown, the transition will not occur. Any thrown
- exception is not caught by the state machine and is the caller's responsibility to handle.
- If `func` completes normally, this method will return the value returned by `func.` If
- values for `args` and `kwargs` are provided, they are expanded and passed like so:
- `func( *args, **kwargs )`.
- '''
- if not args:
- args = []
- if not kwargs:
- kwargs = {}
-
- return self.transition_any((from_state,), to_state, wait=wait,
- func=func, args=args, kwargs=kwargs)
-
-
- def transition_any(self, from_states, to_state, wait=0.0, func=None, args=None, kwargs=None):
- '''
- Transition from any of the given `from_states` to the given `to_state`.
- '''
- if not args:
- args = []
- if not kwargs:
- kwargs = {}
-
- if not isinstance(from_states, (tuple, list, set)):
- raise ValueError("from_states should be a list, tuple, or set")
-
- for state in from_states:
- if not state in self.__states:
- raise ValueError("StateMachine does not contain from_state %s." % state)
- if not to_state in self.__states:
- raise ValueError("StateMachine does not contain to_state %s." % to_state)
-
- if self.__current_state == to_state:
- return True
-
- start = time.time()
- while not self.lock.acquire(False):
- time.sleep(.001)
- if (start + wait - time.time()) <= 0.0:
- log.debug("==== Could not acquire lock in %s sec: %s -> %s ", wait, self.__current_state, to_state)
- return False
-
- while not self.__current_state in from_states:
- # detect timeout:
- remainder = start + wait - time.time()
- if remainder > 0:
- self.lock.wait(remainder)
- else:
- log.debug("State was not ready")
- self.lock.release()
- return False
-
- try: # lock is acquired; all other threads will return false or wait until notify/timeout
- if self.__current_state in from_states: # should always be True due to lock
-
- # Note that func might throw an exception, but that's OK, it aborts the transition
- return_val = func(*args,**kwargs) if func is not None else True
-
- # some 'false' value returned from func,
- # indicating that transition should not occur:
- if not return_val:
- return return_val
-
- log.debug(' ==== TRANSITION %s -> %s', self.__current_state, to_state)
- self._set_state(to_state)
- return return_val # some 'true' value returned by func or True if func was None
- else:
- log.error("StateMachine bug!! The lock should ensure this doesn't happen!")
- return False
- finally:
- self.lock.notify_all()
- self.lock.release()
-
-
- def transition_ctx(self, from_state, to_state, wait=0.0):
- '''
- Use the state machine as a context manager. The transition occurs on /exit/ from
- the `with` context, so long as no exception is thrown. For example:
-
- ::
-
- with state_machine.transition_ctx('one','two', wait=5) as locked:
- if locked:
- # the state machine is currently locked in state 'one', and will
- # transition to 'two' when the 'with' statement ends, so long as
- # no exception is thrown.
- print 'Currently locked in state one: %s' % state_machine['one']
-
- else:
- # The 'wait' timed out, and no lock has been acquired
- print 'Timed out before entering state "one"'
-
- print 'Since no exception was thrown, we are now in state "two": %s' % state_machine['two']
-
-
- The other main difference between this method and `transition()` is that the
- state machine is locked for the duration of the `with` statement. Normally,
- after a `transition()` occurs, the state machine is immediately unlocked and
- available to another thread to call `transition()` again.
- '''
-
- if not from_state in self.__states:
- raise ValueError("StateMachine does not contain from_state %s." % from_state)
- if not to_state in self.__states:
- raise ValueError("StateMachine does not contain to_state %s." % to_state)
-
- return _StateCtx(self, from_state, to_state, wait)
-
-
- def ensure(self, state, wait=0.0, block_on_transition=False):
- '''
- Ensure the state machine is currently in `state`, or wait until it enters `state`.
- '''
- return self.ensure_any((state,), wait=wait, block_on_transition=block_on_transition)
-
-
- def ensure_any(self, states, wait=0.0, block_on_transition=False):
- '''
- Ensure we are currently in one of the given `states` or wait until
- we enter one of those states.
-
- Note that due to the nature of the function, you cannot guarantee that
- the entirety of some operation completes while you remain in a given
- state. That would require acquiring and holding a lock, which
- would mean no other threads could do the same. (You'd essentially
- be serializing all of the threads that are 'ensuring' their tasks
- occurred in some state.
- '''
- if not (isinstance(states,tuple) or isinstance(states,list)):
- raise ValueError('states arg should be a tuple or list')
-
- for state in states:
- if not state in self.__states:
- raise ValueError("StateMachine does not contain state '%s'" % state)
-
- # if we're in the middle of a transition, determine whether we should
- # 'fall back' to the 'current' state, or wait for the new state, in order to
- # avoid an operation occurring in the wrong state.
- # TODO another option would be an ensure_ctx that uses a semaphore to allow
- # threads to indicate they want to remain in a particular state.
- self.lock.acquire()
- start = time.time()
- while not self.__current_state in states:
- # detect timeout:
- remainder = start + wait - time.time()
- if remainder > 0:
- self.lock.wait(remainder)
- else:
- self.lock.release()
- return False
- self.lock.release()
- return True
-
- def reset(self):
- # TODO need to lock before calling this?
- self.transition(self.__current_state, self.__default_state)
-
- def _set_state(self, state): #unsynchronized, only call internally after lock is acquired
- self.__current_state = state
- return state
-
- def current_state(self):
- '''
- Return the current state name.
- '''
- return self.__current_state
-
- def __getitem__(self, state):
- '''
- Non-blocking, non-synchronized test to determine if we are in the given state.
- Use `StateMachine.ensure(state)` to wait until the machine enters a certain state.
- '''
- return self.__current_state == state
-
- def __str__(self):
- return "".join(("StateMachine(", ','.join(self.__states), "): ", self.__current_state))
-
-
-
-class _StateCtx:
-
- def __init__(self, state_machine, from_state, to_state, wait):
- self.state_machine = state_machine
- self.from_state = from_state
- self.to_state = to_state
- self.wait = wait
- self._locked = False
-
- def __enter__(self):
- start = time.time()
- while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False):
- # detect timeout:
- remainder = start + self.wait - time.time()
- if remainder > 0:
- self.state_machine.lock.wait(remainder)
- else:
- log.debug('StateMachine timeout while waiting for state: %s', self.from_state)
- return False
-
- self._locked = True # lock has been acquired at this point
- self.state_machine.lock.clear()
- log.debug('StateMachine entered context in state: %s',
- self.state_machine.current_state())
- return True
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if exc_val is not None:
- log.exception("StateMachine exception in context, remaining in state: %s\n%s:%s",
- self.state_machine.current_state(), exc_type.__name__, exc_val)
-
- if self._locked:
- if exc_val is None:
- log.debug(' ==== TRANSITION %s -> %s',
- self.state_machine.current_state(), self.to_state)
- self.state_machine._set_state(self.to_state)
-
- self.state_machine.lock.notify_all()
- self.state_machine.lock.release()
-
- return False # re-raise any exception
-
-if __name__ == '__main__':
-
- def callback(s, s2):
- print((1, s.transition('on', 'off', wait=0.0, func=callback, args=[s,s2])))
- print((2, s2.transition('off', 'on', func=callback, args=[s,s2])))
- return True
-
- s = StateMachine(('off', 'on'))
- s2 = StateMachine(('off', 'on'))
- print((3, s.transition('off', 'on', wait=0.0, func=callback, args=[s,s2]),))
- print((s.current_state(), s2.current_state()))
diff --git a/sleekxmpp/util/__init__.py b/sleekxmpp/util/__init__.py
deleted file mode 100644
index 47a935af..00000000
--- a/sleekxmpp/util/__init__.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.util
- ~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
- :license: MIT, see LICENSE for more details
-"""
-
-
-from sleekxmpp.util.misc_ops import bytes, unicode, hashes, hash, \
- num_to_bytes, bytes_to_num, quote, \
- XOR, safedict
-
-
-# =====================================================================
-# Standardize import of Queue class:
-
-import sys
-
-def _gevent_threads_enabled():
- if not 'gevent' in sys.modules:
- return False
- try:
- from gevent import thread as green_thread
- thread = __import__('thread')
- return thread.LockType is green_thread.LockType
- except ImportError:
- return False
-
-if _gevent_threads_enabled():
- import gevent.queue as queue
- _queue = queue.JoinableQueue
-else:
- try:
- import queue
- except ImportError:
- import Queue as queue
- _queue = queue.Queue
-class Queue(_queue):
- def put(self, item, block=True, timeout=None):
- if _queue.full(self):
- _queue.get(self)
- return _queue.put(self, item, block, timeout)
-
-QueueEmpty = queue.Empty
diff --git a/sleekxmpp/util/misc_ops.py b/sleekxmpp/util/misc_ops.py
deleted file mode 100644
index 18c919a8..00000000
--- a/sleekxmpp/util/misc_ops.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import sys
-import hashlib
-
-
-def unicode(text):
- if sys.version_info < (3, 0):
- if isinstance(text, str):
- text = text.decode('utf-8')
- import __builtin__
- return __builtin__.unicode(text)
- elif not isinstance(text, str):
- return text.decode('utf-8')
- else:
- return text
-
-
-def bytes(text):
- """
- Convert Unicode text to UTF-8 encoded bytes.
-
- Since Python 2.6+ and Python 3+ have similar but incompatible
- signatures, this function unifies the two to keep code sane.
-
- :param text: Unicode text to convert to bytes
- :rtype: bytes (Python3), str (Python2.6+)
- """
- if text is None:
- return b''
-
- if sys.version_info < (3, 0):
- import __builtin__
- return __builtin__.bytes(text)
- else:
- import builtins
- if isinstance(text, builtins.bytes):
- # We already have bytes, so do nothing
- return text
- if isinstance(text, list):
- # Convert a list of integers to bytes
- return builtins.bytes(text)
- else:
- # Convert UTF-8 text to bytes
- return builtins.bytes(text, encoding='utf-8')
-
-
-def quote(text):
- """
- Enclose in quotes and escape internal slashes and double quotes.
-
- :param text: A Unicode or byte string.
- """
- text = bytes(text)
- return b'"' + text.replace(b'\\', b'\\\\').replace(b'"', b'\\"') + b'"'
-
-
-def num_to_bytes(num):
- """
- Convert an integer into a four byte sequence.
-
- :param integer num: An integer to convert to its byte representation.
- """
- bval = b''
- bval += bytes(chr(0xFF & (num >> 24)))
- bval += bytes(chr(0xFF & (num >> 16)))
- bval += bytes(chr(0xFF & (num >> 8)))
- bval += bytes(chr(0xFF & (num >> 0)))
- return bval
-
-
-def bytes_to_num(bval):
- """
- Convert a four byte sequence to an integer.
-
- :param bytes bval: A four byte sequence to turn into an integer.
- """
- num = 0
- num += ord(bval[0] << 24)
- num += ord(bval[1] << 16)
- num += ord(bval[2] << 8)
- num += ord(bval[3])
- return num
-
-
-def XOR(x, y):
- """
- Return the results of an XOR operation on two equal length byte strings.
-
- :param bytes x: A byte string
- :param bytes y: A byte string
- :rtype: bytes
- """
- result = b''
- for a, b in zip(x, y):
- if sys.version_info < (3, 0):
- result += chr((ord(a) ^ ord(b)))
- else:
- result += bytes([a ^ b])
- return result
-
-
-def hash(name):
- """
- Return a hash function implementing the given algorithm.
-
- :param name: The name of the hashing algorithm to use.
- :type name: string
-
- :rtype: function
- """
- name = name.lower()
- if name.startswith('sha-'):
- name = 'sha' + name[4:]
- if name in dir(hashlib):
- return getattr(hashlib, name)
- return None
-
-
-def hashes():
- """
- Return a list of available hashing algorithms.
-
- :rtype: list of strings
- """
- t = []
- if 'md5' in dir(hashlib):
- t = ['MD5']
- if 'md2' in dir(hashlib):
- t += ['MD2']
- hashes = ['SHA-' + h[3:] for h in dir(hashlib) if h.startswith('sha')]
- return t + hashes
-
-
-def setdefaultencoding(encoding):
- """
- Set the current default string encoding used by the Unicode implementation.
-
- Actually calls sys.setdefaultencoding under the hood - see the docs for that
- for more details. This method exists only as a way to call find/call it
- even after it has been 'deleted' when the site module is executed.
-
- :param string encoding: An encoding name, compatible with sys.setdefaultencoding
- """
- func = getattr(sys, 'setdefaultencoding', None)
- if func is None:
- import gc
- import types
- for obj in gc.get_objects():
- if (isinstance(obj, types.BuiltinFunctionType)
- and obj.__name__ == 'setdefaultencoding'):
- func = obj
- break
- if func is None:
- raise RuntimeError("Could not find setdefaultencoding")
- sys.setdefaultencoding = func
- return func(encoding)
-
-
-def safedict(data):
- if sys.version_info < (2, 7):
- safe = {}
- for key in data:
- safe[key.encode('utf8')] = data[key]
- return safe
- else:
- return data
diff --git a/sleekxmpp/util/sasl/__init__.py b/sleekxmpp/util/sasl/__init__.py
deleted file mode 100644
index 2d344e9b..00000000
--- a/sleekxmpp/util/sasl/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.util.sasl
- ~~~~~~~~~~~~~~~~~~~
-
- This module was originally based on Dave Cridland's Suelta library.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copryight: (c) 2004-2013 David Alan Cridland
- :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
-
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.util.sasl.client import *
-from sleekxmpp.util.sasl.mechanisms import *
diff --git a/sleekxmpp/util/sasl/client.py b/sleekxmpp/util/sasl/client.py
deleted file mode 100644
index fd685547..00000000
--- a/sleekxmpp/util/sasl/client.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.util.sasl.client
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module was originally based on Dave Cridland's Suelta library.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copryight: (c) 2004-2013 David Alan Cridland
- :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
-
- :license: MIT, see LICENSE for more details
-"""
-
-import logging
-import stringprep
-
-from sleekxmpp.util import hashes, bytes, stringprep_profiles
-
-
-log = logging.getLogger(__name__)
-
-
-#: Global registry mapping mechanism names to implementation classes.
-MECHANISMS = {}
-
-
-#: Global registry mapping mechanism names to security scores.
-MECH_SEC_SCORES = {}
-
-
-#: The SASLprep profile of stringprep used to validate simple username
-#: and password credentials.
-saslprep = stringprep_profiles.create(
- nfkc=True,
- bidi=True,
- mappings=[
- stringprep_profiles.b1_mapping,
- stringprep_profiles.c12_mapping],
- prohibited=[
- stringprep.in_table_c12,
- stringprep.in_table_c21,
- stringprep.in_table_c22,
- stringprep.in_table_c3,
- stringprep.in_table_c4,
- stringprep.in_table_c5,
- stringprep.in_table_c6,
- stringprep.in_table_c7,
- stringprep.in_table_c8,
- stringprep.in_table_c9],
- unassigned=[stringprep.in_table_a1])
-
-
-def sasl_mech(score):
- sec_score = score
- def register(mech):
- n = 0
- mech.score = sec_score
- if mech.use_hashes:
- for hashing_alg in hashes():
- n += 1
- score = mech.score + n
- name = '%s-%s' % (mech.name, hashing_alg)
- MECHANISMS[name] = mech
- MECH_SEC_SCORES[name] = score
-
- if mech.channel_binding:
- name += '-PLUS'
- score += 10
- MECHANISMS[name] = mech
- MECH_SEC_SCORES[name] = score
- else:
- MECHANISMS[mech.name] = mech
- MECH_SEC_SCORES[mech.name] = mech.score
- if mech.channel_binding:
- MECHANISMS[mech.name + '-PLUS'] = mech
- MECH_SEC_SCORES[name] = mech.score + 10
- return mech
- return register
-
-
-class SASLNoAppropriateMechanism(Exception):
- def __init__(self, value=''):
- self.message = value
-
-
-class SASLCancelled(Exception):
- def __init__(self, value=''):
- self.message = value
-
-
-class SASLFailed(Exception):
- def __init__(self, value=''):
- self.message = value
-
-
-class SASLMutualAuthFailed(SASLFailed):
- def __init__(self, value=''):
- self.message = value
-
-
-class Mech(object):
-
- name = 'GENERIC'
- score = -1
- use_hashes = False
- channel_binding = False
- required_credentials = set()
- optional_credentials = set()
- security = set()
-
- def __init__(self, name, credentials, security_settings):
- self.credentials = credentials
- self.security_settings = security_settings
- self.values = {}
- self.base_name = self.name
- self.name = name
- self.setup(name)
-
- def setup(self, name):
- pass
-
- def process(self, challenge=b''):
- return b''
-
-
-def choose(mech_list, credentials, security_settings, limit=None, min_mech=None):
- available_mechs = set(MECHANISMS.keys())
- if limit is None:
- limit = set(mech_list)
- if not isinstance(limit, set):
- limit = set(limit)
- if not isinstance(mech_list, set):
- mech_list = set(mech_list)
-
- mech_list = mech_list.intersection(limit)
- available_mechs = available_mechs.intersection(mech_list)
-
- best_score = MECH_SEC_SCORES.get(min_mech, -1)
- best_mech = None
- for name in available_mechs:
- if name in MECH_SEC_SCORES:
- if MECH_SEC_SCORES[name] > best_score:
- best_score = MECH_SEC_SCORES[name]
- best_mech = name
- if best_mech is None:
- raise SASLNoAppropriateMechanism()
-
- mech_class = MECHANISMS[best_mech]
-
- try:
- creds = credentials(mech_class.required_credentials,
- mech_class.optional_credentials)
- for req in mech_class.required_credentials:
- if req not in creds:
- raise SASLCancelled('Missing credential: %s' % req)
- for opt in mech_class.optional_credentials:
- if opt not in creds:
- creds[opt] = b''
- for cred in creds:
- if cred in ('username', 'password', 'authzid'):
- creds[cred] = bytes(saslprep(creds[cred]))
- else:
- creds[cred] = bytes(creds[cred])
- security_opts = security_settings(mech_class.security)
-
- return mech_class(best_mech, creds, security_opts)
- except SASLCancelled as e:
- log.info('SASL: %s: %s', best_mech, e.message)
- mech_list.remove(best_mech)
- return choose(mech_list, credentials, security_settings,
- limit=limit,
- min_mech=min_mech)
diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py
deleted file mode 100644
index 7a7ebf7b..00000000
--- a/sleekxmpp/util/sasl/mechanisms.py
+++ /dev/null
@@ -1,550 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.util.sasl.mechanisms
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- A collection of supported SASL mechanisms.
-
- This module was originally based on Dave Cridland's Suelta library.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copryight: (c) 2004-2013 David Alan Cridland
- :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
-
- :license: MIT, see LICENSE for more details
-"""
-
-import sys
-import hmac
-import random
-
-from base64 import b64encode, b64decode
-
-from sleekxmpp.util import bytes, hash, XOR, quote, num_to_bytes
-from sleekxmpp.util.sasl.client import sasl_mech, Mech, \
- SASLCancelled, SASLFailed, \
- SASLMutualAuthFailed
-
-
-@sasl_mech(0)
-class ANONYMOUS(Mech):
-
- name = 'ANONYMOUS'
-
- def process(self, challenge=b''):
- return b'Anonymous, Suelta'
-
-
-@sasl_mech(1)
-class LOGIN(Mech):
-
- name = 'LOGIN'
- required_credentials = set(['username', 'password'])
-
- def setup(self, name):
- self.step = 0
-
- def process(self, challenge=b''):
- if not challenge:
- return b''
-
- if self.step == 0:
- self.step = 1
- return self.credentials['username']
- else:
- return self.credentials['password']
-
-
-@sasl_mech(2)
-class PLAIN(Mech):
-
- name = 'PLAIN'
- required_credentials = set(['username', 'password'])
- optional_credentials = set(['authzid'])
- security = set(['encrypted', 'encrypted_plain', 'unencrypted_plain'])
-
- def setup(self, name):
- if not self.security_settings['encrypted']:
- if not self.security_settings['unencrypted_plain']:
- raise SASLCancelled('PLAIN without encryption')
- else:
- if not self.security_settings['encrypted_plain']:
- raise SASLCancelled('PLAIN with encryption')
-
- def process(self, challenge=b''):
- authzid = self.credentials['authzid']
- authcid = self.credentials['username']
- password = self.credentials['password']
- return authzid + b'\x00' + authcid + b'\x00' + password
-
-
-@sasl_mech(100)
-class EXTERNAL(Mech):
-
- name = 'EXTERNAL'
- optional_credentials = set(['authzid'])
-
- def process(self, challenge=b''):
- return self.credentials['authzid']
-
-
-@sasl_mech(31)
-class X_FACEBOOK_PLATFORM(Mech):
-
- name = 'X-FACEBOOK-PLATFORM'
- required_credentials = set(['api_key', 'access_token'])
-
- def process(self, challenge=b''):
- if challenge:
- values = {}
- for kv in challenge.split(b'&'):
- key, value = kv.split(b'=')
- values[key] = value
-
- resp_data = {
- b'method': values[b'method'],
- b'v': b'1.0',
- b'call_id': b'1.0',
- b'nonce': values[b'nonce'],
- b'access_token': self.credentials['access_token'],
- b'api_key': self.credentials['api_key']
- }
-
- resp = '&'.join(['%s=%s' % (k.decode("utf-8"), v.decode("utf-8")) for k, v in resp_data.items()])
- return bytes(resp)
- return b''
-
-
-@sasl_mech(10)
-class X_MESSENGER_OAUTH2(Mech):
-
- name = 'X-MESSENGER-OAUTH2'
- required_credentials = set(['access_token'])
-
- def process(self, challenge=b''):
- return self.credentials['access_token']
-
-
-@sasl_mech(10)
-class X_OAUTH2(Mech):
-
- name = 'X-OAUTH2'
- required_credentials = set(['username', 'access_token'])
-
- def process(self, challenge=b''):
- return b'\x00' + self.credentials['username'] + \
- b'\x00' + self.credentials['access_token']
-
-
-@sasl_mech(3)
-class X_GOOGLE_TOKEN(Mech):
-
- name = 'X-GOOGLE-TOKEN'
- required_credentials = set(['email', 'access_token'])
-
- def process(self, challenge=b''):
- email = self.credentials['email']
- token = self.credentials['access_token']
- return b'\x00' + email + b'\x00' + token
-
-
-@sasl_mech(20)
-class CRAM(Mech):
-
- name = 'CRAM'
- use_hashes = True
- required_credentials = set(['username', 'password'])
- security = set(['encrypted', 'unencrypted_cram'])
-
- def setup(self, name):
- self.hash_name = name[5:]
- self.hash = hash(self.hash_name)
- if self.hash is None:
- raise SASLCancelled('Unknown hash: %s' % self.hash_name)
- if not self.security_settings['encrypted']:
- if not self.security_settings['unencrypted_cram']:
- raise SASLCancelled('Unecrypted CRAM-%s' % self.hash_name)
-
- def process(self, challenge=b''):
- if not challenge:
- return None
-
- username = self.credentials['username']
- password = self.credentials['password']
-
- mac = hmac.HMAC(key=password, digestmod=self.hash)
- mac.update(challenge)
-
- return username + b' ' + bytes(mac.hexdigest())
-
-
-@sasl_mech(60)
-class SCRAM(Mech):
-
- name = 'SCRAM'
- use_hashes = True
- channel_binding = True
- required_credentials = set(['username', 'password'])
- optional_credentials = set(['authzid', 'channel_binding'])
- security = set(['encrypted', 'unencrypted_scram'])
-
- def setup(self, name):
- self.use_channel_binding = False
- if name[-5:] == '-PLUS':
- name = name[:-5]
- self.use_channel_binding = True
-
- self.hash_name = name[6:]
- self.hash = hash(self.hash_name)
-
- if self.hash is None:
- raise SASLCancelled('Unknown hash: %s' % self.hash_name)
- if not self.security_settings['encrypted']:
- if not self.security_settings['unencrypted_scram']:
- raise SASLCancelled('Unencrypted SCRAM')
-
- self.step = 0
- self._mutual_auth = False
-
- def HMAC(self, key, msg):
- return hmac.HMAC(key=key, msg=msg, digestmod=self.hash).digest()
-
- def Hi(self, text, salt, iterations):
- text = bytes(text)
- ui1 = self.HMAC(text, salt + b'\0\0\0\01')
- ui = ui1
- for i in range(iterations - 1):
- ui1 = self.HMAC(text, ui1)
- ui = XOR(ui, ui1)
- return ui
-
- def H(self, text):
- return self.hash(text).digest()
-
- def saslname(self, value):
- value = value.decode("utf-8")
- escaped = []
- for char in value:
- if char == ',':
- escaped += '=2C'
- elif char == '=':
- escaped += '=3D'
- else:
- escaped += char
- return "".join(escaped).encode("utf-8")
-
- def parse(self, challenge):
- items = {}
- for key, value in [item.split(b'=', 1) for item in challenge.split(b',')]:
- items[key] = value
- return items
-
- def process(self, challenge=b''):
- steps = [self.process_1, self.process_2, self.process_3]
- return steps[self.step](challenge)
-
- def process_1(self, challenge):
- self.step = 1
- data = {}
-
- self.cnonce = bytes(('%s' % random.random())[2:])
-
- gs2_cbind_flag = b'n'
- if self.credentials['channel_binding']:
- if self.use_channel_binding:
- gs2_cbind_flag = b'p=tls-unique'
- else:
- gs2_cbind_flag = b'y'
-
- authzid = b''
- if self.credentials['authzid']:
- authzid = b'a=' + self.saslname(self.credentials['authzid'])
-
- self.gs2_header = gs2_cbind_flag + b',' + authzid + b','
-
- nonce = b'r=' + self.cnonce
- username = b'n=' + self.saslname(self.credentials['username'])
-
- self.client_first_message_bare = username + b',' + nonce
- self.client_first_message = self.gs2_header + \
- self.client_first_message_bare
-
- return self.client_first_message
-
- def process_2(self, challenge):
- self.step = 2
-
- data = self.parse(challenge)
- if b'm' in data:
- raise SASLCancelled('Received reserved attribute.')
-
- salt = b64decode(data[b's'])
- iteration_count = int(data[b'i'])
- nonce = data[b'r']
-
- if nonce[:len(self.cnonce)] != self.cnonce:
- raise SASLCancelled('Invalid nonce')
-
- cbind_data = b''
- if self.use_channel_binding:
- cbind_data = self.credentials['channel_binding']
- cbind_input = self.gs2_header + cbind_data
- channel_binding = b'c=' + b64encode(cbind_input).replace(b'\n', b'')
-
- client_final_message_without_proof = channel_binding + b',' + \
- b'r=' + nonce
-
- salted_password = self.Hi(self.credentials['password'],
- salt,
- iteration_count)
- client_key = self.HMAC(salted_password, b'Client Key')
- stored_key = self.H(client_key)
- auth_message = self.client_first_message_bare + b',' + \
- challenge + b',' + \
- client_final_message_without_proof
- client_signature = self.HMAC(stored_key, auth_message)
- client_proof = XOR(client_key, client_signature)
- server_key = self.HMAC(salted_password, b'Server Key')
-
- self.server_signature = self.HMAC(server_key, auth_message)
-
- client_final_message = client_final_message_without_proof + \
- b',p=' + b64encode(client_proof)
-
- return client_final_message
-
- def process_3(self, challenge):
- data = self.parse(challenge)
- verifier = data.get(b'v', None)
- error = data.get(b'e', 'Unknown error')
-
- if not verifier:
- raise SASLFailed(error)
-
- if b64decode(verifier) != self.server_signature:
- raise SASLMutualAuthFailed()
-
- self._mutual_auth = True
-
- return b''
-
-
-@sasl_mech(30)
-class DIGEST(Mech):
-
- name = 'DIGEST'
- use_hashes = True
- required_credentials = set(['username', 'password', 'realm', 'service', 'host'])
- optional_credentials = set(['authzid', 'service-name'])
- security = set(['encrypted', 'unencrypted_digest'])
-
- def setup(self, name):
- self.hash_name = name[7:]
- self.hash = hash(self.hash_name)
- if self.hash is None:
- raise SASLCancelled('Unknown hash: %s' % self.hash_name)
- if not self.security_settings['encrypted']:
- if not self.security_settings['unencrypted_digest']:
- raise SASLCancelled('Unencrypted DIGEST')
-
- self.qops = [b'auth']
- self.qop = b'auth'
- self.maxbuf = b'65536'
- self.nonce = b''
- self.cnonce = b''
- self.nonce_count = 1
-
- def parse(self, challenge=b''):
- data = {}
- var_name = b''
- var_value = b''
-
- # States: var, new_var, end, quote, escaped_quote
- state = 'var'
-
-
- for char in challenge:
- if sys.version_info >= (3, 0):
- char = bytes([char])
-
- if state == 'var':
- if char.isspace():
- continue
- if char == b'=':
- state = 'value'
- else:
- var_name += char
- elif state == 'value':
- if char == b'"':
- state = 'quote'
- elif char == b',':
- if var_name:
- data[var_name.decode('utf-8')] = var_value
- var_name = b''
- var_value = b''
- state = 'var'
- else:
- var_value += char
- elif state == 'escaped':
- var_value += char
- elif state == 'quote':
- if char == b'\\':
- state = 'escaped'
- elif char == b'"':
- state = 'end'
- else:
- var_value += char
- else:
- if char == b',':
- if var_name:
- data[var_name.decode('utf-8')] = var_value
- var_name = b''
- var_value = b''
- state = 'var'
- else:
- var_value += char
-
- if var_name:
- data[var_name.decode('utf-8')] = var_value
- var_name = b''
- var_value = b''
- state = 'var'
- return data
-
- def MAC(self, key, seq, msg):
- mac = hmac.HMAC(key=key, digestmod=self.hash)
- seqnum = num_to_bytes(seq)
- mac.update(seqnum)
- mac.update(msg)
- return mac.digest()[:10] + b'\x00\x01' + seqnum
-
- def A1(self):
- username = self.credentials['username']
- password = self.credentials['password']
- authzid = self.credentials['authzid']
- realm = self.credentials['realm']
-
- a1 = self.hash()
- a1.update(username + b':' + realm + b':' + password)
- a1 = a1.digest()
- a1 += b':' + self.nonce + b':' + self.cnonce
- if authzid:
- a1 += b':' + authzid
-
- return bytes(a1)
-
- def A2(self, prefix=b''):
- a2 = prefix + b':' + self.digest_uri()
- if self.qop in (b'auth-int', b'auth-conf'):
- a2 += b':00000000000000000000000000000000'
- return bytes(a2)
-
- def response(self, prefix=b''):
- nc = bytes('%08x' % self.nonce_count)
-
- a1 = bytes(self.hash(self.A1()).hexdigest().lower())
- a2 = bytes(self.hash(self.A2(prefix)).hexdigest().lower())
- s = self.nonce + b':' + nc + b':' + self.cnonce + \
- b':' + self.qop + b':' + a2
-
- return bytes(self.hash(a1 + b':' + s).hexdigest().lower())
-
- def digest_uri(self):
- serv_type = self.credentials['service']
- serv_name = self.credentials['service-name']
- host = self.credentials['host']
-
- uri = serv_type + b'/' + host
- if serv_name and host != serv_name:
- uri += b'/' + serv_name
- return uri
-
- def respond(self):
- data = {
- 'username': quote(self.credentials['username']),
- 'authzid': quote(self.credentials['authzid']),
- 'realm': quote(self.credentials['realm']),
- 'nonce': quote(self.nonce),
- 'cnonce': quote(self.cnonce),
- 'nc': bytes('%08x' % self.nonce_count),
- 'qop': self.qop,
- 'digest-uri': quote(self.digest_uri()),
- 'response': self.response(b'AUTHENTICATE'),
- 'maxbuf': self.maxbuf,
- 'charset': 'utf-8'
- }
- resp = b''
- for key, value in data.items():
- if value and value != b'""':
- resp += b',' + bytes(key) + b'=' + bytes(value)
- return resp[1:]
-
- def process(self, challenge=b''):
- if not challenge:
- if self.cnonce and self.nonce and self.nonce_count and self.qop:
- self.nonce_count += 1
- return self.respond()
- return None
-
- data = self.parse(challenge)
- if 'rspauth' in data:
- if data['rspauth'] != self.response():
- raise SASLMutualAuthFailed()
- else:
- self.nonce_count = 1
- self.cnonce = bytes('%s' % random.random())[2:]
- self.qops = data.get('qop', [b'auth'])
- self.qop = b'auth'
- if 'nonce' in data:
- self.nonce = data['nonce']
- if 'realm' in data and not self.credentials['realm']:
- self.credentials['realm'] = data['realm']
-
- return self.respond()
-
-
-try:
- import kerberos
-except ImportError:
- pass
-else:
- @sasl_mech(75)
- class GSSAPI(Mech):
-
- name = 'GSSAPI'
- required_credentials = set(['username', 'service-name'])
- optional_credentials = set(['authzid'])
-
- def setup(self, name):
- authzid = self.credentials['authzid']
- if not authzid:
- authzid = 'xmpp@%s' % self.credentials['service-name']
-
- _, self.gss = kerberos.authGSSClientInit(authzid)
- self.step = 0
-
- def process(self, challenge=b''):
- b64_challenge = b64encode(challenge)
- try:
- if self.step == 0:
- result = kerberos.authGSSClientStep(self.gss, b64_challenge)
- if result != kerberos.AUTH_GSS_CONTINUE:
- self.step = 1
- elif not challenge:
- kerberos.authGSSClientClean(self.gss)
- return b''
- elif self.step == 1:
- username = self.credentials['username']
-
- kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
- resp = kerberos.authGSSClientResponse(self.gss)
- kerberos.authGSSClientWrap(self.gss, resp, username)
-
- resp = kerberos.authGSSClientResponse(self.gss)
- except kerberos.GSSError as e:
- raise SASLCancelled('Kerberos error: %s' % e)
- if not resp:
- return b''
- else:
- return b64decode(resp)
diff --git a/sleekxmpp/util/stringprep_profiles.py b/sleekxmpp/util/stringprep_profiles.py
deleted file mode 100644
index 84326bc3..00000000
--- a/sleekxmpp/util/stringprep_profiles.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.util.stringprep_profiles
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module makes it easier to define profiles of stringprep,
- such as nodeprep and resourceprep for JID validation, and
- SASLprep for SASL.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
- :license: MIT, see LICENSE for more details
-"""
-
-
-from __future__ import unicode_literals
-
-import stringprep
-from unicodedata import ucd_3_2_0 as unicodedata
-
-from sleekxmpp.util import unicode
-
-
-class StringPrepError(UnicodeError):
- pass
-
-
-def b1_mapping(char):
- """Map characters that are commonly mapped to nothing."""
- return '' if stringprep.in_table_b1(char) else None
-
-
-def c12_mapping(char):
- """Map non-ASCII whitespace to spaces."""
- return ' ' if stringprep.in_table_c12(char) else None
-
-
-def map_input(data, tables=None):
- """
- Each character in the input stream MUST be checked against
- a mapping table.
- """
- result = []
- for char in data:
- replacement = None
-
- for mapping in tables:
- replacement = mapping(char)
- if replacement is not None:
- break
-
- if replacement is None:
- replacement = char
- result.append(replacement)
- return ''.join(result)
-
-
-def normalize(data, nfkc=True):
- """
- A profile can specify one of two options for Unicode normalization:
- - no normalization
- - Unicode normalization with form KC
- """
- if nfkc:
- data = unicodedata.normalize('NFKC', data)
- return data
-
-
-def prohibit_output(data, tables=None):
- """
- Before the text can be emitted, it MUST be checked for prohibited
- code points.
- """
- for char in data:
- for check in tables:
- if check(char):
- raise StringPrepError("Prohibited code point: %s" % char)
-
-
-def check_bidi(data):
- """
- 1) The characters in section 5.8 MUST be prohibited.
-
- 2) If a string contains any RandALCat character, the string MUST NOT
- contain any LCat character.
-
- 3) If a string contains any RandALCat character, a RandALCat
- character MUST be the first character of the string, and a
- RandALCat character MUST be the last character of the string.
- """
- if not data:
- return data
-
- has_lcat = False
- has_randal = False
-
- for c in data:
- if stringprep.in_table_c8(c):
- raise StringPrepError("BIDI violation: seciton 6 (1)")
- if stringprep.in_table_d1(c):
- has_randal = True
- elif stringprep.in_table_d2(c):
- has_lcat = True
-
- if has_randal and has_lcat:
- raise StringPrepError("BIDI violation: section 6 (2)")
-
- first_randal = stringprep.in_table_d1(data[0])
- last_randal = stringprep.in_table_d1(data[-1])
- if has_randal and not (first_randal and last_randal):
- raise StringPrepError("BIDI violation: section 6 (3)")
-
-
-def create(nfkc=True, bidi=True, mappings=None,
- prohibited=None, unassigned=None):
- """Create a profile of stringprep.
-
- :param bool nfkc:
- If `True`, perform NFKC Unicode normalization. Defaults to `True`.
- :param bool bidi:
- If `True`, perform bidirectional text checks. Defaults to `True`.
- :param list mappings:
- Optional list of functions for mapping characters to
- suitable replacements.
- :param list prohibited:
- Optional list of functions which check for the presence of
- prohibited characters.
- :param list unassigned:
- Optional list of functions for detecting the use of unassigned
- code points.
-
- :raises: StringPrepError
- :return: Unicode string of the resulting text passing the
- profile's requirements.
- """
- def profile(data, query=False):
- try:
- data = unicode(data)
- except UnicodeError:
- raise StringPrepError
-
- data = map_input(data, mappings)
- data = normalize(data, nfkc)
- prohibit_output(data, prohibited)
- if bidi:
- check_bidi(data)
- if query and unassigned:
- check_unassigned(data, unassigned)
- return data
- return profile
diff --git a/sleekxmpp/version.py b/sleekxmpp/version.py
deleted file mode 100644
index acea9334..00000000
--- a/sleekxmpp/version.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-# We don't want to have to import the entire library
-# just to get the version info for setup.py
-
-__version__ = '1.4.0'
-__version_info__ = (1, 4, 0, '', 0)
diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py
deleted file mode 100644
index 5a1ea1be..00000000
--- a/sleekxmpp/xmlstream/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.jid import JID
-from sleekxmpp.xmlstream.scheduler import Scheduler
-from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET
-from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
-from sleekxmpp.xmlstream.tostring import tostring
-from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT
-from sleekxmpp.xmlstream.xmlstream import RestartStream
-
-__all__ = ['JID', 'Scheduler', 'StanzaBase', 'ElementBase',
- 'ET', 'StateMachine', 'tostring', 'XMLStream',
- 'RESPONSE_TIMEOUT', 'RestartStream']
diff --git a/sleekxmpp/xmlstream/cert.py b/sleekxmpp/xmlstream/cert.py
deleted file mode 100644
index d357b326..00000000
--- a/sleekxmpp/xmlstream/cert.py
+++ /dev/null
@@ -1,184 +0,0 @@
-import logging
-from datetime import datetime, timedelta
-
-# Make a call to strptime before starting threads to
-# prevent thread safety issues.
-datetime.strptime('1970-01-01 12:00:00', "%Y-%m-%d %H:%M:%S")
-
-
-try:
- from pyasn1.codec.der import decoder, encoder
- from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
- from pyasn1.type.char import BMPString, IA5String, UTF8String
- from pyasn1.type.useful import GeneralizedTime
- from pyasn1_modules.rfc2459 import (Certificate, DirectoryString,
- SubjectAltName, GeneralNames,
- GeneralName)
- from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME
- from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
-
- XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
- SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
-
- HAVE_PYASN1 = True
-except ImportError:
- HAVE_PYASN1 = False
-
-
-log = logging.getLogger(__name__)
-
-
-class CertificateError(Exception):
- pass
-
-
-def decode_str(data):
- encoding = 'utf-16-be' if isinstance(data, BMPString) else 'utf-8'
- return bytes(data).decode(encoding)
-
-
-def extract_names(raw_cert):
- results = {'CN': set(),
- 'DNS': set(),
- 'SRV': set(),
- 'URI': set(),
- 'XMPPAddr': set()}
-
- cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0]
- tbs = cert.getComponentByName('tbsCertificate')
- subject = tbs.getComponentByName('subject')
- extensions = tbs.getComponentByName('extensions') or []
-
- # Extract the CommonName(s) from the cert.
- for rdnss in subject:
- for rdns in rdnss:
- for name in rdns:
- oid = name.getComponentByName('type')
- value = name.getComponentByName('value')
-
- if oid != COMMON_NAME:
- continue
-
- value = decoder.decode(value, asn1Spec=DirectoryString())[0]
- value = decode_str(value.getComponent())
- results['CN'].add(value)
-
- # Extract the Subject Alternate Names (DNS, SRV, URI, XMPPAddr)
- for extension in extensions:
- oid = extension.getComponentByName('extnID')
- if oid != SUBJECT_ALT_NAME:
- continue
-
- value = decoder.decode(extension.getComponentByName('extnValue'),
- asn1Spec=OctetString())[0]
- sa_names = decoder.decode(value, asn1Spec=SubjectAltName())[0]
- for name in sa_names:
- name_type = name.getName()
- if name_type == 'dNSName':
- results['DNS'].add(decode_str(name.getComponent()))
- if name_type == 'uniformResourceIdentifier':
- value = decode_str(name.getComponent())
- if value.startswith('xmpp:'):
- results['URI'].add(value[5:])
- elif name_type == 'otherName':
- name = name.getComponent()
-
- oid = name.getComponentByName('type-id')
- value = name.getComponentByName('value')
-
- if oid == XMPP_ADDR:
- value = decoder.decode(value, asn1Spec=UTF8String())[0]
- results['XMPPAddr'].add(decode_str(value))
- elif oid == SRV_NAME:
- value = decoder.decode(value, asn1Spec=IA5String())[0]
- results['SRV'].add(decode_str(value))
-
- return results
-
-
-def extract_dates(raw_cert):
- if not HAVE_PYASN1:
- log.warning("Could not find pyasn1 and pyasn1_modules. " + \
- "SSL certificate expiration COULD NOT BE VERIFIED.")
- return None, None
-
- cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0]
- tbs = cert.getComponentByName('tbsCertificate')
- validity = tbs.getComponentByName('validity')
-
- not_before = validity.getComponentByName('notBefore')
- not_before = str(not_before.getComponent())
-
- not_after = validity.getComponentByName('notAfter')
- not_after = str(not_after.getComponent())
-
- if isinstance(not_before, GeneralizedTime):
- not_before = datetime.strptime(not_before, '%Y%m%d%H%M%SZ')
- else:
- not_before = datetime.strptime(not_before, '%y%m%d%H%M%SZ')
-
- if isinstance(not_after, GeneralizedTime):
- not_after = datetime.strptime(not_after, '%Y%m%d%H%M%SZ')
- else:
- not_after = datetime.strptime(not_after, '%y%m%d%H%M%SZ')
-
- return not_before, not_after
-
-
-def get_ttl(raw_cert):
- not_before, not_after = extract_dates(raw_cert)
- if not_after is None:
- return None
- return not_after - datetime.utcnow()
-
-
-def verify(expected, raw_cert):
- if not HAVE_PYASN1:
- log.warning("Could not find pyasn1 and pyasn1_modules. " + \
- "SSL certificate COULD NOT BE VERIFIED.")
- return
-
- not_before, not_after = extract_dates(raw_cert)
- cert_names = extract_names(raw_cert)
-
- now = datetime.utcnow()
-
- if not_before > now:
- raise CertificateError(
- 'Certificate has not entered its valid date range.')
-
- if not_after <= now:
- raise CertificateError(
- 'Certificate has expired.')
-
- if '.' in expected:
- expected_wild = expected[expected.index('.'):]
- else:
- expected_wild = expected
- expected_srv = '_xmpp-client.%s' % expected
-
- for name in cert_names['XMPPAddr']:
- if name == expected:
- return True
- for name in cert_names['SRV']:
- if name == expected_srv or name == expected:
- return True
- for name in cert_names['DNS']:
- if name == expected:
- return True
- if name.startswith('*'):
- if '.' in name:
- name_wild = name[name.index('.'):]
- else:
- name_wild = name
- if expected_wild == name_wild:
- return True
- for name in cert_names['URI']:
- if name == expected:
- return True
- for name in cert_names['CN']:
- if name == expected:
- return True
-
- raise CertificateError(
- 'Could not match certificate against hostname: %s' % expected)
diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py
deleted file mode 100644
index 53b83bc7..00000000
--- a/sleekxmpp/xmlstream/filesocket.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.filesocket
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module is a shim for correcting deficiencies in the file
- socket implementation of Python2.6.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from socket import _fileobject
-import errno
-import socket
-
-
-class FileSocket(_fileobject):
-
- """Create a file object wrapper for a socket to work around
- issues present in Python 2.6 when using sockets as file objects.
-
- The parser for :class:`~xml.etree.cElementTree` requires a file, but
- we will be reading from the XMPP connection socket instead.
- """
-
- def read(self, size=4096):
- """Read data from the socket as if it were a file."""
- if self._sock is None:
- return None
- while True:
- try:
- data = self._sock.recv(size)
- break
- except socket.error as serr:
- if serr.errno != errno.EINTR:
- raise
- if data is not None:
- return data
-
-
-class Socket26(socket.socket):
-
- """A custom socket implementation that uses our own FileSocket class
- to work around issues in Python 2.6 when using sockets as files.
- """
-
- def makefile(self, mode='r', bufsize=-1):
- """makefile([mode[, bufsize]]) -> file object
- Return a regular file object corresponding to the socket. The mode
- and bufsize arguments are as for the built-in open() function."""
- return FileSocket(self._sock, mode, bufsize)
diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py
deleted file mode 100644
index 83c87f01..00000000
--- a/sleekxmpp/xmlstream/handler/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.handler.callback import Callback
-from sleekxmpp.xmlstream.handler.collector import Collector
-from sleekxmpp.xmlstream.handler.waiter import Waiter
-from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
-from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
-
-__all__ = ['Callback', 'Waiter', 'XMLCallback', 'XMLWaiter']
diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py
deleted file mode 100644
index 01c1991a..00000000
--- a/sleekxmpp/xmlstream/handler/base.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.handler.base
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-import weakref
-
-
-class BaseHandler(object):
-
- """
- Base class for stream handlers. Stream handlers are matched with
- incoming stanzas so that the stanza may be processed in some way.
- Stanzas may be matched with multiple handlers.
-
- Handler execution may take place in two phases: during the incoming
- stream processing, and in the main event loop. The :meth:`prerun()`
- method is executed in the first case, and :meth:`run()` is called
- during the second.
-
- :param string name: The name of the handler.
- :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
- derived object that will be used to determine if a
- stanza should be accepted by this handler.
- :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
- instance that the handle will respond to.
- """
-
- def __init__(self, name, matcher, stream=None):
- #: The name of the handler
- self.name = name
-
- #: The XML stream this handler is assigned to
- self.stream = None
- if stream is not None:
- self.stream = weakref.ref(stream)
- stream.register_handler(self)
-
- self._destroy = False
- self._payload = None
- self._matcher = matcher
-
- def match(self, xml):
- """Compare a stanza or XML object with the handler's matcher.
-
- :param xml: An XML or
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object
- """
- return self._matcher.match(xml)
-
- def prerun(self, payload):
- """Prepare the handler for execution while the XML
- stream is being processed.
-
- :param payload: A :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- object.
- """
- self._payload = payload
-
- def run(self, payload):
- """Execute the handler after XML stream processing and during the
- main event loop.
-
- :param payload: A :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- object.
- """
- self._payload = payload
-
- def check_delete(self):
- """Check if the handler should be removed from the list
- of stream handlers.
- """
- return self._destroy
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-BaseHandler.checkDelete = BaseHandler.check_delete
diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py
deleted file mode 100644
index 7e3388f1..00000000
--- a/sleekxmpp/xmlstream/handler/callback.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.handler.callback
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.xmlstream.handler.base import BaseHandler
-
-
-class Callback(BaseHandler):
-
- """
- The Callback handler will execute a callback function with
- matched stanzas.
-
- The handler may execute the callback either during stream
- processing or during the main event loop.
-
- Callback functions are all executed in the same thread, so be aware if
- you are executing functions that will block for extended periods of
- time. Typically, you should signal your own events using the SleekXMPP
- object's :meth:`~sleekxmpp.xmlstream.xmlstream.XMLStream.event()`
- method to pass the stanza off to a threaded event handler for further
- processing.
-
-
- :param string name: The name of the handler.
- :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
- derived object for matching stanza objects.
- :param pointer: The function to execute during callback.
- :param bool thread: **DEPRECATED.** Remains only for
- backwards compatibility.
- :param bool once: Indicates if the handler should be used only
- once. Defaults to False.
- :param bool instream: Indicates if the callback should be executed
- during stream processing instead of in the
- main event loop.
- :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
- instance this handler should monitor.
- """
-
- def __init__(self, name, matcher, pointer, thread=False,
- once=False, instream=False, stream=None):
- BaseHandler.__init__(self, name, matcher, stream)
- self._pointer = pointer
- self._once = once
- self._instream = instream
-
- def prerun(self, payload):
- """Execute the callback during stream processing, if
- the callback was created with ``instream=True``.
-
- :param payload: The matched
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
- """
- if self._once:
- self._destroy = True
- if self._instream:
- self.run(payload, True)
-
- def run(self, payload, instream=False):
- """Execute the callback function with the matched stanza payload.
-
- :param payload: The matched
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
- :param bool instream: Force the handler to execute during stream
- processing. This should only be used by
- :meth:`prerun()`. Defaults to ``False``.
- """
- if not self._instream or instream:
- self._pointer(payload)
- if self._once:
- self._destroy = True
- del self._pointer
diff --git a/sleekxmpp/xmlstream/handler/collector.py b/sleekxmpp/xmlstream/handler/collector.py
deleted file mode 100644
index 8f02f8c3..00000000
--- a/sleekxmpp/xmlstream/handler/collector.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.handler.collector
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
- :license: MIT, see LICENSE for more details
-"""
-
-import logging
-
-from sleekxmpp.util import Queue, QueueEmpty
-from sleekxmpp.xmlstream.handler.base import BaseHandler
-
-
-log = logging.getLogger(__name__)
-
-
-class Collector(BaseHandler):
-
- """
- The Collector handler allows for collecting a set of stanzas
- that match a given pattern. Unlike the Waiter handler, a
- Collector does not block execution, and will continue to
- accumulate matching stanzas until told to stop.
-
- :param string name: The name of the handler.
- :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
- derived object for matching stanza objects.
- :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
- instance this handler should monitor.
- """
-
- def __init__(self, name, matcher, stream=None):
- BaseHandler.__init__(self, name, matcher, stream=stream)
- self._payload = Queue()
-
- def prerun(self, payload):
- """Store the matched stanza when received during processing.
-
- :param payload: The matched
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
- """
- self._payload.put(payload)
-
- def run(self, payload):
- """Do not process this handler during the main event loop."""
- pass
-
- def stop(self):
- """
- Stop collection of matching stanzas, and return the ones that
- have been stored so far.
- """
- self._destroy = True
- results = []
- try:
- while True:
- results.append(self._payload.get(False))
- except QueueEmpty:
- pass
-
- self.stream().remove_handler(self.name)
- return results
diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py
deleted file mode 100644
index 66e14496..00000000
--- a/sleekxmpp/xmlstream/handler/waiter.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.handler.waiter
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-import logging
-
-from sleekxmpp.util import Queue, QueueEmpty
-from sleekxmpp.xmlstream.handler.base import BaseHandler
-
-
-log = logging.getLogger(__name__)
-
-
-class Waiter(BaseHandler):
-
- """
- The Waiter handler allows an event handler to block until a
- particular stanza has been received. The handler will either be
- given the matched stanza, or ``False`` if the waiter has timed out.
-
- :param string name: The name of the handler.
- :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
- derived object for matching stanza objects.
- :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
- instance this handler should monitor.
- """
-
- def __init__(self, name, matcher, stream=None):
- BaseHandler.__init__(self, name, matcher, stream=stream)
- self._payload = Queue()
-
- def prerun(self, payload):
- """Store the matched stanza when received during processing.
-
- :param payload: The matched
- :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
- """
- self._payload.put(payload)
-
- def run(self, payload):
- """Do not process this handler during the main event loop."""
- pass
-
- def wait(self, timeout=None):
- """Block an event handler while waiting for a stanza to arrive.
-
- Be aware that this will impact performance if called from a
- non-threaded event handler.
-
- Will return either the received stanza, or ``False`` if the
- waiter timed out.
-
- :param int timeout: The number of seconds to wait for the stanza
- to arrive. Defaults to the the stream's
- :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`
- value.
- """
- if timeout is None:
- timeout = self.stream().response_timeout
-
- elapsed_time = 0
- stanza = False
- while elapsed_time < timeout and not self.stream().stop.is_set():
- try:
- stanza = self._payload.get(True, 1)
- break
- except QueueEmpty:
- elapsed_time += 1
- if elapsed_time >= timeout:
- log.warning("Timed out waiting for %s", self.name)
- self.stream().remove_handler(self.name)
- return stanza
-
- def check_delete(self):
- """Always remove waiters after use."""
- return True
diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py
deleted file mode 100644
index 11607ffb..00000000
--- a/sleekxmpp/xmlstream/handler/xmlcallback.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.handler import Callback
-
-
-class XMLCallback(Callback):
-
- """
- The XMLCallback class is identical to the normal Callback class,
- except that XML contents of matched stanzas will be processed instead
- of the stanza objects themselves.
-
- Methods:
- run -- Overrides Callback.run
- """
-
- def run(self, payload, instream=False):
- """
- Execute the callback function with the matched stanza's
- XML contents, instead of the stanza itself.
-
- Overrides BaseHandler.run
-
- Arguments:
- payload -- The matched stanza object.
- instream -- Force the handler to execute during
- stream processing. Used only by prerun.
- Defaults to False.
- """
- Callback.run(self, payload.xml, instream)
diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py
deleted file mode 100644
index 5201caf3..00000000
--- a/sleekxmpp/xmlstream/handler/xmlwaiter.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.handler import Waiter
-
-
-class XMLWaiter(Waiter):
-
- """
- The XMLWaiter class is identical to the normal Waiter class
- except that it returns the XML contents of the stanza instead
- of the full stanza object itself.
-
- Methods:
- prerun -- Overrides Waiter.prerun
- """
-
- def prerun(self, payload):
- """
- Store the XML contents of the stanza to return to the
- waiting event handler.
-
- Overrides Waiter.prerun
-
- Arguments:
- payload -- The matched stanza object.
- """
- Waiter.prerun(self, payload.xml)
diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py
deleted file mode 100644
index 2b59db47..00000000
--- a/sleekxmpp/xmlstream/jid.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import logging
-
-logging.warning('Deprecated: sleekxmpp.xmlstream.jid is moving to sleekxmpp.jid')
-
-from sleekxmpp.jid import JID
diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py
deleted file mode 100644
index aa74c434..00000000
--- a/sleekxmpp/xmlstream/matcher/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.matcher.id import MatcherId
-from sleekxmpp.xmlstream.matcher.idsender import MatchIDSender
-from sleekxmpp.xmlstream.matcher.many import MatchMany
-from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
-from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
-from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
-
-__all__ = ['MatcherId', 'MatchMany', 'StanzaPath',
- 'MatchXMLMask', 'MatchXPath']
diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py
deleted file mode 100644
index 83c26688..00000000
--- a/sleekxmpp/xmlstream/matcher/base.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.matcher.base
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-
-class MatcherBase(object):
-
- """
- Base class for stanza matchers. Stanza matchers are used to pick
- stanzas out of the XML stream and pass them to the appropriate
- stream handlers.
-
- :param criteria: Object to compare some aspect of a stanza against.
- """
-
- def __init__(self, criteria):
- self._criteria = criteria
-
- def match(self, xml):
- """Check if a stanza matches the stored criteria.
-
- Meant to be overridden.
- """
- return False
diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py
deleted file mode 100644
index 11ab70bb..00000000
--- a/sleekxmpp/xmlstream/matcher/id.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.matcher.id
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatcherId(MatcherBase):
-
- """
- The ID matcher selects stanzas that have the same stanza 'id'
- interface value as the desired ID.
- """
-
- def match(self, xml):
- """Compare the given stanza's ``'id'`` attribute to the stored
- ``id`` value.
-
- :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to compare against.
- """
- return xml['id'] == self._criteria
diff --git a/sleekxmpp/xmlstream/matcher/idsender.py b/sleekxmpp/xmlstream/matcher/idsender.py
deleted file mode 100644
index 5c2c1f51..00000000
--- a/sleekxmpp/xmlstream/matcher/idsender.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.matcher.id
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatchIDSender(MatcherBase):
-
- """
- The IDSender matcher selects stanzas that have the same stanza 'id'
- interface value as the desired ID, and that the 'from' value is one
- of a set of approved entities that can respond to a request.
- """
-
- def match(self, xml):
- """Compare the given stanza's ``'id'`` attribute to the stored
- ``id`` value, and verify the sender's JID.
-
- :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to compare against.
- """
-
- selfjid = self._criteria['self']
- peerjid = self._criteria['peer']
-
- allowed = {}
- allowed[''] = True
- allowed[selfjid.bare] = True
- allowed[selfjid.host] = True
- allowed[peerjid.full] = True
- allowed[peerjid.bare] = True
- allowed[peerjid.host] = True
-
- _from = xml['from']
-
- try:
- return xml['id'] == self._criteria['id'] and allowed[_from]
- except KeyError:
- return False
diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py
deleted file mode 100644
index f470ec9c..00000000
--- a/sleekxmpp/xmlstream/matcher/many.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatchMany(MatcherBase):
-
- """
- The MatchMany matcher may compare a stanza against multiple
- criteria. It is essentially an OR relation combining multiple
- matchers.
-
- Each of the criteria must implement a match() method.
-
- Methods:
- match -- Overrides MatcherBase.match.
- """
-
- def match(self, xml):
- """
- Match a stanza against multiple criteria. The match is successful
- if one of the criteria matches.
-
- Each of the criteria must implement a match() method.
-
- Overrides MatcherBase.match.
-
- Arguments:
- xml -- The stanza object to compare against.
- """
- for m in self._criteria:
- if m.match(xml):
- return True
- return False
diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py
deleted file mode 100644
index a4c0fda0..00000000
--- a/sleekxmpp/xmlstream/matcher/stanzapath.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.matcher.stanzapath
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-from sleekxmpp.xmlstream.stanzabase import fix_ns
-
-
-class StanzaPath(MatcherBase):
-
- """
- The StanzaPath matcher selects stanzas that match a given "stanza path",
- which is similar to a normal XPath except that it uses the interfaces and
- plugins of the stanza instead of the actual, underlying XML.
-
- :param criteria: Object to compare some aspect of a stanza against.
- """
-
- def __init__(self, criteria):
- self._criteria = fix_ns(criteria, split=True,
- propagate_ns=False,
- default_ns='jabber:client')
- self._raw_criteria = criteria
-
- def match(self, stanza):
- """
- Compare a stanza against a "stanza path". A stanza path is similar to
- an XPath expression, but uses the stanza's interfaces and plugins
- instead of the underlying XML. See the documentation for the stanza
- :meth:`~sleekxmpp.xmlstream.stanzabase.ElementBase.match()` method
- for more information.
-
- :param stanza: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to compare against.
- """
- return stanza.match(self._criteria) or stanza.match(self._raw_criteria)
diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py
deleted file mode 100644
index 56f728e1..00000000
--- a/sleekxmpp/xmlstream/matcher/xmlmask.py
+++ /dev/null
@@ -1,117 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from xml.parsers.expat import ExpatError
-
-from sleekxmpp.xmlstream.stanzabase import ET
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-log = logging.getLogger(__name__)
-
-
-class MatchXMLMask(MatcherBase):
-
- """
- The XMLMask matcher selects stanzas whose XML matches a given
- XML pattern, or mask. For example, message stanzas with body elements
- could be matched using the mask:
-
- .. code-block:: xml
-
- <message xmlns="jabber:client"><body /></message>
-
- Use of XMLMask is discouraged, and
- :class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
- :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
- should be used instead.
-
- :param criteria: Either an :class:`~xml.etree.ElementTree.Element` XML
- object or XML string to use as a mask.
- """
-
- def __init__(self, criteria, default_ns='jabber:client'):
- MatcherBase.__init__(self, criteria)
- if isinstance(criteria, str):
- self._criteria = ET.fromstring(self._criteria)
- self.default_ns = default_ns
-
- def setDefaultNS(self, ns):
- """Set the default namespace to use during comparisons.
-
- :param ns: The new namespace to use as the default.
- """
- self.default_ns = ns
-
- def match(self, xml):
- """Compare a stanza object or XML object against the stored XML mask.
-
- Overrides MatcherBase.match.
-
- :param xml: The stanza object or XML object to compare against.
- """
- if hasattr(xml, 'xml'):
- xml = xml.xml
- return self._mask_cmp(xml, self._criteria, True)
-
- def _mask_cmp(self, source, mask, use_ns=False, default_ns='__no_ns__'):
- """Compare an XML object against an XML mask.
-
- :param source: The :class:`~xml.etree.ElementTree.Element` XML object
- to compare against the mask.
- :param mask: The :class:`~xml.etree.ElementTree.Element` XML object
- serving as the mask.
- :param use_ns: Indicates if namespaces should be respected during
- the comparison.
- :default_ns: The default namespace to apply to elements that
- do not have a specified namespace.
- Defaults to ``"__no_ns__"``.
- """
- if source is None:
- # If the element was not found. May happend during recursive calls.
- return False
-
- # Convert the mask to an XML object if it is a string.
- if not hasattr(mask, 'attrib'):
- try:
- mask = ET.fromstring(mask)
- except ExpatError:
- log.warning("Expat error: %s\nIn parsing: %s", '', mask)
-
- mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag)
- if source.tag not in [mask.tag, mask_ns_tag]:
- return False
-
- # If the mask includes text, compare it.
- if mask.text and source.text and \
- source.text.strip() != mask.text.strip():
- return False
-
- # Compare attributes. The stanza must include the attributes
- # defined by the mask, but may include others.
- for name, value in mask.attrib.items():
- if source.attrib.get(name, "__None__") != value:
- return False
-
- # Recursively check subelements.
- matched_elements = {}
- for subelement in mask:
- matched = False
- for other in source.findall(subelement.tag):
- matched_elements[other] = False
- if self._mask_cmp(other, subelement, use_ns):
- if not matched_elements.get(other, False):
- matched_elements[other] = True
- matched = True
- if not matched:
- return False
-
- # Everything matches.
- return True
diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py
deleted file mode 100644
index f3d28429..00000000
--- a/sleekxmpp/xmlstream/matcher/xpath.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.matcher.xpath
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from sleekxmpp.xmlstream.stanzabase import ET, fix_ns
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatchXPath(MatcherBase):
-
- """
- The XPath matcher selects stanzas whose XML contents matches a given
- XPath expression.
-
- .. warning::
-
- Using this matcher may not produce expected behavior when using
- attribute selectors. For Python 2.6 and 3.1, the ElementTree
- :meth:`~xml.etree.ElementTree.Element.find()` method does
- not support the use of attribute selectors. If you need to
- support Python 2.6 or 3.1, it might be more useful to use a
- :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher.
-
- If the value of :data:`IGNORE_NS` is set to ``True``, then XPath
- expressions will be matched without using namespaces.
- """
-
- def __init__(self, criteria):
- self._criteria = fix_ns(criteria)
-
- def match(self, xml):
- """
- Compare a stanza's XML contents to an XPath expression.
-
- If the value of :data:`IGNORE_NS` is set to ``True``, then XPath
- expressions will be matched without using namespaces.
-
- .. warning::
-
- In Python 2.6 and 3.1 the ElementTree
- :meth:`~xml.etree.ElementTree.Element.find()` method does not
- support attribute selectors in the XPath expression.
-
- :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to compare against.
- """
- if hasattr(xml, 'xml'):
- xml = xml.xml
- x = ET.Element('x')
- x.append(xml)
-
- return x.find(self._criteria) is not None
diff --git a/sleekxmpp/xmlstream/resolver.py b/sleekxmpp/xmlstream/resolver.py
deleted file mode 100644
index 188e5ac7..00000000
--- a/sleekxmpp/xmlstream/resolver.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# -*- encoding: utf-8 -*-
-
-"""
- sleekxmpp.xmlstream.dns
- ~~~~~~~~~~~~~~~~~~~~~~~
-
- :copyright: (c) 2012 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-import socket
-import logging
-import random
-
-
-log = logging.getLogger(__name__)
-
-
-#: Global flag indicating the availability of the ``dnspython`` package.
-#: Installing ``dnspython`` can be done via:
-#:
-#: .. code-block:: sh
-#:
-#: pip install dnspython
-#:
-#: For Python3, installation may require installing from source using
-#: the ``python3`` branch:
-#:
-#: .. code-block:: sh
-#:
-#: git clone http://github.com/rthalley/dnspython
-#: cd dnspython
-#: git checkout python3
-#: python3 setup.py install
-DNSPYTHON_AVAILABLE = False
-try:
- import dns.resolver
- DNSPYTHON_AVAILABLE = True
-except ImportError as e:
- log.debug("Could not find dnspython package. " + \
- "Not all features will be available")
-
-
-def default_resolver():
- """Return a basic DNS resolver object.
-
- :returns: A :class:`dns.resolver.Resolver` object if dnspython
- is available. Otherwise, ``None``.
- """
- if DNSPYTHON_AVAILABLE:
- return dns.resolver.get_default_resolver()
- return None
-
-
-def resolve(host, port=None, service=None, proto='tcp',
- resolver=None, use_ipv6=True, use_dnspython=True):
- """Peform DNS resolution for a given hostname.
-
- Resolution may perform SRV record lookups if a service and protocol
- are specified. The returned addresses will be sorted according to
- the SRV priorities and weights.
-
- If no resolver is provided, the dnspython resolver will be used if
- available. Otherwise the built-in socket facilities will be used,
- but those do not provide SRV support.
-
- If SRV records were used, queries to resolve alternative hosts will
- be made as needed instead of all at once.
-
- :param host: The hostname to resolve.
- :param port: A default port to connect with. SRV records may
- dictate use of a different port.
- :param service: Optional SRV service name without leading underscore.
- :param proto: Optional SRV protocol name without leading underscore.
- :param resolver: Optionally provide a DNS resolver object that has
- been custom configured.
- :param use_ipv6: Optionally control the use of IPv6 in situations
- where it is either not available, or performance
- is degraded. Defaults to ``True``.
- :param use_dnspython: Optionally control if dnspython is used to make
- the DNS queries instead of the built-in DNS
- library.
-
- :type host: string
- :type port: int
- :type service: string
- :type proto: string
- :type resolver: :class:`dns.resolver.Resolver`
- :type use_ipv6: bool
- :type use_dnspython: bool
-
- :return: An iterable of IP address, port pairs in the order
- dictated by SRV priorities and weights, if applicable.
- """
-
- if not use_dnspython:
- if DNSPYTHON_AVAILABLE:
- log.debug("DNS: Not using dnspython, but dnspython is installed.")
- else:
- log.debug("DNS: Not using dnspython.")
-
- if not use_ipv6:
- log.debug("DNS: Use of IPv6 has been disabled.")
-
- if resolver is None and DNSPYTHON_AVAILABLE and use_dnspython:
- resolver = dns.resolver.get_default_resolver()
-
- # An IPv6 literal is allowed to be enclosed in square brackets, but
- # the brackets must be stripped in order to process the literal;
- # otherwise, things break.
- host = host.strip('[]')
-
- try:
- # If `host` is an IPv4 literal, we can return it immediately.
- ipv4 = socket.inet_aton(host)
- yield (host, host, port)
- except socket.error:
- pass
-
- if use_ipv6:
- try:
- # Likewise, If `host` is an IPv6 literal, we can return
- # it immediately.
- if hasattr(socket, 'inet_pton'):
- ipv6 = socket.inet_pton(socket.AF_INET6, host)
- yield (host, host, port)
- except (socket.error, ValueError):
- pass
-
- # If no service was provided, then we can just do A/AAAA lookups on the
- # provided host. Otherwise we need to get an ordered list of hosts to
- # resolve based on SRV records.
- if not service:
- hosts = [(host, port)]
- else:
- hosts = get_SRV(host, port, service, proto,
- resolver=resolver,
- use_dnspython=use_dnspython)
-
- for host, port in hosts:
- results = []
- if host == 'localhost':
- if use_ipv6:
- results.append((host, '::1', port))
- results.append((host, '127.0.0.1', port))
- if use_ipv6:
- for address in get_AAAA(host, resolver=resolver,
- use_dnspython=use_dnspython):
- results.append((host, address, port))
- for address in get_A(host, resolver=resolver,
- use_dnspython=use_dnspython):
- results.append((host, address, port))
-
- for host, address, port in results:
- yield host, address, port
-
-
-def get_A(host, resolver=None, use_dnspython=True):
- """Lookup DNS A records for a given host.
-
- If ``resolver`` is not provided, or is ``None``, then resolution will
- be performed using the built-in :mod:`socket` module.
-
- :param host: The hostname to resolve for A record IPv4 addresses.
- :param resolver: Optional DNS resolver object to use for the query.
- :param use_dnspython: Optionally control if dnspython is used to make
- the DNS queries instead of the built-in DNS
- library.
-
- :type host: string
- :type resolver: :class:`dns.resolver.Resolver` or ``None``
- :type use_dnspython: bool
-
- :return: A list of IPv4 literals.
- """
- log.debug("DNS: Querying %s for A records." % host)
-
- # If not using dnspython, attempt lookup using the OS level
- # getaddrinfo() method.
- if resolver is None or not use_dnspython:
- try:
- recs = socket.getaddrinfo(host, None, socket.AF_INET,
- socket.SOCK_STREAM)
- return [rec[4][0] for rec in recs]
- except socket.gaierror:
- log.debug("DNS: Error retreiving A address info for %s." % host)
- return []
-
- # Using dnspython:
- try:
- recs = resolver.query(host, dns.rdatatype.A)
- return [rec.to_text() for rec in recs]
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
- log.debug("DNS: No A records for %s" % host)
- return []
- except dns.exception.Timeout:
- log.debug("DNS: A record resolution timed out for %s" % host)
- return []
- except dns.exception.DNSException as e:
- log.debug("DNS: Error querying A records for %s" % host)
- log.exception(e)
- return []
-
-
-def get_AAAA(host, resolver=None, use_dnspython=True):
- """Lookup DNS AAAA records for a given host.
-
- If ``resolver`` is not provided, or is ``None``, then resolution will
- be performed using the built-in :mod:`socket` module.
-
- :param host: The hostname to resolve for AAAA record IPv6 addresses.
- :param resolver: Optional DNS resolver object to use for the query.
- :param use_dnspython: Optionally control if dnspython is used to make
- the DNS queries instead of the built-in DNS
- library.
-
- :type host: string
- :type resolver: :class:`dns.resolver.Resolver` or ``None``
- :type use_dnspython: bool
-
- :return: A list of IPv6 literals.
- """
- log.debug("DNS: Querying %s for AAAA records." % host)
-
- # If not using dnspython, attempt lookup using the OS level
- # getaddrinfo() method.
- if resolver is None or not use_dnspython:
- if not socket.has_ipv6:
- log.debug("Unable to query %s for AAAA records: IPv6 is not supported", host)
- return []
- try:
- recs = socket.getaddrinfo(host, None, socket.AF_INET6,
- socket.SOCK_STREAM)
- return [rec[4][0] for rec in recs]
- except (OSError, socket.gaierror):
- log.debug("DNS: Error retreiving AAAA address " + \
- "info for %s." % host)
- return []
-
- # Using dnspython:
- try:
- recs = resolver.query(host, dns.rdatatype.AAAA)
- return [rec.to_text() for rec in recs]
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
- log.debug("DNS: No AAAA records for %s" % host)
- return []
- except dns.exception.Timeout:
- log.debug("DNS: AAAA record resolution timed out for %s" % host)
- return []
- except dns.exception.DNSException as e:
- log.debug("DNS: Error querying AAAA records for %s" % host)
- log.exception(e)
- return []
-
-
-def get_SRV(host, port, service, proto='tcp', resolver=None, use_dnspython=True):
- """Perform SRV record resolution for a given host.
-
- .. note::
-
- This function requires the use of the ``dnspython`` package. Calling
- :func:`get_SRV` without ``dnspython`` will return the provided host
- and port without performing any DNS queries.
-
- :param host: The hostname to resolve.
- :param port: A default port to connect with. SRV records may
- dictate use of a different port.
- :param service: Optional SRV service name without leading underscore.
- :param proto: Optional SRV protocol name without leading underscore.
- :param resolver: Optionally provide a DNS resolver object that has
- been custom configured.
-
- :type host: string
- :type port: int
- :type service: string
- :type proto: string
- :type resolver: :class:`dns.resolver.Resolver`
-
- :return: A list of hostname, port pairs in the order dictacted
- by SRV priorities and weights.
- """
- if resolver is None or not use_dnspython:
- log.warning("DNS: dnspython not found. Can not use SRV lookup.")
- return [(host, port)]
-
- log.debug("DNS: Querying SRV records for %s" % host)
- try:
- recs = resolver.query('_%s._%s.%s' % (service, proto, host),
- dns.rdatatype.SRV)
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
- log.debug("DNS: No SRV records for %s." % host)
- return [(host, port)]
- except dns.exception.Timeout:
- log.debug("DNS: SRV record resolution timed out for %s." % host)
- return [(host, port)]
- except dns.exception.DNSException as e:
- log.debug("DNS: Error querying SRV records for %s." % host)
- log.exception(e)
- return [(host, port)]
-
- if len(recs) == 1 and recs[0].target == '.':
- return [(host, port)]
-
- answers = {}
- for rec in recs:
- if rec.priority not in answers:
- answers[rec.priority] = []
- if rec.weight == 0:
- answers[rec.priority].insert(0, rec)
- else:
- answers[rec.priority].append(rec)
-
- sorted_recs = []
- for priority in sorted(answers.keys()):
- while answers[priority]:
- running_sum = 0
- sums = {}
- for rec in answers[priority]:
- running_sum += rec.weight
- sums[running_sum] = rec
-
- selected = random.randint(0, running_sum + 1)
- for running_sum in sums:
- if running_sum >= selected:
- rec = sums[running_sum]
- host = rec.target.to_text()
- if host.endswith('.'):
- host = host[:-1]
- sorted_recs.append((host, rec.port))
- answers[priority].remove(rec)
- break
-
- return sorted_recs
diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py
deleted file mode 100644
index e6fae37a..00000000
--- a/sleekxmpp/xmlstream/scheduler.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.scheduler
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module provides a task scheduler that works better
- with SleekXMPP's threading usage than the stock version.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-import time
-import threading
-import logging
-import itertools
-
-from sleekxmpp.util import Queue, QueueEmpty
-
-
-#: The time in seconds to wait for events from the event queue, and also the
-#: time between checks for the process stop signal.
-WAIT_TIMEOUT = 1.0
-
-
-log = logging.getLogger(__name__)
-
-
-class Task(object):
-
- """
- A scheduled task that will be executed by the scheduler
- after a given time interval has passed.
-
- :param string name: The name of the task.
- :param int seconds: The number of seconds to wait before executing.
- :param callback: The function to execute.
- :param tuple args: The arguments to pass to the callback.
- :param dict kwargs: The keyword arguments to pass to the callback.
- :param bool repeat: Indicates if the task should repeat.
- Defaults to ``False``.
- :param pointer: A pointer to an event queue for queuing callback
- execution instead of executing immediately.
- """
-
- def __init__(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False, qpointer=None):
- #: The name of the task.
- self.name = name
-
- #: The number of seconds to wait before executing.
- self.seconds = seconds
-
- #: The function to execute once enough time has passed.
- self.callback = callback
-
- #: The arguments to pass to :attr:`callback`.
- self.args = args or tuple()
-
- #: The keyword arguments to pass to :attr:`callback`.
- self.kwargs = kwargs or {}
-
- #: Indicates if the task should repeat after executing,
- #: using the same :attr:`seconds` delay.
- self.repeat = repeat
-
- #: The time when the task should execute next.
- self.next = time.time() + self.seconds
-
- #: The main event queue, which allows for callbacks to
- #: be queued for execution instead of executing immediately.
- self.qpointer = qpointer
-
- def run(self):
- """Execute the task's callback.
-
- If an event queue was supplied, place the callback in the queue;
- otherwise, execute the callback immediately.
- """
- if self.qpointer is not None:
- self.qpointer.put(('schedule', self.callback,
- self.args, self.kwargs, self.name))
- else:
- self.callback(*self.args, **self.kwargs)
- self.reset()
- return self.repeat
-
- def reset(self):
- """Reset the task's timer so that it will repeat."""
- self.next = time.time() + self.seconds
-
-
-class Scheduler(object):
-
- """
- A threaded scheduler that allows for updates mid-execution unlike the
- scheduler in the standard library.
-
- Based on: http://docs.python.org/library/sched.html#module-sched
-
- :param parentstop: An :class:`~threading.Event` to signal stopping
- the scheduler.
- """
-
- def __init__(self, parentstop=None):
- #: A queue for storing tasks
- self.addq = Queue()
-
- #: A list of tasks in order of execution time.
- self.schedule = []
-
- #: If running in threaded mode, this will be the thread processing
- #: the schedule.
- self.thread = None
-
- #: A flag indicating that the scheduler is running.
- self.run = False
-
- #: An :class:`~threading.Event` instance for signalling to stop
- #: the scheduler.
- self.stop = parentstop
-
- #: Lock for accessing the task queue.
- self.schedule_lock = threading.RLock()
-
- #: The time in seconds to wait for events from the event queue,
- #: and also the time between checks for the process stop signal.
- self.wait_timeout = WAIT_TIMEOUT
-
- def process(self, threaded=True, daemon=False):
- """Begin accepting and processing scheduled tasks.
-
- :param bool threaded: Indicates if the scheduler should execute
- in its own thread. Defaults to ``True``.
- """
- if threaded:
- self.thread = threading.Thread(name='scheduler_process',
- target=self._process)
- self.thread.daemon = daemon
- self.thread.start()
- else:
- self._process()
-
- def _process(self):
- """Process scheduled tasks."""
- self.run = True
- try:
- while self.run and not self.stop.is_set():
- updated = False
- if self.schedule:
- wait = self.schedule[0].next - time.time()
- else:
- wait = self.wait_timeout
- try:
- if wait <= 0.0:
- newtask = self.addq.get(False)
- else:
- newtask = None
- while self.run and \
- not self.stop.is_set() and \
- newtask is None and \
- wait > 0:
- try:
- newtask = self.addq.get(True, min(wait, self.wait_timeout))
- except QueueEmpty: # Nothing to add, nothing to do. Check run flags and continue waiting.
- wait -= self.wait_timeout
- except QueueEmpty: # Time to run some tasks, and no new tasks to add.
- self.schedule_lock.acquire()
- # select only those tasks which are to be executed now
- relevant = itertools.takewhile(
- lambda task: time.time() >= task.next, self.schedule)
- # run the tasks and keep the return value in a tuple
- status = map(lambda task: (task, task.run()), relevant)
- # remove non-repeating tasks
- for task, doRepeat in status:
- if not doRepeat:
- try:
- self.schedule.remove(task)
- except ValueError:
- pass
- else:
- # only need to resort tasks if a repeated task has
- # been kept in the list.
- updated = True
- else: # Add new task
- self.schedule_lock.acquire()
- if newtask is not None:
- self.schedule.append(newtask)
- updated = True
- finally:
- if updated:
- self.schedule.sort(key=lambda task: task.next)
- self.schedule_lock.release()
- except KeyboardInterrupt:
- self.run = False
- except SystemExit:
- self.run = False
- log.debug("Quitting Scheduler thread")
-
- def add(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False, qpointer=None):
- """Schedule a new task.
-
- :param string name: The name of the task.
- :param int seconds: The number of seconds to wait before executing.
- :param callback: The function to execute.
- :param tuple args: The arguments to pass to the callback.
- :param dict kwargs: The keyword arguments to pass to the callback.
- :param bool repeat: Indicates if the task should repeat.
- Defaults to ``False``.
- :param pointer: A pointer to an event queue for queuing callback
- execution instead of executing immediately.
- """
- try:
- self.schedule_lock.acquire()
- for task in self.schedule:
- if task.name == name:
- raise ValueError("Key %s already exists" % name)
-
- self.addq.put(Task(name, seconds, callback, args,
- kwargs, repeat, qpointer))
- except:
- raise
- finally:
- self.schedule_lock.release()
-
- def remove(self, name):
- """Remove a scheduled task ahead of schedule, and without
- executing it.
-
- :param string name: The name of the task to remove.
- """
- try:
- self.schedule_lock.acquire()
- the_task = None
- for task in self.schedule:
- if task.name == name:
- the_task = task
- if the_task is not None:
- self.schedule.remove(the_task)
- except:
- raise
- finally:
- self.schedule_lock.release()
-
- def quit(self):
- """Shutdown the scheduler."""
- self.run = False
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
deleted file mode 100644
index c2e0f718..00000000
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ /dev/null
@@ -1,1654 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.stanzabase
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- module implements a wrapper layer for XML objects
- that allows them to be treated like dictionaries.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import with_statement, unicode_literals
-
-import copy
-import logging
-import weakref
-from xml.etree import cElementTree as ET
-
-from sleekxmpp.util import safedict
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.xmlstream.tostring import tostring
-from sleekxmpp.thirdparty import OrderedDict
-
-
-log = logging.getLogger(__name__)
-
-
-# Used to check if an argument is an XML object.
-XML_TYPE = type(ET.Element('xml'))
-
-
-XML_NS = 'http://www.w3.org/XML/1998/namespace'
-
-
-def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
- """
- Associate a stanza object as a plugin for another stanza.
-
- >>> from sleekxmpp.xmlstream import register_stanza_plugin
- >>> register_stanza_plugin(Iq, CustomStanza)
-
- Plugin stanzas marked as iterable will be included in the list of
- substanzas for the parent, using ``parent['substanzas']``. If the
- attribute ``plugin_multi_attrib`` was defined for the plugin, then
- the substanza set can be filtered to only instances of the plugin
- class. For example, given a plugin class ``Foo`` with
- ``plugin_multi_attrib = 'foos'`` then::
-
- parent['foos']
-
- would return a collection of all ``Foo`` substanzas.
-
- :param class stanza: The class of the parent stanza.
- :param class plugin: The class of the plugin stanza.
- :param bool iterable: Indicates if the plugin stanza should be
- included in the parent stanza's iterable
- ``'substanzas'`` interface results.
- :param bool overrides: Indicates if the plugin should be allowed
- to override the interface handlers for
- the parent stanza, based on the plugin's
- ``overrides`` field.
-
- .. versionadded:: 1.0-Beta1
- Made ``register_stanza_plugin`` the default name. The prior
- ``registerStanzaPlugin`` function name remains as an alias.
- """
- tag = "{%s}%s" % (plugin.namespace, plugin.name)
-
- # Prevent weird memory reference gotchas by ensuring
- # that the parent stanza class has its own set of
- # plugin info maps and is not using the mappings from
- # an ancestor class (like ElementBase).
- plugin_info = ('plugin_attrib_map', 'plugin_tag_map',
- 'plugin_iterables', 'plugin_overrides')
- for attr in plugin_info:
- info = getattr(stanza, attr)
- setattr(stanza, attr, info.copy())
-
- stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
- stanza.plugin_tag_map[tag] = plugin
-
- if iterable:
- stanza.plugin_iterables.add(plugin)
- if plugin.plugin_multi_attrib:
- multiplugin = multifactory(plugin, plugin.plugin_multi_attrib)
- register_stanza_plugin(stanza, multiplugin)
- if overrides:
- for interface in plugin.overrides:
- stanza.plugin_overrides[interface] = plugin.plugin_attrib
-
-
-# To maintain backwards compatibility for now, preserve the camel case name.
-registerStanzaPlugin = register_stanza_plugin
-
-
-def multifactory(stanza, plugin_attrib):
- """
- Returns a ElementBase class for handling reoccuring child stanzas
- """
-
- def plugin_filter(self):
- return lambda x: isinstance(x, self._multistanza)
-
- def plugin_lang_filter(self, lang):
- return lambda x: isinstance(x, self._multistanza) and \
- x['lang'] == lang
-
- class Multi(ElementBase):
- """
- Template class for multifactory
- """
- def setup(self, xml=None):
- self.xml = ET.Element('')
-
- def get_multi(self, lang=None):
- parent = self.parent()
- if not lang or lang == '*':
- res = filter(plugin_filter(self), parent)
- else:
- res = filter(plugin_filter(self, lang), parent)
- return list(res)
-
- def set_multi(self, val, lang=None):
- parent = self.parent()
- del_multi = getattr(self, 'del_%s' % plugin_attrib)
- del_multi(lang)
- for sub in val:
- parent.append(sub)
-
- def del_multi(self, lang=None):
- parent = self.parent()
- if not lang or lang == '*':
- res = filter(plugin_filter(self), parent)
- else:
- res = filter(plugin_filter(self, lang), parent)
- res = list(res)
- if not res:
- del parent.plugins[(plugin_attrib, None)]
- parent.loaded_plugins.remove(plugin_attrib)
- try:
- parent.xml.remove(self.xml)
- except ValueError:
- pass
- else:
- for stanza in list(res):
- parent.iterables.remove(stanza)
- parent.xml.remove(stanza.xml)
-
- Multi.is_extension = True
- Multi.plugin_attrib = plugin_attrib
- Multi._multistanza = stanza
- Multi.interfaces = set([plugin_attrib])
- Multi.lang_interfaces = set([plugin_attrib])
- setattr(Multi, "get_%s" % plugin_attrib, get_multi)
- setattr(Multi, "set_%s" % plugin_attrib, set_multi)
- setattr(Multi, "del_%s" % plugin_attrib, del_multi)
- return Multi
-
-
-def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
- """Apply the stanza's namespace to elements in an XPath expression.
-
- :param string xpath: The XPath expression to fix with namespaces.
- :param bool split: Indicates if the fixed XPath should be left as a
- list of element names with namespaces. Defaults to
- False, which returns a flat string path.
- :param bool propagate_ns: Overrides propagating parent element
- namespaces to child elements. Useful if
- you wish to simply split an XPath that has
- non-specified namespaces, and child and
- parent namespaces are known not to always
- match. Defaults to True.
- """
- fixed = []
- # Split the XPath into a series of blocks, where a block
- # is started by an element with a namespace.
- ns_blocks = xpath.split('{')
- for ns_block in ns_blocks:
- if '}' in ns_block:
- # Apply the found namespace to following elements
- # that do not have namespaces.
- namespace = ns_block.split('}')[0]
- elements = ns_block.split('}')[1].split('/')
- else:
- # Apply the stanza's namespace to the following
- # elements since no namespace was provided.
- namespace = default_ns
- elements = ns_block.split('/')
-
- for element in elements:
- if element:
- # Skip empty entry artifacts from splitting.
- if propagate_ns and element[0] != '*':
- tag = '{%s}%s' % (namespace, element)
- else:
- tag = element
- fixed.append(tag)
- if split:
- return fixed
- return '/'.join(fixed)
-
-
-class ElementBase(object):
-
- """
- The core of SleekXMPP's stanza XML manipulation and handling is provided
- by ElementBase. ElementBase wraps XML cElementTree objects and enables
- access to the XML contents through dictionary syntax, similar in style
- to the Ruby XMPP library Blather's stanza implementation.
-
- Stanzas are defined by their name, namespace, and interfaces. For
- example, a simplistic Message stanza could be defined as::
-
- >>> class Message(ElementBase):
- ... name = "message"
- ... namespace = "jabber:client"
- ... interfaces = set(('to', 'from', 'type', 'body'))
- ... sub_interfaces = set(('body',))
-
- The resulting Message stanza's contents may be accessed as so::
-
- >>> message['to'] = "user@example.com"
- >>> message['body'] = "Hi!"
- >>> message['body']
- "Hi!"
- >>> del message['body']
- >>> message['body']
- ""
-
- The interface values map to either custom access methods, stanza
- XML attributes, or (if the interface is also in sub_interfaces) the
- text contents of a stanza's subelement.
-
- Custom access methods may be created by adding methods of the
- form "getInterface", "setInterface", or "delInterface", where
- "Interface" is the titlecase version of the interface name.
-
- Stanzas may be extended through the use of plugins. A plugin
- is simply a stanza that has a plugin_attrib value. For example::
-
- >>> class MessagePlugin(ElementBase):
- ... name = "custom_plugin"
- ... namespace = "custom"
- ... interfaces = set(('useful_thing', 'custom'))
- ... plugin_attrib = "custom"
-
- The plugin stanza class must be associated with its intended
- container stanza by using register_stanza_plugin as so::
-
- >>> register_stanza_plugin(Message, MessagePlugin)
-
- The plugin may then be accessed as if it were built-in to the parent
- stanza::
-
- >>> message['custom']['useful_thing'] = 'foo'
-
- If a plugin provides an interface that is the same as the plugin's
- plugin_attrib value, then the plugin's interface may be assigned
- directly from the parent stanza, as shown below, but retrieving
- information will require all interfaces to be used, as so::
-
- >>> # Same as using message['custom']['custom']
- >>> message['custom'] = 'bar'
- >>> # Must use all interfaces
- >>> message['custom']['custom']
- 'bar'
-
- If the plugin sets :attr:`is_extension` to ``True``, then both setting
- and getting an interface value that is the same as the plugin's
- plugin_attrib value will work, as so::
-
- >>> message['custom'] = 'bar' # Using is_extension=True
- >>> message['custom']
- 'bar'
-
-
- :param xml: Initialize the stanza object with an existing XML object.
- :param parent: Optionally specify a parent stanza object will
- contain this substanza.
- """
-
- #: The XML tag name of the element, not including any namespace
- #: prefixes. For example, an :class:`ElementBase` object for
- #: ``<message />`` would use ``name = 'message'``.
- name = 'stanza'
-
- #: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
- #: then ``namespace = "bar"`` should be used. The default namespace
- #: is ``jabber:client`` since this is being used in an XMPP library.
- namespace = 'jabber:client'
-
- #: For :class:`ElementBase` subclasses which are intended to be used
- #: as plugins, the ``plugin_attrib`` value defines the plugin name.
- #: Plugins may be accessed by using the ``plugin_attrib`` value as
- #: the interface. An example using ``plugin_attrib = 'foo'``::
- #:
- #: register_stanza_plugin(Message, FooPlugin)
- #: msg = Message()
- #: msg['foo']['an_interface_from_the_foo_plugin']
- plugin_attrib = 'plugin'
-
- #: For :class:`ElementBase` subclasses that are intended to be an
- #: iterable group of items, the ``plugin_multi_attrib`` value defines
- #: an interface for the parent stanza which returns the entire group
- #: of matching substanzas. So the following are equivalent::
- #:
- #: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
- #: parent['foos']
- #: filter(isinstance(item, Foo), parent['substanzas'])
- plugin_multi_attrib = ''
-
- #: The set of keys that the stanza provides for accessing and
- #: manipulating the underlying XML object. This set may be augmented
- #: with the :attr:`plugin_attrib` value of any registered
- #: stanza plugins.
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
-
- #: A subset of :attr:`interfaces` which maps interfaces to direct
- #: subelements of the underlying XML object. Using this set, the text
- #: of these subelements may be set, retrieved, or removed without
- #: needing to define custom methods.
- sub_interfaces = set()
-
- #: A subset of :attr:`interfaces` which maps the presence of
- #: subelements to boolean values. Using this set allows for quickly
- #: checking for the existence of empty subelements like ``<required />``.
- #:
- #: .. versionadded:: 1.1
- bool_interfaces = set()
-
- #: .. versionadded:: 1.1.2
- lang_interfaces = set()
-
- #: In some cases you may wish to override the behaviour of one of the
- #: parent stanza's interfaces. The ``overrides`` list specifies the
- #: interface name and access method to be overridden. For example,
- #: to override setting the parent's ``'condition'`` interface you
- #: would use::
- #:
- #: overrides = ['set_condition']
- #:
- #: Getting and deleting the ``'condition'`` interface would not
- #: be affected.
- #:
- #: .. versionadded:: 1.0-Beta5
- overrides = []
-
- #: If you need to add a new interface to an existing stanza, you
- #: can create a plugin and set ``is_extension = True``. Be sure
- #: to set the :attr:`plugin_attrib` value to the desired interface
- #: name, and that it is the only interface listed in
- #: :attr:`interfaces`. Requests for the new interface from the
- #: parent stanza will be passed to the plugin directly.
- #:
- #: .. versionadded:: 1.0-Beta5
- is_extension = False
-
- #: A map of interface operations to the overriding functions.
- #: For example, after overriding the ``set`` operation for
- #: the interface ``body``, :attr:`plugin_overrides` would be::
- #:
- #: {'set_body': <some function>}
- #:
- #: .. versionadded: 1.0-Beta5
- plugin_overrides = {}
-
- #: A mapping of the :attr:`plugin_attrib` values of registered
- #: plugins to their respective classes.
- plugin_attrib_map = {}
-
- #: A mapping of root element tag names (in ``'{namespace}elementname'``
- #: format) to the plugin classes responsible for them.
- plugin_tag_map = {}
-
- #: The set of stanza classes that can be iterated over using
- #: the 'substanzas' interface. Classes are added to this set
- #: when registering a plugin with ``iterable=True``::
- #:
- #: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
- #:
- #: .. versionadded:: 1.0-Beta5
- plugin_iterables = set()
-
- #: A deprecated version of :attr:`plugin_iterables` that remains
- #: for backward compatibility. It required a parent stanza to
- #: know beforehand what stanza classes would be iterable::
- #:
- #: class DiscoItem(ElementBase):
- #: ...
- #:
- #: class DiscoInfo(ElementBase):
- #: subitem = (DiscoItem, )
- #: ...
- #:
- #: .. deprecated:: 1.0-Beta5
- subitem = set()
-
- #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
- xml_ns = XML_NS
-
- def __init__(self, xml=None, parent=None):
- self._index = 0
-
- #: The underlying XML object for the stanza. It is a standard
- #: :class:`xml.etree.cElementTree` object.
- self.xml = xml
-
- #: An ordered dictionary of plugin stanzas, mapped by their
- #: :attr:`plugin_attrib` value.
- self.plugins = OrderedDict()
- self.loaded_plugins = set()
-
- #: A list of child stanzas whose class is included in
- #: :attr:`plugin_iterables`.
- self.iterables = []
-
- #: The name of the tag for the stanza's root element. It is the
- #: same as calling :meth:`tag_name()` and is formatted as
- #: ``'{namespace}elementname'``.
- self.tag = self.tag_name()
-
- #: A :class:`weakref.weakref` to the parent stanza, if there is one.
- #: If not, then :attr:`parent` is ``None``.
- self.parent = None
- if parent is not None:
- if not isinstance(parent, weakref.ReferenceType):
- self.parent = weakref.ref(parent)
- else:
- self.parent = parent
-
- if self.subitem is not None:
- for sub in self.subitem:
- self.plugin_iterables.add(sub)
-
- if self.setup(xml):
- # If we generated our own XML, then everything is ready.
- return
-
- # Initialize values using provided XML
- for child in self.xml:
- if child.tag in self.plugin_tag_map:
- plugin_class = self.plugin_tag_map[child.tag]
- self.init_plugin(plugin_class.plugin_attrib,
- existing_xml=child,
- reuse=False)
-
- def setup(self, xml=None):
- """Initialize the stanza's XML contents.
-
- Will return ``True`` if XML was generated according to the stanza's
- definition instead of building a stanza object from an existing
- XML object.
-
- :param xml: An existing XML object to use for the stanza's content
- instead of generating new XML.
- """
- if self.xml is None:
- self.xml = xml
-
- last_xml = self.xml
- if self.xml is None:
- # Generate XML from the stanza definition
- for ename in self.name.split('/'):
- new = ET.Element("{%s}%s" % (self.namespace, ename))
- if self.xml is None:
- self.xml = new
- else:
- last_xml.append(new)
- last_xml = new
- if self.parent is not None:
- self.parent().xml.append(self.xml)
-
- # We had to generate XML
- return True
- else:
- # We did not generate XML
- return False
-
- def enable(self, attrib, lang=None):
- """Enable and initialize a stanza plugin.
-
- Alias for :meth:`init_plugin`.
-
- :param string attrib: The :attr:`plugin_attrib` value of the
- plugin to enable.
- """
- return self.init_plugin(attrib, lang)
-
- def _get_plugin(self, name, lang=None, check=False):
- if lang is None:
- lang = self.get_lang()
-
- if name not in self.plugin_attrib_map:
- return None
-
- plugin_class = self.plugin_attrib_map[name]
-
- if plugin_class.is_extension:
- if (name, None) in self.plugins:
- return self.plugins[(name, None)]
- else:
- return None if check else self.init_plugin(name, lang)
- else:
- if (name, lang) in self.plugins:
- return self.plugins[(name, lang)]
- else:
- return None if check else self.init_plugin(name, lang)
-
- def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
- """Enable and initialize a stanza plugin.
-
- :param string attrib: The :attr:`plugin_attrib` value of the
- plugin to enable.
- """
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- plugin_class = self.plugin_attrib_map[attrib]
-
- if plugin_class.is_extension and (attrib, None) in self.plugins:
- return self.plugins[(attrib, None)]
- if reuse and (attrib, lang) in self.plugins:
- return self.plugins[(attrib, lang)]
-
- plugin = plugin_class(parent=self, xml=existing_xml)
-
- if plugin.is_extension:
- self.plugins[(attrib, None)] = plugin
- else:
- if lang != default_lang:
- plugin['lang'] = lang
- self.plugins[(attrib, lang)] = plugin
-
- if plugin_class in self.plugin_iterables:
- self.iterables.append(plugin)
- if plugin_class.plugin_multi_attrib:
- self.init_plugin(plugin_class.plugin_multi_attrib)
-
- self.loaded_plugins.add(attrib)
-
- return plugin
-
- def _get_stanza_values(self):
- """Return A JSON/dictionary version of the XML content
- exposed through the stanza's interfaces::
-
- >>> msg = Message()
- >>> msg.values
- {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
- 'to': , 'type': 'normal', 'id': '', 'subject': ''}
-
- Likewise, assigning to :attr:`values` will change the XML
- content::
-
- >>> msg = Message()
- >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
- >>> msg
- '<message to="user@example.com"><body>Hi!</body></message>'
-
- .. versionadded:: 1.0-Beta1
- """
- values = OrderedDict()
- values['lang'] = self['lang']
- for interface in self.interfaces:
- if isinstance(self[interface], JID):
- values[interface] = self[interface].jid
- else:
- values[interface] = self[interface]
- if interface in self.lang_interfaces:
- values['%s|*' % interface] = self['%s|*' % interface]
- for plugin, stanza in self.plugins.items():
- lang = stanza['lang']
- if lang:
- values['%s|%s' % (plugin[0], lang)] = stanza.values
- else:
- values[plugin[0]] = stanza.values
- if self.iterables:
- iterables = []
- for stanza in self.iterables:
- iterables.append(stanza.values)
- iterables[-1]['__childtag__'] = stanza.tag
- values['substanzas'] = iterables
- return values
-
- def _set_stanza_values(self, values):
- """Set multiple stanza interface values using a dictionary.
-
- Stanza plugin values may be set using nested dictionaries.
-
- :param values: A dictionary mapping stanza interface with values.
- Plugin interfaces may accept a nested dictionary that
- will be used recursively.
-
- .. versionadded:: 1.0-Beta1
- """
- iterable_interfaces = [p.plugin_attrib for \
- p in self.plugin_iterables]
-
- if 'lang' in values:
- self['lang'] = values['lang']
-
- if 'substanzas' in values:
- # Remove existing substanzas
- for stanza in self.iterables:
- try:
- self.xml.remove(stanza.xml)
- except ValueError:
- pass
- self.iterables = []
-
- # Add new substanzas
- for subdict in values['substanzas']:
- if '__childtag__' in subdict:
- for subclass in self.plugin_iterables:
- child_tag = "{%s}%s" % (subclass.namespace,
- subclass.name)
- if subdict['__childtag__'] == child_tag:
- sub = subclass(parent=self)
- sub.values = subdict
- self.iterables.append(sub)
-
- for interface, value in values.items():
- full_interface = interface
- interface_lang = ('%s|' % interface).split('|')
- interface = interface_lang[0]
- lang = interface_lang[1] or self.get_lang()
-
- if interface == 'lang':
- continue
- elif interface == 'substanzas':
- continue
- elif interface in self.interfaces:
- self[full_interface] = value
- elif interface in self.plugin_attrib_map:
- if interface not in iterable_interfaces:
- plugin = self._get_plugin(interface, lang)
- if plugin:
- plugin.values = value
- return self
-
- def __getitem__(self, attrib):
- """Return the value of a stanza interface using dict-like syntax.
-
- Example::
-
- >>> msg['body']
- 'Message contents'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a ``get_attrib``
- method (or ``get_foo`` where the interface is named ``'foo'``, etc).
-
- The search order for interface value retrieval for an interface
- named ``'foo'`` is:
-
- 1. The list of substanzas (``'substanzas'``)
- 2. The result of calling the ``get_foo`` override handler.
- 3. The result of calling ``get_foo``.
- 4. The result of calling ``getFoo``.
- 5. The contents of the ``foo`` subelement, if ``foo`` is listed
- in :attr:`sub_interfaces`.
- 6. True or False depending on the existence of a ``foo``
- subelement and ``foo`` is in :attr:`bool_interfaces`.
- 7. The value of the ``foo`` attribute of the XML object.
- 8. The plugin named ``'foo'``
- 9. An empty string.
-
- :param string attrib: The name of the requested stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib == 'substanzas':
- return self.iterables
- elif attrib in self.interfaces or attrib == 'lang':
- get_method = "get_%s" % attrib.lower()
- get_method2 = "get%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(get_method, None)
- if name:
- plugin = self._get_plugin(name, lang)
- if plugin:
- handler = getattr(plugin, get_method, None)
- if handler:
- return handler(**kwargs)
-
- if hasattr(self, get_method):
- return getattr(self, get_method)(**kwargs)
- elif hasattr(self, get_method2):
- return getattr(self, get_method2)(**kwargs)
- else:
- if attrib in self.sub_interfaces:
- return self._get_sub_text(attrib, lang=lang)
- elif attrib in self.bool_interfaces:
- elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
- return elem is not None
- else:
- return self._get_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang)
- if plugin and plugin.is_extension:
- return plugin[full_attrib]
- return plugin
- else:
- return ''
-
- def __setitem__(self, attrib, value):
- """Set the value of a stanza interface using dictionary-like syntax.
-
- Example::
-
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a ``set_attrib``
- method (or ``set_foo`` where the interface is named ``'foo'``, etc).
-
- The effect of interface value assignment for an interface
- named ``'foo'`` will be one of:
-
- 1. Delete the interface's contents if the value is None.
- 2. Call the ``set_foo`` override handler, if it exists.
- 3. Call ``set_foo``, if it exists.
- 4. Call ``setFoo``, if it exists.
- 5. Set the text of a ``foo`` element, if ``'foo'`` is
- in :attr:`sub_interfaces`.
- 6. Add or remove an empty subelement ``foo``
- if ``foo`` is in :attr:`bool_interfaces`.
- 7. Set the value of a top level XML attribute named ``foo``.
- 8. Attempt to pass the value to a plugin named ``'foo'`` using
- the plugin's ``'foo'`` interface.
- 9. Do nothing.
-
- :param string attrib: The name of the stanza interface to modify.
- :param value: The new value of the stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib in self.interfaces or attrib == 'lang':
- if value is not None:
- set_method = "set_%s" % attrib.lower()
- set_method2 = "set%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(set_method, None)
- if name:
- plugin = self._get_plugin(name, lang)
- if plugin:
- handler = getattr(plugin, set_method, None)
- if handler:
- return handler(value, **kwargs)
-
- if hasattr(self, set_method):
- getattr(self, set_method)(value, **kwargs)
- elif hasattr(self, set_method2):
- getattr(self, set_method2)(value, **kwargs)
- else:
- if attrib in self.sub_interfaces:
- if lang == '*':
- return self._set_all_sub_text(attrib,
- value,
- lang='*')
- return self._set_sub_text(attrib, text=value,
- lang=lang)
- elif attrib in self.bool_interfaces:
- if value:
- return self._set_sub_text(attrib, '',
- keep=True,
- lang=lang)
- else:
- return self._set_sub_text(attrib, '',
- keep=False,
- lang=lang)
- else:
- self._set_attr(attrib, value)
- else:
- self.__delitem__(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang)
- if plugin:
- plugin[full_attrib] = value
- return self
-
- def __delitem__(self, attrib):
- """Delete the value of a stanza interface using dict-like syntax.
-
- Example::
-
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
- >>> del msg['body']
- >>> msg['body']
- ''
-
- Stanza interfaces are typically mapped directly to the underlyig XML
- object, but can be overridden by the presence of a ``del_attrib``
- method (or ``del_foo`` where the interface is named ``'foo'``, etc).
-
- The effect of deleting a stanza interface value named ``foo`` will be
- one of:
-
- 1. Call ``del_foo`` override handler, if it exists.
- 2. Call ``del_foo``, if it exists.
- 3. Call ``delFoo``, if it exists.
- 4. Delete ``foo`` element, if ``'foo'`` is in
- :attr:`sub_interfaces`.
- 5. Remove ``foo`` element if ``'foo'`` is in
- :attr:`bool_interfaces`.
- 6. Delete top level XML attribute named ``foo``.
- 7. Remove the ``foo`` plugin, if it was loaded.
- 8. Do nothing.
-
- :param attrib: The name of the affected stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib in self.interfaces or attrib == 'lang':
- del_method = "del_%s" % attrib.lower()
- del_method2 = "del%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(del_method, None)
- if name:
- plugin = self._get_plugin(attrib, lang)
- if plugin:
- handler = getattr(plugin, del_method, None)
- if handler:
- return handler(**kwargs)
-
- if hasattr(self, del_method):
- getattr(self, del_method)(**kwargs)
- elif hasattr(self, del_method2):
- getattr(self, del_method2)(**kwargs)
- else:
- if attrib in self.sub_interfaces:
- return self._del_sub(attrib, lang=lang)
- elif attrib in self.bool_interfaces:
- return self._del_sub(attrib, lang=lang)
- else:
- self._del_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang, check=True)
- if not plugin:
- return self
- if plugin.is_extension:
- del plugin[full_attrib]
- del self.plugins[(attrib, None)]
- else:
- del self.plugins[(attrib, plugin['lang'])]
- self.loaded_plugins.remove(attrib)
- try:
- self.xml.remove(plugin.xml)
- except ValueError:
- pass
- return self
-
- def _set_attr(self, name, value):
- """Set the value of a top level attribute of the XML object.
-
- If the new value is None or an empty string, then the attribute will
- be removed.
-
- :param name: The name of the attribute.
- :param value: The new value of the attribute, or None or '' to
- remove it.
- """
- if value is None or value == '':
- self.__delitem__(name)
- else:
- self.xml.attrib[name] = value
-
- def _del_attr(self, name):
- """Remove a top level attribute of the XML object.
-
- :param name: The name of the attribute.
- """
- if name in self.xml.attrib:
- del self.xml.attrib[name]
-
- def _get_attr(self, name, default=''):
- """Return the value of a top level attribute of the XML object.
-
- In case the attribute has not been set, a default value can be
- returned instead. An empty string is returned if no other default
- is supplied.
-
- :param name: The name of the attribute.
- :param default: Optional value to return if the attribute has not
- been set. An empty string is returned otherwise.
- """
- return self.xml.attrib.get(name, default)
-
- def _get_sub_text(self, name, default='', lang=None):
- """Return the text contents of a sub element.
-
- In case the element does not exist, or it has no textual content,
- a default value can be returned instead. An empty string is returned
- if no other default is supplied.
-
- :param name: The name or XPath expression of the element.
- :param default: Optional default to return if the element does
- not exists. An empty string is returned otherwise.
- """
- name = self._fix_ns(name)
- if lang == '*':
- return self._get_all_sub_text(name, default, None)
-
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- stanzas = self.xml.findall(name)
- if not stanzas:
- return default
- for stanza in stanzas:
- if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang:
- if stanza.text is None:
- return default
- return stanza.text
- return default
-
- def _get_all_sub_text(self, name, default='', lang=None):
- name = self._fix_ns(name)
-
- default_lang = self.get_lang()
- results = OrderedDict()
- stanzas = self.xml.findall(name)
- if stanzas:
- for stanza in stanzas:
- stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
- default_lang)
- if not lang or lang == '*' or stanza_lang == lang:
- results[stanza_lang] = stanza.text
- return results
-
- def _set_sub_text(self, name, text=None, keep=False, lang=None):
- """Set the text contents of a sub element.
-
- In case the element does not exist, a element will be created,
- and its text contents will be set.
-
- If the text is set to an empty string, or None, then the
- element will be removed, unless keep is set to True.
-
- :param name: The name or XPath expression of the element.
- :param text: The new textual content of the element. If the text
- is an empty string or None, the element will be removed
- unless the parameter keep is True.
- :param keep: Indicates if the element should be kept if its text is
- removed. Defaults to False.
- """
- default_lang = self.get_lang()
- if lang is None:
- lang = default_lang
-
- if not text and not keep:
- return self._del_sub(name, lang=lang)
-
- path = self._fix_ns(name, split=True)
- name = path[-1]
- parent = self.xml
-
- # The first goal is to find the parent of the subelement, or, if
- # we can't find that, the closest grandparent element.
- missing_path = []
- search_order = path[:-1]
- while search_order:
- parent = self.xml.find('/'.join(search_order))
- ename = search_order.pop()
- if parent is not None:
- break
- else:
- missing_path.append(ename)
- missing_path.reverse()
-
- # Find all existing elements that match the desired
- # element path (there may be multiples due to different
- # languages values).
- if parent is not None:
- elements = self.xml.findall('/'.join(path))
- else:
- parent = self.xml
- elements = []
-
- # Insert the remaining grandparent elements that don't exist yet.
- for ename in missing_path:
- element = ET.Element(ename)
- parent.append(element)
- parent = element
-
- # Re-use an existing element with the proper language, if one exists.
- for element in elements:
- elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
- if not lang and elang == default_lang or lang and lang == elang:
- element.text = text
- return element
-
- # No useable element exists, so create a new one.
- element = ET.Element(name)
- element.text = text
- if lang and lang != default_lang:
- element.attrib['{%s}lang' % XML_NS] = lang
- parent.append(element)
- return element
-
- def _set_all_sub_text(self, name, values, keep=False, lang=None):
- self._del_sub(name, lang)
- for value_lang, value in values.items():
- if not lang or lang == '*' or value_lang == lang:
- self._set_sub_text(name, text=value,
- keep=keep,
- lang=value_lang)
-
- def _del_sub(self, name, all=False, lang=None):
- """Remove sub elements that match the given name or XPath.
-
- If the element is in a path, then any parent elements that become
- empty after deleting the element may also be deleted if requested
- by setting all=True.
-
- :param name: The name or XPath expression for the element(s) to remove.
- :param bool all: If True, remove all empty elements in the path to the
- deleted element. Defaults to False.
- """
- path = self._fix_ns(name, split=True)
- original_target = path[-1]
-
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- for level, _ in enumerate(path):
- # Generate the paths to the target elements and their parent.
- element_path = "/".join(path[:len(path) - level])
- parent_path = "/".join(path[:len(path) - level - 1])
-
- elements = self.xml.findall(element_path)
- parent = self.xml.find(parent_path)
-
- if elements:
- if parent is None:
- parent = self.xml
- for element in elements:
- if element.tag == original_target or not list(element):
- # Only delete the originally requested elements, and
- # any parent elements that have become empty.
- elem_lang = element.attrib.get('{%s}lang' % XML_NS,
- default_lang)
- if lang == '*' or elem_lang == lang:
- parent.remove(element)
- if not all:
- # If we don't want to delete elements up the tree, stop
- # after deleting the first level of elements.
- return
-
- def match(self, xpath):
- """Compare a stanza object with an XPath-like expression.
-
- If the XPath matches the contents of the stanza object, the match
- is successful.
-
- The XPath expression may include checks for stanza attributes.
- For example::
-
- 'presence@show=xa@priority=2/status'
-
- Would match a presence stanza whose show value is set to ``'xa'``,
- has a priority value of ``'2'``, and has a status element.
-
- :param string xpath: The XPath expression to check against. It
- may be either a string or a list of element
- names with attribute checks.
- """
- if not isinstance(xpath, list):
- xpath = self._fix_ns(xpath, split=True, propagate_ns=False)
-
- # Extract the tag name and attribute checks for the first XPath node.
- components = xpath[0].split('@')
- tag = components[0]
- attributes = components[1:]
-
- if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
- tag not in self.loaded_plugins and tag not in self.plugin_attrib:
- # The requested tag is not in this stanza, so no match.
- return False
-
- # Check the rest of the XPath against any substanzas.
- matched_substanzas = False
- for substanza in self.iterables:
- if xpath[1:] == []:
- break
- matched_substanzas = substanza.match(xpath[1:])
- if matched_substanzas:
- break
-
- # Check attribute values.
- for attribute in attributes:
- name, value = attribute.split('=')
- if self[name] != value:
- return False
-
- # Check sub interfaces.
- if len(xpath) > 1:
- next_tag = xpath[1]
- if next_tag in self.sub_interfaces and self[next_tag]:
- return True
-
- # Attempt to continue matching the XPath using the stanza's plugins.
- if not matched_substanzas and len(xpath) > 1:
- # Convert {namespace}tag@attribs to just tag
- next_tag = xpath[1].split('@')[0].split('}')[-1]
- langs = [name[1] for name in self.plugins if name[0] == next_tag]
- for lang in langs:
- plugin = self._get_plugin(next_tag, lang)
- if plugin and plugin.match(xpath[1:]):
- return True
- return False
-
- # Everything matched.
- return True
-
- def find(self, xpath):
- """Find an XML object in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- .. note::
-
- Matching on attribute values is not supported in Python 2.6
- or Python 3.1
-
- :param string xpath: An XPath expression matching a single
- desired element.
- """
- return self.xml.find(xpath)
-
- def findall(self, xpath):
- """Find multiple XML objects in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- .. note::
-
- Matching on attribute values is not supported in Python 2.6
- or Python 3.1.
-
- :param string xpath: An XPath expression matching multiple
- desired elements.
- """
- return self.xml.findall(xpath)
-
- def get(self, key, default=None):
- """Return the value of a stanza interface.
-
- If the found value is None or an empty string, return the supplied
- default value.
-
- Allows stanza objects to be used like dictionaries.
-
- :param string key: The name of the stanza interface to check.
- :param default: Value to return if the stanza interface has a value
- of ``None`` or ``""``. Will default to returning None.
- """
- value = self[key]
- if value is None or value == '':
- return default
- return value
-
- def keys(self):
- """Return the names of all stanza interfaces provided by the
- stanza object.
-
- Allows stanza objects to be used like dictionaries.
- """
- out = []
- out += [x for x in self.interfaces]
- out += [x for x in self.loaded_plugins]
- out.append('lang')
- if self.iterables:
- out.append('substanzas')
- return out
-
- def append(self, item):
- """Append either an XML object or a substanza to this stanza object.
-
- If a substanza object is appended, it will be added to the list
- of iterable stanzas.
-
- Allows stanza objects to be used like lists.
-
- :param item: Either an XML object or a stanza object to add to
- this stanza's contents.
- """
- if not isinstance(item, ElementBase):
- if type(item) == XML_TYPE:
- return self.appendxml(item)
- else:
- raise TypeError
- self.xml.append(item.xml)
- self.iterables.append(item)
- if item.__class__ in self.plugin_iterables:
- if item.__class__.plugin_multi_attrib:
- self.init_plugin(item.__class__.plugin_multi_attrib)
- elif item.__class__ == self.plugin_tag_map.get(item.tag_name(), None):
- self.init_plugin(item.plugin_attrib,
- existing_xml=item.xml,
- reuse=False)
- return self
-
- def appendxml(self, xml):
- """Append an XML object to the stanza's XML.
-
- The added XML will not be included in the list of
- iterable substanzas.
-
- :param XML xml: The XML object to add to the stanza.
- """
- self.xml.append(xml)
- return self
-
- def pop(self, index=0):
- """Remove and return the last substanza in the list of
- iterable substanzas.
-
- Allows stanza objects to be used like lists.
-
- :param int index: The index of the substanza to remove.
- """
- substanza = self.iterables.pop(index)
- self.xml.remove(substanza.xml)
- return substanza
-
- def next(self):
- """Return the next iterable substanza."""
- return self.__next__()
-
- def clear(self):
- """Remove all XML element contents and plugins.
-
- Any attribute values will be preserved.
- """
- for child in list(self.xml):
- self.xml.remove(child)
-
- for plugin in list(self.plugins.keys()):
- del self.plugins[plugin]
- return self
-
- @classmethod
- def tag_name(cls):
- """Return the namespaced name of the stanza's root element.
-
- The format for the tag name is::
-
- '{namespace}elementname'
-
- For example, for the stanza ``<foo xmlns="bar" />``,
- ``stanza.tag_name()`` would return ``"{bar}foo"``.
- """
- return "{%s}%s" % (cls.namespace, cls.name)
-
- def get_lang(self, lang=None):
- result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
- if not result and self.parent and self.parent():
- return self.parent()['lang']
- return result
-
- def set_lang(self, lang):
- self.del_lang()
- attr = '{%s}lang' % XML_NS
- if lang:
- self.xml.attrib[attr] = lang
-
- def del_lang(self):
- attr = '{%s}lang' % XML_NS
- if attr in self.xml.attrib:
- del self.xml.attrib[attr]
-
- @property
- def attrib(self):
- """Return the stanza object itself.
-
- Older implementations of stanza objects used XML objects directly,
- requiring the use of ``.attrib`` to access attribute values.
-
- Use of the dictionary syntax with the stanza object itself for
- accessing stanza interfaces is preferred.
-
- .. deprecated:: 1.0
- """
- return self
-
- def _fix_ns(self, xpath, split=False, propagate_ns=True):
- return fix_ns(xpath, split=split,
- propagate_ns=propagate_ns,
- default_ns=self.namespace)
-
- def __eq__(self, other):
- """Compare the stanza object with another to test for equality.
-
- Stanzas are equal if their interfaces return the same values,
- and if they are both instances of ElementBase.
-
- :param ElementBase other: The stanza object to compare against.
- """
- if not isinstance(other, ElementBase):
- return False
-
- # Check that this stanza is a superset of the other stanza.
- values = self.values
- for key in other.keys():
- if key not in values or values[key] != other[key]:
- return False
-
- # Check that the other stanza is a superset of this stanza.
- values = other.values
- for key in self.keys():
- if key not in values or values[key] != self[key]:
- return False
-
- # Both stanzas are supersets of each other, therefore they
- # must be equal.
- return True
-
- def __ne__(self, other):
- """Compare the stanza object with another to test for inequality.
-
- Stanzas are not equal if their interfaces return different values,
- or if they are not both instances of ElementBase.
-
- :param ElementBase other: The stanza object to compare against.
- """
- return not self.__eq__(other)
-
- def __bool__(self):
- """Stanza objects should be treated as True in boolean contexts.
-
- Python 3.x version.
- """
- return True
-
- def __nonzero__(self):
- """Stanza objects should be treated as True in boolean contexts.
-
- Python 2.x version.
- """
- return True
-
- def __len__(self):
- """Return the number of iterable substanzas in this stanza."""
- return len(self.iterables)
-
- def __iter__(self):
- """Return an iterator object for the stanza's substanzas.
-
- The iterator is the stanza object itself. Attempting to use two
- iterators on the same stanza at the same time is discouraged.
- """
- self._index = 0
- return self
-
- def __next__(self):
- """Return the next iterable substanza."""
- self._index += 1
- if self._index > len(self.iterables):
- self._index = 0
- raise StopIteration
- return self.iterables[self._index - 1]
-
- def __copy__(self):
- """Return a copy of the stanza object that does not share the same
- underlying XML object.
- """
- return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
-
- def __str__(self, top_level_ns=True):
- """Return a string serialization of the underlying XML object.
-
- .. seealso:: :ref:`tostring`
-
- :param bool top_level_ns: Display the top-most namespace.
- Defaults to True.
- """
- return tostring(self.xml, xmlns='',
- top_level=True)
-
- def __repr__(self):
- """Use the stanza's serialized XML as its representation."""
- return self.__str__()
-
-
-class StanzaBase(ElementBase):
-
- """
- StanzaBase provides the foundation for all other stanza objects used
- by SleekXMPP, and defines a basic set of interfaces common to nearly
- all stanzas. These interfaces are the ``'id'``, ``'type'``, ``'to'``,
- and ``'from'`` attributes. An additional interface, ``'payload'``, is
- available to access the XML contents of the stanza. Most stanza objects
- will provided more specific interfaces, however.
-
- **Stanza Interfaces:**
-
- :id: An optional id value that can be used to associate stanzas
- :to: A JID object representing the recipient's JID.
- :from: A JID object representing the sender's JID.
- with their replies.
- :type: The type of stanza, typically will be ``'normal'``,
- ``'error'``, ``'get'``, or ``'set'``, etc.
- :payload: The XML contents of the stanza.
-
- :param XMLStream stream: Optional :class:`sleekxmpp.xmlstream.XMLStream`
- object responsible for sending this stanza.
- :param XML xml: Optional XML contents to initialize stanza values.
- :param string stype: Optional stanza type value.
- :param sto: Optional string or :class:`sleekxmpp.xmlstream.JID`
- object of the recipient's JID.
- :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
- object of the sender's JID.
- :param string sid: Optional ID value for the stanza.
- :param parent: Optionally specify a parent stanza object will
- contain this substanza.
- """
-
- #: The default XMPP client namespace
- namespace = 'jabber:client'
-
- #: There is a small set of attributes which apply to all XMPP stanzas:
- #: the stanza type, the to and from JIDs, the stanza ID, and, especially
- #: in the case of an Iq stanza, a payload.
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
-
- #: A basic set of allowed values for the ``'type'`` interface.
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
-
- def __init__(self, stream=None, xml=None, stype=None,
- sto=None, sfrom=None, sid=None, parent=None):
- self.stream = stream
- if stream is not None:
- self.namespace = stream.default_ns
- ElementBase.__init__(self, xml, parent)
- if stype is not None:
- self['type'] = stype
- if sto is not None:
- self['to'] = sto
- if sfrom is not None:
- self['from'] = sfrom
- if sid is not None:
- self['id'] = sid
- self.tag = "{%s}%s" % (self.namespace, self.name)
-
- def set_type(self, value):
- """Set the stanza's ``'type'`` attribute.
-
- Only type values contained in :attr:`types` are accepted.
-
- :param string value: One of the values contained in :attr:`types`
- """
- if value in self.types:
- self.xml.attrib['type'] = value
- return self
-
- def get_to(self):
- """Return the value of the stanza's ``'to'`` attribute."""
- return JID(self._get_attr('to'))
-
- def set_to(self, value):
- """Set the ``'to'`` attribute of the stanza.
-
- :param value: A string or :class:`sleekxmpp.xmlstream.JID` object
- representing the recipient's JID.
- """
- return self._set_attr('to', str(value))
-
- def get_from(self):
- """Return the value of the stanza's ``'from'`` attribute."""
- return JID(self._get_attr('from'))
-
- def set_from(self, value):
- """Set the 'from' attribute of the stanza.
-
- Arguments:
- from -- A string or JID object representing the sender's JID.
- """
- return self._set_attr('from', str(value))
-
- def get_payload(self):
- """Return a list of XML objects contained in the stanza."""
- return list(self.xml)
-
- def set_payload(self, value):
- """Add XML content to the stanza.
-
- :param value: Either an XML or a stanza object, or a list
- of XML or stanza objects.
- """
- if not isinstance(value, list):
- value = [value]
- for val in value:
- self.append(val)
- return self
-
- def del_payload(self):
- """Remove the XML contents of the stanza."""
- self.clear()
- return self
-
- def reply(self, clear=True):
- """Prepare the stanza for sending a reply.
-
- Swaps the ``'from'`` and ``'to'`` attributes.
-
- If ``clear=True``, then also remove the stanza's
- contents to make room for the reply content.
-
- For client streams, the ``'from'`` attribute is removed.
-
- :param bool clear: Indicates if the stanza's contents should be
- removed. Defaults to ``True``.
- """
- # if it's a component, use from
- if self.stream and hasattr(self.stream, "is_component") and \
- self.stream.is_component:
- self['from'], self['to'] = self['to'], self['from']
- else:
- self['to'] = self['from']
- del self['from']
- if clear:
- self.clear()
- return self
-
- def error(self):
- """Set the stanza's type to ``'error'``."""
- self['type'] = 'error'
- return self
-
- def unhandled(self):
- """Called if no handlers have been registered to process this stanza.
-
- Meant to be overridden.
- """
- pass
-
- def exception(self, e):
- """Handle exceptions raised during stanza processing.
-
- Meant to be overridden.
- """
- log.exception('Error handling {%s}%s stanza', self.namespace,
- self.name)
-
- def send(self, now=False):
- """Queue the stanza to be sent on the XML stream.
-
- :param bool now: Indicates if the queue should be skipped and the
- stanza sent immediately. Useful for stream
- initialization. Defaults to ``False``.
- """
- self.stream.send(self, now=now)
-
- def __copy__(self):
- """Return a copy of the stanza object that does not share the
- same underlying XML object, but does share the same XML stream.
- """
- return self.__class__(xml=copy.deepcopy(self.xml),
- stream=self.stream)
-
- def __str__(self, top_level_ns=False):
- """Serialize the stanza's XML to a string.
-
- :param bool top_level_ns: Display the top-most namespace.
- Defaults to ``False``.
- """
- xmlns = self.stream.default_ns if self.stream else ''
- return tostring(self.xml, xmlns=xmlns,
- stream=self.stream,
- top_level=(self.stream is None))
-
-
-#: A JSON/dictionary version of the XML content exposed through
-#: the stanza interfaces::
-#:
-#: >>> msg = Message()
-#: >>> msg.values
-#: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
-#: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
-#:
-#: Likewise, assigning to the :attr:`values` will change the XML
-#: content::
-#:
-#: >>> msg = Message()
-#: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
-#: >>> msg
-#: '<message to="user@example.com"><body>Hi!</body></message>'
-#:
-#: Child stanzas are exposed as nested dictionaries.
-ElementBase.values = property(ElementBase._get_stanza_values,
- ElementBase._set_stanza_values)
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-ElementBase.initPlugin = ElementBase.init_plugin
-ElementBase._getAttr = ElementBase._get_attr
-ElementBase._setAttr = ElementBase._set_attr
-ElementBase._delAttr = ElementBase._del_attr
-ElementBase._getSubText = ElementBase._get_sub_text
-ElementBase._setSubText = ElementBase._set_sub_text
-ElementBase._delSub = ElementBase._del_sub
-ElementBase.getStanzaValues = ElementBase._get_stanza_values
-ElementBase.setStanzaValues = ElementBase._set_stanza_values
-
-StanzaBase.setType = StanzaBase.set_type
-StanzaBase.getTo = StanzaBase.get_to
-StanzaBase.setTo = StanzaBase.set_to
-StanzaBase.getFrom = StanzaBase.get_from
-StanzaBase.setFrom = StanzaBase.set_from
-StanzaBase.getPayload = StanzaBase.get_payload
-StanzaBase.setPayload = StanzaBase.set_payload
-StanzaBase.delPayload = StanzaBase.del_payload
diff --git a/sleekxmpp/xmlstream/tostring.py b/sleekxmpp/xmlstream/tostring.py
deleted file mode 100644
index c49abd3e..00000000
--- a/sleekxmpp/xmlstream/tostring.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.tostring
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module converts XML objects into Unicode strings and
- intelligently includes namespaces only when necessary to
- keep the output readable.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import unicode_literals
-
-import sys
-
-if sys.version_info < (3, 0):
- import types
-
-
-XML_NS = 'http://www.w3.org/XML/1998/namespace'
-
-
-def tostring(xml=None, xmlns='', stream=None, outbuffer='',
- top_level=False, open_only=False, namespaces=None):
- """Serialize an XML object to a Unicode string.
-
- If an outer xmlns is provided using ``xmlns``, then the current element's
- namespace will not be included if it matches the outer namespace. An
- exception is made for elements that have an attached stream, and appear
- at the stream root.
-
- :param XML xml: The XML object to serialize.
- :param string xmlns: Optional namespace of an element wrapping the XML
- object.
- :param stream: The XML stream that generated the XML object.
- :param string outbuffer: Optional buffer for storing serializations
- during recursive calls.
- :param bool top_level: Indicates that the element is the outermost
- element.
- :param set namespaces: Track which namespaces are in active use so
- that new ones can be declared when needed.
-
- :type xml: :py:class:`~xml.etree.ElementTree.Element`
- :type stream: :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
-
- :rtype: Unicode string
- """
- # Add previous results to the start of the output.
- output = [outbuffer]
-
- # Extract the element's tag name.
- tag_name = xml.tag.split('}', 1)[-1]
-
- # Extract the element's namespace if it is defined.
- if '}' in xml.tag:
- tag_xmlns = xml.tag.split('}', 1)[0][1:]
- else:
- tag_xmlns = ''
-
- default_ns = ''
- stream_ns = ''
- use_cdata = False
-
- if stream:
- default_ns = stream.default_ns
- stream_ns = stream.stream_ns
- use_cdata = stream.use_cdata
-
- # Output the tag name and derived namespace of the element.
- namespace = ''
- if tag_xmlns:
- if top_level and tag_xmlns not in [default_ns, xmlns, stream_ns] \
- or not top_level and tag_xmlns != xmlns:
- namespace = ' xmlns="%s"' % tag_xmlns
- if stream and tag_xmlns in stream.namespace_map:
- mapped_namespace = stream.namespace_map[tag_xmlns]
- if mapped_namespace:
- tag_name = "%s:%s" % (mapped_namespace, tag_name)
- output.append("<%s" % tag_name)
- output.append(namespace)
-
- # Output escaped attribute values.
- new_namespaces = set()
- for attrib, value in xml.attrib.items():
- value = escape(value, use_cdata)
- if '}' not in attrib:
- output.append(' %s="%s"' % (attrib, value))
- else:
- attrib_ns = attrib.split('}')[0][1:]
- attrib = attrib.split('}')[1]
- if attrib_ns == XML_NS:
- output.append(' xml:%s="%s"' % (attrib, value))
- elif stream and attrib_ns in stream.namespace_map:
- mapped_ns = stream.namespace_map[attrib_ns]
- if mapped_ns:
- if namespaces is None:
- namespaces = set()
- if attrib_ns not in namespaces:
- namespaces.add(attrib_ns)
- new_namespaces.add(attrib_ns)
- output.append(' xmlns:%s="%s"' % (
- mapped_ns, attrib_ns))
- output.append(' %s:%s="%s"' % (
- mapped_ns, attrib, value))
-
- if open_only:
- # Only output the opening tag, regardless of content.
- output.append(">")
- return ''.join(output)
-
- if len(xml) or xml.text:
- # If there are additional child elements to serialize.
- output.append(">")
- if xml.text:
- output.append(escape(xml.text, use_cdata))
- if len(xml):
- for child in xml:
- output.append(tostring(child, tag_xmlns, stream,
- namespaces=namespaces))
- output.append("</%s>" % tag_name)
- elif xml.text:
- # If we only have text content.
- output.append(">%s</%s>" % (escape(xml.text, use_cdata), tag_name))
- else:
- # Empty element.
- output.append(" />")
- if xml.tail:
- # If there is additional text after the element.
- output.append(escape(xml.tail, use_cdata))
- for ns in new_namespaces:
- # Remove namespaces introduced in this context. This is necessary
- # because the namespaces object continues to be shared with other
- # contexts.
- namespaces.remove(ns)
- return ''.join(output)
-
-
-def escape(text, use_cdata=False):
- """Convert special characters in XML to escape sequences.
-
- :param string text: The XML text to convert.
- :rtype: Unicode string
- """
- if sys.version_info < (3, 0):
- if type(text) != types.UnicodeType:
- text = unicode(text, 'utf-8', 'ignore')
-
- escapes = {'&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- "'": '&apos;',
- '"': '&quot;'}
-
- if not use_cdata:
- text = list(text)
- for i, c in enumerate(text):
- text[i] = escapes.get(c, c)
- return ''.join(text)
- else:
- escape_needed = False
- for c in text:
- if c in escapes:
- escape_needed = True
- break
- if escape_needed:
- escaped = map(lambda x : "<![CDATA[%s]]>" % x, text.split("]]>"))
- return "<![CDATA[]]]><![CDATA[]>]]>".join(escaped)
- return text
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
deleted file mode 100644
index 62d46100..00000000
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ /dev/null
@@ -1,1817 +0,0 @@
-"""
- sleekxmpp.xmlstream.xmlstream
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This module provides the module for creating and
- interacting with generic XML streams, along with
- the necessary eventing infrastructure.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import with_statement, unicode_literals
-
-import base64
-import copy
-import logging
-import signal
-import socket as Socket
-import ssl
-import sys
-import threading
-import time
-import random
-import weakref
-import uuid
-import errno
-
-from xml.parsers.expat import ExpatError
-
-import sleekxmpp
-from sleekxmpp.util import Queue, QueueEmpty, safedict
-from sleekxmpp.thirdparty.statemachine import StateMachine
-from sleekxmpp.xmlstream import Scheduler, tostring, cert
-from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET, ElementBase
-from sleekxmpp.xmlstream.handler import Waiter, XMLCallback
-from sleekxmpp.xmlstream.matcher import MatchXMLMask
-from sleekxmpp.xmlstream.resolver import resolve, default_resolver
-
-# In Python 2.x, file socket objects are broken. A patched socket
-# wrapper is provided for this case in filesocket.py.
-if sys.version_info < (3, 0):
- from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26
-
-
-#: The time in seconds to wait before timing out waiting for response stanzas.
-RESPONSE_TIMEOUT = 30
-
-#: The time in seconds to wait for events from the event queue, and also the
-#: time between checks for the process stop signal.
-WAIT_TIMEOUT = 1.0
-
-#: The number of threads to use to handle XML stream events. This is not the
-#: same as the number of custom event handling threads.
-#: :data:`HANDLER_THREADS` must be at least 1. For Python implementations
-#: with a GIL, this should be left at 1, but for implemetnations without
-#: a GIL increasing this value can provide better performance.
-HANDLER_THREADS = 1
-
-#: The time in seconds to delay between attempts to resend data
-#: after an SSL error.
-SSL_RETRY_DELAY = 0.5
-
-#: The maximum number of times to attempt resending data due to
-#: an SSL error.
-SSL_RETRY_MAX = 10
-
-#: Maximum time to delay between connection attempts is one hour.
-RECONNECT_MAX_DELAY = 600
-
-#: Maximum number of attempts to connect to the server before quitting
-#: and raising a 'connect_failed' event. Setting this to ``None`` will
-#: allow infinite reconnection attempts, and using ``0`` will disable
-#: reconnections. Defaults to ``None``.
-RECONNECT_MAX_ATTEMPTS = None
-
-
-log = logging.getLogger(__name__)
-
-
-class RestartStream(Exception):
- """
- Exception to restart stream processing, including
- resending the stream header.
- """
-
-
-class XMLStream(object):
- """
- An XML stream connection manager and event dispatcher.
-
- The XMLStream class abstracts away the issues of establishing a
- connection with a server and sending and receiving XML "stanzas".
- A stanza is a complete XML element that is a direct child of a root
- document element. Two streams are used, one for each communication
- direction, over the same socket. Once the connection is closed, both
- streams should be complete and valid XML documents.
-
- Three types of events are provided to manage the stream:
- :Stream: Triggered based on received stanzas, similar in concept
- to events in a SAX XML parser.
- :Custom: Triggered manually.
- :Scheduled: Triggered based on time delays.
-
- Typically, stanzas are first processed by a stream event handler which
- will then trigger custom events to continue further processing,
- especially since custom event handlers may run in individual threads.
-
- :param socket: Use an existing socket for the stream. Defaults to
- ``None`` to generate a new socket.
- :param string host: The name of the target server.
- :param int port: The port to use for the connection. Defaults to 0.
- """
-
- def __init__(self, socket=None, host='', port=0, certfile=None,
- keyfile=None, ca_certs=None, **kwargs):
- #: Most XMPP servers support TLSv1, but OpenFire in particular
- #: does not work well with it. For OpenFire, set
- #: :attr:`ssl_version` to use ``SSLv23``::
- #:
- #: import ssl
- #: xmpp.ssl_version = ssl.PROTOCOL_SSLv23
- self.ssl_version = ssl.PROTOCOL_TLSv1
-
- #: The list of accepted ciphers, in OpenSSL Format.
- #: It might be useful to override it for improved security
- #: over the python defaults.
- self.ciphers = None
-
- #: Path to a file containing certificates for verifying the
- #: server SSL certificate. A non-``None`` value will trigger
- #: certificate checking.
- #:
- #: .. note::
- #:
- #: On Mac OS X, certificates in the system keyring will
- #: be consulted, even if they are not in the provided file.
- self.ca_certs = ca_certs
-
- #: Path to a file containing a client certificate to use for
- #: authenticating via SASL EXTERNAL. If set, there must also
- #: be a corresponding `:attr:keyfile` value.
- self.certfile = certfile
-
- #: Path to a file containing the private key for the selected
- #: client certificate to use for authenticating via SASL EXTERNAL.
- self.keyfile = keyfile
-
- self._der_cert = None
-
- #: The time in seconds to wait for events from the event queue,
- #: and also the time between checks for the process stop signal.
- self.wait_timeout = WAIT_TIMEOUT
-
- #: The time in seconds to wait before timing out waiting
- #: for response stanzas.
- self.response_timeout = RESPONSE_TIMEOUT
-
- #: The current amount to time to delay attempting to reconnect.
- #: This value doubles (with some jitter) with each failed
- #: connection attempt up to :attr:`reconnect_max_delay` seconds.
- self.reconnect_delay = None
-
- #: Maximum time to delay between connection attempts is one hour.
- self.reconnect_max_delay = RECONNECT_MAX_DELAY
-
- #: Maximum number of attempts to connect to the server before
- #: quitting and raising a 'connect_failed' event. Setting to
- #: ``None`` allows infinite reattempts, while setting it to ``0``
- #: will disable reconnection attempts. Defaults to ``None``.
- self.reconnect_max_attempts = RECONNECT_MAX_ATTEMPTS
-
- #: The time in seconds to delay between attempts to resend data
- #: after an SSL error.
- self.ssl_retry_max = SSL_RETRY_MAX
-
- #: The maximum number of times to attempt resending data due to
- #: an SSL error.
- self.ssl_retry_delay = SSL_RETRY_DELAY
-
- #: The connection state machine tracks if the stream is
- #: ``'connected'`` or ``'disconnected'``.
- self.state = StateMachine(('disconnected', 'connected'))
- self.state._set_state('disconnected')
-
- #: The default port to return when querying DNS records.
- self.default_port = int(port)
-
- #: The domain to try when querying DNS records.
- self.default_domain = ''
-
- #: The expected name of the server, for validation.
- self._expected_server_name = ''
- self._service_name = ''
-
- #: The desired, or actual, address of the connected server.
- self.address = (host, int(port))
-
- #: A file-like wrapper for the socket for use with the
- #: :mod:`~xml.etree.ElementTree` module.
- self.filesocket = None
- self.set_socket(socket)
-
- if sys.version_info < (3, 0):
- self.socket_class = Socket26
- else:
- self.socket_class = Socket.socket
-
- #: Enable connecting to the server directly over SSL, in
- #: particular when the service provides two ports: one for
- #: non-SSL traffic and another for SSL traffic.
- self.use_ssl = False
-
- #: Enable connecting to the service without using SSL
- #: immediately, but allow upgrading the connection later
- #: to use SSL.
- self.use_tls = False
-
- #: If set to ``True``, attempt to connect through an HTTP
- #: proxy based on the settings in :attr:`proxy_config`.
- self.use_proxy = False
-
- #: If set to ``True``, attempt to use IPv6.
- self.use_ipv6 = True
-
- #: If set to ``True``, allow using the ``dnspython`` DNS library
- #: if available. If set to ``False``, the builtin DNS resolver
- #: will be used, even if ``dnspython`` is installed.
- self.use_dnspython = True
-
- #: Use CDATA for escaping instead of XML entities. Defaults
- #: to ``False``.
- self.use_cdata = False
-
- #: An optional dictionary of proxy settings. It may provide:
- #: :host: The host offering proxy services.
- #: :port: The port for the proxy service.
- #: :username: Optional username for accessing the proxy.
- #: :password: Optional password for accessing the proxy.
- self.proxy_config = {}
-
- #: The default namespace of the stream content, not of the
- #: stream wrapper itself.
- self.default_ns = ''
-
- self.default_lang = None
- self.peer_default_lang = None
-
- #: The namespace of the enveloping stream element.
- self.stream_ns = ''
-
- #: The default opening tag for the stream element.
- self.stream_header = "<stream>"
-
- #: The default closing tag for the stream element.
- self.stream_footer = "</stream>"
-
- #: If ``True``, periodically send a whitespace character over the
- #: wire to keep the connection alive. Mainly useful for connections
- #: traversing NAT.
- self.whitespace_keepalive = True
-
- #: The default interval between keepalive signals when
- #: :attr:`whitespace_keepalive` is enabled.
- self.whitespace_keepalive_interval = 300
-
- #: An :class:`~threading.Event` to signal that the application
- #: is stopping, and that all threads should shutdown.
- self.stop = threading.Event()
-
- #: An :class:`~threading.Event` to signal receiving a closing
- #: stream tag from the server.
- self.stream_end_event = threading.Event()
- self.stream_end_event.set()
-
- #: An :class:`~threading.Event` to signal the start of a stream
- #: session. Until this event fires, the send queue is not used
- #: and data is sent immediately over the wire.
- self.session_started_event = threading.Event()
-
- #: The default time in seconds to wait for a session to start
- #: after connecting before reconnecting and trying again.
- self.session_timeout = 45
-
- #: Flag for controlling if the session can be considered ended
- #: if the connection is terminated.
- self.end_session_on_disconnect = True
-
- #: A queue of stream, custom, and scheduled events to be processed.
- self.event_queue = Queue()
-
- #: A queue of string data to be sent over the stream.
- self.send_queue = Queue(maxsize=256)
- self.send_queue_lock = threading.Lock()
- self.send_lock = threading.RLock()
-
- #: A :class:`~sleekxmpp.xmlstream.scheduler.Scheduler` instance for
- #: executing callbacks in the future based on time delays.
- self.scheduler = Scheduler(self.stop)
- self.__failed_send_stanza = None
-
- #: A mapping of XML namespaces to well-known prefixes.
- self.namespace_map = {StanzaBase.xml_ns: 'xml'}
-
- self.__thread = {}
- self.__root_stanza = []
- self.__handlers = []
- self.__event_handlers = {}
- self.__event_handlers_lock = threading.Lock()
- self.__filters = {'in': [], 'out': [], 'out_sync': []}
- self.__thread_count = 0
- self.__thread_cond = threading.Condition()
- self.__active_threads = set()
- self._use_daemons = False
- self._disconnect_wait_for_threads = True
-
- self._id = 0
- self._id_lock = threading.Lock()
-
- #: We use an ID prefix to ensure that all ID values are unique.
- self._id_prefix = '%s-' % uuid.uuid4()
-
- #: The :attr:`auto_reconnnect` setting controls whether or not
- #: the stream will be restarted in the event of an error.
- self.auto_reconnect = True
-
- #: The :attr:`disconnect_wait` setting is the default value
- #: for controlling if the system waits for the send queue to
- #: empty before ending the stream. This may be overridden by
- #: passing ``wait=True`` or ``wait=False`` to :meth:`disconnect`.
- #: The default :attr:`disconnect_wait` value is ``False``.
- self.disconnect_wait = False
-
- #: A list of DNS results that have not yet been tried.
- self.dns_answers = []
-
- #: The service name to check with DNS SRV records. For
- #: example, setting this to ``'xmpp-client'`` would query the
- #: ``_xmpp-client._tcp`` service.
- self.dns_service = None
-
- self.add_event_handler('connected', self._session_timeout_check)
- self.add_event_handler('disconnected', self._remove_schedules)
- self.add_event_handler('session_start', self._start_keepalive)
- self.add_event_handler('session_start', self._cert_expiration)
-
- def use_signals(self, signals=None):
- """Register signal handlers for ``SIGHUP`` and ``SIGTERM``.
-
- By using signals, a ``'killed'`` event will be raised when the
- application is terminated.
-
- If a signal handler already existed, it will be executed first,
- before the ``'killed'`` event is raised.
-
- :param list signals: A list of signal names to be monitored.
- Defaults to ``['SIGHUP', 'SIGTERM']``.
- """
- if signals is None:
- signals = ['SIGHUP', 'SIGTERM']
-
- existing_handlers = {}
- for sig_name in signals:
- if hasattr(signal, sig_name):
- sig = getattr(signal, sig_name)
- handler = signal.getsignal(sig)
- if handler:
- existing_handlers[sig] = handler
-
- def handle_kill(signum, frame):
- """
- Capture kill event and disconnect cleanly after first
- spawning the ``'killed'`` event.
- """
-
- if signum in existing_handlers and \
- existing_handlers[signum] != handle_kill:
- existing_handlers[signum](signum, frame)
-
- self.event("killed", direct=True)
- self.disconnect()
-
- try:
- for sig_name in signals:
- if hasattr(signal, sig_name):
- sig = getattr(signal, sig_name)
- signal.signal(sig, handle_kill)
- self.__signals_installed = True
- except:
- log.debug("Can not set interrupt signal handlers. " + \
- "SleekXMPP is not running from a main thread.")
-
- def new_id(self):
- """Generate and return a new stream ID in hexadecimal form.
-
- Many stanzas, handlers, or matchers may require unique
- ID values. Using this method ensures that all new ID values
- are unique in this stream.
- """
- with self._id_lock:
- self._id += 1
- return self.get_id()
-
- def get_id(self):
- """Return the current unique stream ID in hexadecimal form."""
- return "%s%X" % (self._id_prefix, self._id)
-
- def connect(self, host='', port=0, use_ssl=False,
- use_tls=True, reattempt=True):
- """Create a new socket and connect to the server.
-
- Setting ``reattempt`` to ``True`` will cause connection
- attempts to be made with an exponential backoff delay (max of
- :attr:`reconnect_max_delay` which defaults to 10 minute) until a
- successful connection is established.
-
- :param host: The name of the desired server for the connection.
- :param port: Port to connect to on the server.
- :param use_ssl: Flag indicating if SSL should be used by connecting
- directly to a port using SSL.
- :param use_tls: Flag indicating if TLS should be used, allowing for
- connecting to a port without using SSL immediately and
- later upgrading the connection.
- :param reattempt: Flag indicating if the socket should reconnect
- after disconnections.
- """
- self.stop.clear()
-
- if host and port:
- self.address = (host, int(port))
- try:
- Socket.inet_aton(self.address[0])
- except (Socket.error, ssl.SSLError):
- self.default_domain = self.address[0]
-
- # Respect previous SSL and TLS usage directives.
- if use_ssl is not None:
- self.use_ssl = use_ssl
- if use_tls is not None:
- self.use_tls = use_tls
-
- # Repeatedly attempt to connect until a successful connection
- # is established.
- attempts = self.reconnect_max_attempts
- connected = self.state.transition('disconnected', 'connected',
- func=self._connect,
- args=(reattempt,))
- while reattempt and not connected and not self.stop.is_set():
- connected = self.state.transition('disconnected', 'connected',
- func=self._connect)
- if not connected:
- if attempts is not None:
- attempts -= 1
- if attempts <= 0:
- self.event('connection_failed', direct=True)
- return False
- return connected
-
- def _connect(self, reattempt=True):
- self.scheduler.remove('Session timeout check')
-
- if self.reconnect_delay is None:
- delay = 1.0
- self.reconnect_delay = delay
-
- if reattempt:
- delay = min(self.reconnect_delay * 2, self.reconnect_max_delay)
- delay = random.normalvariate(delay, delay * 0.1)
- log.debug('Waiting %s seconds before connecting.', delay)
- elapsed = 0
- try:
- while elapsed < delay and not self.stop.is_set():
- time.sleep(0.1)
- elapsed += 0.1
- except KeyboardInterrupt:
- self.set_stop()
- return False
- except SystemExit:
- self.set_stop()
- return False
-
- if self.default_domain:
- try:
- host, address, port = self.pick_dns_answer(self.default_domain,
- self.address[1])
- self.address = (address, port)
- self._service_name = host
- except StopIteration:
- log.debug("No remaining DNS records to try.")
- self.dns_answers = None
- if reattempt:
- self.reconnect_delay = delay
- return False
-
- af = Socket.AF_INET
- proto = 'IPv4'
- if ':' in self.address[0]:
- af = Socket.AF_INET6
- proto = 'IPv6'
- try:
- self.socket = self.socket_class(af, Socket.SOCK_STREAM)
- except Socket.error:
- log.debug("Could not connect using %s", proto)
- return False
-
- self.configure_socket()
-
- if self.use_proxy:
- connected = self._connect_proxy()
- if not connected:
- if reattempt:
- self.reconnect_delay = delay
- return False
-
- if self.use_ssl:
- log.debug("Socket Wrapped for SSL")
- if self.ca_certs is None:
- cert_policy = ssl.CERT_NONE
- else:
- cert_policy = ssl.CERT_REQUIRED
-
- ssl_args = safedict({
- 'certfile': self.certfile,
- 'keyfile': self.keyfile,
- 'ca_certs': self.ca_certs,
- 'cert_reqs': cert_policy,
- 'do_handshake_on_connect': False,
- "ssl_version": self.ssl_version
- })
-
- if sys.version_info >= (2, 7):
- ssl_args['ciphers'] = self.ciphers
-
- ssl_socket = ssl.wrap_socket(self.socket, **ssl_args)
-
- if hasattr(self.socket, 'socket'):
- # We are using a testing socket, so preserve the top
- # layer of wrapping.
- self.socket.socket = ssl_socket
- else:
- self.socket = ssl_socket
-
- try:
- if not self.use_proxy:
- domain = self.address[0]
- if ':' in domain:
- domain = '[%s]' % domain
- log.debug("Connecting to %s:%s", domain, self.address[1])
- self.socket.connect(self.address)
-
- if self.use_ssl:
- try:
- self.socket.do_handshake()
- except (Socket.error, ssl.SSLError):
- log.error('CERT: Invalid certificate trust chain.')
- if not self.event_handled('ssl_invalid_chain'):
- self.disconnect(self.auto_reconnect,
- send_close=False)
- else:
- self.event('ssl_invalid_chain', direct=True)
- return False
-
- self._der_cert = self.socket.getpeercert(binary_form=True)
- pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
- log.debug('CERT: %s', pem_cert)
-
- self.event('ssl_cert', pem_cert, direct=True)
- try:
- cert.verify(self._expected_server_name, self._der_cert)
- except cert.CertificateError as err:
- if not self.event_handled('ssl_invalid_cert'):
- log.error(err)
- self.disconnect(send_close=False)
- else:
- self.event('ssl_invalid_cert',
- pem_cert,
- direct=True)
-
- self.set_socket(self.socket, ignore=True)
- #this event is where you should set your application state
- self.event('connected', direct=True)
- return True
- except (Socket.error, ssl.SSLError) as serr:
- error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
- self.event('socket_error', serr, direct=True)
- domain = self.address[0]
- if ':' in domain:
- domain = '[%s]' % domain
- log.error(error_msg, domain, self.address[1],
- serr.errno, serr.strerror)
- return False
-
- def _connect_proxy(self):
- """Attempt to connect using an HTTP Proxy."""
-
- # Extract the proxy address, and optional credentials
- address = (self.proxy_config['host'], int(self.proxy_config['port']))
- cred = None
- if self.proxy_config['username']:
- username = self.proxy_config['username']
- password = self.proxy_config['password']
-
- cred = '%s:%s' % (username, password)
- if sys.version_info < (3, 0):
- cred = bytes(cred)
- else:
- cred = bytes(cred, 'utf-8')
- cred = base64.b64encode(cred).decode('utf-8')
-
- # Build the HTTP headers for connecting to the XMPP server
- headers = ['CONNECT %s:%s HTTP/1.0' % self.address,
- 'Host: %s:%s' % self.address,
- 'Proxy-Connection: Keep-Alive',
- 'Pragma: no-cache',
- 'User-Agent: SleekXMPP/%s' % sleekxmpp.__version__]
- if cred:
- headers.append('Proxy-Authorization: Basic %s' % cred)
- headers = '\r\n'.join(headers) + '\r\n\r\n'
-
- try:
- log.debug("Connecting to proxy: %s:%s", *address)
- self.socket.connect(address)
- self.send_raw(headers, now=True)
- resp = ''
- while '\r\n\r\n' not in resp and not self.stop.is_set():
- resp += self.socket.recv(1024).decode('utf-8')
- log.debug('RECV: %s', resp)
-
- lines = resp.split('\r\n')
- if '200' not in lines[0]:
- self.event('proxy_error', resp)
- self.event('connection_failed', direct=True)
- log.error('Proxy Error: %s', lines[0])
- return False
-
- # Proxy connection established, continue connecting
- # with the XMPP server.
- return True
- except (Socket.error, ssl.SSLError) as serr:
- error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
- self.event('socket_error', serr, direct=True)
- log.error(error_msg, self.address[0], self.address[1],
- serr.errno, serr.strerror)
- return False
-
- def _session_timeout_check(self, event=None):
- """
- Add check to ensure that a session is established within
- a reasonable amount of time.
- """
-
- def _handle_session_timeout():
- if not self.session_started_event.is_set():
- log.debug("Session start has taken more " + \
- "than %d seconds", self.session_timeout)
- self.disconnect(reconnect=self.auto_reconnect)
-
- self.schedule("Session timeout check",
- self.session_timeout,
- _handle_session_timeout)
-
- def disconnect(self, reconnect=False, wait=None, send_close=True):
- """Terminate processing and close the XML streams.
-
- Optionally, the connection may be reconnected and
- resume processing afterwards.
-
- If the disconnect should take place after all items
- in the send queue have been sent, use ``wait=True``.
-
- .. warning::
-
- If you are constantly adding items to the queue
- such that it is never empty, then the disconnect will
- not occur and the call will continue to block.
-
- :param reconnect: Flag indicating if the connection
- and processing should be restarted.
- Defaults to ``False``.
- :param wait: Flag indicating if the send queue should
- be emptied before disconnecting, overriding
- :attr:`disconnect_wait`.
- :param send_close: Flag indicating if the stream footer
- should be sent before terminating the
- connection. Setting this to ``False``
- prevents error loops when trying to
- disconnect after a socket error.
- """
- self.state.transition('connected', 'disconnected',
- wait=2.0,
- func=self._disconnect,
- args=(reconnect, wait, send_close))
-
- def _disconnect(self, reconnect=False, wait=None, send_close=True):
- if not reconnect:
- self.auto_reconnect = False
-
- if self.end_session_on_disconnect or send_close:
- self.event('session_end', direct=True)
-
- # Wait for the send queue to empty.
- if wait is not None:
- if wait:
- self.send_queue.join()
- elif self.disconnect_wait:
- self.send_queue.join()
-
- # Clearing this event will pause the send loop.
- self.session_started_event.clear()
-
- self.__failed_send_stanza = None
-
- # Send the end of stream marker.
- if send_close:
- self.send_raw(self.stream_footer, now=True)
-
- # Wait for confirmation that the stream was
- # closed in the other direction. If we didn't
- # send a stream footer we don't need to wait
- # since the server won't know to respond.
- if send_close:
- log.info('Waiting for %s from server', self.stream_footer)
- self.stream_end_event.wait(4)
- else:
- self.stream_end_event.set()
-
- if not self.auto_reconnect:
- self.set_stop()
- if self._disconnect_wait_for_threads:
- self._wait_for_threads()
-
- try:
- self.socket.shutdown(Socket.SHUT_RDWR)
- self.socket.close()
- self.filesocket.close()
- except (Socket.error, ssl.SSLError) as serr:
- self.event('socket_error', serr, direct=True)
- finally:
- #clear your application state
- self.event('disconnected', direct=True)
- return True
-
- def abort(self):
- self.session_started_event.clear()
- self.set_stop()
- if self._disconnect_wait_for_threads:
- self._wait_for_threads()
- try:
- self.socket.shutdown(Socket.SHUT_RDWR)
- self.socket.close()
- self.filesocket.close()
- except Socket.error:
- pass
- self.state.transition_any(['connected', 'disconnected'], 'disconnected', func=lambda: True)
- self.event("killed", direct=True)
-
- def reconnect(self, reattempt=True, wait=False, send_close=True):
- """Reset the stream's state and reconnect to the server."""
- log.debug("reconnecting...")
- if self.state.ensure('connected'):
- self.state.transition('connected', 'disconnected',
- wait=2.0,
- func=self._disconnect,
- args=(True, wait, send_close))
-
- attempts = self.reconnect_max_attempts
-
- log.debug("connecting...")
- connected = self.state.transition('disconnected', 'connected',
- wait=2.0,
- func=self._connect,
- args=(reattempt,))
- while reattempt and not connected and not self.stop.is_set():
- connected = self.state.transition('disconnected', 'connected',
- wait=2.0, func=self._connect)
- connected = connected or self.state.ensure('connected')
- if not connected:
- if attempts is not None:
- attempts -= 1
- if attempts <= 0:
- self.event('connection_failed', direct=True)
- return False
- return connected
-
- def set_socket(self, socket, ignore=False):
- """Set the socket to use for the stream.
-
- The filesocket will be recreated as well.
-
- :param socket: The new socket object to use.
- :param bool ignore: If ``True``, don't set the connection
- state to ``'connected'``.
- """
- self.socket = socket
- if socket is not None:
- # ElementTree.iterparse requires a file.
- # 0 buffer files have to be binary.
-
- # Use the correct fileobject type based on the Python
- # version to work around a broken implementation in
- # Python 2.x.
- if sys.version_info < (3, 0):
- self.filesocket = FileSocket(self.socket)
- else:
- self.filesocket = self.socket.makefile('rb', 0)
- if not ignore:
- self.state._set_state('connected')
-
- def configure_socket(self):
- """Set timeout and other options for self.socket.
-
- Meant to be overridden.
- """
- self.socket.settimeout(None)
-
- def configure_dns(self, resolver, domain=None, port=None):
- """
- Configure and set options for a :class:`~dns.resolver.Resolver`
- instance, and other DNS related tasks. For example, you
- can also check :meth:`~socket.socket.getaddrinfo` to see
- if you need to call out to ``libresolv.so.2`` to
- run ``res_init()``.
-
- Meant to be overridden.
-
- :param resolver: A :class:`~dns.resolver.Resolver` instance
- or ``None`` if ``dnspython`` is not installed.
- :param domain: The initial domain under consideration.
- :param port: The initial port under consideration.
- """
- pass
-
- def start_tls(self):
- """Perform handshakes for TLS.
-
- If the handshake is successful, the XML stream will need
- to be restarted.
- """
- log.info("Negotiating TLS")
- ssl_versions = {3: 'TLS 1.0', 1: 'SSL 3', 2: 'SSL 2/3'}
- log.info("Using SSL version: %s", ssl_versions[self.ssl_version])
- if self.ca_certs is None:
- cert_policy = ssl.CERT_NONE
- else:
- cert_policy = ssl.CERT_REQUIRED
-
- ssl_args = safedict({
- 'certfile': self.certfile,
- 'keyfile': self.keyfile,
- 'ca_certs': self.ca_certs,
- 'cert_reqs': cert_policy,
- 'do_handshake_on_connect': False,
- "ssl_version": self.ssl_version
- })
-
- if sys.version_info >= (2, 7):
- ssl_args['ciphers'] = self.ciphers
-
- ssl_socket = ssl.wrap_socket(self.socket, **ssl_args)
-
- if hasattr(self.socket, 'socket'):
- # We are using a testing socket, so preserve the top
- # layer of wrapping.
- self.socket.socket = ssl_socket
- else:
- self.socket = ssl_socket
-
- try:
- self.socket.do_handshake()
- except (Socket.error, ssl.SSLError):
- log.error('CERT: Invalid certificate trust chain.')
- if not self.event_handled('ssl_invalid_chain'):
- self.disconnect(self.auto_reconnect, send_close=False)
- else:
- self._der_cert = self.socket.getpeercert(binary_form=True)
- self.event('ssl_invalid_chain', direct=True)
- return False
-
- self._der_cert = self.socket.getpeercert(binary_form=True)
- pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
- log.debug('CERT: %s', pem_cert)
- self.event('ssl_cert', pem_cert, direct=True)
-
- try:
- cert.verify(self._expected_server_name, self._der_cert)
- except cert.CertificateError as err:
- if not self.event_handled('ssl_invalid_cert'):
- log.error(err)
- self.disconnect(self.auto_reconnect, send_close=False)
- else:
- self.event('ssl_invalid_cert', pem_cert, direct=True)
-
- self.set_socket(self.socket)
- return True
-
- def _cert_expiration(self, event):
- """Schedule an event for when the TLS certificate expires."""
-
- if not self.use_tls and not self.use_ssl:
- return
-
- if not self._der_cert:
- log.warn("TLS or SSL was enabled, but no certificate was found.")
- return
-
- def restart():
- if not self.event_handled('ssl_expired_cert'):
- log.warn("The server certificate has expired. Restarting.")
- self.reconnect()
- else:
- pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
- self.event('ssl_expired_cert', pem_cert)
-
- cert_ttl = cert.get_ttl(self._der_cert)
- if cert_ttl is None:
- return
-
- if cert_ttl.days < 0:
- log.warn('CERT: Certificate has expired.')
- restart()
-
- try:
- total_seconds = cert_ttl.total_seconds()
- except AttributeError:
- # for Python < 2.7
- total_seconds = (cert_ttl.microseconds + (cert_ttl.seconds + cert_ttl.days * 24 * 3600) * 10**6) / 10**6
-
- log.info('CERT: Time until certificate expiration: %s' % cert_ttl)
- self.schedule('Certificate Expiration',
- total_seconds,
- restart)
-
- def _start_keepalive(self, event):
- """Begin sending whitespace periodically to keep the connection alive.
-
- May be disabled by setting::
-
- self.whitespace_keepalive = False
-
- The keepalive interval can be set using::
-
- self.whitespace_keepalive_interval = 300
- """
- if self.whitespace_keepalive:
- self.schedule('Whitespace Keepalive',
- self.whitespace_keepalive_interval,
- self.send_raw,
- args=(' ',),
- kwargs={'now': True},
- repeat=True)
-
- def _remove_schedules(self, event):
- """Remove whitespace keepalive and certificate expiration schedules."""
- self.scheduler.remove('Whitespace Keepalive')
- self.scheduler.remove('Certificate Expiration')
-
- def start_stream_handler(self, xml):
- """Perform any initialization actions, such as handshakes,
- once the stream header has been sent.
-
- Meant to be overridden.
- """
- pass
-
- def register_stanza(self, stanza_class):
- """Add a stanza object class as a known root stanza.
-
- A root stanza is one that appears as a direct child of the stream's
- root element.
-
- Stanzas that appear as substanzas of a root stanza do not need to
- be registered here. That is done using register_stanza_plugin() from
- sleekxmpp.xmlstream.stanzabase.
-
- Stanzas that are not registered will not be converted into
- stanza objects, but may still be processed using handlers and
- matchers.
-
- :param stanza_class: The top-level stanza object's class.
- """
- self.__root_stanza.append(stanza_class)
-
- def remove_stanza(self, stanza_class):
- """Remove a stanza from being a known root stanza.
-
- A root stanza is one that appears as a direct child of the stream's
- root element.
-
- Stanzas that are not registered will not be converted into
- stanza objects, but may still be processed using handlers and
- matchers.
- """
- self.__root_stanza.remove(stanza_class)
-
- def add_filter(self, mode, handler, order=None):
- """Add a filter for incoming or outgoing stanzas.
-
- These filters are applied before incoming stanzas are
- passed to any handlers, and before outgoing stanzas
- are put in the send queue.
-
- Each filter must accept a single stanza, and return
- either a stanza or ``None``. If the filter returns
- ``None``, then the stanza will be dropped from being
- processed for events or from being sent.
-
- :param mode: One of ``'in'`` or ``'out'``.
- :param handler: The filter function.
- :param int order: The position to insert the filter in
- the list of active filters.
- """
- if order:
- self.__filters[mode].insert(order, handler)
- else:
- self.__filters[mode].append(handler)
-
- def del_filter(self, mode, handler):
- """Remove an incoming or outgoing filter."""
- self.__filters[mode].remove(handler)
-
- def add_handler(self, mask, pointer, name=None, disposable=False,
- threaded=False, filter=False, instream=False):
- """A shortcut method for registering a handler using XML masks.
-
- The use of :meth:`register_handler()` is preferred.
-
- :param mask: An XML snippet matching the structure of the
- stanzas that will be passed to this handler.
- :param pointer: The handler function itself.
- :parm name: A unique name for the handler. A name will
- be generated if one is not provided.
- :param disposable: Indicates if the handler should be discarded
- after one use.
- :param threaded: **DEPRECATED**.
- Remains for backwards compatibility.
- :param filter: **DEPRECATED**.
- Remains for backwards compatibility.
- :param instream: Indicates if the handler should execute during
- stream processing and not during normal event
- processing.
- """
- # To prevent circular dependencies, we must load the matcher
- # and handler classes here.
-
- if name is None:
- name = 'add_handler_%s' % self.new_id()
- self.register_handler(
- XMLCallback(name,
- MatchXMLMask(mask, self.default_ns),
- pointer,
- once=disposable,
- instream=instream))
-
- def register_handler(self, handler, before=None, after=None):
- """Add a stream event handler that will be executed when a matching
- stanza is received.
-
- :param handler:
- The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
- derived object to execute.
- """
- if handler.stream is None:
- self.__handlers.append(handler)
- handler.stream = weakref.ref(self)
-
- def remove_handler(self, name):
- """Remove any stream event handlers with the given name.
-
- :param name: The name of the handler.
- """
- idx = 0
- for handler in self.__handlers:
- if handler.name == name:
- self.__handlers.pop(idx)
- return True
- idx += 1
- return False
-
- def get_dns_records(self, domain, port=None):
- """Get the DNS records for a domain.
-
- :param domain: The domain in question.
- :param port: If the results don't include a port, use this one.
- """
- if port is None:
- port = self.default_port
-
- resolver = default_resolver()
- self.configure_dns(resolver, domain=domain, port=port)
-
- return resolve(domain, port, service=self.dns_service,
- resolver=resolver,
- use_ipv6=self.use_ipv6,
- use_dnspython=self.use_dnspython)
-
- def pick_dns_answer(self, domain, port=None):
- """Pick a server and port from DNS answers.
-
- Gets DNS answers if none available.
- Removes used answer from available answers.
-
- :param domain: The domain in question.
- :param port: If the results don't include a port, use this one.
- """
- if not self.dns_answers:
- self.dns_answers = self.get_dns_records(domain, port)
-
- if sys.version_info < (3, 0):
- return self.dns_answers.next()
- else:
- return next(self.dns_answers)
-
- def add_event_handler(self, name, pointer,
- threaded=False, disposable=False):
- """Add a custom event handler that will be executed whenever
- its event is manually triggered.
-
- :param name: The name of the event that will trigger
- this handler.
- :param pointer: The function to execute.
- :param threaded: If set to ``True``, the handler will execute
- in its own thread. Defaults to ``False``.
- :param disposable: If set to ``True``, the handler will be
- discarded after one use. Defaults to ``False``.
- """
- if not name in self.__event_handlers:
- self.__event_handlers[name] = []
- self.__event_handlers[name].append((pointer, threaded, disposable))
-
- def del_event_handler(self, name, pointer):
- """Remove a function as a handler for an event.
-
- :param name: The name of the event.
- :param pointer: The function to remove as a handler.
- """
- if not name in self.__event_handlers:
- return
-
- # Need to keep handlers that do not use
- # the given function pointer
- def filter_pointers(handler):
- return handler[0] != pointer
-
- self.__event_handlers[name] = list(filter(
- filter_pointers,
- self.__event_handlers[name]))
-
- def event_handled(self, name):
- """Returns the number of registered handlers for an event.
-
- :param name: The name of the event to check.
- """
- return len(self.__event_handlers.get(name, []))
-
- def event(self, name, data=None, direct=False):
- """Manually trigger a custom event.
-
- :param name: The name of the event to trigger.
- :param data: Data that will be passed to each event handler.
- Defaults to an empty dictionary, but is usually
- a stanza object.
- :param direct: Runs the event directly if True, skipping the
- event queue. All event handlers will run in the
- same thread.
- """
- if not data:
- data = {}
-
- log.debug("Event triggered: " + name)
-
- handlers = self.__event_handlers.get(name, [])
- for handler in handlers:
- #TODO: Data should not be copied, but should be read only,
- # but this might break current code so it's left for future.
-
- out_data = copy.copy(data) if len(handlers) > 1 else data
- old_exception = getattr(data, 'exception', None)
- if direct:
- try:
- handler[0](out_data)
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.exception(error_msg, str(handler[0]))
- if old_exception:
- old_exception(e)
- else:
- self.exception(e)
- else:
- self.event_queue.put(('event', handler, out_data))
- if handler[2]:
- # If the handler is disposable, we will go ahead and
- # remove it now instead of waiting for it to be
- # processed in the queue.
- with self.__event_handlers_lock:
- try:
- h_index = self.__event_handlers[name].index(handler)
- self.__event_handlers[name].pop(h_index)
- except:
- pass
-
- def schedule(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False):
- """Schedule a callback function to execute after a given delay.
-
- :param name: A unique name for the scheduled callback.
- :param seconds: The time in seconds to wait before executing.
- :param callback: A pointer to the function to execute.
- :param args: A tuple of arguments to pass to the function.
- :param kwargs: A dictionary of keyword arguments to pass to
- the function.
- :param repeat: Flag indicating if the scheduled event should
- be reset and repeat after executing.
- """
- self.scheduler.add(name, seconds, callback, args, kwargs,
- repeat, qpointer=self.event_queue)
-
- def incoming_filter(self, xml):
- """Filter incoming XML objects before they are processed.
-
- Possible uses include remapping namespaces, or correcting elements
- from sources with incorrect behavior.
-
- Meant to be overridden.
- """
- return xml
-
- def send(self, data, mask=None, timeout=None, now=False, use_filters=True):
- """A wrapper for :meth:`send_raw()` for sending stanza objects.
-
- May optionally block until an expected response is received.
-
- :param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to send on the stream.
- :param mask: **DEPRECATED**
- An XML string snippet matching the structure
- of the expected response. Execution will block
- in this thread until the response is received
- or a timeout occurs.
- :param int timeout: Time in seconds to wait for a response before
- continuing. Defaults to :attr:`response_timeout`.
- :param bool now: Indicates if the send queue should be skipped,
- sending the stanza immediately. Useful mainly
- for stream initialization stanzas.
- Defaults to ``False``.
- :param bool use_filters: Indicates if outgoing filters should be
- applied to the given stanza data. Disabling
- filters is useful when resending stanzas.
- Defaults to ``True``.
- """
- if timeout is None:
- timeout = self.response_timeout
- if hasattr(mask, 'xml'):
- mask = mask.xml
-
- if isinstance(data, ElementBase):
- if use_filters:
- for filter in self.__filters['out']:
- data = filter(data)
- if data is None:
- return
-
- if mask is not None:
- log.warning("Use of send mask waiters is deprecated.")
- wait_for = Waiter("SendWait_%s" % self.new_id(),
- MatchXMLMask(mask))
- self.register_handler(wait_for)
-
- if isinstance(data, ElementBase):
- with self.send_queue_lock:
- if use_filters:
- for filter in self.__filters['out_sync']:
- data = filter(data)
- if data is None:
- return
- str_data = tostring(data.xml, xmlns=self.default_ns,
- stream=self,
- top_level=True)
- self.send_raw(str_data, now)
- else:
- self.send_raw(data, now)
- if mask is not None:
- return wait_for.wait(timeout)
-
- def send_xml(self, data, mask=None, timeout=None, now=False):
- """Send an XML object on the stream, and optionally wait
- for a response.
-
- :param data: The :class:`~xml.etree.ElementTree.Element` XML object
- to send on the stream.
- :param mask: **DEPRECATED**
- An XML string snippet matching the structure
- of the expected response. Execution will block
- in this thread until the response is received
- or a timeout occurs.
- :param int timeout: Time in seconds to wait for a response before
- continuing. Defaults to :attr:`response_timeout`.
- :param bool now: Indicates if the send queue should be skipped,
- sending the stanza immediately. Useful mainly
- for stream initialization stanzas.
- Defaults to ``False``.
- """
- if timeout is None:
- timeout = self.response_timeout
- return self.send(tostring(data), mask, timeout, now)
-
- def send_raw(self, data, now=False, reconnect=None):
- """Send raw data across the stream.
-
- :param string data: Any string value.
- :param bool reconnect: Indicates if the stream should be
- restarted if there is an error sending
- the stanza. Used mainly for testing.
- Defaults to :attr:`auto_reconnect`.
- """
- if now:
- log.debug("SEND (IMMED): %s", data)
- try:
- data = data.encode('utf-8')
- total = len(data)
- sent = 0
- count = 0
- tries = 0
- with self.send_lock:
- while sent < total and not self.stop.is_set():
- try:
- sent += self.socket.send(data[sent:])
- count += 1
- except ssl.SSLError as serr:
- if tries >= self.ssl_retry_max:
- log.debug('SSL error: max retries reached')
- self.exception(serr)
- log.warning("Failed to send %s", data)
- if reconnect is None:
- reconnect = self.auto_reconnect
- if not self.stop.is_set():
- self.disconnect(reconnect,
- send_close=False)
- log.warning('SSL write error: retrying')
- if not self.stop.is_set():
- time.sleep(self.ssl_retry_delay)
- tries += 1
- except Socket.error as serr:
- if serr.errno != errno.EINTR:
- raise
- if count > 1:
- log.debug('SENT: %d chunks', count)
- except (Socket.error, ssl.SSLError) as serr:
- self.event('socket_error', serr, direct=True)
- log.warning("Failed to send %s", data)
- if reconnect is None:
- reconnect = self.auto_reconnect
- if not self.stop.is_set():
- self.disconnect(reconnect, send_close=False)
- else:
- self.send_queue.put(data)
- return True
-
- def _start_thread(self, name, target, track=True):
- self.__thread[name] = threading.Thread(name=name, target=target)
- self.__thread[name].daemon = self._use_daemons
- self.__thread[name].start()
-
- if track:
- self.__active_threads.add(name)
- with self.__thread_cond:
- self.__thread_count += 1
-
- def _end_thread(self, name, early=False):
- with self.__thread_cond:
- curr_thread = threading.current_thread().name
- if curr_thread in self.__active_threads:
- self.__thread_count -= 1
- self.__active_threads.remove(curr_thread)
-
- if early:
- log.debug('Threading deadlock prevention!')
- log.debug(("Marked %s thread as ended due to " + \
- "disconnect() call. %s threads remain.") % (
- name, self.__thread_count))
- else:
- log.debug("Stopped %s thread. %s threads remain." % (
- name, self.__thread_count))
-
- else:
- log.debug(("Finished exiting %s thread after early " + \
- "termination from disconnect() call. " + \
- "%s threads remain.") % (
- name, self.__thread_count))
-
- if self.__thread_count == 0:
- self.__thread_cond.notify()
-
- def set_stop(self):
- self.stop.set()
-
- # Unlock queues
- self.event_queue.put(None)
- self.send_queue.put(None)
-
- def _wait_for_threads(self):
- with self.__thread_cond:
- if self.__thread_count != 0:
- log.debug("Waiting for %s threads to exit." %
- self.__thread_count)
- name = threading.current_thread().name
- if name in self.__thread:
- self._end_thread(name, early=True)
- self.__thread_cond.wait(4)
- if self.__thread_count != 0:
- log.error("Hanged threads: %s" % threading.enumerate())
- log.error("This may be due to calling disconnect() " + \
- "from a non-threaded event handler. Be " + \
- "sure that event handlers that call " + \
- "disconnect() are registered using: " + \
- "add_event_handler(..., threaded=True)")
-
- def process(self, **kwargs):
- """Initialize the XML streams and begin processing events.
-
- The number of threads used for processing stream events is determined
- by :data:`HANDLER_THREADS`.
-
- :param bool block: If ``False``, then event dispatcher will run
- in a separate thread, allowing for the stream to be
- used in the background for another application.
- Otherwise, ``process(block=True)`` blocks the current
- thread. Defaults to ``False``.
- :param bool threaded: **DEPRECATED**
- If ``True``, then event dispatcher will run
- in a separate thread, allowing for the stream to be
- used in the background for another application.
- Defaults to ``True``. This does **not** mean that no
- threads are used at all if ``threaded=False``.
-
- Regardless of these threading options, these threads will
- always exist:
-
- - The event queue processor
- - The send queue processor
- - The scheduler
- """
- if 'threaded' in kwargs and 'block' in kwargs:
- raise ValueError("process() called with both " + \
- "block and threaded arguments")
- elif 'block' in kwargs:
- threaded = not(kwargs.get('block', False))
- else:
- threaded = kwargs.get('threaded', True)
-
- for t in range(0, HANDLER_THREADS):
- log.debug("Starting HANDLER THREAD")
- self._start_thread('event_thread_%s' % t, self._event_runner)
-
- self._start_thread('send_thread', self._send_thread)
- self._start_thread('scheduler_thread', self._scheduler_thread)
-
- if threaded:
- # Run the XML stream in the background for another application.
- self._start_thread('read_thread', self._process, track=False)
- else:
- self._process()
-
- def _process(self):
- """Start processing the XML streams.
-
- Processing will continue after any recoverable errors
- if reconnections are allowed.
- """
-
- # The body of this loop will only execute once per connection.
- # Additional passes will be made only if an error occurs and
- # reconnecting is permitted.
- while True:
- shutdown = False
- try:
- # The call to self.__read_xml will block and prevent
- # the body of the loop from running until a disconnect
- # occurs. After any reconnection, the stream header will
- # be resent and processing will resume.
- while not self.stop.is_set():
- # Only process the stream while connected to the server
- if not self.state.ensure('connected', wait=0.1):
- break
- # Ensure the stream header is sent for any
- # new connections.
- if not self.session_started_event.is_set():
- self.send_raw(self.stream_header, now=True)
- if not self.__read_xml():
- # If the server terminated the stream, end processing
- break
- except KeyboardInterrupt:
- log.debug("Keyboard Escape Detected in _process")
- self.event('killed', direct=True)
- shutdown = True
- except SystemExit:
- log.debug("SystemExit in _process")
- shutdown = True
- except (SyntaxError, ExpatError) as e:
- log.error("Error reading from XML stream.")
- self.exception(e)
- except (Socket.error, ssl.SSLError) as serr:
- self.event('socket_error', serr, direct=True)
- log.error('Socket Error #%s: %s', serr.errno, serr.strerror)
- except ValueError as e:
- msg = e.message if hasattr(e, 'message') else e.args[0]
-
- if 'I/O operation on closed file' in msg:
- log.error('Can not read from closed socket.')
- else:
- self.exception(e)
- except Exception as e:
- if not self.stop.is_set():
- log.error('Connection error.')
- self.exception(e)
-
- if not shutdown and not self.stop.is_set() \
- and self.auto_reconnect:
- self.reconnect()
- else:
- self.disconnect()
- break
-
- def __read_xml(self):
- """Parse the incoming XML stream
-
- Stream events are raised for each received stanza.
- """
- depth = 0
- root = None
- for event, xml in ET.iterparse(self.filesocket, (b'end', b'start')):
- if event == b'start':
- if depth == 0:
- # We have received the start of the root element.
- root = xml
- log.debug('RECV: %s', tostring(root, xmlns=self.default_ns,
- stream=self,
- top_level=True,
- open_only=True))
- # Perform any stream initialization actions, such
- # as handshakes.
- self.stream_end_event.clear()
- self.start_stream_handler(root)
-
- # We have a successful stream connection, so reset
- # exponential backoff for new reconnect attempts.
- self.reconnect_delay = 1.0
- depth += 1
- if event == b'end':
- depth -= 1
- if depth == 0:
- # The stream's root element has closed,
- # terminating the stream.
- log.debug("End of stream recieved")
- self.stream_end_event.set()
- return False
- elif depth == 1:
- # We only raise events for stanzas that are direct
- # children of the root element.
- try:
- self.__spawn_event(xml)
- except RestartStream:
- return True
- if root is not None:
- # Keep the root element empty of children to
- # save on memory use.
- root.clear()
- log.debug("Ending read XML loop")
-
- def _build_stanza(self, xml, default_ns=None):
- """Create a stanza object from a given XML object.
-
- If a specialized stanza type is not found for the XML, then
- a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase`
- stanza will be returned.
-
- :param xml: The :class:`~xml.etree.ElementTree.Element` XML object
- to convert into a stanza object.
- :param default_ns: Optional default namespace to use instead of the
- stream's current default namespace.
- """
- if default_ns is None:
- default_ns = self.default_ns
- stanza_type = StanzaBase
- for stanza_class in self.__root_stanza:
- if xml.tag == "{%s}%s" % (default_ns, stanza_class.name) or \
- xml.tag == stanza_class.tag_name():
- stanza_type = stanza_class
- break
- stanza = stanza_type(self, xml)
- if stanza['lang'] is None and self.peer_default_lang:
- stanza['lang'] = self.peer_default_lang
- return stanza
-
- def __spawn_event(self, xml):
- """
- Analyze incoming XML stanzas and convert them into stanza
- objects if applicable and queue stream events to be processed
- by matching handlers.
-
- :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
- stanza to analyze.
- """
- # Apply any preprocessing filters.
- xml = self.incoming_filter(xml)
-
- # Convert the raw XML object into a stanza object. If no registered
- # stanza type applies, a generic StanzaBase stanza will be used.
- stanza = self._build_stanza(xml)
-
- for filter in self.__filters['in']:
- if stanza is not None:
- stanza = filter(stanza)
- if stanza is None:
- return
-
- log.debug("RECV: %s", stanza)
-
- # Match the stanza against registered handlers. Handlers marked
- # to run "in stream" will be executed immediately; the rest will
- # be queued.
- unhandled = True
- matched_handlers = [h for h in self.__handlers if h.match(stanza)]
- for handler in matched_handlers:
- if len(matched_handlers) > 1:
- stanza_copy = copy.copy(stanza)
- else:
- stanza_copy = stanza
- handler.prerun(stanza_copy)
- self.event_queue.put(('stanza', handler, stanza_copy))
- try:
- if handler.check_delete():
- self.__handlers.remove(handler)
- except:
- pass # not thread safe
- unhandled = False
-
- # Some stanzas require responses, such as Iq queries. A default
- # handler will be executed immediately for this case.
- if unhandled:
- stanza.unhandled()
-
- def _threaded_event_wrapper(self, func, args):
- """Capture exceptions for event handlers that run
- in individual threads.
-
- :param func: The event handler to execute.
- :param args: Arguments to the event handler.
- """
- # this is always already copied before this is invoked
- orig = args[0]
- try:
- func(*args)
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.exception(error_msg, str(func))
- if hasattr(orig, 'exception'):
- orig.exception(e)
- else:
- self.exception(e)
-
- def _event_runner(self):
- """Process the event queue and execute handlers.
-
- The number of event runner threads is controlled by HANDLER_THREADS.
-
- Stream event handlers will all execute in this thread. Custom event
- handlers may be spawned in individual threads.
- """
- log.debug("Loading event runner")
- try:
- while not self.stop.is_set():
- event = self.event_queue.get()
- if event is None:
- continue
-
- etype, handler = event[0:2]
- args = event[2:]
- orig = copy.copy(args[0])
-
- if etype == 'stanza':
- try:
- handler.run(args[0])
- except Exception as e:
- error_msg = 'Error processing stream handler: %s'
- log.exception(error_msg, handler.name)
- orig.exception(e)
- elif etype == 'schedule':
- name = args[2]
- try:
- log.debug('Scheduled event: %s: %s', name, args[0])
- handler(*args[0], **args[1])
- except Exception as e:
- log.exception('Error processing scheduled task')
- self.exception(e)
- elif etype == 'event':
- func, threaded, disposable = handler
- try:
- if threaded:
- x = threading.Thread(
- name="Event_%s" % str(func),
- target=self._threaded_event_wrapper,
- args=(func, args))
- x.daemon = self._use_daemons
- x.start()
- else:
- func(*args)
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.exception(error_msg, str(func))
- if hasattr(orig, 'exception'):
- orig.exception(e)
- else:
- self.exception(e)
- elif etype == 'quit':
- log.debug("Quitting event runner thread")
- break
- except KeyboardInterrupt:
- log.debug("Keyboard Escape Detected in _event_runner")
- self.event('killed', direct=True)
- self.disconnect()
- except SystemExit:
- self.disconnect()
- self.event_queue.put(('quit', None, None))
-
- self._end_thread('event runner')
-
- def _send_thread(self):
- """Extract stanzas from the send queue and send them on the stream."""
- try:
- while not self.stop.is_set():
- while not self.stop.is_set() and \
- not self.session_started_event.is_set():
- self.session_started_event.wait(timeout=0.1) # Wait for session start
- if self.__failed_send_stanza is not None:
- data = self.__failed_send_stanza
- self.__failed_send_stanza = None
- else:
- data = self.send_queue.get() # Wait for data to send
- if data is None:
- continue
- log.debug("SEND: %s", data)
- enc_data = data.encode('utf-8')
- total = len(enc_data)
- sent = 0
- count = 0
- tries = 0
- try:
- with self.send_lock:
- while sent < total and not self.stop.is_set() and \
- self.session_started_event.is_set():
- try:
- sent += self.socket.send(enc_data[sent:])
- count += 1
- except ssl.SSLError as serr:
- if tries >= self.ssl_retry_max:
- log.debug('SSL error: max retries reached')
- self.exception(serr)
- log.warning("Failed to send %s", data)
- if not self.stop.is_set():
- self.disconnect(self.auto_reconnect,
- send_close=False)
- log.warning('SSL write error: retrying')
- if not self.stop.is_set():
- time.sleep(self.ssl_retry_delay)
- tries += 1
- except Socket.error as serr:
- if serr.errno != errno.EINTR:
- raise
- if count > 1:
- log.debug('SENT: %d chunks', count)
- self.send_queue.task_done()
- except (Socket.error, ssl.SSLError) as serr:
- self.event('socket_error', serr, direct=True)
- log.warning("Failed to send %s", data)
- if not self.stop.is_set():
- self.__failed_send_stanza = data
- self._end_thread('send')
- self.disconnect(self.auto_reconnect, send_close=False)
- return
- except Exception as ex:
- log.exception('Unexpected error in send thread: %s', ex)
- self.exception(ex)
- if not self.stop.is_set():
- self._end_thread('send')
- self.disconnect(self.auto_reconnect)
- return
-
- self._end_thread('send')
-
- def _scheduler_thread(self):
- self.scheduler.process(threaded=False)
- self._end_thread('scheduler')
-
- def exception(self, exception):
- """Process an unknown exception.
-
- Meant to be overridden.
-
- :param exception: An unhandled exception object.
- """
- pass
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-XMLStream.startTLS = XMLStream.start_tls
-XMLStream.registerStanza = XMLStream.register_stanza
-XMLStream.removeStanza = XMLStream.remove_stanza
-XMLStream.registerHandler = XMLStream.register_handler
-XMLStream.removeHandler = XMLStream.remove_handler
-XMLStream.setSocket = XMLStream.set_socket
-XMLStream.sendRaw = XMLStream.send_raw
-XMLStream.getId = XMLStream.get_id
-XMLStream.getNewId = XMLStream.new_id
-XMLStream.sendXML = XMLStream.send_xml