| // Copyright 2015 The Weave Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "src/notification/xmpp_iq_stanza_handler.h" | 
 |  | 
 | #include <base/bind.h> | 
 | #include <base/strings/string_number_conversions.h> | 
 | #include <base/strings/stringprintf.h> | 
 | #include <weave/provider/task_runner.h> | 
 |  | 
 | #include "src/notification/xml_node.h" | 
 | #include "src/notification/xmpp_channel.h" | 
 |  | 
 | namespace weave { | 
 |  | 
 | namespace { | 
 |  | 
 | // Default timeout for <iq> requests to the server. If the response hasn't been | 
 | // received within this time interval, the request is considered as failed. | 
 | const int kTimeoutIntervalSeconds = 30; | 
 |  | 
 | // Builds an XML stanza that looks like this: | 
 | //  <iq id='${id}' type='${type}' from='${from}' to='${to}'>$body</iq> | 
 | // where 'to' and 'from' are optional attributes. | 
 | std::string BuildIqStanza(const std::string& id, | 
 |                           const std::string& type, | 
 |                           const std::string& to, | 
 |                           const std::string& from, | 
 |                           const std::string& body) { | 
 |   std::string to_attr; | 
 |   if (!to.empty()) { | 
 |     CHECK_EQ(std::string::npos, to.find_first_of("<'>")) | 
 |         << "Destination address contains invalid XML characters"; | 
 |     base::StringAppendF(&to_attr, " to='%s'", to.c_str()); | 
 |   } | 
 |   std::string from_attr; | 
 |   if (!from.empty()) { | 
 |     CHECK_EQ(std::string::npos, from.find_first_of("<'>")) | 
 |         << "Source address contains invalid XML characters"; | 
 |     base::StringAppendF(&from_attr, " from='%s'", from.c_str()); | 
 |   } | 
 |   return base::StringPrintf("<iq id='%s' type='%s'%s%s>%s</iq>", id.c_str(), | 
 |                             type.c_str(), from_attr.c_str(), to_attr.c_str(), | 
 |                             body.c_str()); | 
 | } | 
 |  | 
 | }  // anonymous namespace | 
 |  | 
 | IqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel, | 
 |                                  provider::TaskRunner* task_runner) | 
 |     : xmpp_channel_{xmpp_channel}, task_runner_{task_runner} {} | 
 |  | 
 | void IqStanzaHandler::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) { | 
 |   return SendRequestWithCustomTimeout( | 
 |       type, from, to, body, | 
 |       base::TimeDelta::FromSeconds(kTimeoutIntervalSeconds), response_callback, | 
 |       timeout_callback); | 
 | } | 
 |  | 
 | void IqStanzaHandler::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) { | 
 |   // Remember the response callback to call later. | 
 |   requests_.emplace(++last_request_id_, response_callback); | 
 |   // Schedule a time-out callback for this request. | 
 |   if (timeout < base::TimeDelta::Max()) { | 
 |     task_runner_->PostDelayedTask( | 
 |         FROM_HERE, | 
 |         base::Bind(&IqStanzaHandler::OnTimeOut, weak_ptr_factory_.GetWeakPtr(), | 
 |                    last_request_id_, timeout_callback), | 
 |         timeout); | 
 |   } | 
 |  | 
 |   std::string message = | 
 |       BuildIqStanza(std::to_string(last_request_id_), type, to, from, body); | 
 |   xmpp_channel_->SendMessage(message); | 
 | } | 
 |  | 
 | bool IqStanzaHandler::HandleIqStanza(std::unique_ptr<XmlNode> stanza) { | 
 |   std::string type; | 
 |   if (!stanza->GetAttribute("type", &type)) { | 
 |     LOG(ERROR) << "IQ stanza missing 'type' attribute"; | 
 |     return false; | 
 |   } | 
 |  | 
 |   std::string id_str; | 
 |   if (!stanza->GetAttribute("id", &id_str)) { | 
 |     LOG(ERROR) << "IQ stanza missing 'id' attribute"; | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (type == "result" || type == "error") { | 
 |     // These are response stanzas from the server. | 
 |     // Find the corresponding request. | 
 |     RequestId id; | 
 |     if (!base::StringToInt(id_str, &id)) { | 
 |       LOG(ERROR) << "IQ stanza's 'id' attribute is invalid"; | 
 |       return false; | 
 |     } | 
 |     auto p = requests_.find(id); | 
 |     if (p != requests_.end()) { | 
 |       task_runner_->PostDelayedTask( | 
 |           FROM_HERE, base::Bind(p->second, base::Passed(std::move(stanza))), | 
 |           {}); | 
 |       requests_.erase(p); | 
 |     } | 
 |   } else { | 
 |     // We do not support server-initiated IQ requests ("set" / "get" / "query"). | 
 |     // So just reply with "not implemented" error (and swap "to"/"from" attrs). | 
 |     std::string error_body = | 
 |         "<error type='modify'>" | 
 |         "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" | 
 |         "</error>"; | 
 |     std::string message = | 
 |         BuildIqStanza(id_str, "error", stanza->GetAttributeOrEmpty("from"), | 
 |                       stanza->GetAttributeOrEmpty("to"), error_body); | 
 |     xmpp_channel_->SendMessage(message); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void IqStanzaHandler::OnTimeOut(RequestId id, | 
 |                                 const TimeoutCallback& timeout_callback) { | 
 |   // Request has not been processed yes, so a real timeout occurred. | 
 |   if (requests_.erase(id) > 0) | 
 |     timeout_callback.Run(); | 
 | } | 
 |  | 
 | }  // namespace weave |