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