2using System.Collections.Generic;
4using System.Threading.Tasks;
20 protected SortedDictionary<string, bool>
features =
new SortedDictionary<string, bool>();
27 private readonly Dictionary<string, EventHandlerAsync<IqEventArgs>> iqGetHandlers =
new Dictionary<string, EventHandlerAsync<IqEventArgs>>();
28 private readonly Dictionary<string, EventHandlerAsync<IqEventArgs>> iqSetHandlers =
new Dictionary<string, EventHandlerAsync<IqEventArgs>>();
29 private readonly Dictionary<string, EventHandlerAsync<MessageEventArgs>> messageHandlers =
new Dictionary<string, EventHandlerAsync<MessageEventArgs>>();
30 private readonly Dictionary<string, EventHandlerAsync<PresenceEventArgs>> presenceHandlers =
new Dictionary<string, EventHandlerAsync<PresenceEventArgs>>();
36 private readonly
string name;
61 this.mainDomain =
new XmppAddress(this.subdomainSuffixed + this.server.Domain);
63 if (!
string.IsNullOrEmpty(
Subdomain) && !(this.server is
null))
65 if (!this.server.RegisterComponent(
this))
66 throw new InvalidOperationException(
"A component with subdomain " +
Subdomain +
" has already been registered.");
91 public string Name => this.name;
103 if (!
string.IsNullOrEmpty(this.subdomain) && !(this.server is
null))
104 this.server.UnregisterComponent(
this);
108 this.responses.Dispose();
125 if (Domain == this.subdomainSuffixed + this.server.Domain)
128 if (IncludeAlternativeDomains && !(this.server.AlternativeDomains is
null))
132 if (Domain == this.subdomainSuffixed + s)
149 public void RegisterIqGetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool PublishNamespaceAsFeature)
151 this.RegisterIqHandler(this.iqGetHandlers, LocalName, Namespace, Handler, PublishNamespaceAsFeature);
161 public void RegisterIqSetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool PublishNamespaceAsFeature)
163 this.RegisterIqHandler(this.iqSetHandlers, LocalName, Namespace, Handler, PublishNamespaceAsFeature);
166 private void RegisterIqHandler(Dictionary<
string, EventHandlerAsync<IqEventArgs>> Handlers,
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
167 bool PublishNamespaceAsFeature)
169 string Key = LocalName +
" " + Namespace;
171 lock (this.synchObject)
173 if (Handlers.ContainsKey(Key))
174 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
176 Handlers[Key] = Handler;
178 if (PublishNamespaceAsFeature)
179 this.features[Namespace] =
true;
190 public void RegisterMessageHandler(
string LocalName,
string Namespace, EventHandlerAsync<MessageEventArgs> Handler,
bool PublishNamespaceAsFeature)
192 string Key = LocalName +
" " + Namespace;
194 lock (this.synchObject)
196 if (this.messageHandlers.ContainsKey(Key))
197 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
199 this.messageHandlers[Key] = Handler;
201 if (PublishNamespaceAsFeature)
202 this.features[Namespace] =
true;
213 public void RegisterPresenceHandler(
string LocalName,
string Namespace, EventHandlerAsync<PresenceEventArgs> Handler,
bool PublishNamespaceAsFeature)
215 string Key = LocalName +
" " + Namespace;
217 lock (this.synchObject)
219 if (this.presenceHandlers.ContainsKey(Key))
220 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
222 this.presenceHandlers[Key] = Handler;
224 if (PublishNamespaceAsFeature)
225 this.features[Namespace] =
true;
235 lock (this.synchObject)
237 this.features[Namespace] =
true;
249 public bool UnregisterIqGetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool RemoveNamespaceAsFeature)
251 return this.UnregisterIqHandler(this.iqGetHandlers, LocalName, Namespace, Handler, RemoveNamespaceAsFeature);
262 public bool UnregisterIqSetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool RemoveNamespaceAsFeature)
264 return this.UnregisterIqHandler(this.iqSetHandlers, LocalName, Namespace, Handler, RemoveNamespaceAsFeature);
267 private bool UnregisterIqHandler(Dictionary<
string, EventHandlerAsync<IqEventArgs>> Handlers,
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
268 bool RemoveNamespaceAsFeature)
270 string Key = LocalName +
" " + Namespace;
272 lock (this.synchObject)
274 if (!Handlers.TryGetValue(Key, out EventHandlerAsync<IqEventArgs> h))
280 Handlers.Remove(Key);
282 if (RemoveNamespaceAsFeature)
283 this.features.Remove(Namespace);
297 public bool UnregisterMessageHandler(
string LocalName,
string Namespace, EventHandlerAsync<MessageEventArgs> Handler,
bool RemoveNamespaceAsFeature)
299 string Key = LocalName +
" " + Namespace;
301 lock (this.synchObject)
303 if (!this.messageHandlers.TryGetValue(Key, out EventHandlerAsync<MessageEventArgs> h))
309 this.messageHandlers.Remove(Key);
311 if (RemoveNamespaceAsFeature)
312 this.features.Remove(Namespace);
326 public bool UnregisterPresenceHandler(
string LocalName,
string Namespace, EventHandlerAsync<PresenceEventArgs> Handler,
bool RemoveNamespaceAsFeature)
328 string Key = LocalName +
" " + Namespace;
330 lock (this.synchObject)
332 if (!this.presenceHandlers.TryGetValue(Key, out EventHandlerAsync<PresenceEventArgs> h))
338 this.presenceHandlers.Remove(Key);
340 if (RemoveNamespaceAsFeature)
341 this.features.Remove(Namespace);
353 lock (this.synchObject)
355 this.features.Remove(Namespace);
371 Dictionary<string, EventHandlerAsync<IqEventArgs>> Handlers;
372 EventHandlerAsync<IqEventArgs> h =
null;
378 if (this.responses.TryGet(From.
Address, Id, out
string ResponseXml, out
bool Ok))
381 return await Sender.
IqResult(Id, From, To, ResponseXml);
383 return await Sender.
IqError(Id, From, To, ResponseXml);
386 Handlers = this.iqGetHandlers;
390 if (this.responses.TryGet(From.
Address, Id, out ResponseXml, out Ok))
393 return await Sender.
IqResult(Id, From, To, ResponseXml);
395 return await Sender.
IqError(Id, From, To, ResponseXml);
398 Handlers = this.iqSetHandlers;
403 return await this.server.ProcessResponse(Type, Id, To, From, Language,
true,
false,
Stanza, Sender);
409 return !(await Sender.IqErrorBadRequest(Id, From, To,
"Invalid type.",
"en") is
null);
414 lock (this.synchObject)
418 if (!(N is XmlElement E))
421 Key = E.LocalName +
" " + E.NamespaceURI;
422 if (Handlers.TryGetValue(Key, out h))
437 return !(await Sender.
IqError(Id, From, To,
"cancel",
"<feature-not-implemented xmlns='" +
XmppServer.
StanzaNamespace +
"'/>",
string.Empty,
string.Empty) is
null);
451 return !(await Sender.
IqError(Id, From, To, ex) is
null);
468 return this.
IQ(Type, Id, To, From, Language,
XmppServer.ToStanza(
"iq", Type, Id, To, From, Language, ContentXml), Sender);
484 EventHandlerAsync<MessageEventArgs> h =
null;
487 lock (this.synchObject)
491 if (!(N is XmlElement E))
494 Key = E.LocalName +
" " + E.NamespaceURI;
495 if (this.messageHandlers.TryGetValue(Key, out h))
514 if (!(Sender is
null))
515 await (Sender?.
MessageError(Id, From, To, ex) ?? Task.CompletedTask);
527 return Task.CompletedTask;
542 return this.
Message(Type, Id, To, From, Language,
XmppServer.ToStanza(
"message", Type, Id, To, From, Language, ContentXml), Sender);
573 EventHandlerAsync<PresenceEventArgs> h =
null;
576 lock (this.synchObject)
580 if (!(N is XmlElement E))
583 Key = E.LocalName +
" " + E.NamespaceURI;
584 if (this.presenceHandlers.TryGetValue(Key, out h))
603 if (!(Sender is
null))
604 await (Sender?.
PresenceError(Id, From, To, ex) ?? Task.CompletedTask);
616 return Task.CompletedTask;
631 return this.
Presence(Type, Id, To, From, Language,
XmppServer.ToStanza(
"presence", Type, Id, To, From, Language, ContentXml), Sender);
644 string s = Xml.OuterXml;
645 int i = s.IndexOf(
'>');
646 int j = s.LastIndexOf(
'<');
651 return s.Substring(i + 1, j - i - 1);
656 #region Discovery XEP-0030
665 XmlElement E = e.
Query;
674 if (!
string.IsNullOrEmpty(Node) && !await this.
SupportsNode(Node))
680 StringBuilder Xml =
new StringBuilder();
682 Xml.Append(
"<query xmlns='");
688 if (
string.IsNullOrEmpty(e.
To.
Account) &&
string.IsNullOrEmpty(Node))
690 lock (this.synchObject)
692 foreach (
string Feature
in this.features.Keys)
694 Xml.Append(
"<feature var='");
703 Xml.Append(
"</query>");
715 return Task.FromResult(
false);
725 return Task.FromResult(
false);
735 XmlElement E = e.
Query;
744 if (!
string.IsNullOrEmpty(Node) && !await this.
SupportsNode(Node))
750 StringBuilder Xml =
new StringBuilder();
752 Xml.Append(
"<query xmlns='");
758 Xml.Append(
"</query>");
771 return Task.CompletedTask;
782 return Task.CompletedTask;
793 return Task.CompletedTask;
810 return Task.FromResult(
string.Empty);
823 return Task.FromResult(
true);
836 return Task.FromResult(
true);
849 return Task.FromResult(
true);
862 return Task.FromResult(
true);
876 return Task.FromResult(
true);
890 return Task.FromResult(
true);
903 return Task.FromResult(
true);
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
static string Encode(string s)
Encodes a string for use in XML.
Base class for components.
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a message handler.
virtual Task AppendServiceDiscoveryIdentities(StringBuilder Xml, IqEventArgs e, string Node)
Appends component identities, as defined in XEP-0030, to a discovery response.
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
CaseInsensitiveString Subdomain
Subdomain name.
Component(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
Base class for components.
CaseInsensitiveString SubdomainSuffixed
Subdomain name, suffixed with a period (.).
virtual Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an Presence Error stanza.
virtual Task< bool > IqError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends an IQ Error stanza.
bool UnregisterPresenceHandler(string LocalName, string Namespace, EventHandlerAsync< PresenceEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a presence handler.
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
static string InnerXmlNoJabberNamespace(XmlElement Xml)
Removes the jabber namespace from the outer XML of an element.
Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
IQ stanza.
void RegisterPresenceHandler(string LocalName, string Namespace, EventHandlerAsync< PresenceEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a presence handler.
virtual async Task DiscoveryQueryGet(object Sender, IqEventArgs e)
Get handler for Service Discovery query (XEP-0030).
virtual Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends an Presence Error stanza.
virtual Task MessageNoHandlerReceived(MessageEventArgs e)
Message without corresponding handler received.
virtual async Task DiscoveryQueryItemsGet(object Sender, IqEventArgs e)
Get handler for Service Discovery items query (XEP-0030).
virtual Task< bool > MessageError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends a message error.
string Name
Component name.
Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Presence stanza.
XmppAddress MainDomain
Main/principal domain address
readonly object synchObject
Synchronization object for thread-safe access to internal structures.
virtual Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Presence stanza.
virtual Task AppendItemInformation(StringBuilder Xml, IqEventArgs e, string Node)
Appends item information to a service item description response.
virtual Task AppendServiceDiscoveryFeatures(StringBuilder Xml, IqEventArgs e, string Node)
Appends custom service descriptions.
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
virtual void Dispose()
IDisposable.Dispose
async Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Message stanza.
async Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
IQ stanza.
virtual bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
virtual Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Message stanza.
async Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Presence stanza.
XmppServer Server
XMPP Server.
virtual Task PresenceNoHandlerReceived(PresenceEventArgs e)
Presence stanza without a corresponding handler received.
virtual Task< bool > IqResult(string Id, XmppAddress To, XmppAddress From, string ResultXml)
Sends an IQ Result stanza.
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
virtual Task< bool > SupportsNode(string Node)
If the component supports a named node.
virtual Task< bool > SupportsAccount(string Account)
If the component supports a named account.
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
void UnregisterFeature(string Namespace)
Unregisters a feature.
virtual Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ Error stanza.
Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Message stanza.
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a message handler.
SortedDictionary< string, bool > features
Features supported by component.
Component(XmppServer Server, string Subdomain, string Name)
Base class for components.
void RegisterFeature(string Namespace)
Registers a feature.
Event arguments for IQ queries.
Task IqResult(string Xml, string From)
Returns a response to the current request.
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
XmlElement Query
Query element, if found, null otherwise.
XmppAddress To
To address attribute
Maintains a set of IQ responses, for a limited time.
Event arguments for Messages.
Presence information event arguments.
Contains information about a stanza.
XmlElement StanzaElement
Stanza element.
Contains information about one XMPP address.
bool HasAccount
If the address has an account part.
CaseInsensitiveString Address
XMPP Address
CaseInsensitiveString Account
Account
const string DiscoveryItemsNamespace
http://jabber.org/protocol/disco#items (XEP-0030)
const string DiscoveryNamespace
http://jabber.org/protocol/disco#info (XEP-0030)
const string StanzaNamespace
urn:ietf:params:xml:ns:xmpp-stanzas (RFC 6120)
Represents a case-insensitive string.
Interface for components.
Interface for senders of stanzas.
Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ Error stanza.
Task< bool > IqResult(string Id, XmppAddress To, XmppAddress From, string ResultXml)
Sends an IQ Result stanza.