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.
7 #include "jingle/notifier/communicator/single_login_attempt.h"
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 "third_party/webrtc/libjingle/xmllite/xmlelement.h"
18 #include "webrtc/libjingle/xmpp/constants.h"
19 #include "webrtc/libjingle/xmpp/xmppclientsettings.h"
23 SingleLoginAttempt::Delegate::~Delegate() {}
25 SingleLoginAttempt::SingleLoginAttempt(const LoginSettings
& login_settings
,
27 : login_settings_(login_settings
),
30 MakeConnectionSettingsList(login_settings_
.GetServers(),
31 login_settings_
.try_ssltcp_first())),
32 current_settings_(settings_list_
.begin()) {
33 if (settings_list_
.empty()) {
37 TryConnect(*current_settings_
);
40 SingleLoginAttempt::~SingleLoginAttempt() {}
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.
45 // TODO(akalin): Add unit tests to enforce the behavior above.
47 void SingleLoginAttempt::OnConnect(
48 base::WeakPtr
<buzz::XmppTaskParentInterface
> base_task
) {
49 DVLOG(1) << "Connected to " << current_settings_
->ToString();
50 delegate_
->OnConnect(base_task
);
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
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
);
65 return redirect_server
;
67 redirect_server
.set_host(parts
[0]);
68 if (parts
.size() <= 1) {
69 return redirect_server
;
71 // Try to parse the port, falling back to kDefaultXmppPort.
72 int port
= kDefaultXmppPort
;
73 if (!base::StringToInt(parts
[1], &port
)) {
74 port
= kDefaultXmppPort
;
76 if (port
<= 0 || port
> kuint16max
) {
77 port
= kDefaultXmppPort
;
79 redirect_server
.set_port(port
);
80 return redirect_server
;
85 void SingleLoginAttempt::OnError(buzz::XmppEngine::Error error
, int subcode
,
86 const buzz::XmlElement
* stream_error
) {
87 DVLOG(1) << "Error: " << error
<< ", subcode: " << subcode
89 ? (", stream error: " + XmlElementToString(*stream_error
))
92 DCHECK_EQ(error
== buzz::XmppEngine::ERROR_STREAM
, stream_error
!= NULL
);
94 // Check for redirection. We expect something like:
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]
98 // There are some differences from the spec [1]:
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
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.)
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
112 const buzz::XmlElement
* other
=
113 stream_error
->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST
);
115 const buzz::XmlElement
* text
=
116 stream_error
->FirstNamed(buzz::QN_XSTREAM_TEXT
);
118 // Yep, its a "stream:error" with "see-other-host" text,
119 // let's parse out the server:port, and then reconnect
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(
131 current_settings_
->ssltcp_support
));
132 // May be deleted at this point.
139 if (error
== buzz::XmppEngine::ERROR_UNAUTHORIZED
) {
140 DVLOG(1) << "Credentials rejected";
141 delegate_
->OnCredentialsRejected();
145 if (current_settings_
== settings_list_
.end()) {
151 if (current_settings_
== settings_list_
.end()) {
152 DVLOG(1) << "Could not connect to any XMPP server";
153 delegate_
->OnSettingsExhausted();
157 TryConnect(*current_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
);
168 buzz::Jid
jid(client_settings
.user(), client_settings
.host(),
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(),
182 } // namespace notifier