Home | History | Annotate | Download | only in notification
      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.
     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 
     50 IqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel,
     51                                  provider::TaskRunner* task_runner)
     52     : xmpp_channel_{xmpp_channel}, task_runner_{task_runner} {}
     53 
     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 
     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 
     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 
    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