• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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