diff options
Diffstat (limited to 'chromium/jingle/notifier/communicator/single_login_attempt.cc')
-rw-r--r-- | chromium/jingle/notifier/communicator/single_login_attempt.cc | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/chromium/jingle/notifier/communicator/single_login_attempt.cc b/chromium/jingle/notifier/communicator/single_login_attempt.cc new file mode 100644 index 00000000000..67268c34b16 --- /dev/null +++ b/chromium/jingle/notifier/communicator/single_login_attempt.cc @@ -0,0 +1,182 @@ +// 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 |