Home | History | Annotate | Download | only in communicator
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <string>
      6 
      7 #include "jingle/notifier/communicator/single_login_attempt.h"
      8 
      9 #include "base/basictypes.h"
     10 #include "base/logging.h"
     11 #include "base/strings/string_number_conversions.h"
     12 #include "base/strings/string_split.h"
     13 #include "jingle/notifier/base/const_communicator.h"
     14 #include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h"
     15 #include "jingle/notifier/listener/xml_element_util.h"
     16 #include "net/base/host_port_pair.h"
     17 #include "talk/xmllite/xmlelement.h"
     18 #include "talk/xmpp/constants.h"
     19 #include "talk/xmpp/xmppclientsettings.h"
     20 
     21 namespace notifier {
     22 
     23 SingleLoginAttempt::Delegate::~Delegate() {}
     24 
     25 SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings,
     26                                        Delegate* delegate)
     27     : login_settings_(login_settings),
     28       delegate_(delegate),
     29       settings_list_(
     30           MakeConnectionSettingsList(login_settings_.GetServers(),
     31                                      login_settings_.try_ssltcp_first())),
     32       current_settings_(settings_list_.begin()) {
     33   if (settings_list_.empty()) {
     34     NOTREACHED();
     35     return;
     36   }
     37   TryConnect(*current_settings_);
     38 }
     39 
     40 SingleLoginAttempt::~SingleLoginAttempt() {}
     41 
     42 // In the code below, we assume that calling a delegate method may end
     43 // up in ourselves being deleted, so we always call it last.
     44 //
     45 // TODO(akalin): Add unit tests to enforce the behavior above.
     46 
     47 void SingleLoginAttempt::OnConnect(
     48     base::WeakPtr<buzz::XmppTaskParentInterface> base_task) {
     49   DVLOG(1) << "Connected to " << current_settings_->ToString();
     50   delegate_->OnConnect(base_task);
     51 }
     52 
     53 namespace {
     54 
     55 // This function is more permissive than
     56 // net::HostPortPair::FromString().  If the port is missing or
     57 // unparseable, it assumes the default XMPP port.  The hostname may be
     58 // empty.
     59 net::HostPortPair ParseRedirectText(const std::string& redirect_text) {
     60   std::vector<std::string> parts;
     61   base::SplitString(redirect_text, ':', &parts);
     62   net::HostPortPair redirect_server;
     63   redirect_server.set_port(kDefaultXmppPort);
     64   if (parts.empty()) {
     65     return redirect_server;
     66   }
     67   redirect_server.set_host(parts[0]);
     68   if (parts.size() <= 1) {
     69     return redirect_server;
     70   }
     71   // Try to parse the port, falling back to kDefaultXmppPort.
     72   int port = kDefaultXmppPort;
     73   if (!base::StringToInt(parts[1], &port)) {
     74     port = kDefaultXmppPort;
     75   }
     76   if (port <= 0 || port > kuint16max) {
     77     port = kDefaultXmppPort;
     78   }
     79   redirect_server.set_port(port);
     80   return redirect_server;
     81 }
     82 
     83 }  // namespace
     84 
     85 void SingleLoginAttempt::OnError(buzz::XmppEngine::Error error, int subcode,
     86                                  const buzz::XmlElement* stream_error) {
     87   DVLOG(1) << "Error: " << error << ", subcode: " << subcode
     88            << (stream_error
     89                    ? (", stream error: " + XmlElementToString(*stream_error))
     90                    : std::string());
     91 
     92   DCHECK_EQ(error == buzz::XmppEngine::ERROR_STREAM, stream_error != NULL);
     93 
     94   // Check for redirection.  We expect something like:
     95   //
     96   // <stream:error><see-other-host xmlns="urn:ietf:params:xml:ns:xmpp-streams"/><str:text xmlns:str="urn:ietf:params:xml:ns:xmpp-streams">talk.google.com</str:text></stream:error> [2]
     97   //
     98   // There are some differences from the spec [1]:
     99   //
    100   //   - we expect a separate text element with the redirection info
    101   //     (which is the format Google Talk's servers use), whereas the
    102   //     spec puts the redirection info directly in the see-other-host
    103   //     element;
    104   //   - we check for redirection only during login, whereas the
    105   //     server can send down a redirection at any time according to
    106   //     the spec. (TODO(akalin): Figure out whether we need to handle
    107   //     redirection at any other point.)
    108   //
    109   // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host
    110   // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops
    111   if (stream_error) {
    112     const buzz::XmlElement* other =
    113         stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST);
    114     if (other) {
    115       const buzz::XmlElement* text =
    116           stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT);
    117       if (text) {
    118         // Yep, its a "stream:error" with "see-other-host" text,
    119         // let's parse out the server:port, and then reconnect
    120         // with that.
    121         const net::HostPortPair& redirect_server =
    122             ParseRedirectText(text->BodyText());
    123         // ParseRedirectText shouldn't return a zero port.
    124         DCHECK_NE(redirect_server.port(), 0u);
    125         // If we don't have a host, ignore the redirection and treat
    126         // it like a regular error.
    127         if (!redirect_server.host().empty()) {
    128           delegate_->OnRedirect(
    129               ServerInformation(
    130                   redirect_server,
    131                   current_settings_->ssltcp_support));
    132           // May be deleted at this point.
    133           return;
    134         }
    135       }
    136     }
    137   }
    138 
    139   if (error == buzz::XmppEngine::ERROR_UNAUTHORIZED) {
    140     DVLOG(1) << "Credentials rejected";
    141     delegate_->OnCredentialsRejected();
    142     return;
    143   }
    144 
    145   if (current_settings_ == settings_list_.end()) {
    146     NOTREACHED();
    147     return;
    148   }
    149 
    150   ++current_settings_;
    151   if (current_settings_ == settings_list_.end()) {
    152     DVLOG(1) << "Could not connect to any XMPP server";
    153     delegate_->OnSettingsExhausted();
    154     return;
    155   }
    156 
    157   TryConnect(*current_settings_);
    158 }
    159 
    160 void SingleLoginAttempt::TryConnect(
    161     const ConnectionSettings& connection_settings) {
    162   DVLOG(1) << "Trying to connect to " << connection_settings.ToString();
    163   // Copy the user settings and fill in the connection parameters from
    164   // |connection_settings|.
    165   buzz::XmppClientSettings client_settings = login_settings_.user_settings();
    166   connection_settings.FillXmppClientSettings(&client_settings);
    167 
    168   buzz::Jid jid(client_settings.user(), client_settings.host(),
    169                 buzz::STR_EMPTY);
    170   buzz::PreXmppAuth* pre_xmpp_auth =
    171       new GaiaTokenPreXmppAuth(
    172           jid.Str(), client_settings.auth_token(),
    173           client_settings.token_service(),
    174           login_settings_.auth_mechanism());
    175   xmpp_connection_.reset(
    176       new XmppConnection(client_settings,
    177                          login_settings_.request_context_getter(),
    178                          this,
    179                          pre_xmpp_auth));
    180 }
    181 
    182 }  // namespace notifier
    183