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 "jingle/notifier/communicator/single_login_attempt.h"
6
7 #include <stdint.h>
8
9 #include <limits>
10 #include <string>
11
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "jingle/notifier/base/const_communicator.h"
16 #include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h"
17 #include "jingle/notifier/listener/xml_element_util.h"
18 #include "net/base/host_port_pair.h"
19 #include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
20 #include "third_party/libjingle_xmpp/xmpp/constants.h"
21 #include "third_party/libjingle_xmpp/xmpp/xmppclientsettings.h"
22
23 namespace notifier {
24
~Delegate()25 SingleLoginAttempt::Delegate::~Delegate() {}
26
SingleLoginAttempt(const LoginSettings & login_settings,Delegate * delegate)27 SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings,
28 Delegate* delegate)
29 : login_settings_(login_settings),
30 delegate_(delegate),
31 settings_list_(
32 MakeConnectionSettingsList(login_settings_.GetServers(),
33 login_settings_.try_ssltcp_first())),
34 current_settings_(settings_list_.begin()) {
35 if (settings_list_.empty()) {
36 NOTREACHED();
37 return;
38 }
39 TryConnect(*current_settings_);
40 }
41
~SingleLoginAttempt()42 SingleLoginAttempt::~SingleLoginAttempt() {}
43
44 // In the code below, we assume that calling a delegate method may end
45 // up in ourselves being deleted, so we always call it last.
46 //
47 // TODO(akalin): Add unit tests to enforce the behavior above.
48
OnConnect(base::WeakPtr<jingle_xmpp::XmppTaskParentInterface> base_task)49 void SingleLoginAttempt::OnConnect(
50 base::WeakPtr<jingle_xmpp::XmppTaskParentInterface> base_task) {
51 DVLOG(1) << "Connected to " << current_settings_->ToString();
52 delegate_->OnConnect(base_task);
53 }
54
55 namespace {
56
57 // This function is more permissive than
58 // net::HostPortPair::FromString(). If the port is missing or
59 // unparseable, it assumes the default XMPP port. The hostname may be
60 // empty.
ParseRedirectText(const std::string & redirect_text)61 net::HostPortPair ParseRedirectText(const std::string& redirect_text) {
62 std::vector<std::string> parts = base::SplitString(
63 redirect_text, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
64 net::HostPortPair redirect_server;
65 redirect_server.set_port(kDefaultXmppPort);
66 if (parts.empty()) {
67 return redirect_server;
68 }
69 redirect_server.set_host(parts[0]);
70 if (parts.size() <= 1) {
71 return redirect_server;
72 }
73 // Try to parse the port, falling back to kDefaultXmppPort.
74 int port = kDefaultXmppPort;
75 if (!base::StringToInt(parts[1], &port)) {
76 port = kDefaultXmppPort;
77 }
78 if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
79 port = kDefaultXmppPort;
80 }
81 redirect_server.set_port(port);
82 return redirect_server;
83 }
84
85 } // namespace
86
OnError(jingle_xmpp::XmppEngine::Error error,int subcode,const jingle_xmpp::XmlElement * stream_error)87 void SingleLoginAttempt::OnError(jingle_xmpp::XmppEngine::Error error, int subcode,
88 const jingle_xmpp::XmlElement* stream_error) {
89 DVLOG(1) << "Error: " << error << ", subcode: " << subcode
90 << (stream_error
91 ? (", stream error: " + XmlElementToString(*stream_error))
92 : std::string());
93
94 DCHECK_EQ(error == jingle_xmpp::XmppEngine::ERROR_STREAM, stream_error != NULL);
95
96 // Check for redirection. We expect something like:
97 //
98 // <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]
99 //
100 // There are some differences from the spec [1]:
101 //
102 // - we expect a separate text element with the redirection info
103 // (which is the format Google Talk's servers use), whereas the
104 // spec puts the redirection info directly in the see-other-host
105 // element;
106 // - we check for redirection only during login, whereas the
107 // server can send down a redirection at any time according to
108 // the spec. (TODO(akalin): Figure out whether we need to handle
109 // redirection at any other point.)
110 //
111 // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host
112 // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops
113 if (stream_error) {
114 const jingle_xmpp::XmlElement* other =
115 stream_error->FirstNamed(jingle_xmpp::QN_XSTREAM_SEE_OTHER_HOST);
116 if (other) {
117 const jingle_xmpp::XmlElement* text =
118 stream_error->FirstNamed(jingle_xmpp::QN_XSTREAM_TEXT);
119 if (text) {
120 // Yep, its a "stream:error" with "see-other-host" text,
121 // let's parse out the server:port, and then reconnect
122 // with that.
123 const net::HostPortPair& redirect_server =
124 ParseRedirectText(text->BodyText());
125 // ParseRedirectText shouldn't return a zero port.
126 DCHECK_NE(redirect_server.port(), 0u);
127 // If we don't have a host, ignore the redirection and treat
128 // it like a regular error.
129 if (!redirect_server.host().empty()) {
130 delegate_->OnRedirect(
131 ServerInformation(
132 redirect_server,
133 current_settings_->ssltcp_support));
134 // May be deleted at this point.
135 return;
136 }
137 }
138 }
139 }
140
141 if (error == jingle_xmpp::XmppEngine::ERROR_UNAUTHORIZED) {
142 DVLOG(1) << "Credentials rejected";
143 delegate_->OnCredentialsRejected();
144 return;
145 }
146
147 if (current_settings_ == settings_list_.end()) {
148 NOTREACHED();
149 return;
150 }
151
152 ++current_settings_;
153 if (current_settings_ == settings_list_.end()) {
154 DVLOG(1) << "Could not connect to any XMPP server";
155 delegate_->OnSettingsExhausted();
156 return;
157 }
158
159 TryConnect(*current_settings_);
160 }
161
TryConnect(const ConnectionSettings & connection_settings)162 void SingleLoginAttempt::TryConnect(
163 const ConnectionSettings& connection_settings) {
164 DVLOG(1) << "Trying to connect to " << connection_settings.ToString();
165 // Copy the user settings and fill in the connection parameters from
166 // |connection_settings|.
167 jingle_xmpp::XmppClientSettings client_settings = login_settings_.user_settings();
168 connection_settings.FillXmppClientSettings(&client_settings);
169
170 jingle_xmpp::Jid jid(client_settings.user(), client_settings.host(),
171 jingle_xmpp::STR_EMPTY);
172 jingle_xmpp::PreXmppAuth* pre_xmpp_auth =
173 new GaiaTokenPreXmppAuth(
174 jid.Str(), client_settings.auth_token(),
175 client_settings.token_service(),
176 login_settings_.auth_mechanism());
177 xmpp_connection_.reset(new XmppConnection(
178 client_settings, login_settings_.get_socket_factory_callback(), this,
179 pre_xmpp_auth, login_settings_.traffic_annotation()));
180 }
181
182 } // namespace notifier
183