| // 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 |