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