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