|  | // 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_.insert(std::make_pair(++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 |