Home | History | Annotate | Download | only in xmpp
      1 // Copyright 2004 Google Inc. All Rights Reserved
      2 // Author: David Bau
      3 
      4 #include <string>
      5 #include <sstream>
      6 #include <iostream>
      7 #include "talk/base/common.h"
      8 #include "talk/base/gunit.h"
      9 #include "talk/xmllite/xmlelement.h"
     10 #include "talk/xmpp/constants.h"
     11 #include "talk/xmpp/util_unittest.h"
     12 #include "talk/xmpp/saslplainmechanism.h"
     13 #include "talk/xmpp/plainsaslhandler.h"
     14 #include "talk/xmpp/xmppengine.h"
     15 
     16 using buzz::Jid;
     17 using buzz::QName;
     18 using buzz::XmlElement;
     19 using buzz::XmppEngine;
     20 using buzz::XmppIqCookie;
     21 using buzz::XmppIqHandler;
     22 using buzz::XmppTestHandler;
     23 using buzz::QN_ID;
     24 using buzz::QN_IQ;
     25 using buzz::QN_TYPE;
     26 using buzz::QN_ROSTER_QUERY;
     27 using buzz::XMPP_RETURN_OK;
     28 using buzz::XMPP_RETURN_BADARGUMENT;
     29 
     30 // XmppEngineTestIqHandler
     31 //    This class grabs the response to an IQ stanza and stores it in a string.
     32 class 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 
     48 class 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     talk_base::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, talk_base::CryptString(pass), true));
     65   }
     66   virtual void TearDown() {
     67     handler_.reset();
     68     engine_.reset();
     69   }
     70   void RunLogin();
     71 
     72  private:
     73   talk_base::scoped_ptr<XmppEngine> engine_;
     74   talk_base::scoped_ptr<XmppTestHandler> handler_;
     75 };
     76 
     77 void 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
    195 TEST_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 
    203 TEST_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
    214 TEST_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.
    233 TEST_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.
    263 TEST_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