aboutsummaryrefslogtreecommitdiffstats
path: root/spec/scansion
diff options
context:
space:
mode:
Diffstat (limited to 'spec/scansion')
-rw-r--r--spec/scansion/basic_message.scs6
-rw-r--r--spec/scansion/blocking.scs8
-rw-r--r--spec/scansion/extdisco.scs57
-rw-r--r--spec/scansion/http_upload.scs83
-rw-r--r--spec/scansion/keep_full_sub_req.scs58
-rw-r--r--spec/scansion/lastactivity.scs45
-rw-r--r--spec/scansion/mam_extended.scs126
-rw-r--r--spec/scansion/muc_create_destroy.scs316
-rw-r--r--spec/scansion/muc_members_only_change.scs2
-rw-r--r--spec/scansion/muc_nickname_change.scs127
-rw-r--r--spec/scansion/muc_nickname_robotface.scs46
-rw-r--r--spec/scansion/muc_password.scs2
-rw-r--r--spec/scansion/muc_presence_probe.scs178
-rw-r--r--spec/scansion/muc_register.scs14
-rw-r--r--spec/scansion/muc_show_offline.scs544
-rw-r--r--spec/scansion/muc_subject_issue_667.scs129
-rw-r--r--spec/scansion/presence_preapproval.scs74
-rw-r--r--spec/scansion/prosody.cfg.lua71
-rw-r--r--spec/scansion/pubsub_advanced.scs6
-rw-r--r--spec/scansion/pubsub_basic.scs2
-rw-r--r--spec/scansion/pubsub_preconditions.scs234
-rw-r--r--spec/scansion/server_contact_info.scs81
-rw-r--r--spec/scansion/uptime.scs21
-rw-r--r--spec/scansion/version.scs27
24 files changed, 2233 insertions, 24 deletions
diff --git a/spec/scansion/basic_message.scs b/spec/scansion/basic_message.scs
index fb21c465..1258dbf5 100644
--- a/spec/scansion/basic_message.scs
+++ b/spec/scansion/basic_message.scs
@@ -79,7 +79,7 @@ Juliet's phone receives:
<message from="${Romeo's full JID}" type="chat">
<body>Hello Juliet, are you there?</body>
<delay xmlns='urn:xmpp:delay' from='localhost' stamp='{scansion:any}' />
- </message>
+ </message>
# Romeo sends another bare-JID message, it should be delivered
# instantly to Juliet's phone
@@ -92,7 +92,7 @@ Romeo sends:
Juliet's phone receives:
<message from="${Romeo's full JID}" type="chat">
<body>Oh, hi!</body>
- </message>
+ </message>
# Juliet's laptop goes online, but with a negative priority
@@ -122,7 +122,7 @@ Romeo sends:
Juliet's phone receives:
<message from="${Romeo's full JID}" type="chat">
<body>How are you?</body>
- </message>
+ </message>
# Romeo sends direct to Juliet's full JID, and she should receive it
diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
index 6a9f199e..5bb5a41b 100644
--- a/spec/scansion/blocking.scs
+++ b/spec/scansion/blocking.scs
@@ -145,16 +145,16 @@ Juliet receives:
</message>
# Bye
-Juliet disconnects
-
Juliet sends:
<presence type="unavailable"/>
+Juliet disconnects
+
Romeo receives:
<presence from="${Juliet's full JID}" to="${Romeo's JID}" type="unavailable"/>
-Romeo disconnects
-
Romeo sends:
<presence type="unavailable"/>
+Romeo disconnects
+
diff --git a/spec/scansion/extdisco.scs b/spec/scansion/extdisco.scs
new file mode 100644
index 00000000..fd73c9da
--- /dev/null
+++ b/spec/scansion/extdisco.scs
@@ -0,0 +1,57 @@
+# XEP-0215: External Service Discovery
+
+[Client] Romeo
+ password: password
+ jid: user@localhost/mFquWxSr
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx2' to='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx2' from='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'>
+ <service host='default.example' transport='udp' port='9876' type='stun'/>
+ <service port='9876' type='turn' restricted='1' password='yHYYBDN7M3mdlug0LTdJbW0GvvQ=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='9876' type='turn' restricted='1' password='1Uc6QfrDhIlbK97rGCUQ/cUICxs=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='2121' type='ftp' restricted='1' password='password' transport='tcp' host='default.example' username='john'/>
+ <service port='21' type='ftp' restricted='1' password='password' transport='tcp' host='ftp.example.com' username='john'/>
+ </services>
+ </iq>
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx3' to='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2' type='ftp'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx3' from='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'>
+ <service port='2121' type='ftp' restricted='1' password='password' transport='tcp' host='default.example' username='john'/>
+ <service port='21' type='ftp' restricted='1' password='password' transport='tcp' host='ftp.example.com' username='john'/>
+ </services>
+ </iq>
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx4' to='localhost'>
+ <credentials xmlns='urn:xmpp:extdisco:2'>
+ <service host='default.example' type='turn'/>
+ </credentials>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx4' from='localhost'>
+ <credentials xmlns='urn:xmpp:extdisco:2'>
+ <service port='9876' type='turn' restricted='1' password='yHYYBDN7M3mdlug0LTdJbW0GvvQ=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='9876' type='turn' restricted='1' password='1Uc6QfrDhIlbK97rGCUQ/cUICxs=' transport='udp' host='default.example' username='1219525744'/>
+ </credentials>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2020-07-18T16:47:57Z
diff --git a/spec/scansion/http_upload.scs b/spec/scansion/http_upload.scs
new file mode 100644
index 00000000..b3031bac
--- /dev/null
+++ b/spec/scansion/http_upload.scs
@@ -0,0 +1,83 @@
+# XEP-0363 HTTP Upload with mod_http_file_share
+
+[Client] Romeo
+ password: password
+ jid: filesharingenthusiast@localhost/krxLaE3s
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='932c02fe-4461-4ad4-9c85-54863294b4dc' xml:lang='en'>
+ <request content-type='text/plain' filename='verysmall.dat' xmlns='urn:xmpp:http:upload:0' size='5'/>
+ </iq>
+
+Romeo receives:
+ <iq id='932c02fe-4461-4ad4-9c85-54863294b4dc' from='upload.localhost' type='result'>
+ <slot xmlns='urn:xmpp:http:upload:0'>
+ <get url='{scansion:any}'/>
+ <put url='{scansion:any}'>
+ <header name='Authorization'></header>
+ </put>
+ </slot>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='46ca64f3-518e-42bd-8e2f-4ab2f6d2224f' xml:lang='en'>
+ <request content-type='text/plain' filename='toolarge.dat' xmlns='urn:xmpp:http:upload:0' size='10000000000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='46ca64f3-518e-42bd-8e2f-4ab2f6d2224f' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large</text>
+ <file-too-large xmlns='urn:xmpp:http:upload:0'>
+ <max-file-size>10000000</max-file-size>
+ </file-too-large>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='497c20dd-dda2-4feb-8199-7086e203de46' xml:lang='en'>
+ <request content-type='text/plain' filename='negative.dat' xmlns='urn:xmpp:http:upload:0' size='-1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='497c20dd-dda2-4feb-8199-7086e203de46' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File size must be positive integer</text>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='ac56d83f-a627-4732-8399-60492d1210b6' xml:lang='en'>
+ <request content-type='text/plain' filename='invalid/filename.dat' xmlns='urn:xmpp:http:upload:0' size='1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='ac56d83f-a627-4732-8399-60492d1210b6' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Invalid filename</text>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='1401d3b5-7973-486f-85b3-3e63d13c7f0e' xml:lang='en'>
+ <request content-type='application/x-executable' filename='evil.exe' xmlns='urn:xmpp:http:upload:0' size='1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='1401d3b5-7973-486f-85b3-3e63d13c7f0e' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File type not allowed</text>
+ </error>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2021-01-27T22:10:46Z
diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs
new file mode 100644
index 00000000..41ffec0d
--- /dev/null
+++ b/spec/scansion/keep_full_sub_req.scs
@@ -0,0 +1,58 @@
+# server MUST keep a record of the complete presence stanza comprising the subscription request (#689)
+
+[Client] Alice
+ jid: pars-a@localhost
+ password: password
+
+[Client] Bob
+ jid: pars-b@localhost
+ password: password
+
+[Client] Bob's phone
+ jid: pars-b@localhost/phone
+ password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+ <presence to="${Bob's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Alice disconnects
+
+Bob connects
+
+Bob sends:
+ <presence/>
+
+Bob receives:
+ <presence from="${Bob's full JID}"/>
+
+Bob receives:
+ <presence from="${Alice's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Bob disconnects
+
+# Works if they reconnect too
+
+Bob's phone connects
+
+Bob's phone sends:
+ <presence/>
+
+Bob's phone receives:
+ <presence from="${Bob's phone's full JID}"/>
+
+
+Bob's phone receives:
+ <presence from="${Alice's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Bob's phone disconnects
+
diff --git a/spec/scansion/lastactivity.scs b/spec/scansion/lastactivity.scs
new file mode 100644
index 00000000..44f4e516
--- /dev/null
+++ b/spec/scansion/lastactivity.scs
@@ -0,0 +1,45 @@
+# XEP-0012: Last Activity / mod_lastactivity
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence>
+ <status>Hello</status>
+ </presence>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}">
+ <status>Hello</status>
+ </presence>
+
+Romeo sends:
+ <presence type="unavailable">
+ <status>Goodbye</status>
+ </presence>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" type="unavailable">
+ <status>Goodbye</status>
+ </presence>
+
+# mod_lastlog saves time + status message from the last unavailable presence
+
+Romeo sends:
+ <iq id='a' type='get'>
+ <query xmlns='jabber:iq:last'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='a'>
+ <query xmlns='jabber:iq:last' seconds='0'>Goodbye</query>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2020-04-20T14:39:47Z
diff --git a/spec/scansion/mam_extended.scs b/spec/scansion/mam_extended.scs
new file mode 100644
index 00000000..2c6840df
--- /dev/null
+++ b/spec/scansion/mam_extended.scs
@@ -0,0 +1,126 @@
+# MAM 0.7.x Extended features
+
+[Client] Romeo
+ jid: extmamtester@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+# Enable MAM so we can save some messages
+Romeo sends:
+ <iq type="set" id="enablemam">
+ <prefs xmlns="urn:xmpp:mam:2" default="always">
+ <always/>
+ <never/>
+ </prefs>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="enablemam">
+ <prefs xmlns="urn:xmpp:mam:2" default="always">
+ <always/>
+ <never/>
+ </prefs>
+ </iq>
+
+# Some messages to look for later
+Romeo sends:
+ <message to="someone@localhost" type="chat" id="chat01">
+ <body>Hello</body>
+ </message>
+
+Romeo sends:
+ <message to="someone@localhost" type="chat" id="chat02">
+ <body>U there?</body>
+ </message>
+
+# Metadata
+Romeo sends:
+ <iq type="get" id="mamextmeta">
+ <metadata xmlns="urn:xmpp:mam:2"/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="mamextmeta">
+ <metadata xmlns="urn:xmpp:mam:2">
+ <start timestamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:mam:2" id="{scansion:any}"/>
+ <end timestamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:mam:2" id="{scansion:any}"/>
+ </metadata>
+ </iq>
+
+Romeo sends:
+ <iq type="set" id="mamquery1">
+ <query xmlns="urn:xmpp:mam:2" queryid="q1"/>
+ </iq>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat01" from="${Romeo's full JID}">
+ <body>Hello</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat02" from="${Romeo's full JID}">
+ <body>U there?</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+# FIXME unstable tag order from util.rsm
+Romeo receives:
+ <iq type="result" id="mamquery1" to="${Romeo's full JID}">
+ <fin xmlns="urn:xmpp:mam:2" complete="true" scansion:strict="false">
+ </fin>
+ </iq>
+
+# Get results in reverse order
+Romeo sends:
+ <iq type="set" id="mamquery2">
+ <query xmlns="urn:xmpp:mam:2" queryid="q1">
+ <flip-page/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat02" from="${Romeo's full JID}">
+ <body>U there?</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat01" from="${Romeo's full JID}">
+ <body>Hello</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+# FIXME unstable tag order from util.rsm
+Romeo receives:
+ <iq type="result" id="mamquery2" to="${Romeo's full JID}">
+ <fin xmlns="urn:xmpp:mam:2" complete="true" scansion:strict="false">
+ </fin>
+ </iq>
diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs
new file mode 100644
index 00000000..789d4c41
--- /dev/null
+++ b/spec/scansion/muc_create_destroy.scs
@@ -0,0 +1,316 @@
+# MUC creation, basic messages and destruction
+
+[Client] Romeo
+ jid: romeo@localhost/mK0dD6Ha
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost/lVwkim_k
+ password: password
+
+[Client] Admin
+ jid: admin@localhost/DfNgg9VE
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="garden@conference.localhost/romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="moderator"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message from="garden@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq to="garden@conference.localhost" id="lx3" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <x type="submit" xmlns="jabber:x:data"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="lx3" type="result" from="garden@conference.localhost"/>
+
+Juliet connects
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm1">
+ <body>Where are thou my Juliet?</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="garden@conference.localhost/romeo" id="rm1">
+ <body>Where are thou my Juliet?</body>
+ </message>
+
+Juliet sends:
+ <presence to="garden@conference.localhost/juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator"/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/juliet">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="none" jid="${Juliet's full JID}" role="participant"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <message from="garden@conference.localhost/romeo" id="rm1" type="groupchat">
+ <body>Where are thou my Juliet?</body>
+ <delay stamp="{scansion:any}" xmlns="urn:xmpp:delay" from="garden@conference.localhost"/>
+ </message>
+
+Juliet receives:
+ <message from="garden@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/juliet">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="none" jid="${Juliet's full JID}" role="participant"/>
+ </x>
+ </presence>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm1">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm1" from="garden@conference.localhost/juliet">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm1" from="garden@conference.localhost/juliet">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm2">
+ <body>Here I am!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm2" from="garden@conference.localhost/juliet">
+ <body>Here I am!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm2" from="garden@conference.localhost/juliet">
+ <body>Here I am!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm2">
+ <body>What is this place?</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm2" from="garden@conference.localhost/romeo">
+ <body>What is this place?</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm2" from="garden@conference.localhost/romeo">
+ <body>What is this place?</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm3">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm3" from="garden@conference.localhost/juliet">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm3" from="garden@conference.localhost/juliet">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm3">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm3" from="garden@conference.localhost/romeo">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm3" from="garden@conference.localhost/romeo">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm4">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm4" from="garden@conference.localhost/juliet">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm4" from="garden@conference.localhost/juliet">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm4">
+ <body>Yes!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm4" from="garden@conference.localhost/romeo">
+ <body>Yes!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm4" from="garden@conference.localhost/romeo">
+ <body>Yes!</body>
+ </message>
+
+Romeo sends:
+ <iq to="garden@conference.localhost" id="lx4" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ </query>
+ </iq>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/juliet" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ <item affiliation="none" jid="${Juliet's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/romeo" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq id="lx4" type="result" from="garden@conference.localhost"/>
+
+Juliet disconnects
+
+Romeo sends:
+ <presence to="elsewhere@conference.localhost/romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="elsewhere@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="moderator"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message from="elsewhere@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq to="elsewhere@conference.localhost" id="lx5" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <x type="submit" xmlns="jabber:x:data"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="lx5" type="result" from="elsewhere@conference.localhost"/>
+
+Admin connects
+
+Admin sends:
+ <iq id="destroy" type="set" to="conference.localhost">
+ <command xmlns="http://jabber.org/protocol/commands" node="http://prosody.im/protocol/muc#destroy">
+ <x xmlns="jabber:x:data">
+ <field var="rooms">
+ <value>elsewhere@conference.localhost</value>
+ </field>
+ </x>
+ </command>
+ </iq>
+
+Romeo receives:
+ <presence from="elsewhere@conference.localhost/romeo" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo disconnects
+
+Admin receives:
+ <iq id="destroy" type="result" from="conference.localhost">
+ <command xmlns="http://jabber.org/protocol/commands" node="http://prosody.im/protocol/muc#destroy" status="completed" sessionid="{scansion:any}">
+ <note type="info">The following rooms were destroyed:&#10;elsewhere@conference.localhost</note>
+ </command>
+ </iq>
+
+Admin disconnects
+
+# recording ended on 2019-08-31T13:45:32Z
diff --git a/spec/scansion/muc_members_only_change.scs b/spec/scansion/muc_members_only_change.scs
index dc40b5a0..a708dbfb 100644
--- a/spec/scansion/muc_members_only_change.scs
+++ b/spec/scansion/muc_members_only_change.scs
@@ -94,7 +94,7 @@ Romeo sends:
<item affiliation='none' jid="${Juliet's JID}" />
</query>
</iq>
-
+
# As a non-member, Juliet must now be removed from the room
Romeo receives:
<presence type='unavailable' from='room@conference.localhost/Juliet'>
diff --git a/spec/scansion/muc_nickname_change.scs b/spec/scansion/muc_nickname_change.scs
new file mode 100644
index 00000000..73f81203
--- /dev/null
+++ b/spec/scansion/muc_nickname_change.scs
@@ -0,0 +1,127 @@
+# MUC: Change nickname
+# Make sure a role is not reset, see #1466
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_moderatedroom'>
+ <value>1</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result"/>
+
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item role='moderator' xmlns='http://jabber.org/protocol/muc#user' affiliation='owner'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation="none" role="visitor" jid="${Juliet's full JID}"/>
+ </x>
+ </presence>
+
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_moderatedroom'>
+ <value>0</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result"/>
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='104'/>
+ </x>
+ </message>
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet2">
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='303'/>
+ <item nick='Juliet2' jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet2'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
diff --git a/spec/scansion/muc_nickname_robotface.scs b/spec/scansion/muc_nickname_robotface.scs
new file mode 100644
index 00000000..160c13d6
--- /dev/null
+++ b/spec/scansion/muc_nickname_robotface.scs
@@ -0,0 +1,46 @@
+# MUC: Prevent nicknames failing strict resourceprep
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Roboteo
+ jid: bot@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="nobots@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='nobots@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='nobots@conference.localhost'><subject/></message>
+
+Roboteo connects
+
+Roboteo sends:
+ <presence to="nobots@conference.localhost/🤖️">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Roboteo receives:
+ <presence type='error' from='nobots@conference.localhost/🤖'>
+ <error by='nobots@conference.localhost' type='modify'>
+ <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Nickname must pass strict validation</text>
+ </error>
+ </presence>
+
diff --git a/spec/scansion/muc_password.scs b/spec/scansion/muc_password.scs
index 82611183..63c821e0 100644
--- a/spec/scansion/muc_password.scs
+++ b/spec/scansion/muc_password.scs
@@ -58,7 +58,7 @@ Juliet sends:
Juliet receives:
<presence from="room@conference.localhost/Juliet" type="error">
- <error type="auth" code="401">
+ <error type="auth" code="401" by="room@conference.localhost">
<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
diff --git a/spec/scansion/muc_presence_probe.scs b/spec/scansion/muc_presence_probe.scs
new file mode 100644
index 00000000..1fb5d9f5
--- /dev/null
+++ b/spec/scansion/muc_presence_probe.scs
@@ -0,0 +1,178 @@
+# #1535 Let MUCs respond to presence probes
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Mercutio
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+# Romeo joins the MUC
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Disable presences for non-mods
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_presencebroadcast'>
+ <value>moderator</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Romeo probes himself
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ </x>
+ </presence>
+
+# Juliet tries to probe Romeo before joining the room
+
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Romeo" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+# Juliet tries to probe Mercutio (who's not in the MUC) before joining the room
+
+Juliet sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Mercutio" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+# Juliet joins the room
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Romeo probes Juliet
+
+Romeo sends:
+ <presence to="room@conference.localhost/Juliet" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+
+# Mercutio tries to probe himself in a MUC before joining
+
+Mercutio connects
+
+Mercutio sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Mercutio receives:
+ <presence from="room@conference.localhost/Mercutio" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+
+# Romeo makes Mercutio a member and registers his nickname
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Mercutio's JID}" nick="Mercutio"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Mercutio's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+
+# Romeo probes Mercutio, even though he's unavailable
+
+Romeo sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Mercutio' type="unavailable">
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item nick="Mercutio" affiliation='member' role='none' jid="${Mercutio's JID}" />
+ </x>
+ </presence>
diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs
index a7e57177..9fcce688 100644
--- a/spec/scansion/muc_register.scs
+++ b/spec/scansion/muc_register.scs
@@ -100,7 +100,9 @@ Juliet receives:
<field type='hidden' var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
- <field type='text-single' label='Nickname' var='muc#register_roomnick'/>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
</x>
</query>
</iq>
@@ -175,7 +177,7 @@ Rosaline sends:
Rosaline receives:
<presence type='error' from='room@conference.localhost/Juliet'>
- <error type='cancel'>
+ <error type='cancel' by='room@conference.localhost'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
<x xmlns='http://jabber.org/protocol/muc'/>
@@ -286,7 +288,7 @@ Rosaline sends:
Rosaline receives:
<presence type='error' from='room@conference.localhost/Juliet'>
- <error type='cancel'>
+ <error type='cancel' by='room@conference.localhost'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
<x xmlns='http://jabber.org/protocol/muc'/>
@@ -326,7 +328,7 @@ Romeo receives:
</iq>
# Romeo updates his own registration
-
+
Romeo sends:
<iq id='jw81b36f' to='room@conference.localhost' type='get'>
<query xmlns='jabber:iq:register'/>
@@ -339,7 +341,9 @@ Romeo receives:
<field type='hidden' var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
- <field type='text-single' label='Nickname' var='muc#register_roomnick'/>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
</x>
</query>
</iq>
diff --git a/spec/scansion/muc_show_offline.scs b/spec/scansion/muc_show_offline.scs
new file mode 100644
index 00000000..57b75ec7
--- /dev/null
+++ b/spec/scansion/muc_show_offline.scs
@@ -0,0 +1,544 @@
+# MUC: Room registration and presence broadcast of unavailable members
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Rosaline
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_presencebroadcast'>
+ <value>none</value>
+ <value>participant</value>
+ <value>moderator</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Juliet retrieves the registration form
+
+Juliet sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='jw81b36f'>
+ <query xmlns='jabber:iq:register'>
+ <x type='form' xmlns='jabber:x:data'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Juliet sends:
+ <iq id='nv71va54' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Juliet</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' jid="${Juliet's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+# Juliet discovers her reserved nick
+
+Juliet sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'>
+ <identity category='conference' name='Juliet' type='text'/>
+ </query>
+ </iq>
+
+# Juliet leaves the room:
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='none'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline connect and tries to join the room as Juliet
+
+Rosaline connects
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel' by='room@conference.localhost'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# In a heated moment, Juliet unregisters from the room
+
+Juliet sends:
+ <iq type='set' to='room@conference.localhost' id='unreg1'>
+ <query xmlns='jabber:iq:register'>
+ <remove/>
+ </query>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='unreg1'/>
+
+# Romeo is notified of Juliet's sad decision
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user' scansion:strict='true'>
+ <item jid="${Juliet's JID}" affiliation='none' />
+ </x>
+ </message>
+
+# Rosaline attempts once more to sneak into the room, disguised as Juliet
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' role='moderator'/>
+ </x>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ </x>
+ </presence>
+
+# On discovering the ruse, Romeo restores Juliet's nick and status within the room
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" nick='Juliet' />
+ </query>
+ </iq>
+
+# Rosaline is evicted from the room
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' role='none' jid="${Rosaline's full JID}">
+ <reason>This nickname is reserved</reason>
+ </item>
+ </x>
+ </presence>
+
+# An out-of-room affiliation change is received for Juliet
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq type='result' id='member1' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='none'>
+ <reason>This nickname is reserved</reason>
+ </item>
+ <status code='110'/>
+ </x>
+ </presence>
+
+# Rosaline, frustrated, attempts to get back into the room...
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# ...but once again, is denied
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel' by='room@conference.localhost'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# Juliet, however, quietly joins the room with success
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Romeo checks whether he has reserved his own nick yet
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+# But no nick is returned, as he hasn't registered yet!
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item' scansion:strict='true' />
+ </iq>
+
+# Romeo updates his own registration
+
+Romeo sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='jw81b36f'>
+ <query xmlns='jabber:iq:register'>
+ <x type='form' xmlns='jabber:x:data'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq id='nv71va54' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' jid="${Romeo's full JID}" role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item role='moderator' xmlns='http://jabber.org/protocol/muc#user' affiliation='owner'/>
+ </x>
+ </presence>
+
+# Romeo discovers his reserved nick
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'>
+ <identity category='conference' name='Romeo' type='text'/>
+ </query>
+ </iq>
+
+# To check the status of the room is as expected, Romeo requests the member list
+
+Romeo sends:
+ <iq id='member3' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member'/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='room@conference.localhost' type='result' id='member3'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item jid="${Juliet's JID}" affiliation='member' nick='Juliet'/>
+ </query>
+ </iq>
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable' />
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet' />
+
+# Rosaline joins as herself
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Rosaline">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='none' nick='Juliet' xmlns='http://jabber.org/protocol/muc#user'/>
+ </x>
+ </presence>
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Rosaline" />
+
+Rosaline receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Rosaline's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline tries to register her own nickname, but unaffiliated
+# registration is disabled by default
+
+Rosaline sends:
+ <iq id='reg990' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Rosaline receives:
+ <iq type='error' from='room@conference.localhost' id='reg990'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Rosaline sends:
+ <iq id='reg991' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Rosaline receives:
+ <iq id='reg991' type='error'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+# Romeo reserves her nickname for her
+
+Romeo sends:
+ <iq id='member2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Rosaline's JID}" nick='Rosaline' />
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor jid="${Romeo's full JID}" nick='Romeo'/>
+ </item>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' id='member2' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor nick='Romeo' />
+ </item>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
+ </x>
+ </presence>
+
+# Romeo sets their their own nickname via admin query (see #1273)
+Romeo sends:
+ <iq to="room@conference.localhost" id="reserve" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#admin">
+ <item nick="Romeo" affiliation="owner" jid="${Romeo's JID}"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item xmlns="http://jabber.org/protocol/muc#user" role="moderator" jid="${Romeo's full JID}" affiliation="owner">
+ <actor xmlns="http://jabber.org/protocol/muc#user" nick="Romeo"/>
+ </item>
+ <status xmlns="http://jabber.org/protocol/muc#user" code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq from="room@conference.localhost" id="reserve" type="result"/>
+
diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs
new file mode 100644
index 00000000..74980073
--- /dev/null
+++ b/spec/scansion/muc_subject_issue_667.scs
@@ -0,0 +1,129 @@
+# #667 MUC message with subject and body SHALL NOT be interpreted as a subject change
+
+[Client] Romeo
+ password: password
+ jid: romeo@localhost
+
+-----
+
+Romeo connects
+
+# and creates a room
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+# the default (empty) subject
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost">
+ <subject/>
+ </message>
+
+# this should be treated as a normal message
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+# Resync
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Presences
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+# the still empty subject
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost">
+ <subject/>
+ </message>
+
+# this is a subject change
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <subject>Something to talk about</subject>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Something to talk about</subject>
+ </message>
+
+# a message without <subject>
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+# Resync
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Presences
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+# History
+# These have delay tags but we ignore those for now
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+# Finally, the topic
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Something to talk about</subject>
+ </message>
+
+Romeo disconnects
+
diff --git a/spec/scansion/presence_preapproval.scs b/spec/scansion/presence_preapproval.scs
new file mode 100644
index 00000000..e34ac7cf
--- /dev/null
+++ b/spec/scansion/presence_preapproval.scs
@@ -0,0 +1,74 @@
+# server supports contact subscription pre-approval (RFC 6121 3.4)
+
+[Client] Alice
+ jid: preappove-a@localhost
+ password: password
+
+[Client] Bob
+ jid: preapprove-b@localhost
+ password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+ <presence/>
+
+Alice receives:
+ <presence/>
+
+Alice sends:
+ <presence to="${Bob's JID}" type="subscribed"/>
+
+Bob connects
+
+Bob sends:
+ <iq type="get" id="roster1">
+ <query xmlns="jabber:iq:roster"/>
+ </iq>
+
+Bob receives:
+ <iq type="result" id="roster1">
+ <query xmlns="jabber:iq:roster" ver="{scansion:any}">
+ </query>
+ </iq>
+
+Bob sends:
+ <presence/>
+
+Bob receives:
+ <presence from="${Bob's full JID}"/>
+
+Bob sends:
+ <presence to="${Alice's JID}" type="subscribe" />
+
+Bob receives:
+ <iq type='set' id='{scansion:any}'>
+ <query ver='1' xmlns='jabber:iq:roster'>
+ <item jid="${Alice's JID}" subscription='none' ask='subscribe' />
+ </query>
+ </iq>
+
+
+
+Bob receives:
+ <presence from="${Alice's JID}" type="subscribed" />
+
+Bob disconnects
+
+Alice sends:
+ <iq type="get" id="roster1">
+ <query xmlns="jabber:iq:roster"/>
+ </iq>
+
+Alice receives:
+ <iq type="result" id="roster1">
+ <query xmlns="jabber:iq:roster" ver="{scansion:any}">
+ <item jid="${Bob's JID}" subscription="from" />
+ </query>
+ </iq>
+
+Alice disconnects
+
+Bob disconnects
diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua
index f95ea31b..6d3980ed 100644
--- a/spec/scansion/prosody.cfg.lua
+++ b/spec/scansion/prosody.cfg.lua
@@ -1,23 +1,37 @@
--luacheck: ignore
+-- Mock time functions to simplify tests
+function _G.os.time()
+ return 1219439344;
+end
+package.preload["util.time"] = function ()
+ return {
+ now = function () return 1219439344.1; end;
+ monotonic = function () return 0.1; end;
+ }
+end
+
admins = { "admin@localhost" }
-use_libevent = true
+network_backend = "epoll"
+network_settings = {
+}
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
- "tls"; -- Add support for secure TLS on c2s/s2s connections
- "dialback"; -- s2s dialback support
+ --"tls"; -- Add support for secure TLS on c2s/s2s connections
+ --"dialback"; -- s2s dialback support
"disco"; -- Service discovery
-- Not essential, but recommended
"carbons"; -- Keep multiple clients in sync
- "pep"; -- Enables users to publish their mood, activity, playing music and more
+ "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more
"private"; -- Private XML storage (for room bookmarks, etc.)
"blocklist"; -- Allow users to block communications with other users
- "vcard"; -- Allow users to set vCards
+ "vcard4"; -- User profiles (stored in PEP)
+ "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
-- Nice to have
"version"; -- Replies to server version requests
@@ -26,6 +40,11 @@ modules_enabled = {
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"mam"; -- Store messages in an archive and allow users to access it
+ --"csi_simple"; -- Simple Mobile optimizations
+
+ -- Admin interfaces
+ --"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
+ --"admin_telnet"; -- Opens telnet console interface on localhost port 5582
-- HTTP modules
--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
@@ -35,18 +54,44 @@ modules_enabled = {
-- Other specific functionality
--"limits"; -- Enable bandwidth limiting for XMPP connections
--"groups"; -- Shared roster support
- --"server_contact_info"; -- Publish contact information for this service
+ "server_contact_info"; -- Publish contact information for this service
--"announce"; -- Send announcement to all online users
--"welcome"; -- Welcome users who register accounts
--"watchregistrations"; -- Alert admins of registrations
--"motd"; -- Send a message to users when they log in
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
--"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
+ "lastactivity";
+ "external_services";
-- Useful for testing
--"scansion_record"; -- Records things that happen in scansion test case format
}
+contact_info = {
+ abuse = { "mailto:abuse@localhost", "xmpp:abuse@localhost" };
+ admin = { "mailto:admin@localhost", "xmpp:admin@localhost" };
+ feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" };
+ sales = { "xmpp:sales@localhost" };
+ security = { "xmpp:security@localhost" };
+ status = { "gopher://status.localhost" };
+ support = { "https://localhost/support.html", "xmpp:support@localhost" };
+}
+
+external_service_host = "default.example"
+external_service_port = 9876
+external_service_secret = "<secret>"
+external_services = {
+ {type = "stun"; transport = "udp"};
+ {type = "turn"; transport = "udp"; secret = true};
+ {type = "turn"; transport = "udp"; secret = "foo"};
+ {type = "ftp"; transport = "tcp"; port = 2121; username = "john"; password = "password"};
+ {type = "ftp"; transport = "tcp"; host = "ftp.example.com"; port = 21; username = "john"; password = "password"};
+}
+
+modules_disabled = {
+ "s2s";
+}
certificate = "certs"
allow_registration = false
@@ -69,15 +114,25 @@ mam_smart_enable = true
-- Logging configuration
-- For advanced logging see https://prosody.im/doc/logging
-log = "*console"
+log = {"*console",debug = ENV_PROSODY_LOGFILE}
-daemonize = true
pidfile = "prosody.pid"
VirtualHost "localhost"
+hide_os_type = true -- absence tested for in version.scs
+
Component "conference.localhost" "muc"
storage = "memory"
+ admins = { "Admin@localhost" }
+ modules_enabled = {
+ "muc_mam";
+ }
+
Component "pubsub.localhost" "pubsub"
storage = "memory"
+
+Component "upload.localhost" "http_file_share"
+http_file_share_size_limit = 10000000
+http_file_share_allowed_file_types = { "text/plain", "image/*" }
diff --git a/spec/scansion/pubsub_advanced.scs b/spec/scansion/pubsub_advanced.scs
index c873486e..86410677 100644
--- a/spec/scansion/pubsub_advanced.scs
+++ b/spec/scansion/pubsub_advanced.scs
@@ -150,7 +150,11 @@ Juliet sends:
</iq>
Juliet receives:
- <iq type="result" id='unsub1'/>
+ <iq type="result" id='unsub1'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <subscription jid="${Juliet's full JID}" node='princely_musings' subscription='none'/>
+ </pubsub>
+ </iq>
Balthasar sends:
<iq type="set" to="pubsub.localhost" id='del1'>
diff --git a/spec/scansion/pubsub_basic.scs b/spec/scansion/pubsub_basic.scs
index d983ff66..0adf6857 100644
--- a/spec/scansion/pubsub_basic.scs
+++ b/spec/scansion/pubsub_basic.scs
@@ -32,7 +32,7 @@ Juliet connects
-- <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
-- </pubsub>
-- </iq>
---
+--
-- Juliet receives:
-- <iq type="error"/>
diff --git a/spec/scansion/pubsub_preconditions.scs b/spec/scansion/pubsub_preconditions.scs
new file mode 100644
index 00000000..25afaa8d
--- /dev/null
+++ b/spec/scansion/pubsub_preconditions.scs
@@ -0,0 +1,234 @@
+# Pubsub preconditions are enforced
+
+[Client] Romeo
+ password: password
+ jid: jqpcrbq2@localhost
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current">
+ <tune xmlns="http://jabber.org/protocol/tune"/>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="form">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" label="Title" type="text-single"/>
+ <field var="pubsub#description" label="Description" type="text-single"/>
+ <field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
+ <field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" label="Whether to deliver payloads with event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" label="Specify the delivery style for notifications" type="list-single">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" label="Whether to notify subscribers when the node is deleted" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" label="Whether to notify subscribers when items are removed from the node" type="boolean">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="submit">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" type="text-single" label="Title">
+ <value>Nice tunes</value>
+ </field>
+ <field var="pubsub#description" type="text-single" label="Description"/>
+ <field var="pubsub#type" type="text-single" label="The type of node data, usually specified by the namespace of the payload (if any)"/>
+ <field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" type="boolean" label="Persist items to storage">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field var="pubsub#deliver_notifications" type="boolean" label="Whether to deliver event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" type="boolean" label="Whether to deliver payloads with event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" type="list-single" label="Specify the delivery style for notifications">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" type="boolean" label="Whether to notify subscribers when the node is deleted">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" type="boolean" label="Whether to notify subscribers when items are removed from the node">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="result"/>
+
+Romeo sends:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="get">
+ <query xmlns="http://jabber.org/protocol/disco#items"/>
+ </iq>
+
+Romeo receives:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="result">
+ <query xmlns="http://jabber.org/protocol/disco#items">
+ <item name="Nice tunes" node="http://jabber.org/protocol/tune" jid="${Romeo's JID}"/>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current">
+ <tune xmlns="http://jabber.org/protocol/tune"/>
+ </item>
+ </publish>
+ <publish-options>
+ <x xmlns="jabber:x:data">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#publish-options</value>
+ </field>
+ <field var="pubsub#access_model">
+ <value>whitelist</value>
+ </field>
+ </x>
+ </publish-options>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type='error' id='67eb1f47-1e69-4cb3-91e2-4d5943e72d4c'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Field does not match: access_model</text>
+ <precondition-not-met xmlns='http://jabber.org/protocol/pubsub#errors'/>
+ </error>
+ </iq>
+
+Romeo disconnects
+
diff --git a/spec/scansion/server_contact_info.scs b/spec/scansion/server_contact_info.scs
new file mode 100644
index 00000000..f33d0957
--- /dev/null
+++ b/spec/scansion/server_contact_info.scs
@@ -0,0 +1,81 @@
+# XEP-0157: Contact Addresses for XMPP Services
+# mod_server_contact_info
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq type='get' id='lx2' to='localhost'>
+ <query xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+# Ignore other disco#info features, identities etc
+
+Romeo receives:
+ <iq from='localhost' id='lx2' type='result'>
+ <query xmlns='http://jabber.org/protocol/disco#info' scansion:strict='false'>
+ <x xmlns='jabber:x:data' type='result'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/network/serverinfo</value>
+ </field>
+ <field type='list-multi' var='abuse-addresses'>
+ <value>mailto:abuse@localhost</value>
+ <value>xmpp:abuse@localhost</value>
+ </field>
+ <field type='list-multi' var='admin-addresses'>
+ <value>mailto:admin@localhost</value>
+ <value>xmpp:admin@localhost</value>
+ </field>
+ <field type='list-multi' var='feedback-addresses'>
+ <value>http://localhost/feedback.html</value>
+ <value>mailto:feedback@localhost</value>
+ <value>xmpp:feedback@localhost</value>
+ </field>
+ <field type='list-multi' var='sales-addresses'>
+ <value>xmpp:sales@localhost</value>
+ </field>
+ <field type='list-multi' var='security-addresses'>
+ <value>xmpp:security@localhost</value>
+ </field>
+ <field type='list-multi' var='status-addresses'>
+ <value>gopher://status.localhost</value>
+ </field>
+ <field type='list-multi' var='support-addresses'>
+ <value>https://localhost/support.html</value>
+ <value>xmpp:support@localhost</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+
+Romeo sends:
+ <iq type='get' id='lx2' to='conference.localhost'>
+ <query xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+ <iq from='localhost' id='lx2' type='result'>
+ <query xmlns='http://jabber.org/protocol/disco#info' scansion:strict='false'>
+ <x xmlns='jabber:x:data' type='result'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/network/serverinfo</value>
+ </field>
+ <field type='list-multi' var='abuse-addresses'/>
+ <field type='list-multi' var='admin-addresses'>
+ <value>xmpp:admin@localhost</value>
+ </field>
+ <field type='list-multi' var='feedback-addresses'/>
+ <field type='list-multi' var='sales-addresses'/>
+ <field type='list-multi' var='security-addresses'/>
+ <field type='list-multi' var='status-addresses'/>
+ <field type='list-multi' var='support-addresses'/>
+ </x>
+ </query>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/uptime.scs b/spec/scansion/uptime.scs
new file mode 100644
index 00000000..188b9eb5
--- /dev/null
+++ b/spec/scansion/uptime.scs
@@ -0,0 +1,21 @@
+# XEP-0012: Last Activity / mod_uptime
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id='a' type='get' to='localhost'>
+ <query xmlns='jabber:iq:last'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='a' from='localhost'>
+ <query xmlns='jabber:iq:last' seconds='0'/>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/version.scs b/spec/scansion/version.scs
new file mode 100644
index 00000000..6c841dd9
--- /dev/null
+++ b/spec/scansion/version.scs
@@ -0,0 +1,27 @@
+# XEP-0092: Software Version / mod_version
+
+[Client] Romeo
+ password: password
+ jid: romeo@localhost/dfaZpuxV
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id='lx2' to='localhost' type='get'>
+ <query xmlns='jabber:iq:version'/>
+ </iq>
+
+# Version string would vary so we can't do an exact match atm
+# Inclusion of <os/> is disabled in the config, it should be absent
+Romeo receives:
+ <iq id='lx2' from='localhost' type='result'>
+ <query xmlns='jabber:iq:version' scansion:strict='true'>
+ <name>Prosody</name>
+ <version scansion:strict='false'/>
+ </query>
+ </iq>
+
+
+Romeo disconnects