// 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 #include #include #include #include "src/notification/xml_node.h" #include "src/notification/xmpp_channel.h" namespace weave { namespace { // Default timeout for 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: // $body // 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("%s", 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 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 = "" "" ""; 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