4using System.Threading.Tasks;
21 protected SortedDictionary<string, bool>
features =
new SortedDictionary<string, bool>();
28 private readonly Dictionary<string, EventHandlerAsync<IqEventArgs>> iqGetHandlers =
new Dictionary<string, EventHandlerAsync<IqEventArgs>>();
29 private readonly Dictionary<string, EventHandlerAsync<IqEventArgs>> iqSetHandlers =
new Dictionary<string, EventHandlerAsync<IqEventArgs>>();
30 private readonly Dictionary<string, EventHandlerAsync<MessageEventArgs>> messageHandlers =
new Dictionary<string, EventHandlerAsync<MessageEventArgs>>();
31 private readonly Dictionary<string, EventHandlerAsync<PresenceEventArgs>> presenceHandlers =
new Dictionary<string, EventHandlerAsync<PresenceEventArgs>>();
32 private readonly
IqResponses responses =
new IqResponses(TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(10));
37 private readonly
string name;
62 this.mainDomain =
new XmppAddress(this.subdomainSuffixed + this.server.Domain);
64 if (!
string.IsNullOrEmpty(
Subdomain) && !(this.server is
null))
66 if (!this.server.RegisterComponent(
this))
67 throw new InvalidOperationException(
"A component with subdomain " +
Subdomain +
" has already been registered.");
92 public string Name => this.name;
104 if (!
string.IsNullOrEmpty(this.subdomain) && !(this.server is
null))
105 this.server.UnregisterComponent(
this);
109 this.responses.Dispose();
126 if (Domain == this.subdomainSuffixed + this.server.Domain)
129 if (IncludeAlternativeDomains && !(this.server.AlternativeDomains is
null))
133 if (Domain == this.subdomainSuffixed + s)
150 public void RegisterIqGetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool PublishNamespaceAsFeature)
152 this.RegisterIqHandler(this.iqGetHandlers, LocalName, Namespace, Handler, PublishNamespaceAsFeature);
162 public void RegisterIqSetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool PublishNamespaceAsFeature)
164 this.RegisterIqHandler(this.iqSetHandlers, LocalName, Namespace, Handler, PublishNamespaceAsFeature);
167 private void RegisterIqHandler(Dictionary<
string, EventHandlerAsync<IqEventArgs>> Handlers,
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
168 bool PublishNamespaceAsFeature)
170 string Key = LocalName +
" " + Namespace;
172 lock (this.synchObject)
174 if (Handlers.ContainsKey(Key))
175 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
177 Handlers[Key] = Handler;
179 if (PublishNamespaceAsFeature)
180 this.features[Namespace] =
true;
191 public void RegisterMessageHandler(
string LocalName,
string Namespace, EventHandlerAsync<MessageEventArgs> Handler,
bool PublishNamespaceAsFeature)
193 string Key = LocalName +
" " + Namespace;
195 lock (this.synchObject)
197 if (this.messageHandlers.ContainsKey(Key))
198 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
200 this.messageHandlers[Key] = Handler;
202 if (PublishNamespaceAsFeature)
203 this.features[Namespace] =
true;
214 public void RegisterPresenceHandler(
string LocalName,
string Namespace, EventHandlerAsync<PresenceEventArgs> Handler,
bool PublishNamespaceAsFeature)
216 string Key = LocalName +
" " + Namespace;
218 lock (this.synchObject)
220 if (this.presenceHandlers.ContainsKey(Key))
221 throw new ArgumentException(
"Handler already registered.", nameof(LocalName));
223 this.presenceHandlers[Key] = Handler;
225 if (PublishNamespaceAsFeature)
226 this.features[Namespace] =
true;
236 lock (this.synchObject)
238 this.features[Namespace] =
true;
250 public bool UnregisterIqGetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool RemoveNamespaceAsFeature)
252 return this.UnregisterIqHandler(this.iqGetHandlers, LocalName, Namespace, Handler, RemoveNamespaceAsFeature);
263 public bool UnregisterIqSetHandler(
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
bool RemoveNamespaceAsFeature)
265 return this.UnregisterIqHandler(this.iqSetHandlers, LocalName, Namespace, Handler, RemoveNamespaceAsFeature);
268 private bool UnregisterIqHandler(Dictionary<
string, EventHandlerAsync<IqEventArgs>> Handlers,
string LocalName,
string Namespace, EventHandlerAsync<IqEventArgs> Handler,
269 bool RemoveNamespaceAsFeature)
271 string Key = LocalName +
" " + Namespace;
273 lock (this.synchObject)
275 if (!Handlers.TryGetValue(Key, out EventHandlerAsync<IqEventArgs> h))
281 Handlers.Remove(Key);
283 if (RemoveNamespaceAsFeature)
284 this.features.Remove(Namespace);
298 public bool UnregisterMessageHandler(
string LocalName,
string Namespace, EventHandlerAsync<MessageEventArgs> Handler,
bool RemoveNamespaceAsFeature)
300 string Key = LocalName +
" " + Namespace;
302 lock (this.synchObject)
304 if (!this.messageHandlers.TryGetValue(Key, out EventHandlerAsync<MessageEventArgs> h))
310 this.messageHandlers.Remove(Key);
312 if (RemoveNamespaceAsFeature)
313 this.features.Remove(Namespace);
327 public bool UnregisterPresenceHandler(
string LocalName,
string Namespace, EventHandlerAsync<PresenceEventArgs> Handler,
bool RemoveNamespaceAsFeature)
329 string Key = LocalName +
" " + Namespace;
331 lock (this.synchObject)
333 if (!this.presenceHandlers.TryGetValue(Key, out EventHandlerAsync<PresenceEventArgs> h))
339 this.presenceHandlers.Remove(Key);
341 if (RemoveNamespaceAsFeature)
342 this.features.Remove(Namespace);
354 lock (this.synchObject)
356 this.features.Remove(Namespace);
372 Dictionary<string, EventHandlerAsync<IqEventArgs>> Handlers;
373 EventHandlerAsync<IqEventArgs> h =
null;
381 if (this.responses.TryGet(From.
Address, Id,
true, out Response, out
bool Created))
385 KeyValuePair<string, bool> P = await Response.
GetResponse();
388 return await Sender.
IqResult(Id, From, To, P.Key);
390 return await Sender.
IqError(Id, From, To, P.Key);
394 Handlers = this.iqGetHandlers;
395 Counter =
"XMPP.Component." + this.subdomain +
".Get";
399 if (this.responses.TryGet(From.
Address, Id,
true, out Response, out Created))
403 KeyValuePair<string, bool> P = await Response.GetResponse();
406 return await Sender.
IqResult(Id, From, To, P.Key);
408 return await Sender.
IqError(Id, From, To, P.Key);
412 Handlers = this.iqSetHandlers;
413 Counter =
"XMPP.Component." + this.subdomain +
".Set";
418 return await this.server.ProcessResponse(Type, Id, To, From, Language,
true,
false,
Stanza, Sender);
424 return !(await Sender.IqErrorBadRequest(Id, From, To,
"Invalid type.",
"en") is
null);
429 lock (this.synchObject)
433 if (!(N is XmlElement E))
436 Key = E.LocalName +
" " + E.NamespaceURI;
437 if (Handlers.TryGetValue(Key, out h))
439 Counter +=
"." + E.LocalName;
452 Response.SetResult(
string.Empty,
false);
457 string ErrorXml = await Sender.
IqError(Id, From, To,
"cancel",
"<feature-not-implemented xmlns='" +
XmppServer.
StanzaNamespace +
"'/>",
string.Empty,
string.Empty);
458 Response.SetResult(ErrorXml,
false);
459 return !(ErrorXml is
null);
473 Response.SetResult(
string.Empty,
false);
478 string ErrorXml = await Sender.
IqError(Id, From, To, ex);
479 Response.SetResult(ErrorXml,
false);
480 return !(ErrorXml is
null);
497 return this.
IQ(Type, Id, To, From, Language,
XmppServer.ToStanza(
"iq", Type, Id, To, From, Language, ContentXml), Sender);
513 EventHandlerAsync<MessageEventArgs> h =
null;
514 string Counter =
"XMPP.Component." + this.subdomain +
".Message";
517 lock (this.synchObject)
521 if (!(N is XmlElement E))
524 Key = E.LocalName +
" " + E.NamespaceURI;
525 if (this.messageHandlers.TryGetValue(Key, out h))
527 Counter +=
"." + E.LocalName;
548 if (!(Sender is
null))
549 await (Sender?.
MessageError(Id, From, To, ex) ?? Task.CompletedTask);
561 return Task.CompletedTask;
576 return this.
Message(Type, Id, To, From, Language,
XmppServer.ToStanza(
"message", Type, Id, To, From, Language, ContentXml), Sender);
607 EventHandlerAsync<PresenceEventArgs> h =
null;
608 string Counter =
"XMPP.Component." + this.subdomain +
".Presence";
611 lock (this.synchObject)
615 if (!(N is XmlElement E))
618 Key = E.LocalName +
" " + E.NamespaceURI;
619 if (this.presenceHandlers.TryGetValue(Key, out h))
621 Counter +=
"." + E.LocalName;
642 if (!(Sender is
null))
643 await (Sender?.
PresenceError(Id, From, To, ex) ?? Task.CompletedTask);
655 return Task.CompletedTask;
670 return this.
Presence(Type, Id, To, From, Language,
XmppServer.ToStanza(
"presence", Type, Id, To, From, Language, ContentXml), Sender);
683 string s = Xml.OuterXml;
684 int i = s.IndexOf(
'>');
685 int j = s.LastIndexOf(
'<');
690 return s.Substring(i + 1, j - i - 1);
695 #region Discovery XEP-0030
704 XmlElement E = e.
Query;
713 if (!
string.IsNullOrEmpty(Node) && !await this.
SupportsNode(Node))
719 StringBuilder Xml =
new StringBuilder();
721 Xml.Append(
"<query xmlns='");
727 if (
string.IsNullOrEmpty(e.
To.
Account) &&
string.IsNullOrEmpty(Node))
729 lock (this.synchObject)
731 foreach (
string Feature
in this.features.Keys)
733 Xml.Append(
"<feature var='");
742 Xml.Append(
"</query>");
754 return Task.FromResult(
false);
764 return Task.FromResult(
false);
774 XmlElement E = e.
Query;
783 if (!
string.IsNullOrEmpty(Node) && !await this.
SupportsNode(Node))
789 StringBuilder Xml =
new StringBuilder();
791 Xml.Append(
"<query xmlns='");
797 Xml.Append(
"</query>");
810 return Task.CompletedTask;
821 return Task.CompletedTask;
832 return Task.CompletedTask;
849 return Task.FromResult(
string.Empty);
862 return Task.FromResult(
true);
875 return Task.FromResult(
true);
888 return Task.FromResult(
true);
901 return Task.FromResult(
true);
915 return Task.FromResult(
true);
929 return Task.FromResult(
true);
942 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
Associates a specific IQ request with an IQ response.
async Task< KeyValuePair< string, bool > > GetResponse()
Gets the response of the IQ request.
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.
Static class managing persistent counters.
static Task< long > IncrementCounter(CaseInsensitiveString Key)
Increments a counter.
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.