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 21namespace notifier { 22 23SingleLoginAttempt::Delegate::~Delegate() {} 24 25SingleLoginAttempt::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 40SingleLoginAttempt::~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 47void SingleLoginAttempt::OnConnect( 48 base::WeakPtr<buzz::XmppTaskParentInterface> base_task) { 49 DVLOG(1) << "Connected to " << current_settings_->ToString(); 50 delegate_->OnConnect(base_task); 51} 52 53namespace { 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. 59net::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 85void 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 160void 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