1 // Copyright 2015 The Weave 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 "src/notification/xmpp_iq_stanza_handler.h"
6 
7 #include <base/bind.h>
8 #include <base/strings/string_number_conversions.h>
9 #include <base/strings/stringprintf.h>
10 #include <weave/provider/task_runner.h>
11 
12 #include "src/notification/xml_node.h"
13 #include "src/notification/xmpp_channel.h"
14 
15 namespace weave {
16 
17 namespace {
18 
19 // Default timeout for <iq> requests to the server. If the response hasn't been
20 // received within this time interval, the request is considered as failed.
21 const int kTimeoutIntervalSeconds = 30;
22 
23 // Builds an XML stanza that looks like this:
24 //  <iq id='${id}' type='${type}' from='${from}' to='${to}'>$body</iq>
25 // where 'to' and 'from' are optional attributes.
BuildIqStanza(const std::string & id,const std::string & type,const std::string & to,const std::string & from,const std::string & body)26 std::string BuildIqStanza(const std::string& id,
27                           const std::string& type,
28                           const std::string& to,
29                           const std::string& from,
30                           const std::string& body) {
31   std::string to_attr;
32   if (!to.empty()) {
33     CHECK_EQ(std::string::npos, to.find_first_of("<'>"))
34         << "Destination address contains invalid XML characters";
35     base::StringAppendF(&to_attr, " to='%s'", to.c_str());
36   }
37   std::string from_attr;
38   if (!from.empty()) {
39     CHECK_EQ(std::string::npos, from.find_first_of("<'>"))
40         << "Source address contains invalid XML characters";
41     base::StringAppendF(&from_attr, " from='%s'", from.c_str());
42   }
43   return base::StringPrintf("<iq id='%s' type='%s'%s%s>%s</iq>", id.c_str(),
44                             type.c_str(), from_attr.c_str(), to_attr.c_str(),
45                             body.c_str());
46 }
47 
48 }  // anonymous namespace
49 
IqStanzaHandler(XmppChannelInterface * xmpp_channel,provider::TaskRunner * task_runner)50 IqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel,
51                                  provider::TaskRunner* task_runner)
52     : xmpp_channel_{xmpp_channel}, task_runner_{task_runner} {}
53 
SendRequest(const std::string & type,const std::string & from,const std::string & to,const std::string & body,const ResponseCallback & response_callback,const TimeoutCallback & timeout_callback)54 void IqStanzaHandler::SendRequest(const std::string& type,
55                                   const std::string& from,
56                                   const std::string& to,
57                                   const std::string& body,
58                                   const ResponseCallback& response_callback,
59                                   const TimeoutCallback& timeout_callback) {
60   return SendRequestWithCustomTimeout(
61       type, from, to, body,
62       base::TimeDelta::FromSeconds(kTimeoutIntervalSeconds), response_callback,
63       timeout_callback);
64 }
65 
SendRequestWithCustomTimeout(const std::string & type,const std::string & from,const std::string & to,const std::string & body,base::TimeDelta timeout,const ResponseCallback & response_callback,const TimeoutCallback & timeout_callback)66 void IqStanzaHandler::SendRequestWithCustomTimeout(
67     const std::string& type,
68     const std::string& from,
69     const std::string& to,
70     const std::string& body,
71     base::TimeDelta timeout,
72     const ResponseCallback& response_callback,
73     const TimeoutCallback& timeout_callback) {
74   // Remember the response callback to call later.
75   requests_.insert(std::make_pair(++last_request_id_, response_callback));
76   // Schedule a time-out callback for this request.
77   if (timeout < base::TimeDelta::Max()) {
78     task_runner_->PostDelayedTask(
79         FROM_HERE,
80         base::Bind(&IqStanzaHandler::OnTimeOut, weak_ptr_factory_.GetWeakPtr(),
81                    last_request_id_, timeout_callback),
82         timeout);
83   }
84 
85   std::string message =
86       BuildIqStanza(std::to_string(last_request_id_), type, to, from, body);
87   xmpp_channel_->SendMessage(message);
88 }
89 
HandleIqStanza(std::unique_ptr<XmlNode> stanza)90 bool IqStanzaHandler::HandleIqStanza(std::unique_ptr<XmlNode> stanza) {
91   std::string type;
92   if (!stanza->GetAttribute("type", &type)) {
93     LOG(ERROR) << "IQ stanza missing 'type' attribute";
94     return false;
95   }
96 
97   std::string id_str;
98   if (!stanza->GetAttribute("id", &id_str)) {
99     LOG(ERROR) << "IQ stanza missing 'id' attribute";
100     return false;
101   }
102 
103   if (type == "result" || type == "error") {
104     // These are response stanzas from the server.
105     // Find the corresponding request.
106     RequestId id;
107     if (!base::StringToInt(id_str, &id)) {
108       LOG(ERROR) << "IQ stanza's 'id' attribute is invalid";
109       return false;
110     }
111     auto p = requests_.find(id);
112     if (p != requests_.end()) {
113       task_runner_->PostDelayedTask(
114           FROM_HERE, base::Bind(p->second, base::Passed(std::move(stanza))),
115           {});
116       requests_.erase(p);
117     }
118   } else {
119     // We do not support server-initiated IQ requests ("set" / "get" / "query").
120     // So just reply with "not implemented" error (and swap "to"/"from" attrs).
121     std::string error_body =
122         "<error type='modify'>"
123         "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
124         "</error>";
125     std::string message =
126         BuildIqStanza(id_str, "error", stanza->GetAttributeOrEmpty("from"),
127                       stanza->GetAttributeOrEmpty("to"), error_body);
128     xmpp_channel_->SendMessage(message);
129   }
130   return true;
131 }
132 
OnTimeOut(RequestId id,const TimeoutCallback & timeout_callback)133 void IqStanzaHandler::OnTimeOut(RequestId id,
134                                 const TimeoutCallback& timeout_callback) {
135   // Request has not been processed yes, so a real timeout occurred.
136   if (requests_.erase(id) > 0)
137     timeout_callback.Run();
138 }
139 
140 }  // namespace weave
141