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