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 15namespace weave { 16 17namespace { 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. 21const 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. 26std::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 50IqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel, 51 provider::TaskRunner* task_runner) 52 : xmpp_channel_{xmpp_channel}, task_runner_{task_runner} {} 53 54void 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 66void 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 90bool 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 133void 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