cancer 0.1.0.a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/cancer.gemspec +156 -0
  7. data/documentation/STREAM_INITIATION.markdown +18 -0
  8. data/examples/example.rb +80 -0
  9. data/examples/example_2.rb +20 -0
  10. data/examples/example_em.rb +11 -0
  11. data/examples/example_em_helper.rb +23 -0
  12. data/examples/example_im_roster.rb +26 -0
  13. data/examples/example_xep_0004.rb +124 -0
  14. data/examples/example_xep_0047.rb +35 -0
  15. data/examples/example_xep_0050.rb +78 -0
  16. data/examples/example_xep_0065.rb +66 -0
  17. data/examples/example_xep_0096.dup.rb +40 -0
  18. data/examples/example_xep_0096_xep_0047.rb +42 -0
  19. data/examples/example_xep_0096_xep_0065.rb +40 -0
  20. data/lib/cancer.rb +122 -0
  21. data/lib/cancer/adapter.rb +33 -0
  22. data/lib/cancer/adapters/em.rb +88 -0
  23. data/lib/cancer/adapters/socket.rb +122 -0
  24. data/lib/cancer/builder.rb +28 -0
  25. data/lib/cancer/controller.rb +32 -0
  26. data/lib/cancer/dependencies.rb +187 -0
  27. data/lib/cancer/events/abstract_event.rb +10 -0
  28. data/lib/cancer/events/base_matchers.rb +63 -0
  29. data/lib/cancer/events/binary_matchers.rb +30 -0
  30. data/lib/cancer/events/eventable.rb +92 -0
  31. data/lib/cancer/events/exception_events.rb +28 -0
  32. data/lib/cancer/events/handler.rb +33 -0
  33. data/lib/cancer/events/manager.rb +130 -0
  34. data/lib/cancer/events/named_events.rb +25 -0
  35. data/lib/cancer/events/proc_matcher.rb +16 -0
  36. data/lib/cancer/events/xml_events.rb +44 -0
  37. data/lib/cancer/exceptions.rb +53 -0
  38. data/lib/cancer/jid.rb +215 -0
  39. data/lib/cancer/lock.rb +32 -0
  40. data/lib/cancer/spec.rb +35 -0
  41. data/lib/cancer/spec/error.rb +18 -0
  42. data/lib/cancer/spec/extentions.rb +79 -0
  43. data/lib/cancer/spec/matcher.rb +30 -0
  44. data/lib/cancer/spec/mock_adapter.rb +139 -0
  45. data/lib/cancer/spec/mock_stream.rb +15 -0
  46. data/lib/cancer/spec/transcript.rb +107 -0
  47. data/lib/cancer/stream.rb +182 -0
  48. data/lib/cancer/stream/builder.rb +20 -0
  49. data/lib/cancer/stream/controller.rb +36 -0
  50. data/lib/cancer/stream/event_helper.rb +12 -0
  51. data/lib/cancer/stream/xep.rb +52 -0
  52. data/lib/cancer/stream_parser.rb +144 -0
  53. data/lib/cancer/support.rb +27 -0
  54. data/lib/cancer/support/hash.rb +32 -0
  55. data/lib/cancer/support/string.rb +22 -0
  56. data/lib/cancer/synchronized_stanza.rb +79 -0
  57. data/lib/cancer/thread_pool.rb +118 -0
  58. data/lib/cancer/xep.rb +43 -0
  59. data/lib/cancer/xeps/core.rb +109 -0
  60. data/lib/cancer/xeps/core/bind.rb +33 -0
  61. data/lib/cancer/xeps/core/sasl.rb +113 -0
  62. data/lib/cancer/xeps/core/session.rb +22 -0
  63. data/lib/cancer/xeps/core/stream.rb +18 -0
  64. data/lib/cancer/xeps/core/terminator.rb +21 -0
  65. data/lib/cancer/xeps/core/tls.rb +34 -0
  66. data/lib/cancer/xeps/im.rb +323 -0
  67. data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
  68. data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
  69. data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
  70. data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
  71. data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
  72. data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
  73. data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
  74. data/lib/cancer/xeps/xep_0095_si.rb +211 -0
  75. data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
  76. data/lib/cancer/xeps/xep_0114_component.rb +73 -0
  77. data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
  78. data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
  79. data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
  80. data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
  81. data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
  82. data/spec/spec.opts +2 -0
  83. data/spec/spec_helper.rb +14 -0
  84. data/spec/stream/stanza_errors_spec.rb +47 -0
  85. data/spec/stream/stream_errors_spec.rb +38 -0
  86. data/spec/stream/stream_initialization_spec.rb +160 -0
  87. data/spec/xep_0050/local_spec.rb +165 -0
  88. data/spec/xep_0050/remote_spec.rb +44 -0
  89. metadata +200 -0
@@ -0,0 +1,40 @@
1
+
2
+ module Cancer
3
+ # Message Receipts
4
+ module XEP_0184
5
+ include Cancer::XEP
6
+
7
+ NS = 'urn:xmpp:receipts'
8
+
9
+ dependency 'core'
10
+
11
+ def self.enhance_stream(stream)
12
+ stream.extend_builder do
13
+ include Cancer::XEP_0184::Builder
14
+ end
15
+ stream.install_controller(Cancer::XEP_0184::Controller)
16
+ end
17
+
18
+ module Builder
19
+
20
+ def request_receipt
21
+ request :xmlns => NS
22
+ end
23
+
24
+ end
25
+
26
+ class Controller < Cancer::Controller
27
+
28
+ priority 1000
29
+
30
+ on { |e| e.xpath('/c:message/n:request', 'n' => NS, 'c' => CLIENT_NS) }
31
+ def respond_with_received(e)
32
+ send_message(e.sender, nil, e.id) do |x|
33
+ x.received :xmlns => NS
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+
2
+ module Cancer
3
+ # XMPP Ping
4
+ # http://xmpp.org/extensions/xep-0199.html
5
+ module XEP_0199
6
+ include Cancer::XEP
7
+
8
+ NS = 'urn:xmpp:ping'
9
+
10
+ dependency 'core'
11
+ dependency 'xep-0030'
12
+
13
+ def self.enhance_stream(stream)
14
+ stream.extend_stream do
15
+ include Cancer::XEP_0199::StreamHelpers
16
+ end
17
+ stream.install_controller(Cancer::XEP_0199::Controller)
18
+ stream.disco.feature(NS)
19
+ end
20
+
21
+ module StreamHelpers
22
+
23
+ def ping(to)
24
+ send_iq(to) do |x|
25
+ x.ping(:xmlns => NS)
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ class Controller < Cancer::Controller
32
+
33
+ on { |e| e.xpath('/c:iq/p:ping', 'p' => NS, 'c' => CLIENT_NS) }
34
+ def pong(e)
35
+ send_iq(e.sender, :result, e.id)
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,2 @@
1
+ -f specdoc
2
+ --color
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/cancer')
2
+
3
+ module Kernel
4
+ def hp(object)
5
+ puts "<pre>"+object.inspect.gsub('<', '&lt;')+"</pre>"
6
+ end
7
+ end
8
+
9
+ Cancer::Spec.debug = false
10
+ Cancer.logger.level = Logger::FATAL
11
+
12
+ Spec::Runner.configure do |config|
13
+ config.include(Cancer::Spec)
14
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Cancer::StanzaError do
4
+
5
+ it "should be raised when a stream error is received" do
6
+ dialog_with do |stream|
7
+
8
+ lambda do
9
+ stream.send_iq('knave@wonderland.lit')
10
+ end.should raise_error(Cancer::StanzaModifyError)
11
+
12
+ end.should_produce_transcript do |t|
13
+ t.out %{<iq type="get" to="knave@wonderland.lit" id="cancer-3"/>}
14
+ t.in %{<iq type="error" to="alice@wonderland.lit" id="cancer-3"><error type="modify"><bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /></error></iq>}
15
+ end
16
+ end
17
+
18
+ it "should contain error conditions" do
19
+ dialog_with do |stream|
20
+
21
+ lambda do
22
+ stream.send_iq('knave@wonderland.lit')
23
+ end.should raise_error(Cancer::StanzaModifyError) { |error|
24
+ error.conditions.should_not be_empty
25
+ error.conditions.first.name.should == 'bad-request'
26
+ }
27
+
28
+ end.should_produce_transcript do |t|
29
+ t.out %{<iq type="get" to="knave@wonderland.lit" id="cancer-3"/>}
30
+ t.in %{<iq type="error" to="alice@wonderland.lit" id="cancer-3"><error type="modify"><bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /></error></iq>}
31
+ end
32
+ end
33
+
34
+ it "should have message equal to error/text if present" do
35
+ dialog_with do |stream|
36
+
37
+ lambda do
38
+ stream.send_iq('knave@wonderland.lit')
39
+ end.should raise_error(Cancer::StanzaModifyError, 'My error message')
40
+
41
+ end.should_produce_transcript do |t|
42
+ t.out %{<iq type="get" to="knave@wonderland.lit" id="cancer-3"/>}
43
+ t.in %{<iq type="error" to="alice@wonderland.lit" id="cancer-3"><error type="modify"><bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /><text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>My error message</text></error></iq>}
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Cancer::StreamError do
4
+
5
+ it "should be raised when a stream error is received" do
6
+ lambda do
7
+ dialog_with do |stream|
8
+
9
+ end.should_produce_transcript do |t|
10
+ t.in %{<stream:error><bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>}
11
+ end
12
+ end.should raise_error(Cancer::StreamError)
13
+ end
14
+
15
+ it "should contain error conditions" do
16
+ lambda do
17
+ dialog_with do |stream|
18
+
19
+ end.should_produce_transcript do |t|
20
+ t.in %{<stream:error><bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>}
21
+ end
22
+ end.should raise_error(Cancer::StreamError) { |error|
23
+ error.conditions.should_not be_empty
24
+ error.conditions.first.name.should == 'bad-format'
25
+ }
26
+ end
27
+
28
+ it "should have message equal to stream:error/text if present" do
29
+ lambda do
30
+ dialog_with do |stream|
31
+
32
+ end.should_produce_transcript do |t|
33
+ t.in %{<stream:error><bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/><text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>My error message</text></stream:error>}
34
+ end
35
+ end.should raise_error(Cancer::StreamError, 'My error message')
36
+ end
37
+
38
+ end
@@ -0,0 +1,160 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Cancer::Stream do
4
+
5
+ it "should initiate with tls sasl bind and session" do
6
+ dialog do |adapter|
7
+ Cancer::Stream.open(:adapter => adapter,
8
+ :jid => 'alice@wonderland.lit', :password => 'ihatethequeen') { }
9
+ end.should_produce_transcript do |t|
10
+ t.start_stream('alice@wonderland.lit')
11
+ t.in %{<stream:features>
12
+ <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
13
+ <optional/>
14
+ </starttls>
15
+ <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
16
+ <mechanism>PLAIN</mechanism>
17
+ <required/>
18
+ </mechanisms>
19
+ </stream:features>}
20
+
21
+ t.out %{<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>}
22
+ t.in %{<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>}
23
+
24
+ t.start_stream('alice@wonderland.lit')
25
+ t.in %{<stream:features>
26
+ <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
27
+ <mechanism>PLAIN</mechanism>
28
+ <required/>
29
+ </mechanisms>
30
+ </stream:features>}
31
+
32
+ t.out %{<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGFsaWNlQHdvbmRlcmxhbmQubGl0AGloYXRldGhlcXVlZW4=</auth>}
33
+ t.in %{<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />}
34
+
35
+ t.start_stream('alice@wonderland.lit')
36
+ t.in %{<stream:features>
37
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
38
+ <required/>
39
+ </bind>
40
+ <session xmlns="urn:ietf:params:xml:ns:xmpp-session">
41
+ <optional/>
42
+ </session>
43
+ </stream:features>}
44
+
45
+ t.out %{<iq type="set" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>}
46
+ t.in %{<iq type="result" id="cancer-1">
47
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
48
+ <jid>alice@wonderland.lit/rabbithole</jid></bind></iq>}
49
+
50
+ t.out %{<iq type="set" id="cancer-2"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>}
51
+ t.in %{<iq type="result" id="cancer-2" />}
52
+
53
+ t.close_stream
54
+
55
+ end
56
+ end
57
+
58
+ it "should initiate with sasl bind and session" do
59
+ dialog do |adapter|
60
+ Cancer::Stream.open(:adapter => adapter,
61
+ :jid => 'alice@wonderland.lit', :password => 'ihatethequeen') { }
62
+ end.should_produce_transcript do |t|
63
+
64
+ t.start_stream('alice@wonderland.lit')
65
+ t.in %{<stream:features>
66
+ <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
67
+ <mechanism>PLAIN</mechanism>
68
+ <required/>
69
+ </mechanisms>
70
+ </stream:features>}
71
+
72
+ t.out %{<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGFsaWNlQHdvbmRlcmxhbmQubGl0AGloYXRldGhlcXVlZW4=</auth>}
73
+ t.in %{<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />}
74
+
75
+ t.start_stream('alice@wonderland.lit')
76
+ t.in %{<stream:features>
77
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
78
+ <required/>
79
+ </bind>
80
+ <session xmlns="urn:ietf:params:xml:ns:xmpp-session">
81
+ <optional/>
82
+ </session>
83
+ </stream:features>}
84
+
85
+ t.out %{<iq type="set" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>}
86
+ t.in %{<iq type="result" id="cancer-1">
87
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
88
+ <jid>alice@wonderland.lit/rabbithole</jid></bind></iq>}
89
+
90
+ t.out %{<iq type="set" id="cancer-2"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>}
91
+ t.in %{<iq type="result" id="cancer-2" />}
92
+
93
+ t.close_stream
94
+
95
+ end
96
+ end
97
+
98
+ it "should initiate with bind and session" do
99
+ dialog do |adapter|
100
+ Cancer::Stream.open(:adapter => adapter,
101
+ :jid => 'alice@wonderland.lit', :password => 'ihatethequeen') { }
102
+ end.should_produce_transcript do |t|
103
+
104
+ t.start_stream('alice@wonderland.lit')
105
+ t.in %{<stream:features>
106
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
107
+ <required/>
108
+ </bind>
109
+ <session xmlns="urn:ietf:params:xml:ns:xmpp-session">
110
+ <optional/>
111
+ </session>
112
+ </stream:features>}
113
+
114
+ t.out %{<iq type="set" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>}
115
+ t.in %{<iq type="result" id="cancer-1">
116
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
117
+ <jid>alice@wonderland.lit/rabbithole</jid></bind></iq>}
118
+
119
+ t.out %{<iq type="set" id="cancer-2"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>}
120
+ t.in %{<iq type="result" id="cancer-2" />}
121
+
122
+ t.close_stream
123
+
124
+ end
125
+ end
126
+
127
+ it "should initiate with bind" do
128
+ dialog do |adapter|
129
+ Cancer::Stream.open(:adapter => adapter,
130
+ :jid => 'alice@wonderland.lit', :password => 'ihatethequeen') { }
131
+ end.should_produce_transcript do |t|
132
+
133
+ t.start_stream('alice@wonderland.lit')
134
+ t.in %{<stream:features>
135
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
136
+ <required/>
137
+ </bind>
138
+ </stream:features>}
139
+
140
+ t.out %{<iq type="set" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>}
141
+ t.in %{<iq type="result" id="cancer-1">
142
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
143
+ <jid>alice@wonderland.lit/rabbithole</jid></bind></iq>}
144
+
145
+
146
+ t.close_stream
147
+
148
+ t.verify { |stream| stream.options[:jid].to_s.should == "alice@wonderland.lit/rabbithole" }
149
+ end
150
+ end
151
+
152
+ it "should initiate " do
153
+ dialog_with() do |stream|
154
+
155
+ end.should_produce_transcript do |t|
156
+ t.verify { |stream| stream.options[:jid].to_s.should == "alice@wonderland.lit/rabbithole" }
157
+ end
158
+ end
159
+
160
+ end
@@ -0,0 +1,165 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Cancer::XEP_0050, '#local' do
4
+
5
+ it "handles command events" do
6
+ dialog_with(:xeps => %w( xep-0050 )) do |stream|
7
+
8
+ stream.controller do
9
+
10
+ on { |e| e.command(:config) }
11
+ def config(e)
12
+ e.session.instance_variable_set :@id, 'config-1' # mock the session id
13
+
14
+ e.session.complete! do |x|
15
+ x.note(:type => 'info') do
16
+ x.text "Service 'httpd' has been configured."
17
+ end
18
+ end
19
+
20
+ ensure
21
+ $waiting_thread.wakeup
22
+ end
23
+
24
+ end
25
+
26
+ $waiting_thread = Thread.current and Thread.stop
27
+
28
+ end.should_produce_transcript do |t|
29
+ t.in %{<iq type="set" from="knave@wonderland.lit" id="cancer-3"><command xmlns='http://jabber.org/protocol/commands' node='config' action='execute'/></iq>}
30
+ t.out %{<iq type='result' to='knave@wonderland.lit' id='cancer-3'><command xmlns='http://jabber.org/protocol/commands' sessionid='config-1' node='config' status='completed'><note type='info'>Service 'httpd' has been configured.</note></command></iq>}
31
+ end
32
+ end
33
+
34
+ it "handles multistage commands" do
35
+ dialog_with(:xeps => %w( xep-0050 )) do |stream|
36
+
37
+ stream.controller do
38
+
39
+ define_form 'config.form' do |form|
40
+ form.title = "Setup your profile"
41
+ form.instructions << "Enter your profile in the fields below."
42
+
43
+ form.define :name do |f|
44
+ f.type = :'text-single'
45
+ f.required = true
46
+ f.label = "Name"
47
+ end
48
+
49
+ form.define :sex do |f|
50
+ f.type = :'list-single'
51
+ f.add_option 'm', 'Man'
52
+ f.add_option 'w', 'Woman'
53
+ f.add_option 'o', 'Other'
54
+ f.label = "Sex"
55
+ end
56
+
57
+ form.define :jid do |f|
58
+ f.type = :'jid-single'
59
+ f.required = true
60
+ f.add_option 'alice@wonderland.lit'
61
+ f.add_option 'knave@wonderland.lit'
62
+ f.label = "JID"
63
+ f.description = 'Choose your preferred jabber address.'
64
+ end
65
+ end
66
+
67
+ on { |e| e.command(:config) }
68
+ def config(e)
69
+
70
+ e.session.next! :run, 'config.form', :values => { :name => 'Simon Menke'}
71
+
72
+ throw :halt
73
+ rescue Exception => e
74
+ $action_exception = e
75
+ ensure
76
+ $waiting_thread.wakeup
77
+ end
78
+
79
+ on { |e| e.command(:config, :stage => :run) }
80
+ def config_run(e)
81
+
82
+ e.session.complete! do |x|
83
+ x.note(:type => 'info') do
84
+ x.text "Your profile has been updated."
85
+ end
86
+ end
87
+
88
+ rescue Exception => e
89
+ $action_exception = e
90
+ ensure
91
+ $waiting_thread.wakeup
92
+ end
93
+
94
+ end
95
+
96
+ $waiting_thread = Thread.current and Thread.stop
97
+ raise $action_exception if $action_exception
98
+
99
+ $waiting_thread = Thread.current and Thread.stop
100
+ raise $action_exception if $action_exception
101
+
102
+ end.should_produce_transcript do |t|
103
+ t.in %{
104
+ <iq type="set" from="knave@wonderland.lit" id="cancer-3">
105
+ <command xmlns="http://jabber.org/protocol/commands" node="config" action="execute" sessionid="config-1"/>
106
+ </iq>}
107
+
108
+ t.out %{
109
+ <iq type="result" to="knave@wonderland.lit" id="cancer-3">
110
+ <command xmlns="http://jabber.org/protocol/commands" status="executing" node="config" sessionid="config-1">
111
+ <actions/>
112
+ <x xmlns="jabber:x:data" type="form">
113
+ <title>Setup your profile</title>
114
+ <instructions>Enter your profile in the fields below.</instructions>
115
+ <field label="Name" var="name">
116
+ <required/>
117
+ <value>Simon Menke</value>
118
+ </field>
119
+ <field type="list-single" label="Sex" var="sex">
120
+ <option label="Woman">
121
+ <value>w</value>
122
+ </option>
123
+ <option label="Man">
124
+ <value>m</value>
125
+ </option>
126
+ <option label="Other">
127
+ <value>o</value>
128
+ </option>
129
+ </field>
130
+ <field type="jid-single" label="JID" var="jid">
131
+ <required/>
132
+ <desc>Choose your preferred jabber address.</desc>
133
+ <option>
134
+ <value>knave@wonderland.lit</value>
135
+ </option>
136
+ <option>
137
+ <value>alice@wonderland.lit</value>
138
+ </option>
139
+ </field>
140
+ </x>
141
+ </command>
142
+ </iq>}
143
+
144
+ t.in %{
145
+ <iq type="set" from="knave@wonderland.lit" id="cancer-4">
146
+ <command xmlns="http://jabber.org/protocol/commands" action="execute" node="config" sessionid="config-1">
147
+ <x xmlns="jabber:x:data" type="submit">
148
+ <field var="name">
149
+ <value>Hans Spooren</value>
150
+ </field>
151
+ <field type="list-single" var="sex">
152
+ <value>m</value>
153
+ </field>
154
+ <field type="jid-single" var="jid">
155
+ <value>knave@wonderland.lit</value>
156
+ </field>
157
+ </x>
158
+ </command>
159
+ </iq>}
160
+
161
+ t.out %{<iq type='result' to='knave@wonderland.lit' id='cancer-4'><command xmlns='http://jabber.org/protocol/commands' sessionid='config-1' node='config' status='completed'><note type='info'>Your profile has been updated.</note></command></iq>}
162
+ end
163
+ end
164
+
165
+ end