1 /*
2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "webrtc/libjingle/xmpp/pubsub_task.h"
12 
13 #include <map>
14 #include <string>
15 
16 #include "webrtc/libjingle/xmpp/constants.h"
17 #include "webrtc/libjingle/xmpp/xmppengine.h"
18 #include "webrtc/base/common.h"
19 
20 namespace buzz {
21 
PubsubTask(XmppTaskParentInterface * parent,const buzz::Jid & pubsub_node_jid)22 PubsubTask::PubsubTask(XmppTaskParentInterface* parent,
23                        const buzz::Jid& pubsub_node_jid)
24     : buzz::XmppTask(parent, buzz::XmppEngine::HL_SENDER),
25       pubsub_node_jid_(pubsub_node_jid) {
26 }
27 
~PubsubTask()28 PubsubTask::~PubsubTask() {
29 }
30 
31 // Checks for pubsub publish events as well as responses to get IQs.
HandleStanza(const buzz::XmlElement * stanza)32 bool PubsubTask::HandleStanza(const buzz::XmlElement* stanza) {
33   const buzz::QName& stanza_name(stanza->Name());
34   if (stanza_name == buzz::QN_MESSAGE) {
35     if (MatchStanzaFrom(stanza, pubsub_node_jid_)) {
36       const buzz::XmlElement* pubsub_event_item =
37           stanza->FirstNamed(QN_PUBSUB_EVENT);
38       if (pubsub_event_item != NULL) {
39         QueueStanza(pubsub_event_item);
40         return true;
41       }
42     }
43   } else if (stanza_name == buzz::QN_IQ) {
44     if (MatchResponseIq(stanza, pubsub_node_jid_, task_id())) {
45       const buzz::XmlElement* pubsub_item = stanza->FirstNamed(QN_PUBSUB);
46       if (pubsub_item != NULL) {
47         QueueStanza(pubsub_item);
48         return true;
49       }
50     }
51   }
52   return false;
53 }
54 
ProcessResponse()55 int PubsubTask::ProcessResponse() {
56   const buzz::XmlElement* stanza = NextStanza();
57   if (stanza == NULL) {
58     return STATE_BLOCKED;
59   }
60 
61   if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
62     OnPubsubError(stanza->FirstNamed(buzz::QN_ERROR));
63     return STATE_RESPONSE;
64   }
65 
66   const buzz::QName& stanza_name(stanza->Name());
67   if (stanza_name == QN_PUBSUB_EVENT) {
68     HandlePubsubEventMessage(stanza);
69   } else if (stanza_name == QN_PUBSUB) {
70     HandlePubsubIqGetResponse(stanza);
71   }
72 
73   return STATE_RESPONSE;
74 }
75 
76 // Registers a function pointer to be called when the value of the pubsub
77 // node changes.
78 // Note that this does not actually change the XMPP pubsub
79 // subscription. All publish events are always received by everyone in the
80 // MUC. This function just controls whether the handle function will get
81 // called when the event is received.
SubscribeToNode(const std::string & pubsub_node,NodeHandler handler)82 bool PubsubTask::SubscribeToNode(const std::string& pubsub_node,
83                                  NodeHandler handler) {
84   subscribed_nodes_[pubsub_node] = handler;
85   rtc::scoped_ptr<buzz::XmlElement> get_iq_request(
86       MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id()));
87   if (!get_iq_request) {
88     return false;
89   }
90   buzz::XmlElement* pubsub_element = new buzz::XmlElement(QN_PUBSUB, true);
91   buzz::XmlElement* items_element = new buzz::XmlElement(QN_PUBSUB_ITEMS, true);
92 
93   items_element->AddAttr(buzz::QN_NODE, pubsub_node);
94   pubsub_element->AddElement(items_element);
95   get_iq_request->AddElement(pubsub_element);
96 
97   if (SendStanza(get_iq_request.get()) != buzz::XMPP_RETURN_OK) {
98     return false;
99   }
100 
101   return true;
102 }
103 
UnsubscribeFromNode(const std::string & pubsub_node)104 void PubsubTask::UnsubscribeFromNode(const std::string& pubsub_node) {
105   subscribed_nodes_.erase(pubsub_node);
106 }
107 
OnPubsubError(const buzz::XmlElement * error_stanza)108 void PubsubTask::OnPubsubError(const buzz::XmlElement* error_stanza) {
109 }
110 
111 // Checks for a pubsub event message like the following:
112 //
113 //  <message from="muvc-private-chat-some-id@groupchat.google.com"
114 //   to="john@site.com/gcomm582B14C9">
115 //    <event xmlns:"http://jabber.org/protocol/pubsub#event">
116 //      <items node="node-name">
117 //        <item id="some-id">
118 //          <payload/>
119 //        </item>
120 //      </items>
121 //    </event>
122 //  </message>
123 //
124 // It also checks for retraction event messages like the following:
125 //
126 //  <message from="muvc-private-chat-some-id@groupchat.google.com"
127 //   to="john@site.com/gcomm582B14C9">
128 //    <event xmlns:"http://jabber.org/protocol/pubsub#event">
129 //      <items node="node-name">
130 //        <retract id="some-id"/>
131 //      </items>
132 //    </event>
133 //  </message>
HandlePubsubEventMessage(const buzz::XmlElement * pubsub_event)134 void PubsubTask::HandlePubsubEventMessage(
135     const buzz::XmlElement* pubsub_event) {
136   ASSERT(pubsub_event->Name() == QN_PUBSUB_EVENT);
137   for (const buzz::XmlChild* child = pubsub_event->FirstChild();
138        child != NULL;
139        child = child->NextChild()) {
140     const buzz::XmlElement* child_element = child->AsElement();
141     const buzz::QName& child_name(child_element->Name());
142     if (child_name == QN_PUBSUB_EVENT_ITEMS) {
143       HandlePubsubItems(child_element);
144     }
145   }
146 }
147 
148 // Checks for a response to an pubsub IQ get like the following:
149 //
150 //  <iq from="muvc-private-chat-some-id@groupchat.google.com"
151 //   to="john@site.com/gcomm582B14C9"
152 //   type="result">
153 //    <pubsub xmlns:"http://jabber.org/protocol/pubsub">
154 //      <items node="node-name">
155 //        <item id="some-id">
156 //          <payload/>
157 //        </item>
158 //      </items>
159 //    </event>
160 //  </message>
HandlePubsubIqGetResponse(const buzz::XmlElement * pubsub_iq_response)161 void PubsubTask::HandlePubsubIqGetResponse(
162     const buzz::XmlElement* pubsub_iq_response) {
163   ASSERT(pubsub_iq_response->Name() == QN_PUBSUB);
164   for (const buzz::XmlChild* child = pubsub_iq_response->FirstChild();
165        child != NULL;
166        child = child->NextChild()) {
167     const buzz::XmlElement* child_element = child->AsElement();
168     const buzz::QName& child_name(child_element->Name());
169     if (child_name == QN_PUBSUB_ITEMS) {
170       HandlePubsubItems(child_element);
171     }
172   }
173 }
174 
175 // Calls registered handlers in response to pubsub event or response to
176 // IQ pubsub get.
177 // 'items' is the child of a pubsub#event:event node or pubsub:pubsub node.
HandlePubsubItems(const buzz::XmlElement * items)178 void PubsubTask::HandlePubsubItems(const buzz::XmlElement* items) {
179   ASSERT(items->HasAttr(QN_NODE));
180   const std::string& node_name(items->Attr(QN_NODE));
181   NodeSubscriptions::iterator iter = subscribed_nodes_.find(node_name);
182   if (iter != subscribed_nodes_.end()) {
183     NodeHandler handler = iter->second;
184     const buzz::XmlElement* item = items->FirstElement();
185     while (item != NULL) {
186       const buzz::QName& item_name(item->Name());
187       if (item_name != QN_PUBSUB_EVENT_ITEM &&
188           item_name != QN_PUBSUB_EVENT_RETRACT &&
189           item_name != QN_PUBSUB_ITEM) {
190         continue;
191       }
192 
193       (this->*handler)(item);
194       item = item->NextElement();
195     }
196     return;
197   }
198 }
199 
200 }
201