14615e0d5aa416ab1a8596bde68f71f7ebe431b86Vitaly Buka// Copyright 2015 The Weave Authors. All rights reserved. 2dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// Use of this source code is governed by a BSD-style license that can be 3dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// found in the LICENSE file. 4dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 52d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/notification/xmpp_iq_stanza_handler.h" 6dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 7dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko#include <base/bind.h> 8dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko#include <base/strings/string_number_conversions.h> 9dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko#include <base/strings/stringprintf.h> 101e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka#include <weave/provider/task_runner.h> 11dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 122d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/notification/xml_node.h" 132d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/notification/xmpp_channel.h" 14dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 15b6f015a1ef3caffbc2af53184c0ec5342e42e048Vitaly Bukanamespace weave { 16dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 17dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenkonamespace { 18dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 19dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// Default timeout for <iq> requests to the server. If the response hasn't been 20dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// received within this time interval, the request is considered as failed. 21dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenkoconst int kTimeoutIntervalSeconds = 30; 22dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 23dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// Builds an XML stanza that looks like this: 24dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// <iq id='${id}' type='${type}' from='${from}' to='${to}'>$body</iq> 25dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko// where 'to' and 'from' are optional attributes. 26dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenkostd::string BuildIqStanza(const std::string& id, 27dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko const std::string& type, 28dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko const std::string& to, 29dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko const std::string& from, 30dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko const std::string& body) { 31dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko std::string to_attr; 32dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (!to.empty()) { 33dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko CHECK_EQ(std::string::npos, to.find_first_of("<'>")) 34dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko << "Destination address contains invalid XML characters"; 35dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko base::StringAppendF(&to_attr, " to='%s'", to.c_str()); 36dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 37dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko std::string from_attr; 38dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (!from.empty()) { 39dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko CHECK_EQ(std::string::npos, from.find_first_of("<'>")) 40dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko << "Source address contains invalid XML characters"; 41dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko base::StringAppendF(&from_attr, " from='%s'", from.c_str()); 42dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 43a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka return base::StringPrintf("<iq id='%s' type='%s'%s%s>%s</iq>", id.c_str(), 44a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka type.c_str(), from_attr.c_str(), to_attr.c_str(), 45a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka body.c_str()); 46dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko} 47dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 48dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko} // anonymous namespace 49dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 50f9630fbb4aab7028af10600ef87be2e65df0ac91Vitaly BukaIqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel, 511e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka provider::TaskRunner* task_runner) 52f9630fbb4aab7028af10600ef87be2e65df0ac91Vitaly Buka : xmpp_channel_{xmpp_channel}, task_runner_{task_runner} {} 53dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 54a647c857f3098b366b379bde308d051f6a9aac2fVitaly Bukavoid IqStanzaHandler::SendRequest(const std::string& type, 55a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka const std::string& from, 56a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka const std::string& to, 57a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka const std::string& body, 58a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka const ResponseCallback& response_callback, 59a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka const TimeoutCallback& timeout_callback) { 6063cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka return SendRequestWithCustomTimeout( 6163cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka type, from, to, body, 6263cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka base::TimeDelta::FromSeconds(kTimeoutIntervalSeconds), response_callback, 6363cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka timeout_callback); 6463cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka} 6563cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka 6663cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Bukavoid IqStanzaHandler::SendRequestWithCustomTimeout( 6763cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const std::string& type, 6863cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const std::string& from, 6963cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const std::string& to, 7063cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const std::string& body, 7163cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka base::TimeDelta timeout, 7263cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const ResponseCallback& response_callback, 7363cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka const TimeoutCallback& timeout_callback) { 74dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // Remember the response callback to call later. 7552d006a131c61955e3a8a915d7f22941b3a4eee2Vitaly Buka requests_.insert(std::make_pair(++last_request_id_, response_callback)); 76dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // Schedule a time-out callback for this request. 7763cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka if (timeout < base::TimeDelta::Max()) { 78f9630fbb4aab7028af10600ef87be2e65df0ac91Vitaly Buka task_runner_->PostDelayedTask( 7963cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka FROM_HERE, 8063cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka base::Bind(&IqStanzaHandler::OnTimeOut, weak_ptr_factory_.GetWeakPtr(), 8163cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka last_request_id_, timeout_callback), 8263cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka timeout); 8363cc3d2c6150f2cc884998702af1ca2ef7afa9c9Vitaly Buka } 84dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 85a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka std::string message = 86a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka BuildIqStanza(std::to_string(last_request_id_), type, to, from, body); 87dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko xmpp_channel_->SendMessage(message); 88dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko} 89dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 90dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenkobool IqStanzaHandler::HandleIqStanza(std::unique_ptr<XmlNode> stanza) { 91dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko std::string type; 92dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (!stanza->GetAttribute("type", &type)) { 93dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko LOG(ERROR) << "IQ stanza missing 'type' attribute"; 94dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko return false; 95dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 96dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 97dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko std::string id_str; 98dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (!stanza->GetAttribute("id", &id_str)) { 99dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko LOG(ERROR) << "IQ stanza missing 'id' attribute"; 100dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko return false; 101dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 102dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 103dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (type == "result" || type == "error") { 104dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // These are response stanzas from the server. 105dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // Find the corresponding request. 106dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko RequestId id; 107dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (!base::StringToInt(id_str, &id)) { 108dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko LOG(ERROR) << "IQ stanza's 'id' attribute is invalid"; 109dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko return false; 110dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 111dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko auto p = requests_.find(id); 112dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (p != requests_.end()) { 113823fdda17d72adf6722d6573151add921c996c43Vitaly Buka task_runner_->PostDelayedTask( 114823fdda17d72adf6722d6573151add921c996c43Vitaly Buka FROM_HERE, base::Bind(p->second, base::Passed(std::move(stanza))), 115823fdda17d72adf6722d6573151add921c996c43Vitaly Buka {}); 116dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko requests_.erase(p); 117dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 118dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } else { 119dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // We do not support server-initiated IQ requests ("set" / "get" / "query"). 120dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // So just reply with "not implemented" error (and swap "to"/"from" attrs). 121dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko std::string error_body = 122dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko "<error type='modify'>" 123dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" 124dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko "</error>"; 125a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka std::string message = 126a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka BuildIqStanza(id_str, "error", stanza->GetAttributeOrEmpty("from"), 127a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka stanza->GetAttributeOrEmpty("to"), error_body); 128dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko xmpp_channel_->SendMessage(message); 129dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko } 130dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko return true; 131dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko} 132dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 133dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenkovoid IqStanzaHandler::OnTimeOut(RequestId id, 134dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko const TimeoutCallback& timeout_callback) { 135dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko // Request has not been processed yes, so a real timeout occurred. 136dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko if (requests_.erase(id) > 0) 137dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko timeout_callback.Run(); 138dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko} 139dea76b265236f7e20324360116efdd25ec4d312cAlex Vakulenko 140b6f015a1ef3caffbc2af53184c0ec5342e42e048Vitaly Buka} // namespace weave 141