root/jingle/notifier/communicator/single_login_attempt.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. current_settings_
  2. OnConnect
  3. ParseRedirectText
  4. OnError
  5. TryConnect

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>

#include "jingle/notifier/communicator/single_login_attempt.h"

#include "base/basictypes.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "jingle/notifier/base/const_communicator.h"
#include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h"
#include "jingle/notifier/listener/xml_element_util.h"
#include "net/base/host_port_pair.h"
#include "talk/xmllite/xmlelement.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/xmppclientsettings.h"

namespace notifier {

SingleLoginAttempt::Delegate::~Delegate() {}

SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings,
                                       Delegate* delegate)
    : login_settings_(login_settings),
      delegate_(delegate),
      settings_list_(
          MakeConnectionSettingsList(login_settings_.GetServers(),
                                     login_settings_.try_ssltcp_first())),
      current_settings_(settings_list_.begin()) {
  if (settings_list_.empty()) {
    NOTREACHED();
    return;
  }
  TryConnect(*current_settings_);
}

SingleLoginAttempt::~SingleLoginAttempt() {}

// In the code below, we assume that calling a delegate method may end
// up in ourselves being deleted, so we always call it last.
//
// TODO(akalin): Add unit tests to enforce the behavior above.

void SingleLoginAttempt::OnConnect(
    base::WeakPtr<buzz::XmppTaskParentInterface> base_task) {
  DVLOG(1) << "Connected to " << current_settings_->ToString();
  delegate_->OnConnect(base_task);
}

namespace {

// This function is more permissive than
// net::HostPortPair::FromString().  If the port is missing or
// unparseable, it assumes the default XMPP port.  The hostname may be
// empty.
net::HostPortPair ParseRedirectText(const std::string& redirect_text) {
  std::vector<std::string> parts;
  base::SplitString(redirect_text, ':', &parts);
  net::HostPortPair redirect_server;
  redirect_server.set_port(kDefaultXmppPort);
  if (parts.empty()) {
    return redirect_server;
  }
  redirect_server.set_host(parts[0]);
  if (parts.size() <= 1) {
    return redirect_server;
  }
  // Try to parse the port, falling back to kDefaultXmppPort.
  int port = kDefaultXmppPort;
  if (!base::StringToInt(parts[1], &port)) {
    port = kDefaultXmppPort;
  }
  if (port <= 0 || port > kuint16max) {
    port = kDefaultXmppPort;
  }
  redirect_server.set_port(port);
  return redirect_server;
}

}  // namespace

void SingleLoginAttempt::OnError(buzz::XmppEngine::Error error, int subcode,
                                 const buzz::XmlElement* stream_error) {
  DVLOG(1) << "Error: " << error << ", subcode: " << subcode
           << (stream_error
                   ? (", stream error: " + XmlElementToString(*stream_error))
                   : std::string());

  DCHECK_EQ(error == buzz::XmppEngine::ERROR_STREAM, stream_error != NULL);

  // Check for redirection.  We expect something like:
  //
  // <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]
  //
  // There are some differences from the spec [1]:
  //
  //   - we expect a separate text element with the redirection info
  //     (which is the format Google Talk's servers use), whereas the
  //     spec puts the redirection info directly in the see-other-host
  //     element;
  //   - we check for redirection only during login, whereas the
  //     server can send down a redirection at any time according to
  //     the spec. (TODO(akalin): Figure out whether we need to handle
  //     redirection at any other point.)
  //
  // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host
  // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops
  if (stream_error) {
    const buzz::XmlElement* other =
        stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST);
    if (other) {
      const buzz::XmlElement* text =
          stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT);
      if (text) {
        // Yep, its a "stream:error" with "see-other-host" text,
        // let's parse out the server:port, and then reconnect
        // with that.
        const net::HostPortPair& redirect_server =
            ParseRedirectText(text->BodyText());
        // ParseRedirectText shouldn't return a zero port.
        DCHECK_NE(redirect_server.port(), 0u);
        // If we don't have a host, ignore the redirection and treat
        // it like a regular error.
        if (!redirect_server.host().empty()) {
          delegate_->OnRedirect(
              ServerInformation(
                  redirect_server,
                  current_settings_->ssltcp_support));
          // May be deleted at this point.
          return;
        }
      }
    }
  }

  if (error == buzz::XmppEngine::ERROR_UNAUTHORIZED) {
    DVLOG(1) << "Credentials rejected";
    delegate_->OnCredentialsRejected();
    return;
  }

  if (current_settings_ == settings_list_.end()) {
    NOTREACHED();
    return;
  }

  ++current_settings_;
  if (current_settings_ == settings_list_.end()) {
    DVLOG(1) << "Could not connect to any XMPP server";
    delegate_->OnSettingsExhausted();
    return;
  }

  TryConnect(*current_settings_);
}

void SingleLoginAttempt::TryConnect(
    const ConnectionSettings& connection_settings) {
  DVLOG(1) << "Trying to connect to " << connection_settings.ToString();
  // Copy the user settings and fill in the connection parameters from
  // |connection_settings|.
  buzz::XmppClientSettings client_settings = login_settings_.user_settings();
  connection_settings.FillXmppClientSettings(&client_settings);

  buzz::Jid jid(client_settings.user(), client_settings.host(),
                buzz::STR_EMPTY);
  buzz::PreXmppAuth* pre_xmpp_auth =
      new GaiaTokenPreXmppAuth(
          jid.Str(), client_settings.auth_token(),
          client_settings.token_service(),
          login_settings_.auth_mechanism());
  xmpp_connection_.reset(
      new XmppConnection(client_settings,
                         login_settings_.request_context_getter(),
                         this,
                         pre_xmpp_auth));
}

}  // namespace notifier

/* [<][>][^][v][top][bottom][index][help] */