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
~Delegate()23 SingleLoginAttempt::Delegate::~Delegate() {}
24
SingleLoginAttempt(const LoginSettings & login_settings,Delegate * delegate)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
~SingleLoginAttempt()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
OnConnect(base::WeakPtr<buzz::XmppTaskParentInterface> base_task)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.
ParseRedirectText(const std::string & redirect_text)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
OnError(buzz::XmppEngine::Error error,int subcode,const buzz::XmlElement * stream_error)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
TryConnect(const ConnectionSettings & connection_settings)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