Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Chromium OS 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 | |
Vitaly Buka | 912b698 | 2015-07-06 11:13:03 -0700 | [diff] [blame] | 5 | #include "libweave/src/notification/xmpp_iq_stanza_handler.h" |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 6 | |
| 7 | #include <base/bind.h> |
| 8 | #include <base/strings/string_number_conversions.h> |
| 9 | #include <base/strings/stringprintf.h> |
Bertrand SIMONNET | d282884 | 2015-08-12 12:18:02 -0700 | [diff] [blame^] | 10 | #include <chromeos/message_loops/message_loop.h> |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 11 | |
Vitaly Buka | 912b698 | 2015-07-06 11:13:03 -0700 | [diff] [blame] | 12 | #include "libweave/src/notification/xml_node.h" |
| 13 | #include "libweave/src/notification/xmpp_channel.h" |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 14 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 15 | namespace weave { |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 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 | } |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 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()); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 46 | } |
| 47 | |
| 48 | } // anonymous namespace |
| 49 | |
Bertrand SIMONNET | d282884 | 2015-08-12 12:18:02 -0700 | [diff] [blame^] | 50 | IqStanzaHandler::IqStanzaHandler(XmppChannelInterface* xmpp_channel) |
| 51 | : xmpp_channel_{xmpp_channel} { |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 52 | } |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 53 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 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) { |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 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) { |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 74 | // Remember the response callback to call later. |
| 75 | requests_.emplace(++last_request_id_, response_callback); |
| 76 | // Schedule a time-out callback for this request. |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 77 | if (timeout < base::TimeDelta::Max()) { |
Bertrand SIMONNET | d282884 | 2015-08-12 12:18:02 -0700 | [diff] [blame^] | 78 | chromeos::MessageLoop::current()->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 79 | FROM_HERE, |
| 80 | base::Bind(&IqStanzaHandler::OnTimeOut, weak_ptr_factory_.GetWeakPtr(), |
| 81 | last_request_id_, timeout_callback), |
| 82 | timeout); |
| 83 | } |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 84 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 85 | std::string message = |
| 86 | BuildIqStanza(std::to_string(last_request_id_), type, to, from, body); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 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()) { |
Bertrand SIMONNET | d282884 | 2015-08-12 12:18:02 -0700 | [diff] [blame^] | 113 | chromeos::MessageLoop::current()->PostTask( |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 114 | FROM_HERE, base::Bind(p->second, base::Passed(std::move(stanza)))); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 115 | requests_.erase(p); |
| 116 | } |
| 117 | } else { |
| 118 | // We do not support server-initiated IQ requests ("set" / "get" / "query"). |
| 119 | // So just reply with "not implemented" error (and swap "to"/"from" attrs). |
| 120 | std::string error_body = |
| 121 | "<error type='modify'>" |
| 122 | "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" |
| 123 | "</error>"; |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 124 | std::string message = |
| 125 | BuildIqStanza(id_str, "error", stanza->GetAttributeOrEmpty("from"), |
| 126 | stanza->GetAttributeOrEmpty("to"), error_body); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 127 | xmpp_channel_->SendMessage(message); |
| 128 | } |
| 129 | return true; |
| 130 | } |
| 131 | |
| 132 | void IqStanzaHandler::OnTimeOut(RequestId id, |
| 133 | const TimeoutCallback& timeout_callback) { |
| 134 | // Request has not been processed yes, so a real timeout occurred. |
| 135 | if (requests_.erase(id) > 0) |
| 136 | timeout_callback.Run(); |
| 137 | } |
| 138 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 139 | } // namespace weave |