Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PubSubComponent.cs
1using System;
3using System.IO;
4using System.Text;
5using System.Xml;
6using System.Threading.Tasks;
7using Waher.Content;
9using Waher.Events;
24
26{
32 {
36 public const string NamespacePubSub = "http://jabber.org/protocol/pubsub";
37
41 public const string NamespacePubSubOwner = "http://jabber.org/protocol/pubsub#owner";
42
46 public const string NamespacePubSubEvents = "http://jabber.org/protocol/pubsub#event";
47
51 public const string NamespaceStanzaHeaders = "http://jabber.org/protocol/shim";
52
56 public const string FormTypeNodeConfig = "http://jabber.org/protocol/pubsub#node_config";
57
61 public const string FormTypeSubscribeOptions = "http://jabber.org/protocol/pubsub#subscribe_options";
62
66 public const string FormTypeSubscriptionAuthorization = "http://jabber.org/protocol/pubsub#subscribe_authorization";
67
71 public const string FormTypeNodeMetaData = "http://jabber.org/protocol/pubsub#meta-data";
72
76 public const string NamespacePubSubAccessAuthorize = "http://jabber.org/protocol/pubsub#access-authorize";
77
81 public const string NamespacePubSubAccessOpen = "http://jabber.org/protocol/pubsub#access-open";
82
86 public const string NamespacePubSubAccessPresence = "http://jabber.org/protocol/pubsub#access-presence";
87
91 public const string NamespacePubSubAccessRoster = "http://jabber.org/protocol/pubsub#access-roster";
92
96 public const string NamespacePubSubAccessWhitelist = "http://jabber.org/protocol/pubsub#access-whitelist";
97
101 public const string NamespacePubSubCollections = "http://jabber.org/protocol/pubsub#collections";
102
106 public const string NamespacePubSubNodeConfiguration = "http://jabber.org/protocol/pubsub#config-node";
107
111 public const string NamespacePubSubCreateAndConfigure = "http://jabber.org/protocol/pubsub#create-and-configure";
112
116 public const string NamespacePubSubCreateNodes = "http://jabber.org/protocol/pubsub#create-nodes";
117
121 public const string NamespacePubSubDeleteItems = "http://jabber.org/protocol/pubsub#delete-items";
122
126 public const string NamespacePubSubDeleteNodes = "http://jabber.org/protocol/pubsub#delete-nodes";
127
131 public const string NamespacePubSubItemIds = "http://jabber.org/protocol/pubsub#item-ids";
132
136 public const string NamespacePubSubLastPublished = "http://jabber.org/protocol/pubsub#last-published";
137
141 public const string NamespacePubSubLeasedSubscription = "http://jabber.org/protocol/pubsub#leased-subscription";
142
146 public const string NamespacePubSubManageSubscriptions = "http://jabber.org/protocol/pubsub#manage-subscriptions";
147
151 public const string NamespacePubSubemberAffiliation = "http://jabber.org/protocol/pubsub#member-affiliation";
152
156 public const string NamespacePubSubMetaData = "http://jabber.org/protocol/pubsub#meta-data";
157
161 public const string NamespacePubSubModifyAffiliations = "http://jabber.org/protocol/pubsub#modify-affiliations";
162
166 public const string NamespacePubSubMultiCollecton = "http://jabber.org/protocol/pubsub#multi-collection";
167
171 public const string NamespacePubSubMultiItems = "http://jabber.org/protocol/pubsub#multi-items";
172
176 public const string NamespacePubSubOutcastAffiliation = "http://jabber.org/protocol/pubsub#outcast-affiliation";
177
181 public const string NamespacePubSubPersistentItems = "http://jabber.org/protocol/pubsub#persistent-items";
182
186 public const string NamespacePubSubPresenceSubscribe = "http://jabber.org/protocol/pubsub#presence-subscribe";
187
191 public const string NamespacePubSubPublish = "http://jabber.org/protocol/pubsub#publish";
192
196 public const string NamespacePubSubPublishOnlyAffiliation = "http://jabber.org/protocol/pubsub#publish-only-affiliation";
197
201 public const string NamespacePubSubPublisheAffiliation = "http://jabber.org/protocol/pubsub#publisher-affiliation";
202
206 public const string NamespacePubSubPurgeNodes = "http://jabber.org/protocol/pubsub#purge-nodes";
207
211 public const string NamespacePubSubRetractItems = "http://jabber.org/protocol/pubsub#retract-items";
212
216 public const string NamespacePubSubRetrieveAffiliations = "http://jabber.org/protocol/pubsub#retrieve-affiliations";
217
221 public const string NamespacePubSubRetrieveDefault = "http://jabber.org/protocol/pubsub#retrieve-default";
222
226 public const string NamespacePubSubRetrieveDefaultSub = "http://jabber.org/protocol/pubsub#retrieve-default-sub";
227
231 public const string NamespacePubSubRetrieveItems = "http://jabber.org/protocol/pubsub#retrieve-items";
232
236 public const string NamespacePubSubRetrieveSubscriptions = "http://jabber.org/protocol/pubsub#retrieve-subscriptions";
237
241 public const string NamespacePubSubSubscribe = "http://jabber.org/protocol/pubsub#subscribe";
242
246 public const string NamespacePubSubSubscriptionOptions = "http://jabber.org/protocol/pubsub#subscription-options";
247
251 public const string NamespacePubSubSubscriptionNotifications = "http://jabber.org/protocol/pubsub#subscription-notifications";
252
256 public const string NamespacePubSubAutoCreate = "http://jabber.org/protocol/pubsub#auto-create";
257
261 public const string NamespacePubSubAutoSubscribe = "http://jabber.org/protocol/pubsub#auto-subscribe";
262
263
264 private static ProvisioningClient provisioningClient = null;
265
266 private readonly Dictionary<CaseInsensitiveString, Nodes> nodesByServiceAndName = new Dictionary<CaseInsensitiveString, Nodes>();
267 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>> subscriptionsByServiceAndNodeAndJid = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>>();
268 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>> subscriptionsByJidAndServiceAndNode = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>>();
269 private readonly Dictionary<CaseInsensitiveString, NodeVerInfo> nodeVerByFullJidByBareJid = new Dictionary<CaseInsensitiveString, NodeVerInfo>();
270 private Cache<string, Dictionary<string, bool>> featuresByNodeVer = new Cache<string, Dictionary<string, bool>>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromDays(1));
271 private Cache<CaseInsensitiveString, PubSubItem> items = new Cache<CaseInsensitiveString, PubSubItem>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromMinutes(1));
272 private ObjectSerializer itemSerializer;
273 private bool disposed;
274
275 private class Nodes
276 {
277 public Dictionary<string, PubSubNode> NodesByName;
278 public PubSubNode[] NodesArray;
279 }
280
282 : base(Server, Subdomain, Name)
283 {
284 this.RegisterIqGetHandler("pubsub", NamespacePubSub, this.PubSubGetHandler, true);
285 Server.RegisterIqGetHandler("pubsub", NamespacePubSub, this.PepGetHandler, false);
286 Server.Accounts.RegisterIqGetHandler("pubsub", NamespacePubSub, this.PepGetHandler, true);
287
288 this.RegisterIqSetHandler("pubsub", NamespacePubSub, this.PubSubSetHandler, false);
289 Server.RegisterIqSetHandler("pubsub", NamespacePubSub, this.PepSetHandler, false);
290 Server.Accounts.RegisterIqSetHandler("pubsub", NamespacePubSub, this.PepSetHandler, false);
291
292 this.RegisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PubSubOwnerGetHandler, true);
293 Server.RegisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerGetHandler, false);
294 Server.Accounts.RegisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerGetHandler, true);
295
296 this.RegisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PubSubOwnerSetHandler, false);
297 Server.RegisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerSetHandler, false);
298 Server.Accounts.RegisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerSetHandler, false);
299
300 this.RegisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.MessageFormHandler, true);
301 Server.RegisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.PepMessageFormHandler, false);
302 Server.Accounts.RegisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.PepMessageFormHandler, true);
303
304 Server.Accounts.OnDiscoveryQueryNodeGet += this.PepDiscoveryQueryGet;
305 Server.Accounts.OnDiscoveryQueryNodeItemsGet += this.PepDiscoveryQueryItemsGet;
306 Server.Accounts.DiscoverIdentities += this.PepDiscoverIdentities;
307
308 // TODO: http://jabber.org/protocol/pubsub#get-pending
309
310 this.AddFeatures(this, pubSubFeatures);
311 this.AddFeatures(Server.Accounts, pepFeatures);
312
313 provisioningClient ??= Types.TryGetModuleParameter<ProvisioningClient>("Provisioning");
314
315 Server.OnPresenceLocalRecipient += this.Server_OnPresenceLocalRecipient;
316 }
317
324 public static async Task<PubSubComponent> Create(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
325 {
327
329 Result.itemSerializer = await FilesProvider.GetObjectSerializerEx(typeof(PubSubItem));
330 else
331 Result.itemSerializer = null;
332
333 return Result;
334 }
335
336 private static readonly DefaultFeatures pubSubFeatures = new DefaultFeatures()
337 {
338 AutoCreate = false,
339 AutoSubscribe = false
340 };
341
342 private static readonly DefaultFeatures pepFeatures = new DefaultFeatures()
343 {
344 AutoCreate = true,
345 AutoSubscribe = true
346 };
347
348 private void AddFeatures(Component Component, DefaultFeatures Features)
349 {
387
388 if (Features.AutoCreate)
390
391 if (Features.AutoSubscribe)
393 }
394
398 public override void Dispose()
399 {
400 if (!this.disposed)
401 {
402 this.disposed = true;
403
404 this.UnregisterIqGetHandler("pubsub", NamespacePubSub, this.PubSubGetHandler, true);
405 this.Server.UnregisterIqGetHandler("pubsub", NamespacePubSub, this.PepGetHandler, false);
406 this.Server.Accounts.UnregisterIqGetHandler("pubsub", NamespacePubSub, this.PepGetHandler, true);
407
408 this.UnregisterIqSetHandler("pubsub", NamespacePubSub, this.PubSubSetHandler, false);
409 this.Server.UnregisterIqSetHandler("pubsub", NamespacePubSub, this.PepSetHandler, false);
410 this.Server.Accounts.UnregisterIqSetHandler("pubsub", NamespacePubSub, this.PepSetHandler, false);
411
412 this.UnregisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PubSubOwnerGetHandler, true);
413 this.Server.UnregisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerGetHandler, false);
414 this.Server.Accounts.UnregisterIqGetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerGetHandler, true);
415
416 this.UnregisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PubSubOwnerSetHandler, false);
417 this.Server.UnregisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerSetHandler, false);
418 this.Server.Accounts.UnregisterIqSetHandler("pubsub", NamespacePubSubOwner, this.PepOwnerSetHandler, false);
419
420 this.UnregisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.MessageFormHandler, true);
421 this.Server.UnregisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.PepMessageFormHandler, false);
422 this.Server.Accounts.UnregisterMessageHandler("x", Networking.XMPP.XmppClient.NamespaceData, this.PepMessageFormHandler, true);
423
424 this.Server.Accounts.OnDiscoveryQueryNodeGet -= this.PepDiscoveryQueryGet;
425 this.Server.Accounts.OnDiscoveryQueryNodeItemsGet -= this.PepDiscoveryQueryItemsGet;
426
427 this.RemoveFeatures(this);
428 this.RemoveFeatures(this.Server.Accounts);
429
430 this.itemSerializer = null;
431
432 this.Server.OnPresenceLocalRecipient -= this.Server_OnPresenceLocalRecipient;
433
434 this.featuresByNodeVer?.Dispose();
435 this.featuresByNodeVer = null;
436
437 this.items?.Dispose();
438 this.items = null;
439
440 base.Dispose();
441 }
442 }
443
444 private void RemoveFeatures(Component Component)
445 {
483 }
484
489 public override bool SupportsAccounts => false;
490
501 public async Task<PubSubNode> GetNodeAsync(CaseInsensitiveString Service, CaseInsensitiveString NodeName,
502 NodeAccessModel? AutoCreateAccess, XmppAddress From, CaseInsensitiveString Domain)
503 {
504 Nodes NodesOnService;
505 PubSubNode Node;
506 bool FirstRequestOnService = false;
507
508 lock (this.nodesByServiceAndName)
509 {
510 if (!this.nodesByServiceAndName.TryGetValue(Service, out NodesOnService))
511 {
512 FirstRequestOnService = true;
513 NodesOnService = new Nodes()
514 {
515 NodesByName = new Dictionary<string, PubSubNode>(),
516 NodesArray = Array.Empty<PubSubNode>()
517 };
518
519 this.nodesByServiceAndName[Service] = NodesOnService;
520 }
521
522 if (NodesOnService.NodesByName.TryGetValue(NodeName, out Node))
523 return Node;
524 }
525
526 if (FirstRequestOnService && !string.IsNullOrEmpty(Service))
527 {
528 foreach (PubSubNode Node2 in await Database.Find<PubSubNode>(
529 new FilterFieldEqualTo("Service", Service)))
530 {
531 lock (this.nodesByServiceAndName)
532 {
533 NodesOnService.NodesByName[NodeName] = Node2;
534 }
535
536 if (Node2.Name == NodeName)
537 Node = Node2;
538 }
539
540 lock (this.nodesByServiceAndName)
541 {
542 NodesOnService.NodesArray = new PubSubNode[NodesOnService.NodesByName.Count];
543 NodesOnService.NodesByName.Values.CopyTo(NodesOnService.NodesArray, 0);
544 }
545 }
546 else
547 {
548 Node = await Database.FindFirstDeleteRest<PubSubNode>(new FilterAnd(
549 new FilterFieldEqualTo("Service", Service),
550 new FilterFieldEqualTo("Name", NodeName)));
551 }
552
553 if (Node is null)
554 {
555 if (!AutoCreateAccess.HasValue)
556 return null;
557
558 CaseInsensitiveString Creator = From.BareJid;
559
560 Node = new PubSubNode()
561 {
562 Service = Service,
563 Name = NodeName,
564 Domain = Domain,
565 Creator = Creator,
566 Created = DateTime.UtcNow,
567 Owners = new CaseInsensitiveString[] { Creator },
568 IsRoot = true,
569 AccessModel = AutoCreateAccess.Value,
570 PublisherModel = PublisherModel.publishers,
571 SendLastPublishedItem = SendLastPublishedItem.on_sub_and_presence,
573 MaxItems = 10,
574 AutoSubscribe = true,
575 AutoDelete = true,
576 NodeType = NodeType.leaf
577 };
578
579 await Database.Insert(Node);
580 }
581
582 lock (this.nodesByServiceAndName)
583 {
584 NodesOnService.NodesByName[NodeName] = Node;
585
586 NodesOnService.NodesArray = new PubSubNode[NodesOnService.NodesByName.Count];
587 NodesOnService.NodesByName.Values.CopyTo(NodesOnService.NodesArray, 0);
588 }
589
590 return Node;
591 }
592
601 {
602 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> SubscriptionsByServiceAndNode;
603 Dictionary<CaseInsensitiveString, Subscription> SubscriptionsByNode;
604 Subscription Result;
605
606 lock (this.subscriptionsByJidAndServiceAndNode)
607 {
608 if (!this.subscriptionsByJidAndServiceAndNode.TryGetValue(Jid, out SubscriptionsByServiceAndNode))
609 {
610 SubscriptionsByServiceAndNode = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>();
611 this.subscriptionsByJidAndServiceAndNode[Jid] = SubscriptionsByServiceAndNode;
612 }
613
614 if (SubscriptionsByServiceAndNode.TryGetValue(Service, out SubscriptionsByNode))
615 {
616 if (SubscriptionsByNode.TryGetValue(Node, out Result))
617 return Result;
618 else
619 return null;
620 }
621 }
622
623 SubscriptionsByNode = new Dictionary<CaseInsensitiveString, Subscription>();
624
626 new FilterFieldEqualTo("Jid", Jid), new FilterFieldEqualTo("Service", Service))))
627 {
628 SubscriptionsByNode[Subscription.Node] = Subscription;
629 }
630
631 lock (this.subscriptionsByJidAndServiceAndNode)
632 {
633 SubscriptionsByServiceAndNode[Service] = SubscriptionsByNode;
634
635 if (SubscriptionsByNode.TryGetValue(Node, out Result))
636 return Result;
637 else
638 return null;
639 }
640 }
641
648 public async Task<Subscription[]> GetSubscriptionsByJid(CaseInsensitiveString Service, CaseInsensitiveString Jid)
649 {
650 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> SubscriptionsByServiceAndNode;
651 Dictionary<CaseInsensitiveString, Subscription> SubscriptionsByNode;
652 Subscription[] Result;
653
654 lock (this.subscriptionsByJidAndServiceAndNode)
655 {
656 if (!this.subscriptionsByJidAndServiceAndNode.TryGetValue(Jid, out SubscriptionsByServiceAndNode))
657 {
658 SubscriptionsByServiceAndNode = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>();
659 this.subscriptionsByJidAndServiceAndNode[Jid] = SubscriptionsByServiceAndNode;
660 }
661
662 if (SubscriptionsByServiceAndNode.TryGetValue(Service, out SubscriptionsByNode))
663 {
664 Result = new Subscription[SubscriptionsByNode.Count];
665 SubscriptionsByNode.Values.CopyTo(Result, 0);
666 return Result;
667 }
668 }
669
670 SubscriptionsByNode = new Dictionary<CaseInsensitiveString, Subscription>();
671
673 new FilterFieldEqualTo("Jid", Jid), new FilterFieldEqualTo("Service", Service))))
674 {
675 SubscriptionsByNode[Subscription.Node] = Subscription;
676 }
677
678 Result = new Subscription[SubscriptionsByNode.Count];
679 SubscriptionsByNode.Values.CopyTo(Result, 0);
680
681 lock (this.subscriptionsByJidAndServiceAndNode)
682 {
683 SubscriptionsByServiceAndNode[Service] = SubscriptionsByNode;
684 }
685
686 return Result;
687 }
688
695 public async Task<Subscription[]> GetSubscriptionsOnNode(CaseInsensitiveString Service, CaseInsensitiveString Node)
696 {
697 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> SubscriptionsByNodeAndJid;
698 Dictionary<CaseInsensitiveString, Subscription> SubscriptionsByJid;
699 Subscription[] Result;
700
701 lock (this.subscriptionsByServiceAndNodeAndJid)
702 {
703 if (!this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out SubscriptionsByNodeAndJid))
704 {
705 SubscriptionsByNodeAndJid = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>>();
706 this.subscriptionsByServiceAndNodeAndJid[Service] = SubscriptionsByNodeAndJid;
707 }
708
709 if (SubscriptionsByNodeAndJid.TryGetValue(Node, out SubscriptionsByJid))
710 {
711 Result = new Subscription[SubscriptionsByJid.Count];
712 SubscriptionsByJid.Values.CopyTo(Result, 0);
713 return Result;
714 }
715 }
716
717 SubscriptionsByJid = new Dictionary<CaseInsensitiveString, Subscription>();
718
720 new FilterFieldEqualTo("Service", Service), new FilterFieldEqualTo("Node", Node))))
721 {
722 SubscriptionsByJid[Subscription.Jid] = Subscription;
723 }
724
725 Result = new Subscription[SubscriptionsByJid.Count];
726 SubscriptionsByJid.Values.CopyTo(Result, 0);
727
728 lock (this.subscriptionsByServiceAndNodeAndJid)
729 {
730 SubscriptionsByNodeAndJid[Node] = SubscriptionsByJid;
731 }
732
733 return Result;
734 }
735
736 private Task PubSubGetHandler(object Sender, IqEventArgs e)
737 {
738 return this.PubSubGetHandler(string.Empty, e, pubSubFeatures);
739 }
740
741 private Task PepGetHandler(object Sender, IqEventArgs e)
742 {
743 return this.PubSubGetHandler((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
744 }
745
746 private async Task PubSubGetHandler(CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures _)
747 {
748 foreach (XmlNode N in e.Query.ChildNodes)
749 {
750 if (N is XmlElement E)
751 {
752 switch (E.LocalName)
753 {
754 case "options":
755 string NodeName = XML.Attribute(E, "node");
756 if (string.IsNullOrEmpty(NodeName))
757 {
758 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
759 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
760 return;
761 }
762
763 CaseInsensitiveString Jid = XML.Attribute(E, "jid");
765 {
766 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
767 "<jid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "JID not specified.", "en");
768 return;
769 }
770
771 XmppAddress Address = new XmppAddress(Jid);
772
773 if (Address.BareJid != e.From.BareJid)
774 {
775 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
776 "<invalid-jid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid JID", "en");
777 }
778
779 PubSubNode Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
780 if (Node is null)
781 {
782 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
783 return;
784 }
785
786 Subscription Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
787
788 if (Subscription is null)
789 {
790 await e.IqError("modify", "<unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
791 "<not-subscribed xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "No such subscriber.", "en");
792 return;
793 }
794
795 string SubID = XML.Attribute(E, "subid");
796 /*if (string.IsNullOrEmpty(SubID))
797 {
798 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
799 "<subid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Subscription ID required.", "en");
800 return;
801 }*/
802
803 if (!string.IsNullOrEmpty(SubID) && SubID != Subscription.ObjectId)
804 {
805 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
806 "<invalid-subid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid subscription ID.", "en");
807 return;
808 }
809
810 DataForm Form = this.GetForm(Node, Subscription, e);
811 StringBuilder Xml = new StringBuilder();
812
813 Xml.Append("<pubsub xmlns='");
814 Xml.Append(NamespacePubSub);
815 Xml.Append("'><options node='");
816 Xml.Append(XML.Encode(NodeName));
817 Xml.Append("' jid='");
818 Xml.Append(XML.Encode(Jid));
819 Xml.Append("'>");
820 Form.SerializeForm(Xml);
821 Xml.Append("</options></pubsub>");
822
823 await e.IqResult(Xml.ToString(), e.To);
824 return;
825
826 case "default":
827 NodeName = XML.Attribute(E, "node");
828 if (!string.IsNullOrEmpty(NodeName))
829 {
830 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
831 if (Node is null)
832 {
833 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
834 return;
835 }
836 }
837 else
838 Node = null;
839
840 Subscription = new Subscription();
841 Form = this.GetForm(Node, Subscription, e);
842 Xml = new StringBuilder();
843
844 Xml.Append("<pubsub xmlns='");
845 Xml.Append(NamespacePubSub);
846 Xml.Append("'><default");
847
848 if (!string.IsNullOrEmpty(NodeName))
849 {
850 Xml.Append(" node='");
851 Xml.Append(XML.Encode(NodeName));
852 Xml.Append('\'');
853 }
854
855 Xml.Append('>');
856 Form.SerializeForm(Xml);
857 Xml.Append("</default></pubsub>");
858
859 await e.IqResult(Xml.ToString(), e.To);
860 return;
861
862 case "items":
863 NodeName = XML.Attribute(E, "node");
864 if (string.IsNullOrEmpty(NodeName))
865 {
866 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
867 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
868 return;
869 }
870
871 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
872 if (Node is null)
873 {
874 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
875 return;
876 }
877
878 if (!(XmppServerModule.PersistenceLayer is null) &&
879 await XmppServerModule.PersistenceLayer.IsBlocked(e.From.BareJid, Node.Creator))
880 {
881 await e.IqErrorForbidden(e.To, "Account has been blocked.", "en");
882 return;
883 // TODO: When blocking an account, remove subscriptions of nodes of owner performing the block.
884 }
885
886 if (!await Node.CanRetrieveItems(e.From.BareJid, Node.AccessModel != NodeAccessModel.whitelist))
887 {
888 await e.IqErrorForbidden(e.To, "Not authoried to retrieve items.", "en");
889 return;
890 }
891
892 switch (Node.AccessModel)
893 {
894 case NodeAccessModel.open:
895 break;
896
897 case NodeAccessModel.presence:
898 XmppAddress Owner = new XmppAddress(Node.Creator);
900
901 if (this.Server.IsServerDomain(Owner.Domain, true))
902 RosterItem = await XmppServerModule.GetRosterItemAsync(Owner.Account, e.From.BareJid);
903 else
904 RosterItem = null;
905
906 if (RosterItem is null || (RosterItem.Subscription != SubscriptionStatus.both &&
907 RosterItem.Subscription != SubscriptionStatus.from))
908 {
909 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
910 "<presence-subscription-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
911 "Presence subscription required to get item.", "en");
912 return;
913 }
914 break;
915
916 case NodeAccessModel.authorize:
917 case NodeAccessModel.roster:
918 case NodeAccessModel.whitelist:
919 default:
920 Subscription = await this.GetSubscriptionAsync(Service, NodeName, e.From.Address);
921 Subscription ??= await this.GetSubscriptionAsync(Service, NodeName, e.From.BareJid);
922
923 if (Subscription is null || Subscription.Status == NodeSubscriptionStatus.none)
924 {
925 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
926 "<not-subscribed xmlns='http://jabber.org/protocol/pubsub#errors'/>",
927 e.To, "Not subscribed.", "en");
928 return;
929 }
930 else if (Subscription.Status == NodeSubscriptionStatus.pending)
931 {
932 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
933 "<pending-subscription xmlns='http://jabber.org/protocol/pubsub#errors'/>",
934 e.To, "Pending subscription.", "en");
935 return;
936 }
937 else if (Subscription.Status == NodeSubscriptionStatus.unconfigured)
938 {
939 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
940 "<configuration-required xmlns='http://jabber.org/protocol/pubsub#errors'/>",
941 e.To, "Configuration required.", "en");
942 return;
943 }
944 break;
945 }
946
948 List<PubSubItem> RequestedItems = null;
949 IEnumerable<PubSubItem> Items;
951 bool NewestFirst = !(RestrictedQuery is null) && RestrictedQuery.Max.HasValue;
952 int Offset = 0;
953 int Max;
954 bool OneExtra = RestrictedQuery is null;
955 bool Paginated = !(RestrictedQuery is null);
956 bool ReverseOrder = false;
957
958 if (Node.DeliverPayloads)
959 Max = 10;
960 else
961 Max = 100;
962
963 if (E.HasAttribute("max_items"))
964 {
965 NewestFirst = true;
966 OneExtra = false;
967 Paginated = true;
968 ReverseOrder = true;
969
970 int i = XML.Attribute(E, "max_items", Max);
971 if (i < Max)
972 Max = i;
973 }
974
975 if (Node.PersistItems)
976 {
977 foreach (XmlNode N2 in E.ChildNodes)
978 {
979 if (N2 is XmlElement E2 && E2.LocalName == "item")
980 {
981 RequestedItems ??= new List<PubSubItem>();
982
983 try
984 {
985 PubSubItem Item = await this.LoadItem(Service, NodeName, XML.Attribute(E2, "id"));
986 if (!(Item is null) && Item.Node == NodeName)
987 RequestedItems.Add(Item);
988 }
989 catch (Exception)
990 {
991 // Erroneous node references should just be ignored (§ 6.5.9.12)
992 }
993 }
994 }
995
996 if (!(RequestedItems is null))
997 Items = RequestedItems.ToArray();
998 else
999 {
1000 List<Filter> Filters = new List<Filter>()
1001 {
1002 new FilterFieldEqualTo("Service", Service),
1003 new FilterFieldEqualTo("Node", NodeName)
1004 };
1005
1006 if (!(RestrictedQuery is null))
1007 {
1008 if (RestrictedQuery.Max.HasValue && RestrictedQuery.Max.Value < Max)
1009 Max = RestrictedQuery.Max.Value;
1010
1011 if (RestrictedQuery.Index.HasValue && RestrictedQuery.Index.Value >= 0)
1012 Offset = RestrictedQuery.Index.Value;
1013
1014 if (!(RestrictedQuery.Before is null))
1015 {
1016 NewestFirst = false;
1017 ReverseOrder = true;
1018
1019 if (!string.IsNullOrEmpty(RestrictedQuery.Before))
1020 {
1021 PubSubItem Item = await this.LoadItem(Service, NodeName, RestrictedQuery.Before);
1022 if (Item is null)
1023 {
1024 await e.IqErrorBadRequest(e.To, "Before item not found.", "en");
1025 return;
1026 }
1027
1028 if (Item.Node != NodeName)
1029 {
1030 await e.IqErrorBadRequest(e.To, "Before item identity points to item belonging to another node.", "en");
1031 return;
1032 }
1033
1034 Filters.Add(new FilterFieldGreaterThan("Created", Item.Created));
1035 }
1036 }
1037
1038 if (!string.IsNullOrEmpty(RestrictedQuery.After))
1039 {
1040 try
1041 {
1042 PubSubItem Item = await this.LoadItem(Service, NodeName, RestrictedQuery.After);
1043 if (Item is null)
1044 {
1045 await e.IqErrorBadRequest(e.To, "After item not found.", "en");
1046 return;
1047 }
1048
1049 if (Item.Node != NodeName)
1050 {
1051 await e.IqErrorBadRequest(e.To, "After item identity points to item belonging to another node.", "en");
1052 return;
1053 }
1054
1055 NewestFirst = true;
1056 Filters.Add(new FilterFieldLesserThan("Created", Item.Created));
1057 }
1058 catch (Exception)
1059 {
1060 await e.IqErrorBadRequest(e.To, "After item not found.", "en");
1061 return;
1062 }
1063 }
1064 }
1065
1066 Filter = new FilterAnd(Filters.ToArray());
1067
1068 if (NewestFirst)
1069 Items = await Database.Find<PubSubItem>(Offset, OneExtra ? Max + 1 : Max, Filter, "-Service", "-Node", "-Created");
1070 else
1071 Items = await Database.Find<PubSubItem>(Offset, OneExtra ? Max + 1 : Max, Filter, "Service", "Node", "Created");
1072
1073 if (ReverseOrder)
1074 {
1075 if (Items is PubSubItem[] ItemArray)
1076 Array.Reverse(ItemArray);
1077 else
1078 {
1079 List<PubSubItem> ItemList = new List<PubSubItem>();
1080 ItemList.AddRange(Items);
1081 ItemList.Reverse();
1082 Items = ItemList;
1083 }
1084 }
1085 }
1086 }
1087 else if (!(RestrictedQuery is null))
1088 {
1089 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1090 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='persistent-items'/>",
1091 e.To, "Node does not support persisted items and pagination.", "en");
1092 return;
1093 }
1094 else if (Node.NodeType == NodeType.collection || Node.TransientNode)
1095 {
1096 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1097 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='retrieve-items'/>",
1098 e.To, "Collection and transient nodes do not support item retrieval.", "en");
1099 return;
1100 }
1101 else if (!(Node.LastPublished is null))
1102 Items = new PubSubItem[] { Node.LastPublished };
1103 else
1104 Items = Array.Empty<PubSubItem>();
1105
1106 Xml = new StringBuilder();
1107
1108 Xml.Append("<pubsub xmlns='");
1109 Xml.Append(NamespacePubSub);
1110 Xml.Append("'><items node='");
1111 Xml.Append(XML.Encode(NodeName));
1112 Xml.Append("'>");
1113
1114 string First = null;
1115 string Last = null;
1116 int Left = Max;
1117
1118 foreach (PubSubItem Item in Items)
1119 {
1120 if (Left-- <= 0)
1121 {
1122 Paginated = true;
1123 break;
1124 }
1125
1126 First ??= Item.ItemIdOrObjectId;
1127 Last = Item.ItemIdOrObjectId;
1128
1129 Xml.Append("<item id='");
1130 Xml.Append(XML.Encode(Item.ItemIdOrObjectId));
1131
1132 if (Node.DeliverPayloads || !(RequestedItems is null))
1133 {
1134 Xml.Append("' publisher='");
1135 Xml.Append(XML.Encode(Item.Publisher));
1136 Xml.Append("'>");
1137
1138 if (XML.IsValidXml(Item.Payload, true, true, true, true, false, false))
1139 Xml.Append(Item.Payload);
1140
1141 Xml.Append("</item>");
1142 }
1143 else
1144 Xml.Append("'/>");
1145 }
1146
1147 Xml.Append("</items>");
1148
1149 if (Paginated)
1150 ResultPage.Append(Xml, First, null, Last, null);
1151
1152 Xml.Append("</pubsub>");
1153
1154 await e.IqResult(Xml.ToString(), e.To);
1155 return;
1156
1157 case "subscriptions":
1158 Xml = new StringBuilder();
1159
1160 Xml.Append("<pubsub xmlns='");
1161 Xml.Append(NamespacePubSub);
1162
1163 if (E.HasAttribute("node"))
1164 {
1165 NodeName = E.GetAttribute("node");
1166
1167 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1168 if (Node is null)
1169 {
1170 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1171 return;
1172 }
1173
1174 Xml.Append("'><subscriptions node='");
1175 Xml.Append(XML.Encode(NodeName));
1176 Xml.Append("'>");
1177
1178 Serialize(Subset(await this.GetSubscriptionsByJid(Service, e.From.Address), NodeName), Xml);
1179 if (e.From.IsFullJID)
1180 Serialize(Subset(await this.GetSubscriptionsByJid(Service, e.From.BareJid), NodeName), Xml);
1181 }
1182 else
1183 {
1184 Xml.Append("'><subscriptions>");
1185
1186 Serialize(await this.GetSubscriptionsByJid(Service, e.From.Address), Xml);
1187 if (e.From.IsFullJID)
1188 Serialize(await this.GetSubscriptionsByJid(Service, e.From.BareJid), Xml);
1189 }
1190
1191 Xml.Append("</subscriptions></pubsub>");
1192
1193 await e.IqResult(Xml.ToString(), e.To);
1194 return;
1195
1196 case "affiliations":
1197 Xml = new StringBuilder();
1198
1199 Xml.Append("<pubsub xmlns='");
1200 Xml.Append(NamespacePubSub);
1201
1202 if (E.HasAttribute("node"))
1203 {
1204 NodeName = E.GetAttribute("node");
1205
1206 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1207 if (Node is null)
1208 {
1209 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1210 return;
1211 }
1212
1213 Xml.Append("'><affiliations node='");
1214 Xml.Append(XML.Encode(NodeName));
1215
1216 if (Node.TryGetAffiliation(e.From.BareJid, out AffiliationStatus Affiliation))
1217 {
1218 Xml.Append("'>");
1219 Xml.Append("<affiliation node='");
1220 Xml.Append(XML.Encode(NodeName));
1221 Xml.Append("' affiliation='");
1222 Xml.Append(ToString(Affiliation));
1223 Xml.Append("'/></affiliations>");
1224 }
1225 else
1226 Xml.Append("'/>");
1227 }
1228 else
1229 {
1230 Xml.Append("'><affiliations>");
1231
1232 // TODO: Search on affiliations
1233
1234 foreach (PubSubNode Node2 in await Database.Find<PubSubNode>(new FilterFieldEqualTo("Service", Service)))
1235 {
1236 if (Node2.TryGetAffiliation(e.From.BareJid, out AffiliationStatus Affiliation))
1237 {
1238 Xml.Append("<affiliation node='");
1239 Xml.Append(XML.Encode(Node2.Name));
1240 Xml.Append("' affiliation='");
1241 Xml.Append(ToString(Affiliation));
1242 Xml.Append("'/>");
1243 }
1244 }
1245
1246 Xml.Append("</affiliations>");
1247 }
1248
1249 Xml.Append("</pubsub>");
1250
1251 await e.IqResult(Xml.ToString(), e.To);
1252 return;
1253
1254 default:
1255 await e.IqErrorBadRequest(e.To, "Element not supported.", "en");
1256 return;
1257 }
1258 }
1259 }
1260
1261 await e.IqResult(string.Empty, e.To);
1262 }
1263
1264 private static IEnumerable<Subscription> Subset(Subscription[] Subscriptions, string NodeName)
1265 {
1266 LinkedList<Subscription> Result = new LinkedList<Subscription>();
1267
1268 foreach (Subscription Subscription in Subscriptions)
1269 {
1270 if (Subscription.Node == NodeName)
1271 Result.AddLast(Subscription);
1272 }
1273
1274 return Result;
1275 }
1276
1277 private static void Serialize(IEnumerable<Subscription> Subscriptions, StringBuilder Xml)
1278 {
1279 foreach (Subscription Subscription in Subscriptions)
1280 {
1281 Xml.Append("<subscription node='");
1282 Xml.Append(XML.Encode(Subscription.Node));
1283 Xml.Append("' jid='");
1284 Xml.Append(XML.Encode(Subscription.Jid));
1285 Xml.Append("' subscription='");
1286 Xml.Append(Subscription.Status.ToString());
1287 Xml.Append("' subid='");
1288 Xml.Append(Subscription.ObjectId);
1289 Xml.Append("'/>");
1290 }
1291 }
1292
1293 private DataForm GetForm(PubSubNode Node, Subscription Subscription, IqEventArgs e)
1294 {
1295 List<Field> Fields = new List<Field>()
1296 {
1297 new HiddenField("FORM_TYPE", FormTypeSubscribeOptions),
1298 new BooleanField(null, "pubsub#deliver", "Enable notifications.", false, new string[] { Subscription.Deliver ? "1" : "0" }, null,
1299 "Whether an entity wants to receive or disable notifications.", BooleanDataType.Instance, new BasicValidation(), string.Empty, false, false, false),
1300 new BooleanField(null, "pubsub#include_body", "Include message body.", false, new string[] { Subscription.IncludeBody ? "1" : "0" }, null,
1301 "Whether an entity wants to receive an XMPP message body in addition to the payload format.", BooleanDataType.Instance, new BasicValidation(), string.Empty, false, false, false),
1302 new BooleanField(null, "pubsub#digest", "Digest.", false, new string[] { Subscription.Digest ? "1" : "0" }, null,
1303 "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually.", BooleanDataType.Instance, new BasicValidation(), string.Empty, false, false, false),
1304 new TextSingleField(null, "pubsub#digest_frequency", "Digest interval (ms):", false, new string[] { Subscription.DigestFrequencyMilliseconds.ToString() }, null,
1305 "The minimum number of milliseconds between sending any two notification digests.", IntegerDataType.Instance, new RangeValidation("0", int.MaxValue.ToString()), string.Empty, false, false, false),
1306 new TextSingleField(null, "pubsub#expire", "Expiry time:", false, new string[] { Subscription.Expires == DateTime.MaxValue ? string.Empty : XML.Encode(Subscription.Expires) }, null,
1307 "The DateTime at which a leased subscription will end or has ended.", DateTimeDataType.Instance, new BasicValidation(), string.Empty, false, false, false)
1308 };
1309
1310 if (!(Node is null) && Node.NodeType == NodeType.collection)
1311 {
1312 Fields.Add(new ListSingleField(null, "pubsub#subscription_type", "Subcription Type:", false, new string[] { Subscription.Type.ToString() },
1313 new KeyValuePair<string, string>[]
1314 {
1315 new KeyValuePair<string, string>("Receive notification of new items only", "items"),
1316 new KeyValuePair<string, string>("Receive notification of new nodes only", "nodes")
1317 }, "Type of subscription.", null, null, string.Empty, false, false, false));
1318
1319 Fields.Add(new ListSingleField(null, "pubsub#subscription_depth", "Subcription Depth:", false, new string[] { Subscription.Depth == SubscriptonDepth.one ? "1" : Subscription.Depth.ToString() },
1320 new KeyValuePair<string, string>[]
1321 {
1322 new KeyValuePair<string, string>("Receive notification from direct child nodes only", "1"),
1323 new KeyValuePair<string, string>("Receive notification from all descendent nodes", "all")
1324 }, "Type of subscription.", null, null, string.Empty, false, false, false));
1325 }
1326
1327 return new DataForm(null, FormType.Form, e.To.Address, e.From.Address, Fields.ToArray());
1328 }
1329
1330 private Task PubSubSetHandler(object Sender, IqEventArgs e)
1331 {
1332 return this.PubSubSetHandler(string.Empty, e, pubSubFeatures);
1333 }
1334
1335 private Task PepSetHandler(object Sender, IqEventArgs e)
1336 {
1337 return this.PubSubSetHandler((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
1338 }
1339
1340 private async Task PubSubSetHandler(CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures Features)
1341 {
1342 XmppAddress FromBareJid = string.IsNullOrEmpty(Service) ? e.To : e.From.ToBareJID();
1343 Dictionary<CaseInsensitiveString, Subscription> List;
1344 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> List2;
1345 Subscription Subscription = null;
1346 PubSubNode Node = null;
1347 StringBuilder Xml;
1348 string NodeName = null;
1349 CaseInsensitiveString Jid = null;
1350 bool InsertNode = false;
1351 bool NodeUpdated = false;
1352 bool InsertSubscription = false;
1353 bool SubscriptionUpdated = false;
1354 bool SubscriptionDeleted = false;
1355 bool IncludeOptions = false;
1356 bool NotificationToOwner = false;
1357
1358 foreach (XmlNode N in e.Query.ChildNodes)
1359 {
1360 if (N is XmlElement E)
1361 {
1362 switch (E.LocalName)
1363 {
1364 case "create":
1365 if (!this.Server.IsServerDomain(e.From.Domain, true))
1366 {
1367 await e.IqErrorForbidden(e.To, "Only accounts registered on the broker are allowed to create nodes.", "en");
1368 return;
1369 }
1370 // Persistence layer
1371 IAccount Account = await XmppServerModule.GetAccountAsync(e.From.Account);
1372 if (Account is null)
1373 {
1374 await e.IqError("auth", "<registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>", e.To,
1375 "Only accounts registered on the broker are allowed to create nodes.", "en");
1376 return;
1377 }
1378
1379 if (!await RuntimeSettings.GetAsync(e.From.BareJid + ".PubSub.CanCreate", true))
1380 {
1381 await e.IqErrorForbidden(e.To, "Account not permitted to create nodes.", "en");
1382 return;
1383 }
1384
1385 NodeName = XML.Attribute(E, "node");
1386 if (string.IsNullOrEmpty(NodeName))
1387 {
1388 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1389 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
1390 return;
1391 }
1392
1393 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1394 if (!(Node is null))
1395 {
1396 await e.IqErrorConflict(e.To, "Node already exists.", "en");
1397 return;
1398 }
1399
1400 if (string.IsNullOrEmpty(Service))
1401 {
1402 if (NodeName != e.From.Account && !(await XmppServerModule.GetAccountAsync(NodeName) is null))
1403 {
1404 await e.IqErrorConflict(e.To, "Node name conflict: Conflicts with existing account.", "en");
1405 return;
1406 }
1407
1408 if (!(IoTGateway.Gateway.RootFolder is null))
1409 {
1410 try
1411 {
1412 string s = Path.Combine(IoTGateway.Gateway.RootFolder, NodeName);
1413
1414 if (File.Exists(s))
1415 {
1416 await e.IqErrorConflict(e.To, "Node name conflict: Conflicts with existing web file.", "en");
1417 return;
1418 }
1419 else if (Directory.Exists(s))
1420 {
1421 await e.IqErrorConflict(e.To, "Node name conflict: Conflicts with existing web folder.", "en");
1422 return;
1423 }
1424 }
1425 catch (Exception)
1426 {
1427 // Ignore.
1428 }
1429 }
1430 }
1431
1432 Node = new PubSubNode()
1433 {
1434 Service = Service,
1435 Name = NodeName,
1436 Domain = e.To.Address,
1437 Creator = e.From.BareJid,
1438 Created = DateTime.UtcNow,
1439 Owners = new CaseInsensitiveString[] { e.From.BareJid },
1440 IsRoot = true
1441 };
1442
1443 InsertNode = true;
1444 break;
1445
1446 case "configure":
1447 if (await this.ConfigureNode(E, Node, e))
1448 NodeUpdated = true;
1449 else
1450 return;
1451 break;
1452
1453 case "subscribe":
1454 NodeName = XML.Attribute(E, "node");
1455 if (string.IsNullOrEmpty(NodeName))
1456 {
1457 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1458 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
1459 return;
1460 }
1461
1462 Jid = XML.Attribute(E, "jid");
1463 XmppAddress Address = new XmppAddress(Jid);
1464
1465 if (Address.BareJid != e.From.BareJid)
1466 {
1467 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1468 "<invalid-jid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid JID", "en");
1469 }
1470
1471 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1472 if (Node is null)
1473 {
1474 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1475 return;
1476 }
1477
1478 if (!Node.AllowSubscriptions)
1479 {
1480 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1481 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='subscribe'/>", e.To, "Node does not support subscriptions.", "en");
1482 return;
1483 }
1484
1485 if (!(XmppServerModule.PersistenceLayer is null) &&
1486 await XmppServerModule.PersistenceLayer.IsBlocked(e.From.BareJid, Node.Creator))
1487 {
1488 await e.IqErrorForbidden(e.To, "Account has been blocked.", "en");
1489 return;
1490 // TODO: When blocking an account, remove subscriptions of nodes of owner performing the block.
1491 }
1492
1493 if (!await Node.CanSubscribe(e.From.BareJid))
1494 {
1495 await e.IqErrorForbidden(e.To, "Not authorized to subscribe to node.", "en");
1496 return;
1497 }
1498
1499 switch (Node.AccessModel)
1500 {
1501 case NodeAccessModel.open:
1502 break;
1503
1504 case NodeAccessModel.whitelist:
1505 if (!await Node.IsOnWhiteList(e.From.BareJid))
1506 {
1507 await e.IqError("cancel", "<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1508 "<closed-node xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Not on whitelist.", "en");
1509 return;
1510 }
1511 break;
1512
1513 case NodeAccessModel.roster:
1514 bool Found = false;
1516 XmppAddress Owner = new XmppAddress(Node.Creator);
1517
1518 if (this.Server.IsServerDomain(Owner.Domain, true))
1519 RosterItem = await XmppServerModule.GetRosterItemAsync(Owner.Account, e.From.BareJid);
1520 else
1521 RosterItem = null;
1522
1523 if (!(Node.RosterGroupsAllowed is null) && !(RosterItem is null) && !(RosterItem.Groups is null))
1524 {
1525 foreach (string s in Node.RosterGroupsAllowed)
1526 {
1527 foreach (string s2 in RosterItem.Groups)
1528 {
1529 if (string.Compare(s, s2, true) == 0)
1530 {
1531 Found = true;
1532 break;
1533 }
1534 }
1535
1536 if (Found)
1537 break;
1538 }
1539 }
1540
1541 if (!Found)
1542 {
1543 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1544 "<not-in-roster-group xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1545 "Not in corresponding roster group.", "en");
1546 return;
1547 }
1548 break;
1549
1550 case NodeAccessModel.presence:
1551 Owner = new XmppAddress(Node.Creator);
1552
1553 if (this.Server.IsServerDomain(Owner.Domain, true))
1554 RosterItem = await XmppServerModule.GetRosterItemAsync(Owner.Account, e.From.BareJid);
1555 else
1556 RosterItem = null;
1557
1558 if (RosterItem is null || (RosterItem.Subscription != SubscriptionStatus.both &&
1559 RosterItem.Subscription != SubscriptionStatus.from))
1560 {
1561 await e.IqError("auth", "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1562 "<presence-subscription-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1563 "Presence subscription required to subscribe to node.", "en");
1564 return;
1565 }
1566 break;
1567
1568 case NodeAccessModel.authorize:
1569 Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
1570
1571 if (Subscription is null)
1572 {
1573 Subscription = new Subscription()
1574 {
1575 Service = Service,
1576 Node = NodeName,
1577 Jid = Jid,
1578 IsFullJid = Address.IsFullJID,
1579 Status = NodeSubscriptionStatus.pending
1580 };
1581
1582 InsertSubscription = true;
1583 }
1584 else
1585 {
1586 if (Subscription.Status == NodeSubscriptionStatus.none)
1587 {
1588 Subscription.Status = NodeSubscriptionStatus.pending;
1589 SubscriptionUpdated = true;
1590 }
1591 }
1592
1593 NotificationToOwner = true;
1594 break;
1595 }
1596
1597 if (Subscription is null)
1598 {
1599 Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
1600
1601 if (Subscription is null)
1602 {
1603 Subscription = new Subscription()
1604 {
1605 Service = Service,
1606 Node = NodeName,
1607 Jid = Jid,
1608 IsFullJid = Address.IsFullJID,
1609 Status = NodeSubscriptionStatus.subscribed
1610 };
1611
1612 InsertSubscription = true;
1613 }
1614 else
1615 {
1616 if (Subscription.Status != NodeSubscriptionStatus.subscribed)
1617 {
1618 Subscription.Status = NodeSubscriptionStatus.subscribed;
1619 SubscriptionUpdated = true;
1620 }
1621 }
1622 }
1623 break;
1624
1625 case "unsubscribe":
1626 NodeName = XML.Attribute(E, "node");
1627 if (string.IsNullOrEmpty(NodeName))
1628 {
1629 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1630 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
1631 return;
1632 }
1633
1634 Jid = XML.Attribute(E, "jid");
1635 Address = new XmppAddress(Jid);
1636
1637 if (Address.BareJid != e.From.BareJid)
1638 {
1639 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1640 "<invalid-jid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid JID", "en");
1641 }
1642
1643 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1644 if (Node is null)
1645 {
1646 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1647 return;
1648 }
1649
1650 string SubId = XML.Attribute(E, "subid");
1651
1652 Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
1653 if (Subscription is null)
1654 {
1655 await e.IqError("cancel", "<unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1656 "<not-subscribed xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Not subscribed.", "en");
1657 return;
1658 }
1659
1660 if (!string.IsNullOrEmpty(SubId) && SubId != Subscription.ObjectId)
1661 {
1662 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1663 "<invalid-subid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid subscription ID.", "en");
1664 return;
1665 }
1666
1667 lock (this.subscriptionsByJidAndServiceAndNode)
1668 {
1669 if (this.subscriptionsByJidAndServiceAndNode.TryGetValue(Jid, out List2) &&
1670 List2.TryGetValue(Service, out List))
1671 {
1672 List.Remove(NodeName);
1673 }
1674 }
1675
1676 lock (this.subscriptionsByServiceAndNodeAndJid)
1677 {
1678 if (this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out List2) &&
1679 List2.TryGetValue(NodeName, out List))
1680 {
1681 List.Remove(Jid);
1682 }
1683 }
1684
1685 Subscription.Status = NodeSubscriptionStatus.none;
1686 SubscriptionDeleted = true;
1687 break;
1688
1689 case "options":
1690 if (Subscription is null)
1691 {
1692 NodeName = XML.Attribute(E, "node");
1693 if (string.IsNullOrEmpty(NodeName))
1694 {
1695 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1696 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
1697 return;
1698 }
1699
1700 Jid = XML.Attribute(E, "jid");
1701 Address = new XmppAddress(Jid);
1702
1703 if (Address.BareJid != e.From.BareJid)
1704 {
1705 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1706 "<invalid-jid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid JID", "en");
1707 }
1708
1709 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
1710 if (Node is null)
1711 {
1712 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1713 return;
1714 }
1715
1716 SubId = XML.Attribute(E, "subid");
1717
1718 Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
1719 if (Subscription is null)
1720 {
1721 await e.IqError("cancel", "<unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1722 "<not-subscribed xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Not subscribed.", "en");
1723 return;
1724 }
1725
1726 if (!string.IsNullOrEmpty(SubId) && SubId != Subscription.ObjectId)
1727 {
1728 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1729 "<invalid-subid xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Invalid subscription ID.", "en");
1730 return;
1731 }
1732 }
1733
1734 if (!await this.ConfigureSubscription(E, Subscription, e))
1735 return;
1736 else
1737 SubscriptionUpdated = true;
1738
1739 IncludeOptions = true;
1740 break;
1741
1742 case "publish":
1743 NodeName = XML.Attribute(E, "node");
1744 if (string.IsNullOrEmpty(NodeName))
1745 {
1746 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1747 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
1748 return;
1749 }
1750
1751 Node = await this.GetNodeAsync(Service, NodeName,
1752 Features.AutoCreate ? NodeAccessModel.presence : (NodeAccessModel?)null,
1753 e.From, e.To.Address);
1754
1755 if (Node is null)
1756 {
1757 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
1758 return;
1759 }
1760
1761 if (!await Node.CanPublishItems(e.From.BareJid))
1762 {
1763 await e.IqErrorForbidden(e.To, "Access denied. Not authorized to publish item on node.", "en");
1764 return;
1765 }
1766
1767 if (Node.NodeType == NodeType.collection)
1768 {
1769 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1770 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='publish'/>", e.To,
1771 "Publishing items on collection nodes is not supported.", "en");
1772 return;
1773 }
1774
1775 foreach (XmlNode N2 in E.ChildNodes)
1776 {
1777 if (N2 is XmlElement E2 && E2.LocalName == "item" && E2.NamespaceURI == E.NamespaceURI)
1778 {
1779 if (Node.TransientNode)
1780 {
1781 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1782 "<item-forbidden xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Transient nodes do not accept items.", "en");
1783 return;
1784 }
1785
1786 CaseInsensitiveString ItemId = XML.Attribute(E2, "id");
1787 PubSubItem Item = null;
1788
1789 if (!CaseInsensitiveString.IsNullOrEmpty(ItemId) && Node.PersistItems)
1790 {
1791 Item = await this.LoadItem(Service, NodeName, ItemId);
1792
1793 if (!(Item is null) && Item.Node != NodeName)
1794 {
1795 await e.IqErrorBadRequest(e.To, "Item identity points to item belonging to another node.", "en");
1796 return;
1797 }
1798 }
1799
1800 string Content;
1801
1802 if (Node.PublishOnWeb && !string.IsNullOrEmpty(ItemId))
1803 {
1804 foreach (XmlNode N3 in E2.ChildNodes)
1805 {
1806 if (N3 is XmlElement E3 &&
1807 E3.LocalName == "markdownContent" &&
1808 E3.NamespaceURI == WebServices.PubSubContent.Content.LilsisNamespace)
1809 {
1810 StringBuilder Link = new StringBuilder();
1811 Networking.HTTP.HttpServer WebServer = IoTGateway.Gateway.HttpServer;
1812 int[] Ports = WebServer.OpenHttpsPorts;
1813
1814 if (Ports.Length > 0)
1815 {
1816 Link.Append("https://");
1817 Link.Append(IoTGateway.Gateway.Domain);
1818
1819 if (Array.IndexOf(Ports, Networking.HTTP.HttpServer.DefaultHttpsPort) < 0)
1820 {
1821 Link.Append(':');
1822 Link.Append(Ports[0].ToString());
1823 }
1824 }
1825 else
1826 {
1827 Ports = WebServer.OpenHttpPorts;
1828
1829 Link.Append("http://");
1830 Link.Append(IoTGateway.Gateway.Domain);
1831
1832 if (Ports.Length > 0 && Array.IndexOf(Ports, Networking.HTTP.HttpServer.DefaultHttpPort) < 0)
1833 {
1834 Link.Append(':');
1835 Link.Append(Ports[0].ToString());
1836 }
1837 }
1838
1839 Link.Append('/');
1840 Link.Append(System.Web.HttpUtility.UrlEncode(Node.Name));
1841
1842 if (!ItemId.StartsWith("/"))
1843 Link.Append('/');
1844
1845 Link.Append(ItemId);
1846
1847 E3.SetAttribute("link", Link.ToString());
1848 }
1849 }
1850 }
1851
1852 Content = E2.InnerXml.Trim();
1853
1854 byte[] ContentBin = Encoding.UTF8.GetBytes(Content);
1855 DateTime Now = DateTime.UtcNow;
1856
1857 if (Node.DeliverPayloads && string.IsNullOrEmpty(Content))
1858 {
1859 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1860 "<payload-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1861 "Missing payload.", "en");
1862 return;
1863 }
1864
1865 if (ContentBin.Length > Node.MaxPayloadSize)
1866 {
1867 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1868 "<payload-too-big xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1869 "Payload too big.", "en");
1870 return;
1871 }
1872
1873 if (!string.IsNullOrEmpty(Node.PayloadType))
1874 {
1875 if (InternetContent.Decodes(Node.PayloadType, out Grade Grade, out IContentDecoder Decoder))
1876 {
1877 try
1878 {
1879 ContentResponse Temp = await Decoder.DecodeAsync(Node.PayloadType, ContentBin, Encoding.UTF8,
1880 Array.Empty<KeyValuePair<string, string>>(), null, null);
1881
1882 if (Temp.HasError)
1883 {
1884 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1885 "<invalid-payload xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1886 "Payload does not conform to Internet Content Type " + Node.PayloadType, "en");
1887 return;
1888 }
1889 }
1890 catch (Exception)
1891 {
1892 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1893 "<invalid-payload xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1894 "Payload does not conform to Internet Content Type " + Node.PayloadType, "en");
1895 return;
1896 }
1897 }
1898 else
1899 {
1900 foreach (XmlNode N3 in E2.ChildNodes)
1901 {
1902 if (N3 is XmlElement E3 && E3.NamespaceURI != Node.PayloadType)
1903 {
1904 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
1905 "<invalid-payload xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To,
1906 "Payload does not conform to namespace " + Node.PayloadType, "en");
1907 return;
1908 }
1909 }
1910 }
1911 }
1912
1913 if (!(Item is null))
1914 {
1915 Node.BytesPublished -= await this.GetItemSize(Item);
1916
1917 Item.Publisher = e.From.BareJid;
1918 Item.Payload = Content;
1919 Item.Updated = Now;
1920
1921 await Database.Update(Item);
1922 }
1923 else
1924 {
1925 if (Node.NrItems >= Node.MaxItems)
1926 {
1927 if (Node.AutoDelete)
1928 {
1929 bool Deleted = false;
1930
1931 foreach (PubSubItem Item2 in await Database.FindDelete<PubSubItem>(0, 1, new FilterAnd(
1932 new FilterFieldEqualTo("Service", Service),
1933 new FilterFieldEqualTo("Node", NodeName)), "Service", "Node", "Created"))
1934 {
1935 Node.BytesDeleted += await this.GetItemSize(Item2);
1936
1937 Node.NrItems--;
1938 if (Node.NrItems < 0)
1939 Node.NrItems = 0;
1940
1941 Deleted = true;
1942 break;
1943 }
1944
1945 if (!Deleted)
1946 Node.NrItems = 0;
1947 }
1948
1949 if (Node.NrItems >= Node.MaxItems)
1950 {
1951 await e.IqErrorNotAcceptable(e.To, "Too many items registered on node.", "en");
1952 return;
1953 }
1954 }
1955
1956 Item = new PubSubItem()
1957 {
1958 ItemId = ItemId,
1959 Service = Service,
1960 Node = NodeName,
1961 Publisher = e.From.BareJid,
1962 Payload = Content,
1963 Created = Now,
1964 };
1965
1966 if (Node.ItemExpireSeconds != int.MaxValue)
1967 Item.Expires = Now.AddSeconds(Node.ItemExpireSeconds);
1968
1969 if (Node.PersistItems)
1970 {
1971 Node.NrItems++;
1972 await Database.Insert(Item);
1973 this.items[Item.Key] = Item;
1974 }
1975
1976 Node.LastPublished = Item;
1977 }
1978
1979 if (Node.PersistItems)
1980 {
1981 Node.BytesPublished += await this.GetItemSize(Item);
1982 await Database.Update(Node);
1983 }
1984
1985 Xml = new StringBuilder();
1986
1987 Xml.Append("<pubsub xmlns='");
1988 Xml.Append(NamespacePubSub);
1989 Xml.Append("'><publish node='");
1990 Xml.Append(XML.Encode(NodeName));
1991 Xml.Append("'><item id='");
1992 Xml.Append(XML.Encode(Item.ItemIdOrObjectId));
1993 Xml.Append("'/></publish></pubsub>");
1994
1995 await e.IqResult(Xml.ToString(), e.To);
1996
1997 if (Node.DeliverNotifications)
1998 await this.SendItemEventNotification(Node, Item, e.From, e.Language, Now);
1999
2000 return;
2001 }
2002 }
2003
2004 if (Node.PersistItems)
2005 {
2006 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2007 "<item-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Item required.", "en");
2008 return;
2009 }
2010 else
2011 {
2012 // TODO: Empty notification
2013 }
2014 break;
2015
2016 case "retract":
2017 NodeName = XML.Attribute(E, "node");
2018 if (string.IsNullOrEmpty(NodeName))
2019 {
2020 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2021 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
2022 return;
2023 }
2024
2025 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
2026 if (Node is null)
2027 {
2028 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
2029 return;
2030 }
2031
2032 if (Node.NodeType == NodeType.collection)
2033 {
2034 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2035 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='publish'/>", e.To,
2036 "Publishing items on collection nodes is not supported.", "en");
2037 return;
2038 }
2039
2040 if (!Node.PersistItems)
2041 {
2042 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2043 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='persistent-items'/>",
2044 e.To, "Persisted items not supported.", "en");
2045 return;
2046 }
2047
2048 foreach (XmlNode N2 in E.ChildNodes)
2049 {
2050 if (N2 is XmlElement E2 && E2.LocalName == "item" && E2.NamespaceURI == E.NamespaceURI)
2051 {
2052 CaseInsensitiveString ItemId = XML.Attribute(E2, "id");
2053 PubSubItem Item = null;
2054
2055 Item = await this.LoadItem(Service, NodeName, ItemId);
2056
2057 if (Item is null)
2058 {
2059 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2060 "<item-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Item required.", "en");
2061 return;
2062 }
2063
2064 if (Item.Node != NodeName)
2065 {
2066 await e.IqErrorBadRequest(e.To, "Item identity points to item belonging to another node.", "en");
2067 return;
2068 }
2069
2070 if (!await Node.CanDeleteSingleItem(e.From.BareJid, e.From.BareJid == Item.Publisher))
2071 {
2072 await e.IqErrorForbidden(e.To, "Access denied. Not authorized to delete item on node.", "en");
2073 return;
2074 }
2075
2076 Node.BytesDeleted += await this.GetItemSize(Item);
2077
2078 Node.NrItems--;
2079 if (Node.NrItems < 0)
2080 Node.NrItems = 0;
2081
2082 await Database.Delete(Item);
2083 await Database.Update(Node);
2084
2085 await e.IqResult(string.Empty, e.To);
2086
2087 if (Node.DeliverNotifications)
2088 {
2089 DateTime Now = DateTime.UtcNow;
2090 Xml = null;
2091
2092 foreach (Subscription Rec in await this.GetSubscriptionsOnNode(Service, NodeName))
2093 {
2094 if (Rec.Expires < Now)
2095 {
2096 await this.DeleteSubscription(Service, Rec);
2097 await this.NotifySubscriptionChanged(Rec, FromBareJid);
2098 }
2099 else if (Rec.Deliver &&
2100 Rec.Status == NodeSubscriptionStatus.subscribed &&
2101 Rec.Type == SubscriptionType.items)
2102 {
2103 if (Xml is null)
2104 Xml = new StringBuilder();
2105 else
2106 Xml.Clear();
2107
2108 Xml.Append("<event xmlns='");
2109 Xml.Append(NamespacePubSubEvents);
2110 Xml.Append("'><items node='");
2111 Xml.Append(XML.Encode(NodeName));
2112 Xml.Append("'><retract id='");
2113 Xml.Append(XML.Encode(Item.ItemIdOrObjectId));
2114 Xml.Append("'/></items></event><headers xmlns='");
2115 Xml.Append(NamespaceStanzaHeaders);
2116 Xml.Append("'><header name='SubID'>");
2117 Xml.Append(Rec.ObjectId);
2118 Xml.Append("</header></headers>");
2119
2120 await this.Server.SendMessage(string.Empty,
2121 Guid.NewGuid().ToString().Replace("-", string.Empty),
2122 FromBareJid, new XmppAddress(Rec.Jid),
2123 e.Language, Xml.ToString());
2124 }
2125 }
2126 }
2127
2128 return;
2129 }
2130 }
2131
2132 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2133 "<item-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Item required.", "en");
2134 return;
2135
2136 default:
2137 await e.IqErrorBadRequest(e.To, "Element not supported.", "en");
2138 return;
2139 }
2140 }
2141 }
2142
2143 if (InsertNode)
2144 {
2145 await Database.Insert(Node);
2146 Log.Informational("Publish/Subscribe node created.", Node.Name, e.From.BareJid);
2147
2148 lock (this.nodesByServiceAndName)
2149 {
2150 if (this.nodesByServiceAndName.TryGetValue(Service, out Nodes Nodes))
2151 {
2152 Nodes.NodesByName[Node.Name] = Node;
2153
2154 Nodes.NodesArray = new PubSubNode[Nodes.NodesByName.Count];
2155 Nodes.NodesByName.Values.CopyTo(Nodes.NodesArray, 0);
2156 }
2157 }
2158
2159 }
2160 else if (NodeUpdated)
2161 {
2162 await Database.Update(Node);
2163 Log.Informational("Publish/Subscribe node updated.", Node.Name, e.From.BareJid);
2164 }
2165
2166 if (!(Subscription is null))
2167 {
2168 if (InsertSubscription)
2169 {
2170 lock (this.subscriptionsByJidAndServiceAndNode)
2171 {
2172 if (this.subscriptionsByJidAndServiceAndNode.TryGetValue(Jid, out List2) &&
2173 List2.TryGetValue(Service, out List))
2174 {
2175 List[NodeName] = Subscription;
2176 }
2177 }
2178
2179 lock (this.subscriptionsByServiceAndNodeAndJid)
2180 {
2181 if (this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out List2) &&
2182 List2.TryGetValue(NodeName, out List))
2183 {
2184 List[Jid] = Subscription;
2185 }
2186 }
2187
2188 await Database.Insert(Subscription);
2189 }
2190 else if (SubscriptionUpdated)
2191 await Database.Update(Subscription);
2192 else if (SubscriptionDeleted)
2193 await Database.Delete(Subscription);
2194
2195 if (e.From.Address != Subscription.Jid)
2196 await this.NotifySubscriptionChanged(Subscription, FromBareJid);
2197
2198 Xml = new StringBuilder();
2199
2200 Xml.Append("<pubsub xmlns='");
2201 Xml.Append(NamespacePubSub);
2202 Xml.Append("'>");
2203 Subscription.Serialize(Node, Xml, IncludeOptions);
2204 Xml.Append("</pubsub>");
2205
2206 await e.IqResult(Xml.ToString(), e.To);
2207
2208 if (Subscription.Status == NodeSubscriptionStatus.subscribed &&
2209 Node.SendLastPublishedItem != SendLastPublishedItem.never)
2210 {
2211 await this.SendLastItem(Service, Node, Subscription.Jid, FromBareJid, e.Language);
2212 }
2213
2214 if (NotificationToOwner)
2215 {
2216 DataForm Form = new DataForm(null, FormType.Form, FromBareJid.Address, Node.Creator,
2218 new HiddenField("pubsub#subid", Subscription.ObjectId),
2219 new TextSingleField(null, "pubsub#node", "Node:", false,
2220 new string[] { NodeName }, null, "Name of node.",
2221 null, null, string.Empty, false, false, false),
2222 new JidSingleField(null, "pubsub#subscriber_jid", "Subscriber:", false,
2223 new string[] { Jid }, null, "JID of subscriber.",
2224 null, null, string.Empty, false, false, false),
2225 new BooleanField(null, "pubsub#allow", "Authorize subscription request.", true,
2226 new string[] { "0" }, null, "Check this box to authorize the subscription.",
2227 BooleanDataType.Instance, new BasicValidation(), string.Empty, false, false, false))
2228 {
2229 Title = "Subscription request",
2230 Instructions = new string[]
2231 {
2232 Jid + " wants to subscribe to " + NodeName + ".",
2233 "Approve this subscription request by checking the box below, and click OK.",
2234 "Deny this request by leaving the box uncheced, and click OK."
2235 }
2236 };
2237
2238 Xml = new StringBuilder();
2239 Form.SerializeForm(Xml);
2240
2241 await this.Server.SendMessage(string.Empty,
2242 Guid.NewGuid().ToString().Replace("-", string.Empty),
2243 FromBareJid, new XmppAddress(Node.Creator),
2244 "en", Xml.ToString());
2245 }
2246
2247 // TODO: Unregister subscriptions when
2248 // * Accounts get deleted
2249 // * Full JID subscriptions when account goes offline
2250 }
2251 else
2252 await e.IqResult(string.Empty, e.To);
2253 }
2254
2267 public Task<string> PublishItem(string Service, string NodeName, string AutoCreateAccess, string From,
2268 string Domain, string ItemId, string XmlContent, string Language)
2269 {
2270 XmlDocument Doc = XML.ParseXml(XmlContent, true);
2271
2272 return this.PublishItem(Service, NodeName, AutoCreateAccess, From, Domain, ItemId, Doc, Language);
2273 }
2274
2287 public Task<string> PublishItem(string Service, string NodeName, string AutoCreateAccess, string From,
2288 string Domain, string ItemId, XmlDocument XmlContent, string Language)
2289 {
2290 NodeAccessModel? AutoCreateAccess2;
2291
2292 if (string.IsNullOrEmpty(AutoCreateAccess))
2293 AutoCreateAccess2 = null;
2294 else if (Enum.TryParse(AutoCreateAccess, out NodeAccessModel Parsed))
2295 AutoCreateAccess2 = Parsed;
2296 else
2297 throw new ArgumentException("Invalid enum value", nameof(AutoCreateAccess));
2298
2299 return this.PublishItem(Service, NodeName, AutoCreateAccess2, From, Domain, ItemId, XmlContent, Language);
2300 }
2301
2314 public Task<string> PublishItem(string Service, string NodeName, NodeAccessModel? AutoCreateAccess, string From,
2315 string Domain, string ItemId, XmlDocument XmlContent, string Language)
2316 {
2317 return this.PublishItem(Service, NodeName, AutoCreateAccess, new XmppAddress(From), (CaseInsensitiveString)Domain,
2318 (CaseInsensitiveString)ItemId, XmlContent, Language);
2319 }
2320
2333 public async Task<string> PublishItem(string Service, string NodeName, NodeAccessModel? AutoCreateAccess, XmppAddress From,
2334 CaseInsensitiveString Domain, CaseInsensitiveString ItemId, XmlDocument XmlContent, string Language)
2335 {
2336 if (string.IsNullOrEmpty(NodeName))
2337 throw new ArgumentException("Node name required.", nameof(NodeName));
2338
2339 PubSubNode Node = await this.GetNodeAsync(Service, NodeName, AutoCreateAccess, From, Domain)
2340 ?? throw new ArgumentException("Node not found.", nameof(NodeName));
2341
2342 if (Node.NodeType == NodeType.collection)
2343 throw new ArgumentException("Publishing items on collection nodes is not supported.", nameof(NodeName));
2344
2345 if (Node.TransientNode)
2346 throw new ArgumentException("Transient nodes do not accept items.", nameof(NodeName));
2347
2348 PubSubItem Item = null;
2349
2351 {
2352 Item = await this.LoadItem(Service, NodeName, ItemId);
2353
2354 if (!(Item is null) && Item.Node != NodeName)
2355 throw new ArgumentException("Item identity points to item belonging to another node.", nameof(ItemId));
2356 }
2357
2358 string Content;
2359
2360 if (Node.PublishOnWeb && !string.IsNullOrEmpty(ItemId))
2361 {
2362 if (!(XmlContent.DocumentElement is null) &&
2363 XmlContent.DocumentElement.LocalName == "markdownContent" &&
2364 XmlContent.DocumentElement.NamespaceURI == WebServices.PubSubContent.Content.LilsisNamespace)
2365 {
2366 StringBuilder Link = new StringBuilder();
2367 Networking.HTTP.HttpServer WebServer = IoTGateway.Gateway.HttpServer;
2368 int[] Ports = WebServer.OpenHttpsPorts;
2369
2370 if (Ports.Length > 0)
2371 {
2372 Link.Append("https://");
2373 Link.Append(IoTGateway.Gateway.Domain);
2374
2375 if (Array.IndexOf(Ports, Networking.HTTP.HttpServer.DefaultHttpsPort) < 0)
2376 {
2377 Link.Append(':');
2378 Link.Append(Ports[0].ToString());
2379 }
2380 }
2381 else
2382 {
2383 Ports = WebServer.OpenHttpPorts;
2384
2385 Link.Append("http://");
2386 Link.Append(IoTGateway.Gateway.Domain);
2387
2388 if (Ports.Length > 0 && Array.IndexOf(Ports, Networking.HTTP.HttpServer.DefaultHttpPort) < 0)
2389 {
2390 Link.Append(':');
2391 Link.Append(Ports[0].ToString());
2392 }
2393 }
2394
2395 Link.Append('/');
2396 Link.Append(System.Web.HttpUtility.UrlEncode(Node.Name));
2397
2398 if (!ItemId.StartsWith("/"))
2399 Link.Append('/');
2400
2401 Link.Append(ItemId);
2402
2403 XmlContent.DocumentElement.SetAttribute("link", Link.ToString());
2404 }
2405 }
2406
2407 Content = XmlContent.InnerXml.Trim();
2408
2409 byte[] ContentBin = Encoding.UTF8.GetBytes(Content);
2410 DateTime Now = DateTime.UtcNow;
2411
2412 if (Node.DeliverPayloads && string.IsNullOrEmpty(Content))
2413 throw new ArgumentException("Missing payload.", nameof(XmlContent));
2414
2415 if (ContentBin.Length > Node.MaxPayloadSize)
2416 throw new ArgumentException("Payload too big.", nameof(XmlContent));
2417
2418 if (!string.IsNullOrEmpty(Node.PayloadType))
2419 {
2420 if (InternetContent.Decodes(Node.PayloadType, out _, out IContentDecoder Decoder))
2421 {
2422 try
2423 {
2424 ContentResponse Temp = await Decoder.DecodeAsync(Node.PayloadType, ContentBin, Encoding.UTF8,
2425 Array.Empty<KeyValuePair<string, string>>(), null, null);
2426
2427 if (Temp.HasError)
2428 throw new ArgumentException("Payload does not conform to Internet Content Type " + Node.PayloadType, nameof(XmlContent));
2429 }
2430 catch (Exception)
2431 {
2432 throw new ArgumentException("Payload does not conform to Internet Content Type " + Node.PayloadType, nameof(XmlContent));
2433 }
2434 }
2435 else if (XmlContent.DocumentElement is null || XmlContent.DocumentElement.NamespaceURI != Node.PayloadType)
2436 throw new ArgumentException("Payload does not conform to namespace " + Node.PayloadType, nameof(XmlContent));
2437 }
2438
2439 if (!(Item is null))
2440 {
2441 Node.BytesPublished -= await this.GetItemSize(Item);
2442
2443 Item.Publisher = From.BareJid;
2444 Item.Payload = Content;
2445 Item.Updated = Now;
2446
2447 await Database.Update(Item);
2448 }
2449 else
2450 {
2451 if (Node.NrItems >= Node.MaxItems)
2452 {
2453 if (Node.AutoDelete)
2454 {
2455 bool Deleted = false;
2456
2457 foreach (PubSubItem Item2 in await Database.FindDelete<PubSubItem>(0, 1, new FilterAnd(
2458 new FilterFieldEqualTo("Service", Service),
2459 new FilterFieldEqualTo("Node", NodeName)), "Service", "Node", "Created"))
2460 {
2461 Node.BytesDeleted += await this.GetItemSize(Item2);
2462
2463 Node.NrItems--;
2464 if (Node.NrItems < 0)
2465 Node.NrItems = 0;
2466
2467 Deleted = true;
2468 break;
2469 }
2470
2471 if (!Deleted)
2472 Node.NrItems = 0;
2473 }
2474
2475 if (Node.NrItems >= Node.MaxItems)
2476 throw new InvalidOperationException("Too many items registered on node.");
2477 }
2478
2479 Item = new PubSubItem()
2480 {
2481 ItemId = ItemId,
2482 Service = Service,
2483 Node = NodeName,
2484 Publisher = From.BareJid,
2485 Payload = Content,
2486 Created = Now,
2487 };
2488
2489 if (Node.ItemExpireSeconds != int.MaxValue)
2490 Item.Expires = Now.AddSeconds(Node.ItemExpireSeconds);
2491
2492 if (Node.PersistItems)
2493 {
2494 Node.NrItems++;
2495 await Database.Insert(Item);
2496 this.items[Item.Key] = Item;
2497 }
2498
2499 Node.LastPublished = Item;
2500 }
2501
2502 if (Node.PersistItems)
2503 {
2504 Node.BytesPublished += await this.GetItemSize(Item);
2505 await Database.Update(Node);
2506 }
2507
2508 if (Node.DeliverNotifications)
2509 await this.SendItemEventNotification(Node, Item, From, Language, Now);
2510
2511 return Item.ObjectId;
2512 }
2513
2514 private async Task SendItemEventNotification(PubSubNode Node, PubSubItem Item, XmppAddress From, string Language, DateTime Now)
2515 {
2516 StringBuilder Xml = new StringBuilder();
2517
2518 Xml.Append("<event xmlns='");
2519 Xml.Append(NamespacePubSubEvents);
2520 Xml.Append("'><items node='");
2521 Xml.Append(XML.Encode(Node.Name));
2522 Xml.Append("'><item id='");
2523 Xml.Append(XML.Encode(Item.ItemIdOrObjectId));
2524 Xml.Append("' publisher='");
2525 Xml.Append(XML.Encode(Item.Publisher));
2526
2527 if (Node.DeliverPayloads)
2528 {
2529 Xml.Append("'>");
2530
2531 if (XML.IsValidXml(Item.Payload, true, true, true, true, false, false))
2532 Xml.Append(Item.Payload);
2533
2534 Xml.Append("</item>");
2535 }
2536 else
2537 Xml.Append("'/>");
2538
2539 Xml.Append("</items></event>");
2540
2541 // TODO: include body, if transform exists
2542
2543 string EventXml;
2544
2545 if (Node.AutoSubscribe && Node.AccessModel == NodeAccessModel.presence)
2546 {
2547 if (From.IsFullJID)
2548 {
2549 Xml.Append("<addresses xmlns='");
2551 Xml.Append("'><address type='replyto' jid='");
2552 Xml.Append(From.Address);
2553 Xml.Append("'/></addresses>");
2554 }
2555
2556 EventXml = Xml.ToString();
2557
2558 IEnumerable<IRosterItem> Roster = await XmppServerModule.GetRosterAsync(From.Account);
2559 if (!(Roster is null))
2560 {
2561 foreach (IRosterItem RosterItem in Roster)
2562 {
2563 if (RosterItem.Subscription == SubscriptionStatus.both ||
2564 RosterItem.Subscription == SubscriptionStatus.from)
2565 {
2566 CaseInsensitiveString[] Jids = this.FilterNotifiations(new XmppAddress(RosterItem.BareJid), Node);
2567 if (!(Jids is null))
2568 {
2569 foreach (CaseInsensitiveString Jid2 in Jids)
2570 {
2571 await this.Server.SendMessage(Node.NotificationType.ToString(),
2572 Guid.NewGuid().ToString().Replace("-", string.Empty),
2573 From.BareJid, Jid2, Language, EventXml);
2574 }
2575 }
2576 }
2577 }
2578
2579 await this.Server.SendMessage(Node.NotificationType.ToString(),
2580 Guid.NewGuid().ToString().Replace("-", string.Empty),
2581 From.BareJid, From.BareJid, Language, EventXml);
2582 }
2583 }
2584 else
2585 {
2586 XmppAddress FromBareJid = string.IsNullOrEmpty(Node.Service) ? this.MainDomain : From.ToBareJID();
2587 EventXml = Xml.ToString();
2588
2589 foreach (Subscription Rec in await this.GetSubscriptionsOnNode(Node.Service, Node.Name))
2590 {
2591 if (Rec.Expires < Now)
2592 {
2593 await this.DeleteSubscription(Node.Service, Rec);
2594 await this.NotifySubscriptionChanged(Rec, FromBareJid);
2595 }
2596 else if (Rec.Deliver &&
2597 Rec.Status == NodeSubscriptionStatus.subscribed &&
2598 Rec.Type == SubscriptionType.items)
2599 {
2600 Xml.Clear();
2601
2602 Xml.Append(EventXml);
2603 Xml.Append("<headers xmlns='");
2604 Xml.Append(NamespaceStanzaHeaders);
2605 Xml.Append("'><header name='SubID'>");
2606 Xml.Append(Rec.ObjectId);
2607 Xml.Append("</header></headers>");
2608
2609 await this.Server.SendMessage(Node.NotificationType.ToString(),
2610 Guid.NewGuid().ToString().Replace("-", string.Empty),
2611 FromBareJid, new XmppAddress(Rec.Jid), Language, Xml.ToString());
2612 }
2613 }
2614 }
2615 }
2616
2617 private async Task SendLastItem(CaseInsensitiveString Service, PubSubNode Node, CaseInsensitiveString Jid,
2618 XmppAddress FromBareJid, string Language)
2619 {
2620 if (!Node.LastPublishedKnown)
2621 {
2622 foreach (PubSubItem Item in await Database.Find<PubSubItem>(0, 1, new FilterAnd(
2623 new FilterFieldEqualTo("Service", Service),
2624 new FilterFieldEqualTo("Node", Node.Name)), "-Service", "-Node", "-Created"))
2625 {
2626 Node.LastPublished = Item;
2627 }
2628
2629 Node.LastPublishedKnown = true;
2630 }
2631
2632 PubSubItem LastItem = Node.LastPublished;
2633 if (!(LastItem is null))
2634 {
2635 StringBuilder Xml = new StringBuilder();
2636
2637 Xml.Append("<event xmlns='");
2638 Xml.Append(NamespacePubSubEvents);
2639 Xml.Append("'><items node='");
2640 Xml.Append(XML.Encode(Node.Name));
2641 Xml.Append("'><item id='");
2642 Xml.Append(XML.Encode(LastItem.ItemIdOrObjectId));
2643 Xml.Append("' publisher='");
2644 Xml.Append(XML.Encode(LastItem.Publisher));
2645
2646 if (Node.DeliverPayloads)
2647 {
2648 Xml.Append("'>");
2649
2650 if (XML.IsValidXml(LastItem.Payload, true, true, true, true, false, false))
2651 Xml.Append(LastItem.Payload);
2652
2653 Xml.Append("</item>");
2654 }
2655 else
2656 Xml.Append("'/>");
2657
2658 Xml.Append("</items></event>");
2659 Xml.Append("<delay xmlns='");
2661 Xml.Append("' stamp='");
2662 Xml.Append(XML.Encode(LastItem.Created));
2663 Xml.Append("'/>");
2664
2665 await this.Server.SendMessage(string.Empty,
2666 Guid.NewGuid().ToString().Replace("-", string.Empty),
2667 FromBareJid, new XmppAddress(Jid),
2668 Language, Xml.ToString());
2669 }
2670 }
2671
2672 public async Task<Tuple<PubSubItem[], bool, string>> LoadItems(CaseInsensitiveString Service, CaseInsensitiveString NodeName,
2673 CaseInsensitiveString From, int MaxCount)
2674 {
2675 List<Filter> Filters = new List<Filter>()
2676 {
2677 new FilterFieldEqualTo("Service", Service),
2678 new FilterFieldEqualTo("Node", NodeName)
2679 };
2680
2681 if (!string.IsNullOrEmpty(From))
2682 {
2683 try
2684 {
2685 PubSubItem Item = await this.LoadItem(Service, NodeName, From);
2686 if (Item is null || Item.Node != NodeName)
2687 return null;
2688
2689 Filters.Add(new FilterFieldLesserThan("Created", Item.Created));
2690 }
2691 catch (Exception)
2692 {
2693 return null;
2694 }
2695 }
2696
2697 List<PubSubItem> Result = new List<PubSubItem>();
2698 bool More = false;
2699 string Last = string.Empty;
2700
2701 foreach (PubSubItem Item in await Database.Find<PubSubItem>(0, MaxCount + 1, new FilterAnd(Filters.ToArray()), "-Service", "-Node", "-Created"))
2702 {
2703 if (MaxCount-- <= 0)
2704 {
2705 More = true;
2706 break;
2707 }
2708
2709 Last = Item.ItemIdOrObjectId;
2710 Result.Add(Item);
2711 }
2712
2713 return new Tuple<PubSubItem[], bool, string>(Result.ToArray(), More, Last);
2714 }
2715
2716 public async Task<PubSubItem> LoadItem(CaseInsensitiveString Service, CaseInsensitiveString NodeName, CaseInsensitiveString ItemId)
2717 {
2718 StringBuilder Key = new StringBuilder();
2719
2720 Key.Append(Service);
2721 Key.Append('/');
2722 Key.Append(NodeName);
2723 if (!ItemId.StartsWith("/"))
2724 Key.Append('/');
2725 Key.Append(ItemId);
2726
2727 if (this.items.TryGetValue(Key.ToString(), out PubSubItem Item))
2728 return Item;
2729
2730 foreach (PubSubItem Item2 in await Database.Find<PubSubItem>(new FilterAnd(
2731 new FilterFieldEqualTo("Service", Service),
2732 new FilterFieldEqualTo("Node", NodeName),
2733 new FilterFieldEqualTo("ItemId", ItemId))))
2734 {
2735 return Item2;
2736 }
2737
2738 if (ItemId.StartsWith("/"))
2739 ItemId = ItemId.Substring(1);
2740
2741 if (Guid.TryParse(ItemId, out Guid Guid2))
2742 return await Database.TryLoadObject<PubSubItem>(Guid2);
2743
2744 return null;
2745 }
2746
2747 private async Task DeleteSubscription(CaseInsensitiveString Service, Subscription Subscription)
2748 {
2749 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> List2;
2750 Dictionary<CaseInsensitiveString, Subscription> List;
2751
2752 lock (this.subscriptionsByJidAndServiceAndNode)
2753 {
2754 if (this.subscriptionsByJidAndServiceAndNode.TryGetValue(Subscription.Jid, out List2) &&
2755 List2.TryGetValue(Service, out List))
2756 {
2757 List.Remove(Subscription.Node);
2758 }
2759 }
2760
2761 lock (this.subscriptionsByServiceAndNodeAndJid)
2762 {
2763 if (this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out List2) &&
2764 List2.TryGetValue(Subscription.Node, out List))
2765 {
2766 List.Remove(Subscription.Jid);
2767 }
2768 }
2769
2770 await Database.Delete(Subscription);
2771 }
2772
2773 private async Task<int> GetItemSize(PubSubItem Item)
2774 {
2775 if (!(this.itemSerializer is null))
2776 {
2777 BinarySerializer Serializer = new BinarySerializer("PubSubItems", Encoding.UTF8);
2778 await this.itemSerializer.Serialize(Serializer, false, false, Item, null);
2779 Serializer.FlushBits();
2780 byte[] Binary = Serializer.GetSerialization();
2781 return Binary.Length;
2782 }
2783 else
2784 {
2785 return
2786 Encoding.UTF8.GetBytes(Item.ObjectId).Length +
2787 Encoding.UTF8.GetBytes(Item.ItemId).Length +
2788 Encoding.UTF8.GetBytes(Item.Node).Length +
2789 Encoding.UTF8.GetBytes(Item.Publisher).Length +
2790 Encoding.UTF8.GetBytes(Item.Payload).Length +
2791 8 + 8 + 5;
2792 }
2793 }
2794
2795 private Task PubSubOwnerGetHandler(object Sender, IqEventArgs e)
2796 {
2797 return this.PubSubOwnerGetHandler(string.Empty, e, pubSubFeatures);
2798 }
2799
2800 private Task PepOwnerGetHandler(object Sender, IqEventArgs e)
2801 {
2802 return this.PubSubOwnerGetHandler((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
2803 }
2804
2805 private async Task PubSubOwnerGetHandler(CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures _)
2806 {
2807 PubSubNode Node;
2808
2809 foreach (XmlNode N in e.Query.ChildNodes)
2810 {
2811 if (N is XmlElement E)
2812 {
2813 switch (E.LocalName)
2814 {
2815 case "configure":
2816 string NodeName = XML.Attribute(E, "node");
2817 if (string.IsNullOrEmpty(NodeName))
2818 {
2819 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2820 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
2821 return;
2822 }
2823
2824 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
2825 if (Node is null)
2826 {
2827 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
2828 return;
2829 }
2830
2831 if (!await Node.CanConfigureNode(e.From.BareJid))
2832 {
2833 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
2834 return;
2835 }
2836
2837 DataForm Form = this.GetForm(Node, e);
2838 StringBuilder Xml = new StringBuilder();
2839
2840 Xml.Append("<pubsub xmlns='");
2841 Xml.Append(NamespacePubSubOwner);
2842 Xml.Append("'><configure node='");
2843 Xml.Append(XML.Encode(NodeName));
2844 Xml.Append("'>");
2845 Form.SerializeForm(Xml);
2846 Xml.Append("</configure></pubsub>");
2847
2848 await e.IqResult(Xml.ToString(), e.To);
2849 return;
2850
2851 case "default":
2852 Node = new PubSubNode()
2853 {
2854 Domain = e.To.Address
2855 };
2856 Form = this.GetForm(Node, e);
2857 Xml = new StringBuilder();
2858
2859 Xml.Append("<pubsub xmlns='");
2860 Xml.Append(NamespacePubSubOwner);
2861 Xml.Append("'><default>");
2862 Form.SerializeForm(Xml);
2863 Xml.Append("</default></pubsub>");
2864
2865 await e.IqResult(Xml.ToString(), e.To);
2866 return;
2867
2868 case "subscriptions":
2869 NodeName = XML.Attribute(E, "node");
2870 if (string.IsNullOrEmpty(NodeName))
2871 {
2872 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2873 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
2874 return;
2875 }
2876
2877 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
2878 if (Node is null)
2879 {
2880 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
2881 return;
2882 }
2883
2884 if (!await Node.CanConfigureNode(e.From.BareJid))
2885 {
2886 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
2887 return;
2888 }
2889
2890 Xml = new StringBuilder();
2891
2892 Xml.Append("<pubsub xmlns='");
2893 Xml.Append(NamespacePubSubOwner);
2894 Xml.Append("'><subscriptions node='");
2895 Xml.Append(XML.Encode(NodeName));
2896 Xml.Append("'>");
2897
2898 Subscription[] Subscriptions = await this.GetSubscriptionsOnNode(Service, NodeName);
2899
2900 foreach (Subscription Subscription in Subscriptions)
2901 {
2902 Xml.Append("<subscription jid='");
2903 Xml.Append(XML.Encode(Subscription.Jid));
2904 Xml.Append("' subscription='");
2905 Xml.Append(Subscription.Status.ToString());
2906 Xml.Append("'/>");
2907 }
2908
2909 Xml.Append("</subscriptions></pubsub>");
2910
2911 await e.IqResult(Xml.ToString(), e.To);
2912 return;
2913
2914 case "affiliations":
2915 NodeName = XML.Attribute(E, "node");
2916 if (string.IsNullOrEmpty(NodeName))
2917 {
2918 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
2919 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
2920 return;
2921 }
2922
2923 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
2924 if (Node is null)
2925 {
2926 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
2927 return;
2928 }
2929
2930 if (!await Node.CanConfigureNode(e.From.BareJid))
2931 {
2932 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
2933 return;
2934 }
2935
2936 Xml = new StringBuilder();
2937
2938 Xml.Append("<pubsub xmlns='");
2939 Xml.Append(NamespacePubSubOwner);
2940 Xml.Append("'><affiliations node='");
2941 Xml.Append(XML.Encode(NodeName));
2942 Xml.Append("'>");
2943
2944 foreach (KeyValuePair<CaseInsensitiveString, AffiliationStatus> Affiliation in Node.GetAffiliations())
2945 {
2946 Xml.Append("<affiliation jid='");
2947 Xml.Append(XML.Encode(Affiliation.Key));
2948 Xml.Append("' affiliation='");
2949 Xml.Append(ToString(Affiliation.Value));
2950 Xml.Append("'/>");
2951 }
2952
2953 Xml.Append("</affiliations></pubsub>");
2954
2955 await e.IqResult(Xml.ToString(), e.To);
2956 return;
2957
2958 default:
2959 await e.IqErrorBadRequest(e.To, "Element not supported.", "en");
2960 return;
2961 }
2962 }
2963 }
2964
2965 await e.IqErrorBadRequest(e.To, "Missing element.", "en");
2966 }
2967
2968 private static string ToString(AffiliationStatus Affiliation)
2969 {
2970 if (Affiliation == AffiliationStatus.publishOnly)
2971 return "publish-only";
2972 else
2973 return Affiliation.ToString();
2974 }
2975
2976 private DataForm GetForm(PubSubNode Node, IqEventArgs e)
2977 {
2978 return new DataForm(null, FormType.Form, e.To.Address, e.From.Address,
2979 new HiddenField("FORM_TYPE", FormTypeNodeConfig),
2980 new TextSingleField(null, "pubsub#title", "Title:", false, new string[] { Node.Title }, null,
2981 "A friendly name for the node.", StringDataType.Instance, null, string.Empty, false, false, false),
2982 new BooleanField(null, "pubsub#deliver_notifications", "Event notifications.", false,
2983 new string[] { Node.DeliverNotifications ? "1" : "0" }, null,
2984 "Whether to deliver event notifications.", BooleanDataType.Instance, null, string.Empty, false, false, false),
2985 new BooleanField(null, "pubsub#deliver_payloads", "Deliver payloads.", false,
2986 new string[] { Node.DeliverPayloads ? "1" : "0" }, null,
2987 "Whether to deliver payloads with event notifications.", BooleanDataType.Instance, null, string.Empty, false, false, false),
2988 new BooleanField(null, "pubsub#notify_config", "Notify configurations.", false,
2989 new string[] { Node.NotifyConfig ? "1" : "0" }, null,
2990 "Notify subscribers when the node configuration changes.", BooleanDataType.Instance, null, string.Empty, false, false, false),
2991 new BooleanField(null, "pubsub#notify_delete", "Notify delete.", false,
2992 new string[] { Node.NotifyDelete ? "1" : "0" }, null,
2993 "Notify subscribers when the node is deleted.", BooleanDataType.Instance, null, string.Empty, false, false, false),
2994 new BooleanField(null, "pubsub#notify_retract", "Notify retract.", false,
2995 new string[] { Node.NotifyRetract ? "1" : "0" }, null,
2996 "Notify subscribers when items are removed from the node.", BooleanDataType.Instance, null, string.Empty, false, false, false),
2997 new BooleanField(null, "pubsub#notify_sub", "Notify subscriptions.", false,
2998 new string[] { Node.NotifySubscriptions ? "1" : "0" }, null,
2999 "Notify owners about new subscribers and unsubscribes.", BooleanDataType.Instance, null, string.Empty, false, false, false),
3000 new BooleanField(null, "pubsub#persist_items", "Persist items.", false,
3001 new string[] { Node.PersistItems ? "1" : "0" }, null,
3002 "Persist items to storage.", BooleanDataType.Instance, null, string.Empty, false, false, false),
3003 new TextSingleField(null, "pubsub#max_items", "Items to persist:", false,
3004 new string[] { Node.MaxItems.ToString() }, null,
3005 "Max # of items to persist.", IntegerDataType.Instance, new RangeValidation("1", int.MaxValue.ToString()),
3006 string.Empty, false, false, false),
3007 new TextSingleField(null, "pubsub#item_expire", "Seconds before items expire:", false,
3008 new string[] { Node.ItemExpireSeconds.ToString() }, null,
3009 "Time after which to automatically purge items.", IntegerDataType.Instance, new RangeValidation("1", int.MaxValue.ToString()),
3010 string.Empty, false, false, false),
3011 new BooleanField(null, "pubsub#purge_offline", "Purge when publisher offline.", false,
3012 new string[] { Node.PurgeOffline ? "1" : "0" }, null,
3013 "Purge all items when the relevant publisher goes offline?", BooleanDataType.Instance, null,
3014 string.Empty, false, false, false),
3015 new TextSingleField(null, "pubsub#node_expire", "Date when node expires:", false, // TODO: Write XEP
3016 new string[] { XML.Encode(Node.Expires, true) }, null,
3017 "The node and all its items will be purged after this date.", DateDataType.Instance, new RangeValidation(XML.Encode(DateTime.Today, true), XML.Encode(DateTime.MaxValue, true)),
3018 string.Empty, false, false, false),
3019 new BooleanField(null, "pubsub#subscribe", "Allow subscriptions.", false,
3020 new string[] { Node.AllowSubscriptions ? "1" : "0" }, null,
3021 "Whether to allow subscriptions.", BooleanDataType.Instance, null,
3022 string.Empty, false, false, false),
3023 new ListSingleField(null, "pubsub#access_model", "Access model:", false,
3024 new string[] { Node.AccessModel.ToString() }, new KeyValuePair<string, string>[]
3025 {
3026 new KeyValuePair<string, string>("Everyone has access", "open"),
3027 new KeyValuePair<string, string>("Contacts with approved presence subscriptions", "presence"),
3028 new KeyValuePair<string, string>("Contacts in specified groups", "roster"),
3029 new KeyValuePair<string, string>("Only authorized entities", "authorize"),
3030 new KeyValuePair<string, string>("Only if on whitelist", "whitelist")
3031 },
3032 "Specify the subscriber model.", null, new BasicValidation(),
3033 string.Empty, false, false, false),
3034 new TextMultiField(null, "pubsub#roster_groups_allowed", "Roster groups allowed:", false,
3035 Node.RosterGroupsAllowed, null,
3036 "Roster groups allowed to subscribe.", StringDataType.Instance, null,
3037 string.Empty, false, false, false),
3038 new ListSingleField(null, "pubsub#publish_model", "Publish model:", false,
3039 new string[] { Node.PublisherModel.ToString() }, new KeyValuePair<string, string>[]
3040 {
3041 new KeyValuePair<string, string>("Only publishers may publish", "publishers"),
3042 new KeyValuePair<string, string>("Subscribers may publish", "subscribers"),
3043 new KeyValuePair<string, string>("Anyone may publish", "open")
3044 },
3045 "Specify the publisher model.", null, new BasicValidation(),
3046 string.Empty, false, false, false),
3047 new TextSingleField(null, "pubsub#max_payload_size", "Maximum payload size:", false,
3048 new string[] { Node.MaxPayloadSize.ToString() }, null,
3049 "Max Payload size in bytes.", IntegerDataType.Instance, new RangeValidation("1", int.MaxValue.ToString()),
3050 string.Empty, false, false, false),
3051 new ListSingleField(null, "pubsub#send_last_published_item", "Send last item:", false,
3052 new string[] { Node.SendLastPublishedItem.ToString() }, new KeyValuePair<string, string>[]
3053 {
3054 new KeyValuePair<string, string>("Never", "never"),
3055 new KeyValuePair<string, string>("When a new subscription is processed", "on_sub"),
3056 new KeyValuePair<string, string>("When a new subscription is processed and whenever a subscriber comes online", "on_sub_and_presence")
3057 },
3058 "When to send the last published item.", null, new BasicValidation(),
3059 string.Empty, false, false, false),
3060 new BooleanField(null, "pubsub#presence_based_delivery", "Notifications only to available users.", false,
3061 new string[] { Node.PresenceBasedDelivery ? "1" : "0" }, null,
3062 "Deliver event notifications only to available users.", BooleanDataType.Instance, null,
3063 string.Empty, false, false, false),
3064 new ListSingleField(null, "pubsub#notification_type", "Notification type:", false,
3065 new string[] { Node.NotificationType.ToString() }, new KeyValuePair<string, string>[]
3066 {
3067 new KeyValuePair<string, string>("Messages of type normal", "normal"),
3068 new KeyValuePair<string, string>("Messages of type headline", "headline")
3069 },
3070 "Specify the delivery style for event notifications.", null, new BasicValidation(),
3071 string.Empty, false, false, false),
3072 new TextSingleField(null, "pubsub#type", "Payload type:", false,
3073 new string[] { Node.PayloadType }, null,
3074 "Specify the type of payload data to be provided at this node.", StringDataType.Instance, new BasicValidation(),
3075 string.Empty, false, false, false),
3076 new TextSingleField(null, "pubsub#dataform_xslt", "Data Forms XSLT:", false,
3077 new string[] { Node.DataFormXsltUrl }, null,
3078 "The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine.", StringDataType.Instance, new BasicValidation(),
3079 string.Empty, false, false, false),
3080 new TextSingleField(null, "pubsub#body_xslt", "Message Body XSLT:", false,
3081 new string[] { Node.BodyXsltUrl }, null,
3082 "The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.", StringDataType.Instance, new BasicValidation(),
3083 string.Empty, false, false, false),
3084 new TextMultiField(null, "pubsub#collection", "Collections:", false, Node.Collections.ToStringArray(), null,
3085 "The collection(s) with which a node is affiliated.", StringDataType.Instance, new BasicValidation(),
3086 string.Empty, false, false, false),
3087 new JidMultiField(null, "pubsub#contact", "Contacts:", false, Node.Contact.ToStringArray(), null,
3088 "The JIDs of those to contact with questions.", StringDataType.Instance, new BasicValidation(),
3089 string.Empty, false, false, false),
3090 new ListSingleField(null, "pubsub#itemreply", "Send replies to:", false,
3091 new string[] { Node.ItemReply.ToString() }, new KeyValuePair<string, string>[]
3092 {
3093 new KeyValuePair<string, string>("Statically specify a replyto of the node owner(s)", "owner"),
3094 new KeyValuePair<string, string>("Dynamically specify a replyto of the item publisher", "publisher")
3095 },
3096 "Whether owners or publisher should receive replies to items.", null, new BasicValidation(),
3097 string.Empty, false, false, false),
3098 new ListSingleField(null, "pubsub#children_association_policy", "Who can associate child nodes:", false,
3099 new string[] { Node.ChildAssociationPolicy.ToString() }, new KeyValuePair<string, string>[]
3100 {
3101 new KeyValuePair<string, string>("Anyone may associate leaf nodes with the collection", "all"),
3102 new KeyValuePair<string, string>("Only collection node owners may associate leaf nodes with the collection", "owners"),
3103 new KeyValuePair<string, string>("Only those on a whitelist may associate leaf nodes with the collection", "whitelist")
3104 },
3105 "Who may associate leaf nodes with a collection.", null, new BasicValidation(),
3106 string.Empty, false, false, false),
3107 new TextMultiField(null, "pubsub#children_association_whitelist", "Child association whitelist:", false, Node.ChildAssociationWhitelist.ToStringArray(), null,
3108 "The list of JIDs that may associate leaf nodes with a collection.", StringDataType.Instance, new BasicValidation(),
3109 string.Empty, false, false, false),
3110 new TextMultiField(null, "pubsub#children", "Child nodes:", false, Node.Children.ToStringArray(), null,
3111 "The child nodes (leaf or collection) associated with a collection.", StringDataType.Instance, new BasicValidation(),
3112 string.Empty, false, false, false),
3113 new TextSingleField(null, "pubsub#children_max", "Maximum amount of child nodes:", false, Node.Children.ToStringArray(), null,
3114 "The maximum number of child nodes that can be associated with a collection.", IntegerDataType.Instance,
3115 new RangeValidation("1", int.MaxValue.ToString()), string.Empty, false, false, false),
3116 new ListSingleField(null, "pubsub#node_type", "Node type:", false,
3117 new string[] { Node.NodeType.ToString() }, new KeyValuePair<string, string>[]
3118 {
3119 new KeyValuePair<string, string>("The node is a leaf node (default)", "leaf"),
3120 new KeyValuePair<string, string>("The node is a collection node", "collection")
3121 },
3122 "Whether the node is a leaf (default) or a collection.", null, new BasicValidation(),
3123 string.Empty, false, false, false),
3124 new JidMultiField(null, "pubsub#replyroom", "Reply rooms:", false, Node.ReplyRooms.ToStringArray(), null,
3125 "The specific multi-user chat rooms to specify for replyroom.", StringDataType.Instance, new BasicValidation(),
3126 string.Empty, false, false, false),
3127 new JidMultiField(null, "pubsub#replyto", "Reply to:", false, Node.ReplyTo.ToStringArray(), null,
3128 "The specific JID(s) to specify for replyto.", StringDataType.Instance, new BasicValidation(),
3129 string.Empty, false, false, false),
3130 new TextMultiField(null, "pubsub#description", "Description:", false, Node.Description, null,
3131 "A description of the node.", StringDataType.Instance, null, string.Empty, false, false, false),
3132 new TextSingleField(null, "pubsub#language", "Default language:", false, new string[] { Node.Language }, null,
3133 "Default language for the node.", StringDataType.Instance, null, string.Empty, false, false, false));
3134 }
3135
3136 private Task PubSubOwnerSetHandler(object Sender, IqEventArgs e)
3137 {
3138 return this.PubSubOwnerSetHandler(string.Empty, e, pubSubFeatures);
3139 }
3140
3141 private Task PepOwnerSetHandler(object Sender, IqEventArgs e)
3142 {
3143 return this.PubSubOwnerSetHandler((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
3144 }
3145
3146 private async Task PubSubOwnerSetHandler(CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures _)
3147 {
3148 XmppAddress FromBareJid = string.IsNullOrEmpty(Service) ? e.To : e.From.ToBareJID();
3149 PubSubNode Node = null;
3150 bool NodeUpdated = false;
3151
3152 foreach (XmlNode N in e.Query.ChildNodes)
3153 {
3154 if (N is XmlElement E)
3155 {
3156 switch (E.LocalName)
3157 {
3158 case "configure":
3159 string NodeName = XML.Attribute(E, "node");
3160 if (string.IsNullOrEmpty(NodeName))
3161 {
3162 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
3163 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
3164 return;
3165 }
3166
3167 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
3168 if (Node is null)
3169 {
3170 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
3171 return;
3172 }
3173
3174 if (await this.ConfigureNode(E, Node, e))
3175 {
3176 NodeUpdated = true;
3177 // TODO: Notify subscribers (Examples 150 & 151)
3178 }
3179 else
3180 return;
3181 break;
3182
3183 case "delete":
3184 NodeName = XML.Attribute(E, "node");
3185 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
3186 if (Node is null)
3187 {
3188 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
3189 return;
3190 }
3191
3192 if (!await Node.CanDeleteNode(e.From.BareJid))
3193 {
3194 await e.IqErrorForbidden(e.To, "Not authorized to delete node.", "en");
3195 return;
3196 }
3197
3198 string RedirectUri = null;
3199
3200 foreach (XmlNode N2 in E.ChildNodes)
3201 {
3202 if (N2 is XmlElement E2 && E2.LocalName == "redirect" && E2.NamespaceURI == E.NamespaceURI)
3203 {
3204 RedirectUri = XML.Attribute(E2, "uri");
3205 break;
3206 }
3207 }
3208
3209 await this.DeleteNode(Service, Node, FromBareJid);
3210
3211 await e.IqResult(string.Empty, e.To);
3212 return;
3213
3214 case "purge":
3215 NodeName = XML.Attribute(E, "node");
3216 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
3217 if (Node is null)
3218 {
3219 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
3220 return;
3221 }
3222
3223 (bool CanPurge, bool OnlyItemsBySameEntity) = await Node.CanPurgeNode(e.From.BareJid);
3224 if (!CanPurge)
3225 {
3226 await e.IqErrorForbidden(e.To, "Not authorized to purge node.", "en");
3227 return;
3228 }
3229
3230 Node.LastPublished = null;
3231
3232 if (!Node.PersistItems)
3233 {
3234 await e.IqError("cancel", "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
3235 "<unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='persistent-items'/>",
3236 e.To, "Node does not persist items.", "en");
3237 return;
3238 }
3239
3240 if (OnlyItemsBySameEntity)
3241 {
3242 await Database.Delete<PubSubItem>(new FilterAnd(
3243 new FilterFieldEqualTo("Service", Service),
3244 new FilterFieldEqualTo("Publisher", e.From.BareJid),
3245 new FilterFieldEqualTo("Node", NodeName)));
3246 }
3247 else
3248 {
3249 await Database.Delete<PubSubItem>(new FilterAnd(
3250 new FilterFieldEqualTo("Service", Service), new FilterFieldEqualTo("Node", NodeName)));
3251 }
3252
3253 await e.IqResult(string.Empty, e.To);
3254
3255 if (Node.DeliverNotifications)
3256 {
3257 DateTime Now = DateTime.UtcNow;
3258 StringBuilder Xml = null;
3259
3260 foreach (Subscription Rec in await this.GetSubscriptionsOnNode(Service, NodeName))
3261 {
3262 if (Rec.Expires < Now)
3263 {
3264 await this.DeleteSubscription(Service, Rec);
3265 await this.NotifySubscriptionChanged(Rec, FromBareJid);
3266 }
3267 else if (Rec.Deliver &&
3268 Rec.Status == NodeSubscriptionStatus.subscribed &&
3269 Rec.Type == SubscriptionType.items)
3270 {
3271 if (Xml is null)
3272 Xml = new StringBuilder();
3273 else
3274 Xml.Clear();
3275
3276 Xml.Append("<event xmlns='");
3277 Xml.Append(NamespacePubSubEvents);
3278 Xml.Append("'><purge node='");
3279 Xml.Append(XML.Encode(NodeName));
3280 Xml.Append("'/></event><headers xmlns='");
3281 Xml.Append(NamespaceStanzaHeaders);
3282 Xml.Append("'><header name='SubID'>");
3283 Xml.Append(Rec.ObjectId);
3284 Xml.Append("</header></headers>");
3285
3286 await this.Server.SendMessage(string.Empty,
3287 Guid.NewGuid().ToString().Replace("-", string.Empty),
3288 FromBareJid, new XmppAddress(Rec.Jid),
3289 e.Language, Xml.ToString());
3290 }
3291 }
3292 }
3293 return;
3294
3295 case "subscriptions":
3296 NodeName = XML.Attribute(E, "node");
3297 if (string.IsNullOrEmpty(NodeName))
3298 {
3299 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
3300 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
3301 return;
3302 }
3303
3304 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
3305 if (Node is null)
3306 {
3307 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
3308 return;
3309 }
3310
3311 if (!await Node.CanConfigureNode(e.From.BareJid))
3312 {
3313 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
3314 return;
3315 }
3316
3317 foreach (XmlNode N2 in E.ChildNodes)
3318 {
3319 if (N2 is XmlElement E2 && E2.LocalName == "subscription" &&
3320 E2.NamespaceURI == E.NamespaceURI)
3321 {
3322 CaseInsensitiveString Jid = XML.Attribute(E2, "jid");
3323 NodeSubscriptionStatus Status = XML.Attribute(E2, "subscription", NodeSubscriptionStatus.none);
3324 Subscription Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
3325
3326 if (Subscription is null)
3327 {
3328 if (Status == NodeSubscriptionStatus.none)
3329 continue;
3330
3331 XmppAddress Address = new XmppAddress(Jid);
3332 Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> List2;
3333 Dictionary<CaseInsensitiveString, Subscription> List = null;
3334
3335 Subscription = new Subscription()
3336 {
3337 Jid = Jid,
3338 IsFullJid = Address.IsFullJID,
3339 Service = Service,
3340 Node = NodeName,
3341 Status = Status
3342 };
3343
3344 lock (this.subscriptionsByJidAndServiceAndNode)
3345 {
3346 if (this.subscriptionsByJidAndServiceAndNode.TryGetValue(Jid, out List2) &&
3347 List2.TryGetValue(Service, out List))
3348 {
3349 List[NodeName] = Subscription;
3350 }
3351 }
3352
3353 lock (this.subscriptionsByServiceAndNodeAndJid)
3354 {
3355 if (this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out List2) &&
3356 List2.TryGetValue(NodeName, out List))
3357 {
3358 List[Jid] = Subscription;
3359 }
3360 }
3361
3362 await Database.Insert(Subscription);
3363 }
3364 else if (Subscription.Status == Status)
3365 continue;
3366 else
3367 {
3368 Subscription.Status = Status;
3369
3370 if (Status == NodeSubscriptionStatus.none)
3371 await this.DeleteSubscription(Service, Subscription);
3372 else
3373 await Database.Update(Subscription);
3374 }
3375
3376 await this.NotifySubscriptionChanged(Subscription, FromBareJid);
3377 }
3378 }
3379
3380 await e.IqResult(string.Empty, e.To);
3381 return;
3382
3383 case "affiliations":
3384 NodeName = XML.Attribute(E, "node");
3385 if (string.IsNullOrEmpty(NodeName))
3386 {
3387 await e.IqError("modify", "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
3388 "<nodeid-required xmlns='http://jabber.org/protocol/pubsub#errors'/>", e.To, "Node name required.", "en");
3389 return;
3390 }
3391
3392 Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
3393 if (Node is null)
3394 {
3395 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
3396 return;
3397 }
3398
3399 if (!await Node.CanConfigureNode(e.From.BareJid))
3400 {
3401 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
3402 return;
3403 }
3404
3405 foreach (XmlNode N2 in E.ChildNodes)
3406 {
3407 if (N2 is XmlElement E2 && E2.LocalName == "affiliation" &&
3408 E2.NamespaceURI == E.NamespaceURI)
3409 {
3410 CaseInsensitiveString Jid = XML.Attribute(E2, "jid");
3411 string s = XML.Attribute(E2, "affiliation");
3413
3414 if (s == "publish-only")
3415 Affiliation = AffiliationStatus.publishOnly;
3416 else if (!Enum.TryParse<AffiliationStatus>(s, out Affiliation))
3417 continue;
3418
3419 Node.SetAffiliation(Jid, Affiliation);
3420 await this.NotifyAffiliationChanged(NodeName, Jid, Affiliation, FromBareJid);
3421
3422 NodeUpdated = true;
3423 }
3424 }
3425
3426 if (NodeUpdated)
3427 await Database.Update(Node);
3428
3429 await e.IqResult(string.Empty, e.To);
3430 return;
3431
3432 default:
3433 await e.IqErrorBadRequest(e.To, "Element not supported.", "en");
3434 return;
3435 }
3436 }
3437 }
3438
3439 if (NodeUpdated)
3440 {
3441 await Database.Update(Node);
3442 Log.Informational("Publish/Subscribe node updated.", Node.Name, e.From.BareJid);
3443 }
3444
3445 await e.IqResult(string.Empty, e.To);
3446 }
3447
3448 internal async Task DeleteNode(CaseInsensitiveString Service, PubSubNode Node, XmppAddress FromBareJid)
3449 {
3450 Dictionary<CaseInsensitiveString, Subscription> List = null;
3451 string NodeName = Node.Name;
3452
3453 lock (this.subscriptionsByServiceAndNodeAndJid)
3454 {
3455 if (this.subscriptionsByServiceAndNodeAndJid.TryGetValue(Service, out Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Subscription>> List2) &&
3456 List2.TryGetValue(NodeName, out List))
3457 {
3458 List2.Remove(NodeName);
3459 }
3460 else
3461 List = null;
3462 }
3463
3464 if (!(List is null))
3465 {
3466 foreach (Subscription Subscription in List.Values)
3467 {
3468 await Database.Delete(Subscription);
3469 await this.NotifySubscriptionChanged(Subscription, FromBareJid);
3470 }
3471 }
3472 else
3473 {
3474 foreach (Subscription Subscription in await Database.FindDelete<Subscription>(new FilterAnd(
3475 new FilterFieldEqualTo("Service", Service), new FilterFieldEqualTo("Node", NodeName))))
3476 {
3477 await this.NotifySubscriptionChanged(Subscription, FromBareJid);
3478 }
3479 }
3480
3481 await Database.Delete(Node);
3482
3483 lock (this.nodesByServiceAndName)
3484 {
3485 if (this.nodesByServiceAndName.TryGetValue(Service, out Nodes Nodes))
3486 {
3487 Nodes.NodesByName.Remove(NodeName);
3488
3489 Nodes.NodesArray = new PubSubNode[Nodes.NodesByName.Count];
3490 Nodes.NodesByName.Values.CopyTo(Nodes.NodesArray, 0);
3491 }
3492 }
3493
3494 await Database.Delete<PubSubItem>(new FilterAnd(new FilterFieldEqualTo("Service", Service),
3495 new FilterFieldEqualTo("Node", NodeName)));
3496 }
3497
3498 private async Task NotifySubscriptionChanged(Subscription Subscription, XmppAddress FromBareJid)
3499 {
3500 StringBuilder Xml = new StringBuilder();
3501
3502 Xml.Append("<event xmlns='");
3503 Xml.Append(NamespacePubSubEvents);
3504 Xml.Append("'><subscription node='");
3505 Xml.Append(XML.Encode(Subscription.Node));
3506 Xml.Append("' jid='");
3507 Xml.Append(XML.Encode(Subscription.Jid));
3508 Xml.Append("' subscription='");
3509 Xml.Append(Subscription.Status.ToString());
3510 Xml.Append("'/></event>");
3511
3512 await this.Server.SendMessage(string.Empty,
3513 Guid.NewGuid().ToString().Replace("-", string.Empty),
3514 FromBareJid, new XmppAddress(Subscription.Jid),
3515 string.Empty, Xml.ToString());
3516 }
3517
3518 private async Task NotifyAffiliationChanged(CaseInsensitiveString Node, CaseInsensitiveString Jid, AffiliationStatus Affiliation, XmppAddress FromBareJid)
3519 {
3520 StringBuilder Xml = new StringBuilder();
3521
3522 Xml.Append("<event xmlns='");
3523 Xml.Append(NamespacePubSubEvents);
3524 Xml.Append("'><affiliations node='");
3525 Xml.Append(XML.Encode(Node));
3526 Xml.Append("'><affiliation jid='");
3527 Xml.Append(XML.Encode(Jid));
3528 Xml.Append("' affiliation='");
3529 Xml.Append(ToString(Affiliation));
3530 Xml.Append("'/></affiliations></event>");
3531
3532 await this.Server.SendMessage(string.Empty,
3533 Guid.NewGuid().ToString().Replace("-", string.Empty),
3534 FromBareJid, new XmppAddress(Jid),
3535 string.Empty, Xml.ToString());
3536 }
3537
3538 private async Task<bool> ConfigureNode(XmlElement E, PubSubNode Node, IqEventArgs e)
3539 {
3540 foreach (XmlNode N2 in E.ChildNodes)
3541 {
3542 if (N2 is XmlElement E2 && E2.LocalName == "x" && E2.NamespaceURI == Networking.XMPP.XmppClient.NamespaceData)
3543 {
3544 DataForm Form = new DataForm(null, E2, null, null, e.From.Address, e.To.Address);
3545
3546 if (Form.Type == FormType.Submit)
3547 {
3548 switch (Form["FORM_TYPE"]?.ValueString)
3549 {
3550 case FormTypeNodeConfig:
3551 if (Node is null)
3552 {
3553 await e.IqErrorBadRequest(e.To, "No node defined.", "en");
3554 return false;
3555 }
3556
3557 if (!await Node.CanConfigureNode(e.From.BareJid))
3558 {
3559 await e.IqErrorForbidden(e.To, "Not authorized to configure node.", "en");
3560 return false;
3561 }
3562
3563 foreach (Field F in Form.Fields)
3564 {
3565 switch (F.Var)
3566 {
3567 case "FORM_TYPE":
3568 break;
3569
3570 case "pubsub#access_model":
3571 if (!string.IsNullOrEmpty(F.ValueString))
3572 {
3573 if (Enum.TryParse<NodeAccessModel>(F.ValueString, out NodeAccessModel AccessModel))
3574 Node.AccessModel = AccessModel;
3575 else
3576 {
3577 await e.IqError("modify", "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
3578 "<unsupported-access-model xmlns='http://jabber.org/protocol/pubsub#errors'/>",
3579 e.To, "Access model not supported.", "en");
3580 return false;
3581 }
3582 }
3583 break;
3584
3585 case "pubsub#body_xslt":
3586 Node.BodyXsltUrl = F.ValueString;
3587 break;
3588
3589 case "pubsub#collection":
3590 Node.Collections = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3591 Node.IsRoot = (Node.Collections is null || Node.Collections.Length == 0);
3592 break;
3593
3594 case "pubsub#contact":
3595 Node.Contact = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3596 break;
3597
3598 case "pubsub#dataform_xslt":
3599 Node.DataFormXsltUrl = F.ValueString;
3600 break;
3601
3602 case "pubsub#deliver_notifications":
3603 if (!string.IsNullOrEmpty(F.ValueString))
3604 {
3605 if (CommonTypes.TryParse(F.ValueString, out bool b))
3606 Node.DeliverNotifications = b;
3607 else
3608 {
3609 await e.IqErrorBadRequest(e.To, "Unsupported delivery notification.", "en");
3610 return false;
3611 }
3612 }
3613 break;
3614
3615 case "pubsub#deliver_payloads":
3616 if (!string.IsNullOrEmpty(F.ValueString))
3617 {
3618 if (CommonTypes.TryParse(F.ValueString, out bool b))
3619 Node.DeliverPayloads = b;
3620 else
3621 {
3622 await e.IqErrorBadRequest(e.To, "Unsupported payload delivery.", "en");
3623 return false;
3624 }
3625 }
3626 break;
3627
3628 case "pubsub#itemreply":
3629 if (!string.IsNullOrEmpty(F.ValueString))
3630 {
3631 if (Enum.TryParse(F.ValueString, out NodeItemReply ItemReply))
3632 Node.ItemReply = ItemReply;
3633 else
3634 {
3635 await e.IqErrorBadRequest(e.To, "Unsupported reply mode.", "en");
3636 return false;
3637 }
3638 }
3639 break;
3640
3641 case "pubsub#children_association_policy":
3642 if (!string.IsNullOrEmpty(F.ValueString))
3643 {
3644 if (Enum.TryParse(F.ValueString, out NodeChildAssociationPolicy ChildAssociationPolicy))
3645 Node.ChildAssociationPolicy = ChildAssociationPolicy;
3646 else
3647 {
3648 await e.IqErrorBadRequest(e.To, "Unsupported child association policy.", "en");
3649 return false;
3650 }
3651 }
3652 break;
3653
3654 case "pubsub#children_association_whitelist":
3655 Node.ChildAssociationWhitelist = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3656 break;
3657
3658 case "pubsub#children":
3659 Node.Children = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3660 break;
3661
3662 case "pubsub#children_max":
3663 if (!string.IsNullOrEmpty(F.ValueString))
3664 {
3665 if (int.TryParse(F.ValueString, out int i) && i > 0)
3666 Node.MaxChildren = i;
3667 else
3668 {
3669 await e.IqErrorBadRequest(e.To, "Invalid maximum number of children.", "en");
3670 return false;
3671 }
3672 }
3673 break;
3674
3675 case "pubsub#max_items":
3676 if (!string.IsNullOrEmpty(F.ValueString))
3677 {
3678 if (int.TryParse(F.ValueString, out int i) && i > 0)
3679 Node.MaxItems = i;
3680 else
3681 {
3682 await e.IqErrorBadRequest(e.To, "Invalid maximum number of items.", "en");
3683 return false;
3684 }
3685 }
3686 break;
3687
3688 case "pubsub#item_expire":
3689 if (!string.IsNullOrEmpty(F.ValueString))
3690 {
3691 if (int.TryParse(F.ValueString, out int i) && i > 0)
3692 Node.ItemExpireSeconds = i;
3693 else
3694 {
3695 await e.IqErrorBadRequest(e.To, "Invalid item expiry time.", "en");
3696 return false;
3697 }
3698 }
3699 break;
3700
3701 case "pubsub#node_expire":
3702 if (!string.IsNullOrEmpty(F.ValueString))
3703 {
3704 if (XML.TryParse(F.ValueString, out DateTime TP) && TP >= DateTime.Today)
3705 Node.Expires = TP;
3706 else
3707 {
3708 await e.IqErrorBadRequest(e.To, "Invalid note expiry date.", "en");
3709 return false;
3710 }
3711 }
3712 break;
3713
3714 case "pubsub#max_payload_size":
3715 if (!string.IsNullOrEmpty(F.ValueString))
3716 {
3717 if (int.TryParse(F.ValueString, out int i) && i > 0)
3718 Node.MaxPayloadSize = i;
3719 else
3720 {
3721 await e.IqErrorBadRequest(e.To, "Invalid maximum payload size.", "en");
3722 return false;
3723 }
3724 }
3725 break;
3726
3727 case "pubsub#node_type":
3728 if (!string.IsNullOrEmpty(F.ValueString))
3729 {
3730 if (Enum.TryParse(F.ValueString, out NodeType NodeType))
3731 Node.NodeType = NodeType;
3732 else
3733 {
3734 await e.IqErrorBadRequest(e.To, "Unsupported node type.", "en");
3735 return false;
3736 }
3737 }
3738 break;
3739
3740 case "pubsub#notify_config":
3741 if (!string.IsNullOrEmpty(F.ValueString))
3742 {
3743 if (CommonTypes.TryParse(F.ValueString, out bool b))
3744 Node.NotifyConfig = b;
3745 else
3746 {
3747 await e.IqErrorBadRequest(e.To, "Unsupported configuration notification.", "en");
3748 return false;
3749 }
3750 }
3751 break;
3752
3753 case "pubsub#notify_delete":
3754 if (!string.IsNullOrEmpty(F.ValueString))
3755 {
3756 if (CommonTypes.TryParse(F.ValueString, out bool b))
3757 Node.NotifyDelete = b;
3758 else
3759 {
3760 await e.IqErrorBadRequest(e.To, "Unsupported delete notification.", "en");
3761 return false;
3762 }
3763 }
3764 break;
3765
3766 case "pubsub#notify_retract":
3767 if (!string.IsNullOrEmpty(F.ValueString))
3768 {
3769 if (CommonTypes.TryParse(F.ValueString, out bool b))
3770 Node.NotifyRetract = b;
3771 else
3772 {
3773 await e.IqErrorBadRequest(e.To, "Unsupported retraction notification.", "en");
3774 return false;
3775 }
3776 }
3777 break;
3778
3779 case "pubsub#notify_sub":
3780 if (!string.IsNullOrEmpty(F.ValueString))
3781 {
3782 if (CommonTypes.TryParse(F.ValueString, out bool b))
3783 Node.NotifySubscriptions = b;
3784 else
3785 {
3786 await e.IqErrorBadRequest(e.To, "Unsupported subscription notification.", "en");
3787 return false;
3788 }
3789 }
3790 break;
3791
3792 case "pubsub#persist_items":
3793 if (!string.IsNullOrEmpty(F.ValueString))
3794 {
3795 if (CommonTypes.TryParse(F.ValueString, out bool b))
3796 Node.PersistItems = b;
3797 else
3798 {
3799 await e.IqErrorBadRequest(e.To, "Unsupported items persistence.", "en");
3800 return false;
3801 }
3802 }
3803 break;
3804
3805 case "pubsub#presence_based_delivery":
3806 if (!string.IsNullOrEmpty(F.ValueString))
3807 {
3808 if (CommonTypes.TryParse(F.ValueString, out bool b))
3809 Node.PresenceBasedDelivery = b;
3810 else
3811 {
3812 await e.IqErrorBadRequest(e.To, "Unsupported presence based delivery.", "en");
3813 return false;
3814 }
3815 }
3816 break;
3817
3818 case "pubsub#publish_model":
3819 if (!string.IsNullOrEmpty(F.ValueString))
3820 {
3821 if (Enum.TryParse<PublisherModel>(F.ValueString, out PublisherModel PublisherModel))
3822 Node.PublisherModel = PublisherModel;
3823 else
3824 {
3825 await e.IqErrorBadRequest(e.To, "Unsupported publisher model.", "en");
3826 return false;
3827 }
3828 }
3829 break;
3830
3831 case "pubsub#replyroom":
3832 Node.ReplyRooms = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3833 break;
3834
3835 case "pubsub#replyto":
3836 Node.ReplyTo = CheckEmpty(F.ValueStrings.ToCaseInsensitiveStringArray());
3837 break;
3838
3839 case "pubsub#roster_groups_allowed":
3840 Node.RosterGroupsAllowed = CheckEmpty(F.ValueStrings);
3841 break;
3842
3843 case "pubsub#send_last_published_item":
3844 if (!string.IsNullOrEmpty(F.ValueString))
3845 {
3846 if (Enum.TryParse<SendLastPublishedItem>(F.ValueString, out SendLastPublishedItem SendLastPublishedItem))
3847 Node.SendLastPublishedItem = SendLastPublishedItem;
3848 else
3849 {
3850 await e.IqErrorBadRequest(e.To, "Unsupported send last published item.", "en");
3851 return false;
3852 }
3853 }
3854 break;
3855
3856 case "pubsub#subscribe":
3857 if (!string.IsNullOrEmpty(F.ValueString))
3858 {
3859 if (CommonTypes.TryParse(F.ValueString, out bool b))
3860 Node.AllowSubscriptions = b;
3861 else
3862 {
3863 await e.IqErrorBadRequest(e.To, "Unsupported subscription allowance.", "en");
3864 return false;
3865 }
3866 }
3867 break;
3868
3869 case "pubsub#title":
3870 Node.Title = F.ValueString;
3871 break;
3872
3873 case "pubsub#description":
3874 Node.Description = CheckEmpty(F.ValueStrings);
3875 break;
3876
3877 case "pubsub#language":
3878 Node.Language = F.ValueString;
3879 break;
3880
3881 case "pubsub#type":
3882 Node.PayloadType = F.ValueString;
3883 break;
3884
3885 case "pubsub#notification_type":
3886 if (!string.IsNullOrEmpty(F.ValueString))
3887 {
3888 if (Enum.TryParse<NotificationType>(F.ValueString, out NotificationType NotificationType))
3889 Node.NotificationType = NotificationType;
3890 else
3891 {
3892 await e.IqErrorBadRequest(e.To, "Unsupported notification type.", "en");
3893 return false;
3894 }
3895 }
3896 break;
3897
3898 case "pubsub#purge_offline":
3899 if (!string.IsNullOrEmpty(F.ValueString))
3900 {
3901 if (CommonTypes.TryParse(F.ValueString, out bool b))
3902 Node.PurgeOffline = b;
3903 else
3904 {
3905 await e.IqErrorBadRequest(e.To, "Unsupported offline purge.", "en");
3906 return false;
3907 }
3908 }
3909 break;
3910
3911 default:
3912 await e.IqErrorBadRequest(e.To, "Unsupported form field.", "en");
3913 return false;
3914 }
3915 }
3916
3917 return true;
3918
3919 default:
3920 await e.IqErrorBadRequest(e.To, "Unrecognized FORM_TYPE.", "en");
3921 return false;
3922 }
3923 }
3924 else if (Form.Type == FormType.Cancel)
3925 {
3926 await e.IqResult(string.Empty, e.To);
3927 return false;
3928 }
3929 else
3930 {
3931 await e.IqErrorBadRequest(e.To, "Unexpected form type.", "en");
3932 return false;
3933 }
3934 }
3935 else
3936 {
3937 await e.IqErrorBadRequest(e.To, "Unrecognized element.", "en");
3938 return false;
3939 }
3940 }
3941
3942 await e.IqErrorBadRequest(e.To, "Missing form.", "en");
3943 return false;
3944 }
3945
3946 private static CaseInsensitiveString[] CheckEmpty(CaseInsensitiveString[] Array)
3947 {
3948 if (!(Array is null) && Array.Length == 1 && CaseInsensitiveString.IsNullOrEmpty(Array[0]))
3949 return System.Array.Empty<CaseInsensitiveString>();
3950 else
3951 return Array;
3952 }
3953
3954 private static string[] CheckEmpty(string[] Array)
3955 {
3956 if (!(Array is null) && Array.Length == 1 && string.IsNullOrEmpty(Array[0]))
3957 return System.Array.Empty<string>();
3958 else
3959 return Array;
3960 }
3961
3962 private async Task<bool> ConfigureSubscription(XmlElement E, Subscription Subscription, IqEventArgs e)
3963 {
3964 foreach (XmlNode N2 in E.ChildNodes)
3965 {
3966 if (N2 is XmlElement E2 && E2.LocalName == "x" && E2.NamespaceURI == Networking.XMPP.XmppClient.NamespaceData)
3967 {
3968 DataForm Form = new DataForm(null, E2, null, null, e.From.Address, e.To.Address);
3969
3970 if (Form.Type == FormType.Submit)
3971 {
3972 switch (Form["FORM_TYPE"]?.ValueString)
3973 {
3975 foreach (Field F in Form.Fields)
3976 {
3977 switch (F.Var)
3978 {
3979 case "FORM_TYPE":
3980 break;
3981
3982 case "pubsub#deliver":
3983 if (CommonTypes.TryParse(F.ValueString, out bool b))
3984 Subscription.Deliver = b;
3985 else
3986 {
3987 await e.IqErrorBadRequest(e.To, "Unsupported delivery value.", "en");
3988 return false;
3989 }
3990 break;
3991
3992 case "pubsub#digest":
3993 if (CommonTypes.TryParse(F.ValueString, out b))
3994 Subscription.Digest = b;
3995 else
3996 {
3997 await e.IqErrorBadRequest(e.To, "Unsupported digest value.", "en");
3998 return false;
3999 }
4000 break;
4001
4002 case "pubsub#include_body":
4003 if (CommonTypes.TryParse(F.ValueString, out b))
4004 Subscription.IncludeBody = b;
4005 else
4006 {
4007 await e.IqErrorBadRequest(e.To, "Unsupported inclusion value.", "en");
4008 return false;
4009 }
4010 break;
4011
4012 case "pubsub#digest_frequency":
4013 if (int.TryParse(F.ValueString, out int i))
4014 Subscription.DigestFrequencyMilliseconds = i;
4015 else
4016 {
4017 await e.IqErrorBadRequest(e.To, "Invalid digest frequency.", "en");
4018 return false;
4019 }
4020 break;
4021
4022 case "pubsub#expire":
4023 if (XML.TryParse(F.ValueString, out DateTime TP))
4024 Subscription.Expires = TP;
4025 else if (DateTime.TryParse(F.ValueString, out TP))
4026 Subscription.Expires = TP;
4027 else
4028 {
4029 await e.IqErrorBadRequest(e.To, "Invalid expiry date and time.", "en");
4030 return false;
4031 }
4032 break;
4033
4034 case "pubsub#subscription_type":
4035 if (Enum.TryParse(F.ValueString, out SubscriptionType SubscriptionType))
4036 Subscription.Type = SubscriptionType;
4037 else
4038 {
4039 await e.IqErrorNotAcceptable(e.To, "Unsupported subscription type.", "en");
4040 return false;
4041 }
4042 break;
4043
4044 case "pubsub#subscription_depth":
4045 if (F.ValueString == "1")
4046 Subscription.Depth = SubscriptonDepth.one;
4047 else if (Enum.TryParse(F.ValueString, out SubscriptonDepth SubscriptonDepth))
4048 Subscription.Depth = SubscriptonDepth;
4049 else
4050 {
4051 await e.IqErrorNotAcceptable(e.To, "Unsupported subscription depth.", "en");
4052 return false;
4053 }
4054 break;
4055
4056 default:
4057 await e.IqErrorNotAcceptable(e.To, "Unsupported form field.", "en");
4058 return false;
4059 }
4060 }
4061
4062 return true;
4063
4064 default:
4065 await e.IqErrorBadRequest(e.To, "Unrecognized FORM_TYPE.", "en");
4066 return false;
4067 }
4068 }
4069 else if (Form.Type == FormType.Cancel)
4070 {
4071 await e.IqResult(string.Empty, e.To);
4072 return false;
4073 }
4074 else
4075 {
4076 await e.IqErrorBadRequest(e.To, "Unexpected form type.", "en");
4077 return false;
4078 }
4079 }
4080 else
4081 {
4082 await e.IqErrorBadRequest(e.To, "Unrecognized element.", "en");
4083 return false;
4084 }
4085 }
4086
4087 await e.IqErrorBadRequest(e.To, "Missing form.", "en");
4088 return false;
4089 }
4090
4096 internal static bool IsOwnerOfBroker(CaseInsensitiveString BareJid)
4097 {
4098 return !(provisioningClient is null) && provisioningClient.OwnerJid == BareJid;
4099 }
4100
4101 private Task MessageFormHandler(object Sender, MessageEventArgs e)
4102 {
4103 return this.MessageFormHandler(string.Empty, e, pubSubFeatures);
4104 }
4105
4106 private Task PepMessageFormHandler(object Sender, MessageEventArgs e)
4107 {
4108 return this.MessageFormHandler((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
4109 }
4110
4111 private async Task MessageFormHandler(CaseInsensitiveString Service, MessageEventArgs e, DefaultFeatures _)
4112 {
4113 XmppAddress FromBareJid = CaseInsensitiveString.IsNullOrEmpty(Service) ? e.To : e.From.ToBareJID();
4114 DataForm Form = new DataForm(null, e.Content, null, null, e.From.Address, e.To.Address);
4115 Field FormTypeField = Form["FORM_TYPE"];
4116 if (FormTypeField is null)
4117 return;
4118
4119 switch (FormTypeField.ValueString)
4120 {
4122 if (Form.Type != FormType.Submit)
4123 break;
4124
4125 string SubscriptionId = Form["pubsub#subid"]?.ValueString ?? string.Empty;
4126 CaseInsensitiveString NodeName = Form["pubsub#node"]?.ValueString ?? string.Empty;
4127 CaseInsensitiveString Jid = Form["pubsub#subscriber_jid"]?.ValueString ?? string.Empty;
4128
4129 if (!CommonTypes.TryParse(Form["pubsub#allow"]?.ValueString ?? string.Empty, out bool Allow))
4130 break;
4131
4132 Subscription Subscription = await this.GetSubscriptionAsync(Service, NodeName, Jid);
4133 if (Subscription is null ||
4134 Subscription.ObjectId != SubscriptionId ||
4135 Subscription.Node != NodeName ||
4136 Subscription.Jid != Jid)
4137 {
4138 break;
4139 }
4140
4141 PubSubNode Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
4142 if (Node is null)
4143 break;
4144
4145 if (Node.Creator != e.From.BareJid)
4146 break;
4147
4148 if (Allow)
4149 Subscription.Status = NodeSubscriptionStatus.subscribed;
4150 else
4151 Subscription.Status = NodeSubscriptionStatus.none;
4152
4153 await Database.Update(Subscription);
4154 await this.NotifySubscriptionChanged(Subscription, FromBareJid);
4155
4156 if (Subscription.Status == NodeSubscriptionStatus.subscribed &&
4157 Node.SendLastPublishedItem != SendLastPublishedItem.never)
4158 {
4159 await this.SendLastItem(Service, Node, Subscription.Jid, FromBareJid, e.Language);
4160 }
4161 break;
4162 }
4163 }
4164
4168 protected override Task DiscoveryQueryGet(object Sender, IqEventArgs e)
4169 {
4170 return this.DiscoveryQueryGet(Sender, string.Empty, e, pubSubFeatures);
4171 }
4172
4173 private Task PepDiscoveryQueryGet(object Sender, IqEventArgs e)
4174 {
4175 return this.DiscoveryQueryGet(Sender, (e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
4176 }
4177
4178 private Task PepDiscoverIdentities(object Sender, StringBuilder sb)
4179 {
4180 sb.Append("<identity category='pubsub' type='pep'/>");
4181 return Task.CompletedTask;
4182 }
4183
4184 private async Task DiscoveryQueryGet(object Sender, CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures _)
4185 {
4186 XmlElement E = e.Query;
4187 string NodeName = XML.Attribute(E, "node");
4188 if (string.IsNullOrEmpty(NodeName))
4189 await base.DiscoveryQueryGet(Sender, e);
4190 else
4191 {
4192 PubSubNode Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
4193 if (Node is null)
4194 await e.IqErrorItemNotFound(e.To, "Node not found", "en");
4195 else
4196 {
4197 StringBuilder Xml = new StringBuilder();
4198
4199 Xml.Append("<query xmlns='");
4200 Xml.Append(XmppServer.DiscoveryNamespace);
4201 Xml.Append("' node='");
4202 Xml.Append(XML.Encode(NodeName));
4203 Xml.Append("'><identity category='pubsub' type='");
4204 Xml.Append(Node.NodeType.ToString());
4205
4206 if (!string.IsNullOrEmpty(Node.Title))
4207 {
4208 Xml.Append("' name='");
4209 Xml.Append(XML.Encode(Node.Title));
4210 }
4211 Xml.Append("'/>");
4212
4213 int NrSubscribers = 0;
4214
4215 foreach (Subscription Subscription in await this.GetSubscriptionsOnNode(Service, NodeName))
4216 {
4217 if (Subscription.Status == NodeSubscriptionStatus.subscribed)
4218 NrSubscribers++;
4219 }
4220
4221 DataForm Form = new DataForm(null, FormType.Result, e.To.Address, e.From.Address,
4222 new HiddenField("FORM_TYPE", FormTypeNodeMetaData),
4223 new TextSingleField("pubsub#type", "Payload type", Node.PayloadType),
4224 new JidSingleField("pubsub#creator", "Node creator", Node.Creator),
4225 new TextSingleField("pubsub#creation_date", "Creation date", XML.Encode(Node.Created)),
4226 new TextSingleField("pubsub#title", "A short name for the node", Node.Title),
4227 new TextMultiField("pubsub#description", "A description of the node", Node.Description),
4228 new ListSingleField("pubsub#language", "Default language", Node.Language),
4229 new JidMultiField("pubsub#contact", "People to contact with questions", Node.Contact.ToStringArray()),
4230 new TextSingleField("pubsub#max_items", "Max # of items to persist", Node.MaxItems.ToString()),
4231 new JidMultiField("pubsub#owner", "Node owners", Node.Owners.ToStringArray()),
4232 new JidMultiField("pubsub#publisher", "Publishers to this node", Node.Publishers.ToStringArray()),
4233 new TextSingleField("pubsub#num_subscribers", "Number of subscribers to this node", NrSubscribers.ToString()));
4234
4235 Form.SerializeResult(Xml);
4236 Xml.Append("</query>");
4237
4238 await e.IqResult(Xml.ToString(), e.To);
4239 }
4240 }
4241 }
4242
4246 protected override Task AppendServiceDiscoveryIdentities(StringBuilder Xml, IqEventArgs e, string Node)
4247 {
4248 Xml.Append("<identity category='pubsub' type='service' name='Publish/Subscribe service' />");
4249 return Task.CompletedTask;
4250 }
4251
4252 protected override Task DiscoveryQueryItemsGet(object Sender, IqEventArgs e)
4253 {
4254 return this.DiscoveryQueryItemsGet(string.Empty, e, pubSubFeatures);
4255 }
4256
4257 private Task PepDiscoveryQueryItemsGet(object Sender, IqEventArgs e)
4258 {
4259 return this.DiscoveryQueryItemsGet((e.To.IsEmpty ? e.From : e.To).BareJid, e, pepFeatures);
4260 }
4261
4262 private async Task DiscoveryQueryItemsGet(CaseInsensitiveString Service, IqEventArgs e, DefaultFeatures _)
4263 {
4264 XmlElement E = e.Query;
4265 StringBuilder Xml = new StringBuilder();
4266
4267 Xml.Append("<query xmlns='");
4269
4270 if (E.HasAttribute("node"))
4271 {
4272 string NodeName = XML.Attribute(E, "node");
4273 PubSubNode Node = await this.GetNodeAsync(Service, NodeName, null, e.From, e.To.Address);
4274
4275 if (Node is null)
4276 {
4277 await e.IqErrorItemNotFound(e.To, "Node not found.", "en");
4278 return;
4279 }
4280
4281 Xml.Append("' node='");
4282 Xml.Append(XML.Encode(NodeName));
4283 Xml.Append("'>");
4284
4285 switch (Node.NodeType)
4286 {
4287 case NodeType.collection:
4288 if (!(Node.Children is null))
4289 {
4290 foreach (string Child in Node.Children)
4291 {
4292 PubSubNode Node2 = await this.GetNodeAsync(Service, Child, null, e.From, e.To.Address);
4293 if (Node2 is null)
4294 continue;
4295
4296 Xml.Append("<item jid='");
4297 Xml.Append(XML.Encode(e.To.Address));
4298 Xml.Append("' node='");
4299 Xml.Append(XML.Encode(Node2.Name));
4300
4301 if (!string.IsNullOrEmpty(Node2.Title))
4302 {
4303 Xml.Append("' name='");
4304 Xml.Append(XML.Encode(Node2.Title));
4305 }
4306
4307 Xml.Append("'/>");
4308 }
4309 }
4310 break;
4311
4312 case NodeType.leaf:
4313 IEnumerable<PubSubItem> Items = await Database.Find<PubSubItem>(new FilterAnd(
4314 new FilterFieldEqualTo("Service", Service), new FilterFieldEqualTo("Node", NodeName)), "-Service", "-Node", "-Created");
4315 foreach (PubSubItem Item in Items)
4316 {
4317 Xml.Append("<item jid='");
4318 Xml.Append(XML.Encode(e.To.Address));
4319 Xml.Append("' name='");
4320 Xml.Append(XML.Encode(Item.ItemIdOrObjectId));
4321 Xml.Append("'/>");
4322 }
4323 break;
4324 }
4325 }
4326 else
4327 {
4328 Xml.Append("'>");
4329
4330 foreach (PubSubNode Node in await Database.Find<PubSubNode>(new FilterAnd(
4331 new FilterFieldEqualTo("Service", Service), new FilterFieldEqualTo("IsRoot", true)), "Name"))
4332 {
4333 Xml.Append("<item jid='");
4334 Xml.Append(XML.Encode(e.To.Address));
4335 Xml.Append("' node='");
4336 Xml.Append(XML.Encode(Node.Name));
4337
4338 if (!string.IsNullOrEmpty(Node.Title))
4339 {
4340 Xml.Append("' name='");
4341 Xml.Append(XML.Encode(Node.Title));
4342 }
4343
4344 Xml.Append("'/>");
4345 }
4346 }
4347
4348 Xml.Append("</query>");
4349
4350 await e.IqResult(Xml.ToString(), e.To);
4351 }
4352
4353 private async Task Server_OnPresenceLocalRecipient(object Sender, PresenceEventArgs e)
4354 {
4355 if (this.disposed)
4356 return;
4357
4358 try
4359 {
4360 switch (e.Type)
4361 {
4362 case "":
4363 string NodeVer = null;
4364
4365 foreach (XmlNode N in e.Stanza.StanzaElement.ChildNodes)
4366 {
4367 if (N is XmlElement E &&
4368 E.LocalName == "c" &&
4369 E.NamespaceURI == "http://jabber.org/protocol/caps")
4370 {
4371 string Node = XML.Attribute(E, "node");
4372 string Version = XML.Attribute(E, "ver");
4373
4374 NodeVer = Node + "#" + Version;
4375 break;
4376 }
4377 }
4378
4379 if (NodeVer is null)
4380 await this.SendLast(e.To, e.From, e.Language);
4381 else
4382 {
4383 lock (this.nodeVerByFullJidByBareJid)
4384 {
4385 if (!this.nodeVerByFullJidByBareJid.TryGetValue(e.From.BareJid,
4386 out NodeVerInfo NodeVerInfo))
4387 {
4388 NodeVerInfo = new NodeVerInfo()
4389 {
4390 NodeVerPerFullJid = new Dictionary<CaseInsensitiveString, string>()
4391 };
4392
4393 this.nodeVerByFullJidByBareJid[e.From.BareJid] = NodeVerInfo;
4394 }
4395
4396 NodeVerInfo.NodeVerPerFullJid[e.From.Address] = NodeVer;
4397 NodeVerInfo.UpdateStaticLocked();
4398 }
4399
4400 if (this.featuresByNodeVer.ContainsKey(NodeVer))
4401 await this.SendLast(e.To, e.From, e.Language);
4402 else
4403 {
4404 await this.Server.SendIqRequest("get", e.To, e.From, e.Language,
4405 "<query xmlns='" + XmppServer.DiscoveryNamespace + "' node='" +
4406 NodeVer + "'/>", false, async (sender2, e2) =>
4407 {
4408 try
4409 {
4410 XmlElement E2;
4411
4412 if (e2.Ok &&
4413 !((E2 = e2.FirstElement) is null) &&
4414 E2.LocalName == "query" &&
4415 E2.NamespaceURI == XmppServer.DiscoveryNamespace &&
4416 XML.Attribute(E2, "node") == NodeVer)
4417 {
4418 Dictionary<string, bool> Features = new Dictionary<string, bool>();
4419
4420 foreach (XmlNode N in E2.ChildNodes)
4421 {
4422 if (N is XmlElement E3 && E3.LocalName == "feature")
4423 Features[XML.Attribute(E3, "var")] = true;
4424 }
4425
4426 this.featuresByNodeVer[NodeVer] = Features;
4427
4428 await this.SendLast(e.From, e.To, e.Language);
4429 }
4430 else
4431 await this.SendLast(e.From, e.To, e.Language);
4432 }
4433 catch (Exception ex)
4434 {
4435 Log.Exception(ex);
4436 }
4437 }, null);
4438 }
4439 }
4440 break;
4441
4442 case "subscribed":
4443 await this.SendLast(e.From, e.To, e.Language);
4444 break;
4445
4446 case "unavailable":
4447 lock (this.nodeVerByFullJidByBareJid)
4448 {
4449 if (this.nodeVerByFullJidByBareJid.TryGetValue(e.From.BareJid,
4450 out NodeVerInfo NodeVerInfo) &&
4451 NodeVerInfo.NodeVerPerFullJid.Remove(e.From.Address))
4452 {
4453 if (NodeVerInfo.NodeVerPerFullJid.Count == 0)
4454 this.nodeVerByFullJidByBareJid.Remove(e.From.BareJid);
4455 else
4456 NodeVerInfo.UpdateStaticLocked();
4457 }
4458 }
4459 break;
4460 }
4461 }
4462 catch (Exception ex)
4463 {
4464 Log.Exception(ex);
4465 }
4466 }
4467
4468 private async Task SendLast(XmppAddress Publisher, XmppAddress Subscriber, string Language)
4469 {
4470 string SubscriberBareJid = Subscriber.BareJid;
4471 IRosterItem RosterItem = await XmppServerModule.GetRosterItemAsync(Publisher.Account, SubscriberBareJid);
4472 if (RosterItem is null || (RosterItem.Subscription != SubscriptionStatus.both && RosterItem.Subscription != SubscriptionStatus.from))
4473 return;
4474
4475 PubSubNode[] NodeArray = null;
4476 Nodes Nodes;
4477 XmppAddress FromBareJid = Publisher.ToBareJID();
4478 CaseInsensitiveString Service = FromBareJid.Address;
4479
4480 lock (this.nodesByServiceAndName)
4481 {
4482 if (this.nodesByServiceAndName.TryGetValue(Service, out Nodes))
4483 NodeArray = Nodes.NodesArray;
4484 else
4485 Nodes = null;
4486 }
4487
4488 if (Nodes is null)
4489 {
4490 await this.GetNodeAsync(Service, string.Empty, null, Publisher.ToBareJID(), null); // Loads available nodes
4491
4492 lock (this.nodesByServiceAndName)
4493 {
4494 if (!this.nodesByServiceAndName.TryGetValue(Service, out Nodes))
4495 return;
4496
4497 NodeArray = Nodes.NodesArray;
4498 }
4499 }
4500
4501 foreach (PubSubNode Node in NodeArray)
4502 {
4503 if (Node.AutoSubscribe && Node.AccessModel == NodeAccessModel.presence)
4504 {
4505 CaseInsensitiveString[] Jids = this.FilterNotifiations(Subscriber, Node);
4506 if (Jids is null)
4507 continue;
4508
4509 foreach (CaseInsensitiveString Jid in Jids)
4510 await this.SendLastItem(Service, Node, Jid, FromBareJid, Language);
4511 }
4512 }
4513 }
4514
4515 private CaseInsensitiveString[] FilterNotifiations(XmppAddress Jid, PubSubNode Node)
4516 {
4517 CaseInsensitiveString BareJid = Jid.BareJid;
4518 KeyValuePair<CaseInsensitiveString, string>[] NodeVerPerFullJid;
4519
4520 lock (this.nodeVerByFullJidByBareJid)
4521 {
4522 if (!this.nodeVerByFullJidByBareJid.TryGetValue(BareJid, out NodeVerInfo NodeVerInfo))
4523 return new CaseInsensitiveString[] { BareJid };
4524
4525 NodeVerPerFullJid = NodeVerInfo.NodeVerPerFullJidStatic;
4526 }
4527
4528 string Feature = Node.Name + "+notify";
4529 List<CaseInsensitiveString> FullJids = null;
4530
4531 foreach (KeyValuePair<CaseInsensitiveString, string> P in NodeVerPerFullJid)
4532 {
4533 if (!(this.featuresByNodeVer?.TryGetValue(P.Value, out Dictionary<string, bool> Features) ?? false))
4534 continue;
4535
4536 if (!Features.ContainsKey(Feature))
4537 continue;
4538
4539 FullJids ??= new List<CaseInsensitiveString>();
4540 FullJids.Add(P.Key);
4541 }
4542
4543 return FullJids?.ToArray();
4544 }
4545
4546 private class NodeVerInfo
4547 {
4548 public Dictionary<CaseInsensitiveString, string> NodeVerPerFullJid;
4549 public KeyValuePair<CaseInsensitiveString, string>[] NodeVerPerFullJidStatic;
4550
4551 public void UpdateStaticLocked()
4552 {
4553 int i = 0;
4554
4555 this.NodeVerPerFullJidStatic = new KeyValuePair<CaseInsensitiveString, string>[this.NodeVerPerFullJid.Count];
4556
4557 foreach (KeyValuePair<CaseInsensitiveString, string> P in this.NodeVerPerFullJid)
4558 this.NodeVerPerFullJidStatic[i++] = P;
4559 }
4560 }
4561
4566 public async Task<(int, int)> DeleteExpiredNodes()
4567 {
4568 int NrNodes = 0;
4569 int NrItems = 0;
4570
4571 foreach (PubSubNode Node in await Database.FindDelete<PubSubNode>(new FilterFieldLesserThan("Expires", DateTime.Today.AddDays(-1))))
4572 {
4573 NrNodes++;
4574
4575 NrItems = await Database.Delete<PubSubItem>(new FilterAnd(
4576 new FilterFieldEqualTo("Service", Node.Service),
4577 new FilterFieldEqualTo("Node", Node.Name)));
4578 }
4579
4580 return (NrNodes, NrItems);
4581 }
4582
4583
4584 // TODO: Delete expired subscriptions
4585 // TODO: Delete expired items
4586 }
4587}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
Contains information about a response to a content request.
bool HasError
If an error occurred.
Static class managing encoding and decoding of internet content.
static bool Decodes(string ContentType, out Grade Grade, out IContentDecoder Decoder)
If an object with a given content type can be decoded.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:892
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
static bool IsValidXml(string Xml)
Checks if a string is valid XML
Definition: XML.cs:1397
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Implements an HTTP server.
Definition: HttpServer.cs:41
int[] OpenHttpPorts
HTTP Ports successfully opened.
Definition: HttpServer.cs:768
HttpServer(params ISniffer[] Sniffers)
Implements an HTTPS server.
Definition: HttpServer.cs:124
int[] OpenHttpsPorts
HTTPS Ports successfully opened.
Definition: HttpServer.cs:773
Implements support for data forms. Data Forms are defined in the following XEPs:
Definition: DataForm.cs:42
int SerializeForm(StringBuilder Output)
Serializes the form as an editable form.
Definition: DataForm.cs:967
Field[] Fields
Fields in the form.
Definition: DataForm.cs:715
int SerializeResult(StringBuilder Output)
Serializes the form as a result submission.
Definition: DataForm.cs:936
static readonly BooleanDataType Instance
Public instance of data type.
static readonly DateDataType Instance
Public instance of data type.
Definition: DateDataType.cs:13
static readonly DateTimeDataType Instance
Public instance of data type.
static readonly IntegerDataType Instance
Public instance of data type.
static readonly StringDataType Instance
Public instance of data type.
Base class for form fields
Definition: Field.cs:17
string ValueString
Value as a single string. If field contains multiple values, they will be concatenated into a single ...
Definition: Field.cs:102
Implements an XMPP provisioning client interface.
string OwnerJid
JID of owner, if known or available.
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
string[] Groups
Any groups the roster item belongs to.
Definition: RosterItem.cs:186
string BareJid
Bare JID of the roster item.
Definition: RosterItem.cs:276
Base class for components.
Definition: Component.cs:17
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a message handler.
Definition: Component.cs:298
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: Component.cs:162
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:77
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:150
XmppServer Server
XMPP Server.
Definition: Component.cs:97
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:250
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: Component.cs:263
void UnregisterFeature(string Namespace)
Unregisters a feature.
Definition: Component.cs:352
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a message handler.
Definition: Component.cs:191
void RegisterFeature(string Namespace)
Registers a feature.
Definition: Component.cs:234
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqErrorNotAcceptable(XmppAddress From, string ErrorText, string Language)
Returns a not-acceptable error.
Definition: IqEventArgs.cs:248
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:206
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
async Task IqError(string ErrorType, string Xml, XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Definition: IqEventArgs.cs:139
Task IqErrorConflict(XmppAddress From, string ErrorText, string Language)
Returns a conflict error.
Definition: IqEventArgs.cs:262
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:164
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:234
XmppAddress From
From address attribute
XmlElement Content
Content element, if found, null otherwise.
Presence information event arguments.
Contains information about a restricted query, as deinfed in XEP-0059: Result Set Management
const string NamespaceResultSetManagement
http://jabber.org/protocol/rsm
static RestrictedQuery IsRestricted(XmlNode FirstSibling)
Searches for a restricted query, by traversing siblings.
int? Max
If result set should be limited in size.
Contains information about a result page, as deinfed in XEP-0059: Result Set Management
Definition: ResultPage.cs:13
void Append(StringBuilder Xml)
Appends pagination information to an XML request.
Definition: ResultPage.cs:102
XmlElement StanzaElement
Stanza element.
Definition: Stanza.cs:113
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
bool IsEmpty
If the address is empty.
Definition: XmppAddress.cs:183
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
XmppAddress ToBareJID()
Returns the Bare JID as an XmppAddress object.
Definition: XmppAddress.cs:215
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
CaseInsensitiveString Account
Account
Definition: XmppAddress.cs:124
bool IsFullJID
If the Address is a Full JID.
Definition: XmppAddress.cs:151
const string DiscoveryItemsNamespace
http://jabber.org/protocol/disco#items (XEP-0030)
Definition: XmppServer.cs:138
const string ExtendedAddressingNamespace
http://jabber.org/protocol/address (XEP-0033)
Definition: XmppServer.cs:143
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a message handler.
Definition: XmppServer.cs:1804
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: XmppServer.cs:1703
const string DiscoveryNamespace
http://jabber.org/protocol/disco#info (XEP-0030)
Definition: XmppServer.cs:133
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3862
Task< bool > SendIqRequest(string Type, string From, string To, string Language, string ContentXml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ stanza to a recipient.
Definition: XmppServer.cs:3668
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:898
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: XmppServer.cs:1756
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: XmppServer.cs:1691
const string DelayedDeliveryNamespace
urn:xmpp:delay (XEP-0203)
Definition: XmppServer.cs:193
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: XmppServer.cs:1769
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a message handler.
Definition: XmppServer.cs:1732
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int Length
Gets the number of characters in the current CaseInsensitiveString object.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
bool StartsWith(CaseInsensitiveString value)
Determines whether the beginning of this string instance matches the specified string.
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:59
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
static Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Definition: Database.cs:1838
Persists objects into binary files.
Task< ObjectSerializer > GetObjectSerializerEx(object Object)
Gets the object serializer corresponding to a specific object.
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field greater than a given value.
This filter selects objects that have a named field lesser than a given value.
Base class for all filter classes.
Definition: Filter.cs:15
Manages binary serialization of data.
void FlushBits()
Flushes any bit field values.
byte[] GetSerialization()
Gets the binary serialization.
Serializes a class, taking into account attributes defined in Attributes.
virtual async Task Serialize(ISerializer Writer, bool WriteTypeCode, bool Embedded, object Value, object State)
Serializes a value.
Implements an in-memory cache.
Definition: Cache.cs:17
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
Definition: Cache.cs:404
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
PubSub component, as defined in XEP-0060. https://xmpp.org/extensions/xep-0060.html
const string NamespacePubSubMultiCollecton
http://jabber.org/protocol/pubsub#multi-collection
const string NamespacePubSubAccessPresence
http://jabber.org/protocol/pubsub#access-presence
const string FormTypeNodeMetaData
http://jabber.org/protocol/pubsub#meta-data
Task< string > PublishItem(string Service, string NodeName, string AutoCreateAccess, string From, string Domain, string ItemId, string XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
const string NamespacePubSubLeasedSubscription
http://jabber.org/protocol/pubsub#leased-subscription
const string NamespacePubSub
http://jabber.org/protocol/pubsub
const string NamespacePubSubCreateAndConfigure
http://jabber.org/protocol/pubsub#create-and-configure
const string NamespacePubSubDeleteNodes
http://jabber.org/protocol/pubsub#delete-nodes
const string NamespacePubSubAccessAuthorize
http://jabber.org/protocol/pubsub#access-authorize
const string NamespacePubSubOwner
http://jabber.org/protocol/pubsub#owner
const string NamespacePubSubRetrieveDefaultSub
http://jabber.org/protocol/pubsub#retrieve-default-sub
const string NamespacePubSubPersistentItems
http://jabber.org/protocol/pubsub#persistent-items
const string NamespacePubSubManageSubscriptions
http://jabber.org/protocol/pubsub#manage-subscriptions
const string NamespacePubSubemberAffiliation
http://jabber.org/protocol/pubsub#member-affiliation
const string NamespacePubSubDeleteItems
http://jabber.org/protocol/pubsub#delete-items
override Task AppendServiceDiscoveryIdentities(StringBuilder Xml, IqEventArgs e, string Node)
Component.AppendServiceDiscoveryIdentities(StringBuilder, IqEventArgs, string)
Task< string > PublishItem(string Service, string NodeName, NodeAccessModel? AutoCreateAccess, string From, string Domain, string ItemId, XmlDocument XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
async Task<(int, int)> DeleteExpiredNodes()
Deletes expired nodes
const string NamespacePubSubAutoSubscribe
http://jabber.org/protocol/pubsub#auto-subscribe
const string NamespacePubSubItemIds
http://jabber.org/protocol/pubsub#item-ids
const string NamespacePubSubAutoCreate
http://jabber.org/protocol/pubsub#auto-create
async Task< PubSubNode > GetNodeAsync(CaseInsensitiveString Service, CaseInsensitiveString NodeName, NodeAccessModel? AutoCreateAccess, XmppAddress From, CaseInsensitiveString Domain)
Gets a pubsub node.
async Task< string > PublishItem(string Service, string NodeName, NodeAccessModel? AutoCreateAccess, XmppAddress From, CaseInsensitiveString Domain, CaseInsensitiveString ItemId, XmlDocument XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
const string NamespacePubSubSubscribe
http://jabber.org/protocol/pubsub#subscribe
Task< string > PublishItem(string Service, string NodeName, string AutoCreateAccess, string From, string Domain, string ItemId, XmlDocument XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
async Task< Subscription[]> GetSubscriptionsOnNode(CaseInsensitiveString Service, CaseInsensitiveString Node)
Gets available subscriptions on a node.
const string FormTypeSubscriptionAuthorization
http://jabber.org/protocol/pubsub#subscribe_authorization
const string FormTypeSubscribeOptions
http://jabber.org/protocol/pubsub#subscribe_options
const string NamespacePubSubRetrieveDefault
http://jabber.org/protocol/pubsub#retrieve-default
const string NamespacePubSubMetaData
http://jabber.org/protocol/pubsub#meta-data
override void Dispose()
IDisposable.Dispose
const string NamespaceStanzaHeaders
http://jabber.org/protocol/shim
const string NamespacePubSubNodeConfiguration
http://jabber.org/protocol/pubsub#config-node
const string NamespacePubSubSubscriptionOptions
http://jabber.org/protocol/pubsub#subscription-options
const string NamespacePubSubAccessOpen
http://jabber.org/protocol/pubsub#access-open
const string NamespacePubSubRetrieveSubscriptions
http://jabber.org/protocol/pubsub#retrieve-subscriptions
const string NamespacePubSubPublisheAffiliation
http://jabber.org/protocol/pubsub#publisher-affiliation
async Task< Subscription[]> GetSubscriptionsByJid(CaseInsensitiveString Service, CaseInsensitiveString Jid)
Gets available subscriptions for a JID.
const string NamespacePubSubRetractItems
http://jabber.org/protocol/pubsub#retract-items
const string NamespacePubSubCreateNodes
http://jabber.org/protocol/pubsub#create-nodes
const string NamespacePubSubLastPublished
http://jabber.org/protocol/pubsub#last-published
const string NamespacePubSubAccessRoster
http://jabber.org/protocol/pubsub#access-roster
const string NamespacePubSubPublish
http://jabber.org/protocol/pubsub#publish
const string NamespacePubSubPurgeNodes
http://jabber.org/protocol/pubsub#purge-nodes
async Task< Subscription > GetSubscriptionAsync(CaseInsensitiveString Service, CaseInsensitiveString Node, CaseInsensitiveString Jid)
Gets a subscription object.
static async Task< PubSubComponent > Create(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
PubSub component, as defined in XEP-0060.
const string NamespacePubSubOutcastAffiliation
http://jabber.org/protocol/pubsub#outcast-affiliation
const string NamespacePubSubRetrieveAffiliations
http://jabber.org/protocol/pubsub#retrieve-affiliations
const string NamespacePubSubAccessWhitelist
http://jabber.org/protocol/pubsub#access-whitelist
const string NamespacePubSubEvents
http://jabber.org/protocol/pubsub#event
const string FormTypeNodeConfig
http://jabber.org/protocol/pubsub#node_config
const string NamespacePubSubRetrieveItems
http://jabber.org/protocol/pubsub#retrieve-items
override Task DiscoveryQueryGet(object Sender, IqEventArgs e)
Component.DiscoveryQueryGet(object, IqEventArgs)
override Task DiscoveryQueryItemsGet(object Sender, IqEventArgs e)
Get handler for Service Discovery items query (XEP-0030).
const string NamespacePubSubSubscriptionNotifications
http://jabber.org/protocol/pubsub#subscription-notifications
const string NamespacePubSubPublishOnlyAffiliation
http://jabber.org/protocol/pubsub#publish-only-affiliation
const string NamespacePubSubModifyAffiliations
http://jabber.org/protocol/pubsub#modify-affiliations
const string NamespacePubSubPresenceSubscribe
http://jabber.org/protocol/pubsub#presence-subscribe
const string NamespacePubSubCollections
http://jabber.org/protocol/pubsub#collections
const string NamespacePubSubMultiItems
http://jabber.org/protocol/pubsub#multi-items
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
CaseInsensitiveString ItemIdOrObjectId
Item ID, if not empty, else the object ID.
Definition: PubSubItem.cs:60
CaseInsensitiveString Node
Name of node.
Definition: PubSubItem.cs:83
CaseInsensitiveString Publisher
Publisher of content.
Definition: PubSubItem.cs:92
Defines a node on which items can be published.
Definition: PubSubNode.cs:19
bool DeliverNotifications
Whether to deliver event notifications
Definition: PubSubNode.cs:253
NodeAccessModel AccessModel
Who may subscribe and retrieve items
Definition: PubSubNode.cs:202
bool AutoDelete
If old items are automatically deleted.
Definition: PubSubNode.cs:644
CaseInsensitiveString Name
Name of node.
Definition: PubSubNode.cs:113
int MaxItems
The maximum number of items to persist
Definition: PubSubNode.cs:332
int MaxPayloadSize
The maximum number of items to persist
Definition: PubSubNode.cs:342
bool PublishOnWeb
If the items published to the node should be available on the web or not.
Definition: PubSubNode.cs:654
bool PersistItems
Whether to persist items to storage
Definition: PubSubNode.cs:402
string PayloadType
The type of node data, usually specified by the namespace of the payload (if any); MAY be list-single...
Definition: PubSubNode.cs:512
bool AutoSubscribe
If the node is a root node or not.
Definition: PubSubNode.cs:634
bool TransientNode
If the node is transient (i.e. does not persist items, and do not deliver payloads.
Definition: PubSubNode.cs:273
NotificationType NotificationType
Notification type
Definition: PubSubNode.cs:532
int ItemExpireSeconds
Number of seconds after which to automatically purge items.
Definition: PubSubNode.cs:522
CaseInsensitiveString Service
Name of PubSub Service.
Definition: PubSubNode.cs:104
bool DeliverPayloads
Whether to deliver payloads with event notifications; applies only to leaf nodes
Definition: PubSubNode.cs:263
NodeType NodeType
Whether the node is a leaf (default) or a collection
Definition: PubSubNode.cs:352
CaseInsensitiveString Jid
JID to whom event notifications will be sent.
Definition: Subscription.cs:84
CaseInsensitiveString Node
Node name
Definition: Subscription.cs:75
Basic interface for Internet Content decoders. A class implementing this interface and having a defau...
Interface for XMPP user accounts.
Definition: IAccount.cs:9
Interface for roster items.
Definition: IRosterItem.cs:43
Definition: ImplTypes.g.cs:58
FormType
Type of data form.
Definition: FormType.cs:7
Affiliation
Affiliation enumeration
Definition: Enumerations.cs:11
SubscriptionStatus
Roster item subscription status enumeration.
Definition: IRosterItem.cs:10
Grade
Grade enumeration
Definition: Grade.cs:7
NodeAccessModel
Node access model.
Definition: Enumerations.cs:9
NodeType
Whether the node is a leaf (default) or a collection
AffiliationStatus
User affiliation
NotificationType
Delivery style for notifications
Definition: Enumerations.cs:87
SubscriptionType
Subscription type
NodeItemReply
Whether owners or publisher should receive replies to items
NodeChildAssociationPolicy
Who may associate leaf nodes with a collection
NodeSubscriptionStatus
Node subscription status
SubscriptonDepth
Subscription depth
SendLastPublishedItem
When to send the last published item
Definition: Enumerations.cs:66