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