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