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