OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license | |
5 * that can be found in the LICENSE file in the root of the source | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 #include <iostream> | |
12 #include <memory> | |
13 #include <sstream> | |
14 #include <string> | |
15 | |
16 #include "webrtc/libjingle/xmllite/xmlelement.h" | |
17 #include "webrtc/libjingle/xmpp/constants.h" | |
18 #include "webrtc/libjingle/xmpp/plainsaslhandler.h" | |
19 #include "webrtc/libjingle/xmpp/saslplainmechanism.h" | |
20 #include "webrtc/libjingle/xmpp/util_unittest.h" | |
21 #include "webrtc/libjingle/xmpp/xmppengine.h" | |
22 #include "webrtc/base/common.h" | |
23 #include "webrtc/base/gunit.h" | |
24 | |
25 using buzz::Jid; | |
26 using buzz::QName; | |
27 using buzz::XmlElement; | |
28 using buzz::XmppEngine; | |
29 using buzz::XmppIqCookie; | |
30 using buzz::XmppIqHandler; | |
31 using buzz::XmppTestHandler; | |
32 using buzz::QN_ID; | |
33 using buzz::QN_IQ; | |
34 using buzz::QN_TYPE; | |
35 using buzz::QN_ROSTER_QUERY; | |
36 using buzz::XMPP_RETURN_OK; | |
37 using buzz::XMPP_RETURN_BADARGUMENT; | |
38 | |
39 // XmppEngineTestIqHandler | |
40 // This class grabs the response to an IQ stanza and stores it in a string. | |
41 class XmppEngineTestIqHandler : public XmppIqHandler { | |
42 public: | |
43 virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) { | |
44 ss_ << stanza->Str(); | |
45 } | |
46 | |
47 std::string IqResponseActivity() { | |
48 std::string result = ss_.str(); | |
49 ss_.str(""); | |
50 return result; | |
51 } | |
52 | |
53 private: | |
54 std::stringstream ss_; | |
55 }; | |
56 | |
57 class XmppEngineTest : public testing::Test { | |
58 public: | |
59 XmppEngine* engine() { return engine_.get(); } | |
60 XmppTestHandler* handler() { return handler_.get(); } | |
61 virtual void SetUp() { | |
62 engine_.reset(XmppEngine::Create()); | |
63 handler_.reset(new XmppTestHandler(engine_.get())); | |
64 | |
65 Jid jid("david@my-server"); | |
66 rtc::InsecureCryptStringImpl pass; | |
67 pass.password() = "david"; | |
68 engine_->SetSessionHandler(handler_.get()); | |
69 engine_->SetOutputHandler(handler_.get()); | |
70 engine_->AddStanzaHandler(handler_.get()); | |
71 engine_->SetUser(jid); | |
72 engine_->SetSaslHandler( | |
73 new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true)); | |
74 } | |
75 virtual void TearDown() { | |
76 handler_.reset(); | |
77 engine_.reset(); | |
78 } | |
79 void RunLogin(); | |
80 | |
81 private: | |
82 std::unique_ptr<XmppEngine> engine_; | |
83 std::unique_ptr<XmppTestHandler> handler_; | |
84 }; | |
85 | |
86 void XmppEngineTest::RunLogin() { | |
87 // Connect | |
88 EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState()); | |
89 engine()->Connect(); | |
90 EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState()); | |
91 | |
92 EXPECT_EQ("[OPENING]", handler_->SessionActivity()); | |
93 | |
94 EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" " | |
95 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
96 "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity()); | |
97 | |
98 std::string input = | |
99 "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" " | |
100 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
101 "xmlns=\"jabber:client\">"; | |
102 engine()->HandleInput(input.c_str(), input.length()); | |
103 | |
104 input = | |
105 "<stream:features>" | |
106 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>" | |
107 "<required/>" | |
108 "</starttls>" | |
109 "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" | |
110 "<mechanism>DIGEST-MD5</mechanism>" | |
111 "<mechanism>PLAIN</mechanism>" | |
112 "</mechanisms>" | |
113 "</stream:features>"; | |
114 engine()->HandleInput(input.c_str(), input.length()); | |
115 EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>", | |
116 handler_->OutputActivity()); | |
117 | |
118 EXPECT_EQ("", handler_->SessionActivity()); | |
119 EXPECT_EQ("", handler_->StanzaActivity()); | |
120 | |
121 input = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"; | |
122 engine()->HandleInput(input.c_str(), input.length()); | |
123 EXPECT_EQ("[START-TLS my-server]" | |
124 "<stream:stream to=\"my-server\" xml:lang=\"*\" " | |
125 "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
126 "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity()); | |
127 | |
128 EXPECT_EQ("", handler_->SessionActivity()); | |
129 EXPECT_EQ("", handler_->StanzaActivity()); | |
130 | |
131 input = "<stream:stream id=\"01234567\" version=\"1.0\" " | |
132 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
133 "xmlns=\"jabber:client\">"; | |
134 engine()->HandleInput(input.c_str(), input.length()); | |
135 | |
136 input = | |
137 "<stream:features>" | |
138 "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" | |
139 "<mechanism>DIGEST-MD5</mechanism>" | |
140 "<mechanism>PLAIN</mechanism>" | |
141 "</mechanisms>" | |
142 "</stream:features>"; | |
143 engine()->HandleInput(input.c_str(), input.length()); | |
144 EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" " | |
145 "mechanism=\"PLAIN\" " | |
146 "auth:allow-non-google-login=\"true\" " | |
147 "auth:client-uses-full-bind-result=\"true\" " | |
148 "xmlns:auth=\"http://www.google.com/talk/protocol/auth\"" | |
149 ">AGRhdmlkAGRhdmlk</auth>", | |
150 handler_->OutputActivity()); | |
151 | |
152 EXPECT_EQ("", handler_->SessionActivity()); | |
153 EXPECT_EQ("", handler_->StanzaActivity()); | |
154 | |
155 input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"; | |
156 engine()->HandleInput(input.c_str(), input.length()); | |
157 EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" " | |
158 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
159 "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity()); | |
160 | |
161 EXPECT_EQ("", handler_->SessionActivity()); | |
162 EXPECT_EQ("", handler_->StanzaActivity()); | |
163 | |
164 input = "<stream:stream id=\"01234567\" version=\"1.0\" " | |
165 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
166 "xmlns=\"jabber:client\">"; | |
167 engine()->HandleInput(input.c_str(), input.length()); | |
168 | |
169 input = "<stream:features>" | |
170 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>" | |
171 "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" | |
172 "</stream:features>"; | |
173 engine()->HandleInput(input.c_str(), input.length()); | |
174 EXPECT_EQ("<iq type=\"set\" id=\"0\">" | |
175 "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>", | |
176 handler_->OutputActivity()); | |
177 | |
178 EXPECT_EQ("", handler_->SessionActivity()); | |
179 EXPECT_EQ("", handler_->StanzaActivity()); | |
180 | |
181 input = "<iq type='result' id='0'>" | |
182 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>" | |
183 "david@my-server/test</jid></bind></iq>"; | |
184 engine()->HandleInput(input.c_str(), input.length()); | |
185 | |
186 EXPECT_EQ("<iq type=\"set\" id=\"1\">" | |
187 "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>", | |
188 handler_->OutputActivity()); | |
189 | |
190 EXPECT_EQ("", handler_->SessionActivity()); | |
191 EXPECT_EQ("", handler_->StanzaActivity()); | |
192 | |
193 input = "<iq type='result' id='1'/>"; | |
194 engine()->HandleInput(input.c_str(), input.length()); | |
195 | |
196 EXPECT_EQ("[OPEN]", handler_->SessionActivity()); | |
197 EXPECT_EQ("", handler_->StanzaActivity()); | |
198 EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid()); | |
199 } | |
200 | |
201 // TestSuccessfulLogin() | |
202 // This function simply tests to see if a login works. This includes | |
203 // encryption and authentication | |
204 TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) { | |
205 RunLogin(); | |
206 engine()->Disconnect(); | |
207 EXPECT_EQ("</stream:stream>[CLOSED]", handler()->OutputActivity()); | |
208 EXPECT_EQ("[CLOSED]", handler()->SessionActivity()); | |
209 EXPECT_EQ("", handler()->StanzaActivity()); | |
210 } | |
211 | |
212 TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) { | |
213 RunLogin(); | |
214 engine()->ConnectionClosed(0); | |
215 EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); | |
216 EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity()); | |
217 EXPECT_EQ("", handler()->StanzaActivity()); | |
218 } | |
219 | |
220 | |
221 // TestNotXmpp() | |
222 // This tests the error case when connecting to a non XMPP service | |
223 TEST_F(XmppEngineTest, TestNotXmpp) { | |
224 // Connect | |
225 engine()->Connect(); | |
226 EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" " | |
227 "xmlns:stream=\"http://etherx.jabber.org/streams\" " | |
228 "xmlns=\"jabber:client\">\r\n", handler()->OutputActivity()); | |
229 | |
230 // Send garbage response (courtesy of apache) | |
231 std::string input = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"; | |
232 engine()->HandleInput(input.c_str(), input.length()); | |
233 | |
234 EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); | |
235 EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity()); | |
236 EXPECT_EQ("", handler()->StanzaActivity()); | |
237 } | |
238 | |
239 // TestPassthrough() | |
240 // This tests that arbitrary stanzas can be passed to the server through | |
241 // the engine. | |
242 TEST_F(XmppEngineTest, TestPassthrough) { | |
243 // Queue up an app stanza | |
244 XmlElement application_stanza(QName("test", "app-stanza")); | |
245 application_stanza.AddText("this-is-a-test"); | |
246 engine()->SendStanza(&application_stanza); | |
247 | |
248 // Do the whole login handshake | |
249 RunLogin(); | |
250 | |
251 EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test" | |
252 "</test:app-stanza>", handler()->OutputActivity()); | |
253 | |
254 // do another stanza | |
255 XmlElement roster_get(QN_IQ); | |
256 roster_get.AddAttr(QN_TYPE, "get"); | |
257 roster_get.AddAttr(QN_ID, engine()->NextId()); | |
258 roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); | |
259 engine()->SendStanza(&roster_get); | |
260 EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>" | |
261 "</iq>", handler()->OutputActivity()); | |
262 | |
263 // now say the server ends the stream | |
264 engine()->HandleInput("</stream:stream>", 16); | |
265 EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity()); | |
266 EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); | |
267 EXPECT_EQ("", handler()->StanzaActivity()); | |
268 } | |
269 | |
270 // TestIqCallback() | |
271 // This tests the routing of Iq stanzas and responses. | |
272 TEST_F(XmppEngineTest, TestIqCallback) { | |
273 XmppEngineTestIqHandler iq_response; | |
274 XmppIqCookie cookie; | |
275 | |
276 // Do the whole login handshake | |
277 RunLogin(); | |
278 | |
279 // Build an iq request | |
280 XmlElement roster_get(QN_IQ); | |
281 roster_get.AddAttr(QN_TYPE, "get"); | |
282 roster_get.AddAttr(QN_ID, engine()->NextId()); | |
283 roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); | |
284 engine()->SendIq(&roster_get, &iq_response, &cookie); | |
285 EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>" | |
286 "</iq>", handler()->OutputActivity()); | |
287 EXPECT_EQ("", handler()->SessionActivity()); | |
288 EXPECT_EQ("", handler()->StanzaActivity()); | |
289 EXPECT_EQ("", iq_response.IqResponseActivity()); | |
290 | |
291 // now say the server responds to the iq | |
292 std::string input = "<iq type='result' id='2'>" | |
293 "<query xmlns='jabber:iq:roster'><item>foo</item>" | |
294 "</query></iq>"; | |
295 engine()->HandleInput(input.c_str(), input.length()); | |
296 EXPECT_EQ("", handler()->OutputActivity()); | |
297 EXPECT_EQ("", handler()->SessionActivity()); | |
298 EXPECT_EQ("", handler()->StanzaActivity()); | |
299 EXPECT_EQ("<cli:iq type=\"result\" id=\"2\" xmlns:cli=\"jabber:client\">" | |
300 "<query xmlns=\"jabber:iq:roster\"><item>foo</item></query>" | |
301 "</cli:iq>", iq_response.IqResponseActivity()); | |
302 | |
303 EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL)); | |
304 | |
305 // Do it again with another id to test cancel | |
306 roster_get.SetAttr(QN_ID, engine()->NextId()); | |
307 engine()->SendIq(&roster_get, &iq_response, &cookie); | |
308 EXPECT_EQ("<iq type=\"get\" id=\"3\"><query xmlns=\"jabber:iq:roster\"/>" | |
309 "</iq>", handler()->OutputActivity()); | |
310 EXPECT_EQ("", handler()->SessionActivity()); | |
311 EXPECT_EQ("", handler()->StanzaActivity()); | |
312 EXPECT_EQ("", iq_response.IqResponseActivity()); | |
313 | |
314 // cancel the handler this time | |
315 EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL)); | |
316 | |
317 // now say the server responds to the iq: the iq handler should not get it. | |
318 input = "<iq type='result' id='3'><query xmlns='jabber:iq:roster'><item>bar" | |
319 "</item></query></iq>"; | |
320 engine()->HandleInput(input.c_str(), input.length()); | |
321 EXPECT_EQ("<cli:iq type=\"result\" id=\"3\" xmlns:cli=\"jabber:client\">" | |
322 "<query xmlns=\"jabber:iq:roster\"><item>bar</item></query>" | |
323 "</cli:iq>", handler()->StanzaActivity()); | |
324 EXPECT_EQ("", iq_response.IqResponseActivity()); | |
325 EXPECT_EQ("", handler()->OutputActivity()); | |
326 EXPECT_EQ("", handler()->SessionActivity()); | |
327 } | |
OLD | NEW |